前端預覽PDF:PDFObject、PDF.js

這兩天有個需求,要在網頁上顯示PDF文件。首先< object >、< embed >、< iframe >這幾個標籤就能實現PDF文件的預覽(無需JavaScript支持),我還在網上看了下發現挺多第三方js庫可以實現PDF預覽,如jQuery Document Viewer、jquery.media.js、PDFObject、PDF.js等等。我大概看了下PDFObject、PDF.js這兩個庫,前者並不是一個PDF的渲染工具,而是通過使用< embed >標籤來顯示PDF;後者則會解析PDF文件內容,還能將PDF渲染成Canvas。

< iframe >

所有瀏覽器都支持 < iframe > 標籤,直接將src設置爲指定的PDF文件就可以預覽了。此外可以把需要的文本放置在 < iframe > 和 之間,這樣就可以應對無法理解 iframe 的瀏覽器,比如下面的代碼可以提供一個PDF的下載鏈接:

<iframe src="/index.pdf" width="100%" height="100%">

This browser does not support PDFs. Please download the PDF to view it: <a href="/index.pdf">Download PDF</a>

</iframe>

< embed >

< embed > 標籤定義嵌入的內容,比如插件。在HTML5中這個標籤有4個屬性:

屬性 描述
height pixels 設置嵌入內容的高度。
width pixels 設置嵌入內容的寬度。
type type 定義嵌入內容的類型。
src url 嵌入內容的 URL。

但是需要注意的是這個標籤不能提供回退方案,與< iframe > < / iframe >
不同,這個標籤是自閉合的的,也就是說如果瀏覽器不支持PDF的嵌入,那麼這個標籤的內容什麼都看不到。用法如下:

<embed src="/index.pdf" type="application/pdf" width="100%" height="100%">

< object >

< object >定義一個嵌入的對象,請使用此元素向頁面添加多媒體。此元素允許您規定插入 HTML 文檔中的對象的數據和參數,以及可用來顯示和操作數據的代碼。用於包含對象,比如圖像、音頻、視頻、Java applets、ActiveX、PDF 以及 Flash。幾乎所有主流瀏覽器都擁有部分對 < object > 標籤的支持。這個標籤在這裏的用法和< iframe >很小,也支持回退:

<object data="/index.php" type="application/pdf" width="100%" height="100%">

This browser does not support PDFs. Please download the PDF to view it: <a href="/index.pdf">Download PDF</a>

</object>

當然,結合< object >和< iframe >能提供一個更強大的回退方案:

<object data="/index.pdf" type="application/pdf" width="100%" height="100%">

<iframe src="/index.pdf" width="100%" height="100%" style="border: none;">

This browser does not support PDFs. Please download the PDF to view it: <a href="/index.pdf">Download PDF</a>

</iframe>

</object>

以上三個標籤是一種無需JavaScript支持的PDF預覽方案。下面提到的PDFObject和PDF.js都是js庫。

PDFObject

看官網上的介紹,PDFObject並不是一個PDF渲染工具,它也是通過< embed >標籤實現PDF預覽:

PDFObject is not a rendering engine. PDFObject just writes an < embed > element to the page, and relies on the browser or browser plugins to render the PDF. If the browser does not support embedded PDFs, PDFObject is not capable of forcing the browser to render the PDF.

PDFObject提供了一個PDFObject.supportsPDFs用於判斷該瀏覽器能否使用PDFObject:

if(PDFObject.supportsPDFs){
   console.log("Yay, this browser supports inline PDFs.");
} else {
   console.log("Boo, inline PDFs are not supported by this browser");
}

整個PDFObject使用起來非常簡單,完整代碼:

<!DOCTYPE html>
<html>
<head>
    <title>Show PDF</title>
    <meta charset="utf-8" />
    <script type="text/javascript" src='pdfobject.min.js'></script>
    <style type="text/css">
        html,body,#pdf_viewer{
            width: 100%;
            height: 100%;
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>
    <div id="pdf_viewer"></div>
</body>
<script type="text/javascript">
    if(PDFObject.supportsPDFs){
        // PDF嵌入到網頁
        PDFObject.embed("index.pdf", "#pdf_viewer" );
    } else {
        location.href = "/canvas";
    }
</script>
</html>

效果如下:
這裏寫圖片描述

PDF.js

PDF.js可以實現在html下直接瀏覽pdf文檔,是一款開源的pdf文檔讀取解析插件,非常強大,能將PDF文件渲染成Canvas。PDF.js主要包含兩個庫文件,一個pdf.js和一個pdf.worker.js,一個負責API解析,一個負責核心解析。
首先引入pdf.js文件<script type="text/javascript" src='pdf.js'></script>
PDF.js大部分用法都是基於Promise的,PDFJS.getDocument(url)方法返回的就是一個Promise:

    PDFJS.getDocument('../index.pdf').then(pdf=>{
        var numPages = pdf.numPages;
        var start = 1;
        renderPageAsync(pdf, numPages, start);
    });

Promise返回的pdf是一個PDFDocumentProxy對象官網API介紹是:

Proxy to a PDFDocument in the worker thread. Also, contains commonly used properties that can be read synchronously.

PDF的解析工作需要通過pdf.getPage(page)去執行,這個方法返回的也是一個Promise,因此可以通過async/await函數去逐頁解析PDF:

    async function renderPageAsync(pdf, numPages, current){
        for(let i=1; i<=numPages; i++){
            // 解析page
            let page = await pdf.getPage(i);
            // 渲染
            // ...
        }
    }

得到的page是一個PDFPageProxy對象,即Proxy to a PDFPage in the worker thread 。這個對象得到了這一頁的PDF解析結果,我們可以看下這個對象提供的方法:

方法 返回
getAnnotations A promise that is resolved with an {Array} of the annotation objects.
getTextContent That is resolved a TextContent object that represent the page text content.
getViewport Contains ‘width’ and ‘height’ properties along with transforms required for rendering.
render An object that contains the promise, which is resolved when the page finishes rendering.

我們可以試試調用getTextContent方法,並將其結果打印出來:

page.getTextContent().then(v=>console.log('page', v));

第一頁部分結果如下:

{
    "items": [
        {
            "str": "小冊子標題",
            "dir": "ltr",
            "width": 240,
            "height": 2304,
            "transform": [
                48,
                0,
                0,
                48,
                45.32495,
                679.04
            ],
            "fontName": "g_d0_f1"
        },
        {
            "str": " ",
            "dir": "ltr",
            "width": 9.600000000000001,
            "height": 2304,
            "transform": [
                48,
                0,
                0,
                48,
                285.325,
                679.04
            ],
            "fontName": "g_d0_f2"
        }
      ],
    "styles": {
        "g_d0_f1": {
            "fontFamily": "monospace",
            "ascent": 1.05810546875,
            "descent": -0.26171875,
            "vertical": false
        },
        "g_d0_f2": {
            "fontFamily": "sans-serif",
            "ascent": 0.74365234375,
            "descent": -0.25634765625
        }
    }
 }

我們可以發現,PDF.js將每頁文本的字符串、位置、字體都解析出來,感覺還是挺厲害的。

官網有個demo,還用到了官網提到的viewer.js(我認爲它的作用是對PDF.js渲染結果再次處理):http://mozilla.github.io/pdf.js/web/viewer.html,我看了一下它的HTML機構,首先底圖是一個Canvas,內容和PDF一樣(通過下面介紹的page.render方法可以得到),底圖之上是一個textLayer,我猜想這一層就是通過page.getTextContent()得到了字體的位置和樣式,再覆蓋在Canvas上:
這裏寫圖片描述
通過這種方式就能實現再預覽文件上選中文字(剛開始我還在納悶爲什麼渲染成Canvas還能選擇文字)
這裏寫圖片描述

將page渲染成Canvas是通過render方法實現的,代碼如下:

    async function renderPageAsync(pdf, numPages, current){
        console.log("renderPage async");
        for(let i=1; i<=numPages; i++){
            // page
            let page = await pdf.getPage(i);

            let scale = 1.5;
            let viewport = page.getViewport(scale);
            // Prepare canvas using PDF page dimensions.
            let canvas = document.createElement("canvas");
            let context = canvas.getContext('2d');
            document.body.appendChild(canvas);

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

            // Render PDF page into canvas context.
            let renderContext = {
                    canvasContext: context,
                    viewport: viewport
            };
            page.render(renderContext);
        }
    }

PDF.js是Mozilla實驗室的作品,感覺真的很強大!
我在碼雲上有個demo,結合了PDFObject和PDF.js。因爲PDFObject使用的< embed >標籤可以直接顯示PDF文件,速度很快;但是手機上很多瀏覽器不支持,比如微信的瀏覽器、小米瀏覽器,所以我就使用了PDF.js將其渲染成Canvas,速度與PDFObject相比慢多了,但至少能看。-_-||
demo地址:https://git.oschina.net/liuyaqi/PDFViewer.git

參考:
PDFObject:https://pdfobject.com
PDF.js: http://mozilla.github.io/pdf.js/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章