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

Shr*_*rey 5 puppeteer

在尝试使用headerTemplatefooterTemplate选项时,我注意到了一些不一致page.pdf:

  • 页眉和页脚的DPI似乎更低(我认为主体为72比96).因此,如果我想要匹配边距,我必须按比例缩放.
  • 样式不与主体共享,因此我必须将它们包含在模板中.
  • 如果我尝试使用本地存储的字体,它可以在主体上工作,但不在页眉/页脚中,即使我在页眉/页脚模板中包含相同的CSS.

我怀疑这是因为页眉和页脚被视为单独的文档并分别转换为image/pdf(https://cs.chromium.org/chromium/src/components/printing/resources/print_header_footer_template_page.html也暗示类似的东西).熟悉该实现的人是否可以解释它实际上是如何工作的?谢谢!

Gra*_*ler 10

简答:

Puppeteer通过DevTools协议控制Chrome或Chromium .

Chromium使用Skia生成PDF.

Skia分别处理标题,一组对象和页脚.


详细解答:

Puppeteer文档:

page.pdf(选项)

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

注意目前仅在Chrome无头中支持生成pdf.


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

  1. 不评估模板内的脚本标记.
  2. 页面样式在模板中不可见.

我们可以从Puppeteer源代码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>将生成包含标题的span.
  • footerTemplate 字符串(可选)
    • 打印页脚的HTML模板.应该使用相同的格式headerTemplate.

返回对象

  • data
    • Base64编码的pdf数据.

铬源代码Page.printToPDF向我们表明:

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

在进一步挖掘之后,我们可以看到Chromium具有一个称为SkDocument创建PDF文件的类的具体实现.

SkDocument来自Skia图形库,Chromium用于生成PDF.

操作的Skia的PDF理论,在PDF对象和文档结构部分,指出:

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

该文件进一步解释:

该PDF后端需要在PDF中使用的所有间接对象被添加到SkPDFObjNumMapSkPDFDocument.目录负责分配对象编号并生成PDF文件末尾所需的目录.从某种意义上说,生成PDF是一个三步过程.在第一步中,创建它们中的所有对象和引用(主要由它完成SkPDFDevice).在第二步中,SkPDFObjNumMap分配并记住对象编号.最后,在第三步骤中,报头被印刷,每一个对象被印刷,然后将印刷的内容和拖车表.SkPDFDocument负责收集各种SkPDFDevice实例中的所有对象,将它们添加到一个SkPDFObjNumMap,迭代对象一次以设置其文件位置,然后再次迭代以生成最终的PDF.


Shr*_*rey 6

感谢其他答案(/sf/answers/3602244901/)和代码搜索,我想我找到了我一直在寻找的大多数答案。

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

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

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

  • 这些不同的 DPI 真的很疯狂。我只尝试了`style =“transform:scale(0.75);transform-origin:left;`(`72/96=0.75`)的测试,它确实匹配。在缩放之后,我的边框宽度现在是相同的( header vs content)。但是边距等仍然是问题。这真的令人难以置信这个 DPI sh*t:你为页面设置 `margin` 对象,然后在 header/footerTemplate 的内联样式中使用相同的边距值,并且它确实如此不适合(导致 DPI 问题)。而且我不知道如何处理这个问题,因为简单的乘法或除以“96/72”因子对我来说不起作用。 (3认同)