web-dev-qa-db-fra.com

Conversion de HTML en PDF en utilisant iText

Je poste cette question car de nombreux développeurs posent plus ou moins la même question sous différentes formes. Je vais répondre moi-même à cette question (je suis le fondateur/directeur technique du groupe iText), afin qu'il puisse s'agir d'une "réponse Wiki". Si la fonctionnalité de documentation de Stack Overflow existait toujours, cela aurait été un bon candidat pour une rubrique de documentation.

Le fichier source:

J'essaie de convertir le fichier HTML suivant en 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>

Dans un navigateur, ce code HTML ressemble à ceci:

enter image description here

Les problèmes que j'ai rencontrés:

HTMLWorker ne prend pas du tout en compte CSS

Lorsque j'ai utilisé HTMLWorker, je dois créer un ImageProvider pour éviter une erreur qui m'informe que l'image est introuvable. J'ai également besoin de créer une instance StyleSheet pour changer certains des styles:

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();   
}

Le résultat ressemble à ceci:

enter image description here

Pour une raison quelconque, HTMLWorker affiche également le contenu de la balise <title>. Je ne sais pas comment éviter cela. Le CSS dans l'en-tête n'est pas analysé du tout, je dois définir tous les styles dans mon code, en utilisant l'objet StyleSheet.

Quand je regarde mon code, je vois que beaucoup d'objets et de méthodes que j'utilise sont obsolètes:

enter image description here

J'ai donc décidé de passer à l'utilisation de XML Worker.


Les images ne sont pas trouvées lors de l'utilisation de XML Worker

J'ai essayé le code suivant:

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();
}

Cela a abouti au PDF suivant:

enter image description here

Au lieu de Times-Roman, la police par défaut Helvetica est utilisée; c'est typique pour iText (j'aurais dû définir une police explicitement dans mon HTML). Sinon, le CSS semble être respecté, mais l'image manque, et je n'ai pas reçu de message d'erreur.

Avec HTMLWorker, une exception a été levée et j'ai pu résoudre le problème en introduisant un ImageProvider. Voyons si cela fonctionne pour XML Worker.

Tous les styles CSS ne sont pas pris en charge dans XML Worker

J'ai adapté mon code comme ceci:

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();
}

Mon code est beaucoup plus long, mais maintenant l'image est rendue:

enter image description here

L'image est plus grande que lorsque je l'ai rendue en utilisant HTMLWorker ce qui me dit que l'attribut CSS width pour la classe poster est pris en compte, mais l'attribut float est ignoré . Comment puis-je réparer ça?

La dernière question:

La question se résume donc à ceci: j'ai un fichier HTML spécifique que j'essaie de convertir en PDF. J'ai fait beaucoup de travail, en résolvant un problème après l'autre, mais il y a un spécifique problème que je ne peux pas résoudre: comment faire pour que iText respecte le CSS qui définit la position de un élément, tel que float: right?

Question supplémentaire:

Lorsque mon HTML contient des éléments de formulaire (tels que <input>), Ces éléments de formulaire sont ignorés.

10
Bruno Lowagie

Pourquoi votre code ne fonctionne pas

Comme expliqué dans l'introduction du HTML to PDF tutorial , HTMLWorker a été déconseillé il y a de nombreuses années. Il n'était pas destiné à convertir le HTML complet Il ne sait pas qu'une page HTML a un <head> et un <body> section; il analyse simplement tout le contenu. Il était destiné à analyser de petits extraits HTML, et vous pouviez définir des styles en utilisant la classe StyleSheet; le vrai CSS n'était pas pris en charge.

Puis vint XML Worker. XML Worker était conçu comme un cadre générique pour analyser XML. Comme preuve de concept, nous avons décidé d'écrire du XHTML dans PDF fonctionnalité, mais nous ne prenions pas en charge toutes les balises HTML. Par exemple: les formulaires n'étaient pas pris en charge du tout, et était très difficile à prendre en charge CSS qui est utilisé pour positionner le contenu. Les formulaires en HTML sont très différents des formulaires en PDF. Il y avait aussi un décalage entre l'architecture iText et l'architecture de HTML + CSS. Progressivement, nous avons étendu XML Worker, principalement basé sur à la demande des clients, mais XML Worker est devenu un monstre avec de nombreux tentacules.

Finalement, nous avons décidé de réécrire iText à partir de zéro, avec les exigences de conversion HTML + CSS à l'esprit. Cela a abouti à iText 7 . En plus d'iText 7, nous avons créé plusieurs modules complémentaires, le plus important dans ce contexte étant pdfHTML .

Comment résoudre le problème

En utilisant la dernière version d'iText (iText 7.1.0 + pdfHTML 2.0.0), le code pour convertir le HTML de la question en PDF est réduit à cet extrait:

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));
}

Le résultat ressemble à ceci:

enter image description here

Comme vous pouvez le voir, c'est à peu près le résultat que vous attendez. Depuis iText 7.1.0/pdfHTML 2.0.0, la police par défaut est Times-Roman. Le CSS est respecté: l'image flotte maintenant à droite.

Quelques réflexions supplémentaires.

Les développeurs se sentent souvent opposés à la mise à niveau vers une version plus récente d'iText lorsque je donne les conseils pour mettre à niveau vers iText 7/pdfHTML 2. Permettez-moi de répondre aux 3 principaux arguments que j'entends:

J'ai besoin d'utiliser l'iText gratuit, et l'iText 7 n'est pas gratuit/le module complémentaire pdfHTML est une source fermée.

iText 7 est publié à l'aide de l'AGPL, tout comme iText 5 et XML Worker. L'AGPL permet une utilisation gratuite au sens de gratuitement dans le contexte des projets open source. Si vous distribuez un produit fermé/propriétaire (par exemple, vous utilisez iText dans un contexte SaaS), vous ne pouvez pas utiliser iText gratuitement; dans ce cas, vous devez acheter une licence commerciale . Cela était déjà vrai pour iText 5; c'est toujours vrai pour iText 7. Comme pour les versions antérieures à iText 5: vous ne devriez pas du tout les utiliser . Concernant pdfHTML: les premières versions n'étaient en effet que des disponible en tant que logiciel open source. Nous avons eu de nombreuses discussions au sein d'iText Group: d'une part, il y avait les gens qui voulaient éviter les abus massifs des entreprises qui n'écoutent pas leurs développeurs lorsque ces derniers disent l'open source n'est pas la même chose que la gratuité. Les développeurs nous disaient que leur patron les avait forcés à faire la mauvaise chose, et qu'ils ne pouvaient pas convaincre leur patron d'acheter une licence commerciale. D'un autre côté, il y avait les gens qui a fait valoir que nous ne devrions pas punir les développeurs pour le mauvais comportement de leurs patrons. Finalement, les gens i n faveur de l'open sourcing pdfHTML, c'est-à-dire que les développeurs d'iText ont remporté l'argument. Veuillez prouver qu'ils ne se sont pas trompés et utiliser iText correctement: respectez l'AGPL si vous utilisez iText gratuitement ; assurez-vous que votre patron achète une licence commerciale si vous utilisez iText dans un contexte de source fermée.

J'ai besoin de maintenir un système hérité et je dois utiliser une ancienne version iText.

Sérieusement? La maintenance implique également l'application de mises à niveau et la migration vers de nouvelles versions du logiciel que vous utilisez. Comme vous pouvez le voir, le code nécessaire lors de l'utilisation d'iText 7 et de pdfHTML est très simple et moins sujet aux erreurs que le code nécessaire auparavant. Un projet de migration ne devrait pas prendre trop de temps.

Je viens juste de commencer et je ne connaissais pas iText 7; Je ne l'ai découvert qu'après avoir terminé mon projet.

C'est pourquoi je poste cette question et cette réponse. Considérez-vous comme un programmeur eXtreme. Jetez tout votre code et recommencez. Vous remarquerez que ce n'est pas autant de travail que vous l'imaginiez, et vous dormirez mieux en sachant que vous avez rendu votre projet pérenne car iText 5 est en cours de suppression. Nous offrons toujours une assistance aux clients payants, mais à terme, nous cesserons complètement de prendre en charge iText 5.

13
Bruno Lowagie

tilisez iText 7 et ce code:

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();
    }

}

0
Abhishek Sengupta