pdfmake 生成平铺式水印:核心方法与优化

需求背景

公司产品的项目上要求给导出的 pdf 文件添加平铺式水印,当前项目使用的 pdf 导出插件是 pdfmake ,但是该插件并没有实现平铺式水印,官方自带的水印效果无法满足项目要求,需要自行编写水印生成代码

源码分析

通过研究 pdfmake 源码中的 createpdf,可以得知内部实现 pdf 生成是通过 _createdoc 方法创建 pdfkit 实例,并使用该插件的 API 完成相关的文档操作,进一步研究得知 _createdoc 方法还提供了一个回调函数,通过改回调函数可以对文档进行二次交互,进而可以考虑在回调函数中操作文档生成平铺式水印的思路,最后通过对 download 源码的理解实现 save 方法将添加水印后的文档导出为 pdf 文件

源码参考

实现代码

完整的实现代码如下所示,该实现依赖于 pdfmake": "^0.2.13",请自行安装相关依赖,其中用于文件保存的 FileSaver.js 可从 github 上下载:github.com/eligrey/Fil...

ts 复制代码
import pdfMake, { createPdf } from 'pdfmake' // 基于 v0.2
import pdfFonts from 'pdfmake/build/vfs_fonts'
import saveAs from './FileSaver'

pdfMake.vfs = pdfFonts.pdfMake.vfs

export class PDFFile {
  private readonly pdfDocument: any = null
  private readonly _docDefinition: any = {}
  /**
   * Creates an instance of PDFFile.
   * @param {*} docDefinition reference to https://pdfmake.github.io/docs/0.1/document-definition-object/
   * @memberof PDFFile
   */
  constructor(docDefinition: any) {
    this._docDefinition = docDefinition
    // 导入自定义中文字体包,如果不导入的话使用中文字体会有问题,中文字体包请自行下载
    const root = '/'
    const fonts = {
      commonFont: {
        normal: root + 'fonts/SourceHanSansSC-Normal-2.otf',
        bold: root + 'fonts/SourceHanSansSC-Medium-2.otf',
        italics: root + 'fonts/SourceHanSansSC-Normal-2.otf',
        bolditalics: root + 'fonts/SourceHanSansSC-Medium-2.otf',
      },
    }
    this.pdfDocument = createPdf(
      {
        ...docDefinition,
        watermark: undefined, // 水印另外实现,且只支持文本水印
      },
      null,
      fonts
    )
  }

  /**
   * 保存文档
   *
   * @param {boolean} download 是否自动下载文档,如果为否则返回一个 blob 对象
   * @param {string} [fileName] 文档名称名称
   * @return {*} 
   * @memberof PDFFile
   */
  public save(download: boolean, fileName?: string) {
    return new Promise((resolve, reject) => {
      // 使用 pdfmake 内部的 _createDoc 以获取 pdfkit 实例用于生成水印
      // doc options reference to https://pdfkit.org/docs/getting_started.html#switching_to_previous_pages
      // To use it, just pass bufferPages: true as an option to the PDFDocument constructor. 
      // Then, you can call doc.switchToPage(pageNumber) to switch to a previous page (page numbers start at 0).
      this.pdfDocument._createDoc({ bufferPages: true }, (doc: any) => {
        // console.log(this.pdfDocument, doc)
        const rejectTimeout = setTimeout(() => reject('Timeout!'), 5000) // 等待超时未能正常导出判定为失败
        
        const range = doc.bufferedPageRange() // { start: number, count: number }

        // loop throgh all pages
        const { text } = this._docDefinition.watermark

        if (text) {
          // 给所有页面添加平铺式水印
          for (let i = 0; i < range.start + range.count; i++) {
            const watermark = Object.assign({
              color: 'black',
              opacity: 0.2,
              angle: -50,
              fontSize: 14
            }, this._docDefinition.watermark)
            doc.switchToPage(i)
            doc.fill(watermark.color)
            doc.opacity(watermark.opacity)
            doc.rotate(watermark.angle, { origin: [doc.page.width / 2, doc.page.height / 2] })
            doc.fontSize(watermark.fontSize)
            // 获取当前页宽高
            const { width, height } = doc.page
            const margin = [200, 100] // 水平/垂直间距
            for (let ix = 0; ix <= width + margin[0] + 70; ix += margin[0]) {
              // 水印横向间隔
              let lineNum = 0
              for (let iy = 0; iy <= height; iy += margin[1]) {
                // 水印纵向间隔
                lineNum++
                const pos = [lineNum & 1 ? ix - 70 : ix, iy]
                // console.log(pos)
                doc.text(text, pos[0], pos[1], { lineBreak: false })
              }
            }
          }
        }
        this.pdfDocument._flushDoc(doc, (buffer: any) => {
          // get a blob you can do whatever you like with
          const blob = this.pdfDocument._bufferToBlob(buffer)
          clearTimeout(rejectTimeout)
          if (download) {
            saveAs(blob, fileName + '.pdf')
            resolve(true)
          } else {
            resolve(blob)
          }
        })
      })
    })
  }
}

使用示例

ts 复制代码
const watermark = {
     text: 'your watermark text',
}
const pdfFile = new PDFFile({
    // 这里除了增加 watermark 属性外,其他属性和官方文档完全一致
    watermark,
    content: pdfContent,
    defaultStyle: {
        font: 'commonFont'
    }
})
pdfFile.save(true, '导出示例')

实现效果

相关推荐
search73 小时前
前端设计:CRG 2--CDC检查
前端·芯片设计
松涛和鸣3 小时前
DAY33 Linux Thread Synchronization and Mutual Exclusion
linux·运维·服务器·前端·数据结构·哈希算法
逛逛GitHub3 小时前
我把公众号文章导入了腾讯 ima,可以对话找开源项目了。
前端·github
lionliu05193 小时前
JavaScript 变量声明最佳实践
前端·javascript·vue.js
源去_云走3 小时前
自建 Iconfy API 服务:解决国内访问不稳定问题
前端·容器·npm·node.js
AAA阿giao3 小时前
从零开始学 React:用搭积木的方式构建你的第一个网页!
前端·javascript·学习·react.js·前端框架·vite·jsx
遇到困难睡大觉哈哈3 小时前
Harmony OS Web 组件:如何在新窗口中打开网页(实战分享)
前端·华为·harmonyos
你脸上有BUG4 小时前
【工程化】前端打包时间优化
前端
TeleostNaCl4 小时前
Google Chrome 浏览器历史记录的存储位置
前端·chrome·经验分享