Convirtiendo HTML a PDF usando iText
pdf-generation xmlworker (2)
Por qué tu código no funciona
Como se explica en la introducción del
tutorial de HTML a PDF
,
HTMLWorker
ha quedado en desuso hace muchos años.
No estaba destinado a convertir páginas HTML completas.
No sabe que una página HTML tiene una sección
<head>
y una sección
<body>
;
simplemente analiza todo el contenido.
Estaba destinado a analizar pequeños fragmentos de HTML, y podría definir estilos utilizando la clase
StyleSheet
;
CSS real no era compatible.
Luego vino XML Worker. XML Worker fue un marco genérico para analizar XML. Como prueba de concepto, decidimos escribir algunas funciones XHTML a PDF, pero no admitimos todas las etiquetas HTML. Por ejemplo: los formularios no eran compatibles en absoluto, y era muy difícil admitir CSS que se usaba para posicionar contenido. Los formularios en HTML son muy diferentes de los formularios en PDF. También hubo una falta de coincidencia entre la arquitectura iText y la arquitectura de HTML + CSS. Poco a poco, ampliamos XML Worker, principalmente en función de las solicitudes de los clientes, pero XML Worker se convirtió en un monstruo con muchos tentáculos.
Finalmente, decidimos reescribir iText desde cero, teniendo en cuenta los requisitos para la conversión de HTML + CSS. Esto resultó en iText 7 . Además de iText 7, creamos varios complementos, el más importante en este contexto es pdfHTML .
Como resolver el problema
Usando la última versión de iText (iText 7.1.0 + pdfHTML 2.0.0), el código para convertir el HTML de la pregunta a PDF se reduce a este fragmento:
public static final String SRC = "src/main/resources/html/sample.html";
public static final String DEST = "target/results/sample.pdf";
public void createPdf(String src, String dest) throws IOException {
HtmlConverter.convertToPdf(new File(src), new File(dest));
}
El resultado se ve así:
Como puede ver, este es más o menos el resultado que esperaría. Desde iText 7.1.0 / pdfHTML 2.0.0, la fuente predeterminada es Times-Roman. Se respeta el CSS: la imagen ahora está flotando a la derecha.
Algunos pensamientos adicionales.
Los desarrolladores a menudo se oponen a actualizar a una versión más reciente de iText cuando doy el consejo de actualizar a iText 7 / pdfHTML 2. Permítanme responder a los 3 argumentos principales que escucho:
Necesito usar el iText gratuito, y iText 7 no es gratis / el complemento pdfHTML es de código cerrado.
iText 7 se lanza utilizando AGPL, al igual que iText 5 y XML Worker. El AGPL permite el uso gratuito en el sentido de forma gratuita en el contexto de proyectos de código abierto. Si está distribuyendo un producto de código cerrado / propietario (por ejemplo, usa iText en un contexto SaaS), no puede usar iText de forma gratuita; en ese caso, debe comprar una licencia comercial. Esto ya era cierto para iText 5; esto sigue siendo cierto para iText 7. En cuanto a las versiones anteriores a iText 5: no debe usarlas en absoluto . Con respecto a pdfHTML: las primeras versiones solo estaban disponibles como software de código cerrado. Hemos tenido una fuerte discusión dentro de iText Group: por un lado, había personas que querían evitar el abuso masivo por parte de las empresas que no escuchan a sus desarrolladores cuando esos desarrolladores dicen que el código abierto no es el Igual que gratis. Los desarrolladores nos decían que su jefe los obligó a hacer algo incorrecto y que no podían convencer a su jefe de comprar una licencia comercial. Por otro lado, hubo personas que argumentaron que no deberíamos castigar a los desarrolladores por el comportamiento incorrecto de sus jefes. Finalmente, las personas a favor del código abierto pdfHTML, es decir: los desarrolladores de iText, ganaron el argumento. Demuestre que no estaban equivocados y use iText correctamente: respete la AGPL si está usando iText de forma gratuita ; asegúrese de que su jefe compre una licencia comercial si está usando iText en un contexto de código cerrado.
Necesito mantener un sistema heredado y tengo que usar una versión antigua de iText.
¿Seriamente? El mantenimiento también implica la aplicación de actualizaciones y la migración a nuevas versiones del software que está utilizando. Como puede ver, el código necesario cuando se usa iText 7 y pdfHTML es muy simple y menos propenso a errores que el código necesario antes. Un proyecto de migración no debería llevar mucho tiempo.
Acabo de empezar y no sabía sobre iText 7; Solo lo descubrí después de terminar mi proyecto.
Es por eso que estoy publicando esta pregunta y respuesta. Piense en usted como un programador eXtreme. Deseche todo su código y comience de nuevo. Notarás que no es tanto trabajo como imaginaste, y dormirás mejor sabiendo que has hecho que tu proyecto esté preparado para el futuro porque iText 5 se está eliminando gradualmente. Todavía ofrecemos soporte a clientes que pagan, pero eventualmente, dejaremos de soportar iText 5 por completo.
Estoy publicando esta pregunta porque muchos desarrolladores hacen más o menos la misma pregunta en diferentes formas. Contestaré esta pregunta yo mismo (soy el Fundador / CTO de iText Group), para que pueda ser una "respuesta Wiki". Si todavía existiera la función de "documentación" de desbordamiento de pila, esto habría sido un buen candidato para un tema de documentación.
El archivo fuente:
Estoy tratando de convertir el siguiente archivo HTML a PDF:
<html>
<head>
<title>Colossal (movie)</title>
<style>
.poster { width: 120px;float: right; }
.director { font-style: italic; }
.description { font-family: serif; }
.imdb { font-size: 0.8em; }
a { color: red; }
</style>
</head>
<body>
<img src="img/colossal.jpg" class="poster" />
<h1>Colossal (2016)</h1>
<div class="director">Directed by Nacho Vigalondo</div>
<div class="description">Gloria is an out-of-work party girl
forced to leave her life in New York City, and move back home.
When reports surface that a giant creature is destroying Seoul,
she gradually comes to the realization that she is somehow connected
to this phenomenon.
</div>
<div class="imdb">Read more about this movie on
<a href="www.imdb.com/title/tt4680182">IMDB</a>
</div>
</body>
</html>
En un navegador, este HTML se ve así:
Los problemas que encontré:
HTMLWorker no tiene en cuenta CSS en absoluto
Cuando utilicé
HTMLWorker
, necesito crear un
ImageProvider
para evitar un error que me informa que no se puede encontrar la imagen.
También necesito crear una instancia de
StyleSheet
para cambiar algunos de los estilos:
public static class MyImageFactory implements ImageProvider {
public Image getImage(String src, Map<String, String> h,
ChainedProperties cprops, DocListener doc) {
try {
return Image.getInstance(
String.format("resources/html/img/%s",
src.substring(src.lastIndexOf("/") + 1)));
} catch (DocumentException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
public static void main(String[] args) throws IOException, DocumentException {
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream("results/htmlworker.pdf"));
document.open();
StyleSheet styles = new StyleSheet();
styles.loadStyle("imdb", "size", "-3");
HTMLWorker htmlWorker = new HTMLWorker(document, null, styles);
HashMap<String,Object> providers = new HashMap<String, Object>();
providers.put(HTMLWorker.IMG_PROVIDER, new MyImageFactory());
htmlWorker.setProviders(providers);
htmlWorker.parse(new FileReader("resources/html/sample.html"));
document.close();
}
El resultado se ve así:
Por alguna razón,
HTMLWorker
también muestra el contenido de la etiqueta
<title>
.
No sé cómo evitar esto.
El CSS en el encabezado no se analiza en absoluto, tengo que definir todos los estilos en mi código, usando el objeto
StyleSheet
.
Cuando miro mi código, veo que muchos objetos y métodos que estoy usando están en desuso:
Así que decidí actualizar a usar XML Worker.
Las imágenes no se encuentran cuando se utiliza XML Worker
Intenté el siguiente código:
public static final String DEST = "results/xmlworker1.pdf";
public static final String HTML = "resources/html/sample.html";
public void createPdf(String file) throws IOException, DocumentException {
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file));
document.open();
XMLWorkerHelper.getInstance().parseXHtml(writer, document,
new FileInputStream(HTML));
document.close();
}
Esto dio como resultado el siguiente PDF:
En lugar de Times-Roman, se usa la fuente predeterminada Helvetica; Esto es típico para iText (debería haber definido una fuente explícitamente en mi HTML). De lo contrario, parece respetarse el CSS, pero falta la imagen y no recibí un mensaje de error.
Con
HTMLWorker
, se
HTMLWorker
una excepción y pude solucionar el problema introduciendo un
ImageProvider
.
Veamos si esto funciona para XML Worker.
No todos los estilos CSS son compatibles con XML Worker
Adapté mi código así:
public static final String DEST = "results/xmlworker2.pdf";
public static final String HTML = "resources/html/sample.html";
public static final String IMG_PATH = "resources/html/";
public void createPdf(String file) throws IOException, DocumentException {
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(file));
document.open();
CSSResolver cssResolver =
XMLWorkerHelper.getInstance().getDefaultCssResolver(true);
HtmlPipelineContext htmlContext = new HtmlPipelineContext(null);
htmlContext.setTagFactory(Tags.getHtmlTagProcessorFactory());
htmlContext.setImageProvider(new AbstractImageProvider() {
public String getImageRootPath() {
return IMG_PATH;
}
});
PdfWriterPipeline pdf = new PdfWriterPipeline(document, writer);
HtmlPipeline html = new HtmlPipeline(htmlContext, pdf);
CssResolverPipeline css = new CssResolverPipeline(cssResolver, html);
XMLWorker worker = new XMLWorker(css, true);
XMLParser p = new XMLParser(worker);
p.parse(new FileInputStream(HTML));
document.close();
}
Mi código es mucho más largo, pero ahora se muestra la imagen:
La imagen es más grande que cuando la
HTMLWorker
usando
HTMLWorker
que me dice que se tiene en cuenta el
width
atributo CSS para la clase de
poster
, pero se ignora el atributo
float
.
¿Cómo puedo solucionar esto?
La pregunta restante:
Entonces la pregunta se reduce a esto: tengo un archivo HTML
específico
que trato de convertir a PDF.
He trabajado mucho, solucionando un problema tras otro, pero hay un problema
específico
que no puedo resolver: ¿cómo hago que iText respete CSS que define la posición de un elemento, como
float: right
¿
float: right
?
Pregunta adicional
Cuando mi HTML contiene elementos de formulario (como
<input>
), esos elementos de formulario se ignoran.
Use iText 7 y este código:
public void generatePDF(String htmlFile) { try { //HTML String String htmlString = htmlFile; //Setting destination FileOutputStream fileOutputStream = new FileOutputStream(new File(dirPath + "/USER-16-PF-Report.pdf")); PdfWriter pdfWriter = new PdfWriter(fileOutputStream); ConverterProperties converterProperties = new ConverterProperties(); PdfDocument pdfDocument = new PdfDocument(pdfWriter); //For setting the PAGE SIZE pdfDocument.setDefaultPageSize(new PageSize(PageSize.A3)); Document document = HtmlConverter.convertToDocument(htmlFile, pdfDocument, converterProperties); document.close(); } catch (Exception e) { e.printStackTrace(); }
}