如何使用 CSS 页面规则在 itext html2pdf 中启用页面方向支持
How to enable support of page orientation in itext html2pdf using CSS page rule
我们有 HTML 的内容,我们将打印布局与混合横向和纵向页面结合在一起。页面方向与页面规则一起应用,以保持页面清洁和可维护。
html2pdf 似乎完全忽略了这一点,例如:
<html>
<head>
<title>UnitTest</title>
<style type="text/css">* {
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
}
@page page-portrait {
size: A4;
orientation: portait;
}
@page page-landscape {
size: A4;
orientation: landscape;
}
.page-portrait {
page-break-after: always;
page: page-portrait;
}
.page-landscapes {
page-break-after: always;
page: page-landscape;
}
</style>
</head>
<body>
<div class="page-portrait">
<p>This should be a portrait page</p>
</div>
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
<div class="page-portrait">
<p>This should be a portrait page</p>
</div>
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
</body>
</html>
pdfHTML 目前不支持命名页面。您的 HTML 代码也有不一致之处:您希望以下块位于横向页面上
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
在上一个(纵向)块之后,您对纵向页面进行了分页。
奇偶 landscape/portrait 页来自 pdfHTML
如果您的目标是交替显示横向和纵向页面,您可以使用如下方法:
<html>
<head>
<title>UnitTest</title>
<style type="text/css">* {
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
}
@page:left {
size: A4 landscape;
}
@page:right {
size: A4 portrait;
}
.page-portrait {
page-break-after: always;
}
.page-landscapes {
page-break-after: always;
}
</style>
</head>
<body>
<div class="page-portrait">
<p>This should be a portrait page</p>
</div>
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
<div class="page-portrait">
<p>This should be a portrait page</p>
</div>
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
</body>
</html>
中间转换为元素的自定义元素处理
您可以利用 HtmlConverter.convertToElements
获取中间元素列表并逐个处理它们,根据需要更改默认页面大小。请注意,只有当进入新页面的元素是 HTML.
中的顶级元素时,它才会起作用
我的回答将基于以下 HTML:
<html>
<head>
<title>UnitTest</title>
<style type="text/css">* {
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
}
.page-portrait {
page-break-after: always;
page: page-portrait;
}
.page-landscapes {
page-break-after: always;
page: page-landscape;
}
</style>
</head>
<body>
<div class="page-landscapes">
<p>This should be a portrait page</p>
</div>
<div class="page-portrait">
<p>This should be a landscape page</p>
</div>
<div class="page-landscapes">
<p>This should be a portrait page</p>
</div>
<div>
<p>This should be a landscape page</p>
</div>
</body>
</html>
首先,让我们定义一个自定义标签工作者工厂,它将把自定义标签工作者分配给 <div>
元素,这些元素也可以包含我们负责分页的特殊 类作为 <div>
元素本身的标签工作者,它将负责将我们的特殊 类 传播到结果 layout
级别元素:
private static class CustomDivTagWorker extends DivTagWorker {
public CustomDivTagWorker(IElementNode element, ProcessorContext context) {
super(element, context);
}
@Override
public void processEnd(IElementNode element, ProcessorContext context) {
super.processEnd(element, context);
IPropertyContainer result = getElementResult();
if (result != null) {
if ("page-portrait".equals(element.getAttribute("class"))) {
result.setProperty(PAGE_BREAK_AFTER_RPOPERTY, "portrait");
} else if ("page-landscapes".equals(element.getAttribute("class"))) {
result.setProperty(PAGE_BREAK_AFTER_RPOPERTY, "landscape");
}
}
}
}
private static class CustomTagWorkerFactory extends DefaultTagWorkerFactory {
@Override
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
if ("div".equals(tag.name())) {
return new CustomDivTagWorker(tag, context);
}
return super.getCustomTagWorker(tag, context);
}
}
现在主要代码将 HTML 转换为元素,将根元素逐一添加到结果 Document
(来自布局模块),然后处理特殊 [=43= 的存在] 通过相应地更改页面大小。完整代码:
PdfDocument pdfDocument = new PdfDocument(new PdfWriter("path/to/out.pdf"));
ConverterProperties properties = new ConverterProperties();
properties.setTagWorkerFactory(new CustomTagWorkerFactory());
List<IElement> elements = HtmlConverter.convertToElements(new FileInputStream(sourceHTML), properties);
Document document = new Document(pdfDocument);
for (IElement element : elements) {
if (element instanceof IBlockElement) {
document.add((IBlockElement) element);
} else if (element instanceof AreaBreak) {
document.add((AreaBreak) element);
} else {
throw new RuntimeException();
}
if (element.hasProperty(PAGE_BREAK_AFTER_RPOPERTY)) {
String prop = element.getProperty(PAGE_BREAK_AFTER_RPOPERTY);
if ("portrait".equals(prop)) {
document.getPdfDocument().setDefaultPageSize(PageSize.A4);
} else {
document.getPdfDocument().setDefaultPageSize(PageSize.A4.rotate());
}
}
}
pdfDocument.close();
到目前为止我们设法找到的最佳解决方案如下,它基于 DIV 级别 CSS class 分配来控制页面方向。它可以扩展到其他 HTML 标签 and/or CSS classes.
该解决方案旨在通知页面侦听器应用的正确方向,并根据需要旋转页面。标记工作人员检查 HTML 并查找特定的 CSS classes 以向页面侦听器发送已找到方向的信号。
这种方法的好处是它允许保留东西 'simple' 并使用 'HtmlConverter.convertToPdf' 方法避免丢失底层实现中的某些特性。
PageOrientationsEventHandler eventHandler = new PageOrientationsEventHandler();
pdfDocument.addEventHandler(PdfDocumentEvent.START_PAGE, eventHandler);
converterProperties.setTagWorkerFactory(
new DefaultTagWorkerFactory() {
@Override
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
if (HTML_DIV.equalsIgnoreCase(tag.name())) {
return new CustomDivTagWorker(tag, context, pdfDocument, eventHandler);
}
return null;
}
});
// Run the conversion
HtmlConverter.convertToPdf(fis, pdfDocument, converterProperties);
以及以下引用常量:
private static final String CSS_PAGE_PORTRAIT = "page";
private static final String CSS_PAGE_LANDSCAPE = "page-landscape";
以及以下引用的 classes:
private static final class PageOrientationsEventHandler implements IEventHandler {
public static final PdfNumber PORTRAIT = new PdfNumber(0);
public static final PdfNumber LANDSCAPE = new PdfNumber(90);
private PdfNumber orientation = PORTRAIT;
private Map<Integer, PdfNumber> orientationHistory = new HashMap<Integer, PdfNumber>();
public void setOrientation(PdfNumber orientation) {
this.orientation = orientation;
}
@Override
public void handleEvent(Event currentEvent) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) currentEvent;
PdfPage page = docEvent.getPage();
// Check if we already rendered this page before
int pageNr = docEvent.getDocument().getPageNumber(page);
if(orientationHistory.containsKey(pageNr)) {
// We did, use the same orientation
// as we re-render we have lost track of the right orientation instrucrion
// since it only works well on 1st pass, not on subsequent renders
orientation = orientationHistory.get(pageNr);
} else {
// First render pass, store orientation
orientationHistory.put(pageNr, orientation);
}
// Toggle orientation
page.setIgnorePageRotationForContent(LANDSCAPE.equals(orientation));
page.put(PdfName.Rotate, orientation);
}
}
private static final class CustomDivTagWorker extends DivTagWorker {
private IElementNode element;
private PageOrientationsEventHandler eventHandler;
public CustomDivTagWorker(IElementNode element, ProcessorContext context, PdfDocument pdfDocument, PageOrientationsEventHandler eventHandler) {
super(element, context);
this.element = element;
this.eventHandler = eventHandler;
}
@Override
public IPropertyContainer getElementResult() {
IPropertyContainer baseElementResult = super.getElementResult();
// We are interested in Divs only
if (baseElementResult instanceof Div) {
// Check landscape based on class identifier
boolean landscape = false;
String cssClass = element.getAttribute(AttributeConstants.CLASS);
if (CSS_PAGE_LANDSCAPE.equals(cssClass)) {
landscape=true;
} else if (CSS_PAGE_PORTRAIT.equals(cssClass)) {
landscape=false;
}
// Flag requested orientation to our start page handler
if(cssClass!=null && cssClass.length()>0) {
eventHandler.setOrientation(landscape?PageOrientationsEventHandler.LANDSCAPE:PageOrientationsEventHandler.PORTRAIT);
}
}
return baseElementResult;
}
}
我们有 HTML 的内容,我们将打印布局与混合横向和纵向页面结合在一起。页面方向与页面规则一起应用,以保持页面清洁和可维护。
html2pdf 似乎完全忽略了这一点,例如:
<html>
<head>
<title>UnitTest</title>
<style type="text/css">* {
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
}
@page page-portrait {
size: A4;
orientation: portait;
}
@page page-landscape {
size: A4;
orientation: landscape;
}
.page-portrait {
page-break-after: always;
page: page-portrait;
}
.page-landscapes {
page-break-after: always;
page: page-landscape;
}
</style>
</head>
<body>
<div class="page-portrait">
<p>This should be a portrait page</p>
</div>
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
<div class="page-portrait">
<p>This should be a portrait page</p>
</div>
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
</body>
</html>
pdfHTML 目前不支持命名页面。您的 HTML 代码也有不一致之处:您希望以下块位于横向页面上
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
在上一个(纵向)块之后,您对纵向页面进行了分页。
奇偶 landscape/portrait 页来自 pdfHTML
如果您的目标是交替显示横向和纵向页面,您可以使用如下方法:
<html>
<head>
<title>UnitTest</title>
<style type="text/css">* {
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
}
@page:left {
size: A4 landscape;
}
@page:right {
size: A4 portrait;
}
.page-portrait {
page-break-after: always;
}
.page-landscapes {
page-break-after: always;
}
</style>
</head>
<body>
<div class="page-portrait">
<p>This should be a portrait page</p>
</div>
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
<div class="page-portrait">
<p>This should be a portrait page</p>
</div>
<div class="page-landscapes">
<p>This should be a landscape page</p>
</div>
</body>
</html>
中间转换为元素的自定义元素处理
您可以利用 HtmlConverter.convertToElements
获取中间元素列表并逐个处理它们,根据需要更改默认页面大小。请注意,只有当进入新页面的元素是 HTML.
我的回答将基于以下 HTML:
<html>
<head>
<title>UnitTest</title>
<style type="text/css">* {
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
}
.page-portrait {
page-break-after: always;
page: page-portrait;
}
.page-landscapes {
page-break-after: always;
page: page-landscape;
}
</style>
</head>
<body>
<div class="page-landscapes">
<p>This should be a portrait page</p>
</div>
<div class="page-portrait">
<p>This should be a landscape page</p>
</div>
<div class="page-landscapes">
<p>This should be a portrait page</p>
</div>
<div>
<p>This should be a landscape page</p>
</div>
</body>
</html>
首先,让我们定义一个自定义标签工作者工厂,它将把自定义标签工作者分配给 <div>
元素,这些元素也可以包含我们负责分页的特殊 类作为 <div>
元素本身的标签工作者,它将负责将我们的特殊 类 传播到结果 layout
级别元素:
private static class CustomDivTagWorker extends DivTagWorker {
public CustomDivTagWorker(IElementNode element, ProcessorContext context) {
super(element, context);
}
@Override
public void processEnd(IElementNode element, ProcessorContext context) {
super.processEnd(element, context);
IPropertyContainer result = getElementResult();
if (result != null) {
if ("page-portrait".equals(element.getAttribute("class"))) {
result.setProperty(PAGE_BREAK_AFTER_RPOPERTY, "portrait");
} else if ("page-landscapes".equals(element.getAttribute("class"))) {
result.setProperty(PAGE_BREAK_AFTER_RPOPERTY, "landscape");
}
}
}
}
private static class CustomTagWorkerFactory extends DefaultTagWorkerFactory {
@Override
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
if ("div".equals(tag.name())) {
return new CustomDivTagWorker(tag, context);
}
return super.getCustomTagWorker(tag, context);
}
}
现在主要代码将 HTML 转换为元素,将根元素逐一添加到结果 Document
(来自布局模块),然后处理特殊 [=43= 的存在] 通过相应地更改页面大小。完整代码:
PdfDocument pdfDocument = new PdfDocument(new PdfWriter("path/to/out.pdf"));
ConverterProperties properties = new ConverterProperties();
properties.setTagWorkerFactory(new CustomTagWorkerFactory());
List<IElement> elements = HtmlConverter.convertToElements(new FileInputStream(sourceHTML), properties);
Document document = new Document(pdfDocument);
for (IElement element : elements) {
if (element instanceof IBlockElement) {
document.add((IBlockElement) element);
} else if (element instanceof AreaBreak) {
document.add((AreaBreak) element);
} else {
throw new RuntimeException();
}
if (element.hasProperty(PAGE_BREAK_AFTER_RPOPERTY)) {
String prop = element.getProperty(PAGE_BREAK_AFTER_RPOPERTY);
if ("portrait".equals(prop)) {
document.getPdfDocument().setDefaultPageSize(PageSize.A4);
} else {
document.getPdfDocument().setDefaultPageSize(PageSize.A4.rotate());
}
}
}
pdfDocument.close();
到目前为止我们设法找到的最佳解决方案如下,它基于 DIV 级别 CSS class 分配来控制页面方向。它可以扩展到其他 HTML 标签 and/or CSS classes.
该解决方案旨在通知页面侦听器应用的正确方向,并根据需要旋转页面。标记工作人员检查 HTML 并查找特定的 CSS classes 以向页面侦听器发送已找到方向的信号。
这种方法的好处是它允许保留东西 'simple' 并使用 'HtmlConverter.convertToPdf' 方法避免丢失底层实现中的某些特性。
PageOrientationsEventHandler eventHandler = new PageOrientationsEventHandler(); pdfDocument.addEventHandler(PdfDocumentEvent.START_PAGE, eventHandler);
converterProperties.setTagWorkerFactory(
new DefaultTagWorkerFactory() {
@Override
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
if (HTML_DIV.equalsIgnoreCase(tag.name())) {
return new CustomDivTagWorker(tag, context, pdfDocument, eventHandler);
}
return null;
}
});
// Run the conversion
HtmlConverter.convertToPdf(fis, pdfDocument, converterProperties);
以及以下引用常量:
private static final String CSS_PAGE_PORTRAIT = "page";
private static final String CSS_PAGE_LANDSCAPE = "page-landscape";
以及以下引用的 classes:
private static final class PageOrientationsEventHandler implements IEventHandler {
public static final PdfNumber PORTRAIT = new PdfNumber(0);
public static final PdfNumber LANDSCAPE = new PdfNumber(90);
private PdfNumber orientation = PORTRAIT;
private Map<Integer, PdfNumber> orientationHistory = new HashMap<Integer, PdfNumber>();
public void setOrientation(PdfNumber orientation) {
this.orientation = orientation;
}
@Override
public void handleEvent(Event currentEvent) {
PdfDocumentEvent docEvent = (PdfDocumentEvent) currentEvent;
PdfPage page = docEvent.getPage();
// Check if we already rendered this page before
int pageNr = docEvent.getDocument().getPageNumber(page);
if(orientationHistory.containsKey(pageNr)) {
// We did, use the same orientation
// as we re-render we have lost track of the right orientation instrucrion
// since it only works well on 1st pass, not on subsequent renders
orientation = orientationHistory.get(pageNr);
} else {
// First render pass, store orientation
orientationHistory.put(pageNr, orientation);
}
// Toggle orientation
page.setIgnorePageRotationForContent(LANDSCAPE.equals(orientation));
page.put(PdfName.Rotate, orientation);
}
}
private static final class CustomDivTagWorker extends DivTagWorker {
private IElementNode element;
private PageOrientationsEventHandler eventHandler;
public CustomDivTagWorker(IElementNode element, ProcessorContext context, PdfDocument pdfDocument, PageOrientationsEventHandler eventHandler) {
super(element, context);
this.element = element;
this.eventHandler = eventHandler;
}
@Override
public IPropertyContainer getElementResult() {
IPropertyContainer baseElementResult = super.getElementResult();
// We are interested in Divs only
if (baseElementResult instanceof Div) {
// Check landscape based on class identifier
boolean landscape = false;
String cssClass = element.getAttribute(AttributeConstants.CLASS);
if (CSS_PAGE_LANDSCAPE.equals(cssClass)) {
landscape=true;
} else if (CSS_PAGE_PORTRAIT.equals(cssClass)) {
landscape=false;
}
// Flag requested orientation to our start page handler
if(cssClass!=null && cssClass.length()>0) {
eventHandler.setOrientation(landscape?PageOrientationsEventHandler.LANDSCAPE:PageOrientationsEventHandler.PORTRAIT);
}
}
return baseElementResult;
}
}