1.引入pdf.js文件

官网地址:PDF.js

有对应的示例git clone后,将viewer.html   open whith live server

注:若针对pdf还有个性化的功能,不建议直接引入pdf.js 方法,里面的事件都是原生js的写法,而且有关pdf的配置也很多

2. pdfjs-dist

(1)安装 pdfjs-dist (版本2.0.943,其余版本都出现过问题)

npm i pdfjs-dist@2.0.943

(2)引用 pdfjs-dist

import * as pdfjsLib from 'pdfjs-dist';

const workerSrc = import('pdfjs-dist/build/pdf.worker.entry');
export default () => {
  pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
  // pdf 资源
  const [pdfRefList, setPdfRefList] = useState([]);
  // 当前页码
  const [currentPage, setCurrentPage] = useState(1);
  
  const getDetail = (pageNum, href) => {
    const loadingTask = pdfjsLib.getDocument({
      url: href,  // pdf文件地址 或者 请求资源地址
      cMapUrl: 'https://unpkg.com/pdfjs-dist@2.0.943/cmaps/', // 使用cdn加载pdf.js提供的字体文件。
      cMapPacked: true, // 此参数需要设为true 
    });
    loadingTask.promise.then((loadedPdf) => {
      const pdf = [{ id: pageNum, detail: loadedPdf }];
      setPdfRefList(pdf);
      setCurrentPage(pageNum);
    }, function (reason) {
      console.log(reason);
    });
  }

 useEffect(() => {
   pdfRefList&& pdfRefList.map((pdf) => {
      pdf.detail && pdf.detail.getPage(pageNum).then(function (page) {
        const viewport = page.getViewport(1.5);
        const canvas = document.getElementById('canvas' + pdf.id);
        canvas.height = viewport.height;
        canvas.width = viewport.width;
        const canvasContext = canvas.getContext('2d')
        const renderContext = {
          canvasContext: canvasContext,
          viewport: viewport,
          background: background === '#303033' ? '#999999' : background,
        };
        // background 可修改canvas背景色
        page.render(renderContext);

        // 显示文本可进行复制
        // page.render(renderContext).then(() => {
        //   return page.getTextContent()
        // }).then((textContent) => {
        //   // console.log(textContent) 解析出dom元素
        //   const pageDiv = document.getElementById('canvas')
        //   console.log(viewport)
        //   // 创建文本图层div
        //   const textLayerDiv = document.createElement('div')
        //   textLayerDiv.setAttribute('class', 'textLayer');
        //   textLayerDiv.id = 'textLayer' + pageNum
        //   textLayerDiv.style.height = viewport.height + 'px';
        //   textLayerDiv.style.width = viewport.width + 'px';
        //   // 将文本图层div添加至每页pdf的div中
        //   pageDiv.appendChild(textLayerDiv)
        //   // 创建新的TextLayerBuilder实例
        //   let textLayer = new TextLayerBuilder({
        //     textLayerDiv: textLayerDiv,
        //     pageIndex: 1,
        //     viewport: viewport
        //   })
        //   textLayer.setTextContent(textContent)
        //   textLayer.render()
        // });

      });
    })
  }, [pdfRefList])
  return (<div class="pdfViewer">
              <div class="viewer">
                {pdfRefList && pdfRefList.map((pdf) => {
                  const markIndex = bookMarkList.findIndex((book) => (book.chapterId === pdf.id));
                  return (
                    <div class="page"
                      key={pdf.id}>
                      <div class="canvasWrapper">
                        <canvas id={"canvas" + pdf.id}></canvas>
                      </div>
                    </div>
                  )
                })}
              </div>)
}

文本复制有个问题:当pdf缩小时  scale < 1,显示的textLayer 不会进行缩小、回导致文本超出canvas显示的宽度

3.移动端 引用 pdfjs-dist 分辨率问题

getViewport(1) 获取到的是原pdf大小,原pdf宽度小于移动端h5宽度,也就是scale<1时,加载出来的pdf 分辨率很低,是模糊的

解决不降低pdf分辨率,按照手机屏幕展示

AbortController 是一个用于在 JavaScript 中处理异步操作的控制器,可以用于取消异步任务,当pdf列表 pdfRefList 更新时,canvas渲染绘制可能还没有结束,这时候需要取消当前绘制的canvas,重新根据pdfRefList加载渲染

  useEffect(() => {
    if (!utils.helper.isEmptyObj(pdfRefList)){
      const abortController = new AbortController();
      abortControllerRef.current = abortController;
      renderPage(pdfRefList, pdfRefList[0], window.previousBackgroundColor, settings.theme, settings.type, abortController);
    }
    return () => {
      abortControllerRef.current.abort(); // Abort the current rendering
      abortControllerRef.current = new AbortController(); // Create a new AbortController for future rendering
    };
  }, [pdfRefList, settings.theme, window.previousBackgroundColor, settings.type]);


  const isCanvasEmpty = (canvas) => {
    const ctx = canvas.getContext('2d');
    if (canvas.width) {
      const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
      const data = imageData.data;
      for (let i = 0; i < data.length; i += 4) {
        // 检查每个像素的透明度
        if (data[i + 3] !== 0) {
          return false; // 如果有非透明像素,则 Canvas 不为空
        }
      }
    }
    return true; // 所有像素都是透明的,Canvas 为空
  }
  const renderPage = (pdfList, pdf, previousBackgroundColor, background, type, abortController) => {
    return new Promise((resolve, reject) => {
      const pageId = pdf.id % bookInfo.splitSize === 0 ? bookInfo.splitSize : pdf.id % bookInfo.splitSize;
      pdf.detail && pdf.detail.getPage(pageId).then(function (page) {
        const canvas = document.getElementById(`canvas_${copyRightId}_${pdf.id}_${type}`);
        if (canvas && !isCanvasEmpty(canvas) && previousBackgroundColor == background) {
          const index = pdfList.findIndex((v) => (v.id == pdf.id));
          if (index < pdfList.length - 1) {
            renderPage(pdfList, pdfList[index + 1], previousBackgroundColor, background, type, abortController)
          } else {
            window.previousBackgroundColor = settings.theme;
            resolve()
          }
        } else if (canvas) {
          const vp = page.getViewport(1);
          if (!vp.width) {
            vp.width = vp.viewBox[2];
            vp.height = vp.viewBox[3];
            vp.scale = 1;
            vp.transform = [1, 0, 0, -1, 0, vp.viewBox[3]]
          }
          let ctx = canvas.getContext("2d");
          let dpr = window.devicePixelRatio || 1;
          let bsr =
            ctx.webkitBackingStorePixelRatio ||
            ctx.mozBackingStorePixelRatio ||
            ctx.msBackingStorePixelRatio ||
            ctx.oBackingStorePixelRatio ||
            ctx.backingStorePixelRatio ||
            1;
          let ratio = Number(utils.caculate.accDiv(dpr, bsr));
          const scale = Number(utils.caculate.accDiv(window.innerWidth, vp.width))
          let viewport = page.getViewport(scale);
          if (!viewport.width) {
            viewport.width = utils.caculate.accMul(viewport.viewBox[2], scale);
            viewport.height = utils.caculate.accMul(viewport.viewBox[3], scale);
            viewport.scale = scale;
            viewport.transform = [scale, 0, 0, -scale, 0, utils.caculate.accMul(viewport.viewBox[3], scale)]
          }
          canvas.width = viewport.width * ratio;
          canvas.height = viewport.height * ratio;
          canvas.style.width = viewport.width + "px";
          ctx.setTransform(ratio, 0, 0, ratio, 0, 0);
          let renderContext = {
            canvasContext: ctx,
            viewport: viewport,
            background: background === '#000000' ? 'rgb(255,255,255,0.9)' : background,
          };
          const renderTask = page.render(renderContext);
          const abortFn = () => {
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            canvas.width = 0;
            canvas.height = 0;
            canvas.style.width = 0;
            renderTask.cancel();
            reject('cancel');
          }
          abortController.signal.addEventListener('abort', abortFn);
          renderTask.promise.then(() => {
            abortController.signal.removeEventListener('abort', abortFn)
            const index = pdfList.findIndex((v) => (v.id == pdf.id));
            if (index < pdfList.length - 1) {
              renderPage(pdfList, pdfList[index + 1], previousBackgroundColor, background, type, abortController)
            } else {
              window.previousBackgroundColor = settings.theme;
              resolve()
            }
          }).catch((error) => {
            reject(error);
          });
        }
      }) 
    })
  };

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐