【问题标题】:How to enable support of page orientation in itext html2pdf using CSS page rule如何使用 CSS 页面规则在 itext html2pdf 中启用页面方向支持
【发布时间】:2021-09-25 08:52:48
【问题描述】:

我们有 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>

【问题讨论】:

    标签: itext html2pdf


    【解决方案1】:

    pdfHTML 目前不支持命名页面。您的 HTML 代码也存在不一致:您希望以下块位于横向页面上

        <div class="page-landscapes">
            <p>This should be a landscape page</p>
        </div>
    

    在上一个(纵向)块之后,您对纵向页面进行分页。

    通过 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>
    

    首先,让我们定义一个自定义标签工作者工厂,它将自定义标签工作者分配给&lt;div&gt;元素,这些元素可以包含我们负责分页的特殊类,以及&lt;div&gt;的标签工作者元素本身将负责将我们的特殊类传播到生成的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(来自布局模块),然后通过相应地更改页面大小来处理特殊类的存在。完整代码:

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

    【讨论】:

    • 感谢 Alexey 的建议!在基本的 HTML 文档中,它确实起到了作用。我们发现,在我们相当复杂和冗长的 HTML 中,这种方法会导致 iText 应用的渲染出现一些差距(例如未完全应用样式)。我们刚刚在这个线程中添加了一个解决方案来解决这个问题,仅供参考。
    【解决方案2】:

    到目前为止,我们设法找到的最佳解决方案如下,它基于 DIV 级别的 CSS 类分配来控制页面方向。它可以扩展为适用于其他 HTML 标记和/或 CSS 类。

    该解决方案旨在通知页面侦听器以正确的方向应用,并根据需要旋转页面。标记工作人员检查 HTML 并查找特定的 CSS 类,以向页面侦听器发出找到的方向信号。

    这种方法的好处是它可以让事情保持“简单”,并使用“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";
    

    以及以下引用的类:

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

    【讨论】:

      猜你喜欢
      • 2016-11-03
      • 2022-08-24
      • 1970-01-01
      • 2016-01-09
      • 1970-01-01
      • 1970-01-01
      • 2015-05-28
      • 2010-12-07
      • 1970-01-01
      相关资源
      最近更新 更多