Converter HTML para PDF usando o iText

Estou postando esta pergunta porque muitos desenvolvedores fazem mais ou menos a mesma pergunta em diferentes formas. Eu próprio responderei a esta pergunta (sou o Fundador/CTO do grupo iText), para que possa ser uma "resposta Wiki"."Se o recurso de "documentação" da pilha Overflow ainda existisse, este teria sido um bom candidato para um tópico de documentação.

o ficheiro de código:

estou a tentar converter o seguinte ficheiro HTML para 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>

num navegador, Este HTML parece isto:

enter image description here

Os problemas que encontrei:

O HTMLWorker não tem em conta o CSS

Quando eu usei HTMLWorker, Eu preciso criar um ImageProvider para evitar um erro que me informa que a imagem não pode ser encontrada. Também preciso criar uma instância StyleSheet para mudar alguns dos 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();   
}
O resultado é o seguinte:

enter image description here

por alguma razão, {[4] } também mostra o conteúdo de a etiqueta <title>. Não sei como evitar isto. O CSS no cabeçalho não é processado de todo, eu tenho que definir todos os estilos no meu código, usando o objeto StyleSheet.

Quando olho para o meu código, vejo que muitos objectos e métodos que estou a usar estão desactualizados.

enter image description here

Por isso, decidi fazer uma actualização para usar o trabalhador XML.


As imagens não são encontradas ao usar o trabalhador XML

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

isto resultou no seguinte PDF:

enter image description here

em vez do Times-Roman, o tipo de letra predefinido Helvetica é usado; isto é típico do iText (eu deveria ter definido um tipo de letra explicitamente no meu HTML). Caso contrário, o CSS parece ser respeitado, mas a imagem está faltando, e eu não recebi uma mensagem de erro.

Com {[[4]}, abriu-se uma excepção, e consegui resolver o problema introduzindo um ImageProvider. Vamos ver se isto funciona para o trabalhador XML.

nem todos os CSS os estilos são suportados no trabalhador XML

Adaptei o meu código assim:
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();
}

O meu código é muito mais longo, mas agora a imagem é renderizada:

enter image description here

a imagem é maior do que quando a fiz usar HTMLWorker o que me diz que o atributo CSS width para a classe poster é tido em conta, mas o atributo float é ignorado. Como é que eu resolvo isto?

a pergunta restante:

A questão resume - se a isto: ficheiro HTML específico que tento converter para PDF. Eu tenho passado por muito trabalho, corrigindo um problema após o outro, mas há um problema específicoque eu não posso resolver: Como faço para que o seguinte respeite CSS que define a posição de um elemento, como float: right?

pergunta adicional:

quando o meu HTML contém elementos de forma( como <input>), esses elementos de forma são ignorados.

Author: Bruno Lowagie, 2017-12-20

1 answers

Porque é que o teu código não funciona?

Como explicado na introdução do HTML ao tutorial PDF, HTMLWorker foi desacreditado há muitos anos. Não se pretendia converter páginas HTML completas. Ele não sabe que uma página HTML tem uma seção <head> e uma <body>; ela apenas analisa todo o conteúdo. Era para processar pequenos excertos de HTML, e você poderia definir estilos usando a classe StyleSheet; O CSS real não era suportado.

Depois veio o trabalhador de XML. O trabalhador XML foi concebido como um framework genérico para processar XML. Como prova de conceito, decidimos escrever um pouco de XHTML para a funcionalidade PDF, mas não suportamos todas as tags HTML. Por exemplo: formulários não foram suportados de todo, e foi muito difícil suportar CSS que é usado para posicionar conteúdo. Os formulários em HTML são muito diferentes dos formulários em PDF. Houve também um desfasamento entre a arquitetura iText e a arquitetura HTML + CSS. Gradualmente, estendemos o trabalhador XML, principalmente com base em pedidos dos clientes, mas XML Trabalhador tornou-se um monstro com muitos tentáculos.

Eventualmente, decidimos reescrever o texto a partir do zero, com os requisitos para a conversão HTML + CSS em mente. Isto resultou em iText 7 . No topo do iText 7, criamos vários add-ons, sendo o mais importante neste contexto pdfHTML .

Como resolver o problema

Usando a última versão do iText (iText 7.1.0 + pdfHTML 2.0.0) o código para converter o HTML da pergunta para PDF é reduzido para este trecho:

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

O resultado é o seguinte:

enter image description here

Como podem ver, este é o resultado esperado. Uma vez que o iText 7.1.0 / pdfHTML 2.0.0, o tipo de letra padrão é Times-Roman. O CSS está a ser respeitado: a imagem está agora a flutuar à direita.

Alguns pensamentos adicionais.

Os desenvolvedores muitas vezes se sentem contrários a atualizar para uma versão mais recente do iText quando eu dou o conselho para atualizar para iText 7 / pdfHTML 2. Permita-me responder a os três primeiros argumentos que ouço:

Eu preciso usar o iText livre, e o iText 7 não é gratuito / o add-on pdfHTML é código fechado.

O IText 7 é lançado utilizando o AGPL, tal como o iText 5 e o XML Worker. O AGPL permite a utilização gratuita no sentido de a título gratuito no contexto de projectos de código aberto. Se você está distribuindo um produto de código fechado / proprietário (por exemplo, você usa iText em um contexto SaaS), você não pode usar iText gratuitamente; nesse caso, você tenho de comprar uma licença comercial. Isso já era verdade para o iText 5; isso ainda é verdade para o iText 7. Quanto às versões anteriores ao iText 5: Você não deve usá-las de todo . Em relação ao pdfHTML: as primeiras versões só estavam disponíveis como software de código fechado. Nós tivemos uma discussão pesada dentro do grupo iText: por um lado, havia as pessoas que queriam evitar o abuso maciço por empresas que não escutam seus desenvolvedores quando esses desenvolvedores dizem os poderes que estão esse código aberto não é o mesmo que livre. Os desenvolvedores estavam nos dizendo que seu chefe os forçou a fazer a coisa errada, e que eles não conseguiram convencer seu chefe a comprar uma licença comercial. Por outro lado, havia as pessoas que argumentavam que não deveríamos punir os desenvolvedores pelo comportamento errado de seus chefes. Eventualmente, as pessoas a favor do pdfHTML open sourcing, ou seja: os desenvolvedores do iText, ganharam o argumento. Por favor, prove que eles não estavam errados, e use iText corretamente: respeite o AGPL se estiver a utilizar iText gratuitamente ; certifique-se que o seu chefe compra uma licença comercial se estiver a usar iText num contexto de código fechado.

preciso de manter um sistema legado, e tenho de usar uma versão antiga do iText.

A sério? A manutenção também envolve a aplicação de atualizações e migração para novas versões do software que você está usando. Como você pode ver, o código necessário ao usar iText 7 e pdfHTML é muito simples, e menos propenso a erros do que o código necessário antes. Um projecto de migração não deve demorar muito.

acabei de começar e não sabia do iText 7; só descobri depois de terminar o meu projeto.

É por isso que estou a colocar esta pergunta e a responder. Pense em si como um programador extremo. Deita fora todo o teu código, e começa de novo. Você vai notar que não é tanto trabalho como você imaginou, e você vai dormir melhor sabendo que você fez o seu projeto à prova de futuro, porque iText 5 é a ser gradualmente eliminado. Nós ainda oferecemos suporte aos clientes pagantes, mas eventualmente, vamos parar de apoiar iText 5 completamente.
 7
Author: Bruno Lowagie, 2017-12-21 16:15:53