Como analisar vários arquivos HTML em um único PDF?

Eu quero usar o iText para converter uma série de arquivos html para PDF.

Por exemplo: se tiver esses arquivos:

  • page1.html
  • page2.html
  • page3.html

Agora quero criar um único arquivo PDF, onde page1.html é a primeira página, page2.html é a segunda página e assim por diante …

Eu sei como converter um único arquivo HTML em PDF, mas não sei como combinar esses diferentes PDFs resultantes dessa operação em um único PDF.

Antes de começarmos: Eu não sou um desenvolvedor de C #, então não posso dar um exemplo em C #. Todos os exemplos do iText que escrevo são escritos em Java. Felizmente, iText e iTextSharp são sempre mantidos em sincronia. No contexto desta questão, você pode ter certeza de que o que funciona para o iText também funcionará para o iTextSharp, mas você terá que fazer pequenas adaptações específicas para o C #. Pelo que ouvi de desenvolvedores C #, isso geralmente não é difícil de conseguir.

Em relação à resposta: há duas respostas e a resposta nº 2 é geralmente melhor que a resposta nº 1, mas estou dando as duas opções porque pode haver casos específicos em que a resposta nº 1 é melhor.

Dados de teste: criei 3 arquivos HTML simples, cada um contendo algumas informações sobre um estado nos EUA:

  • page1.html : Califórnia
  • page2.html : Nova York
  • page3.html : Massachusetts

Vamos usar o XML Worker para analisar esses três arquivos e queremos um único arquivo PDF como resultado.

Resposta 1: consulte ParseMultipleHtmlFiles1 para o exemplo de código completo e multiple_html_pages1.pdf para o PDF resultante.

Você diz que já conseguiu converter um arquivo HTML em um arquivo PDF. Assume-se que você fez assim:

 public byte[] parseHtml(String html) throws DocumentException, IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // step 1 Document document = new Document(); // step 2 PdfWriter writer = PdfWriter.getInstance(document, baos); // step 3 document.open(); // step 4 XMLWorkerHelper.getInstance().parseXHtml(writer, document, new FileInputStream(html)); // step 5 document.close(); // return the bytes of the PDF return baos.toByteArray(); } 

Esta não é a maneira mais eficiente de analisar um arquivo HTML (existem outros exemplos no site), mas é a maneira mais simples.

Como você pode ver, este método analisa um HTML em um arquivo PDF e retorna esse arquivo PDF na forma de um byte[] . Como queremos criar um único PDF, podemos alimentar essa matriz de bytes em uma instância PdfCopy , para que possamos concatenar vários documentos.

Suponha que tenhamos três documentos:

 public static final String[] HTML = { "resources/xml/page1.html", "resources/xml/page2.html", "resources/xml/page3.html" }; 

Podemos fazer um loop sobre esses três documentos, analisá-los um por um para um byte[] , criar uma instância PdfReader com os bytes PDF e adicionar o documento à instância PdfCopy usando o método addDocument() :

 public void createPdf(String file) throws IOException, DocumentException { Document document = new Document(); PdfCopy copy = new PdfCopy(document, new FileOutputStream(file)); document.open(); PdfReader reader; for (String html : HTML) { reader = new PdfReader(parseHtml(html)); copy.addDocument(reader); reader.close(); } document.close(); } 

Isso resolve o seu problema, mas por que eu acho que não é a solução ideal?

Suponha que você precise usar uma fonte especial que precisa ser incorporada. Nesse caso, cada arquivo PDF separado conterá um subconjunto dessa fonte. Arquivos diferentes exigirão subconjuntos de fonts diferentes e o PdfCopy (nem o PdfSmartCopy ) pode mesclar subconjuntos de fonts. Isso pode resultar em um arquivo PDF inchado com muitos subconjuntos de fonte da mesma fonte.

Como resolvemos isso? Isso é explicado na resposta # 2.

Resposta # 2: Veja ParseMultipleHtmlFiles2 para o exemplo de código completo e multiple_html_pages2.pdf para o PDF resultante. Você já vê a diferença no tamanho do arquivo: 4,61 KB versus 5,05 KB (e nem mesmo introduzimos fonts incorporadas).

Nesse caso, não analisamos o HTML em um arquivo PDF como fizemos no método parseHtml() da resposta # 1. Em vez disso, analisamos o HTML em um iText ElementList usando o método parseToElementList() . Este método requer dois String s. Um contendo o código HTML, o outro contendo valores de CSS.

Nós usamos um método utilitário para ler o arquivo HTML em uma String . Quanto ao valor do CSS, poderíamos passar null para parseToElementList() , mas, nesse caso, os estilos padrão serão ignorados. Você notará que a tag

que introduzimos em nosso HTML parecerá completamente diferente se você não passar o default.css fornecido com o XML Worker.

Longa história curta, este é o código:

 public void createPdf(String file) throws IOException, DocumentException { Document document = new Document(); PdfWriter.getInstance(document, new FileOutputStream(file)); document.open(); String css = readCSS(); for (String htmlfile : HTML) { String html = Utilities.readFileToString(htmlfile); ElementList list = XMLWorkerHelper.parseToElementList(html, css); for (Element e : list) { document.add(e); } document.newPage(); } document.close(); } 

Criamos um único Document e uma única instância do PdfWriter . Analisamos os diferentes arquivos HTML em ElementList um por um, e adicionamos todos os elementos ao Document .

Como você quer uma nova página, cada vez que um novo arquivo HTML é analisado, eu introduzi um document.newPage() . Se você remover essa linha, poderá adicionar as três páginas HTML em uma única página (o que não seria possível se você optasse pela resposta 1).