首页 文章

使用iText将HTML转换为PDF

提问于
浏览
8

我发布这个问题是因为许多开发人员以不同的形式提出或多或少相同的问题 . 我将自己回答这个问题(我是iText集团的创始人/首席技术官),因此它可以成为“维基答案” . 如果Stack Overflow“文档”功能仍然存在,那么这将是文档主题的一个很好的候选者 .

源文件:

我想将以下HTML文件转换为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>

在浏览器中,此HTML如下所示:

我遇到的问题:

HTMLWorker doesn't take CSS into account at all

当我使用 HTMLWorker 时,我需要创建一个 ImageProvider 以避免出现错误,通知我无法找到图像 . 我还需要创建一个 StyleSheet 实例来改变一些样式:

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

结果如下:

出于某种原因, HTMLWorker 还显示 <title> 标签的内容 . 我根本没有解析过,我必须使用 StyleSheet 对象在我的代码中定义所有样式 .

当我查看我的代码时,我发现我正在使用的大量对象和方法已被弃用:

所以我决定升级到使用XML Worker .


Images aren't found when using XML Worker

我尝试了以下代码:

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

这导致以下PDF:

而不是Times-Roman,使用默认字体Helvetica;这是iText的典型特征(我应该在我的HTML中明确定义一种字体) . 否则,CSS似乎得到尊重,但图像丢失,我没有收到错误消息 .

使用 HTMLWorker ,抛出异常,我能够通过引入 ImageProvider 来解决问题 . 让我们看看这是否适用于XML Worker .

Not all CSS styles are supported in XML Worker

我改编了这样的代码:

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

我的代码更长,但现在渲染图像:

图像比我使用 HTMLWorker 渲染时更大,这告诉我 poster 类的CSS属性 width 被考虑在内,但 float 属性被忽略 . 我该如何解决?

剩下的问题:

所以问题归结为:我有一个特定的HTML文件,我尝试转换为PDF . 我经历了很多工作,一个接一个地解决了一个问题,但是有一个我无法解决的特定问题:我如何使iText尊重定义元素位置的CSS,例如 float: right

其他问题:

当我的HTML包含表单元素(例如 <input> )时,将忽略这些表单元素 .

1 回答

  • 10

    为什么你的代码不起作用

    正如在HTML to PDF tutorial的介绍中所解释的那样, HTMLWorker 在很多年前就被弃用了 . 它不知道HTML页面有 <head><body> 部分;它只是解析所有内容 . 它旨在解析小的HTML片段,您可以使用 StyleSheet 类定义样式;真正的CSS不受支持 .

    然后是XML Worker . XML Worker是一种解析XML的通用框架 . 作为概念证明,我们决定将一些XHTML编写为PDF功能,但我们不支持所有HTML标记 . 例如:表单根本不受支持,并且很难支持用于定位内容的CSS . HTML中的表单与PDF中的表单非常不同 . iText体系结构与HTML CSS体系结构之间也存在不匹配 . 渐渐地,我们扩展了XML Worker,主要是基于客户的请求,但XML Worker变成了一个有很多触角的怪物 .

    最终,我们决定从头开始重写iText,并考虑到HTML CSS转换的要求 . 这导致iText 7 . 在iText 7之上,我们创建了几个附加组件,在这个上下文中最重要的是pdfHTML .

    如何解决问题

    使用最新版本的iText(iText 7.1.0 pdfHTML 2.0.0),将HTML从问题转换为PDF的代码简化为以下代码段:

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

    结果如下:

    如您所见,这几乎是您所期望的结果 . 自iText 7.1.0 / pdfHTML 2.0.0起,默认字体为Times-Roman . CSS正在受到尊重:图像现在浮动在右侧 .

    一些额外的想法 .

    当我提出升级到iText 7 / pdfHTML 2的建议时,开发人员常常反对升级到更新的iText版本 . 请允许我回答我听到的前三个论点:

    我需要使用免费的iText,而iText 7不是免费的/ pdfHTML插件是封闭源码 .

    iText 7使用AGPL发布,就像iText 5和XML Worker一样 . AGPL允许在开源项目的环境中免费使用 . 如果您要分发封闭源/专有产品(例如,您在SaaS环境中使用iText),则不能免费使用iText;在这种情况下,您必须购买商业许可证 . 对于iText 5来说,情况已经如此;这对于iText 7仍然如此 . 对于iText 5之前的版本:you shouldn't use these at all . 关于pdfHTML:第一个版本确实只能作为闭源软件使用 . 我们在iText集团内部进行了大量讨论:一方面,有人是谁希望避免那些与免费相同的公司大规模滥用 . 开发人员告诉我们,他们的老板强迫他们做错事,并且他们无法惩罚开发人员,因为他们的老板的行为是错误的 . 最终,支持开源pdfHTML的人们,即iText的开发者,赢得了争论 . 请证明他们没有免费使用iText;如果您在封闭的源环境中使用iText,请确保您的老板购买了商业许可证 .

    我需要维护一个遗留系统,我必须使用旧的iText版本 .

    真的吗?维护还涉及应用升级和迁移到您正在使用的软件的新版本 . 如您所见,使用iText 7和pdfHTML时所需的代码非常简单,并且比以前需要的代码更不容易出错 . 迁移项目不应该花太长时间 .

    我知道iText 7;我完成项目后才发现 .

    这就是我发布这个问题和答案的原因 . 把自己想象成一个极端程序员 . 扔掉所有代码,然后重新开始 . 你会发现它并没有你想象的那么多,而且你知道你已经让你的项目面向未来,因为iText 5正在逐步淘汰,你会睡得更好 . 我们仍然向付费客户提供支持,但最终,我们将完全停止支持iText 5 .

相关问题