Nodejs+Vue下的pdfjs應(yīng)用

????????pdfjs是一個(gè)用JavaScript實(shí)現(xiàn)的PDF文檔查看器,它可以將PDF文檔轉(zhuǎn)換為可操作的HTML5元素翰舌,由此可以:在不需要安裝Adobe Reader的前提下在瀏覽器上預(yù)覽PDF文件恋博,解析出PDF文件中的文字淋纲、圖像等要素進(jìn)行諸如自動(dòng)審核匈挖、文檔修訂等諸多深度應(yīng)用顷级。

????pdfjs官網(wǎng)地址:https://mozilla.github.io/pdf.js/

一胸囱、初識(shí)pdfjs

1、pdfjs的優(yōu)點(diǎn):

? ? ? ?應(yīng)用靈活隔披,pdfjs可以安裝在前端枢冤,也可以安裝在后端鸠姨,這都取決于你的設(shè)計(jì)方案,當(dāng)然要特別注意的是如果是安裝在前端則getDocument(參數(shù))中的“參數(shù)”必須是URL或流數(shù)據(jù)掏导,不能為物理地址(這是由于瀏覽器的安全保護(hù)機(jī)制決定的)享怀;如果是安裝在后端,則“參數(shù)”必須是物理地址或流數(shù)據(jù)趟咆,不能為URL(否則會(huì)報(bào)xmlhttp不支持之類的錯(cuò)誤)添瓷。

? ? ? ?編碼簡(jiǎn)單,在熟悉了pdfjs應(yīng)用的情況下值纱,其代碼實(shí)現(xiàn)還是非常簡(jiǎn)單的鳞贷,而且可以很容易嵌入到各類應(yīng)用中,其代碼量也非常少虐唠。

2搀愧、pdfjs的缺點(diǎn):

? ? ? ?缺文檔,缺乏有效文檔資料疆偿,導(dǎo)致應(yīng)用規(guī)劃咱筛、編碼實(shí)現(xiàn)都比較困難,所以只能找成熟源代碼參考杆故,這也正是本文的價(jià)值所在迅箩。

? ? ? ?難下載,版本兼容性問(wèn)題導(dǎo)致下載安裝困難处铛,不同版本的pdfjs對(duì)nodejs版本以及nodejs內(nèi)各個(gè)組件的版本要求都有莫名的差異饲趋,導(dǎo)致npm install pdfjs-dist時(shí)失敗,解決辦法有兩個(gè)撤蟆,一是都用最新版本奕塑,這適用于全新搭建的開發(fā)環(huán)境;二是反復(fù)試驗(yàn)不同版本的安裝家肯,直到找到一個(gè)可以成功下載安裝的版本(筆者就是用的這個(gè)辦法龄砰,哭…)。

3息楔、應(yīng)用場(chǎng)景

? ? ? ?在線展示PDF文件寝贡,不需要安裝pdfreader的前提下就可以在網(wǎng)頁(yè)上瀏覽PDF文件扒披。這是這個(gè)組件的最主要應(yīng)用值依。

? ? ? ?自動(dòng)審核PDF文件圃泡,可以解析出PDF文件中的文字、圖片等要素信息愿险,然后通過(guò)預(yù)定義的規(guī)則進(jìn)行自動(dòng)化審核颇蜡,如商業(yè)合同的合規(guī)性審核等。這是能體現(xiàn)這個(gè)組件最大價(jià)值的應(yīng)用辆亏。

? ? ? ?其他风秤,如在線修訂、批注PDF文件……扮叨,這些應(yīng)用由于還需要其他組件的支持缤弦,本文中未做具體介紹。

二彻磁、應(yīng)用設(shè)計(jì)思路

?? pdfjs依據(jù)不同的應(yīng)用場(chǎng)景碍沐,有如下三種設(shè)計(jì)思路:

?? 1、展示類應(yīng)用衷蜓,pdfjs只能安裝在前端累提,無(wú)需pdfreader即可通過(guò)瀏覽器在網(wǎng)頁(yè)上瀏覽PDF文件。

?? 2磁浇、簡(jiǎn)單處理類應(yīng)用斋陪,主要是解析PDF中的文本、圖片等要素進(jìn)行快速加工處理置吓,pdfjs一般安裝在前端无虚,當(dāng)然也可以安裝后端,具體取決于和其他業(yè)務(wù)功能的銜接需求衍锚。

?? 3友题、復(fù)雜處理類應(yīng)用,和“簡(jiǎn)單處理類應(yīng)用”的區(qū)別就是加工處理過(guò)程比較復(fù)雜构拳、時(shí)間比較長(zhǎng)咆爽,此類應(yīng)用的pdfjs安裝在后端,可以確保具有良好的界面友好性置森。

三斗埂、pdfjs的安裝

? ? ?在項(xiàng)目的當(dāng)前目錄下執(zhí)行如下命令即可完成下載和安裝:

npm install pdfjs-dist?????????? --save-dev???

? ? ?當(dāng)然也可以下載安裝指定版本的pdfjs:

npm install pdfjs-dist@2.2.228 --save-dev??

? ? ?補(bǔ)充說(shuō)明:下載安裝命令很簡(jiǎn)單,但這個(gè)過(guò)程折騰了筆者好幾天凫海,原因是筆者是在現(xiàn)有應(yīng)用上新增安裝pdfjs呛凶,所以總是出現(xiàn)nodejs版本不匹配、webpack版本不匹配等等之類問(wèn)題導(dǎo)致安裝失敗行贪,經(jīng)過(guò)試驗(yàn)安裝不同的版本終于找到適合的版本才安裝成功(筆者的nodejs是10.2.0,成功安裝的版本是pdfjs-dist2.2.228)漾稀。

?? 以下簡(jiǎn)單列舉幾個(gè)安裝失敗的錯(cuò)誤提示:

? ■ Unsupported platform for fsevents模闲,需要升級(jí)nodejs版本。

? ■?npm ERR! errno CERT_HAS_EXPIRED崭捍,證書過(guò)期問(wèn)題尸折,需要升級(jí)npm

? ■?npm ERR! Cannot read properties of null,需要升級(jí)npm和nodejs的版本

? ■?ReferenceError:primordials is not defined殷蛇,和nodejs版本不兼容

四实夹、編碼準(zhǔn)備

?? 在編寫實(shí)現(xiàn)具體業(yè)務(wù)邏輯的代碼之前,需要做好如下三項(xiàng)準(zhǔn)備工作:

?? 1粒梦、引入組件

?? 在以上成功下載安裝的前提下亮航,通過(guò)以下命令在vue中首先需要引入pdfjs-dist組件:

import * as pdfjsLib from "pdfjs-dist";?? //引入pdfjs-dist組件

?? 2、拷貝文件

?? 為了提供渲染效率匀们,需要設(shè)置workerSrc參數(shù)缴淋,雖然如果不設(shè)置該參數(shù)應(yīng)用程序也能正常運(yùn)行(不過(guò)筆者經(jīng)過(guò)測(cè)試發(fā)現(xiàn),如果不設(shè)置workserSrc泄朴,有時(shí)候程序會(huì)給出出錯(cuò)信息)重抖,但渲染的效率會(huì)比較差。

?? 因此建議大家都需要配置workerSrc參數(shù)叼旋,為了配置該參數(shù)需要首先將一個(gè)文件從pdfjs-dist的安裝目錄(node_modules\pdfjs-dist\build\ pdf.worker.min.js)拷貝到應(yīng)用項(xiàng)目的目錄中(src\statics\pdfjs_dist\):

?? 1)仇哆、pdfjs-dist在下載安裝成功后,一般安裝在當(dāng)前項(xiàng)目的node_modules目錄下夫植,目錄名稱就是pdfjs-dist讹剔。

?? 2)、需要拷貝的文件名稱一般為pdf.worker.min.js详民,但依賴于不同的pdfjs版本延欠,也可能為類似的其他名稱,如pdf.worker.js沈跨、pdf.worker.mjs等由捎;該文件存在于node_modules\pdfjs-dist\build目錄下。

3)饿凛、文件的目錄地址可以自行指定狞玛,只要確保可以訪問(wèn)即可涧窒,例如該目錄放置在src\statics下心肪,專門建立一個(gè)名為pdfjs_dist的目錄放置拷貝過(guò)來(lái)的文件,因此拷貝完成后就得到這個(gè)文件src\statics\pdfjs_dist\pdf.worker.min.js纠吴。

?? 3硬鞍、配置路徑

?? 在文件拷貝成功后,需要通過(guò)如下命令配置workserSrc參數(shù):

pdfjsLib.GlobalWorkerOptions.workerSrc ='statics/pdfjs_dist/pdf.worker.min.js';?//加速渲染配置

?? 1)、依據(jù)實(shí)際的拷貝目標(biāo)地址配置以上參數(shù)目錄

?? 2)固该、以上配置命令一般放置vue文件的mounted()中锅减,當(dāng)然也可以放在其他地方,只需要確保在創(chuàng)建pdf文件實(shí)例前能夠執(zhí)行該命令即可伐坏。

五怔匣、分頁(yè)預(yù)覽的實(shí)現(xiàn)

? ? ? 分頁(yè)預(yù)覽pdf是pdfjs組件最基礎(chǔ)應(yīng)用功能,要實(shí)現(xiàn)這個(gè)應(yīng)用功能著淆,需要如下幾個(gè)步驟:

? ? ?1)劫狠、準(zhǔn)備畫布拴疤,在<template>區(qū)創(chuàng)建pdf展示的容器—畫布

<canvas? ref="the_canvas" style="border: 1px solid black; direction: ltr;"></canvas>

? ? ?2)永部、定義畫布

? ? ?通過(guò)以下代碼得到準(zhǔn)備的畫布:

let canvas = that.$refs.the_canvas;?

let ctx = canvas.getContext('2d');

? ? ?3)、獲取pdf中指定頁(yè)面對(duì)象

? ? ?如果當(dāng)前pdfjs部署在前端呐矾,則以下“參數(shù)”是pdf文件的url苔埋,或是pdf文件的數(shù)據(jù)流;如果部署在后端蜒犯,則“參數(shù)”是pdf文件的物理地址组橄,或是pdf文件的數(shù)據(jù)流。

let loadingPdfDocument = pdfjsLib.getDocument(參數(shù));? // 創(chuàng)建pdf文件實(shí)例

let pdfDoc = await loadingPdfDocument.promise;? ? ? // pdf文件對(duì)象

let pageCount = this.pdfDoc.numPages;? ? ? ? //pdf文件總頁(yè)數(shù)

let page = await pdfDoc.getPage(頁(yè)號(hào));? ? ? ? ? //pdf文件中指定頁(yè)的數(shù)據(jù)對(duì)象

以上“頁(yè)號(hào)”為從1到pageCount之間的整數(shù)罚随。

? ? ?4)玉工、畫布初始化

? ? ?依據(jù)當(dāng)前頁(yè)面的大小初始化畫布,其中“縮放比例”為數(shù)字淘菩,如1為原始尺寸:

let viewport = page.getViewport({

? ? scale: 縮放比例,

});

let outputScale = window.devicePixelRatio || 1;

canvas.width = Math.floor(viewport.width * outputScale);

canvas.height = Math.floor(viewport.height * outputScale);

canvas.style.width = Math.floor(viewport.width) + "px";

canvas.style.height = Math.floor(viewport.height) + "px";

let transform = outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null;

let renderContext = {

? ? ? ? canvasContext: ctx,

? ? ? ? transform: transform,

? ? ? ? viewport: viewport,

};

? ? ?5)遵班、頁(yè)面渲染展示

? ? ?通過(guò)以下代碼將指定頁(yè)面渲染到畫布上:

page.render(renderContext).promise.then(function() {

? ? //渲染完成后的處理

});

? ? ?渲染完成后,可以隱藏加載進(jìn)度提示條等各類后續(xù)事項(xiàng)潮改,這些都可以在以上命令按鈕的程序體中實(shí)現(xiàn)狭郑。

? ? ?6)、補(bǔ)充說(shuō)明

? ? ?以上只是實(shí)現(xiàn)業(yè)務(wù)功能的核心代碼汇在,一般工程實(shí)踐中還需要加上分頁(yè)邏輯(前翻翰萨、后翻、首頁(yè)糕殉、尾頁(yè))亩鬼、縮放邏輯、顯示渲染進(jìn)度等阿蝶,這些請(qǐng)各位自行按需擴(kuò)展雳锋。

六、文字赡磅、圖片解析的實(shí)現(xiàn)

? ? ?解析pdf文件中的文字魄缚、圖片等要素以便進(jìn)行深入處理(如自動(dòng)審核),是最能體現(xiàn)pdfjs強(qiáng)大功能的應(yīng)用。

? ? ?1)冶匹、準(zhǔn)備容器

? ? ?在<template>區(qū)創(chuàng)建一個(gè)畫布和一個(gè)div元素习劫,其中畫布隱藏,作為指定一頁(yè)pdf對(duì)象的緩存嚼隘;div元素中存放解析出來(lái)的圖片诽里;解析出來(lái)的文字本代碼中未定義容器,讀者在實(shí)際應(yīng)用自行添加即可飞蛹。

<canvas ref="the_canvas" style="display:none;"></canvas>

<div ref="imghome"></div>

? ? ?2)谤狡、定義畫布

? ? ?和分頁(yè)預(yù)覽類似,通過(guò)以下代碼得到準(zhǔn)備的畫布:

let canvas = this.$refs.the_canvas;

let ctx = canvas.getContext('2d');

? ? ?3)卧檐、獲取pdf中指定頁(yè)面對(duì)象

? ? ?如果當(dāng)前pdfjs部署在前端墓懂,則以下“參數(shù)”是pdf文件的url,或是pdf文件的數(shù)據(jù)流霉囚;如果部署在后端捕仔,則“參數(shù)”是pdf文件的物理地址,或是pdf文件的數(shù)據(jù)流盈罐。

let loadingPdfDocument = pdfjsLib.getDocument(參數(shù));? // 創(chuàng)建pdf文件實(shí)例

let pdfDoc = await loadingPdfDocument.promise;? ? ? // pdf文件對(duì)象

let pageCount = this.pdfDoc.numPages;? ? ? ? //pdf文件總頁(yè)數(shù)

let page = await pdfDoc.getPage(頁(yè)號(hào));? ? ? ? ? //pdf文件中指定頁(yè)的數(shù)據(jù)對(duì)象

以上“頁(yè)號(hào)”為從1到pageCount之間的整數(shù)榜跌。

? ? ?4)、解析文本

? ? ?以下命令就可以獲得到指定“頁(yè)號(hào)”PDF頁(yè)面上的所有文本盅粪,并放在變量pagetext中:

let textContent = await page.getTextContent();

let pagetext = textContent.items.map(item => item.str).join(' ');

? ? ?5)钓葫、解析圖片

? ? ?以下代碼可以獲取到指定“頁(yè)號(hào)”PDF頁(yè)面上的所有圖片,并著一顯示到第一步準(zhǔn)備好的DIV元素中:

? ?? 獲取本頁(yè)的所有圖片名稱清單

let opList = await page.getOperatorList();

const imageNames = opList.fnArray.reduce((acc, curr, i) => {

? ? ? ? if ([pdfjsLib.OPS.paintImageXObject, pdfjsLib.OPS.paintJpegXObject, pdfjsLib.OPS.paintImageXObjectRepeat].includes(curr)) {

? ? ? ? ? acc.push(opList.argsArray[i][0]);

? ? ? ? }

? ? ? ? return acc;

? ? ? }, []);


? ?? 遍歷該清單中的每一張圖片

for (const imageName of imageNames) {

? ??將解析出來(lái)的圖片渲染到畫布上

? ? let imageUnit8Array = image.data;

? ? let imageWidth = image.width;

? ? let imageHeight = image.height;

? ? let imageUint8ArrayWithAlphaChanel = that.addAlpha(imageUnit8Array, imageWidth, imageHeight);

? ? let imageData = new ImageData(imageUint8ArrayWithAlphaChanel, imageWidth, imageHeight);

? ? canvas.width = imageWidth;

? ? canvas.height = imageHeight;

ctx.putImageData(imageData, 0, 0);

? ??將畫布上的圖片轉(zhuǎn)放到DIV元素中

? ? let img_obj = this.$refs.imghome.appendChild(new Image());

? ? img_obj.width = imageWidth * that.scale;

? ? img_obj.height = imageHeight * that.scale;

? ? img_obj.style.border = '2px solid black';

? ? img_obj.style.marginRight = "5px";

? ? let aa = canvas.toDataURL('image/jpeg', 1.0);

? ? img_obj.src = aa;

};

6)票顾、圖片解析函數(shù)

? ?圖片解析需要的一個(gè)函數(shù):

addAlpha(unit8Array, imageWidth, imageHeight) {

? ? ? let newImageData = new Uint8ClampedArray(imageWidth * imageHeight * 4);

? ? ? for (let j = 0, k = 0, jj = imageWidth * imageHeight * 4; j < jj;) {

? ? ? ? newImageData[j++] = unit8Array[k++];

? ? ? ? newImageData[j++] = unit8Array[k++];

? ? ? ? newImageData[j++] = unit8Array[k++];

? ? ? ? newImageData[j++] = 255;

? ? ? };

? ? ? return newImageData;

? ? },

七础浮、總結(jié)

? ? Nodejs可以實(shí)現(xiàn)所有的應(yīng)用,如果有實(shí)現(xiàn)不了的應(yīng)用库物,說(shuō)明你還沒有找到對(duì)應(yīng)的組件……

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末霸旗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子戚揭,更是在濱河造成了極大的恐慌诱告,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件民晒,死亡現(xiàn)場(chǎng)離奇詭異精居,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)潜必,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門靴姿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人磁滚,你說(shuō)我怎么就攤上這事佛吓∠恚” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵维雇,是天一觀的道長(zhǎng)淤刃。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吱型,這世上最難降的妖魔是什么逸贾? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮津滞,結(jié)果婚禮上铝侵,老公的妹妹穿的比我還像新娘。我一直安慰自己触徐,他們只是感情好咪鲜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著锌介,像睡著了一般嗜诀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孔祸,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音发皿,去河邊找鬼崔慧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛穴墅,可吹牛的內(nèi)容都是我干的惶室。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼玄货,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼皇钞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起松捉,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤夹界,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后隘世,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體可柿,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年丙者,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了复斥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡械媒,死狀恐怖目锭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤痢虹,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布键俱,位于F島的核電站,受9級(jí)特大地震影響世分,放射性物質(zhì)發(fā)生泄漏编振。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一臭埋、第九天 我趴在偏房一處隱蔽的房頂上張望踪央。 院中可真熱鬧,春花似錦瓢阴、人聲如沸畅蹂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)液斜。三九已至,卻和暖如春叠穆,著一層夾襖步出監(jiān)牢的瞬間少漆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工硼被, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留示损,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓嚷硫,卻偏偏與公主長(zhǎng)得像检访,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子仔掸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容