Skip to content
mozilla/pdf.js的Typescript踩坑记录

大坑描述

在不改动源码的情况下,对支持Typescript的mozilla/pdf.js[email protected]

  1. 渲染模糊
  2. 文字遮罩层渲染
  3. 文字遮罩层缩放过小时文字遮罩层无法对齐

这三个问题的处理。

Typescript下pdf.js的引入

pdf渲染部分与文字遮罩层渲染部分的引入方法,不一定合理,仅供参考。

typescript
import { getDocument, GlobalWorkerOptions, renderTextLayer } from "pdfjs-dist";
import {
  PDFDocumentLoadingTask,
  PDFDocumentProxy,
  PDFPageProxy,
  TextContent
} from "pdfjs-dist/types/display/api";

由于pdf.js的ts版api文档不完善,目前ts版api信息只能在pdfjs-dist/types/display/api中查看源码及注释说明。

Typescript下pdf.js的pdf渲染

三个问题(坑)

  • 如何解决pdf渲染后模糊的问题

    通过设置getViewport()scale参数,例如getViewport({scale: 3}),以大比例渲染pdf,再通过css给canvas设置相对较小的 heightwidth缩小pdf,这样可以得到相对清晰的pdf。(注意:区分canvasheightwidth属性与其css的heightwidth的区别!)

    关键代码:

    typescript
    // scale参数建议设置为2以上,以大比例渲染pdf
    const viewport = page.getViewport({ scale: props.renderingScale });
    // PDF canvas初始化
    const canvas = document.getElementById("pdf-demo-canvas") as HTMLCanvasElement;
    // 通过css设置canvas的高度与宽度,达成缩放的目的
    canvas.style.height = canvasHeight + "px";
    canvas.style.width = canvasWidth + "px";
    const context = canvas.getContext("2d") as CanvasRenderingContext2D;
    // 使用viewport返回的高度与宽度作为canvas的高度与宽度属性
    canvas.height = viewport.height;
    canvas.width = viewport.width;
  • Typescript下的文字遮罩层渲染函数

    与Javascript调用pdf.js不同,使用Typescript时需要调用PDFPageProxygetTextContent()函数获取TextContent 类型的文字遮罩层参数,然后调用renderTextLayer()渲染文字遮罩层。

    关键代码:

    typescript
    page.render({...}).promise.then(() = >{
      ...
      // 调用getTextContent函数
      return page.getTextContent();
    }).then((textContent: TextContent) = >{
      ...
      // ts版 pdf.js遮罩层渲染设置
      renderTextLayer({...});
    },
    (err: Error) = >{
      console.log(err);
    });
  • 文字遮罩层在缩放比例过小时无法对齐的问题

    这里的思路跟解决渲染模糊问题有关。由于文字遮罩层需要使用与canvas同样的viewport的渲染,所以我们给文字遮罩层的css设置与 canvasheightwidth属性相同的高度与宽度,通过计算canvas的css高度(或宽度)与canvas的高度(或宽度) 属性得到一个缩放比,然后通过transform: scale()应用该缩放比达到文字遮罩层等比缩放的目的。最终解决了文字遮罩层在缩放比例过小时无法对齐的问题。

    关键代码:

    typescript
    // 创建文字遮罩层
    const textLayerDiv = document.createElement("div");
    textLayerDiv.setAttribute("class", "text-layer");
    // 设置文字遮罩层的css高度与宽度
    textLayerDiv.style.width = canvas.width + "px";
    textLayerDiv.style.height = canvas.height + "px";
    // 计算canvas的css宽度与canvas宽度属性的比例并设置scale缩放
    textLayerDiv.style.transform = `scale(${
      canvasWidth / canvas.width
    })`;
    textLayerDiv.style.transformOrigin = "left top";
    const pdfDiv = document.querySelector(".pdf") as HTMLElement;
    pdfDiv.appendChild(textLayerDiv);

主要代码

  • 由于是配合Vue3的composition api,所以getViewport()调用的是props中的renderingScale属性,可以自行修改为普通的 number
  • 仅列举主体代码,部分代码使用...做省略处理

HTML部分

html
<!-- Vue SFC -->
<template>
  <div class="pdfReader-wrap">
    ...
    <div class="pdf">
      <canvas id="pdf-demo-canvas"></canvas>
    </div>
    ...
  </div>
</template>

Typescript部分

typescript
/**
     * 移除已有的遮罩层,避免遮罩层重复渲染
     */
const removeTextLayer = () = >{
  const layer = document.querySelector(".text-layer");
  if (layer) layer.remove();
};
/**
     * 渲染pdf
     * @param canvasHeight canvas高度
     * @param canvasWidth canvas宽度
     * @param pdfLoadingTask getDocument返回的LoadingTask类型参数
     * @param pageNum 当前页数
     */
const renderPDF = (canvasHeight: number, canvasWidth: number, pdfLoadingTask: PDFDocumentLoadingTask, pageNum: number) = >{
  pdfLoadingTask.promise.then((pdf: PDFDocumentProxy) = >{
    pageCount.value = pdf.numPages;

    // 获取PDF的指定页面
    pdf.getPage(pageNum).then((page: PDFPageProxy) = >{
      // scale参数建议设置为2以上,以大比例渲染pdf
      const viewport = page.getViewport({
        scale: props.renderingScale
      });

      // PDF canvas初始化
      const canvas = document.getElementById("pdf-demo-canvas") as HTMLCanvasElement;
      // 通过css设置canvas的高度与宽度,达成缩放的目的
      canvas.style.height = canvasHeight + "px";
      canvas.style.width = canvasWidth + "px";
      const context = canvas.getContext("2d") as CanvasRenderingContext2D;
      // 使用viewport返回的高度与宽度作为canvas的高度与宽度属性
      canvas.height = viewport.height;
      canvas.width = viewport.width;

      // 渲染PDF canvas
      page.render({
        canvasContext: context,
        viewport: viewport,
        enableWebGL: true // 启用WebGL加速
      }).promise.then(() = >{
        return page.getTextContent();
      }).then((textContent: TextContent) = >{
        // 检测是否已有遮罩层,若存在则删除遮罩层,若getViewport的scale固定,则可以省略掉该函数
        removeTextLayer();

        // 创建文字遮罩层
        const textLayerDiv = document.createElement("div");
        textLayerDiv.setAttribute("class", "text-layer");
        textLayerDiv.style.width = canvas.width + "px";
        textLayerDiv.style.height = canvas.height + "px";
        // 计算文字遮罩层的缩放比例
        textLayerDiv.style.transform = `scale(${
          canvasWidth / canvas.width
        })`;
        textLayerDiv.style.transformOrigin = "left top";
        const pdfDiv = document.querySelector(".pdf") as HTMLElement;
        pdfDiv.appendChild(textLayerDiv);

        // ts版 pdf.js遮罩层渲染设置与文字遮罩层渲染
		renderTextLayer({
          textContent: textContent,
          container: textLayerDiv,
          viewport: viewport
        });
      },
      (err: Error) = >{
        console.log(err);
      });
    });
  },
  (err: Error) = >{
    console.error(err);
  });
};

CSS部分

canvas与文字遮罩层.text-layer均为.pdf的子元素,这里使用绝对定位,做到canvas.text-layer的重叠。

stylus
.pdfReader-wrap {
  
  ...

  .pdf {
    position relative
    width 90vw
    height 100%
    overflow auto
    color transparent

    canvas {
      position absolute
      top 0
      left 0
      overflow visible
    }

    >>> .text-layer {
      position absolute
      top 0
      left 0
      background transparent
      overflow hidden
      line-height 1

      span {
        position absolute
        white-space pre
        transform-origin 0 0
      }

      ::selection {
        background-color rgba(0, 0, 255, 0.3)
      }
    }
  }
}

上次更新于: