如何在 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
元素包围它 - 前面的一个将强制新的横向分页符并更改整个文档的默认页面大小,而下一个将恢复所有内容 - 强制拆分为纵向页面大小并将默认页面大小也设置为纵向。请注意,我们还为 AreaBreak
和 setNextRenderer
设置了自定义渲染器,以实际设置该中断时的默认页面大小:
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);
}
}
因此,您将获得类似于屏幕截图中的页面设置:
我使用 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 遵循此提示
其实做起来还是比较麻烦的,但是整个机制还是很灵活的,可以满足这个需求。
我们将致力于支持以下语法:
<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
元素包围它 - 前面的一个将强制新的横向分页符并更改整个文档的默认页面大小,而下一个将恢复所有内容 - 强制拆分为纵向页面大小并将默认页面大小也设置为纵向。请注意,我们还为 AreaBreak
和 setNextRenderer
设置了自定义渲染器,以实际设置该中断时的默认页面大小:
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);
}
}
因此,您将获得类似于屏幕截图中的页面设置: