pdf预览打印实践

542 阅读3分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

前言

最近这段时间接受了一个业务需求,实现网页内容的pdf预览打印,感觉非常有必要总结一篇文章作为文档来记录一下,以防止大家碰到相同的问题。

pdf预览

内容分页

内容分页?为什么要分页?直接将页面转换为图片,插入到pdf中,不就完事大吉了么。好吧,我怂了,客户爸爸的需求还是要努力实现的。

其实内容分页大致有两种方式:

  • 将内容导出一张图片,然后通过偏移不同的位置插入到不同的页面中

  • 将内容提前按照页面大小切割好,分别插入对应的页面中

两种方式用到的api基本一致,区别只是思路,我们下面针对第二种方式进行详细的说明。

  1. 将网页导出为图片

这里用到了 html2canvas,具体api使用,可以通过传送门前往查看。

    html2canvas(container, {
      windowWidth: container.offsetWidth, // 计算元素宽高,防止出现空白页
      windowHeight: container.scrollHeight,
      allowTaint: false, // 允许不同源的图片污染画布
      useCORS: true, // 开启跨域,解决图片不显示问题
      scale: 2
    }).then((canvas) => { });

细心的小伙伴可能注意到了,我采用了2倍率,这样做的目的是什么呢?其实就是为了防止导出的pdf内容失真,如果直接使用1倍图,这样有可能出现失真的情况。

  1. 将图片进行切割
    imgContainer.getContext('2d')
        .putImageData(ctx.getImageData(0, renderedHeight, canvasWidth, currentImgHeight), 0, 0);

    const currentPageHeight = Math.min(A4_HEIGHT, A4_WIDTH * currentImgHeight / canvasWidth);

    pdf.addImage(imgContainer.toDataURL('image/jpeg', 1.0), 'JPEG', 19, 21, A4_WIDTH, currentPageHeight);

这里需要注意的是,需要进行宽高的换算,宽高比是A4纸的宽高比为准进行计算。特别是对于最后一页来说有其特殊,可能会存在不足一页的情况,这时如果不进行处理,可能出现竖向拉伸的情况,所以计算页面的像素高度时候,需要取A4纸高度和图片的实际高度的最小值。

  1. pdf添加导出
    pdf.addPage();

    pdf.save(`pdf文件名称_${Date.now()}.pdf`);

到这里其实剩下的就很简单了,只需要调用相关的api将pdf生成,然后导出即可。这里有个小的点需要特殊提一下,为了防止每次下载文件名都相同,下载时在文件后面添加一些不是很好看的数字内容,这里可以使用时间戳处理一下。

文字切割

上面的方式是实现了pdf内容的预览,也实现了pdf内容的下载,但是,其实还是有问题的。因为不知道每一页的分割点,所以就有可能在任何位置进行切割,这也就容易出现一种情况:将文字或者图片内容进行了切割。

3810316-7b7913ccf79aa06e.webp

我经过研究之后,找到一种解决这个问题的方法,并经过线上测试发现完全可行。

在写代码的时候,先给每一个最小不可分割的块添加一个标记(这个标记可以根据个人喜好自定义),然后在生成代码之前进行动态处理。

因为涉及到保密问题,这里就详细介绍一下处理的方法,然后粗略展示一下大致的处理代码。初始高度设置为0,然后不断往上添加带有自定义标记的块的高度,并拿这个高度跟A4纸的高度进行比较,一旦发现内容大于A4纸的高度,那么说明如果不换页的话,这个内容就会被切割。所以,这时我们通过插入剩余内容的空白块,这这块内容顶到下一页,这样就可以解决内容被切割的问题了。

    rows.forEach((row, index) => {
        const rowHeight = row.offsetHeight;

        if (rawPageHeight + rowHeight > pageHeight - 30) {
            // 插入空白元素
            const blankHeight = pageHeight - rawPageHeight;
            const blankElement = document.createElement('div');

            blankElement.style.width = '100%';
            blankElement.style.height = `${blankHeight}px`;

            row.parentElement.insertBefore(blankElement, row);

            // 开启新的页面
            rawPageHeight = rowHeight;
        } else {
            rawPageHeight += rowHeight;
        }
    });

在这里,在客户使用过程中还碰到了一点小问题也跟大家说一下,防止大家踩坑。就是随着页数的增多,页面切割位置出现细微的偏差,导致没有按照想想中的位置进行切割。

解决方法:在计算页面高度和块的高度时保留两位小数,页面的高度向下取整,块的高度向上取整(通过先乘以100,再除以100,来保留两位小数)。这种方法经过客户的大量使用测试,目前没有问题,说明完全可行。

好的,我们到这里就结束了。如果有任何疑问或者是更好的解决方案,欢迎大家在下面留言进行交流。