Criar relatórios em PDF a partir de HTML
[How to] – Criar relatórios em PDF a partir de HTML
Acredito que seja consenso o fato de que "montar um relatório usando HTML, CSS e JavaScript é muito mais...
Acredito que seja consenso o fato de que "montar um relatório usando HTML, CSS e JavaScript é muito mais fácil do que um em PDF (usando iText, Crystal Reports entre outros)". E isso por diversos motivos, e citarei apenas um: é muito mais provável que o cliente ou webdesigner não saiba como fazer um relatório em Java, mas tenha maestria com HTML, CSS e JS. Todavia o formato PDF é amplamente requisitado, então acaba-se fazendo o relatório da maneira mais complexa e custosa – em tempo e depois em manutenção.
Podem até dizer que existem APIs em Java e outras linguagens que convertem o HTML em PDF, mas muitas vezes ela falha por completo, ou tem de se fazer algum "ajustezinho", ou "soluções mirabolantes" para tratar de um caso, com outra para outro caso, então o layout muda, e essa "solução" já não serve mais. Por exemplo, se as larguras das colunas de uma tabela estiverem em uma tag HTML col – o que é comum quando o HTML é gerado via alguma ferramenta, como Pentaho Report Designer – o com.itextpdf.tool.xml.iText.XMLWorkerHelper se perde na transformação HTML -> PDF, pois ele remove todas as tags de conteúdo antes de gerar o PDF, desformatando a tabela por consequência.
E como fazer para explicar ao cliente que não dá para gerar o relatório do jeito que ele quer, sendo que ao clicar em "Imprimir como PDF" no browser fica como ele deseja?
Bom, se o browser consegue, por que nós não (e de forma simples)? E se usássemos essa funcionalidade do browser a nosso favor? Mas tem como? O servidor nem tem interface gráfica… e agora?
Simples! Use um browser headless! Parece estranho, ou inusitado, não?
Dado que o relatório já existe em HTML (ou não há uma API bem desenvolvida e simples para gerar o PDF), o browser headless entra em cena.
O PhantomJS, que é idêntico a um browser comum, mas não tem saída em tela, é muito utilizado para testes, tem base em JavaScript, é gratuito e open source, perfeito para o que queremos. E por ser usado para testes, era de se esperar que ele tivesse alguma funcionalidade nativa de imprimir a "tela" para verificar se os testes estão certos, ou "debugar". Felizmente essa expectativa não é frustrada, e o PhantomJS de fato tem a funcionalidade nativa de imprimir o que estaria na tela, caso ele usasse interface gráfica, como PDF!
Se o browser pedir ao servidor o relatório em HTML, tudo segue normal.
Se o browser pedir ao servidor o relatório em PDF, então o servidor pede ajuda ao PhantomJS, que faz uma requisição ao servidor do relatório em HTML, então o PhantomJS grava o HTML em formato PDF em algum lugar em disco, o servidor lê esse PDF, e retorna para o usuário.
Existe um arquivo que já vem com o PhantomJS que se chama "rasterize.js" e vamos usá-lo. Ele é um código simples em JavaScript que recebe como entrada uma URL, o nome de arquivo de saída, e , opcionalmente, o formato (largura*altura ou tipo, como A4) do PDF e um fator de zoom. Caso o arquivo de saída seja um tipo de imagem, ele grava uma imagem do conteúdo da URL, caso seja PDF, ele faz o mesmo, só que o arquivo será um PDF.
1 binário do PhantomJS fresco
1 JavaScript que gera o output em PDF, como o examples/rasterize.js
1 servidor a gosto
Baixe o PhantomJS de acordo com as configurações da máquina do servidor escolhido.
Mova o bin/phantomjs(.exe) e o examples/rasterize.js para locais estratégicos de fácil acesso ao seu servidor (#Tip: guarde os diretórios em algum arquivo de propriedades de fácil leitura/escrita).
Adicione um serviço GET em seu servidor que
receba uma URL como parâmetro
execute "phantomjs rasterize.js URL UUID.pdf A4"
retorne "application/pdf" com conteúdo os bytes lidos do UUID.pdf.
Faça deploy e sirva PDFs.
Note que a solução funciona sempre que se tenha espaço para escrever o relatório em disco, e saiba onde seu servidor está (seu IP pelo menos, de alguma forma) para poder instalar o PhantomJS nele.
Usamos um UUID para que não se corra o risco de retornar ao clientside o relatório pedido por outro usuário.
Aos mais curiosos, vale a pena abrir o rasterize.js e customizá-lo, o que é simples, pois é tudo JavaScript.
Curtiu? Tem uma solução melhor? Essa solução não cabe ao seu ambiente? Dificuldades em colocar header e footer? Deixe seu comentário!
HTMLPDFPhantomJSrasterizeRelatórioReport
Usando a Fluxbox
Java Applet e suas evoluções
Gustavo Ehrhardt30 de outubro de 2013
Agora um dúvida: Você já chegou a usar esse esquema de renderização PDF passando somente um bloco de código HTML e não uma URL?
Tomás Silva Queiroga30 de outubro de 2013
Pode-se fazer isso de pelo menos duas maneiras.
A primeira maneira é salvar este conteudo HTML em disco, e carregá-lo via PhantomJS (Veja este link https://gist.github.com/crazy4groovy/3160121).
A segunda maneira é, ao invés de "page.open(….)", fazer "page.content = 'seu html'" passando seu HTML por parâmetro ao seu script, e renderiza normalmente (Como é feito aqui https://github.com/ariya/phantomjs/blob/master/examples/colorwheel.js).
Valews Tomas.
Mais uma dúvida: Sabe se é possível fazer essa renderização somente em memória, sem a necessidade de gerar o PDF em um arquivo no HD?
Gustavo14 de novembro de 2013
Os documentos ficam perfeitos porém demora 1,5 s para gerar um PDF de 15 páginas. Quem pretende criar PDF's em lote se torna inviável.
George14 de março de 2014
Cara… você usa python, né? Será que essa solução se adapta para Java? hehehe
Tomás Silva Queiroga17 de março de 2014
Sim, pois não há nada que seja exclusivo do python nas linhas de código descritas.
Fábio28 de outubro de 2014
Eu ando à procura de uma solução deste tipo. Contudo para a construção do meu site apenas estou usando html5, css3 e javascript, ou seja, tecnologia do lado do cliente. assim, gostaria de saber se esta técnica funciona sem usar linguagens que atuem do lado do servidor.
Caso isto só funcione com linguagens que trabalhem do lado do servidor, gostaria de saber tudo o que é preciso para pôr em prática este método, visto que só estou a utilizar e só sei utilizar tecnologias do lado do cliente. obrigado pela atenção…
tomas.queiroga28 de outubro de 2014
Esta técnica funciona usando linguagens que atuem do lado do servidor, conforme descrito (o exemplo é em python, mas, conforme já dito, é simples funcionar em Java ou em outra linguagem qualquer).
E existem bibliotecas em JavaScript que fazem exatamente o que você quer, conseguir gerar um PDF do lado do cliente.
Está é uma biblioteca http://goo.gl/tJRfvA que pode te ajudar a resolver sua questão (não a utilizei ainda).