PDF 文件压缩及水印

946 阅读2分钟

前言

在实际场景需要给 PDF 文件进行压缩,由于文件内容比较敏感,不信任网上那些广告漫天的站点,所以自己来实现一个。

第一步,在 GitHub 上搜索是否有类似项目,找到一个使用 HTML 实现的 OpenToolKit/CompressPDF: Fast in-browser PDF compressor (github.com),但是其站点无法启动,没再进行维护更新了;

第二步,克隆源码看看别人是如何进行实现的。

第三方库

1、pdfkit-standalone 用于新建 PDF 文件,其中的 PDFDocument 没找到对应类,在项目中直接引入了;

2、blob-stream 配合 FileSaver 对文件进行处理的脚本,没找到对应的包,也在项目中直接引入了;

3、pdfjs 用于读取 PDF 文件内容,对应的包为 pdfjs-dist - npm (npmjs.com)

4、FileSaver 将压缩的文件进行保存,对应的 npm 包地址为 file-saver - npm (npmjs.com)

5、sortable 用于拖拽排序,这个不管它(属于 noise ,完全不用管,只管 PDF 相关内容)。

核心原理

1、input 将文件上传之后,通过 FileReader 读取并传达给 PDFjs;

2、通过 canvas 对加载的 PDF 当前页进行压缩;

3、新建一个空白 PDF,添加上一步处理的数据,最后进行保存。

function loadPdf(url: string, range: number, name: string) {
  const loadingTask = pdf.getDocument({
    url,
    disableRange: true
  })

  imgData.length = 0
 
  loadingTask.promise.then((pdfDoc) => {
    const totalPages = pdfDoc.numPages
    for (let i = 1; i <= totalPages; i++) {
      pdfDoc.getPage(i).then((page) => {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
        const viewport = page.getViewport({ scale: 1 });

        canvas.width = viewport.width
        canvas.height = viewport.height

        const renderContext = {
          canvasContext: ctx,
          viewport,
        }
        
        const renderTask = page.render(renderContext);

        renderTask.promise.then(function () {
          imgData.push({src: canvas.toDataURL("image/jpeg", +range), width: canvas.width, height: canvas.height})
          if(imgData.length === totalPages){
            img2Pdf(name, compressing)
          }
        })
      })
    }
  })
}

function img2Pdf(name: string) {
  const options = {
    autoFirstPage: false,
    compress: false
  }

  const doc = new PDFDocument(options)
  const stream = doc.pipe(blobStream())

    for (let i = 0; i < imgData.length; i++) {
      const {src, width, height} = imgData[i]
      doc.addPage({
        size: [width, height],
      })
      doc.image(src, 0, 0)
  }

  doc.end()

  stream.on("finish", function () {
    var output_blob = stream.toBlob("application/pdf")
    saveAs(output_blob, name)
  });
}

具体代码详情请看 Efrice/compress-pdf: compress pdf without sever (github.com)

结合图片加水印

之前做了一个图片加水印的功能,也是通过 canvas 来实现的,那么 PDF 肯定也可以加水印。同理,图片也可以压缩。最后,升级了 watermark 的功能,在线使用,感兴趣源码请移步代码仓 Efrice/watermark: Add watermark to image/pdf, or compression. (github.com)