页眉和页脚打印如何在Puppeter的page.pdf API中起作用?

时间:2018-07-21 16:58:21

标签: puppeteer

当尝试将headerTemplatefooterTemplate选项与page.pdf一起使用时,我注意到了一些不一致之处:

  • 页眉和页脚的DPI似乎较低(我认为主体的DPI为72 vs 96)。因此,如果我要匹配边距,则必须按比例进行缩放。
  • 样式没有与主体共享,因此我必须将样式包括在模板中。
  • 如果我尝试使用本地存储的字体,即使我在页眉/页脚模板中包含相同的CSS,它也可以在主体上运行,但不能在页眉/页脚中使用。

我怀疑发生这种情况是因为页眉和页脚被视为单独的文档,并分别转换为image / pdf(https://cs.chromium.org/chromium/src/components/printing/resources/print_header_footer_template_page.html也暗示着类似的意思)。熟悉实施的人可以解释其实际工作原理吗?谢谢!

3 个答案:

答案 0 :(得分:6)

简短答案:

Puppeteer通过DevTools Protocol控制Chrome或Chromium。

Chromium使用Skia生成PDF。

Skia分别处理页眉,对象集和页脚。


详细答案:

来自Puppeteer Documentation

  

page.pdf(选项)

     
      
  • options <Object>选项对象,可能具有以下属性:      
        
    • headerTemplate <string>用于打印标题的HTML模板。应该是有效的HTML标记,其中包含以下用于向其中注入打印值的类:      
          
      • date格式化的打印日期
      •   
      • title文档标题
      •   
      • url文档位置
      •   
      • pageNumber当前页码
      •   
      • totalPages文档中的总页数
      •   
    •   
    • footerTemplate <string>打印页脚的HTML模板。应该使用与headerTemplate相同的格式。
    •   
  •   
  • 返回:<Promise <Buffer >> Promise通过PDF缓冲区解决。
  •   
     
    

注意当前仅在Chrome headless中支持生成pdf。

  
     
     
    

注意 headerTemplatefooterTemplate标记具有以下限制:

         
        
  1. 不评估模板中的脚本标签。
  2.     
  3. 页面样式在模板中不可见。
  4.     
  

我们可以从Puppeteer source code for page.pdf()中学到:

  • Chrome DevTools协议方法Page.printToPDF(以及headerTemplatefooterTemplate参数)被发送到page._client
  • page._clientpage.target().createCDPSession()(Chrome DevTools协议会话)的实例。

Chrome DevTools Protocol Viewer中,我们可以看到Page.printToPDF包含参数headerTemplatefooterTemplate

  

Page.printToPDF

     

将页面打印为PDF。

     

参数

     
      
  • headerTemplate 字符串(可选)      
        
    • 用于打印标题的HTML模板。应该是有效的HTML标记,其中包含以下用于向其中注入打印值的类:      
          
      • date:格式化的打印日期
      •   
      • title:文档标题
      •   
      • url:文档位置
      •   
      • pageNumber:当前页码
      •   
      • totalPages:文档中的页面总数
      •   
    •   
    • 例如,<span class=title></span>将生成包含标题的范围。
    •   
  •   
  • footerTemplate 字符串(可选)      
        
    • 打印页脚的HTML模板。应该使用与headerTemplate相同的格式。
    •   
  •   
     

返回对象

     
      
  • data 字符串      
        
    • Base64编码的pdf数据。
    •   
  •   

Chromium source code for Page.printToPDF向我们显示:

  • Page.printToPDF参数被传递到sendDevToolsMessage函数,该函数发出一个DevTools协议命令并返回对结果的承诺。

进一步挖掘后,我们可以看到Chromium具有创建PDF文件的具体implementation of a class called SkDocument

SkDocument来自Skia Graphics LibraryChromium uses for PDF generation

Skia PDF Theory of Operation部分中的PDF Objects and Document Structure指出:

  

背景:PDF文件格式具有标头对象集页脚,包含文档中所有对象的目录(交叉引用表)。目录列出了每个对象的特定字节位置。这些对象可能具有对其他对象的引用,并且这些引用的ASCII大小取决于分配给所引用对象的对象编号。因此,在知道对象的大小(需要分配对象编号)之前,我们无法计算目录。该文档使用SkWStream::bytesWritten()查询每个对象的偏移量并建立交叉引用表。

文档进一步解释如下:

  

PDF后端要求将PDF中使用的所有间接对象添加到SkPDFObjNumMap的{​​{1}}中。该目录负责分配对象编号并生成PDF文件末尾所需的目录。从某种意义上说,生成PDF是一个三步过程。第一步,创建其中的所有对象和引用(主要由SkPDFDocument完成)。在第二步中,SkPDFDevice分配并记住对象编号。最后,在第三步中,打印标头打印每个对象,然后打印目录和预告片SkPDFObjNumMap负责收集来自各个SkPDFDocument实例的所有对象,将它们添加到SkPDFDevice中,对这些对象进行一次迭代以设置其文件位置,然后再次进行迭代以生成最终对象PDF。

答案 1 :(得分:4)

多亏了其他答案(https://stackoverflow.com/a/51460641/364131)和代码搜索,我认为我找到了我一直在寻找的大多数答案。

打印实现在PrintPageInternal中。它使用两个单独的WebFrame-一个用于呈现内容,另一个用于呈现页眉和页脚。通过创建special frame,将print_header_and_footer_template_page.html的内容写入此帧,使用提供的选项调用setup函数,然后打印到共享画布来完成页眉和页脚的呈现。 。之后,页面的其余内容将在页边距定义的边界内打印在同一画布上。

页眉和页脚由fudge_factor缩放,该比例不适用于其余内容。 DPI可能会发生一些有趣的事情(这可能解释1.33333333f的fudge_factor等于96/72)。

我猜想这个特殊的框架会阻止页眉和页脚共享与页面内容相同的资源(样式,字体等)。可能没有设置加载(和等待)页眉和页脚模板请求的任何其他资源,这就是为什么不加载所请求的字体的原因。

答案 2 :(得分:1)

我对此问题进行了大量研究,最后,我实现了一个小型库以通过一个小技巧来解决此问题:

enter image description here

我创建了两个PDF文件。第一个是没有页眉和页脚的HTML内容。第二个是根据原始内容PDF页面的编号重复页眉和页脚,然后将它们合并在一起。

您可以在这里找到它: https://github.com/PejmanNik/puppeteer-report