PDF.js實現個性化PDF渲染(文本復制)


原文鏈接: https://segmentfault.com/a/1190000016963084

前不久,產品經理提出要在界面上優雅地展示PDF文檔,當即就有了兩種實現方式:

實現方式一
使用embed標記來使用瀏覽器自帶的pdf工具。

這種實現方式優缺點都很明顯:
優點:自帶“打印”,“搜索”,“翻頁”等功能,強大且實現方便。
缺點:不同瀏覽器的pdf工具樣式不一,且無法滿足個性化需求,比如:禁止打印,下載等。

我們的產品經理是挑剔的😒,於是...

實現方式二
使用Mozilla的PDF.js,自定義展示PDF。

下面我們就細致講述一下使用PDF.js過程中遇到的問題。主要包括:

  • 基礎功能集成
  • 使用Text-Layers渲染

什么是PDF.JS

PDF.js是基於HTML5技術構建的,用於展示可移植文檔格式的文件(PDF),它可以在現代瀏覽器中使用且無需安裝任何第三方插件。

基礎功能集成

1️⃣引用

首先,引用PDF.js就遇到了問題,官網中提到通過CDN引用或者下載源碼至本地。
而我們並不想污染我們的index.html並且希望可以對每一個引用的框架有統一的版本管理。於是,我們搜尋到一個包:pdfjs-dist。

通過npm install pdfjs-dist,我們引入了PDF.js。

基礎功能有兩個必須引用的文件:

  • pdf.js
  • pdf.worker.js

如果使用CDN的方式,直接引用如下對應文件即可:

如果使用npm的方式,則在需要使用PDF.js的文件中如下引用:

import PDFJS from 'pdfjs-dist';

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

這兩個文件包含了獲取、解析和展示PDF文檔的方法,但是解析和渲染PDF需要較長的時間,可能會阻塞其它JS代碼的運行。

為解決該問題,pdf.js依賴了HTML5引入的Web Workers——通過從主線程中移除大量CPU操作(如解析和渲染)來提升性能。

PDF.js的API都會返回一個Promise,使得我們可以優雅的處理異步操作。

2️⃣使用

首先,我們需要在HTML中添加<canvas>元素以渲染PDF:

<canvas id="pdf-canvas"></canvas>

然后添加渲染PDF的js代碼:

var url = 'Helloworld.pdf';

PDFJS.getDocument(url).then((pdf) => {
    return pdf.getPage(1);
}).then((page) => {
    // 設置展示比例
    var scale = 1.5;
    // 獲取pdf尺寸
    var viewport = page.getViewport(scale);
    // 獲取需要渲染的元素
    var canvas = document.getElementById('pdf-canvas');
    var context = canvas.getContext('2d');
    canvas.height = viewport.height;
    canvas.width = viewport.width;
    
    var renderContext = {
        canvasContext: context,
        viewport: viewport
    };
    
    page.render(renderContext);
});

現在,PDF已經成功渲染在界面上了。我們來分析一下使用到的函數:

getDocument()用於異步獲取PDf文檔,發送多個Ajax請求以塊的形式下載文檔。它返回一個Promise,該Promise的成功回調傳遞一個對象,該對象包含PDF文檔的信息,該回調中的代碼將在完成PDf文檔獲取時執行。

getPage()用於獲取PDF文檔中的各個頁面。

getViewport()針對提供的展示比例,返回PDf文檔的頁面尺寸。

render()渲染PDF。

到這里,基本功能告一段落了。
滿心歡喜准備上線的時候,產品經理提出了另一個需求:文本復制。
然鵝。。。翻了好幾遍官方文檔,也沒有找到文本復制的方法,並且stackoverflow上有很多類似的問題。
在不斷的嘗試下,我們發現了Text-Layer

使用Text-Layers渲染

PDF.js支持在使用Canvas渲染的PDF頁面上渲染文本圖層。然而,這個功能需要用到額外的兩個文件:text_layer_builder.jstext_layer_builder.css。我們可以在GitHub的repo中獲取到。

如果是使用npm,則需要做如下引用:

import { TextLayerBuilder } from 'pdfjs-dist/web/pdf_viewer';
import 'pdfjs-dist/web/pdf_viewer.css';

現在,我們開始實現文本復制功能。

首先,創建渲染需要用到DOM節點:

<div id="container"></div>

div#container為最外層節點,在該div中,我們會為PDF的每個頁面創建自己的div,在每個頁面的div中,都會有Canvas元素。

接着,我們修改JS代碼:

var container, pageDiv;

function getPDF(url) {
    PDFJS.getDocument(url).then((pdf) => {
        pdfDoc = pdf;
        container = document.getElementById('container');
        for (var i = 1; i<= pdf.numPages; i++) {
            renderPDF(i);
        }
    })
}

function renderPDF(num) {
    pdf.getPage(num).then((page) => {
        var scale = 1.5;
        var viewport = page.getViewport(scale);
        pageDiv = document.createElement('div');
        pageDiv.setAttribute('id', 'page-' + (page.pageIndex + 1));
        pageDiv.setAttribute('style', 'position: relative');
        container.appendChild(pageDiv);
        var canvas = document.createElement('canvas');
        pageDiv.appendChild(canvas);
        var context = canvas.getContext('2d');
        canvas.height = viewport.height;
        canvas.width = view.width;
        
        var renderContext = {
            canvasContext: context,
            viewport: viewport
        };
        
        page.render(renderContext);
    });
}

以上代碼只是實現了多頁渲染,接下來,開始渲染文本圖層。我們需要將page.render(renderContext)修改為以下代碼:

page.render(renderContext).then(() => {
    return page.getTextContent();
}).then((textContent) => {
    // 創建文本圖層div
    const textLayerDiv = document.createElement('div');
    textLayerDiv.setAttribute('class', 'textLayer');
    // 將文本圖層div添加至每頁pdf的div中
    pageDiv.appendChild(textLayerDiv);
    
    // 創建新的TextLayerBuilder實例
    var textLayer = new TextLayerBuilder({
        textLayerDiv: textLayerDiv,
        pageIndex: page.pageIndex,
        viewport: viewport
    });
    
    textLayer.setTextContent(textContent);
    
    textLayer.render();
});

我們依舊來講解以下用到的幾個關鍵函數:

page.render()該函數返回一個當PDF頁面成功渲染到界面上時解析的promise,我們可以使用成功回調來渲染文本圖層。

page.getTextContent()該函數的成功回調會返回PDF頁面上的文本片段。

TextLayerBuilder該類的實例有兩個重要的方法。setTextContent()用於設置page.getTextContent()函數返回的文本片段;render()用於渲染文本圖層。

Bingo😎!通過以上改造,文本復制功能就實現了。官方文檔上可沒有這個小技巧哦。

PDF.js是一個很棒的工具,但無奈文檔寫的較為精簡,需要開發人員不斷探索PDF.js的強大功能。

如果這篇文章有幫助到您,記得點贊咯👍!

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM