Introdução
Ao projetar e desenvolver aplicações web modernas, frequentemente nos deparamos com a necessidade de gerar documentos PDF a partir de conteúdo HTML. Seja para fornecer relatórios, recibos, ou simplesmente para permitir que os usuários salvem informações em um formato portátil e amplamente aceito, os PDFs se mostram um recurso indispensável.
No entanto, ao desenvolver aplicações com Blazor WebAssembly, uma estrutura emergente que oferece uma experiência de programação semelhante a uma aplicação cliente/servidor no navegador, a implementação dessa funcionalidade pode apresentar desafios. A interação entre a linguagem C# utilizada no Blazor e as bibliotecas JavaScript comumente utilizadas para a geração de PDFs, como jsPDF e html2canvas, pode ser complicada.
Neste artigo, vou abordar esses desafios e apresentar uma solução robusta para a geração de PDFs a partir de páginas web utilizando Blazor WebAssembly, jsPDF e html2canvas. Meu objetivo é compartilhar a solução encontrada através da minha experiência, oferecendo um recurso valioso para outros profissionais que possam estar lidando com desafios semelhantes em seus próprios projetos.
Conhecendo as Ferramentas
Blazor WebAssembly
O Blazor WebAssembly é uma estrutura de desenvolvimento de aplicações web desenvolvida pela Microsoft. Essa estrutura permite que os desenvolvedores construam aplicações web interativas utilizando C# e .NET, em vez das tradicionais linguagens de programação de front-end, como JavaScript. Com o Blazor WebAssembly, o código .NET é executado diretamente no navegador, graças à tecnologia WebAssembly, permitindo uma experiência de programação mais unificada e uma maior reutilização do código.
jsPDF
O jsPDF é uma biblioteca JavaScript popular para a geração de arquivos PDF do lado do cliente. É uma ferramenta eficiente e flexível que pode transformar dados de várias fontes, incluindo HTML, em documentos PDF formatados. Com uma API simples e extensa, os desenvolvedores podem usar jsPDF para criar documentos PDF complexos e personalizados diretamente no navegador do usuário.
html2canvas
html2canvas é outra biblioteca JavaScript, projetada para capturar screenshots de páginas web ou partes delas, diretamente no navegador do usuário. O html2canvas funciona "pintando" o conteúdo HTML em um elemento canvas, que então pode ser transformado em uma imagem estática em vários formatos, incluindo PNG. Isso o torna uma ferramenta valiosa quando precisamos converter conteúdo HTML em uma imagem para, por exemplo, incluí-la em um documento PDF.
Em conjunto, essas três ferramentas podem ser usadas para criar uma solução eficiente para a geração de PDFs a partir de páginas web em uma aplicação Blazor WebAssembly. No entanto, a interação entre elas pode ser complexa, e é isso que vamos explorar no próximo tópico.
Preparando o Ambiente
Configurando um Projeto Blazor
Para começar a trabalhar com o Blazor WebAssembly, a primeira coisa que você precisa fazer é configurar um novo projeto. Você pode fazer isso utilizando a linha de comando ou uma IDE que suporte o desenvolvimento .NET, como o Visual Studio. Aqui estão os passos básicos:
- Primeiramente, abra o Visual Studio, crie um novo projeto Blazor. No Visual Studio, você pode fazer isso selecionando "Arquivo -> Novo -> Projeto", depois escolha "Blazor App" e siga o assistente para criar um novo projeto.
2. Em seguida, precisamos adicionar as bibliotecas jsPDF e html2canvas ao nosso projeto. Vamos fazer isso adicionando-os ao nosso arquivo `index.html`. Abra o arquivo `index.html` em `wwwroot` e adicione os seguintes scripts antes do fechamento da tag `</body>`:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.debug.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.0.0-rc.7/html2canvas.min.js"></script>
3. A próxima etapa é criar a função `loadScript` que será responsável por carregar dinamicamente nossos scripts. Adicione o seguinte script logo abaixo dos scripts jsPDF e html2canvas:
<script type="text/javascript">
// A função loadScript recebe dois parâmetros:
// - url: uma string que representa o URL do script que queremos carregar.
// - callback: uma função que será chamada depois que o script for carregado.
function loadScript(url, callback) {
// Criamos um novo elemento de script
var script = document.createElement("script");
// Definimos o tipo do elemento script para "text/javascript"
script.type = "text/javascript";
// Se a propriedade readyState do script existe, significa que estamos
// em um ambiente IE (Internet Explorer)
if (script.readyState) {
// Neste caso, adicionamos uma função de ouvinte ao evento onreadystatechange do script.
// Este evento é acionado sempre que o estado de carregamento do script muda
script.onreadystatechange = function () {
// Se o estado do script é 'loaded' ou 'complete', isso significa que o script foi
// totalmente carregado
if (script.readyState == "loaded" || script.readyState == "complete") {
// Neste ponto, podemos desativar o ouvinte do evento, porque o script foi carregado
script.onreadystatechange = null;
// E então chamamos a função callback, que foi passada como parâmetro
callback();
}
};
} else { // Caso contrário, estamos em um ambiente que não é o IE (como Chrome, Firefox, etc.)
// Nesses ambientes, podemos simplesmente ouvir o evento onload, que é acionado quando
// o script termina de carregar
script.onload = function () {
// Quando o script termina de carregar, chamamos a função callback
callback();
};
}
// Definimos o URL do script para o URL que foi passado como parâmetro
script.src = url;
// Finalmente, anexamos o elemento de script à tag <head> do documento
// Isso faz com que o script seja carregado pelo navegador
document.getElementsByTagName("head")[0].appendChild(script);
}
</script>
4. Em seguida, iremos adicionar um script personalizado, que irá ajudar no carregamento dessas bibliotecas e também na geração do PDF. Este script será colocado logo após as tags de script do jsPDF e html2canvas.
<script type="text/javascript">
// Função para carregar um script de forma assíncrona
function loadScript(url, callback) {
var script = document.createElement("script")
script.type = "text/javascript";
if (script.readyState) { // Para Internet Explorer
script.onreadystatechange = function () {
if (script.readyState == "loaded" || script.readyState == "complete") {
script.onreadystatechange = null;
callback();
}
};
} else { // Para outros navegadores
script.onload = function () {
callback();
};
}
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
// Variáveis globais para verificar se as bibliotecas estão prontas
window.jspdfReady = false;
window.html2canvasReady = false;
// Carrega html2canvas e define html2canvasReady como true quando concluído
loadScript('https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js', function () {
window.html2canvasReady = true;
});
// Função para aguardar jsPDF ficar pronto
window.waitForJsPdf = function () {
return new Promise(function (resolve) {
var checkReady = function () {
if (window.jspdfReady) {
resolve();
} else {
setTimeout(checkReady, 100);
}
};
checkReady();
});
};
// Carrega jsPDF, define jspdfReady como true quando concluído e define a função createPdf
loadScript('https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.debug.js', function () {
console.log("jspdf loaded");
window.jspdfReady = true;
// Função para gerar o PDF
window.createPdf = function () {
if (!window.jspdfReady || !window.html2canvasReady) return;
html2canvas(document.body).then(function (canvas) {
var imgData = canvas.toDataURL('image/png');
var pdf = new jsPDF('p', 'mm', 'a4');
var pageWidth = 210;
var imgWidth = pageWidth;
var imgHeight = (canvas.height * imgWidth) / canvas.width;
var heightLeft = imgHeight;
var position = 0;
var doc = new jsPDF('p', 'mm');
doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageWidth;
while (heightLeft >= 0) {
position = heightLeft - imgHeight;
doc.addPage();
doc.addImage(imgData, 'PNG', 0, position, imgWidth, imgHeight);
heightLeft -= pageWidth;
}
doc.save('boleto.pdf');
});
};
});
// Função para verificar se as bibliotecas estão prontas
window.areLibrariesReady = function () {
return typeof window.jspdfReady !== 'undefined' && window.jspdfReady === true &&
typeof window.html2canvasReady !== 'undefined' && window.html2canvasReady === true;
};
</script>
Esse código acima faz várias coisas. Primeiramente, define uma função loadScript para carregar os scripts de maneira assíncrona. Em seguida, carrega as bibliotecas html2canvas e jsPDF de maneira assíncrona. Além disso, define funções waitForJsPdf e createPdf no objeto global window, que podem ser chamadas de código C#.
A função createPdf captura uma imagem da página atual usando html2canvas, então cria um novo documento PDF usando jsPDF, adiciona a imagem ao documento e salva o documento com o nome 'boleto.pdf'.
O método areLibrariesReady é usado para verificar se ambas as bibliotecas, html2canvas e jsPDF, foram carregadas com sucesso. Este método será utilizado em nosso código Blazor, para evitar tentar gerar o PDF antes que as bibliotecas necessárias tenham sido carregadas.
Integrando Blazor WebAssembly com jsPDF e html2canvas
Agora que o nosso ambiente está preparado e os scripts necessários estão sendo carregados, precisamos escrever o código que irá interagir com esses scripts a partir do nosso código Blazor.
A interação entre o código Blazor e os scripts JavaScript é feita através do JavaScript Interop, que é uma funcionalidade do Blazor que nos permite chamar funções JavaScript a partir de código C# e vice-versa.
O primeiro passo é criar um serviço em Blazor que irá encapsular a lógica para gerar o PDF. Para isso, crie uma nova classe no seu projeto e nomeie como quiser, por exemplo, "PdfService". Nesta classe, adicione um construtor que receba uma instância de IJSRuntime, que é a interface que permite a interação com o JavaScript.
public class PdfService
{
private readonly IJSRuntime _jsRuntime;
public PdfService(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
}
Agora, vamos adicionar um método neste serviço para gerar o PDF. Este método vai chamar as funções JavaScript que adicionamos no nosso arquivo HTML.
public class PdfService
{
private readonly IJSRuntime _jsRuntime;
public PdfService(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
public async Task CreatePdfFromCurrentPageWithHtml2Canvas()
{
await _jsRuntime.InvokeVoidAsync("waitForJsPdf");
var areLibrariesReady = await _jsRuntime.InvokeAsync<bool>("areLibrariesReady");
if (areLibrariesReady)
{
await _jsRuntime.InvokeVoidAsync("createPdfFromCurrentPageWithHtml2Canvas");
}
}
}
No método `CreatePdfFromCurrentPageWithHtml2Canvas` estamos fazendo três chamadas para funções JavaScript:
- "waitForJsPdf": aguarda até que a biblioteca jsPDF esteja pronta.
- "areLibrariesReady": verifica se as bibliotecas jsPDF e html2canvas estão prontas.
- "createPdfFromCurrentPageWithHtml2Canvas": inicia a geração do PDF.
Para usar esse serviço, precisamos registrá-lo na injeção de dependências do Blazor. Isso é feito no arquivo `Startup.cs` (ou `Program.cs` em versões mais recentes do .NET). Adicione a seguinte linha no método `ConfigureServices`:
services.AddScoped<PdfService>();
Agora o serviço está pronto para ser usado. Basta injetá-lo em qualquer componente Blazor e chamar o método `CreatePdfFromCurrentPageWithHtml2Canvas` para gerar o PDF.
Segue um exemplo de uma implementação em um componente do tipo Page:
@page "/mypage"
@inject PdfService PdfService
<button @onclick="CreatePdf">Gerar PDF</button>
@code {
private async Task CreatePdf()
{
await PdfService.CreatePdfFromCurrentPageWithHtml2Canvas();
}
}
No código acima, primeiro estamos usando a diretiva @inject para injetar uma instância do nosso PdfService no componente. Em seguida, temos um botão HTML cujo evento onclick está vinculado a um método chamado CreatePdf.
Na seção @code, definimos o método CreatePdf que é assíncrono (porque a geração do PDF é uma operação assíncrona). Dentro desse método, chamamos o método CreatePdfFromCurrentPageWithHtml2Canvas do nosso serviço, que é responsável por iniciar a geração do PDF.
Ao clicar no botão "Gerar PDF", o método CreatePdf será chamado, iniciando a geração do PDF da página atual.
Descrição dos Problemas Encontrados e como foram Solucionados
Durante o desenvolvimento desta solução, deparei-me com alguns desafios. Os principais problemas encontrados e as respectivas soluções são descritos a seguir:
a. Carregamento das bibliotecas JavaScript: No início, tive problemas com o carregamento das bibliotecas jsPDF e html2canvas. Descobri que as bibliotecas não estavam sendo carregadas corretamente porque o Blazor WebAssembly não aguarda que as bibliotecas JavaScript sejam carregadas antes de renderizar a página. Para resolver este problema, criei uma função JavaScript `waitForJsPdf` que retorna uma `Promise`. Esta `Promise` só é resolvida quando a biblioteca jsPDF termina de carregar. Assim, no lado do Blazor, posso aguardar essa `Promise` antes de tentar usar a biblioteca jsPDF, garantindo assim que ela esteja carregada.
b. Dimensionamento da Imagem: O próximo desafio que encontrei foi ajustar a largura da imagem ao tamanho da página do PDF. Ao gerar o PDF, a imagem capturada da página estava muito grande, ultrapassando os limites da página do PDF. Para corrigir isso, defini a largura da imagem para a mesma largura da página do PDF e ajustei a altura proporcionalmente para manter a relação de aspecto da imagem. Isso garantiu que a imagem se ajustasse perfeitamente à página do PDF.
Estes foram apenas alguns dos desafios enfrentados. Cada problema me levou a um maior entendimento das tecnologias envolvidas e contribuiu para a melhoria final da solução.
Conclusão
Ao longo deste artigo, pude compartilhar minha experiência prática na geração de PDFs a partir de uma página web usando Blazor WebAssembly, jsPDF e html2canvas. Esse processo incluiu desde a configuração inicial do ambiente até a resolução de problemas desafiadores que surgiram durante a implementação.
A capacidade de gerar PDFs a partir de páginas web tem implicações significativas. Isso permite que os desenvolvedores criem uma versão de documento portátil de quase qualquer conteúdo da web. Isso pode ser útil para fornecer recibos digitais, gerar relatórios ou criar cópias arquivadas de conteúdo da web.
O processo não foi sem desafios. O carregamento das bibliotecas JavaScript, o ajuste do tamanho da imagem ao tamanho da página do PDF, e garantir que todo o conteúdo da página fosse capturado, foram todos desafios que eu encontrei e superei.
Espero que minha experiência e os insights que compartilhei possam ser úteis para outros desenvolvedores que enfrentam desafios semelhantes. Estou interessado em ouvir suas experiências, então, se você tem algo para compartilhar ou perguntas para fazer, por favor, não hesite em deixar um comentário abaixo.
Obrigado por dedicar seu tempo para ler este artigo.
Referências
- Documentação Oficial do Blazor: https://docs.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-5.0
- Documentação Oficial do jsPDF: https://rawgit.com/MrRio/jsPDF/master/docs/
- Documentação Oficial do html2canvas: https://html2canvas.hertzen.com/documentation
- Artigo sobre a Interoperabilidade JavaScript em Blazor: https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-5.0
- Artigo sobre Criação de PDFs em JavaScript: https://ourcodeworld.com/articles/read/189/how-to-create-a-screenshot-of-your-website-with-javascript-using-html2canvas
Artigo sobre a Biblioteca HTML2Canvas: https://html2canvas.hertzen.com/features
Comments