一个PDF在线预览小demo(文本可复制)

15,128 阅读3分钟

某天测试妹子告诉我,为啥这个pdf在ie里打不开?然后我去瞟了一眼:

This browser does not support inline PDFs. Please download the PDF to view it: Download PDF

之前没怎么接触过PDF在线阅读,这项目也是老项目,然后就去看这些家伙到底用的什么不兼容ie的PDF在线阅读插件的,发现是PDFObject,然而PDFObject并不支持在没有pdf渲染器的浏览器中呈现pdf
既然这样,那么我们就下载一个Adobe Reader吧!等等...难道每个客户我都要提醒下载这个reader吗?客户心理不会mmp吗?

在这里PDFObject的作者建议可以使用pdf.js 强制渲染PDF

You can also use PDF.js to force PDF rendering in-browser without a plugin, but that's outside the scope of PDFObject.

学习一个新东西,从demo做起

1. 安装PDF.js

此次demo直接使用vue-cli创建

  • 源码安装,从官网直接下载源码
  • 可以通过以下方式使用CDN

www.jsdelivr.com/package/npm…
cdnjs.com/libraries/p…
unpkg.com/pdfjs-dist/

  • 使用预构建版本,从npm下载
npm i pdfjs-dist

or

yarn add pdfjs-dist

2. 在vue中使用

import PDFJS from 'pdfjs-dist';

PDFJS.GlobalWorkerOptions.workerSrc = 'pdfjs-dist/build/pdf.worker.js';

为了提升解析和渲染PDF的性能,pdf.js引入了Web Workers,不了解web worker的童鞋也可以戳阮一峰老师的这篇文章

PDFJs通过canvas将pdf内容渲染到浏览器,所以我们需要在HTML中添加一个canvas

<canvas id="the-canvas" style="border:1px  solid black"></canvas>

然而此方式渲染出来的PDF内容无法被选中,怎么办呢?

使用getTextContent获取pdf内容,再通过TextLayerBuilder将内容渲染到canvas图层之上就可以选中了

首先调用 PDFJS.getDocument 方法获取一个PDFDocumentLoadingTask

getDocument (src)→{ PDFDocumentLoadingTask }

src类型可以为 string | TypedArray | DocumentInitParameters | PDFDataRangeTransport,在这里我们直接使用string类型,即pdf的url字符串

该方法虽然返回一个PDFDocumentLoadingTask,但是仍然可以像使用promise一样使用它,源码如下,在调用pdf.then的时候会返回一个Promise对象。

  class PDFDocumentLoadingTask {
    /* 
    省略若干代码
    */
    /**
     * @return {Promise}
     */
    get promise() {
      return this._capability.promise;
    }

    then(onFulfilled, onRejected) {
      deprecated('PDFDocumentLoadingTask.then method, ' +
                 'use the `promise` getter instead.');
      return this.promise.then.apply(this.promise, arguments);
    }
  }

拿到PDFDocumentLoadingTask之后,根据pdf页码获取当前页的pdf

let pdf = await PDFJS.getDocument(url) // URL为pdf的链接
let page = await pdf.getPage(num) // num 为页码,如 1

设置PDf文档的页面尺寸(展示比例)

let scale = 1.5;
let viewport = page.getViewport(scale);

渲染pdf

let renderContext = {
    canvasContext: context, // 此为canvas的context
    viewport: viewport
};
        
await page.render(renderContext); // 这里await是为了后面渲染pdf文本

然后拿到pdf的内容渲染成文本

let textContent = await page.getTextContent()
/* ... */
// 创建新的TextLayerBuilder实例
var textLayer = new TextLayerBuilder({
    textLayerDiv: textLayerDiv, // 放置文本的dom
    pageIndex: page.pageIndex, // pdf页码
    viewport: viewport
});

textLayer.setTextContent(textContent);

textLayer.render();

完整代码如下,接下来就是展示CV大法的时候了,可以参考example

巴拉巴拉...

import PDFJS from "pdfjs-dist";
import { TextLayerBuilder } from "pdfjs-dist/web/pdf_viewer";
import "pdfjs-dist/web/pdf_viewer.css";
PDFJS.GlobalWorkerOptions.workerSrc = "pdfjs-dist/build/pdf.worker.js";

var container;
export default {
  name: "HelloWorld",
  props: {
    msg: String
  },
  mounted() {
    this.$nextTick(() => {
        let url =
        "http://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf";
        this.getPDF(url);
    });
  },
  methods: {
    async getPDF(url) {
        let pdf = await PDFJS.getDocument(url)
        container = container || document.querySelector('#container')
        for(let i = 0; i < pdf.numPages; i++) {
            try{
                await this.rendPDF(pdf, i)
            } catch(e) {
                // console.error(e)
            }
        }
    },
    async renderPDF(pdf, num) {
        let page = await pdf.getPage(num)
        // 设置展示比例
        let scale = 1.5;
        let viewport = page.getViewport(scale);

        let pageDiv = document.createElement('div');
        pageDiv.setAttribute('id', 'page-' + (page.pageIndex + 1));
        pageDiv.setAttribute('style', 'position: relative');
        container.appendChild(pageDiv);

        let canvas = document.createElement('canvas');
        pageDiv.appendChild(canvas);
        let context = canvas.getContext('2d');
        canvas.height = viewport.height;
        canvas.width = viewport.width;
        
        let renderContext = {
            canvasContext: context,
            viewport: viewport
        };
        
        await page.render(renderContext);
        let textContent = await page.getTextContent()
        // 创建文本图层div
        const textLayerDiv = document.createElement('div');
        textLayerDiv.setAttribute('class', 'textLayer');
        textLayerDiv.setAttribute('style', `width: ${viewport.width}px; margin: 0 auto;`)
        // 将文本图层div添加至每页pdf的div中
        pageDiv.appendChild(textLayerDiv);
        
        // 创建新的TextLayerBuilder实例
        var textLayer = new TextLayerBuilder({
            textLayerDiv: textLayerDiv,
            pageIndex: page.pageIndex,
            viewport: viewport
        });
        
        textLayer.setTextContent(textContent);
        
        textLayer.render();

    }
  }
};

3.效果


可以看到内容文本已经被已纯文本的形式渲染出来了。

文本选择也木有问题。
最后附上demo地址
溜~