Convertire HTML in PDF

Quante volte avete dovuto trasformare un file HTML in PDF in un progetto? E’ una di quelle cose che vengono richieste frequentemente in un progetto e allora vediamo quali sono le possibili soluzioni per ottenere questo risultato in Java

iText

iText è una libreria opensource che permette di creare/modificare documenti PDF tramite API Java. Inizialmente sviluppato da Bruno Lowagie e Paulo Soares, il progetto è cresciuto negli anni ed è stato fatto anche un porting in .NET. Inizialmente la libreria era disponibile con la licenza opensource LGPL, mentre l’ultima versione è ora rilasciata sotto la licenza AGPL in aggiunta alla versione commerciale. Per provare a trasformare HTML in PDF ho inserito tre diversi esempi, utili per valutare alcune situazioni in cui ci possiamo trovare ovvero

  1. Semplice : html banale con quasi nessun markup specifico oltre al testo
  2. Standard : diversi tag html, bold, elenchi numerati etc. etc.
  3. Immagini e CSS : html che include immagini e CSS

Nella nostra classe di test che utilizza iText richiameremo quindi dei metodi per la stampa dei PDF dando in input i diversi HTML come riportato di seguito

/**
* Test HTML 2 PDf using iText
*/

public class ITextTest {

    public static void main(String a[]) 
          throws IOException, FileNotFoundException, 
                 DocumentException, Exception {
        pdfTest("https://raw.githubusercontent.com/fpaparoni/"
                + "html2pdf/master/html/simple.html","simple-itext.pdf");
        pdfTest("https://raw.githubusercontent.com/fpaparoni/"
                + "html2pdf/master/html/standard.html","standard-itext.pdf");
        imageCSSPDFTest("https://raw.githubusercontent.com/fpaparoni/"
                + "html2pdf/master/html/css.html",
                        "https://raw.githubusercontent.com/fpaparoni/"
                                + "html2pdf/master/html/",
                        "image-css-itext.pdf");
    }

Il metodo pdfTest è quello che riesce a supportare facilmente la conversione dei primi due esempi. Utilizzando la classe di utility XMLWorkerHelper e dandogli in pasto l’HTML che dobbiamo convertire, iText riesce a creare semplicemente i nostri PDF.

private static void pdfTest(String url,String fileOut) 
  throws DocumentException, FileNotFoundException, IOException, Exception {
    Document document = new Document();
    PdfWriter writer = PdfWriter.getInstance(document, 
       new FileOutputStream(fileOut));
    document.open();
    XMLWorkerHelper.getInstance().parseXHtml(writer, document,
       new ByteArrayInputStream(
          StringUtil.getText(url).getBytes()));
    document.close();
}

Per quanto riguarda invece l’esempio con immagine e CSS il discorso cambia e dobbiamo scavare un pò nell’API di iText. La prima cosa di cui ci dobbiamo occupare è la trasformazione del nostro HTML in un XHTML. Questo può essere fatto utilizzando librerie come JTidy o JSoup. In questo caso abbiamo utilizzato JSoup specificando che vogliamo bonificare il nostro HTML utilizzando una sintassi XML

org.jsoup.nodes.Document d = Jsoup.parse(new URL(url),5000);
d.outputSettings().syntax(
   org.jsoup.nodes.Document.OutputSettings.Syntax.xml);   

Dopo questo dobbiamo utilizzare l’API di iText che prevede una serie di pipeline per gestire i diversi contenuti del nostro file HTML, come potete vedere dal seguente esempio

Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, 
   new FileOutputStream(fileOut));
document.open();
// CSS
CSSResolver cssResolver =
        XMLWorkerHelper.getInstance().getDefaultCssResolver(true);

// HTML
HtmlPipelineContext htmlContext = new HtmlPipelineContext(null);
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
htmlContext.setImageProvider(new CustomImageProvider(baseImageUrl));


// Pipelines
PdfWriterPipeline pdf = new PdfWriterPipeline(document, writer);
HtmlPipeline html = new HtmlPipeline(htmlContext, pdf);
CssResolverPipeline css = new CssResolverPipeline(cssResolver, html);

// XML Worker
XMLWorker worker = new XMLWorker(css, true);
XMLParser p = new XMLParser(worker);
p.parse(new ByteArrayInputStream(d.outerHtml().getBytes()));
document.close();

Il PDF risultante include i CSS e l’immagine ma il font utilizzato non è quello che abbiamo specificato noi e alcune cose sui bordi CSS non vengono rispettate. Per ottenere il risultato uguale all’HTML di partenza dovremmo agire sui font da includere nel PDF e sul CSS, facendo diventare ancora più laboriosa questa conversione.

 

FlyingSaucer

Dopo aver provato iText vale la pena dare un’occhiata a FlyingSaucer che utilizza iText ma cercando di fornire un API più snella, anche perchè lo scopo principale della libreria è diverso da iText in quanto si propone come renderer generico per documenti XML (o XHTML) che utilizzano CSS per layout e formattazione. Uno degli output è appunto il PDF che viene gestito tramite iText in diverse modalità

  1. org.xhtmlrenderer:flying-saucer-pdf – iText 2.x (versione LGPL)
  2. org.xhtmlrenderer:flying-saucer-pdf-itext5 – iText 5.x (versione AGPL)
  3. org.xhtmlrenderer:flying-saucer-pdf-openpdf – OpenPDF (fork di iText 4)

Negli esempi che abbiamo realizzato è stata utilizzata la versione vecchia LGPL, anche per verificare le differenze rispetto alla nuova versione. Procediamo quindi con gli stessi HTML d’esempio realizzando anche in questo caso un primo metodo che riesce a gestire le prime due casistiche

private static void pdfTest(String url,String fileOut) 
       throws FileNotFoundException, DocumentException, IOException {
    ITextRenderer renderer = new ITextRenderer();
    renderer.setDocument(url);
    renderer.layout();

    FileOutputStream fos = new FileOutputStream( fileOut );
    renderer.createPDF( fos );
    fos.close();

    System.out.println( fileOut+" created." );
}

In questo caso facciamo riferimento alla classe ITextRender, alla quale forniamo direttamente l’URL dell’HTML che vogliamo trasformare e abbiamo semplicemente generato il PDF. I file creati sono la fedele conversione dei sorgenti HTML e in più rispetto alla generazione standard con iText (quindi senza scavare troppe nelle feature dell’API) vediamo che il PDF generato ha i paragrafi nell’indice.

Il metodo che genera il terzo HTML è quasi uguale a quello già visto, l’unica cosa che dobbiamo fare, come con iText, è ripulire l’HTML dall’imperfezione in questo caso del tag img (che in HTML non richiede la chiusura).

ITextRenderer renderer = new ITextRenderer();
Document d = Jsoup.parse(new URL(url),5000);
d.outputSettings().syntax(Document.OutputSettings.Syntax.xml);   

renderer.setDocumentFromString(d.outerHtml(),baseUrl);
renderer.layout();

FileOutputStream fos = new FileOutputStream( fileOut );
renderer.createPDF( fos );
fos.close();

System.out.println( fileOut+" created." );

Anche in questo caso abbiamo utilizzato JSoup e utilizzando la medesima API FlyingSaucer il risultato è praticamente uguale all’HTML di partenza (compreso font e bordi). Quindi rispetto alla versione standard di iText questa libreria ci fornisce un migliore supporto sui CSS out-of-box, che sicuramente è ottenibile anche con iText visto che comunque la libreria sottostante è la stessa.

 

PD4ML

PD4ML è un tool per la generazione di PDF che usa come input HTML e CSS, disponibile sia in versione Java che .NET, che è disponibile in versione gratuita e a pagamento (versione PRO che aggiunge alcune funzionalità). La libreria non è molto nota, però c’è da dire che è molto semplice l’utilizzo

FileOutputStream fos = new FileOutputStream(fileOut);
PD4ML pd4ml = new PD4ML();
pd4ml.setPageSize(PD4Constants.A4);
pd4ml.render(url, fos);
fos.close();
System.out.println( fileOut+" created." );

Questo esempio di codice riesce a gestire tutti e tre gli esempi, anche se nel terzo il CSS non è correttamente gestito. Diciamo che non è sicuramente la prima scelta tra le librerie disponibili ma comunque vale la pena valutarla, specialmente se dovessimo pensare all’acquisto di una licenza per un eventuale libreria.

 

Apache FOP

Un altro progetto opensource che permette, tra le altre cose, di convertire HTML in PDF è Apache FOP. Si tratta di un progetto abbastanza datato (rilasciato opensource nel 1999!!!) ma che viene comunque ancora portato avanti visto che la versione 2.2 è di Aprile 2017. Il meccanismo di FOP è quello di gestire i cosiddetti XSL Formatting Objects e convertirli in diversi formati. Per ottenere quello di cui abbiamo bisogno dobbiamo effettuare prima la seguente catena di conversione

HTML -> XML-FO -> PDF

La trasformazione tra HTML e XML-FO la possiamo effettuare in diversi modi, in questo caso utilizzeremo un file XSLT gentilmente offerto da questo articolo di developerWorks (al quale abbiamo apportato qualche modifica). Vediamo quindi come utilizzare le API di Apache FOP per convertire i nostri tre esempi

String xsltfile = "https://raw.githubusercontent.com/fpaparoni/"
		+ "html2pdf/master/html/html2fo.xsl";
File pdffile = new File(fileOut);

FopFactory fopFactory = FopFactory.newInstance(new File(".").toURI());
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
OutputStream out = new java.io.FileOutputStream(pdffile);
out = new java.io.BufferedOutputStream(out);

try {
	Fop fop = fopFactory.newFop(MimeConstants.MIME_PDF, foUserAgent, out);
	TransformerFactory factory = TransformerFactory.newInstance();

	Document d = Jsoup.parse(new URL(url),5000);
	d.outputSettings().syntax(Document.OutputSettings.Syntax.xml);   
	Transformer transformer = factory.newTransformer(
			new StreamSource(new URL(xsltfile).openStream()));
	Source src = new StreamSource(
			new ByteArrayInputStream(d.outerHtml().getBytes()));
	Result res = new SAXResult(fop.getDefaultHandler());
	transformer.transform(src, res);
} finally {
	out.close();
}

Abbiamo creato il solito metodo pdfTest al quale viene passato l’URL del file HTML che deve convertire. Utilizziamo JSoup come già fatto nei precedenti paragrafi e dopo aver inizializzato la classe Transformer con il file XSLT, richiamiamo il metodo transform che si occuperà di prendere l’input HTML, utilizzare XSLT per trasformarlo in XSL-FO ed infine trasformarlo in output PDF.

Il risultato è abbastanza buono, l’immagine non viene gestita ma solo perchè stata definita con un URL relativo e i CSS non hanno qualcosa che li trasforma nell’adeguato markup XSL-FO. Se volessimo gestire anche i CSS dovremmo utilizzare o realizzare qualcosa di simile al progetto CSSToXSLFO che appunto riesce a gestire anche i fogli di stile CSS2 associati ad un file XML.

 

Wrapper vari

Esistono diversi altri progetti che non sono delle vere e proprie librerie indipendenti ma si appoggiano a software di terze parti per realizzare la conversione in PDF a partire dall’HTML. Qui di seguito una lista con quelli più famosi

  1. JODConverter : permette la conversione tra diversi formati utilizzando un installazione locale o remota di OpenOffice o LibreOffice
  2. Java WkHtmlToPdf Wrapper : wrapper Java di wkhtmltopdf, programma che utilizza WebKit per effettuare la trasformazione HTML to PDF
  3. Universal Document Converter: semplice wrapper per l’esecuzione di pandoc, tool che permette la conversione tra moltissimi formati

 

Conclusioni

Convertire un file HTML in PDF non è una scienza esatta, infatti può sempre capitare quel tag maledetto che fa saltare per magia il nostro processo di creazione del PDF risultante. Se abbiamo bisogno di supportare una grande varietà di possibili HTML, la scelta migliore cade sicuramente sui tool non realizzati in Java, come pandoc e wkhtmltopdf. Utilizzare questi tool infatti garantisce una buona percentuale di successi nella conversione, lo svantaggio è che ovviamente non abbiamo una soluzione Java pura e dobbiamo appoggiarci ad un software esterno.

Se invece dobbiamo gestire una casistica meno variegata di quella precedente, allora una buona soluzione sembra essere FlyingSaucer, che utilizza sotto il motore la libreria iText, alla quale aggiunge un buon supporto per la conversione dei CSS.

Di seguito il classico progetto github dove potete trovare tutti gli esempi presentati all’interno dell’articolo

Progetto d'esempio dell'articolo "Convertire HTML in PDF"
https://github.com/fpaparoni/html2pdf
0 forks.
1 stars.
0 open issues.

Recent commits:

Federico Paparoni

Looking for a right "about me"...

3 risposte

  1. DANIELE ha detto:

    ma perchè esempi non funzionanti ?

  2. Alex ha detto:

    Grazie per le belle informazioni.Io sto usando Itext 7 HtmlConverter.convertToPdf ma non mi funziona bene con i margini.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.