打印为 PDF 时更改默认值 header/footer

Alter the default header/footer when printing to PDF

我正在尝试使用 Google Chrome 代替 PhantomJS 将 HTML 呈现为 PDF。到目前为止,它对我来说效果很好。我遇到的唯一问题是我还没有找到任何方法来翻译以下 PhantomJS 代码:

page.paperSize = {
  footer: {
    contents: phantom.callback(function(pageNum, numPages) {
      return "Power by MyWebsite. Created on "+formatDate(new Date())+"<span style='float:right'>" + pageNum + " / " + numPages + "</span>";
    })
  }
}

格式化日期与问题中的功能相同 如何格式化 JavaScript 日期

但是我还没有找到在 Google Chrome 中无头复制这种行为的方法。 我正在使用来自 https://github.com/cyrus-and/chrome-remote-interface

的 Chrome 远程接口 (CDP)

这是我的 chrome 远程接口代码的概要:

return new Promise(async function (resolve, reject) {
    const url = "<MyURL here>";
    const [tab] = await Cdp.List()
    const client = await Cdp({ host: '127.0.0.1', target: tab });
    await Promise.all([
       Network.enable(),
       Page.enable()
    ]);

    Page.loadEventFired(function () { 
         setTimeout(function () {
             resolve(Page.printToPDF({displayHeaderFooter:true}))); //https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
         }, 3000);
    });
    await Page.navigate({ url }); 
};

我得到的 PDF 很好,但只能得到默认的 Chrome 页眉和页脚。有什么修改方法吗?

注意:我知道我可以在我的页面中使用 JavaScript 将元素附加到每个页面的底部,但我更愿意更改页脚这是在 exporting/printing 时由浏览器附加的,因为我发现正确放置它更加可靠,并且不会导致页面中其他 div 的任何奇怪的重新流动。

您的问题有两种解决方案

A) 不留边距将 chrome-header 推出:

 @page { 
     margin: 0;
     size: auto;
 }

 @media print {
   @page { margin: 0; }
   body { margin: 1.6cm; }
 }

B) 最初是一个 Firefox 解决方案,应该适用于 Chrome

 <html moznomarginboxes mozdisallowselectionprint>

一些样本:

<!DOCTYPE html>
<html moznomarginboxes mozdisallowselectionprint>
<head>
<title>Print PDF without header</title>
<style>
@media print {
    @page { margin: 0; }
    body { margin: 1.6cm; }
}
</style>
</head>
<body>
<p>Some Text in Paragraph to print!</p>
<a href="javascript:print()">Print</a>
</body>
</html>

更新

您可以使用 printToPDF 中的 headerTemplatefooterTemaplate 参数自定义打印为 PDF 时的 header 和页脚。

headerTemplatefooterTemaplate 接受有效的 HTML 标记,您可以使用以下 classes 将打印值注入 HTML 元素:

  • date - 将可打印格式的当前日期注入包含 class
  • 的 HTML 元素
  • title - 文档标题
  • url - 文档位置
  • pageNumber - 当前页码
  • totalPages - 总页数

例如打印页码和总页数:

Page.printToPDF({
    displayHeaderFooter: true,
    footerTemplate: "<span class='pageNumber'></span> <span>out of</span> <span class='totalPages'></span>"
})

原创

(当时,Chrome DevTools 无法实现。)

根据this forum,目前在googlechrome中没有办法做到这一点。您所能做的就是打开或关闭 header/footer。注释表明了这一点:

Currently there isn't a way to edit the header when printing a document. You can currently only turn the header and footer on or off which includes the date, name of the web page, the page URL and how many pages the document you're printing. You may want to check out the Chrome Web Store to see if there are any handy third party extensions that you can install on Chrome that may fit what you're looking for in terms of printing -- Source There may be third-party extensions to get the functionality you are looking for, or as you suggest, you can use JavaScript to append the elements you want to print.

可以使用 <header><footer> 标签创建自定义页眉和页脚。我用它来使用 Chrome Headless 生成 PDF。我还没有在 Firefox、IE 等中测试它...

<header>
  Custom Header
  <img src="http://imageurl.com/image.jpg"/>
</header>
<div class="content">Page Content - as long as you want</div>
<footer>
  Footer Content
</footer>

CSS

@page {
  margin: 0;
}
@media print {
  footer {
    position: fixed;
    bottom: 0;
  }
  header {
    position: fixed;
    top: 0;
  }
}

@page { margin: 0 } 删除默认的页眉和页脚。

希望这对您有所帮助。

这是对问题的update/answer。从 Chromium 64 开始,可以使用 headerTemplatefooterTemplate 参数来 printToPDF

使用 chrome remote interface 下面是应该有效的示例代码:

return new Promise(async function (resolve, reject) {
    const url = "<MyURL here>";
    const [tab] = await Cdp.List()
    const client = await Cdp({ host: '127.0.0.1', target: tab });
    await Promise.all([
       Network.enable(),
       Page.enable()
    ]);

    Page.loadEventFired(function () { 
         setTimeout(function () {
    //https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
             resolve(Page.printToPDF({
                  displayHeaderFooter:true,
                  footerTemplate: "<span class='pageNumber'> of <span class='totalPages'>"
             }))); 
         }, 3000);
    });
    await Page.navigate({ url }); 
};

如果你真的想以一种非 hacky 的方式做到这一点,你必须开始使用 chrome devtools 协议,因为命令行界面不支持太多(又名 --print-to-pdf就是你得到的,没有打印选项)

协议记录在此处:https://chromedevtools.github.io/devtools-protocol/tot

客户端库有多种语言版本,可通过包管理器获取。

这是我整理的一些示例来演示用法:

Page.printToPDF 协议方法支持为页眉和页脚传递自定义标记的参数。

基本上,该协议是在 protocol.json 中定义的,客户端库使用它来生成 classes/methods 以供在任何应用程序中使用。这些方法用协议封装了较低级别的通信。

我所要做的就是安装一个客户端库包(通过 npm 或 composer),确保 chrome 已安装,编写一些代码,然后我生成的 pdf 没有 header/footer!太棒了。

对于那些只想要开箱即用的东西的人,我想分享我今天根据 apokryfos 的回答编写的脚本。

首先需要安装依赖[=​​18=]

yarn global add chrome-remote-interface

接下来您需要启动一个启用了调试端口的无头 chromium

chromium-browser --headless --disable-gpu --run-all-compositor-stages-before-draw --remote-debugging-port=9222

现在你必须保存我的脚本,即 print-via-chrome.js:

#!/usr/bin/env node

const homedir = require('os').homedir();
const CDP = require(homedir+'/.config/yarn/global/node_modules/chrome-remote-interface/');
const fs = require('fs');

const port = process.argv[2];
const htmlFilePath = process.argv[3];
const pdfFilePath = process.argv[4];

(async function() {

        const protocol = await CDP({port: port});

        // Extract the DevTools protocol domains we need and enable them.
        // See API docs: https://chromedevtools.github.io/devtools-protocol/
        const {Page} = protocol;
        await Page.enable();

        Page.loadEventFired(function () {
                console.log("Waiting 100ms just to be sure.")
                setTimeout(function () {
                        //https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-printToPDF
                        console.log("Printing...")
                        Page.printToPDF({
                                displayHeaderFooter: true,
                                headerTemplate: '<div></div>',
                                footerTemplate: '<div class="text center"><span class="pageNumber"></span></div>',
                                //footerTemplate: '<div class="text center"><span class="pageNumber"></span> of <span class="totalPages"></span></div>'
                        }).then((base64EncodedPdf) => {
                                fs.writeFileSync(pdfFilePath, Buffer.from(base64EncodedPdf.data, 'base64'), 'utf8');
                                console.log("Done")
                                protocol.close();
                        });
                }, 100);
        });

        Page.navigate({url: 'file://'+htmlFilePath});
})();

使用 chmod +x print-via-chrome.js 使其可执行后,您应该能够将 html 文件转换为 pdf 文件,如下所示:

./print-via-chrome.js 9222 my.html my.pdf

完成转换后别忘了退出 chromium。

我很确定这个解决方案远非完美,但至少它有效,因为我看到了很多关于这个功能的问题,我不得不花几个小时的时间来让它工作,我想要分享我的解决方案。我遇到的一些问题与 header- 和 footerTemplates 有关,因为空模板似乎不会替换现有模板(您需要 <div></div>),并且虽然有不同的记录,但新模板不会出现在可见区域中,直到用 <div class="text center"> 或类似的东西包裹。

正如 Alec Jacobson 在评论中提到的,在页面上使用 margin:0 以及在正文上使用 margin:1.6cm 仅适用于一页。

对我有用的是将我的内容包装在 table 中,使用 thead 作为上边距,使用 tfoot 作为下边距。 thead 和 tfoot 在所有页面上重复,您的主页内容进入 table.

的 tbody

示例:

`

    <thead> 
        <tr> 
            <th style="height: 1cm">
                Header    
            </th>
        </tr>
    </thead>

    <tbody>
        <tr>
            <td>

                Page Content

            </td>
        </tr>
    </tbody>

    <tfoot>
        <tr>
            <td style="height: 1cm">

                Footer

            </td>
        </tr>
    </tfoot>

</table>`

本想添加到评论线程,但没有足够的声誉。