最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 使用puppeteer生成自动分页PDF指南

    正文概述 掘金(周末玩具)   2021-02-16   2810

    前段时间接到公司一个需求,需要将用户选择的不定数量的文本内容(富文本格式,包含图片),生成自动分页,且每页都有相同页眉、页尾的 PDF 。考虑到如果是用后端语言实现排版,需要实现一套基于富文本格式的动态分页的逻辑,比较麻烦。于是想到利用 web 技术来排版,并通过 puppeteer 生成 PDF 。

    实现的过程中遇到了一些坑点,在这里记录一下。

    写在前面

    puppeteer 是 google 官方维护的 headless Chrome 工具。可以理解为用 JavaScript 去操作 Chrome 完成一些任务(爬虫、截图等)的工具,且可以不打开 Chrome 的图形化界面。有关如何搭建环境、使用puppeteer ,掘金上已有很多文章谈及,这里不展开说。本文只讲如何生成自动分页的 PDF 。

    如何实现分页

    单纯分页其实很容易实现,在操作 puppeteer 的时候,传入每页的大小尺寸参数, puppeteer 在生成 PDF 截取内容时候就会按照设定的分页尺寸,将内容自动分页。效果类似于使用 Chrome 时按 ctrl+P 另存为 PDF 。
    生成每页大小为 A4 的 PDF ,代码如下:

    const browser = await puppeteer.launch({
      headless: true,
    });
    const page = await browser.newPage();
    await page.goto(url);
    const pdfBuffer = await page.pdf({
      format: 'A4',
      scale: 1,
      margin: {
        top: '0',
        bottom: '0',
        left: '0',
        right: '0',
      },
      landscape: false,
      displayHeaderFooter: false,
    });
    await browser.close();
    

    对掘金首页执行生成 PDF 的效果如图:

    使用puppeteer生成自动分页PDF指南

    实现每页固定页头和页尾

    1. 使用 fixed 布局

    分页很容易实现,难点在于如何让每一页呈现相同的页头和页尾。我首先尝试了用 fixed 属性,确实可以实现每页固定出现页头和页尾的效果。但是由于使用的是 fixed 布局,出现了内容被页头遮挡的情况。还是以掘金首页为例:
    使用puppeteer生成自动分页PDF指南

    使用puppeteer生成自动分页PDF指南

    使用puppeteer生成自动分页PDF指南

    如图,红框位置就被 fixed 布局的页头所遮挡。

    2. table 布局

    通过不断的 Google 和调试,最后发现 table 布局可以比较好地实现需求。实现上就是把页头放在 thead,页尾放在 tfoot 。代码如下:

    <table class="table-container">
      <!-- 每页固定头部 -->
      <thead class="table-header">
        <tr class="table-row">
          <th class="table-row-item">
            <div class="page-header-wrapper">
              <header class="page-header">
                <div class="left">
                  <div class="logo-wrapper">
                    <img class="logo" src="@/assets/images/logo.svg"  />
                    <div class="user-name">页头</div>
                  </div>
                </div>
              </header>
            </div>
          </th>
        </tr>
      </thead>
    
      <!-- 包裹段落容器 -->
      <tbody class="table-body">
        <tr class="table-row">
          <td class="table-row-item">
            <div class="container">
              <!-- 此处放置页面内容 -->
            </div>
          </td>
        </tr>
      </tbody>
    
      <!-- 每页固定尾部 -->
      <tfoot class="table-footer">
        <tr class="table-row">
          <td class="table-row-item">
            <div class="page-footer">页尾</div>
          </td>
        </tr>
      </tfoot>
    </table>
    

    实现效果如下:

    使用puppeteer生成自动分页PDF指南

    3. 使用 pdf-lib 生成页尾

    实现了每页固定的页头和页尾,又出现了新问题。由于内容是动态的,最后一页的内容是不一定到底部的,使用上述的实现方法会出现最后一页样式不一致的问题。如图:

    使用puppeteer生成自动分页PDF指南

    调试了很久也没有办法在 web 端解决这个问题,于是转换思路,每页只留下页尾的空白位置,生成PDF后再用工具画上页尾。 具体是使用 pdf-lib 这个 node.js 的库。代码如下:

    const pdfDoc = await PDFDocument.load(pdfBuffer);
    pdfDoc.registerFontkit(fontkit);
    const customFont = await pdfDoc.embedFont(SimSun);
    const pages = pdfDoc.getPages();
    const firstPage = pages[0];
    const { width: pageWidth } = firstPage.getSize();
    pages.forEach((page, index) => {
      const text = motto[this.getRandom(motto.length, 0)];
      page.drawText(text, {
        x: 41,
        y: 23,
        size: 11,
        font: customFont,
        color: rgb(0.302, 0.302, 0.302),
      });
      page.drawText(`第${index + 1}页/共${pages.length}页`, {
        x: pageWidth - 100,
        y: 23,
        size: 11,
        font: customFont,
        color: rgb(0.302, 0.302, 0.302),
      });
      page.drawRectangle({
        x: 41,
        y: 45,
        width: pageWidth - 82,
        height: 0.6,
        borderColor: rgb(0.941, 0.941, 0.941),
        borderWidth: 0.6,
      });
    });
    const editedPdfBuffer = await pdfDoc.save();
    

    PS:pdf-lib 如果需要使用特定中文字体,会把字体打包到 PDF 文件中,导致文件大小激增。可以先用字体裁剪工具裁剪字体,只裁剪出会用到的少数字符。

    实现效果如图:

    使用puppeteer生成自动分页PDF指南

    这样就完美实现需求啦!

    其他

    1. 防止特定内容分页

    有些内容可能不希望被自动分页,可以用css属性 page-break-inside:avoid; 控制。

    2. 垂直方向的 margin 属性导致内容

    垂直方向 margin 属性有时候可能会导致内容错位,因为 Chrome 自动分页的时候没有办法帮你自动分割 margin 。
    于是使用了一点小技巧,使用空的占位 div 来代替 margin 。

    <!-- vue组件 --> 
    <div class="place-holder">
      <div
        class="place-holder-item"
        style="width: 100%; height: 1px;"
        v-for="(item, index) in Array(height)"
        :key="index"
      ></div>
    </div>
    
    <!-- 使用 --> 
    <placeholder :height="30" />
    

    代码地址

    最后附上 demo 代码地址,对你有帮助的话麻烦star一下。如果文章有说得不对的地方,烦请指正。
    Demo GitHub地址


    下载网 » 使用puppeteer生成自动分页PDF指南

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元