如何在 itext 7 中将 html 转换为 pdf 时仅获取某些页面的横向方向?

How to get a landscape orientation for only some pages while converting html to pdf in itext 7?

我使用 iText7 和 pdfHTML 的 convertToPDF() 方法将 HTML 转换为 PDF。我想更改 PDF 文档中几个特定页面的页面方向。这些页面的内容是动态的,我们无法猜测横向应该有多少页(即动态 table 的内容可能超过一页)

当前情况:我创建了一个自定义工作器(实现 ITagWorker),它可以美化跟随标签 <landscape/>

的页面
public byte[] generatePDF(String html) {
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    PdfWriter pdfWriter = new PdfWriter(byteArrayOutputStream);
    PdfDocument pdfDocument = new PdfDocument(pdfWriter);
    try {

        ConverterProperties properties = new ConverterProperties();

        properties.setTagWorkerFactory(
                new DefaultTagWorkerFactory() {
                    @Override
                    public ITagWorker getCustomTagWorker(
                            IElementNode tag, ProcessorContext context) {
                        if ("landscape".equalsIgnoreCase(tag.name())) {
                            return new LandscapeDivTagWorker();
                        }
                        return null;
                    }
                } );

        MediaDeviceDescription mediaDeviceDescription = new MediaDeviceDescription(MediaType.PRINT);
        properties.setMediaDeviceDescription(mediaDeviceDescription);

        HtmlConverter.convertToPdf(html, pdfDocument, properties);
    } catch (IOException e) {
        e.printStackTrace();
    }
    pdfDocument.close();
    return byteArrayOutputStream.toByteArray();
}

定制工人:

public class LandscapeDivTagWorker implements ITagWorker {

    @Override
    public void processEnd(IElementNode element, ProcessorContext context) {
    }

    @Override
    public boolean processContent(String content, ProcessorContext context) {
        return false;
    }

    @Override
    public boolean processTagChild(ITagWorker childTagWorker, ProcessorContext context) {
        return false;
    }

    @Override
    public IPropertyContainer getElementResult() {
        return new AreaBreak(new PageSize(PageSize.A4).rotate());
    }
}

有没有办法定义所有应该横向显示的内容?

类似于:

<p>Display in portrait</p>
<landscape>
<div>
<p>display in landscape</p>
…
<table>
..
</table>
</div>
</landscape>

或 CSS class :

<p>Display in portrait</p>
<div class="landscape">
<p>display in landscape</p>
…
<table>
..
</table>
</div>

结果 => 纵向页面和其他页面s 横向(所有 div 内容应位于横向)

PS:我通过使用自定义 CssApplierFactory 遵循此提示 但结果是相同的 => 只是风景所在的第一页class 用于横向,table 的其他内容用于纵向

其实做起来还是比较麻烦的,但是整个机制还是很灵活的,可以满足这个需求。

我们将致力于支持以下语法:

<p>Display in portrait</p>
<landscape>
<div>
<p>display in landscape</p>
<p>content</p>
.....
<p>content</p>
</div>
</landscape>
<p> After portrait </p>

首先,我们需要先将 HTML 内容转换为元素,然后将这些元素添加到文档中,而不是直接 HTML -> PDF 转换。这是必需的,因为在 HTML 的情况下,根据 CSS 规范,有一个单独的页面大小处理机制,它不够灵活,无法满足您的要求,因此我们将为此使用本机 iText 布局机制.

我们的想法是,除了通过将参数传递给 AreaBreak 来自定义新页面大小之外,我们还将更改 PdfDocument 的默认页面大小,以便所有后续页面都以此创建自定义新页面大小。为此,我们需要一直传递 PdfDocument。高级代码如下所示:

PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFilePath));
ConverterProperties properties = new ConverterProperties();
properties.setTagWorkerFactory(new CustomTagWorkerFactory(pdfDocument));

Document document = new Document(pdfDocument);
List<IElement> elements = HtmlConverter.convertToElements(new FileInputStream(inputHtmlPath), properties);
for (IElement element : elements) {
    if (element instanceof IBlockElement) {
        document.add((IBlockElement) element);
    }
}

pdfDocument.close();

自定义标签工作者工厂也几乎没有变化 - 它只是将 PdfDocument 传递给标签工作者:

private static class CustomTagWorkerFactory extends DefaultTagWorkerFactory {
    PdfDocument pdfDocument;

    public CustomTagWorkerFactory(PdfDocument pdfDocument) {
        this.pdfDocument = pdfDocument;
    }

    @Override
    public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
        if ("landscape".equalsIgnoreCase(tag.name())) {
            return new LandscapeDivTagWorker(tag, context, pdfDocument);
        }
        return null;
    }
}

LandscapeDivTagWorker 的想法是创建一个 Div 包装器并将 <landscape> 标签的内部内容放在那里,但也用 AreaBreak 元素包围它 - 前面的一个将强制新的横向分页符并更改整个文档的默认页面大小,而下一个将恢复所有内容 - 强制拆分为纵向页面大小并将默认页面大小也设置为纵向。请注意,我们还为 AreaBreaksetNextRenderer 设置了自定义渲染器,以实际设置该中断时的默认页面大小:

private static class LandscapeDivTagWorker extends DivTagWorker {
    private PdfDocument pdfDocument;

    public LandscapeDivTagWorker(IElementNode element, ProcessorContext context, PdfDocument pdfDocument) {
        super(element, context);
        this.pdfDocument = pdfDocument;
    }

    @Override
    public IPropertyContainer getElementResult() {
        IPropertyContainer baseElementResult = super.getElementResult();
        if (baseElementResult instanceof Div) {
            Div div = new Div();
            AreaBreak landscapeAreaBreak = new AreaBreak(new PageSize(PageSize.A4).rotate());
            landscapeAreaBreak.setNextRenderer(new DefaultPageSizeChangingAreaBreakRenderer(landscapeAreaBreak, pdfDocument));
            div.add(landscapeAreaBreak);
            div.add((IBlockElement) baseElementResult);
            AreaBreak portraitAreaBreak = new AreaBreak(new PageSize(PageSize.A4));
            portraitAreaBreak.setNextRenderer(new DefaultPageSizeChangingAreaBreakRenderer(portraitAreaBreak, pdfDocument));
            div.add(portraitAreaBreak);
            baseElementResult = div;
        }
        return baseElementResult;
    }
}

自定义区域中断渲染器的实现非常简单 - 我们只将默认页面大小设置为 PdfDocument - 其余部分由我们扩展的默认实现在后台完成:

private static class DefaultPageSizeChangingAreaBreakRenderer extends AreaBreakRenderer {
    private PdfDocument pdfDocument;
    private AreaBreak areaBreak;

    public DefaultPageSizeChangingAreaBreakRenderer(AreaBreak areaBreak, PdfDocument pdfDocument) {
        super(areaBreak);
        this.pdfDocument = pdfDocument;
        this.areaBreak = areaBreak;
    }

    @Override
    public LayoutResult layout(LayoutContext layoutContext) {
        pdfDocument.setDefaultPageSize(areaBreak.getPageSize());
        return super.layout(layoutContext);
    }
}

因此,您将获得类似于屏幕截图中的页面设置: