From dcac898f7d9a5551c18a7aa1a28821e7c45f0304 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 20 Jan 2014 16:41:18 +0100 Subject: [PATCH] Updated pdfviewer plugin to latest version of pdf.js --- plugins/pdfviewer/README | 3 + plugins/pdfviewer/pdfjs-viewer.diff | 128 +- plugins/pdfviewer/viewer/compatibility.js | 12 + plugins/pdfviewer/viewer/debugger.js | 10 +- .../viewer/images/findbarButton-next-rtl.png | Bin 371 -> 272 bytes .../viewer/images/findbarButton-next.png | Bin 381 -> 268 bytes .../images/findbarButton-previous-rtl.png | Bin 381 -> 268 bytes .../viewer/images/findbarButton-previous.png | Bin 371 -> 272 bytes .../pdfviewer/viewer/images/loading-small.png | Bin 9025 -> 3612 bytes .../secondaryToolbarButton-firstPage.png | Bin 1291 -> 340 bytes .../secondaryToolbarButton-lastPage.png | Bin 1188 -> 315 bytes .../secondaryToolbarButton-rotateCcw.png | Bin 3061 -> 414 bytes .../secondaryToolbarButton-rotateCw.png | Bin 3066 -> 414 bytes plugins/pdfviewer/viewer/images/shadow.png | Bin 454 -> 290 bytes plugins/pdfviewer/viewer/images/texture.png | Bin 2459 -> 2418 bytes .../viewer/images/toolbarButton-bookmark.png | Bin 244 -> 210 bytes .../viewer/images/toolbarButton-download.png | Bin 512 -> 343 bytes .../images/toolbarButton-menuArrows.png | Bin 237 -> 200 bytes .../viewer/images/toolbarButton-openFile.png | Bin 417 -> 348 bytes .../images/toolbarButton-pageDown-rtl.png | Bin 558 -> 462 bytes .../viewer/images/toolbarButton-pageDown.png | Bin 353 -> 296 bytes .../images/toolbarButton-pageUp-rtl.png | Bin 426 -> 310 bytes .../viewer/images/toolbarButton-pageUp.png | Bin 344 -> 307 bytes .../images/toolbarButton-presentationMode.png | Bin 491 -> 450 bytes .../viewer/images/toolbarButton-print.png | Bin 474 -> 437 bytes .../viewer/images/toolbarButton-search.png | Bin 503 -> 411 bytes ...olbarButton-secondaryToolbarToggle-rtl.png | Bin 1221 -> 583 bytes .../toolbarButton-secondaryToolbarToggle.png | Bin 302 -> 296 bytes .../toolbarButton-sidebarToggle-rtl.png | Bin 1193 -> 548 bytes .../images/toolbarButton-sidebarToggle.png | Bin 349 -> 306 bytes .../images/toolbarButton-viewOutline-rtl.png | Bin 3036 -> 405 bytes .../images/toolbarButton-viewOutline.png | Bin 300 -> 261 bytes .../images/toolbarButton-viewThumbnail.png | Bin 211 -> 182 bytes .../viewer/images/toolbarButton-zoomOut.png | Bin 143 -> 141 bytes .../viewer/locale/de/viewer.properties | 31 +- .../viewer/locale/en-US/viewer.properties | 5 + .../viewer/locale/es/viewer.properties | 18 +- .../viewer/locale/ja/viewer.properties | 13 +- .../pdfviewer/viewer/locale/locale.properties | 4 +- .../viewer/locale/nl/viewer.properties | 5 + .../viewer/locale/zh-CN/viewer.properties | 14 +- .../viewer/locale/zh-TW/viewer.properties | 11 +- plugins/pdfviewer/viewer/pdf.js | 7776 ++++++++++++++++- plugins/pdfviewer/viewer/pdf.min.js | 178 + plugins/pdfviewer/viewer/pdf.worker.js | 3682 +++++--- plugins/pdfviewer/viewer/viewer.css | 93 +- plugins/pdfviewer/viewer/viewer.html | 117 +- plugins/pdfviewer/viewer/viewer.js | 5542 +++++++++++- plugins/pdfviewer/viewer/viewer.min.js | 164 + 49 files changed, 15960 insertions(+), 1846 deletions(-) create mode 100644 plugins/pdfviewer/viewer/pdf.min.js create mode 100644 plugins/pdfviewer/viewer/viewer.min.js diff --git a/plugins/pdfviewer/README b/plugins/pdfviewer/README index 8949ac81..ecba65e4 100644 --- a/plugins/pdfviewer/README +++ b/plugins/pdfviewer/README @@ -44,6 +44,9 @@ or the YUI Compressor [2]. $ /bin/jsshrink.sh viewer/pdf.js ECMASCRIPT5 $ /bin/jsshrink.sh viewer/viewer.js ECMASCRIPT5 +This will create minimized versions in viewer/*.min.js which are +linked by the viewer.html template. + [1] http://closure-compiler.googlecode.com/ [2] http://developer.yahoo.com/yui/compressor/ diff --git a/plugins/pdfviewer/pdfjs-viewer.diff b/plugins/pdfviewer/pdfjs-viewer.diff index 52a65c63..91bc469b 100644 --- a/plugins/pdfviewer/pdfjs-viewer.diff +++ b/plugins/pdfviewer/pdfjs-viewer.diff @@ -1,57 +1,69 @@ ---- viewer/viewer.html.orig 2013-10-03 14:06:24.000000000 +0200 -+++ viewer/viewer.html 2013-10-03 13:53:17.000000000 +0200 -@@ -31,7 +31,7 @@ +--- viewer/viewer.html.orig 2014-01-20 16:22:49.000000000 +0100 ++++ viewer/viewer.html 2014-01-20 16:38:37.000000000 +0100 +@@ -31,12 +31,12 @@ - -+ ++ -@@ -88,18 +88,10 @@ + +- ++ + + + +@@ -88,7 +88,7 @@ Presentation Mode - -- - + +@@ -96,11 +96,11 @@ Print - -- -
++ - - -- - + +@@ -167,11 +167,11 @@ Print - -- -- Current View -- -
- - + +- ++ + +--- viewer/viewer.js.orig 2014-01-20 16:22:49.000000000 +0100 ++++ viewer/viewer.js 2014-01-20 16:32:24.000000000 +0100 +@@ -23,7 +23,7 @@ 'use strict'; @@ -60,7 +72,7 @@ var DEFAULT_SCALE = 'auto'; var DEFAULT_SCALE_DELTA = 1.1; var UNKNOWN_SCALE = 0; -@@ -49,7 +49,7 @@ +@@ -55,7 +55,7 @@ }; PDFJS.imageResourcesPath = './images/'; @@ -69,19 +81,19 @@ var mozL10n = document.mozL10n || document.webL10n; -@@ -1313,9 +1313,9 @@ - - this.presentationMode.addEventListener('click', - this.presentationModeClick.bind(this)); -- this.openFile.addEventListener('click', this.openFileClick.bind(this)); -+ //this.openFile.addEventListener('click', this.openFileClick.bind(this)); - this.print.addEventListener('click', this.printClick.bind(this)); -- this.download.addEventListener('click', this.downloadClick.bind(this)); -+ //this.download.addEventListener('click', this.downloadClick.bind(this)); - - this.firstPage.addEventListener('click', this.firstPageClick.bind(this)); - this.lastPage.addEventListener('click', this.lastPageClick.bind(this)); -@@ -3991,8 +3991,8 @@ +@@ -1581,9 +1581,9 @@ + // (except for toggleHandTool, hand_tool.js is responsible for it): + { element: this.presentationModeButton, + handler: this.presentationModeClick }, +- { element: this.openFile, handler: this.openFileClick }, ++ //{ element: this.openFile, handler: this.openFileClick }, + { element: this.print, handler: this.printClick }, +- { element: this.download, handler: this.downloadClick }, ++ //{ element: this.download, handler: this.downloadClick }, + { element: this.firstPage, handler: this.firstPageClick }, + { element: this.lastPage, handler: this.lastPageClick }, + { element: this.pageRotateCw, handler: this.pageRotateCwClick }, +@@ -4806,8 +4806,8 @@ document.body.appendChild(fileInput); if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { @@ -92,7 +104,7 @@ } else { document.getElementById('fileInput').value = null; } -@@ -4147,14 +4147,14 @@ +@@ -4970,14 +4970,14 @@ document.getElementById('presentationMode').addEventListener('click', SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar)); @@ -109,29 +121,5 @@ +// document.getElementById('download').addEventListener('click', +// SecondaryToolbar.downloadClick.bind(SecondaryToolbar)); - document.getElementById('contextFirstPage').addEventListener('click', - SecondaryToolbar.firstPageClick.bind(SecondaryToolbar)); -@@ -4229,8 +4229,8 @@ - store.set('scrollLeft', Math.round(topLeft[0])); - store.set('scrollTop', Math.round(topLeft[1])); - }); -- var href = PDFView.getAnchorUrl(pdfOpenParams); -- document.getElementById('viewBookmark').href = href; -+ //var href = PDFView.getAnchorUrl(pdfOpenParams); -+ //document.getElementById('viewBookmark').href = href; - // Update the current bookmark in the browsing history. - PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber); -@@ -4273,9 +4273,9 @@ - PDFView.setTitleUsingUrl(file.name); - - // URL does not reflect proper document location - hiding some icons. -- document.getElementById('viewBookmark').setAttribute('hidden', 'true'); -- document.getElementById('download').setAttribute('hidden', 'true'); -- document.getElementById('secondaryDownload').setAttribute('hidden', 'true'); -+ //document.getElementById('viewBookmark').setAttribute('hidden', 'true'); -+ //document.getElementById('download').setAttribute('hidden', 'true'); -+ //document.getElementById('secondaryDownload').setAttribute('hidden', 'true'); - }, true); - - function selectScaleOption(value) { + PDFView.open(file, 0); diff --git a/plugins/pdfviewer/viewer/compatibility.js b/plugins/pdfviewer/viewer/compatibility.js index 23ca69e2..433e617a 100644 --- a/plugins/pdfviewer/viewer/compatibility.js +++ b/plugins/pdfviewer/viewer/compatibility.js @@ -361,12 +361,16 @@ if (typeof PDFJS === 'undefined') { if (index >= 0 && remove) list.splice(index, 1); element.className = list.join(' '); + return (index >= 0); } var classListPrototype = { add: function(name) { changeList(this.element, name, true, false); }, + contains: function(name) { + return changeList(this.element, name, false, false); + }, remove: function(name) { changeList(this.element, name, false, true); }, @@ -438,6 +442,14 @@ if (typeof PDFJS === 'undefined') { } })(); +// Checks if possible to use URL.createObjectURL() +(function checkOnBlobSupport() { + // sometimes IE loosing the data created with createObjectURL(), see #3977 + if (navigator.userAgent.indexOf('Trident') >= 0) { + PDFJS.disableCreateObjectURL = true; + } +})(); + // Checks if navigator.language is supported (function checkNavigatorLanguage() { if ('language' in navigator) diff --git a/plugins/pdfviewer/viewer/debugger.js b/plugins/pdfviewer/viewer/debugger.js index da57fc3d..a70ad0cd 100644 --- a/plugins/pdfviewer/viewer/debugger.js +++ b/plugins/pdfviewer/viewer/debugger.js @@ -240,6 +240,8 @@ var Stepper = (function StepperClosure() { return out; } + var opMap = null; + var glyphCommands = { 'showText': 0, 'showSpacedText': 0, @@ -271,6 +273,12 @@ var Stepper = (function StepperClosure() { headerRow.appendChild(c('th', 'args')); panel.appendChild(content); this.table = table; + if (!opMap) { + opMap = Object.create(null); + for (var key in PDFJS.OPS) { + opMap[PDFJS.OPS[key]] = key; + } + } }, updateOperatorList: function updateOperatorList(operatorList) { var self = this; @@ -300,7 +308,7 @@ var Stepper = (function StepperClosure() { breakCell.appendChild(cbox); line.appendChild(breakCell); line.appendChild(c('td', i.toString())); - var fn = operatorList.fnArray[i]; + var fn = opMap[operatorList.fnArray[i]]; var decArgs = args; if (fn in glyphCommands) { var glyphIndex = glyphCommands[fn]; diff --git a/plugins/pdfviewer/viewer/images/findbarButton-next-rtl.png b/plugins/pdfviewer/viewer/images/findbarButton-next-rtl.png index 08a2c25327d7df3c77186cc3048bf02b7e72f393..4b5551e23600acf956df706d73cab427db50b90c 100644 GIT binary patch delta 244 zcmV+{%>G# zW#Gdq&8xbl`~TYiYZ-zV1mL2~NV?Z`{a*os422BN3|7LH_NFC+8I)8*3W-H+ u|I7ZDp#&6qP_JtFUw|v9iHIy5(G38jXr5&x9k}fP0000X?- delta 344 zcmV-e0jK_u0`mfpB!2>8OGiWi|A&vvzW@LL32;bRa{vGf6951U69E94oEQKA00(qQ zO+^RX3I!GvFvOasTmS$81W80eR4C7Nkj+ZNKoo_)n}*aD3>x6#~cz*bByt9JFw?s8?3-v{BJ0Jx@)|j*!Bbc-rqj&kHulwyq z1cLxz00A-?xOfDEleUlICxnc7m*!)4qfRlNmrS0eSo7*=FUBAe^Fo5gCqWZQnd9YQCjxopiRgEdinsl<+XWP3 qGX8Z=Bi;-N9t%kY>zK~!%*)ZkHew>0000Q6$+<-A&dniceemt#Srqp_i2AW81{i75_@|yNI}5= z4lJ8OGiWi|A&vvzW@LL32;bRa{vGf6951U69E94oEQKA00(qQ zO+^RX3I!G(02r)(dH?_c4oO5oR4C7Nkv&TTK@f(Y-90Y%Vd6n5!H_~tVUgA!AZTNw zO(H_jLJ+}!BZ#0N2#Q)LXcPP=DZNqxLcrwq64&DH!gAtUEPu1}KC{g1C~Qf5JYZ(0 zY`HA=4{6~34>Y$nUyp4>0?NXx%y!n^PO8lU5vvjOn@v3E&~2En%3 zM*;>A02l=3;j?=|jFv2*(IokqkYu+`?f#Sot_0GOokeN4@_t0%AeiK{n7pfbJT>4j zCFQRS;-#{C!&|q*7Zc>X+7!fN*f}Rg3tB`d0!AnT+9t#x?!tqsY)prN^>D-@t8g3j za=ZN1Ka9{%wcd4Mm*vswpRr;uOO)2_p-AlNUke&ra5_KluK)l507*qoM6N<$f)qlV AY5)KL diff --git a/plugins/pdfviewer/viewer/images/findbarButton-previous-rtl.png b/plugins/pdfviewer/viewer/images/findbarButton-previous-rtl.png index beef8ccea48c932571c9c9009df916a61c850a1c..f681e4ee44e4dd520759b974a7a1f6f868f30894 100644 GIT binary patch delta 240 zcmVQ6$+<-A&dniceemt#Srqp_i2AW81{i75_@|yNI}5= z4lJ8OGiWi|A&vvzW@LL32;bRa{vGf6951U69E94oEQKA00(qQ zO+^RX3I!G(02r)(dH?_c4oO5oR4C7Nkv&TTK@f(Y-90Y%Vd6n5!H_~tVUgA!AZTNw zO(H_jLJ+}!BZ#0N2#Q)LXcPP=DZNqxLcrwq64&DH!gAtUEPu1}KC{g1C~Qf5JYZ(0 zY`HA=4{6~34>Y$nUyp4>0?NXx%y!n^PO8lU5vvjOn@v3E&~2En%3 zM*;>A02l=3;j?=|jFv2*(IokqkYu+`?f#Sot_0GOokeN4@_t0%AeiK{n7pfbJT>4j zCFQRS;-#{C!&|q*7Zc>X+7!fN*f}Rg3tB`d0!AnT+9t#x?!tqsY)prN^>D-@t8g3j za=ZN1Ka9{%wcd4Mm*vswpRr;uOO)2_p-AlNUke&ra5_KluK)l507*qoM6N<$f)qlV AY5)KL diff --git a/plugins/pdfviewer/viewer/images/findbarButton-previous.png b/plugins/pdfviewer/viewer/images/findbarButton-previous.png index 08a2c25327d7df3c77186cc3048bf02b7e72f393..4b5551e23600acf956df706d73cab427db50b90c 100644 GIT binary patch delta 244 zcmV+{%>G# zW#Gdq&8xbl`~TYiYZ-zV1mL2~NV?Z`{a*os422BN3|7LH_NFC+8I)8*3W-H+ u|I7ZDp#&6qP_JtFUw|v9iHIy5(G38jXr5&x9k}fP0000X?- delta 344 zcmV-e0jK_u0`mfpB!2>8OGiWi|A&vvzW@LL32;bRa{vGf6951U69E94oEQKA00(qQ zO+^RX3I!GvFvOasTmS$81W80eR4C7Nkj+ZNKoo_)n}*aD3>x6#~cz*bByt9JFw?s8?3-v{BJ0Jx@)|j*!Bbc-rqj&kHulwyq z1cLxz00A-?xOfDEleUlICxnc7m*!)4qfRlNmrS0eSo7*=FUBAe^Fo5gCqWZQnd9YQCjxopiRgEdinsl<+XWP3 qGX8Z=Bi;-N9t%kY>zK~!%*)ZkHew>0000m)%eg1J{)B`mH2FuG0wII3yw|X;m)aY^--^pGr{6Hoa78~~(0Ice^ z_W%Iql&cffi=-tlba&}J1OPqO|8TBee-!eER@7Den*oRCqRbup52WlyG?_Q$3TTdW zVzim@>q~0C4sE#4N;mhLR8#lU-0HnuvBr5k;5~+}aHC#b0f?_^!ykIyP+fR{2t?FA zX%i`j%-|O1J>9WJ3iMMPQ*B!~frdLlEeVnHw&&D>s-@L}I zy7GAF%jvqR9d$SSPuE|yIn);RgJzL@C!O09`RDosj1teYH=c(6hJJ?QygyWVGMD-1VBF3T(@MlS{WW2!|!^SvSK~*ciCJj8Z=FDCjrw$xQkSqry`n zK+KL&%GcvMs!p8UYrpp*koUVc+gC%B)}3c8k>LSN43k`O!)R036Lnfq3NE`oLpPb$ULb0a^|m2G!_y zKzSi(vd2Q;$~#7bQ2mpauinT1P&o&=42-&^@-}J!ID@BR3=Mt+g+p;$i!jJA&sae9IpL8?~)9k*N9cy8I);)=dHfDCh<61M;fu z&aKMuZ*bUL_p{;WQ}>(l+Ti9P(#waX$!~`wk;YfRUv`0EVA!#S$DU%(UlaM)K;-kh zt@NM0MH|Y+O{WE(^b9uP`d(rc5$hBO`KWn5N&|&} zTnl7kUB5`sWA_pnxn(kMngxoSe2fKu3;vLPj-+RWSCfR2Da1QBP*d{b&9Gt@)@wFO zCU6UT>a;+MF*Hve;dIf>Z{50P&`&Pvig36sZ6x4KlD6X3^@PRj!Q10ozU$s;8GBR9 z9Ot|mZ4dDa@62nZoE^sF)->~4aW^j91%V z5FI4Ep{b^S+-yx}7g85vmUbDDAW7MhybkyQoX@n`F5mw!RQI|f=8Sj}5 z%7gppna)hGLC@TQzlNrb!$lFA<5ui0?&F;gGUKZm9qx@7Te+9R@4ualI78JCoBEhE z@WkcxLAR~Y--fr0orC9&vG5xFlVAyP3j`(lCBDemwr95cTkg!g`=LAk{)JB~ow2;W zDg)B?vHo5+mUQopeR=G?fLFX3WW*sYWlT?tt?=ZavL>a_N@Q`w^7XfUI}LUkl|+Oy zwumD#!i}1Z&5k-3-xS7oSdpe1CA?F}G2m{!Rr`(nt`gMIWgH5qmQ{%=x@BmVaO9|* z6Qlaa5)Oh42Y_+Mo8W&zQ2?S>V1olAhEKpBE+(C`6HqwQ3V-Smjhhd{x05b&p8w~t z#&2w>{Y)H^N{OJj;0_x)xu2?*4(xhm z2^wHy5S6+5A0>J|kds}*4vgLV&jFtQO*9ZthZ@DaJOdz@rO*cNQ3J|J|_&s59iRTSd}D4 z|6C~^`$PRfTQpIVc)=$H+5*kXXoPV!TIj;8zVR=)>ZMcm;S49nkSy|)B;u>$(}onb z#i->8n!IUI%LMT%w67>N4?A$D(jN&$FNG&b(?pw%mU@h0jsSCLJrqaec0TSs!+NTC zLJtHpN6f+Q69eZmE)^seL4F>S|av!>l|z-v&OyT zC1+jxnwN$JB3(8e&kw0=L;YN)Axj)t9m$dhk8bQD4X46=!% z3Z7X0JGPkn1dCtZG5I#&8xXcig4mEIQI9ysoy?rIaAUm=gjs4I7(v_&16Ef=26>`U zXnB)gLFe+VAA!3eZE5=G?ZBtqc^?Bt8v*h0QqYR1?a$fIeNfRkpc0nkTfkPv_41O_ z?|jWuMeAwC?xRkxe?LlKnUjo$Y|D7b?i~3=PV7$}zoT=6{1PWs87D%F;xX2o@38$i zuj}i_Zb_mal-M$m-Y*Sg`@qlN&RjW%QJSBLITBkdwMNLQqs2a5zcL)KSz}(NL{rF= z2tuFY%8CR<*pvS@Lvhf6yD!oEfNO~bWo@D8a7XC;GimH>d0UfO)6c018s^O;NJ#G* zYSSmMvyR4Ir9@katXwE!%ZxuSmG(ye#UgPfD4W@EeXb`5MdTlQegG9UpbPfpCt~CE+okY;t^EJRzbT0o4Rf=9H znChGtt>Atu8uFJVo8nf}9@8T*=;%`F-vj14zpq8oVYnytVmT9@BY$`_c5RCj98Y>9fioJz9sbh&+@5-q!h{vCbc%op@Dw!~!RLdngN=Lff1iwi!P+v7B)<5Sb` zVi+(fFr$N#73EoMhgDNpZRBA`=V;CC zIyj~>(yEn{Y-!j3RH9{EYUSYsyXEK6hG6ki)K9$2%xRW*j%+mCq$UPtcHGvb?YZ}r z=gkNB10^A+J`=a%z&y`f=YX1d6imhE$F5u%m{t@Fuwve3<%n5}3F`1N77bL(s!N7O zws4DBB*W2_=t;VSMRVyUVhVncM_8?DrKXebf>tt3@2iQLnnaZ3VO^TYz^;twuBbyx zb2k5~ypuWGT`f%6cyTzR=bg#p6$_(V92pq^gIZ-{+tGx(XJfw+^M>rnIGPg8gr*#D z{JIv-QZ1P6BdoZ@yH={0GR><;RFTlzW=F~rt`q>~sE5StdazWS=w`YBoca5L_kqe< zD<8{gY(AK3){`(L48mvT$-r7`8LTvTVi)Z3NM>MThBcdL1vq)s%RF0gCXF!#6!; zcQ?W{-+4Hi5g*>L34dH*ghia=+x?u;R!+@UmD`X4ZtE~bYb^r@jM!Qnm24M)GEZHd LcRAg0gcJV5yclb0WL4ce1xcBqSs|Wxj;$%_%EcNr$Y?ktBP? zA?x?-^L_uWt3PsGdb#8nuXB6eANTwHabk@OF4NO+(!gLa`YSqUV{l~x@61#b;NQ;` zVx8bZb?XjB4_xWMJ4~-toEcm=?f>s{e?IlU2mJZqe{Z(PpK!S43vEgm43T#QeG#J= zAM*6=)jJMsU14J+^j5zVwf5{8<4^ft!oS&S2ua^r{30S#CiO~<{M;e!Bh8beWgqp) zP3#5EHLxseW|U3Wi7pt>K0^tLTcVvb9a1#%J}lg3;pndM^Jxzn@@tr%qOE_m5+97% zrSLQ(x<|lY;!JS+@qX%0+`g~Szv~QR5tEb*!o@5#Gw)-FO{kaGW@Wz4oeU})iH|>;RvpI1#;(Zh@BIAv zv!8TGh>niV5f>9n>6_d1D==PgYPG5;uc$b7wr7xlGw=VNcjnQ!LHRgVfa3+r4~! zeMxTdPgh-CUHe_#-3_jM(0wGY-=98yM<#A%b+xW&WP#7P~ zSQ44^AP$FnWmd*c(c-Ddt)i-WSa75C5dXnR#-+KXC3x-NpoQlr$Ei~rPh`awV)HkG z)sFc&;~C(ikNA^x&TQ@-Ag|2Et9J}oQA*JSPfSlAB0SBRJ7+hp;3#D9+WT}OjRS$3;qZ-3HCVFr_!%p zy$T7>)f1!)-^X1Zcdg@Fzf);yX8dD)-Sf4PaKD&@gwnu?sILug7IJcO9JT{hN1trR z<_B%~2h%-AuIfJ0p^-alV=p=Ix~ZvYg3;U6&8>A)Wo<~iedYW2d|yvbBg&(k?Cg-S ze#m}j^Vt;>6BE^wCr^$%{SEu^<45XBMk>1{{!jnk=G*t=_-5Mix}_~i2tzmW9m^)Cgo(*pQz`Y?o^SBc2A}F z{OHD*;Pj~Lvb}hh&55YPlYz9NL87W(s(Dky{Kq3-$XiX1FAYBw2SbR7 z8vN_Y&~F&+I{Y?*x37vfH#av-7*#>q*-3D4a2Nu2S39}aRw8$(-N%wN=_+q-YG(HP zCYp|zcZX4~U&f+Q7 z)JxtMm(iZ8Li66tb6VxkLNfQ<(K z-K#OuLc#>~M?=6HSz6TwGi( z484B+`iz2t0w3f|AQE5jJ*Ruv!G~c{tDvARk=9p+<6lQ(k#B80Idm+~-N3F{m4(M- z#p;~SHc-}BdH3#JZk=21jm@0t>1hSkJCTZEim5d?Y*LjJ%p8D zL^$KRy5ZNbVR=UWHSO(T6`XIe*Dl;0I>sMR>SK`%bxYnee=&~yH{+sOVM+k)%KsQA z`@{HE9rFOT?l2(*Mty>u!2y9wR@+fY(j{`(IN6CyC{n-K2<>_8)(z?ElTGwphcDc_ zqiFL)weAX~pd>6COFHXl2V}otGc=V?OHGW9bmjBE8oZf3M%u`x%#s~Ce|&m%_4wvm(v;rAfU z4|87AxI1_H>o@!l6z=r%)w%UVU?=PNN}Z|J@tHV8-GVnwy)4$H&K8jZ3_{)=2HL-3KAA zoiCxbd)z`}Q&R`?A3l7bF=9&+U|fF{(}_lR_w*z_J}=3|$(axdXG+qwwkC|k$J3jj z#h7E^JU38DyKjA_1NoU^(Gu)5Q3?gdC0-MS9W(3m9UUE_046K`kK^N~(AY`rb~#iC z1g_1+$;pZQyX<{OSl6@z+Dmyocc=Swzp>Rd}SvNl)IRkTWHv zrB~r&)#J;PQBhGQZ*ATnV~`h#2&Rp|)`h*jy-(}w>+CjSeIHFY>ot6W zt@Jnekh$U8ebad2R-_TH9fink4qUI_qE2)^CKYzZg{hZ9`Ymm92xG#!iKO+Zd$_qp8N!CDCSoU#E9Pmrgy3&i zx0xjWLY(~Hh<7K_YXHRC{zIJNe==^E?8MX^c4>cnv!wWHDPioH&1rn}beH?3E@5He z_^u2w@>r&-hxGOO^5e)b!~vYUHyUYihKcHoV3rClmX(q*o(0)WLE|K+dphZ}RM3}1 zdalysmfrsTem(n~0HRCs^&=jl$qPu67Xu50ts>Qgc91>^*7OJDvLV%PfBmZAl4`Z` z=-*RP3y???rJn*;#6eX58*&hWJRBX1sr^}~$jX5n6~)KL3;6i>#N!A_I^X@U+wek} z^o9tL#Gb9b;o)nwF)dRQ6H4-QvwDi$ZPUZUcQL|jYOgCQ{wR0oDK4&{b10Jt^=!fR zSO_4g+Xoa&A$Q1nb#d`idV0D=qlJX&vs%hG&CNk`L?X_hXru)EK!XmcuXK!z?Qg`E z(#_ErEMaAN*&;h5V`5;=D-Hd<)@^dX=tgiUv?h@Be%ev?w<}2iOSqw8w96%;CYM#X}e18seCUIn^( z;Fwx}ArOvVSxD5w#O37Vwu_ysA>U%>$>n9&>meZ_&yV94)MfOmn}Kc3xgpLAP$r+qOhwxnxlh&^$60zg1JBOQ_o2ST3z${q29eQvu$St#!=( z*m~4^en@Nxx{RDHY0S7?npBQ&IO~0{Mrm0pzm&@{&Xl>cm7e0xeb)-L7(?WSTg75W zrwO_BNe(382glv0Jp9$C(e`rFT3@=7svmy6Hd%P6mK{dmYri*0!j(R`zJn!pU#&d( z@z>L1hIMMDor7wP&gw&42=RjppXtq;8*qDJw!k4;d@Cn?l%_&>-|XzH_N!cU#0yAU zNJyvxPEt}XkLO@jI)(ahqVpZO6Qh3vr)m^!gs?3!R_|7tiOp4og@${xT0qa zi$)ZH*dN!rHMcc4?{ED0QRw0BKFFTLPIMu1uV)uS-?#RbSXo&kyc&aVIXdc$jgBrS zCnxu8__rX8O7Rj64ZL+%=hDIfg*uv1A3#+R~PIvCS z(n3dTedi+f$)xKQ7FkFrAdk?fyLCpw+!J*xvKihb7#gL7dt*M+9G+?{u~g7RDHI7! zw70c|mbJDXN>?x&NOAP*>+6R=&>?RT={O7SjT9Jm{3U}M3zv+(bwG81c)VfZs>9FJ zKt9!HU#+mU7o6slF&0p$$m-AaldR3!!lM67a&JXAA%ZWwey|v-sjF*K2Km7I)~&aH zw86{M^U31RpT*ofJhd=OaKch=SH9ob*|}0_dLYBi;6aIqu1~NB8+vH`)9}+ zJTo(sg2@R47hWTWi(pW<(wWlI(rUlTG}VZM%&M!aO~M!2+S(EU|J4El0tBGB=D0&w zd1VMb|_Zj0Umim?=q~ zDB2;VWMF8R`x2^ZZeIFQr%It`sHZnofp<<=YHEczZ{Xj|2dI`Wg#uUIM$QYIO-S0% zyhjPdfqDsekkff})~%)Y_@6XV0EZfj1uP0GZrEm>eqy ztgTLLN#f&s7wU%{rMMv+Nn=wzud(q0t=_y><8W(ht05}sJ(VYQ3|S}vnNwC)Rsb?7 zA6%VcIQSyA00_h8!NEZ(2>-vq0gnTAO*~Z>t2z`1ufJVsiYFcRfG<~1?VF1PL&+SK zw7SFLuU3v6^MU$mj~l!oz|uVDu8&H4#=4U&Y@}x3JT6JnE^F8 z5sOYhww4$PAGEf$9U*PBrHdo_Id~ zO3lyB=pCPuiHYJRc4fKj*Jc?tSHx>R-d^mDQ+{udBUqKmhF&)^;_%-v>PqIc`1s83)!Z>KPpQt;){29SZL!mA*r$+)Hbvh;C~c6bMJi;$qzSoXG*A8@kbca z0_lA9sF#J3NLG%}w-$Tz=8X}xKU7*O`K-9(sS~I{xNIIjeysSx=Qhj=D@Q>|DG7@O z&Q%8J%&@0oa7AfpAME$5VaG&>v%sBBLnMMGtq)(>-oD13B*ewV^$?g5{P6HFYb^Tf z+VZmE$ilBV@H9?rY;5>2--Vg*Fgzp?iA2NyIe5wR!T@re&fArtlXa3epW*W%cdQQp zL>tJL?irTz{I@u|%Ya{xfpiOEiXnG*H#fO~p`rGETWKFum3xN$%=;K8?6kX+Q*2^< z{Go!JoE=gUjTQ}m5|hQsoc{c|qOAT_`p~?nCmqU|jv_JH3lVD(&C$1}C`9_c$Biu=(mzLrk{i6GJbeX=#Y%o;PwcUll$5Lp zQpw?q2<3-mk-x-6f{fh`a)JO6wXW>>E)OyW$wFRMHYGPJON@bz&J~nKPi}Fn`TF^- z!4$wR*dC=hlU7?>I{*%3OKq(Wh|_NShet)QV&@g+sIJ4%WBtjxcJd<_F9a=Y_;*Ky zg>OO5;6HnqnUd;Jw-XZ*C}3m#{d!;Lw^~I`q}WRD*}J>1O5zq*SJi}2bulf6u2;Km z59bcp&q9HmFY@wc+MF_2HKL%LLHp_rjEE0374_kz_qUyj!#%#}IeM>)%X(6O5l{bb z;=APy>;dsT{}IpdN8$B#%x|*wg!O!v6AUG0qDM?tgikoQmL>4T>NuN=M=XE%(trEW zRfiI9fd&y&!NRX-dMTqt|`MxL-ZZtcAFQ>49ks=DONOKNw z)k;27PV0NN-Az9AfZJx+E#M}s05A{1nGzsMG7n3?utdAZ#ON6so&@z07pO?R;LI1m zqz{sjk+~;!?1lXK%iGV7Q0F$uxXa1O2`R|SO9Nk9j43cQFlcGCD3?j=I~C3xi$)YX zKZF>HosYkL`&I^mw?~7m=errtePE*7p`GUQJjdCmG`QAVZ*jE4j#xHsXZ%RSk*zaH z!BxUj($Wavel&lboZKgY49{0uT6za3XG8Eg7+u;{Gbk_)0~z^Xbo55^EshnSqXj0| z^z3YL1^tmzoSeUz7#@m-`vb$i6&V@XgBt*Xn>(`Ly#ySHwpuR1(#pz;Ff~P2R#B0Q zd;!U=KENVfA~bA1-OUOlo?Fj^%)nt&hqoiFOF?*b!5z|~yo6B(V(d#~3`K>7R%DTH zNw^clKVeEORkszfS!w$E3Ywew!of>j5Y&Cr^#t=E6S$d~S&r$mZMya1Mf~C6;qK0k zZv*C`?zg&z+2i0N_=?=Aa*t}??!Uli{5SZbFR~K>_}%}3&-6#?FaEJM(d{xGImzb> zhucmjKP0mgl+|}F#1itK=4(Hsjubv;OQZSVe&>Cz1#R7*-SJDST~Us>*XU32@RJ$w z{NDuWpYki7zwhxmO))^7r#dW+N$N>e7jeCbU zoCe%q9K{JMkJUk=Z5z%g~WWCml+zd7kp$4j#k8zrKY)vf`YTwSVqit1sYX3w|`WdJgaIS8hYevXlQr? zOTuj1+`W5uOVN+QpUPHmBnxSfDq?i~dNS-2&gaE9Q-yFLoUSbAp_8rc8Fo4fJ_HF) zTTCI5mYO=9o0HQvg~#Kmf;~98Epf7#RIqN?1pcuH6tbe(+1YP{)sCy}ZVmwlUk>vF zwuqKMYR^tjAGb0!Rdn?5Aoy95FpAt(a1E?z9G5Qzhl2iA(%QZp4oOtN`nXQE(H7-SVQ{l6Sfa4<0vH=RD>I@J}i==QT3+0MWxgyMXg_WWmlJ{c8FpB!D!)r z2kqKc7=bjjb#N@%LLcCEOzk<(LZU#VE%Fj+U-VTFNEcwN9ONu(Y^LVsw1^q7HAy#Y z-An^hl>mEr@7_J_W`mdEWzaCFR170t&MJ<9(s|Hlx&kbXUg8cx^m`3mYtIzszsL{j zXk$>jKZ`jC_4nWXAUj|rlFG>ove-gEPJp+!x5K@A)h+=6dyLHI!>@HnEpKgYSsP); zr>FIMW;eu)`scSuF12P&77{`jyfJnOtU}r?BnY6i40!Qna?;$?+M4egmikc)v^W9+ zb|={Vtgfvo$7|2#%__!^X{#9H+kugq2Z^+@q2UYSh>e+imtmKBcev_xD(FRr{j}<8 zYqxJnr?=4ChEL-31=!=_19MKoKi9cco0X}#*P4aZvN9!URFCW35IMn0)mYA@jito; z=wUAd>mKbkDPfU7LV$HZWmVNvV?6Wm0I_R42v?8mNJvP?jWAM<0Kjq^k-5W@Xw_iB zj48kO_fLY|jYme|9;~4+OudRyha&Q=YY9ddM46)Spy*qAu);j3*x3-Q?P@?UcD#N2 zTdsZeo{t!N(6@lEa{McbgCl{hM;jn~RvVYFar5!1-?OYAsd^m(H^NKs3!{c23I+!a z7Po&h*10v~u-NIxJ+ot?sk#d5rBDX$D)R;U-PN2&I&QwcJ0eVGrlz~%+zjelJu=`N zEdnDdgp2F#?M?h+=ZiejKRef|72hbeh89nRyUGXo+1L=d_+LOK&b4MoL?Zv_AhB*; zfcKzX`53WWKrLHkE_(Mb*jfJ#yNJS`FMz$}KiJs->`8_?|68CctZ9Xu?=8+RiA*2N z%=k1Km0e;uuv;Wk!f;W9b!7<^C*izMc)#JUkZ_9cczo$9jRf12u+Ejdk5O?Sv$^gH zcjQ0H7u4g@5Ce}&qd{!4}ff}vXfKh}45cYn;x+{#^QdKJ!<*drq< zDtZYLDFk+2tpxAv0OIr<5X8x6b}L`%4i9Qikg(e~Jv`QgK%cY+IzRd^tblcjUHGmV zXS@XP-t;Z!s=(UT?`iLMOMdbxa(Au`e--nJF#@t2l#fwY3qJAX%pZo}Va(M2IcZ{P zeHUL0-_2rzCdwcZ*2gh4R1Ks)3d!yuhRM1WkOY+>rSHcJYVEXeR-EB8yThM7-qc_T z<%A>u9lm~nPfGtD!){|~>CzJLZ{=W;JZ`_!S@FOrqa#Z5bb8<1D(q&56fff9C9IR9 zV}qKs^roYa&vw#MQ&Fm}-I*kvgSh}JWgBqk_;|RvTXKiy6KSIEUjS<~;9La3UXL~$ zs)H5AWFf4zQOxJk`r5(9sht*$^-)gJnG#FY9g84f<)~U%l9I#MR%2r@MMXud8jhg% zyaIYTnid2CtTezTt&O1tbo32pQCQ{+jkd;RH-bPR0J<;VaBv9nW@cuFVAt?Fpsg_z zV<)tIAd1dho;nQkI3h=Tkp(Q`m#zMy{lvd%?>USEFDyX3`j2+@Khi#M6>|%;H5Go| z8JPqNhkoVH!mR8`wqLVqPAx}=JQvSOf3Qb+U&rm$N0A2_QkEAoadt1bPD(rwoX~cE z8O3#8?^>aeldB-*>65-@-a?&;y#6y;q^x<*!2kyCC|_I~RSk0SxeDwN!ycR%e~C<( z65?s*`044DASop+T}7GVl(F+}@AryI2?RC4IKb+GQu*K^zmX6MNE#2$#B!`ztnZQe zv+Xwp1zoV)b#4SE=8lDKSW4f*w^K$%DS@>h{<6n)sqG#fQ3Bt|N8jFUMB#qn`5m(eQVfJ}jfql*gf z8re6i3exJYU*4wZBMB39Fp6anJiOLze-C)iu9;I>`L5pHH9^6_E!WYSsBfa3w{fh* zyMUQjKw?XK#HnpSFmsjSa7C6Q-Eb!cWCxo6JiW!j;^Jjsj_re&GD!EFFw#8%&M+BR zb%9F6?xqsz(;1Y4)|o6l!ORdff}Me4mIgl)c*(>7ip5t^Kk7MG3@mbPZqAS=Jpm`H z&lQ6#(>Ry#Khuf}cgzf~k1V#|Q8C hz@LRXHuETLi_&HcR;M1%flMGcckUd70Th9N zS+iyVMIaC&aGTekukOP4MMlFODYTfBJjpFe-Tefzd((ISY-1q&7c z84xpf?%WB~2-LOwOT97B6qS-7KcM|V+mzWR&sxBs*37lqb?f|H&0D(H_jasU)3#Ks zc_ssIE(1f8kD@nFeX*yDV@O5Z+;gY-4jJ&c1oB%r%#!lD{C)R_|NlFB0|b*AkK8;P z%llW+_uTI7Dyl7~>xy|7JT&HEsh|DO;{46TebpHa@3fA2aqHaB^P0G61JkBh@5|eI zuY`G&U!OnkmmjyK+~&vLW}6nL&gGk(8M|-(zb_1b^4Q|sYioso)-!mz`njxgN@xNA Do0FTA literal 1291 zcmbVMZD`zN9MAQb&b7tCKFqEKpJf)5^^!~4OM7W!FS+E}gI?EN4|ZB;&0U`BrEQXH zl6u#IaBid6IYF5s7KXKmesb)PAzF1puTY052#&S%gKQrho30G2!*=C-()Ow!$^?@< zd0z7W{r>Nn>gn#NK5*m!hGEs=PBDt+gU;Ch74nkR2H!r)3Zt!`j+&896x&ZIFP2s?Oo7b60Vo zDja^w7x6|iL6}xMCrsEo(H%=p3@7~x-u^sj%dv<+gSHHE+K6tkIS$|AWzpI>CULMO zVh?lpwo&m&4+t721U{nClk`#)U>JgG@-Yn60BA2olV0>N9;%7;F)ZB-b}k%gGnEt@ z6+=6^(2m2?ww+-~GMmj3*+#-J2T98B_d6Uk?LiVAYh1VGoJY5gl^H~6B~3MBtA-97 zMmb@O*&L26-F89CL?XMyy0sH16f!a=XGn_hlA7lDwIyxYQTUG;drDie@eCxR&@#r% zB+5tXSQ(6B_h?0qAhL!%VX7!8@`z|8$26$hVUfep7eY}LR`i9?-7GPTz)Lhm@jTNa z2$Em&2_hu~y=9KQuuTF(^SnZoH()EY6ia38~5tznUm<(3l< zOQm*qeSYdyAy9Cg4}7<;NCz68EPnaS?XN0|C*t|yXD@u3T3B9Ms(EGO&o|zB>S9A6 zbF(;gK~>c&fPC%b&u;3?%6`|Vr>nTQxL+)|3cp_er0-Cm&M;Q@1@2G>OKkRD-A7b_ z1QtL2&bywz>y_tkdO%g;OmW7%u{JZ*|AnnK_uQHQ{nZP1`NZwXJk~g!T3UGh@W-K! zj^mq~8xJ1*@P7MyrH6mbFBXeyeFFmnosuMtZm$24&ENUD;{Jf*{$~2H=f$_{YHN=_ zu9UE$_4#Yp)+^8QBQrM)j^lU;Vg1a}tUT$RZzAKD!BK+fKJsDB5X)4VPh6dyo&9mL zy7u0gD%V2m>h~9053Q`M*ntnal(oWd%UAB6o15FHIM<8c%Ks9|V{Yu^h0|x#pZ`?v O{GP&6w|Gf->Ga>4J;0^_ diff --git a/plugins/pdfviewer/viewer/images/secondaryToolbarButton-lastPage.png b/plugins/pdfviewer/viewer/images/secondaryToolbarButton-lastPage.png index 3967cc619606b37c53d2a62478143374fe950949..55a9efe42f1318ada3290f56edc9214eab3d7fdd 100644 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}LV!<*>;M1%flMGcckUd70Th9Q zS+i!r1%Zr3ixvT;fQ-e97cW?_;Lo2w-@bj@xpOB_aM`kDKyvHWtw32IF2fsg%nDH{D5`_ZDUiOJZr(~W-hgvu5C+sJDOLl$z9*OrCDtLuI{a8?%QSq z)n<9RIEH8hPd#^&&%r>X<)Nl-mUdQl?)(4K`X+U`DE-_0a~<1{_-X9rsq+@T>pl_w z#dODo;u_QJg9mp6gg#m|G6y3m<6X5(om-!$&A6oc iCHEzZ&BLcJ)2*-FWo>rwnsWeXD}$%2pUXO@geCwxg_3>% literal 1188 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`k|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*9U+n3Xa^B1$5BeXNr6bM+EIYV;~{3xK*A7;Nk-3KEmEQ%e+* zQqwc@Y?a>c-mj#PnPRIHZt82`Ti~3Uk?B!Ylp0*+7m{3+ootz+WN)WnQ(*-(AUCxn zQK2F?C$HG5!d3}vt`(3C64qBz04piUwpD^SD#ABF!8yMuRl!uxSU1_g&``n5OwZ87 z)XdCKN5ROz&`93^h|F{iO{`4Ktc=VRpg;*|TTx1yRgjAt)Gi>;Rw<*Tq`*pFzr4I$ zuiRKKzbIYb(9+TpWQLKEE>MMTab;dfVufyAu`f(~1RD^r68eAMwS&*t9 zlvv ztM~P_^2{qPNz6-5^>ndS0-B(gnVDi`Xl(9gVe07U=4Rw* zYH8>U)9aF-T$-DjR|3H{644~kf%h=vIPQxAvk`JC z-DPigJ$m#mGscyyFBxB^vI*5U{&?NSEB#JeTYL4Z z*RQ*s_8#kDE!K(K^TV^I=1+~nZ^IKu&jhG9@<#5j`x`5&aN+W0;s3wC>wi8!-`?`y zpPz@1u`s^zo~BbNATDmszD#oU{f3CjmzS2_J@93tTt~R`$Epbu9(=3V>KPhE^rE+| z5noZr&Cz^ghl%kcLzlAG*Vb|>^ljh%ohjxVo1}tS_O&%J+}zy73+n$12)D$jTi z8zg@zSM0x$`<_en?PEc{&o7^GmH5U#{IloxtM`swx^iK$-+!>2N@HYbkzx?YVtAmc zZ?K1H$q%;2|CnFQ+Sqh{p=ax)Sv%PF9iLI})c05F!S{TtE8fwJ3=Q?)dD&jet=(e> PbP$85tDnm{r-UW|ClUC! literal 3061 zcmVKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0003XNkl@x2 zi2)(y7!uJoSzBLUJF%_F`wpJG4D2sOvt2jmz2GEB#>qKkA*y>BoD$T-tdm2M6&pAVIFsw z442=4ENW1J9?YP%K+le`fjSH(OXx&tGz`jdiq`^=M$F;^Q~4>J{9$spctS_AgrcJu zKm)39l?JS1{-s~S2&gXY0>80JDsk6k=;>e;a9!Iu=)10;_~wT#MlFmgRjfZ3uI?-kY!+Su3}_} zOJ=K(UKsU@??;4gw8e2daj$pB^&i|hBC;KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0003cNkls?8`Ue^o?Id{D%ql6(OxW+LKaF0nj)Jv z5J5y*Qphu-)NT+tS#s#@MOU5~X0HxcC@XRx?dX=XMhLZ*G|wMM8?5<hz|2DeuCn-zRG&B9W4;S(3XI?wf;`PEy|= YWOatv5#0Ns01E&B07*qoM6N<$f>=;~rT_o{ delta 439 zcmV;o0Z9I$0>%T78Gi-<0044OHp~D300v@9M??Ss00000`9r&Z00009a7bBm000XU z000XU0RWnu7ytkO2XskIMF-sj0}muB7H%IR0000TX;fHrLvL+uWo~o;00000Lvm$d zbY)~9cWHEJAV*0}P-HG;2LJ#8DoI2^R5;6pl-rKOAPhtYFn?+L|NmMk0VjPKMU*>)Ui&AuMEm!uL0=P#xn)LC9nZtW~e1l2e481g=ZHbM&LpH1?vEw zgayzD3(uTl{82F+Q2EDwTEa zO#m$p)&x}#H-CP2CcFrE(yjq03R7s47J2d~Q}WiN(h+j2sppD3H}UqB043QJEPCjY zXP0o^lISy?yX{Q6T1O=kyYpa^wwMVV@Y7^703&t&nUa1`W(J$gnn`CXXh>_gz;=GS zy|vWYIwE0Q)1VKT%R}Dpahf!!sQ_~Un*!UZJrDX=bTD>Xz&v?3{=3giMDMd}QPn>$ hM}^kKO&it!S|9ajk{t`vv?c%m002ovPDHLkV1jh>tPB7E diff --git a/plugins/pdfviewer/viewer/images/texture.png b/plugins/pdfviewer/viewer/images/texture.png index df00864932368d75024b46b3a1993d0f51f1d59a..eb5ccb5ec3cdf7f20c344483958a2684e11e6df5 100644 GIT binary patch delta 2308 zcmV+f3H$b&6Y>&}BowYtOjJbx000ye6c`v78X6iL92}8*A%8zVKOY|-IXO8yJ39#p z2@4Ag1Ox;P4Gj+u4;B^{2L}f|Jv|W-5kWygL_|bJMn*_TNJ>gdOiWBpPEJu#QCC-2 zQ&Uq_RaF840$f~NUS3{VSy^CUU}9ooX=!O~ZEa>|W@~F}WMpJ-Z*OsNaddQac6N4n zczAkxdWD6Bv;(B^^O6Bce>q7+K~z}7g_l=$T{#v*4|9$;=k&6IU9QwU|B3ZiRZF%k zkARCHumJ*enY}ElXkQ=?2c4gsPfnPD0yxrM`LYkypgCd)t>Yc&=@NHY{-IHfDIVtz zDiN-u+VJ9~Je`SLknl8jxoDpWZ&jCRmvwcTkXhSIcAzT=kDB!af3=NkrGWzoofx1V zlS|zrIL~)~*{xJ2ULTrgEn$cFja}Lrm=vs6h92%=Ce3-23FbmLn^#<&K{X)B_B`D} zGx^4Cwqy%^f1xcsx}?X&JVLT?1A0L5RM1Wo7c|XLj;St6Xs`Qo;n6g5aK=auqH%pW zwO9{zLWchpWQR{`e-+K1iDtLQ~O=$!Bn*gq{uyZ3|%`GQDsy`?_!&G=-afDH`(u2E&pUCa8*2@4ebxUaI7SRw&^*~!De+BUm7~u+puddp~lPTwV z?H^`n&|gG6AU0yZJkepL(ZnJNJ%sQmSi>ZnMt>rn0_7A6v*DuW>tx}!Olj6cuk(@T z{_hqm5OO1(m?$VV+<@S(=Z>Gu0%$-R(3L3z&o`Qu5u>4-AIfaglO^||*AB7~s9Q+u*+rf!tU97ye=#sYPKVLFWrU+;Q%E(p!gYp% zy-pDqo)6a+$7k%EnX8Jbpe^A;YM{8A#)W>bKBt$)yrVPdw!gR*;cOC zIX2^+p^C^`t^IVPq8~kfReCtJh?7|KKEgS?iVLhBw=c<-1wi{ZcL7J3q19M*d*&ln zH&Y3mfAF#Av_3xXj=$DRsxs4aoWv9(ve-zqm=PC|QdI38=xV&(5(%z$r;jJ4m z^nshpWu_t=(Il{iK9=J$2%=>0)+1bx@YZv}}f z)261TOt|({>2qgf<2ri84%99a9m=Fi24*9zyH_WlY$dcak~F8}Toe=%ZqUeane%Af ze{YQz=K=P-7aEtX)G)aeM+R;+Rb1PBY%y<;Is;pvXv_j-QKIU>mTyzmm@VlgC#%3= zqEjmZE)v_$-6VipG|3^3wI##?>CYGtU?S=r-!SaOhfgCL84B*~F$HLK3#H6qvWBnp zrfpm&9|s{p;B*MlhF(&BYsZ|)bQ(Oqe{a-W8}(``z6nWxH23%6pojthznj7WddBp- zaKQJfPblKY?@Np8YYu?md)0q>rO<9^zp^gDM;{b>qtinA`fc68J9_xjTtB(ilW!+jzu;+f z@QxlI&<8H?Q$22hi85FWZQp+)W0smckSmp*!#N^}m_7-prXoyd=oD|}`KDJT7P8=CV|Jz>n1Sr+ zc`Ck3_m6b&(cL#{_w}GWs2}M7AMK*ZA=GlLTq$g8XNfVlYBNtplmE zO}6YM1by~$oMgBXa@GK3f0dOVLH`k4XVWouKtQ~|bz?I_?!?-$ z!PPYTq3cX4c!g8Uie5Ez!goski4WBUBN(!U*eLP0;d&1f=3f8eL-DCiJ!T$*Zi{DY z+R4u|H^+!bbX}kx75=nu0H5&ECEUn=NAs+!^9O4K4yfXl)Rdi#e^PLC*zA5FFQYBk zkilcXPqf*>&=ugLD^55(paJ4+azsA41Rrx~Y!)yQY=|Fo=>Hr8dw9-!5DU=4RZ`pf z;LGIW_{xkFalOM;Z0@udGvEkv^o77=)~9QcILL0g>-TN=$#-f8 za1Z#rxMGPZ5-+f*f2NsojSup@wc+!1;4Yu918!j#C(mZM0kBf^*#~f#a50>glMNOl zEhyUJ)MF5vN_unXtAZe&;`H0|Qye+`0tc60;D7_xu!8MpyEJ+}E#~Ib^1u%g!B*lZ z(jJX01rkhV_98r?aPj22{Qi62TGZOFz+oHqR9QK#Cj+I&Uio!hu4RTC)HP1Pp-IpH zn1U@!AmMN^dVt9l3+SfSaNz^jxg~TNMwwzB07=5;zO%a8C2l{R@=^o*+mb(P*~c@$ enNj#j%l;4ZfE0q(mdIQH000098X6iL92_4XACWjA z3^_SDJ3Bi)Jv~1^KaqeKKvPpwRaI42S65kCSzKIPUS3{cU|?cmVq|1wW@ct-X=!U~ zYi(_9Z*OmLadC8Xbar-jczAevdU}P0h09K;m66*?e?3V=K~z{rg_mb~+&CJ9zh(xJ zb0ERoLEVzA@uc1RUu=KPtYpjb3Gkp$^;Q9(PyA(BW%~kYJm~!71M||36u^`ADwch$ zMJNi*X8E}FP|JAj^oEz9)cHo#f@GknPec1mM63Hm`(*0#gzVZT zvIAW~e+Ja7XDD1=YXbs68pZ*Ym|Thy!EL^W%kGr6`T8&-83jA!FZ|LLz?I~sw)F51 zI~gIeVlWTVyR_o^2D$-7w&&>-n<+GYa}{6c`wMOC(KFL6_7Rzb6VL;Or-AlzxS(my zs$<1aL3`bwhk&L|fVWl&kc}^^sl{e27_#E8f2ewV)+>QPJba+ryJJ4m?XB*hp)>)Y zVekqSXtFBk;9SP+n+iInD^g};GJ5VNew-A&i&43x5t@d+V6qIyaXgf#;nVUt( zG2MC>*S-@{!k|!u3$}R^3)uC(^IFZVAEzNmhck4BR{{Jf{zUG8jaf#3sb6A4w~WSE zf9VI>5*bK%KnGVKV)gYdpKLW3Yx%HagZ?7&0lAU;<%y0f%{C8AnlZ-5z#Fdk)P*w% z6sVvu*bNV*SSJUkWn!}-W}S~Tmw&fdgH#*o5>xl+J;=G-w}5%5ohj(4G_jf+6nYFrdv(sBw1Atylms>3V&oj|mRbF5X zxP4)+8UWhAg^xJH46VVs-;?ODe?Hk-;RWQw$pzb zxKxs+pbcL2;hblhsO)S_};bbM3b25umSc6~jfStB`uD8sF0+_n>#h=vXmbaj+X{{k=X1 z=4xrYRirsH@3W-9aDzI5f2))_=YMOAyp6D@eK5Fcm4V5#+;Q-$t@GOMU5iD7^bPm| zLt_`Hh6>dWzI>UI!F*vai8&1(6T?~=@xb_Y>n91qp-GLoD;yyXNO{A6025j4_=I5} zK71P0s5l7kjwwQGTBuZtle1!_C*$%ug*-|H60<|fHkOKtQ`@CPf2Q*o@O`89+UQqX zi%lx}qq)Bi2SpSB#N7@K&@-mrg#*4d$^tL0yDyKNS_Wi#}6 z>^mD9_{O4xLMzY3IkoVk`=!@>Pd8C(ofG}h{qNHQAA;@;D0OLfe;m1_&|JpxT0=|R z8&JYOv9*NXBrZF=e*wXIc|A63OB%+1WBZfX!_N%zkHNs<0HwVBX{*Y18uffw1*zgc z27^Bo*Gcw2wfx;jW3tW-PdwV~(!tW8J3=Lbr{s|VhCz8c`BQIeJv2OxA&hE190Q9` zg^(R>;bBd-86@O(ON;0`m=kDYCr#kTguf?*j=AzKxu~t4e`MGke@_VbpfBI`Z5>;K z5CJHSF$mTL%XfVXZ{g85#OVN+g0K@o0~^Qc_7)x=!3RF9t2cFb-ZzL|dHSF@Si=@l z7H{hg-q9nR=i0eN{{i{`iT% zpeXnK8#LZbkJq>ZX zL9azK&nL5LxsU}98+S7W$t|QnZ`1Hyx__iY$o{@jx332kQU6E>_-Gep0kKeB?b&ep zXcxWRRgj1~59B|)lB1Oh3KL0@9CKBvAeviH-K64`e~_C2Aj`~Kb-wBquTi&7C%}*- zi{_8$d&iEh2Lkf_ts9p(@@L+54X&p-jD2s}AZpBV()4Pem!a3{PkiV;TFH?cZbexl75$9{m2{cy(d0SyrEm?!GVGx(T8U9*5y z;9~xmL;vR(xFZNrf?R+OzE;K+hghbNyH{?N%SQ7dW{50tW)H zffZaiyQMMnd9f$2mj`~346c^P!1ioaC6VMR@fR5gg-amU<@evq)}hvY1p(W*v)ZX? zVm(`^0#2{%a;*}isIPef0S$u&zz%#_B8i93IRH$4Sim%efr}W0NiCtv*eRCN2uKk= ym%TIfu5kP5lvf7e-+VysROpLCU$^oobZ3+`|_S}0Em^4y zW)W-!@BJSCl^}Uv|0kFuZk%&F3UfgiIoCqV*q0ps@{w4sM;?oSY))pop6kH$R2DRP-|(&%K9x+nOhY zoO9nS0Rf|!#5}UrG$t^H5%5L&p88Datsj;@YM4=p1Pk;+&wu@JK(9$#B@%e_I0%RQ ze0r~8T!I26gZu`nYy&A1-2HP;4+>^Ya4Y8O2oPlJX2y~UZUjnjSjo->6SM`27-B^` z+u&Ltr+yA{cD6y2nu{?iNb4YtN(dypT{?8Si%Z5*Mkm;Pzkyhm|LpqkJbBc@v4m_0Mvof}sdH3KD Z{s6Ex^Oi{w%uoOT002ovPDHLkV1j(Skn{im delta 497 zcmVeSaefwW^{L9a%BKPWN%_+AW3auXJt}l zVPtu6$z?nM00E*&L_t(|+I&+_O9D|4f2(b^5D{%_8`LGE?BGpu6+NgIKY$5-f}qdP zrBfe7@RBEyj@n6(I(qTX%}XMbDIu=qHWTw=*mfKE@n+u6-+$kmRUF3=ilS@^A@%^< zfPnZbAAkwK1}y0m7`sLe`-8#YE*uUU!bhOUZL8HvC6mc}Fo!aUtfoWo7FF`YNg|O* zRVtNh(2oG?JOLUDYR?ZFfO)A@I&Lrxq+A-XMf!0h(scNaC$&QqtQNAqnD7_;Ufs*;8(ic?lT-usCPP@cQ{N}M?rT2 z+vR{2-FX>oKr1FbEuL`oHY|;{VG|G%^Sz`(%6pvBN=OXh;*!{2EW1I+@fK4jeM3kQqr#VaFkF zK9L_uw2+l;XGOM5nsloCosflOzbch4HL0tboBLIgFr{ZLv93z2QDjxGZVvP*5yqKd e@(&aGB=QL`^xW-##_}=%0000yj>DP@;v_+u@+$sG-z@fkCn+Bs~SboD<=L(K7$~5sejdKI=`TJ@#LQ8d0&p> zd<(AYPJQ2>P9~E_6)yx~?B1UWYPQZ?#${ zEGO5pUNPWKFn diff --git a/plugins/pdfviewer/viewer/images/toolbarButton-pageDown-rtl.png b/plugins/pdfviewer/viewer/images/toolbarButton-pageDown-rtl.png index c0051f8f040826d06f38468fcc4d657cfc69e880..9ba3667e94986235e8a27dff14d6a70f26e49193 100644 GIT binary patch literal 462 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbMfmH?j+S0D`s{|NzPl|VL7IYgwP zp`oUx2Ey*{?nYAv=hoHLRaRE+-o3lIxfvq(^XJdT#zr6;s9juK9K!hb?;p@)pwzKr z#~{kCU%!6)_U(v>2q5>wi4#{c)RzKTHYGuR!3<17c5U^Q3wmcwp1FEiu@1BP2BY1Y z5mn`UywzeH5_w#fjBME@nY9*jJ5p^GefU$iZCX0VT50~$66t1H-y9{NIlZ1Pjv*Cs zt|vmp4mk+8T%5SP@CF7qS zdwsomf8Xz9y*>A|{zy!3-O%5#C3(x)#+2YGi6RS+y_MXwQ%vl^^{z!e0p?lqLCO+e zn3P%7PM)1$vgwF}%{dOs8$WiO?(6pTpQ<8iYMa{mw_d0F7x(M$Ji6Q!ALT$k_jL7h JS?83{1OT;+xZnT) delta 544 zcmV+*0^j}41Fi&+8Gi-<001BJ|6u?C00DDSM?wIu&K&6g000JJOGiWi{{a60|De66 zlK=n!32;bRa{vGf6951U69E94oEQKA00(qQO+^RX1p)vd2`8DY2><{Au1Q2eR5;6h zlTC}$P!xurJBis?bwvCC3xoXyt}B$%k6+R0a(+ZhN=a?I>VK+hch$YP5Fv$xPAPSz zi&+eI)l*aZ0dNUi03IOsiJw`P zb+THm-g%z)NlIzQy<9F|o2K~!IM{PJ4@}cMiK6JEl=6d=@_pQk#o`U{3b-Ew^bEkG zMx#*_LR_a0#((4SIq)602BLj{!$P58sCjqFah!AD7ZApKSpdT@j38^a^?H4&%;PqI z0Q^1v+@zh?0YiPRhrk`+D1A$!j)1>vyx+iu0PfkgeO#;6UM60=+5^bNi|KUwwO+5k z)c}1mnRvNe?n$v&ya6`Kq^#Zmo6%_Wv)OFAzz*i9NNmlB1Ysa)4|1J(Hrnl$o_>GYUY4OGUfm^W)yo0o{W2>3&uU6-Nfip=Y!FewVUV~wW+OZzuHZPtzqlG ec9J|p(r^Agoe@%^zFiUk00005tPoJ_^mgN)V zcY}|v>ppP+OO};o*uEYF!8xS2S?aoe#(qmVX6XC=A%xdi@HtcLC$544*oW{Uf!z*p z&Mv}_65L6`JXke;Ux4tv1a~?!qN824SQN!E0uKgv5@v=DU7!}gB+v6JEN=e;vvjnp zJg@=|vMjqs;7Y~@Gt6oSF2*cLlH?M>P=Y&2{#AlocLCwdgzqHz&yp|i zeT_M<2A0>8QSRf3lk%~F$=TaT{9oU`ch6xtFm8`lcj8N5`k#;Qe&>Nx%s&AZ$8@;n h29GNo_G#qD9~4!zoSRPl>Q4Xw002ovPDHLkV1fk0eLDaE delta 400 zcmV;B0dM}c0;&U$B!2;OQb$4nuFf3k00002VoOIv0RM-N%)bBt010qNS#tmY3ljhU z3ljkVnw%H_000McNliru+yw#vA1>NWGF|`x0WL{IK~yNuRgpbQ!%!54pPQ75wM8U| z6cbBRKbBU9&aMi&byQjkB8Y#+zfct2v|St&`~ePK#K{k&T7ShwC}?7uuR6q#+k2PG zd7caJIY%I$G#S9gO-f=68<$PkZt<2Vs*KMLYvrZoALxVR}o930@6 zp(NN0qDLwom^PW@u!zT$2vh6%MCb}ESrAN6sL$a=rH~_wuUJq3(a_q}numw?3VtQ&&YGO)d;mK5+jfz5y0B;Bn A;s5{u diff --git a/plugins/pdfviewer/viewer/images/toolbarButton-presentationMode.png b/plugins/pdfviewer/viewer/images/toolbarButton-presentationMode.png index fa730955054b440a9e32ce282a3361cf13f198fa..2bd1c1dfe84330ee65430f38f73abee8eb2bb72a 100644 GIT binary patch delta 425 zcmV;a0apI&1HuE4BYy#jNkl6JA%b}mjaznqvT8QGtEH%c$;flrB z&Qf{{I&86Z>e8V`un}aogQY@vsbo$`Oz=K-8T$jeHIS{O|Dl_`z4$R6e(>@P3GlYrEKl=2FJ9w0I?Pa>4$p}dabL;YYF`at|*V} T2_=9@00000NkvXXu0mjfQX$O- delta 466 zcmV;@0WJQ*1M35jBYyxHbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUz zjY&j7R47wLQB5yHK@>gp@kF&{Rb5bNGzijG&{UKVshtpNClNu)%Ekhbw2`1J>Z6Ht zgGN0xKEhh;{0EDsrSU_qGw%`SWF~jcxpQak3{VFTy2*App?^U^W7KYF9=h<#tI78* z*o_4H`_`WZFA$U$MLmtM$|tM{>uFO(5Ezgr#jo4IB3Y`Hbbm$Mzqn4;4PN=;~lr(@=9 z6FsOU1L=*xEPpK*9S2Z>Dh`cFEofj=!P(7~hDyWvg@rpEyn%woc5DAFjm>GW1^e_x z)~Mz#cWz-Q$RyK&>unXj&avXLWUf|VgYA4#GUXHhIQ(>6fKN_y@j^;c#e?2)RD>|-rwPl1nct% zo)F_R=gc5Ot?t(!v~U0f&t$E1b-L?_1^_LVQT>$JKzkAcF)W4J+_ zmFT@V4hO=-)8+iE)q~YbN;!q zC#(*h9{(dOgnv~(fCJ)uP}h2hKF$#uloA&QbeKau2DCX!aK)J2TR07L3Gf?m1prQ< zwF!KY%kRKmxxZ6vl&X*JtdV2MpS!(YFE@*yOPRZz1(QqMnkzRST6t0={VD4~voOC# zmWzZ0!N9I%6AmZ&*FoJ#zGatz01zQY6c{j~fL}D&hZVoWw{+vX;?|4+0000R2Ufr!B0z*aR30|=ePH_&CN9bGN^_S5((O&!lLLBgeMOj6rwNSAv$&FSRbGu zsH=zQ0~B3+I+E7_$u#lzZc?gOzn_dVuoBQZj8+QHKH; zeR5sbzJ{izrl#H4s$|P^MxY=XtDT=8&O3^tL?V$WW+mxj&9q@jH1ud;>7v##i?bVr zqziP)H)f>$VSn$|0TKN$60RRM9+}it{j`O!IX&)d9K)*b%U@JI`ot-Z|jYQiA}1 ry${99uWjlEaNMLCx&VmS@l})m=P-e`!Dh&<00000NkvXXu0mjfHmlQE diff --git a/plugins/pdfviewer/viewer/images/toolbarButton-search.png b/plugins/pdfviewer/viewer/images/toolbarButton-search.png index 604e652e5ec48052efa7b342c41c33729dbd287c..e52370e68dcaa9fd75a8764505d617d1cc8b2689 100644 GIT binary patch delta 396 zcmV;70dxNM1DgYo8Gi%-006c6H|hWY0b)r+K~yMHb(I=y6!UV0h3$jH;@9Y#F}+7EK?TfmEkMwf3< z?(GMH^&o6t%dHW`1%S(l%B_7vxE>6BD9NcLKALI9$Q?`WLorxmjbZCpR5_;({1g5O zbx_WVitW%-jSfN{*_gb@Xo9rft5P^V^Kli32pc zmllgj*{|-+?lQ$B5h9G^r=2DM_j;swD2eLMOoA|8+<5TNiWA4rfA7_*#H*Xr+k|MV qEgQ$hNOEIt?LUSlybNIc$KTkqeozAONfQ77002ovP6b4+LSTYMKge4E delta 488 zcmVh<8m3!Gk~Xk5eA5h^ zQG@4(UabwL3x9=z3R6Z^Rhdr$-2y3-$y`vQ2kR-VKQN;x3QcOQR^uk8Hk)mQu*WP- z&F}XI;T`=Q9`X5ne*E)Gm=u8uQbhDhGMNm+I|q7xguPy`r&6iJU^;I!8e!bzJfF{p z@!n6^yWQ?Xco)GC==chIJf1CtWrR-)&ZkVMP eSDgS-yWtNpKna1!X!M-`0000pFMtTt-F)P>_j<2`C6;K%{^S z2uU&SD%!?0q8+7+H$#Tu53?JLhV&R{ORX|&;ZPKJ3gSGh$BuZ5oJeCGw_Rt~aK zhW7S026oz&GJK`d_JX>V^3GaS;-36vMlRuTUxDT?^K@|x(Kz3F-c{(3g8=J=nLFAQ z7@OGIAG~{~_kBP2<&%t`#Pcs+z4}#J@qU7ko*2jLDe62s*bW;=-o2{GTs-CZ&j;0f zdMDa;_Gp}1V6XP=mWbQh;HXKPURy_OFmRhYNvEC5z94mSbB5dHlA24b{o9shzOWH# zN%``TW%;y3g+D)6Wo_Jcv&p(hNeDyt@@KsZm|KD6c X$wgTF*mRTWpz!f@^>bP0l+XkKcCF5e delta 1212 zcmV;t1Vj7B1jPxE8Gi%-006c6H|hWY010qNS#tmY3ljhU3ljkVnw%H_00SFoLqkwd zXm50Hb7*gHAW1_*AaHVTW@&6?004N}V_;yO;OOGy$|S(RP*7A-A zje(hgfq{X6Au+kQz%d}ehk=2CAum5Kl>rqnGBA9*#=ywHz<L4HwU zNoooM0|V3!27d+y240u^5(W3f%sd4{Q&k2kgNVo|1qcffJ_s=cNG>fZg9jx8g8+jT zgC9dB!zzXcjM9uzjPn?uG8r;8FkNESU~Xi-%VNVakCmA$apIif zTP0E?g(Z(lRY>bfKarUu8!9I#cUHbf!AFr-@q*G69)tuXJmsIW}5^0BtCQMDDZW3l^c|J>oW z;{~T<&VPGdwz{r&Tj{>kW0B`VuLa%v{M~HOlHNM6?SFcQV5fIid-vg9hCciLx(WLx{-5kP zrFH6w>AW+7XU>}SXpYw0l6m_Ua4ig5w0QByr4Gv`EPuGtcvb7_n`?E~HLbt7(O^@@ z<_BA?woTpsVW;n|ReL!0rtdp`K>c9nq1Q+Jj&43Kexml|lhfX3ww#ka-+tlKrMSzd zu76rwTXsYAX6vo5chc|Pyzleiz$4SgtDh=9oB2ZUW!G!wH+64+zAydo=~KbycVF|q zz59{>^TY4rKi~dU{r?XDE({%upgajN0000WV@Og>004R=004l4008;_004mL004C` z008P>0026e000+nl3&F}0003bNkl*fLB5sx`50{TMdj5h7V2cznnb6vs$2!Bkj z$R}BowY^i)6Kjt5Pe|Ye2+pj^M_HBCT#-~X?b<*CAQ_kUvMkG+MT~^udJ)w*PP0`$ z0YXjQx{;wF34q%CE==X8u`@BDUOX4{d;~ia6I!X!XhnNH6d#n!x;5YTB{eUDCWD?_cNP$pyVSL3+x7Wq)m!+msbM09R{s6X ap8)`ZfrN|Y*6G9m0000s;giyCBGF!61gj z=0=!c-@fAiRUo{{Vb9qJz{67JN zXQiDP>|ksedj?*Z5@rU2uo?dk0^xBBBL*WF+g6Xk77T$3q<>Ru{_g}qQ&R?67~9g2 z!4e38R!Ftx{@?z8m@pH}{V z<^RS17qe6{=)&2h41CalI+OtvnEQV&Qz?Tu5}Owh3g!$M3^@!b;GjmZsRjTFBaUbt TT=)_I0000=O)OX%MzuI*Tn>{(mq7!)yL042nSJkH5?e zFJSCH%nX)52(&_~HTVDa{~P~rRv1E{OEwid!Td-g2E z6+o_!k57Jn{R16W^yLWF#M+cAux(BH1R`|3& zpoB+BkYBJRgHFiq4Vjg-v)U#vTfHE!x4zgyfTP?fLw-}CP_d?YuFSFkt!9zP!qBxo z-aDM#Q)~n5+vZzmGG!Y_)WmgnI$1O3R_9cw9@_Up2WaMIPZ!4!jbPvNSNWP97+fwc z+!C9@D)!3n?%lip|M%}|S@O`%_uOrT%NeTwwn|BgG^MOr&=6xNx_U`p#od$aL1E?T>gTe~DWM4fdzI;B delta 1184 zcmV;R1Yi541gQy-8Gi%-006c6H|hWY010qNS#tmY3ljhU3ljkVnw%H_00SFoLqkwd zXm50Hb7*gHAW1_*AaHVTW@&6?004N}V_;yO;OOGy$|S(RP*7A-A zje(hgfq{X6Au+kQz%d}ehk=2CAum5Kl>rqnGBA9*#=ywHz<L4HwU zNoooM0|V3!27d+y240u^5(W3f%sd4{Q&k2kgNVo|1qcffJ_s=cNG>fZg9jx8g8+jT zgC9dB!zzXcjM9uzjPn?uG8r;8FkNESU~Xi-%VNVakCmA$apIif zTP0E?g(Z(lRY>bfKarUu8!9I#cUHbf!AFr-@q*G69)tuXJmsIW}5^0BtCQMDDZW3l^c|J>oW z;{~T<&VPGdwz{r&Tj{>kW0B`VuLa%v{M~HOlHNM6?SFcQV5fIid-vg9hCciLx(WLx{-5kP zrFH6w>AW+7XU>}SXpYw0l6m_Ua4ig5w0QByr4Gv`EPuGtcvb7_n`?E~HLbt7(O^@@ z<_BA?woTpsVW;n|ReL!0rtdp`K>c9nq1Q+Jj&43Kexml|lhfX3ww#ka-+tlKrMSzd zu76rwTXsYAX6vo5chc|Pyzleiz$4SgtDh=9oB2ZUW!G!wH+64+zAydo=~KbycVF|q zz59{>^TY4rKi~dU{r?XDE({%upgajN0000WV@Og>004R=004l4008;_004mL004C` z008P>0026e000+nl3&F}00039Nkl?UwS!xW^`o(V&0|G1@_8!ha+e{PODF&ZET>}(96r=}v9A0YX&TOtM#ll60l-_q7% zto?_>9H0f!QjG9$v3Z1wyo)Ew) y;%>TYcqwc#%1XCFtQhXyP(zjc${8?V_%#4}6mk8BE;c~`0000-B!BryL_t(|UX_nMF9cB-M$fyhA;C%vp<6LXBt*HrEi`_E|DmDu zQp&3hCW^Vub{y?Y>1|aE;M+L3o#Vp delta 322 zcmV-I0logR0^I_TB!3xnMObuGZ)S9NVRB^vL1b@YWgtmyVP|DhWnpA_ami&o00030 zNkli_`2&%fJW!f?D|27dqmj8_c9Kw@vT|2IpE02u;_Qk7CA0FWS%$VJIT0ssjDnTPJF)wv8{!9a#U=JnhC z+2w{OBllow=R2JYp(z~R7@@pOOaRpkE%ayY(;HSSFhf?cMl?R{rYvF2#|g4+O<1(?p(Zh5lst_d;a`+h~TMHr%s$W@$%8C z^+3KsNswP~h^01z;;P-5vuZb}=omSO3Ch$@;^ya&WM0%-Rabr!s zDl@&TETb-Y4WQl@PZ!4!jo{Mr?tD&$JS+jpKBoI6K2KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z00038Nkl9a112C?Ep!b9RrtvF+eIJKQRC)F!kQ= zy!Sg`3XqA&cML!VByHPnt+m^*73aVVco|?8W+%WyU;=O!UVa`R3$yA>osf#iR}AXF zIk$1nZT1~}#-I+Grdii@z5aIx_rMi!0bGamPk<3{6d0+4k3(o@YwgNfyVB0xWFqo@ z2<==|)v_$hrFQP7&p~hJZu%TN1rK+?BX9$p_Wgyo7}SX|W?_t3?BBs_4ClfqjJ)8%a7dw9Q~Z-}YPx)p7e=b(HX zvo+HHz0eVkbFuzjA}Un6ieP9P528J*+9qyujVT&GUewy&)NMg|f6C6;ykV>(jB@d_ z4n&37LJdKthsLrEnVHO*L2B?6B<0-^yO;Bp6Nzx1OJeo$zsGC4A?dWFhV+Y`O#@$o uLWb0!yg6Y1TL1S@N4Uzx_jG%*&<=mFZ#%=93QGk500000C&;0rB9QM43=ix5N@ z1qDHy57A5H+7?7ZdqdFD5;Qio_X6S~4l0NUK8gJY4syZDJ9yxO1NVTQ->X2?VL-TV zf)3LXI?oC%P16UV9!6P~y;aaG2tA6TdmP7i>cPO_O9jn>=zqe3aNh$HHYH4+<(+~1 z4j8a5VeqWbk|enjDTAkqJcA$z&S4myr3`K=D$Ae-4Z?j3Mlgmx3}OB|gJNm;tS2I6 za`}cIR4Cnb-M!~|dnuFSH~bcbQq6=jDEhN2Hw(D)M_^+qUI6 ijwS89RZ+=3_!R&Qo=tDjU66bL0000YA61;3;N? zVK5kG#vu4HB)v(CfFQT(acjHtHKW-GSlmcA|DOv0?MS*z76447%is-wfpq0Sn!+Y7 zU3v_ibTd2n13X+wm(|@0cv(sRI-CG#XVPWz%ns=?*s?=<{~pc#P(6lDc)!CJFxocu T2PRUk00000NkvXXu0mjfmw-sh delta 194 zcmV;z06qV<0n-7H8Gi-<001BJ|6u?C0GdfeK~#9!rP84a0#Ou%;nNK^`+{u+o3L#k z!k4gEOeWvKXfjzWb|3CK1Cy44x!}Sd4&O<8ZsA*@0qqtIvgEaGt$kgz4zB}-kBipf zHDP$WXwkLsa?zq|;pw7vc}8ywk{ROZ;uvCaI{C-{|MtwPGZL>bXh|`!+;LdLam0$nz=PM^j5(y?OJ0&e zh;AF>Er(sxC3r%+6B{H1tQiV977i Q>llE*)78&qol`;+0Bw6DoB#j- delta 114 zcmV-&0FD2R0gnNYBx_blL_t(|+U?S@2>>AsKv4@jEv)Q?>+Xhd2-k8Vhs%BYKm;uf zJko?;#fiw48#ozY0t!AuLC^vZd#sqZTHs~(74udLWK=XmMb-iy5y=qY?;{-y{7*}m UsUF^C-~a#s07*qoM6N<$f?OgkU;qFB diff --git a/plugins/pdfviewer/viewer/locale/de/viewer.properties b/plugins/pdfviewer/viewer/locale/de/viewer.properties index 5988c827..c9908b50 100644 --- a/plugins/pdfviewer/viewer/locale/de/viewer.properties +++ b/plugins/pdfviewer/viewer/locale/de/viewer.properties @@ -42,6 +42,8 @@ bookmark.title=Aktuelle Ansicht (Kopieren oder in einem neuen Fenster öffnen) bookmark_label=Aktuelle Ansicht # Secondary toolbar and context menu +tools.title=Werkzeuge +tools_label=Werkzeuge first_page.title=Erste Seite first_page.label=Erste Seite first_page_label=Erste Seite @@ -51,9 +53,14 @@ last_page_label=Letzte Seite page_rotate_cw.title=Im Uhrzeigersinn drehen page_rotate_cw.label=Im Uhrzeigersinn drehen page_rotate_cw_label=Im Uhrzeigersinn drehen -page_rotate_ccw.title=Entgegen dem Uhrzeigersinn drehen -page_rotate_ccw.label=Entgegen dem Uhrzeigersinn drehen -page_rotate_ccw_label=Entgegen dem Uhrzeigersinn drehen +page_rotate_ccw.title=Gegen den Uhrzeigersinn drehen +page_rotate_ccw.label=Gegen den Uhrzeigersinn drehen +page_rotate_ccw_label=Gegen den Uhrzeigersinn drehen + +hand_tool_enable.title=Hand-Werkzeug aktivieren +hand_tool_enable_label=Hand-Werkzeug aktivieren +hand_tool_disable.title=Hand-Werkzeug deaktivieren +hand_tool_disable_label=Hand-Werkzeug deaktivieren # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are @@ -91,9 +98,9 @@ find_not_found=Ausdruck nicht gefunden error_more_info=Mehr Info error_less_info=Weniger Info error_close=Schließen -# LOCALIZATION NOTE (error_build): "{{build}}" will be replaced by the PDF.JS -# build ID. -error_build=PDF.JS Build: {{build}} +# LOCALIZATION NOTE (error_version_info): "{{version}}" and "{{build}}" will be +# replaced by the PDF.JS version and build ID. +error_version_info=PDF.js v{{version}} (build: {{build}}) # LOCALIZATION NOTE (error_message): "{{message}}" will be replaced by an # english string describing the error. error_message=Nachricht: {{message}} @@ -104,7 +111,7 @@ error_stack=Stack: {{stack}} error_file=Datei: {{file}} # LOCALIZATION NOTE (error_line): "{{line}}" will be replaced with a line number error_line=Zeile: {{line}} -rendering_error=Das PDF konnte nicht angezeigt werden. +rendering_error=Die PDF-Datei konnte nicht angezeigt werden. # Predefined zoom values page_scale_width=Seitenbreite @@ -114,15 +121,21 @@ page_scale_actual=Originalgröße # Loading indicator messages loading_error_indicator=Fehler -loading_error=Das PDF konnte nicht geladen werden. +loading_error=Die PDF-Datei konnte nicht geladen werden. invalid_file_error=Ungültige oder beschädigte PDF-Datei. +missing_file_error=Fehlende PDF-Datei. # LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip. # "{{type}}" will be replaced with an annotation type from a list defined in # the PDF spec (32000-1:2008 Table 169 – Annotation types). # Some common types are e.g.: "Check", "Text", "Comment", "Note" text_annotation_type.alt=[{{type}} Annotation] -request_password=Das PDF ist passwortgeschützt: +password_label=Geben Sie das Passwort ein, um diese Datei zu öffnen. +password_invalid=Ungültiges Passwort. Versuchen Sie es erneut. +password_ok=OK +password_cancel=Abbrechen printing_not_supported=Warnung: Drucken wird durch diesen Browser nicht vollständig unterstützt. +printing_not_ready=Warnung: Die PDF-Datei ist zum Drucken noch nicht vollständig geladen. web_fonts_disabled=Webfonts sind deaktiviert: Eingebundene PDF-Schriftarten können nicht verwendet werden. +document_colors_disabled=PDF-Dateien können eigene Farben nicht verwenden: \'Seiten das Verwenden von eigenen Farben erlauben\' ist im Browser deaktiviert. diff --git a/plugins/pdfviewer/viewer/locale/en-US/viewer.properties b/plugins/pdfviewer/viewer/locale/en-US/viewer.properties index 629bfb27..2c893a4a 100644 --- a/plugins/pdfviewer/viewer/locale/en-US/viewer.properties +++ b/plugins/pdfviewer/viewer/locale/en-US/viewer.properties @@ -57,6 +57,11 @@ page_rotate_ccw.title=Rotate Counterclockwise page_rotate_ccw.label=Rotate Counterclockwise page_rotate_ccw_label=Rotate Counterclockwise +hand_tool_enable.title=Enable hand tool +hand_tool_enable_label=Enable hand tool +hand_tool_disable.title=Disable hand tool +hand_tool_disable_label=Disable hand tool + # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are # tooltips) diff --git a/plugins/pdfviewer/viewer/locale/es/viewer.properties b/plugins/pdfviewer/viewer/locale/es/viewer.properties index 188cbed6..fc8848f8 100644 --- a/plugins/pdfviewer/viewer/locale/es/viewer.properties +++ b/plugins/pdfviewer/viewer/locale/es/viewer.properties @@ -38,10 +38,12 @@ print.title=Imprimir print_label=Imprimir download.title=Descargar download_label=Descargar -bookmark.title=Vista actual (copie o abra en una ventana nueva) +bookmark.title=Vista actual (para copiar o abrir en otra ventana) bookmark_label=Vista actual # Secondary toolbar and context menu +tools.title=Herramientas +tools_label=Herramientas first_page.title=Ir a la primera página first_page.label=Ir a la primera página first_page_label=Ir a la primera página @@ -55,6 +57,11 @@ page_rotate_ccw.title=Girar a la izquierda page_rotate_ccw.label=Girar a la izquierda page_rotate_ccw_label=Girar a la izquierda +hand_tool_enable.title=Activar la herramienta Mano +hand_tool_enable_label=Activar la herramienta Mano +hand_tool_disable.title=Desactivar la herramienta Mano +hand_tool_disable_label=Desactivar la herramienta Mano + # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are # tooltips) @@ -118,14 +125,17 @@ loading_error=Ocurrió un error al cargar el PDF. invalid_file_error=El archivo PDF no es válido o está dañado. missing_file_error=Falta el archivo PDF. -# LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip. +# LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip. # "{{type}}" will be replaced with an annotation type from a list defined in # the PDF spec (32000-1:2008 Table 169 – Annotation types). # Some common types are e.g.: "Check", "Text", "Comment", "Note" text_annotation_type.alt=[Anotación {{type}}] -request_password=El archivo PDF está protegido por una contraseña: +password_label=Escriba la contraseña para abrir este archivo PDF. +password_invalid=La contraseña no es válida. Inténtelo de nuevo. +password_ok=Aceptar +password_cancel=Cancelar printing_not_supported=Aviso: Este navegador no es compatible completamente con la impresión. printing_not_ready=Aviso: El PDF no se ha cargado completamente para su impresión. web_fonts_disabled=Se han desactivado los tipos de letra web: no se pueden usar los tipos de letra incrustados en el PDF. -web_colors_disabled=Se han desactivado los colores web. +document_colors_disabled=No se permite que los documentos PDF usen sus propios colores: la opción «Permitir que las páginas elijan sus propios colores» está desactivada en el navegador. diff --git a/plugins/pdfviewer/viewer/locale/ja/viewer.properties b/plugins/pdfviewer/viewer/locale/ja/viewer.properties index dd024cae..3b35bbdf 100644 --- a/plugins/pdfviewer/viewer/locale/ja/viewer.properties +++ b/plugins/pdfviewer/viewer/locale/ja/viewer.properties @@ -23,7 +23,7 @@ next_label=次へ # Do not translate "{{pageCount}}", it will be substituted with a number # representing the total number of pages. page_label=ページ: -page_of=of {{pageCount}} +page_of=/ {{pageCount}} zoom_out.title=縮小 zoom_out_label=縮小 @@ -57,6 +57,11 @@ page_rotate_ccw.title=左回転 page_rotate_ccw.label=左回転 page_rotate_ccw_label=左回転 +hand_tool_enable.title=手のひらツールを有効にする +hand_tool_enable_label=手のひらツールを有効にする +hand_tool_disable.title=手のひらツールを無効にする +hand_tool_disable_label=手のひらツールを無効にする + # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are # tooltips) @@ -125,8 +130,10 @@ missing_file_error=PDF ファイルが見つかりません。 # the PDF spec (32000-1:2008 Table 169 – Annotation types). # Some common types are e.g.: "Check", "Text", "Comment", "Note" text_annotation_type.alt=[{{type}} 注釈] -request_password=PDF はパスワードによって保護されています -invalid_password=無効なパスワードです +password_label=この PDF ファイルを開くためのパスワードを入力してください。 +password_invalid=無効なパスワードです。もう一度やり直してください。 +password_ok=OK +password_cancel=キャンセル printing_not_supported=警告:このブラウザでは印刷が完全にサポートされていません printing_not_ready=警告:PDF を印刷するための読み込みが終了していません diff --git a/plugins/pdfviewer/viewer/locale/locale.properties b/plugins/pdfviewer/viewer/locale/locale.properties index d7063280..7e94bc65 100644 --- a/plugins/pdfviewer/viewer/locale/locale.properties +++ b/plugins/pdfviewer/viewer/locale/locale.properties @@ -70,8 +70,8 @@ [sr] @import url(sr/viewer.properties) -[sv] -@import url(sv/viewer.properties) +[sv-SE] +@import url(sv-SE/viewer.properties) [tr] @import url(tr/viewer.properties) diff --git a/plugins/pdfviewer/viewer/locale/nl/viewer.properties b/plugins/pdfviewer/viewer/locale/nl/viewer.properties index 9288af81..38f266d5 100644 --- a/plugins/pdfviewer/viewer/locale/nl/viewer.properties +++ b/plugins/pdfviewer/viewer/locale/nl/viewer.properties @@ -57,6 +57,11 @@ page_rotate_ccw.title=Tegen de klok in roteren page_rotate_ccw.label=Tegen de klok in roteren page_rotate_ccw_label=Tegen de klok in roteren +hand_tool_enable.title=Handcursor inschakelen +hand_tool_enable_label=Handcursor inschakelen +hand_tool_disable.title=Handcursor uitschakelen +hand_tool_disable_label=Handcursor uitschakelen + # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are # tooltips) diff --git a/plugins/pdfviewer/viewer/locale/zh-CN/viewer.properties b/plugins/pdfviewer/viewer/locale/zh-CN/viewer.properties index 28a99713..bbc77c27 100644 --- a/plugins/pdfviewer/viewer/locale/zh-CN/viewer.properties +++ b/plugins/pdfviewer/viewer/locale/zh-CN/viewer.properties @@ -42,6 +42,8 @@ bookmark.title=当前视图(复制或在新窗口中打开) bookmark_label=当前视图 # Secondary toolbar and context menu +tools.title=工具 +tools_label=工具 first_page.title=转到第一页 first_page.label=转到第一页 first_page_label=转到第一页 @@ -55,6 +57,11 @@ page_rotate_ccw.title=逆时针旋转 page_rotate_ccw.label=逆时针旋转 page_rotate_ccw_label=逆时针旋转 +hand_tool_enable.title=启用掌型工具 +hand_tool_enable_label=启用掌型工具 +hand_tool_disable.title=禁用掌型工具 +hand_tool_disable_label=禁用掌型工具 + # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are # tooltips) @@ -123,7 +130,12 @@ missing_file_error=缺失 PDF 文件。 # the PDF spec (32000-1:2008 Table 169 – Annotation types). # Some common types are e.g.: "Check", "Text", "Comment", "Note" text_annotation_type.alt=[{{type}} 注解] -request_password=该 PDF 文档受密码保护: +password_label=输入 PDF 文件的密码。 +password_invalid=密码无效,请重新输入。 +password_ok=确定 +password_cancel=取消 printing_not_supported=警告:该浏览器不能完全支持打印。 +printing_not_ready=警告:此 PDF 没有完全被载入以供打印。 web_fonts_disabled=Web 页面字体已被禁用,无法使用嵌入到 PDF 中的字体。 +document_colors_disabled=PDF 文件不被允许使用自己的颜色:\‘允许页面选择自己的颜色’\没有在该浏览器中被激活。 diff --git a/plugins/pdfviewer/viewer/locale/zh-TW/viewer.properties b/plugins/pdfviewer/viewer/locale/zh-TW/viewer.properties index 766ff145..34564954 100644 --- a/plugins/pdfviewer/viewer/locale/zh-TW/viewer.properties +++ b/plugins/pdfviewer/viewer/locale/zh-TW/viewer.properties @@ -57,6 +57,11 @@ page_rotate_ccw.title=逆時針旋轉 page_rotate_ccw.label=逆時針旋轉 page_rotate_ccw_label=逆時針旋轉 +hand_tool_enable.title=啟用掌型工具 +hand_tool_enable_label=啟用掌型工具 +hand_tool_disable.title=停用掌型工具 +hand_tool_disable_label=停用掌型工具 + # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are # tooltips) @@ -125,8 +130,10 @@ missing_file_error=找不到 PDF 檔案。 # the PDF spec (32000-1:2008 Table 169 – Annotation types). # Some common types are e.g.: "Check", "Text", "Comment", "Note" text_annotation_type.alt=[{{type}} 註解] -request_password=PDF 已被密碼保護: -invalid_password=密碼無效。 +password_label=請輸入用來開啟此 PDF 檔案的密碼。 +password_invalid=密碼不正確,請再試一次。 +password_ok=確定 +password_cancel=取消 printing_not_supported=警告: 此瀏覽器未完整支援列印功能。 printing_not_ready=警告: 此 PDF 未完成下載以供列印。 diff --git a/plugins/pdfviewer/viewer/pdf.js b/plugins/pdfviewer/viewer/pdf.js index 0ff40a1a..c3723f5d 100644 --- a/plugins/pdfviewer/viewer/pdf.js +++ b/plugins/pdfviewer/viewer/pdf.js @@ -1,166 +1,7610 @@ -"undefined"===typeof PDFJS&&(("undefined"!==typeof window?window:this).PDFJS={});PDFJS.version="0.8.573";PDFJS.build="e97e003"; -(function(){var D,V;function S(b){ba>=na&&(J("Info: "+b),PDFJS.LogManager.notify("info",b))}function F(b){ba>=ca&&(J("Warning: "+b),PDFJS.LogManager.notify("warn",b))}function m(b){if(1c)return a;switch(b.substr(0,c)){case "http":case "https":case "ftp":case "mailto":return!0;default:return!1}}function W(b,a,c){Object.defineProperty(b,a,{value:c,enumerable:!0, -configurable:!0,writable:!1});return c}function T(b){var a,c=b.length,e="";if("\u00fe"===b[0]&&"\u00ff"===b[1])for(a=2;a>2)+(l[h+4]?4:0)+(l[h-n+4]?8:0),k[y]&&(f[d+e]=k[y],++t),h+=4;l[h-n]!==l[h]&&(f[d+e]=l[h]?2:4,++t);h+=4;if(1E3>4,f[h]&=d>>2|d<<2);e.push(h%g);e.push(h/g|0);--t}while(l!==h);q.push(e);--b}}return function(f){f.save();f.scale(1/a,-1/c);f.translate(0,-c);f.beginPath();for(var d=0,e=q.length;da[2]&&(c[0]=a[2],c[2]=a[0]);a[1]>a[3]&&(c[1]=a[3],c[3]=a[1]);return c};b.intersect=function(a,c){function e(a,c){return a-c}var d=[a[0],a[2],c[0],c[2]].sort(e),g=[a[1],a[3],c[1],c[3]].sort(e),f=[];a=b.normalizeRect(a);c=b.normalizeRect(c);if(d[0]===a[0]&& -d[1]===c[0]||d[0]===c[0]&&d[1]===a[0])f[0]=d[1],f[2]=d[2];else return!1;if(g[0]===a[1]&&g[1]===c[1]||g[0]===c[1]&&g[1]===a[1])f[1]=g[1],f[3]=g[2];else return!1;return f};b.sign=function(a){return 0>a?-1:1};b.concatenateToArray=function(a,c){Array.prototype.push.apply(a,c)};b.prependToArray=function(a,c){Array.prototype.unshift.apply(a,c)};b.extendObj=function(a,c){for(var e in c)a[e]=c[e]};b.getInheritableProperty=function(a,c){for(;a&&!a.has(c);)a=a.get("Parent");return a?a.get(c):null};b.inherit= -function(a,c,e){a.prototype=Object.create(c.prototype);a.prototype.constructor=a;for(var d in e)a.prototype[d]=e[d]};b.loadScript=function(a,c){var e=document.createElement("script"),d=!1;e.setAttribute("src",a);c&&(e.onload=function(){d||c();d=!0});document.getElementsByTagName("head")[0].appendChild(e)};return b}();PDFJS.PageViewport=function(){function b(a,c,e,d,b,f){this.viewBox=a;this.scale=c;this.rotation=e;this.offsetX=d;this.offsetY=b;var k=(a[2]+a[0])/2,h=(a[3]+a[1])/2,l,n,t;e%=360;switch(0> -e?e+360:e){case 180:e=-1;n=l=0;t=1;break;case 90:e=0;n=l=1;t=0;break;case 270:e=0;n=l=-1;t=0;break;default:e=1,n=l=0,t=-1}f&&(n=-n,t=-t);0===e?(d=Math.abs(h-a[1])*c+d,b=Math.abs(k-a[0])*c+b,f=Math.abs(a[3]-a[1])*c,a=Math.abs(a[2]-a[0])*c):(d=Math.abs(k-a[0])*c+d,b=Math.abs(h-a[1])*c+b,f=Math.abs(a[2]-a[0])*c,a=Math.abs(a[3]-a[1])*c);this.transform=[e*c,l*c,n*c,t*c,d-e*c*k-n*c*h,b-l*c*k-t*c*h];this.width=f;this.height=a;this.fontScale=c}b.prototype={clone:function(a){a=a||{};var c="scale"in a?a.scale: -this.scale,e="rotation"in a?a.rotation:this.rotation;return new b(this.viewBox.slice(),c,e,this.offsetX,this.offsetY,a.dontFlip)},convertToViewportPoint:function(a,c){return s.applyTransform([a,c],this.transform)},convertToViewportRectangle:function(a){var c=s.applyTransform([a[0],a[1]],this.transform);a=s.applyTransform([a[2],a[3]],this.transform);return[c[0],c[1],a[0],a[1]]},convertToPdfPoint:function(a,c){return s.applyInverseTransform([a,c],this.transform)}};return b}();var pa=[0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250,8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364],x=PDFJS.Promise=function(){function b(){this._status=a;this._handlers=[]}var a=0,c=2,e={handlers:[], -running:!1,unhandledRejections:[],pendingRejectionCheck:!1,scheduleHandlers:function(c){c._status!=a&&(this.handlers=this.handlers.concat(c._handlers),c._handlers=[],this.running||(this.running=!0,setTimeout(this.runHandlers.bind(this),0)))},runHandlers:function(){for(;0e&&(e=f.length)}d=0;for(b=a.length;df&&"DeviceGray"!==this.name&&"DeviceRGB"!==this.name){for(var k=8>=d?new Uint8Array(f):new Uint16Array(f), -h=0;ha?0:255b?0:255f?0:255a?0:255a?0:a;b[f+1]=255e?0:e;b[f+2]=255k?0:k}function a(){this.name="DeviceCMYK";this.numComps=4;this.defaultColor=new Float32Array([0,0,0,1])}a.prototype={getRgb:function(a, -e){var d=new Uint8Array(3);b(a,e,1,d,0);return d},getRgbItem:function(a,e,d,g){b(a,e,1,d,g)},getRgbBuffer:function(a,e,d,g,f,k){k=1/((1<>2)},isPassthrough:p.prototype.isPassthrough,createRgbBuffer:p.prototype.createRgbBuffer,isDefaultDecode:function(a){return p.isDefaultDecode(a,this.numComps)},usesZeroToOneRange:!0};return a}(),wa=function(){function b(a,c,b){this.name="Lab";this.numComps=3;this.defaultColor= -new Float32Array([0,0,0]);a||m("WhitePoint missing - required for color space Lab");c=c||[0,0,0];b=b||[-100,100,-100,100];this.XW=a[0];this.YW=a[1];this.ZW=a[2];this.amin=b[0];this.amax=b[1];this.bmin=b[2];this.bmax=b[3];this.XB=c[0];this.YB=c[1];this.ZB=c[2];(0>this.XW||0>this.ZW||1!==this.YW)&&m("Invalid WhitePoint components, no fallback available");if(0>this.XB||0>this.YB||0>this.ZB)S("Invalid BlackPoint, falling back to default"),this.XB=this.YB=this.ZB=0;if(this.amin>this.amax||this.bmin>this.bmax)S("Invalid Range, falling back to defaults"), -this.amin=-100,this.amax=100,this.bmin=-100,this.bmax=100}function a(a){return a>=6/29?a*a*a:108/841*(a-4/29)}function c(c,d,b,f,k,h){var l=d[b],n=d[b+1];d=d[b+2];!1!==f&&(l=0+100*l/f,n=c.amin+n*(c.amax-c.amin)/f,d=c.bmin+d*(c.bmax-c.bmin)/f);n=n>c.amax?c.amax:nc.bmax?c.bmax:dc.ZW?(c=3.1339*n+-1.617*f+-0.4906*l,d=-0.9785*n+1.916*f+0.0333*l,n=0.072*n+-0.229*f+1.4057*l):(c=3.2406*n+-1.5372*f+-0.4986* -l,d=-0.9689*n+1.8758*f+0.0415*l,n=0.0557*n+-0.204*f+1.057*l);k[h]=255*Math.sqrt(0>c?0:1d?0:1n?0:1=f||0>=h)S("Bad shading domain.");else{for(l=d;l<=f;l+=h)t=c.getRgb(n([l]),0),t=s.makeCssRgb(t),b.push([(l-d)/q,t]);d="transparent"; -a.has("Background")&&(t=c.getRgb(a.get("Background"),0),d=s.makeCssRgb(t));g||(b.unshift([0,d]),b[1][0]+=N.SMALL_NUMBER);k||(b[b.length-1][0]-=N.SMALL_NUMBER,b.push([1,d]));this.colorStops=b}}b.fromIR=function(a){var c=a[1],b=a[2],d=a[3],g=a[4],f=a[5],k=a[6];return{type:"Pattern",getPattern:function(a){var l;2==c?l=a.createLinearGradient(d[0],d[1],g[0],g[1]):c==D&&(l=a.createRadialGradient(d[0],d[1],f,g[0],g[1],k));a=0;for(var n=b.length;a>a)*k);f&=(1<l?f=l:fc[t+1]&&(n=c[t+1]);l[h]=n}g.set(f, -l);return l}}}}(),Ea=function(){function b(){this.cache={};this.total=0}b.prototype={has:function(a){return a in this.cache},get:function(a){return this.cache[a]},set:function(a,c){1024>this.total&&(this.cache[a]=c,this.total++)}};return b}(),Fa=function(){function b(a){this.stack=a||[]}b.prototype={push:function(a){100<=this.stack.length&&m("PostScript function stack overflow.");this.stack.push(a)},pop:function(){0>=this.stack.length&&m("PostScript function stack underflow.");return this.stack.pop()}, -copy:function(a){100<=this.stack.length+a&&m("PostScript function stack overflow.");var c=this.stack,b=c.length-a;for(a-=1;0<=a;a--,b++)c.push(c[b])},index:function(a){this.push(this.stack[this.stack.length-a-1])},roll:function(a,c){var b=this.stack,d=b.length-a,g=b.length-1,f=d+(c-Math.floor(c/a)*a),k,h,l;k=d;for(h=g;k>f);break;case "ceiling":g=a.pop();a.push(Math.ceil(g));break;case "copy":g=a.pop();a.copy(g);break;case "cos":g=a.pop();a.push(Math.cos(g));break;case "cvi":g=a.pop()|0;a.push(g);break;case "cvr":break;case "div":f=a.pop();g=a.pop();a.push(g/f);break;case "dup":a.copy(1);break;case "eq":f=a.pop();g=a.pop();a.push(g==f);break;case "exch":a.roll(2,1);break;case "exp":f=a.pop();g=a.pop();a.push(Math.pow(g,f));break;case "false":a.push(!1);break;case "floor":g=a.pop();a.push(Math.floor(g)); -break;case "ge":f=a.pop();g=a.pop();a.push(g>=f);break;case "gt":f=a.pop();g=a.pop();a.push(g>f);break;case "idiv":f=a.pop();g=a.pop();a.push(g/f|0);break;case "index":g=a.pop();a.index(g);break;case "le":f=a.pop();g=a.pop();a.push(g<=f);break;case "ln":g=a.pop();a.push(Math.log(g));break;case "log":g=a.pop();a.push(Math.log(g)/Math.LN10);break;case "lt":f=a.pop();g=a.pop();a.push(gg?Math.ceil(g):Math.floor(g);a.push(g);break;case "xor":f=a.pop();g=a.pop();L(g)&&L(f)?a.push(g!=f):a.push(g^f);break;default:m("Unknown operator "+g)}return a.stack}};return b}(),Ca=function(){function b(a){this.lexer=a;this.operators=[];this.prev=this.token=null}b.prototype={nextToken:function(){this.prev=this.token;this.token=this.lexer.getToken()},accept:function(a){return this.token.type==a?(this.nextToken(),!0):!1},expect:function(a){if(this.accept(a))return!0; -m("Unexpected symbol: found "+this.token.type+" expected "+a+".")},parse:function(){this.nextToken();this.expect(A.LBRACE);this.parseBlock();this.expect(A.RBRACE);return this.operators},parseBlock:function(){for(;;)if(this.accept(A.NUMBER))this.operators.push(this.prev.value);else if(this.accept(A.OPERATOR))this.operators.push(this.prev.value);else if(this.accept(A.LBRACE))this.parseCondition();else break},parseCondition:function(){var a=this.operators.length;this.operators.push(null,null);this.parseBlock(); -this.expect(A.RBRACE);if(this.accept(A.IF))this.operators[a]=this.operators.length,this.operators[a+1]="jz";else if(this.accept(A.LBRACE)){var c=this.operators.length;this.operators.push(null,null);var b=this.operators.length;this.parseBlock();this.expect(A.RBRACE);this.expect(A.IFELSE);this.operators[c]=this.operators.length;this.operators[c+1]="j";this.operators[a]=b;this.operators[a+1]="jz"}else m("PS Function: error parsing conditional.")}};return b}(),A={LBRACE:0,RBRACE:1,NUMBER:2,OPERATOR:3, -IF:4,IFELSE:5},Q=function(){function b(a,b){this.type=a;this.value=b}var a={};b.getOperator=function(c){var e=a[c];return e?e:a[c]=new b(A.OPERATOR,c)};b.LBRACE=new b(A.LBRACE,"{");b.RBRACE=new b(A.RBRACE,"}");b.IF=new b(A.IF,"IF");b.IFELSE=new b(A.IFELSE,"IFELSE");return b}(),Ba=function(){function b(a){this.stream=a;this.nextChar()}b.prototype={nextChar:function(){return this.currentChar=this.stream.getByte()},getToken:function(){for(var a=!1,c=this.currentChar;;){if(0>c)return EOF;if(a){if(10=== -c||13===c)a=!1}else if(37==c)a=!0;else if(!Lexer.isSpace(c))break;c=this.nextChar()}switch(c|0){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return new Q(A.NUMBER,this.getNumber());case 123:return this.nextChar(),Q.LBRACE;case 125:return this.nextChar(),Q.RBRACE}for(a=String.fromCharCode(c);0<=(c=this.nextChar())&&(65<=c&&90>=c||97<=c&&122>=c);)a+=String.fromCharCode(c);switch(a.toLowerCase()){case "if":return Q.IF;case "ifelse":return Q.IFELSE; -default:return Q.getOperator(a)}},getNumber:function(){for(var a=this.currentChar,c=String.fromCharCode(a);0<=(a=this.nextChar());)if(48<=a&&57>=a||45===a||46===a)c+=String.fromCharCode(a);else break;a=parseFloat(c);isNaN(a)&&m("Invalid floating point number: "+a);return a}};return b}(),H=function(){function b(a,b,d){var g=s.getAxialAlignedBoundingBox(b,d);b=g[0];d=g[1];var f=g[2],g=g[3];if(b===f||d===g)return[1,0,0,1,a[0],a[1]];f=(a[2]-a[0])/(f-b);g=(a[3]-a[1])/(g-d);return[f,0,0,g,a[0]-b*f,a[1]- -d*g]}function a(a){if(a.data)this.data=a.data;else{var b=a.dict;a=this.data={};a.subtype=b.get("Subtype").name;var d=b.get("Rect");a.rect=s.normalizeRect(d);a.annotationFlags=b.get("F");d=b.get("C");B(d)&&3===d.length?a.color=d:a.color=[0,0,0];b.has("BS")?(d=b.get("BS"),a.borderWidth=d.has("W")?d.get("W"):1):(d=b.get("Border")||[0,0,1],a.borderWidth=d[2]||0);var g;d=b.get("AP");O(d)?(d=d.get("N"),O(d)?(b=b.get("AS"))&&d.has(b.name)&&(g=d.get(b.name)):g=d):g=void 0;this.appearance=g;a.hasAppearance= -!!this.appearance}}a.prototype={getData:function(){return this.data},hasHtml:function(){return!1},getHtmlElement:function(a){throw new sa("getHtmlElement() should be implemented in subclass");},getEmptyContainer:function(a,b){U&&m("getEmptyContainer() should be called from main thread");b=b||this.data.rect;var d=document.createElement(a);d.style.width=Math.ceil(b[2]-b[0])+"px";d.style.height=Math.ceil(b[3]-b[1])+"px";return d},isViewable:function(){var a=this.data;return!(!a||a.annotationFlags&&a.annotationFlags& -34||!a.rect)},loadResources:function(a){var b=new x;this.appearance.dict.getAsync("Resources").then(function(d){d?(new ObjectLoader(d.map,a,d.xref)).load().then(function(){b.resolve(d)}):b.resolve()}.bind(this));return b},getOperatorList:function(a){var e=new x;if(!this.appearance)return e.resolve(new OperatorList),e;var d=this.data,g=this.appearance.dict,f=this.loadResources("ExtGState ColorSpace Pattern Shading XObject Font".split(" ")),k=g.get("BBox")||[0,0,1,1],h=g.get("Matrix")||[1,0,0,1,0,0], -l=b(d.rect,k,h);f.then(function(b){var f=new OperatorList;f.addOp("beginAnnotation",[d.rect,l,h]);a.getOperatorList(this.appearance,b,f);f.addOp("endAnnotation",[]);e.resolve(f)}.bind(this));return e}};a.getConstructor=function(c,b){if(c){if("Link"===c)return Ga;if("Text"===c)return Ha;if("Widget"===c){if(b)return"Tx"===b?Ia:aa}else return a}};a.fromData=function(c){var b=a.getConstructor(c.subtype,c.fieldType);if(b)return new b({data:c})};a.fromRef=function(c,b){var d=c.fetchIfRef(b);if(O(d)){var g= -d.get("Subtype");if(g=M(g)?g.name:""){var f=s.getInheritableProperty(d,"FT"),f=M(f)?f.name:"";if(f=a.getConstructor(g,f)){d=new f({dict:d,ref:b});if(d.isViewable())return d;K("unimplemented annotation type: "+g)}}}};a.appendToOperatorList=function(a,b,d,g){var f=new x;d=[];for(var k=0,h=a.length;kc.fontDirection?"rtl":"ltr";a&&(g.fontWeight=a.black?a.bold?"bolder":"bold":a.bold?"bold":"normal",g.fontStyle=a.italic?"italic":"normal",c=a.loadedName,g.fontFamily=(c?'"'+c+'", ':"")+(a.fallbackName||"Helvetica, sans-serif"));b.appendChild(d);return b},getOperatorList:function(a){if(this.appearance)return H.prototype.getOperatorList.call(this,a);var c=new x,b=new OperatorList,d=this.data,g=d.defaultAppearance;if(!g)return c.resolve(b),c;for(var f=Stream,k=g.length, -h=new Uint8Array(k),l=0;lf;++f)k=a[f],h=g[f],"setFont"===k?(d.fontRefName=h[0],k=h[1],0>k?(d.fontDirection=-1,d.fontSize=-k):(d.fontDirection=1,d.fontSize=k)):"setFillRGBColor"===k?d.rgb=h:"setFillGray"===k&&(k=255*h[0],d.rgb=[k,k,k]);c.resolve(b);return c}});return b}(),Ha=function(){function b(a){H.call(this,a);if(!a.data){a=a.dict;var c=this.data,b=a.get("Contents"), -d=a.get("T");c.content=T(b||"");c.title=T(d||"");c.name=a.has("Name")?a.get("Name").name:"Note"}}s.inherit(b,H,{getOperatorList:function(a){a=new x;a.resolve(new OperatorList);return a},hasHtml:function(){return!0},getHtmlElement:function(a){U&&m("getHtmlElement() shall be called from main thread");var c=this.data,b=c.rect;10>b[3]-b[1]&&(b[3]=b[1]+10);10>b[2]-b[0]&&(b[2]=b[0]+(b[3]-b[1]));var d=this.getEmptyContainer("section",b);d.className="annotText";a=document.createElement("img");a.style.height= -d.style.height;var g=c.name;a.src=PDFJS.imageResourcesPath+"annotation-"+g.toLowerCase()+".svg";a.alt="[{{type}} Annotation]";a.dataset.l10nId="text_annotation_type";a.dataset.l10nArgs=JSON.stringify({type:g});var f=document.createElement("div");f.setAttribute("hidden",!0);var g=document.createElement("h1"),k=document.createElement("p");f.style.left=Math.floor(b[2]-b[0])+"px";f.style.top="0px";g.textContent=c.title;if(c.content||c.title){for(var b=document.createElement("span"),c=c.content.split(/(?:\r\n?|\n)/), -h=0,l=c.length;hf;++f)g[f]=Math.round(255*d[f]);b.style.borderColor=s.makeCssRgb(g);b.style.borderStyle="solid";d=a[3]-a[1]-2*e;b.style.width=a[2]-a[0]-2*e+"px";b.style.height=d+"px";b.href=this.data.url||"";return b}});return b}();PDFJS.maxImageSize=void 0===PDFJS.maxImageSize?-1: -PDFJS.maxImageSize;PDFJS.disableFontFace=void 0===PDFJS.disableFontFace?!1:PDFJS.disableFontFace;PDFJS.getDocument=function(b,a,c,e){var d,g;"string"===typeof b?b={url:b}:"object"==typeof b&&null!==b&&void 0!==b&&"byteLength"in b?b={data:b}:"object"!==typeof b&&m("Invalid parameter in getDocument, need either Uint8Array, string or a parameter object");b.url||b.data||m("Invalid parameter array, need either .data or .url");var f={};for(d in b)f[d]="url"===d&&"undefined"!==typeof window?oa(window.location.href, -b[d]):b[d];b=new PDFJS.Promise;d=new PDFJS.Promise;g=new Ja(b,d,a,e);b.then(function(){g.passwordCallback=c;g.fetchDocument(f)});return d};var Ka=function(){function b(a,b){this.pdfInfo=a;this.transport=b}b.prototype={get numPages(){return this.pdfInfo.numPages},get fingerprint(){return this.pdfInfo.fingerprint},get embeddedFontsUsed(){return this.transport.embeddedFontsUsed},getPage:function(a){return this.transport.getPage(a)},getDestinations:function(){return this.transport.getDestinations()}, -getJavaScript:function(){var a=new PDFJS.Promise;a.resolve(this.pdfInfo.javaScript);return a},getOutline:function(){var a=new PDFJS.Promise;a.resolve(this.pdfInfo.outline);return a},getMetadata:function(){var a=new PDFJS.Promise,b=this.pdfInfo.metadata;a.resolve({info:this.pdfInfo.info,metadata:b?new PDFJS.Metadata(b):null});return a},isEncrypted:function(){var a=new PDFJS.Promise;a.resolve(this.pdfInfo.encrypted);return a},getData:function(){var a=new PDFJS.Promise;this.transport.getData(a);return a}, -dataLoaded:function(){return this.transport.dataLoaded()},destroy:function(){this.transport.destroy()}};return b}(),Na=function(){function b(a,b){this.pageInfo=a;this.transport=b;this.stats=new ta;this.stats.enabled=!!v.PDFJS.enableStats;this.commonObjs=b.commonObjs;this.objs=new la;this.pendingDestroy=this.cleanupAfterRender=this.receivingOperatorList=!1;this.renderTasks=[]}b.prototype={get pageNumber(){return this.pageInfo.pageIndex+1},get rotate(){return this.pageInfo.rotate},get ref(){return this.pageInfo.ref}, -get view(){return this.pageInfo.view},getViewport:function(a,b){2>arguments.length&&(b=this.rotate);return new PDFJS.PageViewport(this.view,a,b,0,0)},getAnnotations:function(){if(this.annotationsPromise)return this.annotationsPromise;var a=new PDFJS.Promise;this.annotationsPromise=a;this.transport.getAnnotations(this.pageInfo.pageIndex);return a},render:function(a){function b(a){var c=f.renderTasks.indexOf(d);0<=c&&f.renderTasks.splice(c,1);f.cleanupAfterRender&&(f.pendingDestroy=!0);f._tryDestroy(); -a?g.reject(a):g.resolve();e.timeEnd("Rendering");e.timeEnd("Overall")}var e=this.stats;e.time("Overall");this.pendingDestroy=!1;this.displayReadyPromise||(this.receivingOperatorList=!0,this.displayReadyPromise=new x,this.operatorList={fnArray:[],argsArray:[],lastChunk:!1},this.stats.time("Page Request"),this.transport.messageHandler.send("RenderPageRequest",{pageIndex:this.pageNumber-1}));var d=new La(b,a,this.objs,this.commonObjs,this.operatorList,this.pageNumber);this.renderTasks.push(d);var g= -new Ma(d),f=this;this.displayReadyPromise.then(function(a){f.pendingDestroy?b():(e.time("Rendering"),d.initalizeGraphics(a),d.operatorListChanged())},function(a){b(a)});return g},getTextContent:function(){var a=new PDFJS.Promise;this.transport.messageHandler.send("GetTextContent",{pageIndex:this.pageNumber-1},function(b){a.resolve(b)});return a},getOperationList:function(){var a=new PDFJS.Promise;a.resolve({dependencyFontsID:null,operatorList:null});return a},destroy:function(){this.pendingDestroy= -!0;this._tryDestroy()},_tryDestroy:function(){this.pendingDestroy&&0===this.renderTasks.length&&!this.receivingOperatorList&&(delete this.operatorList,delete this.displayReadyPromise,this.objs.clear(),this.pendingDestroy=!1)},_startRenderPage:function(a){this.displayReadyPromise.resolve(a)},_renderPageChunk:function(a){s.concatenateToArray(this.operatorList.fnArray,a.fnArray);s.concatenateToArray(this.operatorList.argsArray,a.argsArray);this.operatorList.lastChunk=a.lastChunk;for(var b=0;b\\376\\377([^<]+)/g,function(a,b){for(var c=b.replace(/\\([0-3])([0-7])([0-7])/g,function(a,b,c,f){return String.fromCharCode(64*b+8*c+1*f)}),f="",k=0;k"+f})}function a(a){"string"===typeof a?(a=b(a),a=(new DOMParser).parseFromString(a,"application/xml")):a instanceof Document||m("Metadata: Invalid metadata object");this.metaDocument=a;this.metadata={};this.parse()}a.prototype={parse:function(){var a=this.metaDocument.documentElement;if("rdf:rdf"!==a.nodeName.toLowerCase())for(a=a.firstChild;a&&"rdf:rdf"!==a.nodeName.toLowerCase();)a=a.nextSibling;var b= -a?a.nodeName.toLowerCase():null;if(a&&"rdf:rdf"===b&&a.hasChildNodes()){var a=a.childNodes,d,g,f,k,h,l;f=0;for(h=a.length;fy)return setTimeout(c, -0),b}},endDrawing:function(){this.ctx.restore();G.clear();this.textLayer&&this.textLayer.endLayout();this.imageLayer&&this.imageLayer.endLayout()},setLineWidth:function(a){this.current.lineWidth=a;this.ctx.lineWidth=a},setLineCap:function(a){this.ctx.lineCap=c[a]},setLineJoin:function(a){this.ctx.lineJoin=e[a]},setMiterLimit:function(a){this.ctx.miterLimit=a},setDash:function(a,b){var c=this.ctx;"setLineDash"in c?(c.setLineDash(a),c.lineDashOffset=b):(c.mozDash=a,c.mozDashOffset=b)},setRenderingIntent:function(a){}, -setFlatness:function(a){},setGState:function(a){for(var b=0,c=a.length;bb?(b=-b,d.fontDirection=-1):d.fontDirection=1;this.current.font=c;this.current.fontSize=b;if(!c.coded){var d=c.black?c.bold?"bolder":"bold":c.bold?"bold":"normal",e=c.italic?"italic":"normal",c='"'+(c.loadedName||"sans-serif")+ -'", '+c.fallbackName,g=16<=b?b:16;this.current.fontSizeScale=16!=g?1:b/16;this.ctx.font=e+" "+d+" "+g+"px "+c}},setTextRenderingMode:function(a){this.current.textRenderingMode=a},setTextRise:function(a){this.current.textRise=a},moveText:function(a,b){this.current.x=this.current.lineX+=a;this.current.y=this.current.lineY+=b},setLeadingMoveText:function(a,b){this.setLeading(-b);this.moveText(a,b)},setTextMatrix:function(a,b,c,d,e,g){this.current.textMatrix=[a,b,c,d,e,g];this.current.x=this.current.lineX= -0;this.current.y=this.current.lineY=0},nextLine:function(){this.moveText(0,this.current.leading)},applyTextTransforms:function(){var a=this.ctx,b=this.current;a.transform.apply(a,b.textMatrix);a.translate(b.x,b.y+b.textRise);0=a);this.baseTransform=this.baseTransformStack.pop()},beginGroup:function(a){this.save();var b=this.ctx;a.isolated||S("TODO: Support non-isolated groups.");a.knockout&&K("Support knockout groups."); -var c=b.mozCurrentTransform;a.matrix&&b.transform.apply(b,a.matrix);a.bbox||m("Bounding box is required.");var d=s.getAxialAlignedBoundingBox(a.bbox,b.mozCurrentTransform);a=Math.max(Math.ceil(d[2]-d[0]),1);var e=Math.max(Math.ceil(d[3]-d[1]),1);a=G.getCanvas("groupAt"+this.groupLevel,a,e,!0).context;e=d[0];d=d[1];a.translate(-e,-d);a.transform.apply(a,c);b.setTransform(1,0,0,1,0,0);b.translate(e,d);c="strokeStyle fillStyle fillRule globalAlpha lineWidth lineCap lineJoin miterLimit globalCompositeOperation font".split(" "); -d=0;for(e=c.length;d=d&&1E3>=e?ra({data:b.data,width:d,height:e}):null);g&&g.compiled?g.compiled(c):(c=G.getCanvas("maskCanvas",d,e), -g=c.context,g.save(),a(g,b),g.globalCompositeOperation="source-in",b=this.current.fillColor,g.fillStyle=b&&b.hasOwnProperty("type")&&"Pattern"===b.type?b.getPattern(g,this):b,g.fillRect(0,0,d,e),g.restore(),this.paintInlineImageXObject(c.canvas))},paintImageMaskXObjectGroup:function(b){for(var c=this.ctx,d=0,e=b.length;d>24&255)+String.fromCharCode(p>>16&255)+String.fromCharCode(p>>8&255)+String.fromCharCode(p&255));f="url(data:font/opentype;base64,"+btoa(m)+");";R.insertRule('@font-face { font-family:"'+b+'";src:'+f+"}");p=[];f=0;for(k=a.length;f= PDFJS.VERBOSITY_LEVELS.infos) { + console.log('Info: ' + msg); + } +} + +// Non-fatal warnings. +function warn(msg) { + if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) { + console.log('Warning: ' + msg); + } +} + +// Fatal errors that should trigger the fallback UI and halt execution by +// throwing an exception. +function error(msg) { + // If multiple arguments were passed, pass them all to the log function. + if (arguments.length > 1) { + var logArguments = ['Error:']; + logArguments.push.apply(logArguments, arguments); + console.log.apply(console, logArguments); + // Join the arguments into a single string for the lines below. + msg = [].join.call(arguments, ' '); + } else { + console.log('Error: ' + msg); + } + console.log(backtrace()); + UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown); + throw new Error(msg); +} + +function backtrace() { + try { + throw new Error(); + } catch (e) { + return e.stack ? e.stack.split('\n').slice(2).join('\n') : ''; + } +} + +function assert(cond, msg) { + if (!cond) + error(msg); +} + +var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = { + unknown: 'unknown', + forms: 'forms', + javaScript: 'javaScript', + smask: 'smask', + shadingPattern: 'shadingPattern', + font: 'font' +}; + +var UnsupportedManager = PDFJS.UnsupportedManager = + (function UnsupportedManagerClosure() { + var listeners = []; + return { + listen: function (cb) { + listeners.push(cb); + }, + notify: function (featureId) { + warn('Unsupported feature "' + featureId + '"'); + for (var i = 0, ii = listeners.length; i < ii; i++) { + listeners[i](featureId); + } + } + }; +})(); + +// Combines two URLs. The baseUrl shall be absolute URL. If the url is an +// absolute URL, it will be returned as is. +function combineUrl(baseUrl, url) { + if (!url) + return baseUrl; + if (url.indexOf(':') >= 0) + return url; + if (url.charAt(0) == '/') { + // absolute path + var i = baseUrl.indexOf('://'); + i = baseUrl.indexOf('/', i + 3); + return baseUrl.substring(0, i) + url; + } else { + // relative path + var pathLength = baseUrl.length, i; + i = baseUrl.lastIndexOf('#'); + pathLength = i >= 0 ? i : pathLength; + i = baseUrl.lastIndexOf('?', pathLength); + pathLength = i >= 0 ? i : pathLength; + var prefixLength = baseUrl.lastIndexOf('/', pathLength); + return baseUrl.substring(0, prefixLength + 1) + url; + } +} + +// Validates if URL is safe and allowed, e.g. to avoid XSS. +function isValidUrl(url, allowRelative) { + if (!url) { + return false; + } + var colon = url.indexOf(':'); + if (colon < 0) { + return allowRelative; + } + var protocol = url.substr(0, colon); + switch (protocol) { + case 'http': + case 'https': + case 'ftp': + case 'mailto': + return true; + default: + return false; + } +} +PDFJS.isValidUrl = isValidUrl; + +// In a well-formed PDF, |cond| holds. If it doesn't, subsequent +// behavior is undefined. +function assertWellFormed(cond, msg) { + if (!cond) + error(msg); +} + +function shadow(obj, prop, value) { + Object.defineProperty(obj, prop, { value: value, + enumerable: true, + configurable: true, + writable: false }); + return value; +} + +var PasswordResponses = PDFJS.PasswordResponses = { + NEED_PASSWORD: 1, + INCORRECT_PASSWORD: 2 +}; + +var PasswordException = (function PasswordExceptionClosure() { + function PasswordException(msg, code) { + this.name = 'PasswordException'; + this.message = msg; + this.code = code; + } + + PasswordException.prototype = new Error(); + PasswordException.constructor = PasswordException; + + return PasswordException; +})(); + +var UnknownErrorException = (function UnknownErrorExceptionClosure() { + function UnknownErrorException(msg, details) { + this.name = 'UnknownErrorException'; + this.message = msg; + this.details = details; + } + + UnknownErrorException.prototype = new Error(); + UnknownErrorException.constructor = UnknownErrorException; + + return UnknownErrorException; +})(); + +var InvalidPDFException = (function InvalidPDFExceptionClosure() { + function InvalidPDFException(msg) { + this.name = 'InvalidPDFException'; + this.message = msg; + } + + InvalidPDFException.prototype = new Error(); + InvalidPDFException.constructor = InvalidPDFException; + + return InvalidPDFException; +})(); + +var MissingPDFException = (function MissingPDFExceptionClosure() { + function MissingPDFException(msg) { + this.name = 'MissingPDFException'; + this.message = msg; + } + + MissingPDFException.prototype = new Error(); + MissingPDFException.constructor = MissingPDFException; + + return MissingPDFException; +})(); + +var NotImplementedException = (function NotImplementedExceptionClosure() { + function NotImplementedException(msg) { + this.message = msg; + } + + NotImplementedException.prototype = new Error(); + NotImplementedException.prototype.name = 'NotImplementedException'; + NotImplementedException.constructor = NotImplementedException; + + return NotImplementedException; +})(); + +var MissingDataException = (function MissingDataExceptionClosure() { + function MissingDataException(begin, end) { + this.begin = begin; + this.end = end; + this.message = 'Missing data [' + begin + ', ' + end + ')'; + } + + MissingDataException.prototype = new Error(); + MissingDataException.prototype.name = 'MissingDataException'; + MissingDataException.constructor = MissingDataException; + + return MissingDataException; +})(); + +var XRefParseException = (function XRefParseExceptionClosure() { + function XRefParseException(msg) { + this.message = msg; + } + + XRefParseException.prototype = new Error(); + XRefParseException.prototype.name = 'XRefParseException'; + XRefParseException.constructor = XRefParseException; + + return XRefParseException; +})(); + + +function bytesToString(bytes) { + var str = ''; + var length = bytes.length; + for (var n = 0; n < length; ++n) + str += String.fromCharCode(bytes[n]); + return str; +} + +function stringToBytes(str) { + var length = str.length; + var bytes = new Uint8Array(length); + for (var n = 0; n < length; ++n) + bytes[n] = str.charCodeAt(n) & 0xFF; + return bytes; +} + +var IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0]; + +var Util = PDFJS.Util = (function UtilClosure() { + function Util() {} + + Util.makeCssRgb = function Util_makeCssRgb(rgb) { + return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'; + }; + + Util.makeCssCmyk = function Util_makeCssCmyk(cmyk) { + var rgb = ColorSpace.singletons.cmyk.getRgb(cmyk, 0); + return Util.makeCssRgb(rgb); + }; + + // Concatenates two transformation matrices together and returns the result. + Util.transform = function Util_transform(m1, m2) { + return [ + m1[0] * m2[0] + m1[2] * m2[1], + m1[1] * m2[0] + m1[3] * m2[1], + m1[0] * m2[2] + m1[2] * m2[3], + m1[1] * m2[2] + m1[3] * m2[3], + m1[0] * m2[4] + m1[2] * m2[5] + m1[4], + m1[1] * m2[4] + m1[3] * m2[5] + m1[5] + ]; + }; + + // For 2d affine transforms + Util.applyTransform = function Util_applyTransform(p, m) { + var xt = p[0] * m[0] + p[1] * m[2] + m[4]; + var yt = p[0] * m[1] + p[1] * m[3] + m[5]; + return [xt, yt]; + }; + + Util.applyInverseTransform = function Util_applyInverseTransform(p, m) { + var d = m[0] * m[3] - m[1] * m[2]; + var xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d; + var yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d; + return [xt, yt]; + }; + + // Applies the transform to the rectangle and finds the minimum axially + // aligned bounding box. + Util.getAxialAlignedBoundingBox = + function Util_getAxialAlignedBoundingBox(r, m) { + + var p1 = Util.applyTransform(r, m); + var p2 = Util.applyTransform(r.slice(2, 4), m); + var p3 = Util.applyTransform([r[0], r[3]], m); + var p4 = Util.applyTransform([r[2], r[1]], m); + return [ + Math.min(p1[0], p2[0], p3[0], p4[0]), + Math.min(p1[1], p2[1], p3[1], p4[1]), + Math.max(p1[0], p2[0], p3[0], p4[0]), + Math.max(p1[1], p2[1], p3[1], p4[1]) + ]; + }; + + Util.inverseTransform = function Util_inverseTransform(m) { + var d = m[0] * m[3] - m[1] * m[2]; + return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, + (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d]; + }; + + // Apply a generic 3d matrix M on a 3-vector v: + // | a b c | | X | + // | d e f | x | Y | + // | g h i | | Z | + // M is assumed to be serialized as [a,b,c,d,e,f,g,h,i], + // with v as [X,Y,Z] + Util.apply3dTransform = function Util_apply3dTransform(m, v) { + return [ + m[0] * v[0] + m[1] * v[1] + m[2] * v[2], + m[3] * v[0] + m[4] * v[1] + m[5] * v[2], + m[6] * v[0] + m[7] * v[1] + m[8] * v[2] + ]; + }; + + // This calculation uses Singular Value Decomposition. + // The SVD can be represented with formula A = USV. We are interested in the + // matrix S here because it represents the scale values. + Util.singularValueDecompose2dScale = + function Util_singularValueDecompose2dScale(m) { + + var transpose = [m[0], m[2], m[1], m[3]]; + + // Multiply matrix m with its transpose. + var a = m[0] * transpose[0] + m[1] * transpose[2]; + var b = m[0] * transpose[1] + m[1] * transpose[3]; + var c = m[2] * transpose[0] + m[3] * transpose[2]; + var d = m[2] * transpose[1] + m[3] * transpose[3]; + + // Solve the second degree polynomial to get roots. + var first = (a + d) / 2; + var second = Math.sqrt((a + d) * (a + d) - 4 * (a * d - c * b)) / 2; + var sx = first + second || 1; + var sy = first - second || 1; + + // Scale values are the square roots of the eigenvalues. + return [Math.sqrt(sx), Math.sqrt(sy)]; + }; + + // Normalize rectangle rect=[x1, y1, x2, y2] so that (x1,y1) < (x2,y2) + // For coordinate systems whose origin lies in the bottom-left, this + // means normalization to (BL,TR) ordering. For systems with origin in the + // top-left, this means (TL,BR) ordering. + Util.normalizeRect = function Util_normalizeRect(rect) { + var r = rect.slice(0); // clone rect + if (rect[0] > rect[2]) { + r[0] = rect[2]; + r[2] = rect[0]; + } + if (rect[1] > rect[3]) { + r[1] = rect[3]; + r[3] = rect[1]; + } + return r; + }; + + // Returns a rectangle [x1, y1, x2, y2] corresponding to the + // intersection of rect1 and rect2. If no intersection, returns 'false' + // The rectangle coordinates of rect1, rect2 should be [x1, y1, x2, y2] + Util.intersect = function Util_intersect(rect1, rect2) { + function compare(a, b) { + return a - b; + } + + // Order points along the axes + var orderedX = [rect1[0], rect1[2], rect2[0], rect2[2]].sort(compare), + orderedY = [rect1[1], rect1[3], rect2[1], rect2[3]].sort(compare), + result = []; + + rect1 = Util.normalizeRect(rect1); + rect2 = Util.normalizeRect(rect2); + + // X: first and second points belong to different rectangles? + if ((orderedX[0] === rect1[0] && orderedX[1] === rect2[0]) || + (orderedX[0] === rect2[0] && orderedX[1] === rect1[0])) { + // Intersection must be between second and third points + result[0] = orderedX[1]; + result[2] = orderedX[2]; + } else { + return false; + } + + // Y: first and second points belong to different rectangles? + if ((orderedY[0] === rect1[1] && orderedY[1] === rect2[1]) || + (orderedY[0] === rect2[1] && orderedY[1] === rect1[1])) { + // Intersection must be between second and third points + result[1] = orderedY[1]; + result[3] = orderedY[2]; + } else { + return false; + } + + return result; + }; + + Util.sign = function Util_sign(num) { + return num < 0 ? -1 : 1; + }; + + // TODO(mack): Rename appendToArray + Util.concatenateToArray = function concatenateToArray(arr1, arr2) { + Array.prototype.push.apply(arr1, arr2); + }; + + Util.prependToArray = function concatenateToArray(arr1, arr2) { + Array.prototype.unshift.apply(arr1, arr2); + }; + + Util.extendObj = function extendObj(obj1, obj2) { + for (var key in obj2) { + obj1[key] = obj2[key]; + } + }; + + Util.getInheritableProperty = function Util_getInheritableProperty(dict, + name) { + while (dict && !dict.has(name)) { + dict = dict.get('Parent'); + } + if (!dict) { + return null; + } + return dict.get(name); + }; + + Util.inherit = function Util_inherit(sub, base, prototype) { + sub.prototype = Object.create(base.prototype); + sub.prototype.constructor = sub; + for (var prop in prototype) { + sub.prototype[prop] = prototype[prop]; + } + }; + + Util.loadScript = function Util_loadScript(src, callback) { + var script = document.createElement('script'); + var loaded = false; + script.setAttribute('src', src); + if (callback) { + script.onload = function() { + if (!loaded) { + callback(); + } + loaded = true; + }; + } + document.getElementsByTagName('head')[0].appendChild(script); + }; + + return Util; +})(); + +var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() { + function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) { + this.viewBox = viewBox; + this.scale = scale; + this.rotation = rotation; + this.offsetX = offsetX; + this.offsetY = offsetY; + + // creating transform to convert pdf coordinate system to the normal + // canvas like coordinates taking in account scale and rotation + var centerX = (viewBox[2] + viewBox[0]) / 2; + var centerY = (viewBox[3] + viewBox[1]) / 2; + var rotateA, rotateB, rotateC, rotateD; + rotation = rotation % 360; + rotation = rotation < 0 ? rotation + 360 : rotation; + switch (rotation) { + case 180: + rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1; + break; + case 90: + rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0; + break; + case 270: + rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0; + break; + //case 0: + default: + rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1; + break; + } + + if (dontFlip) { + rotateC = -rotateC; rotateD = -rotateD; + } + + var offsetCanvasX, offsetCanvasY; + var width, height; + if (rotateA === 0) { + offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX; + offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY; + width = Math.abs(viewBox[3] - viewBox[1]) * scale; + height = Math.abs(viewBox[2] - viewBox[0]) * scale; + } else { + offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX; + offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY; + width = Math.abs(viewBox[2] - viewBox[0]) * scale; + height = Math.abs(viewBox[3] - viewBox[1]) * scale; + } + // creating transform for the following operations: + // translate(-centerX, -centerY), rotate and flip vertically, + // scale, and translate(offsetCanvasX, offsetCanvasY) + this.transform = [ + rotateA * scale, + rotateB * scale, + rotateC * scale, + rotateD * scale, + offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, + offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY + ]; + + this.width = width; + this.height = height; + this.fontScale = scale; + } + PageViewport.prototype = { + clone: function PageViewPort_clone(args) { + args = args || {}; + var scale = 'scale' in args ? args.scale : this.scale; + var rotation = 'rotation' in args ? args.rotation : this.rotation; + return new PageViewport(this.viewBox.slice(), scale, rotation, + this.offsetX, this.offsetY, args.dontFlip); + }, + convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) { + return Util.applyTransform([x, y], this.transform); + }, + convertToViewportRectangle: + function PageViewport_convertToViewportRectangle(rect) { + var tl = Util.applyTransform([rect[0], rect[1]], this.transform); + var br = Util.applyTransform([rect[2], rect[3]], this.transform); + return [tl[0], tl[1], br[0], br[1]]; + }, + convertToPdfPoint: function PageViewport_convertToPdfPoint(x, y) { + return Util.applyInverseTransform([x, y], this.transform); + } + }; + return PageViewport; +})(); + +var PDFStringTranslateTable = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0x2D8, 0x2C7, 0x2C6, 0x2D9, 0x2DD, 0x2DB, 0x2DA, 0x2DC, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, + 0x2013, 0x192, 0x2044, 0x2039, 0x203A, 0x2212, 0x2030, 0x201E, 0x201C, + 0x201D, 0x2018, 0x2019, 0x201A, 0x2122, 0xFB01, 0xFB02, 0x141, 0x152, 0x160, + 0x178, 0x17D, 0x131, 0x142, 0x153, 0x161, 0x17E, 0, 0x20AC +]; + +function stringToPDFString(str) { + var i, n = str.length, str2 = ''; + if (str[0] === '\xFE' && str[1] === '\xFF') { + // UTF16BE BOM + for (i = 2; i < n; i += 2) + str2 += String.fromCharCode( + (str.charCodeAt(i) << 8) | str.charCodeAt(i + 1)); + } else { + for (i = 0; i < n; ++i) { + var code = PDFStringTranslateTable[str.charCodeAt(i)]; + str2 += code ? String.fromCharCode(code) : str.charAt(i); + } + } + return str2; +} + +function stringToUTF8String(str) { + return decodeURIComponent(escape(str)); +} + +function isEmptyObj(obj) { + for (var key in obj) { + return false; + } + return true; +} + +function isBool(v) { + return typeof v == 'boolean'; +} + +function isInt(v) { + return typeof v == 'number' && ((v | 0) == v); +} + +function isNum(v) { + return typeof v == 'number'; +} + +function isString(v) { + return typeof v == 'string'; +} + +function isNull(v) { + return v === null; +} + +function isName(v) { + return v instanceof Name; +} + +function isCmd(v, cmd) { + return v instanceof Cmd && (!cmd || v.cmd == cmd); +} + +function isDict(v, type) { + if (!(v instanceof Dict)) { + return false; + } + if (!type) { + return true; + } + var dictType = v.get('Type'); + return isName(dictType) && dictType.name == type; +} + +function isArray(v) { + return v instanceof Array; +} + +function isStream(v) { + return typeof v == 'object' && v !== null && v !== undefined && + ('getBytes' in v); +} + +function isArrayBuffer(v) { + return typeof v == 'object' && v !== null && v !== undefined && + ('byteLength' in v); +} + +function isRef(v) { + return v instanceof Ref; +} + +function isPDFFunction(v) { + var fnDict; + if (typeof v != 'object') + return false; + else if (isDict(v)) + fnDict = v; + else if (isStream(v)) + fnDict = v.dict; + else + return false; + return fnDict.has('FunctionType'); +} + +/** + * Legacy support for PDFJS Promise implementation. + * TODO remove eventually + */ +var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { + return function LegacyPromise() { + var resolve, reject; + var promise = new Promise(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; + }); + promise.resolve = resolve; + promise.reject = reject; + return promise; + }; +})(); + +/** + * Polyfill for Promises: + * The following promise implementation tries to generally implment the + * Promise/A+ spec. Some notable differences from other promise libaries are: + * - There currently isn't a seperate deferred and promise object. + * - Unhandled rejections eventually show an error if they aren't handled. + * + * Based off of the work in: + * https://bugzilla.mozilla.org/show_bug.cgi?id=810490 + */ +(function PromiseClosure() { + if (globalScope.Promise) { + // Promises existing in the DOM/Worker, checking presence of all/resolve + if (typeof globalScope.Promise.all !== 'function') { + globalScope.Promise.all = function (iterable) { + var count = 0, results = [], resolve, reject; + var promise = new globalScope.Promise(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; + }); + iterable.forEach(function (p, i) { + count++; + p.then(function (result) { + results[i] = result; + count--; + if (count === 0) { + resolve(results); + } + }, reject); + }); + if (count === 0) { + resolve(results); + } + return promise; + }; + } + if (typeof globalScope.Promise.resolve !== 'function') { + globalScope.Promise.resolve = function (x) { + return new globalScope.Promise(function (resolve) { resolve(x); }); + }; + } + return; + } + var STATUS_PENDING = 0; + var STATUS_RESOLVED = 1; + var STATUS_REJECTED = 2; + + // In an attempt to avoid silent exceptions, unhandled rejections are + // tracked and if they aren't handled in a certain amount of time an + // error is logged. + var REJECTION_TIMEOUT = 500; + + var HandlerManager = { + handlers: [], + running: false, + unhandledRejections: [], + pendingRejectionCheck: false, + + scheduleHandlers: function scheduleHandlers(promise) { + if (promise._status == STATUS_PENDING) { + return; + } + + this.handlers = this.handlers.concat(promise._handlers); + promise._handlers = []; + + if (this.running) { + return; + } + this.running = true; + + setTimeout(this.runHandlers.bind(this), 0); + }, + + runHandlers: function runHandlers() { + var RUN_TIMEOUT = 1; // ms + var timeoutAt = Date.now() + RUN_TIMEOUT; + while (this.handlers.length > 0) { + var handler = this.handlers.shift(); + + var nextStatus = handler.thisPromise._status; + var nextValue = handler.thisPromise._value; + + try { + if (nextStatus === STATUS_RESOLVED) { + if (typeof(handler.onResolve) == 'function') { + nextValue = handler.onResolve(nextValue); + } + } else if (typeof(handler.onReject) === 'function') { + nextValue = handler.onReject(nextValue); + nextStatus = STATUS_RESOLVED; + + if (handler.thisPromise._unhandledRejection) { + this.removeUnhandeledRejection(handler.thisPromise); + } + } + } catch (ex) { + nextStatus = STATUS_REJECTED; + nextValue = ex; + } + + handler.nextPromise._updateStatus(nextStatus, nextValue); + if (Date.now() >= timeoutAt) { + break; + } + } + + if (this.handlers.length > 0) { + setTimeout(this.runHandlers.bind(this), 0); + return; + } + + this.running = false; + }, + + addUnhandledRejection: function addUnhandledRejection(promise) { + this.unhandledRejections.push({ + promise: promise, + time: Date.now() + }); + this.scheduleRejectionCheck(); + }, + + removeUnhandeledRejection: function removeUnhandeledRejection(promise) { + promise._unhandledRejection = false; + for (var i = 0; i < this.unhandledRejections.length; i++) { + if (this.unhandledRejections[i].promise === promise) { + this.unhandledRejections.splice(i); + i--; + } + } + }, + + scheduleRejectionCheck: function scheduleRejectionCheck() { + if (this.pendingRejectionCheck) { + return; + } + this.pendingRejectionCheck = true; + setTimeout(function rejectionCheck() { + this.pendingRejectionCheck = false; + var now = Date.now(); + for (var i = 0; i < this.unhandledRejections.length; i++) { + if (now - this.unhandledRejections[i].time > REJECTION_TIMEOUT) { + var unhandled = this.unhandledRejections[i].promise._value; + var msg = 'Unhandled rejection: ' + unhandled; + if (unhandled.stack) { + msg += '\n' + unhandled.stack; + } + warn(msg); + this.unhandledRejections.splice(i); + i--; + } + } + if (this.unhandledRejections.length) { + this.scheduleRejectionCheck(); + } + }.bind(this), REJECTION_TIMEOUT); + } + }; + + function Promise(resolver) { + this._status = STATUS_PENDING; + this._handlers = []; + resolver.call(this, this._resolve.bind(this), this._reject.bind(this)); + } + /** + * Builds a promise that is resolved when all the passed in promises are + * resolved. + * @param {array} array of data and/or promises to wait for. + * @return {Promise} New dependant promise. + */ + Promise.all = function Promise_all(promises) { + var resolveAll, rejectAll; + var deferred = new Promise(function (resolve, reject) { + resolveAll = resolve; + rejectAll = reject; + }); + var unresolved = promises.length; + var results = []; + if (unresolved === 0) { + resolveAll(results); + return deferred; + } + function reject(reason) { + if (deferred._status === STATUS_REJECTED) { + return; + } + results = []; + rejectAll(reason); + } + for (var i = 0, ii = promises.length; i < ii; ++i) { + var promise = promises[i]; + var resolve = (function(i) { + return function(value) { + if (deferred._status === STATUS_REJECTED) { + return; + } + results[i] = value; + unresolved--; + if (unresolved === 0) + resolveAll(results); + }; + })(i); + if (Promise.isPromise(promise)) { + promise.then(resolve, reject); + } else { + resolve(promise); + } + } + return deferred; + }; + + /** + * Checks if the value is likely a promise (has a 'then' function). + * @return {boolean} true if x is thenable + */ + Promise.isPromise = function Promise_isPromise(value) { + return value && typeof value.then === 'function'; + }; + /** + * Creates resolved promise + * @param x resolve value + * @returns {Promise} + */ + Promise.resolve = function Promise_resolve(x) { + return new Promise(function (resolve) { resolve(x); }); + }; + + Promise.prototype = { + _status: null, + _value: null, + _handlers: null, + _unhandledRejection: null, + + _updateStatus: function Promise__updateStatus(status, value) { + if (this._status === STATUS_RESOLVED || + this._status === STATUS_REJECTED) { + return; + } + + if (status == STATUS_RESOLVED && + Promise.isPromise(value)) { + value.then(this._updateStatus.bind(this, STATUS_RESOLVED), + this._updateStatus.bind(this, STATUS_REJECTED)); + return; + } + + this._status = status; + this._value = value; + + if (status === STATUS_REJECTED && this._handlers.length === 0) { + this._unhandledRejection = true; + HandlerManager.addUnhandledRejection(this); + } + + HandlerManager.scheduleHandlers(this); + }, + + _resolve: function Promise_resolve(value) { + this._updateStatus(STATUS_RESOLVED, value); + }, + + _reject: function Promise_reject(reason) { + this._updateStatus(STATUS_REJECTED, reason); + }, + + then: function Promise_then(onResolve, onReject) { + var nextPromise = new Promise(function (resolve, reject) { + this.resolve = reject; + this.reject = reject; + }); + this._handlers.push({ + thisPromise: this, + onResolve: onResolve, + onReject: onReject, + nextPromise: nextPromise + }); + HandlerManager.scheduleHandlers(this); + return nextPromise; + } + }; + + globalScope.Promise = Promise; +})(); + +var StatTimer = (function StatTimerClosure() { + function rpad(str, pad, length) { + while (str.length < length) + str += pad; + return str; + } + function StatTimer() { + this.started = {}; + this.times = []; + this.enabled = true; + } + StatTimer.prototype = { + time: function StatTimer_time(name) { + if (!this.enabled) + return; + if (name in this.started) + warn('Timer is already running for ' + name); + this.started[name] = Date.now(); + }, + timeEnd: function StatTimer_timeEnd(name) { + if (!this.enabled) + return; + if (!(name in this.started)) + warn('Timer has not been started for ' + name); + this.times.push({ + 'name': name, + 'start': this.started[name], + 'end': Date.now() + }); + // Remove timer from started so it can be called again. + delete this.started[name]; + }, + toString: function StatTimer_toString() { + var times = this.times; + var out = ''; + // Find the longest name for padding purposes. + var longest = 0; + for (var i = 0, ii = times.length; i < ii; ++i) { + var name = times[i]['name']; + if (name.length > longest) + longest = name.length; + } + for (var i = 0, ii = times.length; i < ii; ++i) { + var span = times[i]; + var duration = span.end - span.start; + out += rpad(span['name'], ' ', longest) + ' ' + duration + 'ms\n'; + } + return out; + } + }; + return StatTimer; +})(); + +PDFJS.createBlob = function createBlob(data, contentType) { + if (typeof Blob !== 'undefined') + return new Blob([data], { type: contentType }); + // Blob builder is deprecated in FF14 and removed in FF18. + var bb = new MozBlobBuilder(); + bb.append(data); + return bb.getBlob(contentType); +}; + +PDFJS.createObjectURL = (function createObjectURLClosure() { + // Blob/createObjectURL is not available, falling back to data schema. + var digits = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + + return function createObjectURL(data, contentType) { + if (!PDFJS.disableCreateObjectURL && + typeof URL !== 'undefined' && URL.createObjectURL) { + var blob = PDFJS.createBlob(data, contentType); + return URL.createObjectURL(blob); + } + + var buffer = 'data:' + contentType + ';base64,'; + for (var i = 0, ii = data.length; i < ii; i += 3) { + var b1 = data[i] & 0xFF; + var b2 = data[i + 1] & 0xFF; + var b3 = data[i + 2] & 0xFF; + var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4); + var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64; + var d4 = i + 2 < ii ? (b3 & 0x3F) : 64; + buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4]; + } + return buffer; + }; +})(); + +function MessageHandler(name, comObj) { + this.name = name; + this.comObj = comObj; + this.callbackIndex = 1; + this.postMessageTransfers = true; + var callbacks = this.callbacks = {}; + var ah = this.actionHandler = {}; + + ah['console_log'] = [function ahConsoleLog(data) { + console.log.apply(console, data); + }]; + ah['console_error'] = [function ahConsoleError(data) { + console.error.apply(console, data); + }]; + ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) { + UnsupportedManager.notify(data); + }]; + + comObj.onmessage = function messageHandlerComObjOnMessage(event) { + var data = event.data; + if (data.isReply) { + var callbackId = data.callbackId; + if (data.callbackId in callbacks) { + var callback = callbacks[callbackId]; + delete callbacks[callbackId]; + callback(data.data); + } else { + error('Cannot resolve callback ' + callbackId); + } + } else if (data.action in ah) { + var action = ah[data.action]; + if (data.callbackId) { + var deferred = {}; + var promise = new Promise(function (resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + deferred.promise = promise; + promise.then(function(resolvedData) { + comObj.postMessage({ + isReply: true, + callbackId: data.callbackId, + data: resolvedData + }); + }); + action[0].call(action[1], data.data, deferred); + } else { + action[0].call(action[1], data.data); + } + } else { + error('Unkown action from worker: ' + data.action); + } + }; +} + +MessageHandler.prototype = { + on: function messageHandlerOn(actionName, handler, scope) { + var ah = this.actionHandler; + if (ah[actionName]) { + error('There is already an actionName called "' + actionName + '"'); + } + ah[actionName] = [handler, scope]; + }, + /** + * Sends a message to the comObj to invoke the action with the supplied data. + * @param {String} actionName Action to call. + * @param {JSON} data JSON data to send. + * @param {function} [callback] Optional callback that will handle a reply. + * @param {Array} [transfers] Optional list of transfers/ArrayBuffers + */ + send: function messageHandlerSend(actionName, data, callback, transfers) { + var message = { + action: actionName, + data: data + }; + if (callback) { + var callbackId = this.callbackIndex++; + this.callbacks[callbackId] = callback; + message.callbackId = callbackId; + } + if (transfers && this.postMessageTransfers) { + this.comObj.postMessage(message, transfers); + } else { + this.comObj.postMessage(message); + } + } +}; + +function loadJpegStream(id, imageUrl, objs) { + var img = new Image(); + img.onload = (function loadJpegStream_onloadClosure() { + objs.resolve(id, img); + }); + img.src = imageUrl; +} + + +var ColorSpace = (function ColorSpaceClosure() { + // Constructor should define this.numComps, this.defaultColor, this.name + function ColorSpace() { + error('should not call ColorSpace constructor'); + } + + ColorSpace.prototype = { + /** + * Converts the color value to the RGB color. The color components are + * located in the src array starting from the srcOffset. Returns the array + * of the rgb components, each value ranging from [0,255]. + */ + getRgb: function ColorSpace_getRgb(src, srcOffset) { + error('Should not call ColorSpace.getRgb'); + }, + /** + * Converts the color value to the RGB color, similar to the getRgb method. + * The result placed into the dest array starting from the destOffset. + */ + getRgbItem: function ColorSpace_getRgb(src, srcOffset, dest, destOffset) { + error('Should not call ColorSpace.getRgbItem'); + }, + /** + * Converts the specified number of the color values to the RGB colors. + * The colors are located in the src array starting from the srcOffset. + * The result is placed into the dest array starting from the destOffset. + * The src array items shall be in [0,2^bits) range, the dest array items + * will be in [0,255] range. + */ + getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + error('Should not call ColorSpace.getRgbBuffer'); + }, + /** + * Determines amount of the bytes is required to store the reslut of the + * conversion that done by the getRgbBuffer method. + */ + getOutputLength: function ColorSpace_getOutputLength(inputLength) { + error('Should not call ColorSpace.getOutputLength'); + }, + /** + * Returns true if source data will be equal the result/output data. + */ + isPassthrough: function ColorSpace_isPassthrough(bits) { + return false; + }, + /** + * Creates the output buffer and converts the specified number of the color + * values to the RGB colors, similar to the getRgbBuffer. + */ + createRgbBuffer: function ColorSpace_createRgbBuffer(src, srcOffset, + count, bits) { + if (this.isPassthrough(bits)) { + return src.subarray(srcOffset); + } + var dest = new Uint8Array(count * 3); + var numComponentColors = 1 << bits; + // Optimization: create a color map when there is just one component and + // we are converting more colors than the size of the color map. We + // don't build the map if the colorspace is gray or rgb since those + // methods are faster than building a map. This mainly offers big speed + // ups for indexed and alternate colorspaces. + if (this.numComps === 1 && count > numComponentColors && + this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') { + // TODO it may be worth while to cache the color map. While running + // testing I never hit a cache so I will leave that out for now (perhaps + // we are reparsing colorspaces too much?). + var allColors = bits <= 8 ? new Uint8Array(numComponentColors) : + new Uint16Array(numComponentColors); + for (var i = 0; i < numComponentColors; i++) { + allColors[i] = i; + } + var colorMap = new Uint8Array(numComponentColors * 3); + this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bits); + + var destOffset = 0; + for (var i = 0; i < count; ++i) { + var key = src[srcOffset++] * 3; + dest[destOffset++] = colorMap[key]; + dest[destOffset++] = colorMap[key + 1]; + dest[destOffset++] = colorMap[key + 2]; + } + return dest; + } + this.getRgbBuffer(src, srcOffset, count, dest, 0, bits); + return dest; + }, + /** + * True if the colorspace has components in the default range of [0, 1]. + * This should be true for all colorspaces except for lab color spaces + * which are [0,100], [-128, 127], [-128, 127]. + */ + usesZeroToOneRange: true + }; + + ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { + var IR = ColorSpace.parseToIR(cs, xref, res); + if (IR instanceof AlternateCS) + return IR; + + return ColorSpace.fromIR(IR); + }; + + ColorSpace.fromIR = function ColorSpace_fromIR(IR) { + var name = isArray(IR) ? IR[0] : IR; + + switch (name) { + case 'DeviceGrayCS': + return this.singletons.gray; + case 'DeviceRgbCS': + return this.singletons.rgb; + case 'DeviceCmykCS': + return this.singletons.cmyk; + case 'CalGrayCS': + var whitePoint = IR[1].WhitePoint; + var blackPoint = IR[1].BlackPoint; + var gamma = IR[1].Gamma; + return new CalGrayCS(whitePoint, blackPoint, gamma); + case 'PatternCS': + var basePatternCS = IR[1]; + if (basePatternCS) + basePatternCS = ColorSpace.fromIR(basePatternCS); + return new PatternCS(basePatternCS); + case 'IndexedCS': + var baseIndexedCS = IR[1]; + var hiVal = IR[2]; + var lookup = IR[3]; + return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); + case 'AlternateCS': + var numComps = IR[1]; + var alt = IR[2]; + var tintFnIR = IR[3]; + + return new AlternateCS(numComps, ColorSpace.fromIR(alt), + PDFFunction.fromIR(tintFnIR)); + case 'LabCS': + var whitePoint = IR[1].WhitePoint; + var blackPoint = IR[1].BlackPoint; + var range = IR[1].Range; + return new LabCS(whitePoint, blackPoint, range); + default: + error('Unkown name ' + name); + } + return null; + }; + + ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) { + if (isName(cs)) { + var colorSpaces = res.get('ColorSpace'); + if (isDict(colorSpaces)) { + var refcs = colorSpaces.get(cs.name); + if (refcs) + cs = refcs; + } + } + + cs = xref.fetchIfRef(cs); + var mode; + + if (isName(cs)) { + mode = cs.name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return 'DeviceGrayCS'; + case 'DeviceRGB': + case 'RGB': + return 'DeviceRgbCS'; + case 'DeviceCMYK': + case 'CMYK': + return 'DeviceCmykCS'; + case 'Pattern': + return ['PatternCS', null]; + default: + error('unrecognized colorspace ' + mode); + } + } else if (isArray(cs)) { + mode = cs[0].name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return 'DeviceGrayCS'; + case 'DeviceRGB': + case 'RGB': + return 'DeviceRgbCS'; + case 'DeviceCMYK': + case 'CMYK': + return 'DeviceCmykCS'; + case 'CalGray': + var params = cs[1].getAll(); + return ['CalGrayCS', params]; + case 'CalRGB': + return 'DeviceRgbCS'; + case 'ICCBased': + var stream = xref.fetchIfRef(cs[1]); + var dict = stream.dict; + var numComps = dict.get('N'); + if (numComps == 1) + return 'DeviceGrayCS'; + if (numComps == 3) + return 'DeviceRgbCS'; + if (numComps == 4) + return 'DeviceCmykCS'; + break; + case 'Pattern': + var basePatternCS = cs[1]; + if (basePatternCS) + basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); + return ['PatternCS', basePatternCS]; + case 'Indexed': + case 'I': + var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); + var hiVal = cs[2] + 1; + var lookup = xref.fetchIfRef(cs[3]); + if (isStream(lookup)) { + lookup = lookup.getBytes(); + } + return ['IndexedCS', baseIndexedCS, hiVal, lookup]; + case 'Separation': + case 'DeviceN': + var name = cs[1]; + var numComps = 1; + if (isName(name)) + numComps = 1; + else if (isArray(name)) + numComps = name.length; + var alt = ColorSpace.parseToIR(cs[2], xref, res); + var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); + return ['AlternateCS', numComps, alt, tintFnIR]; + case 'Lab': + var params = cs[1].getAll(); + return ['LabCS', params]; + default: + error('unimplemented color space object "' + mode + '"'); + } + } else { + error('unrecognized color space object: "' + cs + '"'); + } + return null; + }; + /** + * Checks if a decode map matches the default decode map for a color space. + * This handles the general decode maps where there are two values per + * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. + * This does not handle Lab, Indexed, or Pattern decode maps since they are + * slightly different. + * @param {Array} decode Decode map (usually from an image). + * @param {Number} n Number of components the color space has. + */ + ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { + if (!decode) + return true; + + if (n * 2 !== decode.length) { + warn('The decode map is not the correct length'); + return true; + } + for (var i = 0, ii = decode.length; i < ii; i += 2) { + if (decode[i] !== 0 || decode[i + 1] != 1) + return false; + } + return true; + }; + + ColorSpace.singletons = { + get gray() { + return shadow(this, 'gray', new DeviceGrayCS()); + }, + get rgb() { + return shadow(this, 'rgb', new DeviceRgbCS()); + }, + get cmyk() { + return shadow(this, 'cmyk', new DeviceCmykCS()); + } + }; + + return ColorSpace; +})(); + +/** + * Alternate color space handles both Separation and DeviceN color spaces. A + * Separation color space is actually just a DeviceN with one color component. + * Both color spaces use a tinting function to convert colors to a base color + * space. + */ +var AlternateCS = (function AlternateCSClosure() { + function AlternateCS(numComps, base, tintFn) { + this.name = 'Alternate'; + this.numComps = numComps; + this.defaultColor = new Float32Array(numComps); + for (var i = 0; i < numComps; ++i) { + this.defaultColor[i] = 1; + } + this.base = base; + this.tintFn = tintFn; + } + + AlternateCS.prototype = { + getRgb: function AlternateCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + }, + getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var baseNumComps = this.base.numComps; + var input = 'subarray' in src ? + src.subarray(srcOffset, srcOffset + this.numComps) : + Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); + var tinted = this.tintFn(input); + this.base.getRgbItem(tinted, 0, dest, destOffset); + }, + getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var tintFn = this.tintFn; + var base = this.base; + var scale = 1 / ((1 << bits) - 1); + var baseNumComps = base.numComps; + var usesZeroToOneRange = base.usesZeroToOneRange; + var isPassthrough = base.isPassthrough(8) || !usesZeroToOneRange; + var pos = isPassthrough ? destOffset : 0; + var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count); + var numComps = this.numComps; + + var scaled = new Float32Array(numComps); + for (var i = 0; i < count; i++) { + for (var j = 0; j < numComps; j++) { + scaled[j] = src[srcOffset++] * scale; + } + var tinted = tintFn(scaled); + if (usesZeroToOneRange) { + for (var j = 0; j < baseNumComps; j++) { + baseBuf[pos++] = tinted[j] * 255; + } + } else { + base.getRgbItem(tinted, 0, baseBuf, pos); + pos += baseNumComps; + } + } + if (!isPassthrough) { + base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8); + } + }, + getOutputLength: function AlternateCS_getOutputLength(inputLength) { + return this.base.getOutputLength(inputLength * + this.base.numComps / this.numComps); + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + + return AlternateCS; +})(); + +var PatternCS = (function PatternCSClosure() { + function PatternCS(baseCS) { + this.name = 'Pattern'; + this.base = baseCS; + } + PatternCS.prototype = {}; + + return PatternCS; +})(); + +var IndexedCS = (function IndexedCSClosure() { + function IndexedCS(base, highVal, lookup) { + this.name = 'Indexed'; + this.numComps = 1; + this.defaultColor = new Uint8Array([0]); + this.base = base; + this.highVal = highVal; + + var baseNumComps = base.numComps; + var length = baseNumComps * highVal; + var lookupArray; + + if (isStream(lookup)) { + lookupArray = new Uint8Array(length); + var bytes = lookup.getBytes(length); + lookupArray.set(bytes); + } else if (isString(lookup)) { + lookupArray = new Uint8Array(length); + for (var i = 0; i < length; ++i) + lookupArray[i] = lookup.charCodeAt(i); + } else if (lookup instanceof Uint8Array || lookup instanceof Array) { + lookupArray = lookup; + } else { + error('Unrecognized lookup table: ' + lookup); + } + this.lookup = lookupArray; + } + + IndexedCS.prototype = { + getRgb: function IndexedCS_getRgb(src, srcOffset) { + var numComps = this.base.numComps; + var start = src[srcOffset] * numComps; + return this.base.getRgb(this.lookup, start); + }, + getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var numComps = this.base.numComps; + var start = src[srcOffset] * numComps; + this.base.getRgbItem(this.lookup, start, dest, destOffset); + }, + getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset) { + var base = this.base; + var numComps = base.numComps; + var outputDelta = base.getOutputLength(numComps); + var lookup = this.lookup; + + for (var i = 0; i < count; ++i) { + var lookupPos = src[srcOffset++] * numComps; + base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8); + destOffset += outputDelta; + } + }, + getOutputLength: function IndexedCS_getOutputLength(inputLength) { + return this.base.getOutputLength(inputLength * this.base.numComps); + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) { + // indexed color maps shouldn't be changed + return true; + }, + usesZeroToOneRange: true + }; + return IndexedCS; +})(); + +var DeviceGrayCS = (function DeviceGrayCSClosure() { + function DeviceGrayCS() { + this.name = 'DeviceGray'; + this.numComps = 1; + this.defaultColor = new Float32Array([0]); + } + + DeviceGrayCS.prototype = { + getRgb: function DeviceGrayCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + }, + getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var c = (src[srcOffset] * 255) | 0; + c = c < 0 ? 0 : c > 255 ? 255 : c; + dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; + }, + getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var scale = 255 / ((1 << bits) - 1); + var j = srcOffset, q = destOffset; + for (var i = 0; i < count; ++i) { + var c = (scale * src[j++]) | 0; + dest[q++] = c; + dest[q++] = c; + dest[q++] = c; + } + }, + getOutputLength: function DeviceGrayCS_getOutputLength(inputLength) { + return inputLength * 3; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + return DeviceGrayCS; +})(); + +var DeviceRgbCS = (function DeviceRgbCSClosure() { + function DeviceRgbCS() { + this.name = 'DeviceRGB'; + this.numComps = 3; + this.defaultColor = new Float32Array([0, 0, 0]); + } + DeviceRgbCS.prototype = { + getRgb: function DeviceRgbCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + }, + getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var r = (src[srcOffset] * 255) | 0; + var g = (src[srcOffset + 1] * 255) | 0; + var b = (src[srcOffset + 2] * 255) | 0; + dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r; + dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g; + dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b; + }, + getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var length = count * 3; + if (bits == 8) { + dest.set(src.subarray(srcOffset, srcOffset + length), destOffset); + return; + } + var scale = 255 / ((1 << bits) - 1); + var j = srcOffset, q = destOffset; + for (var i = 0; i < length; ++i) { + dest[q++] = (scale * src[j++]) | 0; + } + }, + getOutputLength: function DeviceRgbCS_getOutputLength(inputLength) { + return inputLength; + }, + isPassthrough: function DeviceRgbCS_isPassthrough(bits) { + return bits == 8; + }, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + return DeviceRgbCS; +})(); + +var DeviceCmykCS = (function DeviceCmykCSClosure() { + // The coefficients below was found using numerical analysis: the method of + // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors, + // where color_value is the tabular value from the table of sampled RGB colors + // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding + // CMYK color conversion using the estimation below: + // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255 + function convertToRgb(src, srcOffset, srcScale, dest, destOffset) { + var c = src[srcOffset + 0] * srcScale; + var m = src[srcOffset + 1] * srcScale; + var y = src[srcOffset + 2] * srcScale; + var k = src[srcOffset + 3] * srcScale; + + var r = + c * (-4.387332384609988 * c + 54.48615194189176 * m + + 18.82290502165302 * y + 212.25662451639585 * k + + -285.2331026137004) + + m * (1.7149763477362134 * m - 5.6096736904047315 * y + + -17.873870861415444 * k - 5.497006427196366) + + y * (-2.5217340131683033 * y - 21.248923337353073 * k + + 17.5119270841813) + + k * (-21.86122147463605 * k - 189.48180835922747) + 255; + var g = + c * (8.841041422036149 * c + 60.118027045597366 * m + + 6.871425592049007 * y + 31.159100130055922 * k + + -79.2970844816548) + + m * (-15.310361306967817 * m + 17.575251261109482 * y + + 131.35250912493976 * k - 190.9453302588951) + + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + + k * (-20.737325471181034 * k - 187.80453709719578) + 255; + var b = + c * (0.8842522430003296 * c + 8.078677503112928 * m + + 30.89978309703729 * y - 0.23883238689178934 * k + + -14.183576799673286) + + m * (10.49593273432072 * m + 63.02378494754052 * y + + 50.606957656360734 * k - 112.23884253719248) + + y * (0.03296041114873217 * y + 115.60384449646641 * k + + -193.58209356861505) + + k * (-22.33816807309886 * k - 180.12613974708367) + 255; + + dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r; + dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; + dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; + } + + function DeviceCmykCS() { + this.name = 'DeviceCMYK'; + this.numComps = 4; + this.defaultColor = new Float32Array([0, 0, 0, 1]); + } + DeviceCmykCS.prototype = { + getRgb: function DeviceCmykCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + convertToRgb(src, srcOffset, 1, rgb, 0); + return rgb; + }, + getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, + dest, destOffset) { + convertToRgb(src, srcOffset, 1, dest, destOffset); + }, + getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var scale = 1 / ((1 << bits) - 1); + for (var i = 0; i < count; i++) { + convertToRgb(src, srcOffset, scale, dest, destOffset); + srcOffset += 4; + destOffset += 3; + } + }, + getOutputLength: function DeviceCmykCS_getOutputLength(inputLength) { + return (inputLength >> 2) * 3; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + + return DeviceCmykCS; +})(); + +// +// CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245 +// +var CalGrayCS = (function CalGrayCSClosure() { + function CalGrayCS(whitePoint, blackPoint, gamma) { + this.name = 'CalGray'; + this.numComps = 1; + this.defaultColor = new Float32Array([0]); + + if (!whitePoint) { + error('WhitePoint missing - required for color space CalGray'); + } + blackPoint = blackPoint || [0, 0, 0]; + gamma = gamma || 1; + + // Translate arguments to spec variables. + this.XW = whitePoint[0]; + this.YW = whitePoint[1]; + this.ZW = whitePoint[2]; + + this.XB = blackPoint[0]; + this.YB = blackPoint[1]; + this.ZB = blackPoint[2]; + + this.G = gamma; + + // Validate variables as per spec. + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { + error('Invalid WhitePoint components for ' + this.name + + ', no fallback available'); + } + + if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { + info('Invalid BlackPoint for ' + this.name + ', falling back to default'); + this.XB = this.YB = this.ZB = 0; + } + + if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) { + warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB + + ', ZB: ' + this.ZB + ', only default values are supported.'); + } + + if (this.G < 1) { + info('Invalid Gamma: ' + this.G + ' for ' + this.name + + ', falling back to default'); + this.G = 1; + } + } + + function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) { + // A represents a gray component of a calibrated gray space. + // A <---> AG in the spec + var A = src[srcOffset] * scale; + var AG = Math.pow(A, cs.G); + + // Computes intermediate variables M, L, N as per spec. + // Except if other than default BlackPoint values are used. + var M = cs.XW * AG; + var L = cs.YW * AG; + var N = cs.ZW * AG; + + // Decode XYZ, as per spec. + var X = M; + var Y = L; + var Z = N; + + // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4. + // This yields values in range [0, 100]. + var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0); + + // Convert values to rgb range [0, 255]. + dest[destOffset] = Lstar * 255 / 100; + dest[destOffset + 1] = Lstar * 255 / 100; + dest[destOffset + 2] = Lstar * 255 / 100; + } + + CalGrayCS.prototype = { + getRgb: function CalGrayCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + }, + getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset, + dest, destOffset) { + convertToRgb(this, src, srcOffset, dest, destOffset, 1); + }, + getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var scale = 1 / ((1 << bits) - 1); + + for (var i = 0; i < count; ++i) { + convertToRgb(this, src, srcOffset, dest, destOffset, scale); + srcOffset += 1; + destOffset += 3; + } + }, + getOutputLength: function CalGrayCS_getOutputLength(inputLength) { + return inputLength * 3; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + return CalGrayCS; +})(); + +// +// LabCS: Based on "PDF Reference, Sixth Ed", p.250 +// +var LabCS = (function LabCSClosure() { + function LabCS(whitePoint, blackPoint, range) { + this.name = 'Lab'; + this.numComps = 3; + this.defaultColor = new Float32Array([0, 0, 0]); + + if (!whitePoint) + error('WhitePoint missing - required for color space Lab'); + blackPoint = blackPoint || [0, 0, 0]; + range = range || [-100, 100, -100, 100]; + + // Translate args to spec variables + this.XW = whitePoint[0]; + this.YW = whitePoint[1]; + this.ZW = whitePoint[2]; + this.amin = range[0]; + this.amax = range[1]; + this.bmin = range[2]; + this.bmax = range[3]; + + // These are here just for completeness - the spec doesn't offer any + // formulas that use BlackPoint in Lab + this.XB = blackPoint[0]; + this.YB = blackPoint[1]; + this.ZB = blackPoint[2]; + + // Validate vars as per spec + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) + error('Invalid WhitePoint components, no fallback available'); + + if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { + info('Invalid BlackPoint, falling back to default'); + this.XB = this.YB = this.ZB = 0; + } + + if (this.amin > this.amax || this.bmin > this.bmax) { + info('Invalid Range, falling back to defaults'); + this.amin = -100; + this.amax = 100; + this.bmin = -100; + this.bmax = 100; + } + } + + // Function g(x) from spec + function fn_g(x) { + if (x >= 6 / 29) + return x * x * x; + else + return (108 / 841) * (x - 4 / 29); + } + + function decode(value, high1, low2, high2) { + return low2 + (value) * (high2 - low2) / (high1); + } + + // If decoding is needed maxVal should be 2^bits per component - 1. + function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) { + // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] + // not the usual [0, 1]. If a command like setFillColor is used the src + // values will already be within the correct range. However, if we are + // converting an image we have to map the values to the correct range given + // above. + // Ls,as,bs <---> L*,a*,b* in the spec + var Ls = src[srcOffset]; + var as = src[srcOffset + 1]; + var bs = src[srcOffset + 2]; + if (maxVal !== false) { + Ls = decode(Ls, maxVal, 0, 100); + as = decode(as, maxVal, cs.amin, cs.amax); + bs = decode(bs, maxVal, cs.bmin, cs.bmax); + } + + // Adjust limits of 'as' and 'bs' + as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as; + bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs; + + // Computes intermediate variables X,Y,Z as per spec + var M = (Ls + 16) / 116; + var L = M + (as / 500); + var N = M - (bs / 200); + + var X = cs.XW * fn_g(L); + var Y = cs.YW * fn_g(M); + var Z = cs.ZW * fn_g(N); + + var r, g, b; + // Using different conversions for D50 and D65 white points, + // per http://www.color.org/srgb.pdf + if (cs.ZW < 1) { + // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249) + r = X * 3.1339 + Y * -1.6170 + Z * -0.4906; + g = X * -0.9785 + Y * 1.9160 + Z * 0.0333; + b = X * 0.0720 + Y * -0.2290 + Z * 1.4057; + } else { + // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888) + r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; + g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; + b = X * 0.0557 + Y * -0.2040 + Z * 1.0570; + } + // clamp color values to [0,1] range then convert to [0,255] range. + dest[destOffset] = Math.sqrt(r < 0 ? 0 : r > 1 ? 1 : r) * 255; + dest[destOffset + 1] = Math.sqrt(g < 0 ? 0 : g > 1 ? 1 : g) * 255; + dest[destOffset + 2] = Math.sqrt(b < 0 ? 0 : b > 1 ? 1 : b) * 255; + } + + LabCS.prototype = { + getRgb: function LabCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + convertToRgb(this, src, srcOffset, false, rgb, 0); + return rgb; + }, + getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) { + convertToRgb(this, src, srcOffset, false, dest, destOffset); + }, + getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var maxVal = (1 << bits) - 1; + for (var i = 0; i < count; i++) { + convertToRgb(this, src, srcOffset, maxVal, dest, destOffset); + srcOffset += 3; + destOffset += 3; + } + }, + getOutputLength: function LabCS_getOutputLength(inputLength) { + return inputLength; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) { + // XXX: Decoding is handled with the lab conversion because of the strange + // ranges that are used. + return true; + }, + usesZeroToOneRange: false + }; + return LabCS; +})(); + + + +var PatternType = { + AXIAL: 2, + RADIAL: 3 +}; + +var Pattern = (function PatternClosure() { + // Constructor should define this.getPattern + function Pattern() { + error('should not call Pattern constructor'); + } + + Pattern.prototype = { + // Input: current Canvas context + // Output: the appropriate fillStyle or strokeStyle + getPattern: function Pattern_getPattern(ctx) { + error('Should not call Pattern.getStyle: ' + ctx); + } + }; + + Pattern.shadingFromIR = function Pattern_shadingFromIR(raw) { + return Shadings[raw[0]].fromIR(raw); + }; + + Pattern.parseShading = function Pattern_parseShading(shading, matrix, xref, + res) { + + var dict = isStream(shading) ? shading.dict : shading; + var type = dict.get('ShadingType'); + + switch (type) { + case PatternType.AXIAL: + case PatternType.RADIAL: + // Both radial and axial shadings are handled by RadialAxial shading. + return new Shadings.RadialAxial(dict, matrix, xref, res); + default: + UnsupportedManager.notify(UNSUPPORTED_FEATURES.shadingPattern); + return new Shadings.Dummy(); + } + }; + return Pattern; +})(); + +var Shadings = {}; + +// A small number to offset the first/last color stops so we can insert ones to +// support extend. Number.MIN_VALUE appears to be too small and breaks the +// extend. 1e-7 works in FF but chrome seems to use an even smaller sized number +// internally so we have to go bigger. +Shadings.SMALL_NUMBER = 1e-2; + +// Radial and axial shading have very similar implementations +// If needed, the implementations can be broken into two classes +Shadings.RadialAxial = (function RadialAxialClosure() { + function RadialAxial(dict, matrix, xref, res, ctx) { + this.matrix = matrix; + this.coordsArr = dict.get('Coords'); + this.shadingType = dict.get('ShadingType'); + this.type = 'Pattern'; + this.ctx = ctx; + var cs = dict.get('ColorSpace', 'CS'); + cs = ColorSpace.parse(cs, xref, res); + this.cs = cs; + + var t0 = 0.0, t1 = 1.0; + if (dict.has('Domain')) { + var domainArr = dict.get('Domain'); + t0 = domainArr[0]; + t1 = domainArr[1]; + } + + var extendStart = false, extendEnd = false; + if (dict.has('Extend')) { + var extendArr = dict.get('Extend'); + extendStart = extendArr[0]; + extendEnd = extendArr[1]; + } + + if (this.shadingType === PatternType.RADIAL && + (!extendStart || !extendEnd)) { + // Radial gradient only currently works if either circle is fully within + // the other circle. + var x1 = this.coordsArr[0]; + var y1 = this.coordsArr[1]; + var r1 = this.coordsArr[2]; + var x2 = this.coordsArr[3]; + var y2 = this.coordsArr[4]; + var r2 = this.coordsArr[5]; + var distance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)); + if (r1 <= r2 + distance && + r2 <= r1 + distance) { + warn('Unsupported radial gradient.'); + } + } + + this.extendStart = extendStart; + this.extendEnd = extendEnd; + + var fnObj = dict.get('Function'); + var fn; + if (isArray(fnObj)) { + var fnArray = []; + for (var j = 0, jj = fnObj.length; j < jj; j++) { + var obj = xref.fetchIfRef(fnObj[j]); + if (!isPDFFunction(obj)) { + error('Invalid function'); + } + fnArray.push(PDFFunction.parse(xref, obj)); + } + fn = function radialAxialColorFunction(arg) { + var out = []; + for (var i = 0, ii = fnArray.length; i < ii; i++) { + out.push(fnArray[i](arg)[0]); + } + return out; + }; + } else { + if (!isPDFFunction(fnObj)) { + error('Invalid function'); + } + fn = PDFFunction.parse(xref, fnObj); + } + + // 10 samples seems good enough for now, but probably won't work + // if there are sharp color changes. Ideally, we would implement + // the spec faithfully and add lossless optimizations. + var diff = t1 - t0; + var step = diff / 10; + + var colorStops = this.colorStops = []; + + // Protect against bad domains so we don't end up in an infinte loop below. + if (t0 >= t1 || step <= 0) { + // Acrobat doesn't seem to handle these cases so we'll ignore for + // now. + info('Bad shading domain.'); + return; + } + + for (var i = t0; i <= t1; i += step) { + var rgbColor = cs.getRgb(fn([i]), 0); + var cssColor = Util.makeCssRgb(rgbColor); + colorStops.push([(i - t0) / diff, cssColor]); + } + + var background = 'transparent'; + if (dict.has('Background')) { + var rgbColor = cs.getRgb(dict.get('Background'), 0); + background = Util.makeCssRgb(rgbColor); + } + + if (!extendStart) { + // Insert a color stop at the front and offset the first real color stop + // so it doesn't conflict with the one we insert. + colorStops.unshift([0, background]); + colorStops[1][0] += Shadings.SMALL_NUMBER; + } + if (!extendEnd) { + // Same idea as above in extendStart but for the end. + colorStops[colorStops.length - 1][0] -= Shadings.SMALL_NUMBER; + colorStops.push([1, background]); + } + + this.colorStops = colorStops; + } + + RadialAxial.fromIR = function RadialAxial_fromIR(raw) { + var type = raw[1]; + var colorStops = raw[2]; + var p0 = raw[3]; + var p1 = raw[4]; + var r0 = raw[5]; + var r1 = raw[6]; + return { + type: 'Pattern', + getPattern: function RadialAxial_getPattern(ctx) { + var grad; + if (type == PatternType.AXIAL) + grad = ctx.createLinearGradient(p0[0], p0[1], p1[0], p1[1]); + else if (type == PatternType.RADIAL) + grad = ctx.createRadialGradient(p0[0], p0[1], r0, p1[0], p1[1], r1); + + for (var i = 0, ii = colorStops.length; i < ii; ++i) { + var c = colorStops[i]; + grad.addColorStop(c[0], c[1]); + } + return grad; + } + }; + }; + + RadialAxial.prototype = { + getIR: function RadialAxial_getIR() { + var coordsArr = this.coordsArr; + var type = this.shadingType; + if (type == PatternType.AXIAL) { + var p0 = [coordsArr[0], coordsArr[1]]; + var p1 = [coordsArr[2], coordsArr[3]]; + var r0 = null; + var r1 = null; + } else if (type == PatternType.RADIAL) { + var p0 = [coordsArr[0], coordsArr[1]]; + var p1 = [coordsArr[3], coordsArr[4]]; + var r0 = coordsArr[2]; + var r1 = coordsArr[5]; + } else { + error('getPattern type unknown: ' + type); + } + + var matrix = this.matrix; + if (matrix) { + p0 = Util.applyTransform(p0, matrix); + p1 = Util.applyTransform(p1, matrix); + } + + return ['RadialAxial', type, this.colorStops, p0, p1, r0, r1]; + } + }; + + return RadialAxial; +})(); + +Shadings.Dummy = (function DummyClosure() { + function Dummy() { + this.type = 'Pattern'; + } + + Dummy.fromIR = function Dummy_fromIR() { + return { + type: 'Pattern', + getPattern: function Dummy_fromIR_getPattern() { + return 'hotpink'; + } + }; + }; + + Dummy.prototype = { + getIR: function Dummy_getIR() { + return ['Dummy']; + } + }; + return Dummy; +})(); + +var TilingPattern = (function TilingPatternClosure() { + var PaintType = { + COLORED: 1, + UNCOLORED: 2 + }; + + var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough + + function TilingPattern(IR, color, ctx, objs, commonObjs, baseTransform) { + this.name = IR[1][0].name; + this.operatorList = IR[2]; + this.matrix = IR[3] || [1, 0, 0, 1, 0, 0]; + this.bbox = IR[4]; + this.xstep = IR[5]; + this.ystep = IR[6]; + this.paintType = IR[7]; + this.tilingType = IR[8]; + this.color = color; + this.objs = objs; + this.commonObjs = commonObjs; + this.baseTransform = baseTransform; + this.type = 'Pattern'; + this.ctx = ctx; + } + + TilingPattern.getIR = function TilingPattern_getIR(operatorList, dict, args) { + var matrix = dict.get('Matrix'); + var bbox = dict.get('BBox'); + var xstep = dict.get('XStep'); + var ystep = dict.get('YStep'); + var paintType = dict.get('PaintType'); + var tilingType = dict.get('TilingType'); + + return [ + 'TilingPattern', args, operatorList, matrix, bbox, xstep, ystep, + paintType, tilingType + ]; + }; + + TilingPattern.prototype = { + createPatternCanvas: function TilinPattern_createPatternCanvas(owner) { + var operatorList = this.operatorList; + var bbox = this.bbox; + var xstep = this.xstep; + var ystep = this.ystep; + var paintType = this.paintType; + var tilingType = this.tilingType; + var color = this.color; + var objs = this.objs; + var commonObjs = this.commonObjs; + var ctx = this.ctx; + + info('TilingType: ' + tilingType); + + var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; + + var topLeft = [x0, y0]; + // we want the canvas to be as large as the step size + var botRight = [x0 + xstep, y0 + ystep]; + + var width = botRight[0] - topLeft[0]; + var height = botRight[1] - topLeft[1]; + + // Obtain scale from matrix and current transformation matrix. + var matrixScale = Util.singularValueDecompose2dScale(this.matrix); + var curMatrixScale = Util.singularValueDecompose2dScale( + this.baseTransform); + var combinedScale = [matrixScale[0] * curMatrixScale[0], + matrixScale[1] * curMatrixScale[1]]; + + // MAX_PATTERN_SIZE is used to avoid OOM situation. + // Use width and height values that are as close as possible to the end + // result when the pattern is used. Too low value makes the pattern look + // blurry. Too large value makes it look too crispy. + width = Math.min(Math.ceil(Math.abs(width * combinedScale[0])), + MAX_PATTERN_SIZE); + + height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])), + MAX_PATTERN_SIZE); + + var tmpCanvas = CachedCanvases.getCanvas('pattern', width, height, true); + var tmpCtx = tmpCanvas.context; + var graphics = new CanvasGraphics(tmpCtx, commonObjs, objs); + graphics.groupLevel = owner.groupLevel; + + this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color); + + this.setScale(width, height, xstep, ystep); + this.transformToScale(graphics); + + // transform coordinates to pattern space + var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]]; + graphics.transform.apply(graphics, tmpTranslate); + + this.clipBbox(graphics, bbox, x0, y0, x1, y1); + + graphics.executeOperatorList(operatorList); + return tmpCanvas.canvas; + }, + + setScale: function TilingPattern_setScale(width, height, xstep, ystep) { + this.scale = [width / xstep, height / ystep]; + }, + + transformToScale: function TilingPattern_transformToScale(graphics) { + var scale = this.scale; + var tmpScale = [scale[0], 0, 0, scale[1], 0, 0]; + graphics.transform.apply(graphics, tmpScale); + }, + + scaleToContext: function TilingPattern_scaleToContext() { + var scale = this.scale; + this.ctx.scale(1 / scale[0], 1 / scale[1]); + }, + + clipBbox: function clipBbox(graphics, bbox, x0, y0, x1, y1) { + if (bbox && isArray(bbox) && 4 == bbox.length) { + var bboxWidth = x1 - x0; + var bboxHeight = y1 - y0; + graphics.rectangle(x0, y0, bboxWidth, bboxHeight); + graphics.clip(); + graphics.endPath(); + } + }, + + setFillAndStrokeStyleToContext: + function setFillAndStrokeStyleToContext(context, paintType, color) { + switch (paintType) { + case PaintType.COLORED: + var ctx = this.ctx; + context.fillStyle = ctx.fillStyle; + context.strokeStyle = ctx.strokeStyle; + break; + case PaintType.UNCOLORED: + var rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0); + var cssColor = Util.makeCssRgb(rgbColor); + context.fillStyle = cssColor; + context.strokeStyle = cssColor; + break; + default: + error('Unsupported paint type: ' + paintType); + } + }, + + getPattern: function TilingPattern_getPattern(ctx, owner) { + var temporaryPatternCanvas = this.createPatternCanvas(owner); + + var ctx = this.ctx; + ctx.setTransform.apply(ctx, this.baseTransform); + ctx.transform.apply(ctx, this.matrix); + this.scaleToContext(); + + return ctx.createPattern(temporaryPatternCanvas, 'repeat'); + } + }; + + return TilingPattern; +})(); + + + +var PDFFunction = (function PDFFunctionClosure() { + var CONSTRUCT_SAMPLED = 0; + var CONSTRUCT_INTERPOLATED = 2; + var CONSTRUCT_STICHED = 3; + var CONSTRUCT_POSTSCRIPT = 4; + + return { + getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps, + str) { + var length = 1; + for (var i = 0, ii = size.length; i < ii; i++) + length *= size[i]; + length *= outputSize; + + var array = []; + var codeSize = 0; + var codeBuf = 0; + // 32 is a valid bps so shifting won't work + var sampleMul = 1.0 / (Math.pow(2.0, bps) - 1); + + var strBytes = str.getBytes((length * bps + 7) / 8); + var strIdx = 0; + for (var i = 0; i < length; i++) { + while (codeSize < bps) { + codeBuf <<= 8; + codeBuf |= strBytes[strIdx++]; + codeSize += 8; + } + codeSize -= bps; + array.push((codeBuf >> codeSize) * sampleMul); + codeBuf &= (1 << codeSize) - 1; + } + return array; + }, + + getIR: function PDFFunction_getIR(xref, fn) { + var dict = fn.dict; + if (!dict) + dict = fn; + + var types = [this.constructSampled, + null, + this.constructInterpolated, + this.constructStiched, + this.constructPostScript]; + + var typeNum = dict.get('FunctionType'); + var typeFn = types[typeNum]; + if (!typeFn) + error('Unknown type of function'); + + return typeFn.call(this, fn, dict, xref); + }, + + fromIR: function PDFFunction_fromIR(IR) { + var type = IR[0]; + switch (type) { + case CONSTRUCT_SAMPLED: + return this.constructSampledFromIR(IR); + case CONSTRUCT_INTERPOLATED: + return this.constructInterpolatedFromIR(IR); + case CONSTRUCT_STICHED: + return this.constructStichedFromIR(IR); + //case CONSTRUCT_POSTSCRIPT: + default: + return this.constructPostScriptFromIR(IR); + } + }, + + parse: function PDFFunction_parse(xref, fn) { + var IR = this.getIR(xref, fn); + return this.fromIR(IR); + }, + + constructSampled: function PDFFunction_constructSampled(str, dict) { + function toMultiArray(arr) { + var inputLength = arr.length; + var outputLength = arr.length / 2; + var out = []; + var index = 0; + for (var i = 0; i < inputLength; i += 2) { + out[index] = [arr[i], arr[i + 1]]; + ++index; + } + return out; + } + var domain = dict.get('Domain'); + var range = dict.get('Range'); + + if (!domain || !range) + error('No domain or range'); + + var inputSize = domain.length / 2; + var outputSize = range.length / 2; + + domain = toMultiArray(domain); + range = toMultiArray(range); + + var size = dict.get('Size'); + var bps = dict.get('BitsPerSample'); + var order = dict.get('Order') || 1; + if (order !== 1) { + // No description how cubic spline interpolation works in PDF32000:2008 + // As in poppler, ignoring order, linear interpolation may work as good + info('No support for cubic spline interpolation: ' + order); + } + + var encode = dict.get('Encode'); + if (!encode) { + encode = []; + for (var i = 0; i < inputSize; ++i) { + encode.push(0); + encode.push(size[i] - 1); + } + } + encode = toMultiArray(encode); + + var decode = dict.get('Decode'); + if (!decode) + decode = range; + else + decode = toMultiArray(decode); + + var samples = this.getSampleArray(size, outputSize, bps, str); + + return [ + CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size, + outputSize, Math.pow(2, bps) - 1, range + ]; + }, + + constructSampledFromIR: function PDFFunction_constructSampledFromIR(IR) { + // See chapter 3, page 109 of the PDF reference + function interpolate(x, xmin, xmax, ymin, ymax) { + return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin))); + } + + return function constructSampledFromIRResult(args) { + // See chapter 3, page 110 of the PDF reference. + var m = IR[1]; + var domain = IR[2]; + var encode = IR[3]; + var decode = IR[4]; + var samples = IR[5]; + var size = IR[6]; + var n = IR[7]; + var mask = IR[8]; + var range = IR[9]; + + if (m != args.length) + error('Incorrect number of arguments: ' + m + ' != ' + + args.length); + + var x = args; + + // Building the cube vertices: its part and sample index + // http://rjwagner49.com/Mathematics/Interpolation.pdf + var cubeVertices = 1 << m; + var cubeN = new Float64Array(cubeVertices); + var cubeVertex = new Uint32Array(cubeVertices); + for (var j = 0; j < cubeVertices; j++) + cubeN[j] = 1; + + var k = n, pos = 1; + // Map x_i to y_j for 0 <= i < m using the sampled function. + for (var i = 0; i < m; ++i) { + // x_i' = min(max(x_i, Domain_2i), Domain_2i+1) + var domain_2i = domain[i][0]; + var domain_2i_1 = domain[i][1]; + var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1); + + // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1, + // Encode_2i, Encode_2i+1) + var e = interpolate(xi, domain_2i, domain_2i_1, + encode[i][0], encode[i][1]); + + // e_i' = min(max(e_i, 0), Size_i - 1) + var size_i = size[i]; + e = Math.min(Math.max(e, 0), size_i - 1); + + // Adjusting the cube: N and vertex sample index + var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1; + var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0); + var n1 = e - e0; // (e - e0) / (e1 - e0); + var offset0 = e0 * k; + var offset1 = offset0 + k; // e1 * k + for (var j = 0; j < cubeVertices; j++) { + if (j & pos) { + cubeN[j] *= n1; + cubeVertex[j] += offset1; + } else { + cubeN[j] *= n0; + cubeVertex[j] += offset0; + } + } + + k *= size_i; + pos <<= 1; + } + + var y = new Float64Array(n); + for (var j = 0; j < n; ++j) { + // Sum all cube vertices' samples portions + var rj = 0; + for (var i = 0; i < cubeVertices; i++) + rj += samples[cubeVertex[i] + j] * cubeN[i]; + + // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1, + // Decode_2j, Decode_2j+1) + rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]); + + // y_j = min(max(r_j, range_2j), range_2j+1) + y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]); + } + + return y; + }; + }, + + constructInterpolated: function PDFFunction_constructInterpolated(str, + dict) { + var c0 = dict.get('C0') || [0]; + var c1 = dict.get('C1') || [1]; + var n = dict.get('N'); + + if (!isArray(c0) || !isArray(c1)) + error('Illegal dictionary for interpolated function'); + + var length = c0.length; + var diff = []; + for (var i = 0; i < length; ++i) + diff.push(c1[i] - c0[i]); + + return [CONSTRUCT_INTERPOLATED, c0, diff, n]; + }, + + constructInterpolatedFromIR: + function PDFFunction_constructInterpolatedFromIR(IR) { + var c0 = IR[1]; + var diff = IR[2]; + var n = IR[3]; + + var length = diff.length; + + return function constructInterpolatedFromIRResult(args) { + var x = n == 1 ? args[0] : Math.pow(args[0], n); + + var out = []; + for (var j = 0; j < length; ++j) + out.push(c0[j] + (x * diff[j])); + + return out; + + }; + }, + + constructStiched: function PDFFunction_constructStiched(fn, dict, xref) { + var domain = dict.get('Domain'); + + if (!domain) + error('No domain'); + + var inputSize = domain.length / 2; + if (inputSize != 1) + error('Bad domain for stiched function'); + + var fnRefs = dict.get('Functions'); + var fns = []; + for (var i = 0, ii = fnRefs.length; i < ii; ++i) + fns.push(PDFFunction.getIR(xref, xref.fetchIfRef(fnRefs[i]))); + + var bounds = dict.get('Bounds'); + var encode = dict.get('Encode'); + + return [CONSTRUCT_STICHED, domain, bounds, encode, fns]; + }, + + constructStichedFromIR: function PDFFunction_constructStichedFromIR(IR) { + var domain = IR[1]; + var bounds = IR[2]; + var encode = IR[3]; + var fnsIR = IR[4]; + var fns = []; + + for (var i = 0, ii = fnsIR.length; i < ii; i++) { + fns.push(PDFFunction.fromIR(fnsIR[i])); + } + + return function constructStichedFromIRResult(args) { + var clip = function constructStichedFromIRClip(v, min, max) { + if (v > max) + v = max; + else if (v < min) + v = min; + return v; + }; + + // clip to domain + var v = clip(args[0], domain[0], domain[1]); + // calulate which bound the value is in + for (var i = 0, ii = bounds.length; i < ii; ++i) { + if (v < bounds[i]) + break; + } + + // encode value into domain of function + var dmin = domain[0]; + if (i > 0) + dmin = bounds[i - 1]; + var dmax = domain[1]; + if (i < bounds.length) + dmax = bounds[i]; + + var rmin = encode[2 * i]; + var rmax = encode[2 * i + 1]; + + var v2 = rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin); + + // call the appropropriate function + return fns[i]([v2]); + }; + }, + + constructPostScript: function PDFFunction_constructPostScript(fn, dict, + xref) { + var domain = dict.get('Domain'); + var range = dict.get('Range'); + + if (!domain) + error('No domain.'); + + if (!range) + error('No range.'); + + var lexer = new PostScriptLexer(fn); + var parser = new PostScriptParser(lexer); + var code = parser.parse(); + + return [CONSTRUCT_POSTSCRIPT, domain, range, code]; + }, + + constructPostScriptFromIR: function PDFFunction_constructPostScriptFromIR( + IR) { + var domain = IR[1]; + var range = IR[2]; + var code = IR[3]; + var numOutputs = range.length / 2; + var evaluator = new PostScriptEvaluator(code); + // Cache the values for a big speed up, the cache size is limited though + // since the number of possible values can be huge from a PS function. + var cache = new FunctionCache(); + return function constructPostScriptFromIRResult(args) { + var initialStack = []; + for (var i = 0, ii = (domain.length / 2); i < ii; ++i) { + initialStack.push(args[i]); + } + + var key = initialStack.join('_'); + if (cache.has(key)) + return cache.get(key); + + var stack = evaluator.execute(initialStack); + var transformed = []; + for (i = numOutputs - 1; i >= 0; --i) { + var out = stack.pop(); + var rangeIndex = 2 * i; + if (out < range[rangeIndex]) + out = range[rangeIndex]; + else if (out > range[rangeIndex + 1]) + out = range[rangeIndex + 1]; + transformed[i] = out; + } + cache.set(key, transformed); + return transformed; + }; + } + }; +})(); + +var FunctionCache = (function FunctionCacheClosure() { + // Of 10 PDF's with type4 functions the maxium number of distinct values seen + // was 256. This still may need some tweaking in the future though. + var MAX_CACHE_SIZE = 1024; + function FunctionCache() { + this.cache = {}; + this.total = 0; + } + FunctionCache.prototype = { + has: function FunctionCache_has(key) { + return key in this.cache; + }, + get: function FunctionCache_get(key) { + return this.cache[key]; + }, + set: function FunctionCache_set(key, value) { + if (this.total < MAX_CACHE_SIZE) { + this.cache[key] = value; + this.total++; + } + } + }; + return FunctionCache; +})(); + +var PostScriptStack = (function PostScriptStackClosure() { + var MAX_STACK_SIZE = 100; + function PostScriptStack(initialStack) { + this.stack = initialStack || []; + } + + PostScriptStack.prototype = { + push: function PostScriptStack_push(value) { + if (this.stack.length >= MAX_STACK_SIZE) + error('PostScript function stack overflow.'); + this.stack.push(value); + }, + pop: function PostScriptStack_pop() { + if (this.stack.length <= 0) + error('PostScript function stack underflow.'); + return this.stack.pop(); + }, + copy: function PostScriptStack_copy(n) { + if (this.stack.length + n >= MAX_STACK_SIZE) + error('PostScript function stack overflow.'); + var stack = this.stack; + for (var i = stack.length - n, j = n - 1; j >= 0; j--, i++) + stack.push(stack[i]); + }, + index: function PostScriptStack_index(n) { + this.push(this.stack[this.stack.length - n - 1]); + }, + // rotate the last n stack elements p times + roll: function PostScriptStack_roll(n, p) { + var stack = this.stack; + var l = stack.length - n; + var r = stack.length - 1, c = l + (p - Math.floor(p / n) * n), i, j, t; + for (i = l, j = r; i < j; i++, j--) { + t = stack[i]; stack[i] = stack[j]; stack[j] = t; + } + for (i = l, j = c - 1; i < j; i++, j--) { + t = stack[i]; stack[i] = stack[j]; stack[j] = t; + } + for (i = c, j = r; i < j; i++, j--) { + t = stack[i]; stack[i] = stack[j]; stack[j] = t; + } + } + }; + return PostScriptStack; +})(); +var PostScriptEvaluator = (function PostScriptEvaluatorClosure() { + function PostScriptEvaluator(operators, operands) { + this.operators = operators; + this.operands = operands; + } + PostScriptEvaluator.prototype = { + execute: function PostScriptEvaluator_execute(initialStack) { + var stack = new PostScriptStack(initialStack); + var counter = 0; + var operators = this.operators; + var length = operators.length; + var operator, a, b; + while (counter < length) { + operator = operators[counter++]; + if (typeof operator == 'number') { + // Operator is really an operand and should be pushed to the stack. + stack.push(operator); + continue; + } + switch (operator) { + // non standard ps operators + case 'jz': // jump if false + b = stack.pop(); + a = stack.pop(); + if (!a) + counter = b; + break; + case 'j': // jump + a = stack.pop(); + counter = a; + break; + + // all ps operators in alphabetical order (excluding if/ifelse) + case 'abs': + a = stack.pop(); + stack.push(Math.abs(a)); + break; + case 'add': + b = stack.pop(); + a = stack.pop(); + stack.push(a + b); + break; + case 'and': + b = stack.pop(); + a = stack.pop(); + if (isBool(a) && isBool(b)) + stack.push(a && b); + else + stack.push(a & b); + break; + case 'atan': + a = stack.pop(); + stack.push(Math.atan(a)); + break; + case 'bitshift': + b = stack.pop(); + a = stack.pop(); + if (a > 0) + stack.push(a << b); + else + stack.push(a >> b); + break; + case 'ceiling': + a = stack.pop(); + stack.push(Math.ceil(a)); + break; + case 'copy': + a = stack.pop(); + stack.copy(a); + break; + case 'cos': + a = stack.pop(); + stack.push(Math.cos(a)); + break; + case 'cvi': + a = stack.pop() | 0; + stack.push(a); + break; + case 'cvr': + // noop + break; + case 'div': + b = stack.pop(); + a = stack.pop(); + stack.push(a / b); + break; + case 'dup': + stack.copy(1); + break; + case 'eq': + b = stack.pop(); + a = stack.pop(); + stack.push(a == b); + break; + case 'exch': + stack.roll(2, 1); + break; + case 'exp': + b = stack.pop(); + a = stack.pop(); + stack.push(Math.pow(a, b)); + break; + case 'false': + stack.push(false); + break; + case 'floor': + a = stack.pop(); + stack.push(Math.floor(a)); + break; + case 'ge': + b = stack.pop(); + a = stack.pop(); + stack.push(a >= b); + break; + case 'gt': + b = stack.pop(); + a = stack.pop(); + stack.push(a > b); + break; + case 'idiv': + b = stack.pop(); + a = stack.pop(); + stack.push((a / b) | 0); + break; + case 'index': + a = stack.pop(); + stack.index(a); + break; + case 'le': + b = stack.pop(); + a = stack.pop(); + stack.push(a <= b); + break; + case 'ln': + a = stack.pop(); + stack.push(Math.log(a)); + break; + case 'log': + a = stack.pop(); + stack.push(Math.log(a) / Math.LN10); + break; + case 'lt': + b = stack.pop(); + a = stack.pop(); + stack.push(a < b); + break; + case 'mod': + b = stack.pop(); + a = stack.pop(); + stack.push(a % b); + break; + case 'mul': + b = stack.pop(); + a = stack.pop(); + stack.push(a * b); + break; + case 'ne': + b = stack.pop(); + a = stack.pop(); + stack.push(a != b); + break; + case 'neg': + a = stack.pop(); + stack.push(-b); + break; + case 'not': + a = stack.pop(); + if (isBool(a) && isBool(b)) + stack.push(a && b); + else + stack.push(a & b); + break; + case 'or': + b = stack.pop(); + a = stack.pop(); + if (isBool(a) && isBool(b)) + stack.push(a || b); + else + stack.push(a | b); + break; + case 'pop': + stack.pop(); + break; + case 'roll': + b = stack.pop(); + a = stack.pop(); + stack.roll(a, b); + break; + case 'round': + a = stack.pop(); + stack.push(Math.round(a)); + break; + case 'sin': + a = stack.pop(); + stack.push(Math.sin(a)); + break; + case 'sqrt': + a = stack.pop(); + stack.push(Math.sqrt(a)); + break; + case 'sub': + b = stack.pop(); + a = stack.pop(); + stack.push(a - b); + break; + case 'true': + stack.push(true); + break; + case 'truncate': + a = stack.pop(); + a = a < 0 ? Math.ceil(a) : Math.floor(a); + stack.push(a); + break; + case 'xor': + b = stack.pop(); + a = stack.pop(); + if (isBool(a) && isBool(b)) + stack.push(a != b); + else + stack.push(a ^ b); + break; + default: + error('Unknown operator ' + operator); + break; + } + } + return stack.stack; + } + }; + return PostScriptEvaluator; +})(); + +var PostScriptParser = (function PostScriptParserClosure() { + function PostScriptParser(lexer) { + this.lexer = lexer; + this.operators = []; + this.token = null; + this.prev = null; + } + PostScriptParser.prototype = { + nextToken: function PostScriptParser_nextToken() { + this.prev = this.token; + this.token = this.lexer.getToken(); + }, + accept: function PostScriptParser_accept(type) { + if (this.token.type == type) { + this.nextToken(); + return true; + } + return false; + }, + expect: function PostScriptParser_expect(type) { + if (this.accept(type)) + return true; + error('Unexpected symbol: found ' + this.token.type + ' expected ' + + type + '.'); + }, + parse: function PostScriptParser_parse() { + this.nextToken(); + this.expect(PostScriptTokenTypes.LBRACE); + this.parseBlock(); + this.expect(PostScriptTokenTypes.RBRACE); + return this.operators; + }, + parseBlock: function PostScriptParser_parseBlock() { + while (true) { + if (this.accept(PostScriptTokenTypes.NUMBER)) { + this.operators.push(this.prev.value); + } else if (this.accept(PostScriptTokenTypes.OPERATOR)) { + this.operators.push(this.prev.value); + } else if (this.accept(PostScriptTokenTypes.LBRACE)) { + this.parseCondition(); + } else { + return; + } + } + }, + parseCondition: function PostScriptParser_parseCondition() { + // Add two place holders that will be updated later + var conditionLocation = this.operators.length; + this.operators.push(null, null); + + this.parseBlock(); + this.expect(PostScriptTokenTypes.RBRACE); + if (this.accept(PostScriptTokenTypes.IF)) { + // The true block is right after the 'if' so it just falls through on + // true else it jumps and skips the true block. + this.operators[conditionLocation] = this.operators.length; + this.operators[conditionLocation + 1] = 'jz'; + } else if (this.accept(PostScriptTokenTypes.LBRACE)) { + var jumpLocation = this.operators.length; + this.operators.push(null, null); + var endOfTrue = this.operators.length; + this.parseBlock(); + this.expect(PostScriptTokenTypes.RBRACE); + this.expect(PostScriptTokenTypes.IFELSE); + // The jump is added at the end of the true block to skip the false + // block. + this.operators[jumpLocation] = this.operators.length; + this.operators[jumpLocation + 1] = 'j'; + + this.operators[conditionLocation] = endOfTrue; + this.operators[conditionLocation + 1] = 'jz'; + } else { + error('PS Function: error parsing conditional.'); + } + } + }; + return PostScriptParser; +})(); + +var PostScriptTokenTypes = { + LBRACE: 0, + RBRACE: 1, + NUMBER: 2, + OPERATOR: 3, + IF: 4, + IFELSE: 5 +}; + +var PostScriptToken = (function PostScriptTokenClosure() { + function PostScriptToken(type, value) { + this.type = type; + this.value = value; + } + + var opCache = {}; + + PostScriptToken.getOperator = function PostScriptToken_getOperator(op) { + var opValue = opCache[op]; + if (opValue) + return opValue; + + return opCache[op] = new PostScriptToken(PostScriptTokenTypes.OPERATOR, op); + }; + + PostScriptToken.LBRACE = new PostScriptToken(PostScriptTokenTypes.LBRACE, + '{'); + PostScriptToken.RBRACE = new PostScriptToken(PostScriptTokenTypes.RBRACE, + '}'); + PostScriptToken.IF = new PostScriptToken(PostScriptTokenTypes.IF, 'IF'); + PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE, + 'IFELSE'); + return PostScriptToken; +})(); + +var PostScriptLexer = (function PostScriptLexerClosure() { + function PostScriptLexer(stream) { + this.stream = stream; + this.nextChar(); + } + PostScriptLexer.prototype = { + nextChar: function PostScriptLexer_nextChar() { + return (this.currentChar = this.stream.getByte()); + }, + getToken: function PostScriptLexer_getToken() { + var s = ''; + var comment = false; + var ch = this.currentChar; + + // skip comments + while (true) { + if (ch < 0) { + return EOF; + } + + if (comment) { + if (ch === 0x0A || ch === 0x0D) { + comment = false; + } + } else if (ch == 0x25) { // '%' + comment = true; + } else if (!Lexer.isSpace(ch)) { + break; + } + ch = this.nextChar(); + } + switch (ch | 0) { + case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4' + case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9' + case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.' + return new PostScriptToken(PostScriptTokenTypes.NUMBER, + this.getNumber()); + case 0x7B: // '{' + this.nextChar(); + return PostScriptToken.LBRACE; + case 0x7D: // '}' + this.nextChar(); + return PostScriptToken.RBRACE; + } + // operator + var str = String.fromCharCode(ch); + while ((ch = this.nextChar()) >= 0 && // and 'A'-'Z', 'a'-'z' + ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) { + str += String.fromCharCode(ch); + } + switch (str.toLowerCase()) { + case 'if': + return PostScriptToken.IF; + case 'ifelse': + return PostScriptToken.IFELSE; + default: + return PostScriptToken.getOperator(str); + } + }, + getNumber: function PostScriptLexer_getNumber() { + var ch = this.currentChar; + var str = String.fromCharCode(ch); + while ((ch = this.nextChar()) >= 0) { + if ((ch >= 0x30 && ch <= 0x39) || // '0'-'9' + ch === 0x2D || ch === 0x2E) { // '-', '.' + str += String.fromCharCode(ch); + } else { + break; + } + } + var value = parseFloat(str); + if (isNaN(value)) + error('Invalid floating point number: ' + value); + return value; + } + }; + return PostScriptLexer; +})(); + + + +var Annotation = (function AnnotationClosure() { + // 12.5.5: Algorithm: Appearance streams + function getTransformMatrix(rect, bbox, matrix) { + var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix); + var minX = bounds[0]; + var minY = bounds[1]; + var maxX = bounds[2]; + var maxY = bounds[3]; + + if (minX === maxX || minY === maxY) { + // From real-life file, bbox was [0, 0, 0, 0]. In this case, + // just apply the transform for rect + return [1, 0, 0, 1, rect[0], rect[1]]; + } + + var xRatio = (rect[2] - rect[0]) / (maxX - minX); + var yRatio = (rect[3] - rect[1]) / (maxY - minY); + return [ + xRatio, + 0, + 0, + yRatio, + rect[0] - minX * xRatio, + rect[1] - minY * yRatio + ]; + } + + function getDefaultAppearance(dict) { + var appearanceState = dict.get('AP'); + if (!isDict(appearanceState)) { + return; + } + + var appearance; + var appearances = appearanceState.get('N'); + if (isDict(appearances)) { + var as = dict.get('AS'); + if (as && appearances.has(as.name)) { + appearance = appearances.get(as.name); + } + } else { + appearance = appearances; + } + return appearance; + } + + function Annotation(params) { + if (params.data) { + this.data = params.data; + return; + } + + var dict = params.dict; + var data = this.data = {}; + + data.subtype = dict.get('Subtype').name; + var rect = dict.get('Rect'); + data.rect = Util.normalizeRect(rect); + data.annotationFlags = dict.get('F'); + + var color = dict.get('C'); + if (isArray(color) && color.length === 3) { + // TODO(mack): currently only supporting rgb; need support different + // colorspaces + data.color = color; + } else { + data.color = [0, 0, 0]; + } + + // Some types of annotations have border style dict which has more + // info than the border array + if (dict.has('BS')) { + var borderStyle = dict.get('BS'); + data.borderWidth = borderStyle.has('W') ? borderStyle.get('W') : 1; + } else { + var borderArray = dict.get('Border') || [0, 0, 1]; + data.borderWidth = borderArray[2] || 0; + + // TODO: implement proper support for annotations with line dash patterns. + var dashArray = borderArray[3]; + if (dashArray && isArray(dashArray)) { + var dashArrayLength = dashArray.length; + if (dashArrayLength > 0) { + // According to the PDF specification: the elements in a dashArray + // shall be numbers that are nonnegative and not all equal to zero. + var isInvalid = false; + var numPositive = 0; + for (var i = 0; i < dashArrayLength; i++) { + if (!(+dashArray[i] >= 0)) { + isInvalid = true; + break; + } else if (dashArray[i] > 0) { + numPositive++; + } + } + if (isInvalid || numPositive === 0) { + data.borderWidth = 0; + } + } + } + } + + this.appearance = getDefaultAppearance(dict); + data.hasAppearance = !!this.appearance; + } + + Annotation.prototype = { + + getData: function Annotation_getData() { + return this.data; + }, + + hasHtml: function Annotation_hasHtml() { + return false; + }, + + getHtmlElement: function Annotation_getHtmlElement(commonObjs) { + throw new NotImplementedException( + 'getHtmlElement() should be implemented in subclass'); + }, + + // TODO(mack): Remove this, it's not really that helpful. + getEmptyContainer: function Annotation_getEmptyContainer(tagName, rect) { + assert(!isWorker, + 'getEmptyContainer() should be called from main thread'); + + rect = rect || this.data.rect; + var element = document.createElement(tagName); + element.style.width = Math.ceil(rect[2] - rect[0]) + 'px'; + element.style.height = Math.ceil(rect[3] - rect[1]) + 'px'; + return element; + }, + + isViewable: function Annotation_isViewable() { + var data = this.data; + return !!( + data && + (!data.annotationFlags || + !(data.annotationFlags & 0x22)) && // Hidden or NoView + data.rect // rectangle is nessessary + ); + }, + + loadResources: function(keys) { + var promise = new LegacyPromise(); + this.appearance.dict.getAsync('Resources').then(function(resources) { + if (!resources) { + promise.resolve(); + return; + } + var objectLoader = new ObjectLoader(resources.map, + keys, + resources.xref); + objectLoader.load().then(function() { + promise.resolve(resources); + }); + }.bind(this)); + + return promise; + }, + + getOperatorList: function Annotation_getToOperatorList(evaluator) { + + var promise = new LegacyPromise(); + + if (!this.appearance) { + promise.resolve(new OperatorList()); + return promise; + } + + var data = this.data; + + var appearanceDict = this.appearance.dict; + var resourcesPromise = this.loadResources([ + 'ExtGState', + 'ColorSpace', + 'Pattern', + 'Shading', + 'XObject', + 'Font' + // ProcSet + // Properties + ]); + var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1]; + var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0]; + var transform = getTransformMatrix(data.rect, bbox, matrix); + + var border = data.border; + + resourcesPromise.then(function(resources) { + var opList = new OperatorList(); + opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); + evaluator.getOperatorList(this.appearance, resources, opList); + opList.addOp(OPS.endAnnotation, []); + promise.resolve(opList); + }.bind(this)); + + return promise; + } + }; + + Annotation.getConstructor = + function Annotation_getConstructor(subtype, fieldType) { + + if (!subtype) { + return; + } + + // TODO(mack): Implement FreeText annotations + if (subtype === 'Link') { + return LinkAnnotation; + } else if (subtype === 'Text') { + return TextAnnotation; + } else if (subtype === 'Widget') { + if (!fieldType) { + return; + } + + if (fieldType === 'Tx') { + return TextWidgetAnnotation; + } else { + return WidgetAnnotation; + } + } else { + return Annotation; + } + }; + + // TODO(mack): Support loading annotation from data + Annotation.fromData = function Annotation_fromData(data) { + var subtype = data.subtype; + var fieldType = data.fieldType; + var Constructor = Annotation.getConstructor(subtype, fieldType); + if (Constructor) { + return new Constructor({ data: data }); + } + }; + + Annotation.fromRef = function Annotation_fromRef(xref, ref) { + + var dict = xref.fetchIfRef(ref); + if (!isDict(dict)) { + return; + } + + var subtype = dict.get('Subtype'); + subtype = isName(subtype) ? subtype.name : ''; + if (!subtype) { + return; + } + + var fieldType = Util.getInheritableProperty(dict, 'FT'); + fieldType = isName(fieldType) ? fieldType.name : ''; + + var Constructor = Annotation.getConstructor(subtype, fieldType); + if (!Constructor) { + return; + } + + var params = { + dict: dict, + ref: ref, + }; + + var annotation = new Constructor(params); + + if (annotation.isViewable()) { + return annotation; + } else { + warn('unimplemented annotation type: ' + subtype); + } + }; + + Annotation.appendToOperatorList = function Annotation_appendToOperatorList( + annotations, opList, pdfManager, partialEvaluator) { + + function reject(e) { + annotationsReadyPromise.reject(e); + } + + var annotationsReadyPromise = new LegacyPromise(); + + var annotationPromises = []; + for (var i = 0, n = annotations.length; i < n; ++i) { + annotationPromises.push(annotations[i].getOperatorList(partialEvaluator)); + } + Promise.all(annotationPromises).then(function(datas) { + opList.addOp(OPS.beginAnnotations, []); + for (var i = 0, n = datas.length; i < n; ++i) { + var annotOpList = datas[i]; + opList.addOpList(annotOpList); + } + opList.addOp(OPS.endAnnotations, []); + annotationsReadyPromise.resolve(); + }, reject); + + return annotationsReadyPromise; + }; + + return Annotation; +})(); +PDFJS.Annotation = Annotation; + + +var WidgetAnnotation = (function WidgetAnnotationClosure() { + + function WidgetAnnotation(params) { + Annotation.call(this, params); + + if (params.data) { + return; + } + + var dict = params.dict; + var data = this.data; + + data.fieldValue = stringToPDFString( + Util.getInheritableProperty(dict, 'V') || ''); + data.alternativeText = stringToPDFString(dict.get('TU') || ''); + data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || ''; + var fieldType = Util.getInheritableProperty(dict, 'FT'); + data.fieldType = isName(fieldType) ? fieldType.name : ''; + data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0; + this.fieldResources = Util.getInheritableProperty(dict, 'DR') || new Dict(); + + // Building the full field name by collecting the field and + // its ancestors 'T' data and joining them using '.'. + var fieldName = []; + var namedItem = dict; + var ref = params.ref; + while (namedItem) { + var parent = namedItem.get('Parent'); + var parentRef = namedItem.getRaw('Parent'); + var name = namedItem.get('T'); + if (name) { + fieldName.unshift(stringToPDFString(name)); + } else { + // The field name is absent, that means more than one field + // with the same name may exist. Replacing the empty name + // with the '`' plus index in the parent's 'Kids' array. + // This is not in the PDF spec but necessary to id the + // the input controls. + var kids = parent.get('Kids'); + var j, jj; + for (j = 0, jj = kids.length; j < jj; j++) { + var kidRef = kids[j]; + if (kidRef.num == ref.num && kidRef.gen == ref.gen) + break; + } + fieldName.unshift('`' + j); + } + namedItem = parent; + ref = parentRef; + } + data.fullName = fieldName.join('.'); + } + + var parent = Annotation.prototype; + Util.inherit(WidgetAnnotation, Annotation, { + isViewable: function WidgetAnnotation_isViewable() { + if (this.data.fieldType === 'Sig') { + warn('unimplemented annotation type: Widget signature'); + return false; + } + + return parent.isViewable.call(this); + } + }); + + return WidgetAnnotation; +})(); + +var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { + function TextWidgetAnnotation(params) { + WidgetAnnotation.call(this, params); + + if (params.data) { + return; + } + + this.data.textAlignment = Util.getInheritableProperty(params.dict, 'Q'); + } + + // TODO(mack): This dupes some of the logic in CanvasGraphics.setFont() + function setTextStyles(element, item, fontObj) { + + var style = element.style; + style.fontSize = item.fontSize + 'px'; + style.direction = item.fontDirection < 0 ? 'rtl': 'ltr'; + + if (!fontObj) { + return; + } + + style.fontWeight = fontObj.black ? + (fontObj.bold ? 'bolder' : 'bold') : + (fontObj.bold ? 'bold' : 'normal'); + style.fontStyle = fontObj.italic ? 'italic' : 'normal'; + + var fontName = fontObj.loadedName; + var fontFamily = fontName ? '"' + fontName + '", ' : ''; + // Use a reasonable default font if the font doesn't specify a fallback + var fallbackName = fontObj.fallbackName || 'Helvetica, sans-serif'; + style.fontFamily = fontFamily + fallbackName; + } + + + var parent = WidgetAnnotation.prototype; + Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { + hasHtml: function TextWidgetAnnotation_hasHtml() { + return !this.data.hasAppearance && !!this.data.fieldValue; + }, + + getHtmlElement: function TextWidgetAnnotation_getHtmlElement(commonObjs) { + assert(!isWorker, 'getHtmlElement() shall be called from main thread'); + + var item = this.data; + + var element = this.getEmptyContainer('div'); + element.style.display = 'table'; + + var content = document.createElement('div'); + content.textContent = item.fieldValue; + var textAlignment = item.textAlignment; + content.style.textAlign = ['left', 'center', 'right'][textAlignment]; + content.style.verticalAlign = 'middle'; + content.style.display = 'table-cell'; + + var fontObj = item.fontRefName ? + commonObjs.getData(item.fontRefName) : null; + var cssRules = setTextStyles(content, item, fontObj); + + element.appendChild(content); + + return element; + }, + + getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) { + if (this.appearance) { + return Annotation.prototype.getOperatorList.call(this, evaluator); + } + + var promise = new LegacyPromise(); + var opList = new OperatorList(); + var data = this.data; + + // Even if there is an appearance stream, ignore it. This is the + // behaviour used by Adobe Reader. + + var defaultAppearance = data.defaultAppearance; + if (!defaultAppearance) { + promise.resolve(opList); + return promise; + } + + // Include any font resources found in the default appearance + + var stream = new Stream(stringToBytes(defaultAppearance)); + evaluator.getOperatorList(stream, this.fieldResources, opList); + var appearanceFnArray = opList.fnArray; + var appearanceArgsArray = opList.argsArray; + var fnArray = []; + var argsArray = []; + + // TODO(mack): Add support for stroke color + data.rgb = [0, 0, 0]; + // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY! + for (var i = 0, n = fnArray.length; i < n; ++i) { + var fnId = appearanceFnArray[i]; + var args = appearanceArgsArray[i]; + + if (fnId === OPS.setFont) { + data.fontRefName = args[0]; + var size = args[1]; + if (size < 0) { + data.fontDirection = -1; + data.fontSize = -size; + } else { + data.fontDirection = 1; + data.fontSize = size; + } + } else if (fnId === OPS.setFillRGBColor) { + data.rgb = args; + } else if (fnId === OPS.setFillGray) { + var rgbValue = args[0] * 255; + data.rgb = [rgbValue, rgbValue, rgbValue]; + } + } + promise.resolve(opList); + return promise; + } + }); + + return TextWidgetAnnotation; +})(); + +var TextAnnotation = (function TextAnnotationClosure() { + function TextAnnotation(params) { + Annotation.call(this, params); + + if (params.data) { + return; + } + + var dict = params.dict; + var data = this.data; + + var content = dict.get('Contents'); + var title = dict.get('T'); + data.content = stringToPDFString(content || ''); + data.title = stringToPDFString(title || ''); + data.name = !dict.has('Name') ? 'Note' : dict.get('Name').name; + } + + var ANNOT_MIN_SIZE = 10; + + Util.inherit(TextAnnotation, Annotation, { + + getOperatorList: function TextAnnotation_getOperatorList(evaluator) { + var promise = new LegacyPromise(); + promise.resolve(new OperatorList()); + return promise; + }, + + hasHtml: function TextAnnotation_hasHtml() { + return true; + }, + + getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) { + assert(!isWorker, 'getHtmlElement() shall be called from main thread'); + + var item = this.data; + var rect = item.rect; + + // sanity check because of OOo-generated PDFs + if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) { + rect[3] = rect[1] + ANNOT_MIN_SIZE; + } + if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) { + rect[2] = rect[0] + (rect[3] - rect[1]); // make it square + } + + var container = this.getEmptyContainer('section', rect); + container.className = 'annotText'; + + var image = document.createElement('img'); + image.style.height = container.style.height; + var iconName = item.name; + image.src = PDFJS.imageResourcesPath + 'annotation-' + + iconName.toLowerCase() + '.svg'; + image.alt = '[{{type}} Annotation]'; + image.dataset.l10nId = 'text_annotation_type'; + image.dataset.l10nArgs = JSON.stringify({type: iconName}); + var content = document.createElement('div'); + content.setAttribute('hidden', true); + var title = document.createElement('h1'); + var text = document.createElement('p'); + content.style.left = Math.floor(rect[2] - rect[0]) + 'px'; + content.style.top = '0px'; + title.textContent = item.title; + + if (!item.content && !item.title) { + content.setAttribute('hidden', true); + } else { + var e = document.createElement('span'); + var lines = item.content.split(/(?:\r\n?|\n)/); + for (var i = 0, ii = lines.length; i < ii; ++i) { + var line = lines[i]; + e.appendChild(document.createTextNode(line)); + if (i < (ii - 1)) + e.appendChild(document.createElement('br')); + } + text.appendChild(e); + + var showAnnotation = function showAnnotation() { + container.style.zIndex += 1; + content.removeAttribute('hidden'); + }; + + var hideAnnotation = function hideAnnotation(e) { + if (e.toElement || e.relatedTarget) { // No context menu is used + container.style.zIndex -= 1; + content.setAttribute('hidden', true); + } + }; + + content.addEventListener('mouseover', showAnnotation, false); + content.addEventListener('mouseout', hideAnnotation, false); + image.addEventListener('mouseover', showAnnotation, false); + image.addEventListener('mouseout', hideAnnotation, false); + } + + content.appendChild(title); + content.appendChild(text); + container.appendChild(image); + container.appendChild(content); + + return container; + } + }); + + return TextAnnotation; +})(); + +var LinkAnnotation = (function LinkAnnotationClosure() { + function LinkAnnotation(params) { + Annotation.call(this, params); + + if (params.data) { + return; + } + + var dict = params.dict; + var data = this.data; + + var action = dict.get('A'); + if (action) { + var linkType = action.get('S').name; + if (linkType === 'URI') { + var url = addDefaultProtocolToUrl(action.get('URI')); + // TODO: pdf spec mentions urls can be relative to a Base + // entry in the dictionary. + if (!isValidUrl(url, false)) { + url = ''; + } + data.url = url; + } else if (linkType === 'GoTo') { + data.dest = action.get('D'); + } else if (linkType === 'GoToR') { + var urlDict = action.get('F'); + if (isDict(urlDict)) { + // We assume that the 'url' is a Filspec dictionary + // and fetch the url without checking any further + url = urlDict.get('F') || ''; + } + + // TODO: pdf reference says that GoToR + // can also have 'NewWindow' attribute + if (!isValidUrl(url, false)) { + url = ''; + } + data.url = url; + data.dest = action.get('D'); + } else if (linkType === 'Named') { + data.action = action.get('N').name; + } else { + warn('unrecognized link type: ' + linkType); + } + } else if (dict.has('Dest')) { + // simple destination link + var dest = dict.get('Dest'); + data.dest = isName(dest) ? dest.name : dest; + } + } + + // Lets URLs beginning with 'www.' default to using the 'http://' protocol. + function addDefaultProtocolToUrl(url) { + if (url.indexOf('www.') === 0) { + return ('http://' + url); + } + return url; + } + + Util.inherit(LinkAnnotation, Annotation, { + hasOperatorList: function LinkAnnotation_hasOperatorList() { + return false; + }, + + hasHtml: function LinkAnnotation_hasHtml() { + return true; + }, + + getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) { + var rect = this.data.rect; + var element = document.createElement('a'); + var borderWidth = this.data.borderWidth; + + element.style.borderWidth = borderWidth + 'px'; + var color = this.data.color; + var rgb = []; + for (var i = 0; i < 3; ++i) { + rgb[i] = Math.round(color[i] * 255); + } + element.style.borderColor = Util.makeCssRgb(rgb); + element.style.borderStyle = 'solid'; + + var width = rect[2] - rect[0] - 2 * borderWidth; + var height = rect[3] - rect[1] - 2 * borderWidth; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + + element.href = this.data.url || ''; + return element; + } + }); + + return LinkAnnotation; +})(); + + +/** + * The maximum allowed image size in total pixels e.g. width * height. Images + * above this value will not be drawn. Use -1 for no limit. + * @var {Number} + */ +PDFJS.maxImageSize = PDFJS.maxImageSize === undefined ? -1 : PDFJS.maxImageSize; + +/** + * By default fonts are converted to OpenType fonts and loaded via font face + * rules. If disabled, the font will be rendered using a built in font renderer + * that constructs the glyphs with primitive path commands. + * @var {Boolean} + */ +PDFJS.disableFontFace = PDFJS.disableFontFace === undefined ? + false : PDFJS.disableFontFace; + +/** + * Path for image resources, mainly for annotation icons. Include trailing + * slash. + * @var {String} + */ +PDFJS.imageResourcesPath = PDFJS.imageResourcesPath === undefined ? + '' : PDFJS.imageResourcesPath; + +/** + * Disable the web worker and run all code on the main thread. This will happen + * automatically if the browser doesn't support workers or sending typed arrays + * to workers. + * @var {Boolean} + */ +PDFJS.disableWorker = PDFJS.disableWorker === undefined ? + false : PDFJS.disableWorker; + +/** + * Path and filename of the worker file. Required when the worker is enabled in + * development mode. If unspecified in the production build, the worker will be + * loaded based on the location of the pdf.js file. + * @var {String} + */ +PDFJS.workerSrc = PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc; + +/** + * Disable range request loading of PDF files. When enabled and if the server + * supports partial content requests then the PDF will be fetched in chunks. + * Enabled (false) by default. + * @var {Boolean} + */ +PDFJS.disableRange = PDFJS.disableRange === undefined ? + false : PDFJS.disableRange; + +/** + * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js + * will automatically keep fetching more data even if it isn't needed to display + * the current page. This default behavior can be disabled. + * @var {Boolean} + */ +PDFJS.disableAutoFetch = PDFJS.disableAutoFetch === undefined ? + false : PDFJS.disableAutoFetch; + +/** + * Enables special hooks for debugging PDF.js. + * @var {Boolean} + */ +PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug; + +/** + * Enables transfer usage in postMessage for ArrayBuffers. + * @var {boolean} + */ +PDFJS.postMessageTransfers = PDFJS.postMessageTransfers === undefined ? + true : PDFJS.postMessageTransfers; + +/** + * Disables URL.createObjectURL usage. + * @var {boolean} + */ +PDFJS.disableCreateObjectURL = PDFJS.disableCreateObjectURL === undefined ? + false : PDFJS.disableCreateObjectURL; + +/** + * Controls the logging level. + * The constants from PDFJS.VERBOSITY_LEVELS should be used: + * - errors + * - warnings [default] + * - infos + * @var {Number} + */ +PDFJS.verbosity = PDFJS.verbosity === undefined ? + PDFJS.VERBOSITY_LEVELS.warnings : PDFJS.verbosity; + +/** + * This is the main entry point for loading a PDF and interacting with it. + * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR) + * is used, which means it must follow the same origin rules that any XHR does + * e.g. No cross domain requests without CORS. + * + * @param {string|TypedAray|object} source Can be an url to where a PDF is + * located, a typed array (Uint8Array) already populated with data or + * and parameter object with the following possible fields: + * - url - The URL of the PDF. + * - data - A typed array with PDF data. + * - httpHeaders - Basic authentication headers. + * - withCredentials - Indicates whether or not cross-site Access-Control + * requests should be made using credentials such as + * cookies or authorization headers. The default is false. + * - password - For decrypting password-protected PDFs. + * - initialData - A typed array with the first portion or all of the pdf data. + * Used by the extension since some data is already loaded + * before the switch to range requests. + * + * @param {object} pdfDataRangeTransport is optional. It is used if you want + * to manually serve range requests for data in the PDF. See viewer.js for + * an example of pdfDataRangeTransport's interface. + * + * @param {function} passwordCallback is optional. It is used to request a + * password if wrong or no password was provided. The callback receives two + * parameters: function that needs to be called with new password and reason + * (see {PasswordResponses}). + * + * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object. + */ +PDFJS.getDocument = function getDocument(source, + pdfDataRangeTransport, + passwordCallback, + progressCallback) { + var workerInitializedPromise, workerReadyPromise, transport; + + if (typeof source === 'string') { + source = { url: source }; + } else if (isArrayBuffer(source)) { + source = { data: source }; + } else if (typeof source !== 'object') { + error('Invalid parameter in getDocument, need either Uint8Array, ' + + 'string or a parameter object'); + } + + if (!source.url && !source.data) + error('Invalid parameter array, need either .data or .url'); + + // copy/use all keys as is except 'url' -- full path is required + var params = {}; + for (var key in source) { + if (key === 'url' && typeof window !== 'undefined') { + params[key] = combineUrl(window.location.href, source[key]); + continue; + } + params[key] = source[key]; + } + + workerInitializedPromise = new PDFJS.LegacyPromise(); + workerReadyPromise = new PDFJS.LegacyPromise(); + transport = new WorkerTransport(workerInitializedPromise, + workerReadyPromise, pdfDataRangeTransport, progressCallback); + workerInitializedPromise.then(function transportInitialized() { + transport.passwordCallback = passwordCallback; + transport.fetchDocument(params); + }); + return workerReadyPromise; +}; + +/** + * Proxy to a PDFDocument in the worker thread. Also, contains commonly used + * properties that can be read synchronously. + */ +var PDFDocumentProxy = (function PDFDocumentProxyClosure() { + function PDFDocumentProxy(pdfInfo, transport) { + this.pdfInfo = pdfInfo; + this.transport = transport; + } + PDFDocumentProxy.prototype = { + /** + * @return {number} Total number of pages the PDF contains. + */ + get numPages() { + return this.pdfInfo.numPages; + }, + /** + * @return {string} A unique ID to identify a PDF. Not guaranteed to be + * unique. + */ + get fingerprint() { + return this.pdfInfo.fingerprint; + }, + /** + * @return {boolean} true if embedded document fonts are in use. Will be + * set during rendering of the pages. + */ + get embeddedFontsUsed() { + return this.transport.embeddedFontsUsed; + }, + /** + * @param {number} The page number to get. The first page is 1. + * @return {Promise} A promise that is resolved with a {PDFPageProxy} + * object. + */ + getPage: function PDFDocumentProxy_getPage(number) { + return this.transport.getPage(number); + }, + /** + * @param {object} Must have 'num' and 'gen' properties. + * @return {Promise} A promise that is resolved with the page index that is + * associated with the reference. + */ + getPageIndex: function PDFDocumentProxy_getPageIndex(ref) { + return this.transport.getPageIndex(ref); + }, + /** + * @return {Promise} A promise that is resolved with a lookup table for + * mapping named destinations to reference numbers. + */ + getDestinations: function PDFDocumentProxy_getDestinations() { + return this.transport.getDestinations(); + }, + /** + * @return {Promise} A promise that is resolved with an array of all the + * JavaScript strings in the name tree. + */ + getJavaScript: function PDFDocumentProxy_getJavaScript() { + var promise = new PDFJS.LegacyPromise(); + var js = this.pdfInfo.javaScript; + promise.resolve(js); + return promise; + }, + /** + * @return {Promise} A promise that is resolved with an {array} that is a + * tree outline (if it has one) of the PDF. The tree is in the format of: + * [ + * { + * title: string, + * bold: boolean, + * italic: boolean, + * color: rgb array, + * dest: dest obj, + * items: array of more items like this + * }, + * ... + * ]. + */ + getOutline: function PDFDocumentProxy_getOutline() { + var promise = new PDFJS.LegacyPromise(); + var outline = this.pdfInfo.outline; + promise.resolve(outline); + return promise; + }, + /** + * @return {Promise} A promise that is resolved with an {object} that has + * info and metadata properties. Info is an {object} filled with anything + * available in the information dictionary and similarly metadata is a + * {Metadata} object with information from the metadata section of the PDF. + */ + getMetadata: function PDFDocumentProxy_getMetadata() { + var promise = new PDFJS.LegacyPromise(); + var info = this.pdfInfo.info; + var metadata = this.pdfInfo.metadata; + promise.resolve({ + info: info, + metadata: metadata ? new PDFJS.Metadata(metadata) : null + }); + return promise; + }, + isEncrypted: function PDFDocumentProxy_isEncrypted() { + var promise = new PDFJS.LegacyPromise(); + promise.resolve(this.pdfInfo.encrypted); + return promise; + }, + /** + * @return {Promise} A promise that is resolved with a TypedArray that has + * the raw data from the PDF. + */ + getData: function PDFDocumentProxy_getData() { + var promise = new PDFJS.LegacyPromise(); + this.transport.getData(promise); + return promise; + }, + /** + * @return {Promise} A promise that is resolved when the document's data + * is loaded + */ + dataLoaded: function PDFDocumentProxy_dataLoaded() { + return this.transport.dataLoaded(); + }, + cleanup: function PDFDocumentProxy_cleanup() { + this.transport.startCleanup(); + }, + destroy: function PDFDocumentProxy_destroy() { + this.transport.destroy(); + } + }; + return PDFDocumentProxy; +})(); + +var PDFPageProxy = (function PDFPageProxyClosure() { + function PDFPageProxy(pageInfo, transport) { + this.pageInfo = pageInfo; + this.transport = transport; + this.stats = new StatTimer(); + this.stats.enabled = !!globalScope.PDFJS.enableStats; + this.commonObjs = transport.commonObjs; + this.objs = new PDFObjects(); + this.receivingOperatorList = false; + this.cleanupAfterRender = false; + this.pendingDestroy = false; + this.renderTasks = []; + } + PDFPageProxy.prototype = { + /** + * @return {number} Page number of the page. First page is 1. + */ + get pageNumber() { + return this.pageInfo.pageIndex + 1; + }, + /** + * @return {number} The number of degrees the page is rotated clockwise. + */ + get rotate() { + return this.pageInfo.rotate; + }, + /** + * @return {object} The reference that points to this page. It has 'num' and + * 'gen' properties. + */ + get ref() { + return this.pageInfo.ref; + }, + /** + * @return {array} An array of the visible portion of the PDF page in the + * user space units - [x1, y1, x2, y2]. + */ + get view() { + return this.pageInfo.view; + }, + /** + * @param {number} scale The desired scale of the viewport. + * @param {number} rotate Degrees to rotate the viewport. If omitted this + * defaults to the page rotation. + * @return {PageViewport} Contains 'width' and 'height' properties along + * with transforms required for rendering. + */ + getViewport: function PDFPageProxy_getViewport(scale, rotate) { + if (arguments.length < 2) + rotate = this.rotate; + return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0); + }, + /** + * @return {Promise} A promise that is resolved with an {array} of the + * annotation objects. + */ + getAnnotations: function PDFPageProxy_getAnnotations() { + if (this.annotationsPromise) + return this.annotationsPromise; + + var promise = new PDFJS.LegacyPromise(); + this.annotationsPromise = promise; + this.transport.getAnnotations(this.pageInfo.pageIndex); + return promise; + }, + /** + * Begins the process of rendering a page to the desired context. + * @param {object} params A parameter object that supports: + * { + * canvasContext(required): A 2D context of a DOM Canvas object., + * textLayer(optional): An object that has beginLayout, endLayout, and + * appendText functions., + * imageLayer(optional): An object that has beginLayout, endLayout and + * appendImage functions., + * continueCallback(optional): A function that will be called each time + * the rendering is paused. To continue + * rendering call the function that is the + * first argument to the callback. + * }. + * @return {RenderTask} An extended promise that is resolved when the page + * finishes rendering (see RenderTask). + */ + render: function PDFPageProxy_render(params) { + var stats = this.stats; + stats.time('Overall'); + + // If there was a pending destroy cancel it so no cleanup happens during + // this call to render. + this.pendingDestroy = false; + + // If there is no displayReadyPromise yet, then the operatorList was never + // requested before. Make the request and create the promise. + if (!this.displayReadyPromise) { + this.receivingOperatorList = true; + this.displayReadyPromise = new LegacyPromise(); + this.operatorList = { + fnArray: [], + argsArray: [], + lastChunk: false + }; + + this.stats.time('Page Request'); + this.transport.messageHandler.send('RenderPageRequest', { + pageIndex: this.pageNumber - 1 + }); + } + + var internalRenderTask = new InternalRenderTask(complete, params, + this.objs, this.commonObjs, + this.operatorList, this.pageNumber); + this.renderTasks.push(internalRenderTask); + var renderTask = new RenderTask(internalRenderTask); + + var self = this; + this.displayReadyPromise.then( + function pageDisplayReadyPromise(transparency) { + if (self.pendingDestroy) { + complete(); + return; + } + stats.time('Rendering'); + internalRenderTask.initalizeGraphics(transparency); + internalRenderTask.operatorListChanged(); + }, + function pageDisplayReadPromiseError(reason) { + complete(reason); + } + ); + + function complete(error) { + var i = self.renderTasks.indexOf(internalRenderTask); + if (i >= 0) { + self.renderTasks.splice(i, 1); + } + + if (self.cleanupAfterRender) { + self.pendingDestroy = true; + } + self._tryDestroy(); + + if (error) { + renderTask.promise.reject(error); + } else { + renderTask.promise.resolve(); + } + stats.timeEnd('Rendering'); + stats.timeEnd('Overall'); + } + + return renderTask; + }, + /** + * @return {Promise} That is resolved with the a {string} that is the text + * content from the page. + */ + getTextContent: function PDFPageProxy_getTextContent() { + var promise = new PDFJS.LegacyPromise(); + this.transport.messageHandler.send('GetTextContent', { + pageIndex: this.pageNumber - 1 + }, + function textContentCallback(textContent) { + promise.resolve(textContent); + } + ); + return promise; + }, + /** + * Stub for future feature. + */ + getOperationList: function PDFPageProxy_getOperationList() { + var promise = new PDFJS.LegacyPromise(); + var operationList = { // not implemented + dependencyFontsID: null, + operatorList: null + }; + promise.resolve(operationList); + return promise; + }, + /** + * Destroys resources allocated by the page. + */ + destroy: function PDFPageProxy_destroy() { + this.pendingDestroy = true; + this._tryDestroy(); + }, + /** + * For internal use only. Attempts to clean up if rendering is in a state + * where that's possible. + */ + _tryDestroy: function PDFPageProxy__destroy() { + if (!this.pendingDestroy || + this.renderTasks.length !== 0 || + this.receivingOperatorList) { + return; + } + + delete this.operatorList; + delete this.displayReadyPromise; + this.objs.clear(); + this.pendingDestroy = false; + }, + /** + * For internal use only. + */ + _startRenderPage: function PDFPageProxy_startRenderPage(transparency) { + this.displayReadyPromise.resolve(transparency); + }, + /** + * For internal use only. + */ + _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) { + // Add the new chunk to the current operator list. + for (var i = 0, ii = operatorListChunk.length; i < ii; i++) { + this.operatorList.fnArray.push(operatorListChunk.fnArray[i]); + this.operatorList.argsArray.push(operatorListChunk.argsArray[i]); + } + this.operatorList.lastChunk = operatorListChunk.lastChunk; + + // Notify all the rendering tasks there are more operators to be consumed. + for (var i = 0; i < this.renderTasks.length; i++) { + this.renderTasks[i].operatorListChanged(); + } + + if (operatorListChunk.lastChunk) { + this.receivingOperatorList = false; + this._tryDestroy(); + } + } + }; + return PDFPageProxy; +})(); +/** + * For internal use only. + */ +var WorkerTransport = (function WorkerTransportClosure() { + function WorkerTransport(workerInitializedPromise, workerReadyPromise, + pdfDataRangeTransport, progressCallback) { + this.pdfDataRangeTransport = pdfDataRangeTransport; + + this.workerReadyPromise = workerReadyPromise; + this.progressCallback = progressCallback; + this.commonObjs = new PDFObjects(); + + this.pageCache = []; + this.pagePromises = []; + this.embeddedFontsUsed = false; + + this.passwordCallback = null; + + // If worker support isn't disabled explicit and the browser has worker + // support, create a new web worker and test if it/the browser fullfills + // all requirements to run parts of pdf.js in a web worker. + // Right now, the requirement is, that an Uint8Array is still an Uint8Array + // as it arrives on the worker. Chrome added this with version 15. + if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') { + var workerSrc = PDFJS.workerSrc; + if (!workerSrc) { + error('No PDFJS.workerSrc specified'); + } + + try { + // Some versions of FF can't create a worker on localhost, see: + // https://bugzilla.mozilla.org/show_bug.cgi?id=683280 + var worker = new Worker(workerSrc); + var messageHandler = new MessageHandler('main', worker); + this.messageHandler = messageHandler; + + messageHandler.on('test', function transportTest(data) { + var supportTypedArray = data && data.supportTypedArray; + if (supportTypedArray) { + this.worker = worker; + if (!data.supportTransfers) { + PDFJS.postMessageTransfers = false; + } + this.setupMessageHandler(messageHandler); + workerInitializedPromise.resolve(); + } else { + globalScope.PDFJS.disableWorker = true; + this.loadFakeWorkerFiles().then(function() { + this.setupFakeWorker(); + workerInitializedPromise.resolve(); + }.bind(this)); + } + }.bind(this)); + + var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]); + // Some versions of Opera throw a DATA_CLONE_ERR on serializing the + // typed array. Also, checking if we can use transfers. + try { + messageHandler.send('test', testObj, null, [testObj.buffer]); + } catch (ex) { + info('Cannot use postMessage transfers'); + testObj[0] = 0; + messageHandler.send('test', testObj); + } + return; + } catch (e) { + info('The worker has been disabled.'); + } + } + // Either workers are disabled, not supported or have thrown an exception. + // Thus, we fallback to a faked worker. + globalScope.PDFJS.disableWorker = true; + this.loadFakeWorkerFiles().then(function() { + this.setupFakeWorker(); + workerInitializedPromise.resolve(); + }.bind(this)); + } + WorkerTransport.prototype = { + destroy: function WorkerTransport_destroy() { + this.pageCache = []; + this.pagePromises = []; + var self = this; + this.messageHandler.send('Terminate', null, function () { + if (self.worker) { + self.worker.terminate(); + } + }); + }, + + loadFakeWorkerFiles: function WorkerTransport_loadFakeWorkerFiles() { + if (!PDFJS.fakeWorkerFilesLoadedPromise) { + PDFJS.fakeWorkerFilesLoadedPromise = new LegacyPromise(); + // In the developer build load worker_loader which in turn loads all the + // other files and resolves the promise. In production only the + // pdf.worker.js file is needed. + Util.loadScript(PDFJS.workerSrc, function() { + PDFJS.fakeWorkerFilesLoadedPromise.resolve(); + }); + } + return PDFJS.fakeWorkerFilesLoadedPromise; + }, + + setupFakeWorker: function WorkerTransport_setupFakeWorker() { + warn('Setting up fake worker.'); + // If we don't use a worker, just post/sendMessage to the main thread. + var fakeWorker = { + postMessage: function WorkerTransport_postMessage(obj) { + fakeWorker.onmessage({data: obj}); + }, + terminate: function WorkerTransport_terminate() {} + }; + + var messageHandler = new MessageHandler('main', fakeWorker); + this.setupMessageHandler(messageHandler); + + // If the main thread is our worker, setup the handling for the messages + // the main thread sends to it self. + PDFJS.WorkerMessageHandler.setup(messageHandler); + }, + + setupMessageHandler: + function WorkerTransport_setupMessageHandler(messageHandler) { + this.messageHandler = messageHandler; + + function updatePassword(password) { + messageHandler.send('UpdatePassword', password); + } + + var pdfDataRangeTransport = this.pdfDataRangeTransport; + if (pdfDataRangeTransport) { + pdfDataRangeTransport.addRangeListener(function(begin, chunk) { + messageHandler.send('OnDataRange', { + begin: begin, + chunk: chunk + }); + }); + + pdfDataRangeTransport.addProgressListener(function(loaded) { + messageHandler.send('OnDataProgress', { + loaded: loaded + }); + }); + + messageHandler.on('RequestDataRange', + function transportDataRange(data) { + pdfDataRangeTransport.requestDataRange(data.begin, data.end); + }, this); + } + + messageHandler.on('GetDoc', function transportDoc(data) { + var pdfInfo = data.pdfInfo; + var pdfDocument = new PDFDocumentProxy(pdfInfo, this); + this.pdfDocument = pdfDocument; + this.workerReadyPromise.resolve(pdfDocument); + }, this); + + messageHandler.on('NeedPassword', function transportPassword(data) { + if (this.passwordCallback) { + return this.passwordCallback(updatePassword, + PasswordResponses.NEED_PASSWORD); + } + this.workerReadyPromise.reject(data.exception.message, data.exception); + }, this); + + messageHandler.on('IncorrectPassword', function transportBadPass(data) { + if (this.passwordCallback) { + return this.passwordCallback(updatePassword, + PasswordResponses.INCORRECT_PASSWORD); + } + this.workerReadyPromise.reject(data.exception.message, data.exception); + }, this); + + messageHandler.on('InvalidPDF', function transportInvalidPDF(data) { + this.workerReadyPromise.reject(data.exception.name, data.exception); + }, this); + + messageHandler.on('MissingPDF', function transportMissingPDF(data) { + this.workerReadyPromise.reject(data.exception.message, data.exception); + }, this); + + messageHandler.on('UnknownError', function transportUnknownError(data) { + this.workerReadyPromise.reject(data.exception.message, data.exception); + }, this); + + messageHandler.on('GetPage', function transportPage(data) { + var pageInfo = data.pageInfo; + var page = new PDFPageProxy(pageInfo, this); + this.pageCache[pageInfo.pageIndex] = page; + var promise = this.pagePromises[pageInfo.pageIndex]; + promise.resolve(page); + }, this); + + messageHandler.on('GetAnnotations', function transportAnnotations(data) { + var annotations = data.annotations; + var promise = this.pageCache[data.pageIndex].annotationsPromise; + promise.resolve(annotations); + }, this); + + messageHandler.on('StartRenderPage', function transportRender(data) { + var page = this.pageCache[data.pageIndex]; + + page.stats.timeEnd('Page Request'); + page._startRenderPage(data.transparency); + }, this); + + messageHandler.on('RenderPageChunk', function transportRender(data) { + var page = this.pageCache[data.pageIndex]; + + page._renderPageChunk(data.operatorList); + }, this); + + messageHandler.on('commonobj', function transportObj(data) { + var id = data[0]; + var type = data[1]; + if (this.commonObjs.hasData(id)) + return; + + switch (type) { + case 'Font': + var exportedData = data[2]; + + var font; + if ('error' in exportedData) { + var error = exportedData.error; + warn('Error during font loading: ' + error); + this.commonObjs.resolve(id, error); + break; + } else { + font = new FontFace(exportedData); + } + + FontLoader.bind( + [font], + function fontReady(fontObjs) { + this.commonObjs.resolve(id, font); + }.bind(this) + ); + break; + case 'FontPath': + this.commonObjs.resolve(id, data[2]); + break; + default: + error('Got unknown common object type ' + type); + } + }, this); + + messageHandler.on('obj', function transportObj(data) { + var id = data[0]; + var pageIndex = data[1]; + var type = data[2]; + var pageProxy = this.pageCache[pageIndex]; + if (pageProxy.objs.hasData(id)) + return; + + switch (type) { + case 'JpegStream': + var imageData = data[3]; + loadJpegStream(id, imageData, pageProxy.objs); + break; + case 'Image': + var imageData = data[3]; + pageProxy.objs.resolve(id, imageData); + + // heuristics that will allow not to store large data + var MAX_IMAGE_SIZE_TO_STORE = 8000000; + if ('data' in imageData && + imageData.data.length > MAX_IMAGE_SIZE_TO_STORE) { + pageProxy.cleanupAfterRender = true; + } + break; + default: + error('Got unknown object type ' + type); + } + }, this); + + messageHandler.on('DocProgress', function transportDocProgress(data) { + if (this.progressCallback) { + this.progressCallback({ + loaded: data.loaded, + total: data.total + }); + } + }, this); + + messageHandler.on('DocError', function transportDocError(data) { + this.workerReadyPromise.reject(data); + }, this); + + messageHandler.on('PageError', function transportError(data) { + var page = this.pageCache[data.pageNum - 1]; + if (page.displayReadyPromise) + page.displayReadyPromise.reject(data.error); + else + error(data.error); + }, this); + + messageHandler.on('JpegDecode', function(data, deferred) { + var imageUrl = data[0]; + var components = data[1]; + if (components != 3 && components != 1) + error('Only 3 component or 1 component can be returned'); + + var img = new Image(); + img.onload = (function messageHandler_onloadClosure() { + var width = img.width; + var height = img.height; + var size = width * height; + var rgbaLength = size * 4; + var buf = new Uint8Array(size * components); + var tmpCanvas = createScratchCanvas(width, height); + var tmpCtx = tmpCanvas.getContext('2d'); + tmpCtx.drawImage(img, 0, 0); + var data = tmpCtx.getImageData(0, 0, width, height).data; + + if (components == 3) { + for (var i = 0, j = 0; i < rgbaLength; i += 4, j += 3) { + buf[j] = data[i]; + buf[j + 1] = data[i + 1]; + buf[j + 2] = data[i + 2]; + } + } else if (components == 1) { + for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) { + buf[j] = data[i]; + } + } + deferred.resolve({ data: buf, width: width, height: height}); + }).bind(this); + img.src = imageUrl; + }); + }, + + fetchDocument: function WorkerTransport_fetchDocument(source) { + source.disableAutoFetch = PDFJS.disableAutoFetch; + source.chunkedViewerLoading = !!this.pdfDataRangeTransport; + this.messageHandler.send('GetDocRequest', { + source: source, + disableRange: PDFJS.disableRange, + maxImageSize: PDFJS.maxImageSize, + disableFontFace: PDFJS.disableFontFace, + disableCreateObjectURL: PDFJS.disableCreateObjectURL, + verbosity: PDFJS.verbosity + }); + }, + + getData: function WorkerTransport_getData(promise) { + this.messageHandler.send('GetData', null, function(data) { + promise.resolve(data); + }); + }, + + dataLoaded: function WorkerTransport_dataLoaded() { + var promise = new PDFJS.LegacyPromise(); + this.messageHandler.send('DataLoaded', null, function(args) { + promise.resolve(args); + }); + return promise; + }, + + getPage: function WorkerTransport_getPage(pageNumber, promise) { + var pageIndex = pageNumber - 1; + if (pageIndex in this.pagePromises) + return this.pagePromises[pageIndex]; + var promise = new PDFJS.LegacyPromise(); + this.pagePromises[pageIndex] = promise; + this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex }); + return promise; + }, + + getPageIndex: function WorkerTransport_getPageIndexByRef(ref) { + var promise = new PDFJS.LegacyPromise(); + this.messageHandler.send('GetPageIndex', { ref: ref }, + function (pageIndex) { + promise.resolve(pageIndex); + } + ); + return promise; + }, + + getAnnotations: function WorkerTransport_getAnnotations(pageIndex) { + this.messageHandler.send('GetAnnotationsRequest', + { pageIndex: pageIndex }); + }, + + getDestinations: function WorkerTransport_getDestinations() { + var promise = new PDFJS.LegacyPromise(); + this.messageHandler.send('GetDestinations', null, + function transportDestinations(destinations) { + promise.resolve(destinations); + } + ); + return promise; + }, + + startCleanup: function WorkerTransport_startCleanup() { + this.messageHandler.send('Cleanup', null, + function endCleanup() { + for (var i = 0, ii = this.pageCache.length; i < ii; i++) { + var page = this.pageCache[i]; + if (page) { + page.destroy(); + } + } + this.commonObjs.clear(); + FontLoader.clear(); + }.bind(this) + ); + } + }; + return WorkerTransport; + +})(); + +/** + * A PDF document and page is built of many objects. E.g. there are objects + * for fonts, images, rendering code and such. These objects might get processed + * inside of a worker. The `PDFObjects` implements some basic functions to + * manage these objects. + */ +var PDFObjects = (function PDFObjectsClosure() { + function PDFObjects() { + this.objs = {}; + } + + PDFObjects.prototype = { + /** + * Internal function. + * Ensures there is an object defined for `objId`. + */ + ensureObj: function PDFObjects_ensureObj(objId) { + if (this.objs[objId]) + return this.objs[objId]; + + var obj = { + promise: new LegacyPromise(), + data: null, + resolved: false + }; + this.objs[objId] = obj; + + return obj; + }, + + /** + * If called *without* callback, this returns the data of `objId` but the + * object needs to be resolved. If it isn't, this function throws. + * + * If called *with* a callback, the callback is called with the data of the + * object once the object is resolved. That means, if you call this + * function and the object is already resolved, the callback gets called + * right away. + */ + get: function PDFObjects_get(objId, callback) { + // If there is a callback, then the get can be async and the object is + // not required to be resolved right now + if (callback) { + this.ensureObj(objId).promise.then(callback); + return null; + } + + // If there isn't a callback, the user expects to get the resolved data + // directly. + var obj = this.objs[objId]; + + // If there isn't an object yet or the object isn't resolved, then the + // data isn't ready yet! + if (!obj || !obj.resolved) + error('Requesting object that isn\'t resolved yet ' + objId); + + return obj.data; + }, + + /** + * Resolves the object `objId` with optional `data`. + */ + resolve: function PDFObjects_resolve(objId, data) { + var obj = this.ensureObj(objId); + + obj.resolved = true; + obj.data = data; + obj.promise.resolve(data); + }, + + isResolved: function PDFObjects_isResolved(objId) { + var objs = this.objs; + + if (!objs[objId]) { + return false; + } else { + return objs[objId].resolved; + } + }, + + hasData: function PDFObjects_hasData(objId) { + return this.isResolved(objId); + }, + + /** + * Returns the data of `objId` if object exists, null otherwise. + */ + getData: function PDFObjects_getData(objId) { + var objs = this.objs; + if (!objs[objId] || !objs[objId].resolved) { + return null; + } else { + return objs[objId].data; + } + }, + + clear: function PDFObjects_clear() { + this.objs = {}; + } + }; + return PDFObjects; +})(); + +var RenderTask = (function RenderTaskClosure() { + function RenderTask(internalRenderTask) { + this.internalRenderTask = internalRenderTask; + this.promise = new PDFJS.LegacyPromise(); + } + + /** + * Cancel the rendering task. If the task is curently rendering it will not be + * cancelled until graphics pauses with a timeout. The promise that this + * object extends will resolved when cancelled. + */ + RenderTask.prototype.cancel = function RenderTask_cancel() { + this.internalRenderTask.cancel(); + }; + + return RenderTask; +})(); + +var InternalRenderTask = (function InternalRenderTaskClosure() { + + function InternalRenderTask(callback, params, objs, commonObjs, operatorList, + pageNumber) { + this.callback = callback; + this.params = params; + this.objs = objs; + this.commonObjs = commonObjs; + this.operatorListIdx = null; + this.operatorList = operatorList; + this.pageNumber = pageNumber; + this.running = false; + this.graphicsReadyCallback = null; + this.graphicsReady = false; + this.cancelled = false; + } + + InternalRenderTask.prototype = { + + initalizeGraphics: + function InternalRenderTask_initalizeGraphics(transparency) { + + if (this.cancelled) { + return; + } + if (PDFJS.pdfBug && 'StepperManager' in globalScope && + globalScope.StepperManager.enabled) { + this.stepper = globalScope.StepperManager.create(this.pageNumber - 1); + this.stepper.init(this.operatorList); + this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint(); + } + + var params = this.params; + this.gfx = new CanvasGraphics(params.canvasContext, this.commonObjs, + this.objs, params.textLayer, + params.imageLayer); + + this.gfx.beginDrawing(params.viewport, transparency); + this.operatorListIdx = 0; + this.graphicsReady = true; + if (this.graphicsReadyCallback) { + this.graphicsReadyCallback(); + } + }, + + cancel: function InternalRenderTask_cancel() { + this.running = false; + this.cancelled = true; + this.callback('cancelled'); + }, + + operatorListChanged: function InternalRenderTask_operatorListChanged() { + if (!this.graphicsReady) { + if (!this.graphicsReadyCallback) { + this.graphicsReadyCallback = this._continue.bind(this); + } + return; + } + + if (this.stepper) { + this.stepper.updateOperatorList(this.operatorList); + } + + if (this.running) { + return; + } + this._continue(); + }, + + _continue: function InternalRenderTask__continue() { + this.running = true; + if (this.cancelled) { + return; + } + if (this.params.continueCallback) { + this.params.continueCallback(this._next.bind(this)); + } else { + this._next(); + } + }, + + _next: function InternalRenderTask__next() { + if (this.cancelled) { + return; + } + this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, + this.operatorListIdx, + this._continue.bind(this), + this.stepper); + if (this.operatorListIdx === this.operatorList.argsArray.length) { + this.running = false; + if (this.operatorList.lastChunk) { + this.gfx.endDrawing(); + this.callback(); + } + } + } + + }; + + return InternalRenderTask; +})(); + + +var Metadata = PDFJS.Metadata = (function MetadataClosure() { + function fixMetadata(meta) { + return meta.replace(/>\\376\\377([^<]+)/g, function(all, codes) { + var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g, + function(code, d1, d2, d3) { + return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1); + }); + var chars = ''; + for (var i = 0; i < bytes.length; i += 2) { + var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1); + chars += code >= 32 && code < 127 && code != 60 && code != 62 && + code != 38 && false ? String.fromCharCode(code) : + '&#x' + (0x10000 + code).toString(16).substring(1) + ';'; + } + return '>' + chars; + }); + } + + function Metadata(meta) { + if (typeof meta === 'string') { + // Ghostscript produces invalid metadata + meta = fixMetadata(meta); + + var parser = new DOMParser(); + meta = parser.parseFromString(meta, 'application/xml'); + } else if (!(meta instanceof Document)) { + error('Metadata: Invalid metadata object'); + } + + this.metaDocument = meta; + this.metadata = {}; + this.parse(); + } + + Metadata.prototype = { + parse: function Metadata_parse() { + var doc = this.metaDocument; + var rdf = doc.documentElement; + + if (rdf.nodeName.toLowerCase() !== 'rdf:rdf') { // Wrapped in + rdf = rdf.firstChild; + while (rdf && rdf.nodeName.toLowerCase() !== 'rdf:rdf') + rdf = rdf.nextSibling; + } + + var nodeName = (rdf) ? rdf.nodeName.toLowerCase() : null; + if (!rdf || nodeName !== 'rdf:rdf' || !rdf.hasChildNodes()) + return; + + var children = rdf.childNodes, desc, entry, name, i, ii, length, iLength; + + for (i = 0, length = children.length; i < length; i++) { + desc = children[i]; + if (desc.nodeName.toLowerCase() !== 'rdf:description') + continue; + + for (ii = 0, iLength = desc.childNodes.length; ii < iLength; ii++) { + if (desc.childNodes[ii].nodeName.toLowerCase() !== '#text') { + entry = desc.childNodes[ii]; + name = entry.nodeName.toLowerCase(); + this.metadata[name] = entry.textContent.trim(); + } + } + } + }, + + get: function Metadata_get(name) { + return this.metadata[name] || null; + }, + + has: function Metadata_has(name) { + return typeof this.metadata[name] !== 'undefined'; + } + }; + + return Metadata; +})(); + + +// contexts store most of the state we need natively. +// However, PDF needs a bit more state, which we store here. + +// Minimal font size that would be used during canvas fillText operations. +var MIN_FONT_SIZE = 16; + +var COMPILE_TYPE3_GLYPHS = true; + +function createScratchCanvas(width, height) { + var canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + return canvas; +} + +function addContextCurrentTransform(ctx) { + // If the context doesn't expose a `mozCurrentTransform`, add a JS based on. + if (!ctx.mozCurrentTransform) { + // Store the original context + ctx._scaleX = ctx._scaleX || 1.0; + ctx._scaleY = ctx._scaleY || 1.0; + ctx._originalSave = ctx.save; + ctx._originalRestore = ctx.restore; + ctx._originalRotate = ctx.rotate; + ctx._originalScale = ctx.scale; + ctx._originalTranslate = ctx.translate; + ctx._originalTransform = ctx.transform; + ctx._originalSetTransform = ctx.setTransform; + + ctx._transformMatrix = [ctx._scaleX, 0, 0, ctx._scaleY, 0, 0]; + ctx._transformStack = []; + + Object.defineProperty(ctx, 'mozCurrentTransform', { + get: function getCurrentTransform() { + return this._transformMatrix; + } + }); + + Object.defineProperty(ctx, 'mozCurrentTransformInverse', { + get: function getCurrentTransformInverse() { + // Calculation done using WolframAlpha: + // http://www.wolframalpha.com/input/? + // i=Inverse+{{a%2C+c%2C+e}%2C+{b%2C+d%2C+f}%2C+{0%2C+0%2C+1}} + + var m = this._transformMatrix; + var a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5]; + + var ad_bc = a * d - b * c; + var bc_ad = b * c - a * d; + + return [ + d / ad_bc, + b / bc_ad, + c / bc_ad, + a / ad_bc, + (d * e - c * f) / bc_ad, + (b * e - a * f) / ad_bc + ]; + } + }); + + ctx.save = function ctxSave() { + var old = this._transformMatrix; + this._transformStack.push(old); + this._transformMatrix = old.slice(0, 6); + + this._originalSave(); + }; + + ctx.restore = function ctxRestore() { + var prev = this._transformStack.pop(); + if (prev) { + this._transformMatrix = prev; + this._originalRestore(); + } + }; + + ctx.translate = function ctxTranslate(x, y) { + var m = this._transformMatrix; + m[4] = m[0] * x + m[2] * y + m[4]; + m[5] = m[1] * x + m[3] * y + m[5]; + + this._originalTranslate(x, y); + }; + + ctx.scale = function ctxScale(x, y) { + var m = this._transformMatrix; + m[0] = m[0] * x; + m[1] = m[1] * x; + m[2] = m[2] * y; + m[3] = m[3] * y; + + this._originalScale(x, y); + }; + + ctx.transform = function ctxTransform(a, b, c, d, e, f) { + var m = this._transformMatrix; + this._transformMatrix = [ + m[0] * a + m[2] * b, + m[1] * a + m[3] * b, + m[0] * c + m[2] * d, + m[1] * c + m[3] * d, + m[0] * e + m[2] * f + m[4], + m[1] * e + m[3] * f + m[5] + ]; + + ctx._originalTransform(a, b, c, d, e, f); + }; + + ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) { + this._transformMatrix = [a, b, c, d, e, f]; + + ctx._originalSetTransform(a, b, c, d, e, f); + }; + + ctx.rotate = function ctxRotate(angle) { + var cosValue = Math.cos(angle); + var sinValue = Math.sin(angle); + + var m = this._transformMatrix; + this._transformMatrix = [ + m[0] * cosValue + m[2] * sinValue, + m[1] * cosValue + m[3] * sinValue, + m[0] * (-sinValue) + m[2] * cosValue, + m[1] * (-sinValue) + m[3] * cosValue, + m[4], + m[5] + ]; + + this._originalRotate(angle); + }; + } +} + +var CachedCanvases = (function CachedCanvasesClosure() { + var cache = {}; + return { + getCanvas: function CachedCanvases_getCanvas(id, width, height, + trackTransform) { + var canvasEntry; + if (id in cache) { + canvasEntry = cache[id]; + canvasEntry.canvas.width = width; + canvasEntry.canvas.height = height; + // reset canvas transform for emulated mozCurrentTransform, if needed + canvasEntry.context.setTransform(1, 0, 0, 1, 0, 0); + } else { + var canvas = createScratchCanvas(width, height); + var ctx = canvas.getContext('2d'); + if (trackTransform) { + addContextCurrentTransform(ctx); + } + cache[id] = canvasEntry = {canvas: canvas, context: ctx}; + } + return canvasEntry; + }, + clear: function () { + cache = {}; + } + }; +})(); + +function compileType3Glyph(imgData) { + var POINT_TO_PROCESS_LIMIT = 1000; + + var width = imgData.width, height = imgData.height; + var i, j, j0, width1 = width + 1; + var points = new Uint8Array(width1 * (height + 1)); + var POINT_TYPES = + new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]); + + // decodes bit-packed mask data + var lineSize = (width + 7) & ~7, data0 = imgData.data; + var data = new Uint8Array(lineSize * height), pos = 0, ii; + for (i = 0, ii = data0.length; i < ii; i++) { + var mask = 128, elem = data0[i]; + while (mask > 0) { + data[pos++] = (elem & mask) ? 0 : 255; + mask >>= 1; + } + } + + // finding iteresting points: every point is located between mask pixels, + // so there will be points of the (width + 1)x(height + 1) grid. Every point + // will have flags assigned based on neighboring mask pixels: + // 4 | 8 + // --P-- + // 2 | 1 + // We are interested only in points with the flags: + // - outside corners: 1, 2, 4, 8; + // - inside corners: 7, 11, 13, 14; + // - and, intersections: 5, 10. + var count = 0; + pos = 0; + if (data[pos] !== 0) { + points[0] = 1; + ++count; + } + for (j = 1; j < width; j++) { + if (data[pos] !== data[pos + 1]) { + points[j] = data[pos] ? 2 : 1; + ++count; + } + pos++; + } + if (data[pos] !== 0) { + points[j] = 2; + ++count; + } + for (i = 1; i < height; i++) { + pos = i * lineSize; + j0 = i * width1; + if (data[pos - lineSize] !== data[pos]) { + points[j0] = data[pos] ? 1 : 8; + ++count; + } + // 'sum' is the position of the current pixel configuration in the 'TYPES' + // array (in order 8-1-2-4, so we can use '>>2' to shift the column). + var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0); + for (j = 1; j < width; j++) { + sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + + (data[pos - lineSize + 1] ? 8 : 0); + if (POINT_TYPES[sum]) { + points[j0 + j] = POINT_TYPES[sum]; + ++count; + } + pos++; + } + if (data[pos - lineSize] !== data[pos]) { + points[j0 + j] = data[pos] ? 2 : 4; + ++count; + } + + if (count > POINT_TO_PROCESS_LIMIT) { + return null; + } + } + + pos = lineSize * (height - 1); + j0 = i * width1; + if (data[pos] !== 0) { + points[j0] = 8; + ++count; + } + for (j = 1; j < width; j++) { + if (data[pos] !== data[pos + 1]) { + points[j0 + j] = data[pos] ? 4 : 8; + ++count; + } + pos++; + } + if (data[pos] !== 0) { + points[j0 + j] = 4; + ++count; + } + if (count > POINT_TO_PROCESS_LIMIT) { + return null; + } + + // building outlines + var steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]); + var outlines = []; + for (i = 0; count && i <= height; i++) { + var p = i * width1; + var end = p + width; + while (p < end && !points[p]) { + p++; + } + if (p === end) { + continue; + } + var coords = [p % width1, i]; + + var type = points[p], p0 = p, pp; + do { + var step = steps[type]; + do { p += step; } while (!points[p]); + + pp = points[p]; + if (pp !== 5 && pp !== 10) { + // set new direction + type = pp; + // delete mark + points[p] = 0; + } else { // type is 5 or 10, ie, a crossing + // set new direction + type = pp & ((0x33 * type) >> 4); + // set new type for "future hit" + points[p] &= (type >> 2 | type << 2); + } + + coords.push(p % width1); + coords.push((p / width1) | 0); + --count; + } while (p0 !== p); + outlines.push(coords); + --i; + } + + var drawOutline = function(c) { + c.save(); + // the path shall be painted in [0..1]x[0..1] space + c.scale(1 / width, -1 / height); + c.translate(0, -height); + c.beginPath(); + for (var i = 0, ii = outlines.length; i < ii; i++) { + var o = outlines[i]; + c.moveTo(o[0], o[1]); + for (var j = 2, jj = o.length; j < jj; j += 2) { + c.lineTo(o[j], o[j+1]); + } + } + c.fill(); + c.beginPath(); + c.restore(); + }; + + return drawOutline; +} + +var CanvasExtraState = (function CanvasExtraStateClosure() { + function CanvasExtraState(old) { + // Are soft masks and alpha values shapes or opacities? + this.alphaIsShape = false; + this.fontSize = 0; + this.fontSizeScale = 1; + this.textMatrix = IDENTITY_MATRIX; + this.fontMatrix = FONT_IDENTITY_MATRIX; + this.leading = 0; + // Current point (in user coordinates) + this.x = 0; + this.y = 0; + // Start of text line (in text coordinates) + this.lineX = 0; + this.lineY = 0; + // Character and word spacing + this.charSpacing = 0; + this.wordSpacing = 0; + this.textHScale = 1; + this.textRenderingMode = TextRenderingMode.FILL; + this.textRise = 0; + // Color spaces + this.fillColorSpace = ColorSpace.singletons.gray; + this.fillColorSpaceObj = null; + this.strokeColorSpace = ColorSpace.singletons.gray; + this.strokeColorSpaceObj = null; + this.fillColorObj = null; + this.strokeColorObj = null; + // Default fore and background colors + this.fillColor = '#000000'; + this.strokeColor = '#000000'; + // Note: fill alpha applies to all non-stroking operations + this.fillAlpha = 1; + this.strokeAlpha = 1; + this.lineWidth = 1; + + this.old = old; + } + + CanvasExtraState.prototype = { + clone: function CanvasExtraState_clone() { + return Object.create(this); + }, + setCurrentPoint: function CanvasExtraState_setCurrentPoint(x, y) { + this.x = x; + this.y = y; + } + }; + return CanvasExtraState; +})(); + +var CanvasGraphics = (function CanvasGraphicsClosure() { + // Defines the time the executeOperatorList is going to be executing + // before it stops and shedules a continue of execution. + var EXECUTION_TIME = 15; + + function CanvasGraphics(canvasCtx, commonObjs, objs, textLayer, imageLayer) { + this.ctx = canvasCtx; + this.current = new CanvasExtraState(); + this.stateStack = []; + this.pendingClip = null; + this.pendingEOFill = false; + this.res = null; + this.xobjs = null; + this.commonObjs = commonObjs; + this.objs = objs; + this.textLayer = textLayer; + this.imageLayer = imageLayer; + this.groupStack = []; + this.processingType3 = null; + // Patterns are painted relative to the initial page/form transform, see pdf + // spec 8.7.2 NOTE 1. + this.baseTransform = null; + this.baseTransformStack = []; + this.groupLevel = 0; + if (canvasCtx) { + addContextCurrentTransform(canvasCtx); + } + } + + function putBinaryImageData(ctx, imgData) { + if (typeof ImageData !== 'undefined' && imgData instanceof ImageData) { + ctx.putImageData(imgData, 0, 0); + return; + } + + // Put the image data to the canvas in chunks, rather than putting the + // whole image at once. This saves JS memory, because the ImageData object + // is smaller. It also possibly saves C++ memory within the implementation + // of putImageData(). (E.g. in Firefox we make two short-lived copies of + // the data passed to putImageData()). |n| shouldn't be too small, however, + // because too many putImageData() calls will slow things down. + + var rowsInFullChunks = 16; + var fullChunks = (imgData.height / rowsInFullChunks) | 0; + var rowsInLastChunk = imgData.height - fullChunks * rowsInFullChunks; + var elemsInFullChunks = imgData.width * rowsInFullChunks * 4; + var elemsInLastChunk = imgData.width * rowsInLastChunk * 4; + + var chunkImgData = ctx.createImageData(imgData.width, rowsInFullChunks); + var srcPos = 0; + var src = imgData.data; + var dst = chunkImgData.data; + var haveSetAndSubarray = 'set' in dst && 'subarray' in src; + + // Do all the full-size chunks. + for (var i = 0; i < fullChunks; i++) { + if (haveSetAndSubarray) { + dst.set(src.subarray(srcPos, srcPos + elemsInFullChunks)); + srcPos += elemsInFullChunks; + } else { + for (var j = 0; j < elemsInFullChunks; j++) { + chunkImgData.data[j] = imgData.data[srcPos++]; + } + } + ctx.putImageData(chunkImgData, 0, i * rowsInFullChunks); + } + + // Do the final, partial chunk, if required. + if (rowsInLastChunk !== 0) { + if (haveSetAndSubarray) { + dst.set(src.subarray(srcPos, srcPos + elemsInLastChunk)); + srcPos += elemsInLastChunk; + } else { + for (var j = 0; j < elemsInLastChunk; j++) { + chunkImgData.data[j] = imgData.data[srcPos++]; + } + } + // This (conceptually) puts pixels past the bounds of the canvas. But + // that's ok; any such pixels are ignored. + ctx.putImageData(chunkImgData, 0, fullChunks * rowsInFullChunks); + } + } + + function putBinaryImageMask(ctx, imgData) { + var width = imgData.width, height = imgData.height; + var tmpImgData = ctx.createImageData(width, height); + var data = imgData.data; + var tmpImgDataPixels = tmpImgData.data; + var dataPos = 0; + + // Expand the mask so it can be used by the canvas. Any required inversion + // has already been handled. + var tmpPos = 3; // alpha component offset + for (var i = 0; i < height; i++) { + var mask = 0; + for (var j = 0; j < width; j++) { + if (!mask) { + var elem = data[dataPos++]; + mask = 128; + } + if (!(elem & mask)) { + tmpImgDataPixels[tmpPos] = 255; + } + tmpPos += 4; + mask >>= 1; + } + } + + ctx.putImageData(tmpImgData, 0, 0); + } + + function copyCtxState(sourceCtx, destCtx) { + var properties = ['strokeStyle', 'fillStyle', 'fillRule', 'globalAlpha', + 'lineWidth', 'lineCap', 'lineJoin', 'miterLimit', + 'globalCompositeOperation', 'font']; + for (var i = 0, ii = properties.length; i < ii; i++) { + var property = properties[i]; + if (property in sourceCtx) { + destCtx[property] = sourceCtx[property]; + } + } + if ('setLineDash' in sourceCtx) { + destCtx.setLineDash(sourceCtx.getLineDash()); + destCtx.lineDashOffset = sourceCtx.lineDashOffset; + } else if ('mozDash' in sourceCtx) { + destCtx.mozDash = sourceCtx.mozDash; + destCtx.mozDashOffset = sourceCtx.mozDashOffset; + } + } + + var LINE_CAP_STYLES = ['butt', 'round', 'square']; + var LINE_JOIN_STYLES = ['miter', 'round', 'bevel']; + var NORMAL_CLIP = {}; + var EO_CLIP = {}; + + CanvasGraphics.prototype = { + + beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) { + // For pdfs that use blend modes we have to clear the canvas else certain + // blend modes can look wrong since we'd be blending with a white + // backdrop. The problem with a transparent backdrop though is we then + // don't get sub pixel anti aliasing on text, so we fill with white if + // we can. + var width = this.ctx.canvas.width; + var height = this.ctx.canvas.height; + if (transparency) { + this.ctx.clearRect(0, 0, width, height); + } else { + this.ctx.mozOpaque = true; + this.ctx.save(); + this.ctx.fillStyle = 'rgb(255, 255, 255)'; + this.ctx.fillRect(0, 0, width, height); + this.ctx.restore(); + } + + var transform = viewport.transform; + this.baseTransform = transform.slice(); + this.ctx.save(); + this.ctx.transform.apply(this.ctx, transform); + + if (this.textLayer) { + this.textLayer.beginLayout(); + } + if (this.imageLayer) { + this.imageLayer.beginLayout(); + } + }, + + executeOperatorList: function CanvasGraphics_executeOperatorList( + operatorList, + executionStartIdx, continueCallback, + stepper) { + var argsArray = operatorList.argsArray; + var fnArray = operatorList.fnArray; + var i = executionStartIdx || 0; + var argsArrayLen = argsArray.length; + + // Sometimes the OperatorList to execute is empty. + if (argsArrayLen == i) { + return i; + } + + var executionEndIdx; + var endTime = Date.now() + EXECUTION_TIME; + + var commonObjs = this.commonObjs; + var objs = this.objs; + var fnId; + var deferred = Promise.resolve(); + + while (true) { + if (stepper && i === stepper.nextBreakPoint) { + stepper.breakIt(i, continueCallback); + return i; + } + + fnId = fnArray[i]; + + if (fnId !== OPS.dependency) { + this[fnId].apply(this, argsArray[i]); + } else { + var deps = argsArray[i]; + for (var n = 0, nn = deps.length; n < nn; n++) { + var depObjId = deps[n]; + var common = depObjId.substring(0, 2) == 'g_'; + + // If the promise isn't resolved yet, add the continueCallback + // to the promise and bail out. + if (!common && !objs.isResolved(depObjId)) { + objs.get(depObjId, continueCallback); + return i; + } + if (common && !commonObjs.isResolved(depObjId)) { + commonObjs.get(depObjId, continueCallback); + return i; + } + } + } + + i++; + + // If the entire operatorList was executed, stop as were done. + if (i == argsArrayLen) { + return i; + } + + // If the execution took longer then a certain amount of time, schedule + // to continue exeution after a short delay. + // However, this is only possible if a 'continueCallback' is passed in. + if (continueCallback && Date.now() > endTime) { + deferred.then(continueCallback); + return i; + } + + // If the operatorList isn't executed completely yet OR the execution + // time was short enough, do another execution round. + } + }, + + endDrawing: function CanvasGraphics_endDrawing() { + this.ctx.restore(); + CachedCanvases.clear(); + + if (this.textLayer) { + this.textLayer.endLayout(); + } + if (this.imageLayer) { + this.imageLayer.endLayout(); + } + }, + + // Graphics state + setLineWidth: function CanvasGraphics_setLineWidth(width) { + this.current.lineWidth = width; + this.ctx.lineWidth = width; + }, + setLineCap: function CanvasGraphics_setLineCap(style) { + this.ctx.lineCap = LINE_CAP_STYLES[style]; + }, + setLineJoin: function CanvasGraphics_setLineJoin(style) { + this.ctx.lineJoin = LINE_JOIN_STYLES[style]; + }, + setMiterLimit: function CanvasGraphics_setMiterLimit(limit) { + this.ctx.miterLimit = limit; + }, + setDash: function CanvasGraphics_setDash(dashArray, dashPhase) { + var ctx = this.ctx; + if ('setLineDash' in ctx) { + ctx.setLineDash(dashArray); + ctx.lineDashOffset = dashPhase; + } else { + ctx.mozDash = dashArray; + ctx.mozDashOffset = dashPhase; + } + }, + setRenderingIntent: function CanvasGraphics_setRenderingIntent(intent) { + // Maybe if we one day fully support color spaces this will be important + // for now we can ignore. + // TODO set rendering intent? + }, + setFlatness: function CanvasGraphics_setFlatness(flatness) { + // There's no way to control this with canvas, but we can safely ignore. + // TODO set flatness? + }, + setGState: function CanvasGraphics_setGState(states) { + for (var i = 0, ii = states.length; i < ii; i++) { + var state = states[i]; + var key = state[0]; + var value = state[1]; + + switch (key) { + case 'LW': + this.setLineWidth(value); + break; + case 'LC': + this.setLineCap(value); + break; + case 'LJ': + this.setLineJoin(value); + break; + case 'ML': + this.setMiterLimit(value); + break; + case 'D': + this.setDash(value[0], value[1]); + break; + case 'RI': + this.setRenderingIntent(value); + break; + case 'FL': + this.setFlatness(value); + break; + case 'Font': + this.setFont(value[0], value[1]); + break; + case 'CA': + this.current.strokeAlpha = state[1]; + break; + case 'ca': + this.current.fillAlpha = state[1]; + this.ctx.globalAlpha = state[1]; + break; + case 'BM': + if (value && value.name && (value.name !== 'Normal')) { + var mode = value.name.replace(/([A-Z])/g, + function(c) { + return '-' + c.toLowerCase(); + } + ).substring(1); + this.ctx.globalCompositeOperation = mode; + if (this.ctx.globalCompositeOperation !== mode) { + warn('globalCompositeOperation "' + mode + + '" is not supported'); + } + } else { + this.ctx.globalCompositeOperation = 'source-over'; + } + break; + } + } + }, + save: function CanvasGraphics_save() { + this.ctx.save(); + var old = this.current; + this.stateStack.push(old); + this.current = old.clone(); + }, + restore: function CanvasGraphics_restore() { + var prev = this.stateStack.pop(); + if (prev) { + this.current = prev; + this.ctx.restore(); + } + }, + transform: function CanvasGraphics_transform(a, b, c, d, e, f) { + this.ctx.transform(a, b, c, d, e, f); + }, + + // Path + moveTo: function CanvasGraphics_moveTo(x, y) { + this.ctx.moveTo(x, y); + this.current.setCurrentPoint(x, y); + }, + lineTo: function CanvasGraphics_lineTo(x, y) { + this.ctx.lineTo(x, y); + this.current.setCurrentPoint(x, y); + }, + curveTo: function CanvasGraphics_curveTo(x1, y1, x2, y2, x3, y3) { + this.ctx.bezierCurveTo(x1, y1, x2, y2, x3, y3); + this.current.setCurrentPoint(x3, y3); + }, + curveTo2: function CanvasGraphics_curveTo2(x2, y2, x3, y3) { + var current = this.current; + this.ctx.bezierCurveTo(current.x, current.y, x2, y2, x3, y3); + current.setCurrentPoint(x3, y3); + }, + curveTo3: function CanvasGraphics_curveTo3(x1, y1, x3, y3) { + this.curveTo(x1, y1, x3, y3, x3, y3); + this.current.setCurrentPoint(x3, y3); + }, + closePath: function CanvasGraphics_closePath() { + this.ctx.closePath(); + }, + rectangle: function CanvasGraphics_rectangle(x, y, width, height) { + this.ctx.rect(x, y, width, height); + }, + stroke: function CanvasGraphics_stroke(consumePath) { + consumePath = typeof consumePath !== 'undefined' ? consumePath : true; + var ctx = this.ctx; + var strokeColor = this.current.strokeColor; + if (this.current.lineWidth === 0) + ctx.lineWidth = this.getSinglePixelWidth(); + // For stroke we want to temporarily change the global alpha to the + // stroking alpha. + ctx.globalAlpha = this.current.strokeAlpha; + if (strokeColor && strokeColor.hasOwnProperty('type') && + strokeColor.type === 'Pattern') { + // for patterns, we transform to pattern space, calculate + // the pattern, call stroke, and restore to user space + ctx.save(); + ctx.strokeStyle = strokeColor.getPattern(ctx, this); + ctx.stroke(); + ctx.restore(); + } else { + ctx.stroke(); + } + if (consumePath) + this.consumePath(); + // Restore the global alpha to the fill alpha + ctx.globalAlpha = this.current.fillAlpha; + }, + closeStroke: function CanvasGraphics_closeStroke() { + this.closePath(); + this.stroke(); + }, + fill: function CanvasGraphics_fill(consumePath) { + consumePath = typeof consumePath !== 'undefined' ? consumePath : true; + var ctx = this.ctx; + var fillColor = this.current.fillColor; + var needRestore = false; + + if (fillColor && fillColor.hasOwnProperty('type') && + fillColor.type === 'Pattern') { + ctx.save(); + ctx.fillStyle = fillColor.getPattern(ctx, this); + needRestore = true; + } + + if (this.pendingEOFill) { + if ('mozFillRule' in this.ctx) { + this.ctx.mozFillRule = 'evenodd'; + this.ctx.fill(); + this.ctx.mozFillRule = 'nonzero'; + } else { + try { + this.ctx.fill('evenodd'); + } catch (ex) { + // shouldn't really happen, but browsers might think differently + this.ctx.fill(); + } + } + this.pendingEOFill = false; + } else { + this.ctx.fill(); + } + + if (needRestore) { + ctx.restore(); + } + if (consumePath) { + this.consumePath(); + } + }, + eoFill: function CanvasGraphics_eoFill() { + this.pendingEOFill = true; + this.fill(); + }, + fillStroke: function CanvasGraphics_fillStroke() { + this.fill(false); + this.stroke(false); + + this.consumePath(); + }, + eoFillStroke: function CanvasGraphics_eoFillStroke() { + this.pendingEOFill = true; + this.fillStroke(); + }, + closeFillStroke: function CanvasGraphics_closeFillStroke() { + this.closePath(); + this.fillStroke(); + }, + closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() { + this.pendingEOFill = true; + this.closePath(); + this.fillStroke(); + }, + endPath: function CanvasGraphics_endPath() { + this.consumePath(); + }, + + // Clipping + clip: function CanvasGraphics_clip() { + this.pendingClip = NORMAL_CLIP; + }, + eoClip: function CanvasGraphics_eoClip() { + this.pendingClip = EO_CLIP; + }, + + // Text + beginText: function CanvasGraphics_beginText() { + this.current.textMatrix = IDENTITY_MATRIX; + this.current.x = this.current.lineX = 0; + this.current.y = this.current.lineY = 0; + }, + endText: function CanvasGraphics_endText() { + if (!('pendingTextPaths' in this)) { + this.ctx.beginPath(); + return; + } + var paths = this.pendingTextPaths; + var ctx = this.ctx; + + ctx.save(); + ctx.beginPath(); + for (var i = 0; i < paths.length; i++) { + var path = paths[i]; + ctx.setTransform.apply(ctx, path.transform); + ctx.translate(path.x, path.y); + path.addToPath(ctx, path.fontSize); + } + ctx.restore(); + ctx.clip(); + ctx.beginPath(); + delete this.pendingTextPaths; + }, + setCharSpacing: function CanvasGraphics_setCharSpacing(spacing) { + this.current.charSpacing = spacing; + }, + setWordSpacing: function CanvasGraphics_setWordSpacing(spacing) { + this.current.wordSpacing = spacing; + }, + setHScale: function CanvasGraphics_setHScale(scale) { + this.current.textHScale = scale / 100; + }, + setLeading: function CanvasGraphics_setLeading(leading) { + this.current.leading = -leading; + }, + setFont: function CanvasGraphics_setFont(fontRefName, size) { + var fontObj = this.commonObjs.get(fontRefName); + var current = this.current; + + if (!fontObj) + error('Can\'t find font for ' + fontRefName); + + current.fontMatrix = fontObj.fontMatrix ? fontObj.fontMatrix : + FONT_IDENTITY_MATRIX; + + // A valid matrix needs all main diagonal elements to be non-zero + // This also ensures we bypass FF bugzilla bug #719844. + if (current.fontMatrix[0] === 0 || + current.fontMatrix[3] === 0) { + warn('Invalid font matrix for font ' + fontRefName); + } + + // The spec for Tf (setFont) says that 'size' specifies the font 'scale', + // and in some docs this can be negative (inverted x-y axes). + if (size < 0) { + size = -size; + current.fontDirection = -1; + } else { + current.fontDirection = 1; + } + + this.current.font = fontObj; + this.current.fontSize = size; + + if (fontObj.coded) + return; // we don't need ctx.font for Type3 fonts + + var name = fontObj.loadedName || 'sans-serif'; + var bold = fontObj.black ? (fontObj.bold ? 'bolder' : 'bold') : + (fontObj.bold ? 'bold' : 'normal'); + + var italic = fontObj.italic ? 'italic' : 'normal'; + var typeface = '"' + name + '", ' + fontObj.fallbackName; + + // Some font backends cannot handle fonts below certain size. + // Keeping the font at minimal size and using the fontSizeScale to change + // the current transformation matrix before the fillText/strokeText. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=726227 + var browserFontSize = size >= MIN_FONT_SIZE ? size : MIN_FONT_SIZE; + this.current.fontSizeScale = browserFontSize != MIN_FONT_SIZE ? 1.0 : + size / MIN_FONT_SIZE; + + var rule = italic + ' ' + bold + ' ' + browserFontSize + 'px ' + typeface; + this.ctx.font = rule; + }, + setTextRenderingMode: function CanvasGraphics_setTextRenderingMode(mode) { + this.current.textRenderingMode = mode; + }, + setTextRise: function CanvasGraphics_setTextRise(rise) { + this.current.textRise = rise; + }, + moveText: function CanvasGraphics_moveText(x, y) { + this.current.x = this.current.lineX += x; + this.current.y = this.current.lineY += y; + }, + setLeadingMoveText: function CanvasGraphics_setLeadingMoveText(x, y) { + this.setLeading(-y); + this.moveText(x, y); + }, + setTextMatrix: function CanvasGraphics_setTextMatrix(a, b, c, d, e, f) { + this.current.textMatrix = [a, b, c, d, e, f]; + + this.current.x = this.current.lineX = 0; + this.current.y = this.current.lineY = 0; + }, + nextLine: function CanvasGraphics_nextLine() { + this.moveText(0, this.current.leading); + }, + applyTextTransforms: function CanvasGraphics_applyTextTransforms() { + var ctx = this.ctx; + var current = this.current; + ctx.transform.apply(ctx, current.textMatrix); + ctx.translate(current.x, current.y + current.textRise); + if (current.fontDirection > 0) { + ctx.scale(current.textHScale, -1); + } else { + ctx.scale(-current.textHScale, 1); + } + }, + createTextGeometry: function CanvasGraphics_createTextGeometry() { + var geometry = {}; + var ctx = this.ctx; + var font = this.current.font; + var ctxMatrix = ctx.mozCurrentTransform; + var a = ctxMatrix[0], b = ctxMatrix[1], c = ctxMatrix[2]; + var d = ctxMatrix[3], e = ctxMatrix[4], f = ctxMatrix[5]; + var sx = (a >= 0) ? + Math.sqrt((a * a) + (b * b)) : -Math.sqrt((a * a) + (b * b)); + var sy = (d >= 0) ? + Math.sqrt((c * c) + (d * d)) : -Math.sqrt((c * c) + (d * d)); + var angle = Math.atan2(b, a); + var x = e; + var y = f; + geometry.x = x; + geometry.y = y; + geometry.hScale = sx; + geometry.vScale = sy; + geometry.angle = angle; + geometry.spaceWidth = font.spaceWidth; + geometry.fontName = font.loadedName; + geometry.fontFamily = font.fallbackName; + geometry.fontSize = this.current.fontSize; + geometry.ascent = font.ascent; + geometry.descent = font.descent; + return geometry; + }, + + paintChar: function (character, x, y) { + var ctx = this.ctx; + var current = this.current; + var font = current.font; + var fontSize = current.fontSize / current.fontSizeScale; + var textRenderingMode = current.textRenderingMode; + var fillStrokeMode = textRenderingMode & + TextRenderingMode.FILL_STROKE_MASK; + var isAddToPathSet = !!(textRenderingMode & + TextRenderingMode.ADD_TO_PATH_FLAG); + + var addToPath; + if (font.disableFontFace || isAddToPathSet) { + addToPath = font.getPathGenerator(this.commonObjs, character); + } + + if (font.disableFontFace) { + ctx.save(); + ctx.translate(x, y); + ctx.beginPath(); + addToPath(ctx, fontSize); + if (fillStrokeMode === TextRenderingMode.FILL || + fillStrokeMode === TextRenderingMode.FILL_STROKE) { + ctx.fill(); + } + if (fillStrokeMode === TextRenderingMode.STROKE || + fillStrokeMode === TextRenderingMode.FILL_STROKE) { + ctx.stroke(); + } + ctx.restore(); + } else { + if (fillStrokeMode === TextRenderingMode.FILL || + fillStrokeMode === TextRenderingMode.FILL_STROKE) { + ctx.fillText(character, x, y); + } + if (fillStrokeMode === TextRenderingMode.STROKE || + fillStrokeMode === TextRenderingMode.FILL_STROKE) { + ctx.strokeText(character, x, y); + } + } + + if (isAddToPathSet) { + var paths = this.pendingTextPaths || (this.pendingTextPaths = []); + paths.push({ + transform: ctx.mozCurrentTransform, + x: x, + y: y, + fontSize: fontSize, + addToPath: addToPath + }); + } + }, + + get isFontSubpixelAAEnabled() { + // Checks if anti-aliasing is enabled when scaled text is painted. + // On Windows GDI scaled fonts looks bad. + var ctx = document.createElement('canvas').getContext('2d'); + ctx.scale(1.5, 1); + ctx.fillText('I', 0, 10); + var data = ctx.getImageData(0, 0, 10, 10).data; + var enabled = false; + for (var i = 3; i < data.length; i += 4) { + if (data[i] > 0 && data[i] < 255) { + enabled = true; + break; + } + } + return shadow(this, 'isFontSubpixelAAEnabled', enabled); + }, + + showText: function CanvasGraphics_showText(glyphs, skipTextSelection) { + var ctx = this.ctx; + var current = this.current; + var font = current.font; + var fontSize = current.fontSize; + var fontSizeScale = current.fontSizeScale; + var charSpacing = current.charSpacing; + var wordSpacing = current.wordSpacing; + var textHScale = current.textHScale * current.fontDirection; + var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX; + var glyphsLength = glyphs.length; + var textLayer = this.textLayer; + var geom; + var textSelection = textLayer && !skipTextSelection ? true : false; + var canvasWidth = 0.0; + var vertical = font.vertical; + var defaultVMetrics = font.defaultVMetrics; + + // Type3 fonts - each glyph is a "mini-PDF" + if (font.coded) { + ctx.save(); + ctx.transform.apply(ctx, current.textMatrix); + ctx.translate(current.x, current.y); + + ctx.scale(textHScale, 1); + + if (textSelection) { + this.save(); + ctx.scale(1, -1); + geom = this.createTextGeometry(); + this.restore(); + } + for (var i = 0; i < glyphsLength; ++i) { + + var glyph = glyphs[i]; + if (glyph === null) { + // word break + this.ctx.translate(wordSpacing, 0); + current.x += wordSpacing * textHScale; + continue; + } + + this.processingType3 = glyph; + this.save(); + ctx.scale(fontSize, fontSize); + ctx.transform.apply(ctx, fontMatrix); + this.executeOperatorList(glyph.operatorList); + this.restore(); + + var transformed = Util.applyTransform([glyph.width, 0], fontMatrix); + var width = (transformed[0] * fontSize + charSpacing) * + current.fontDirection; + + ctx.translate(width, 0); + current.x += width * textHScale; + + canvasWidth += width; + } + ctx.restore(); + this.processingType3 = null; + } else { + ctx.save(); + this.applyTextTransforms(); + + var lineWidth = current.lineWidth; + var a1 = current.textMatrix[0], b1 = current.textMatrix[1]; + var scale = Math.sqrt(a1 * a1 + b1 * b1); + if (scale === 0 || lineWidth === 0) + lineWidth = this.getSinglePixelWidth(); + else + lineWidth /= scale; + + if (textSelection) + geom = this.createTextGeometry(); + + if (fontSizeScale != 1.0) { + ctx.scale(fontSizeScale, fontSizeScale); + lineWidth /= fontSizeScale; + } + + ctx.lineWidth = lineWidth; + + var x = 0; + for (var i = 0; i < glyphsLength; ++i) { + var glyph = glyphs[i]; + if (glyph === null) { + // word break + x += current.fontDirection * wordSpacing; + continue; + } + + var restoreNeeded = false; + var character = glyph.fontChar; + var vmetric = glyph.vmetric || defaultVMetrics; + if (vertical) { + var vx = glyph.vmetric ? vmetric[1] : glyph.width * 0.5; + vx = -vx * fontSize * current.fontMatrix[0]; + var vy = vmetric[2] * fontSize * current.fontMatrix[0]; + } + var width = vmetric ? -vmetric[0] : glyph.width; + var charWidth = width * fontSize * current.fontMatrix[0] + + charSpacing * current.fontDirection; + var accent = glyph.accent; + + var scaledX, scaledY, scaledAccentX, scaledAccentY; + if (!glyph.disabled) { + if (vertical) { + scaledX = vx / fontSizeScale; + scaledY = (x + vy) / fontSizeScale; + } else { + scaledX = x / fontSizeScale; + scaledY = 0; + } + + if (font.remeasure && width > 0 && this.isFontSubpixelAAEnabled) { + // some standard fonts may not have the exact width, trying to + // rescale per character + var measuredWidth = ctx.measureText(character).width * 1000 / + current.fontSize * current.fontSizeScale; + var characterScaleX = width / measuredWidth; + restoreNeeded = true; + ctx.save(); + ctx.scale(characterScaleX, 1); + scaledX /= characterScaleX; + if (accent) { + scaledAccentX /= characterScaleX; + } + } + + this.paintChar(character, scaledX, scaledY); + if (accent) { + scaledAccentX = scaledX + accent.offset.x / fontSizeScale; + scaledAccentY = scaledY - accent.offset.y / fontSizeScale; + this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY); + } + } + + x += charWidth; + + canvasWidth += charWidth; + + if (restoreNeeded) { + ctx.restore(); + } + } + if (vertical) { + current.y -= x * textHScale; + } else { + current.x += x * textHScale; + } + ctx.restore(); + } + + if (textSelection) { + geom.canvasWidth = canvasWidth; + if (vertical) { + var VERTICAL_TEXT_ROTATION = Math.PI / 2; + geom.angle += VERTICAL_TEXT_ROTATION; + } + this.textLayer.appendText(geom); + } + + return canvasWidth; + }, + showSpacedText: function CanvasGraphics_showSpacedText(arr) { + var ctx = this.ctx; + var current = this.current; + var font = current.font; + var fontSize = current.fontSize; + // TJ array's number is independent from fontMatrix + var textHScale = current.textHScale * 0.001 * current.fontDirection; + var arrLength = arr.length; + var textLayer = this.textLayer; + var geom; + var canvasWidth = 0.0; + var textSelection = textLayer ? true : false; + var vertical = font.vertical; + var spacingAccumulator = 0; + + if (textSelection) { + ctx.save(); + this.applyTextTransforms(); + geom = this.createTextGeometry(); + ctx.restore(); + } + + for (var i = 0; i < arrLength; ++i) { + var e = arr[i]; + if (isNum(e)) { + var spacingLength = -e * fontSize * textHScale; + if (vertical) { + current.y += spacingLength; + } else { + current.x += spacingLength; + } + + if (textSelection) + spacingAccumulator += spacingLength; + } else { + var shownCanvasWidth = this.showText(e, true); + + if (textSelection) { + canvasWidth += spacingAccumulator + shownCanvasWidth; + spacingAccumulator = 0; + } + } + } + + if (textSelection) { + geom.canvasWidth = canvasWidth; + if (vertical) { + var VERTICAL_TEXT_ROTATION = Math.PI / 2; + geom.angle += VERTICAL_TEXT_ROTATION; + } + this.textLayer.appendText(geom); + } + }, + nextLineShowText: function CanvasGraphics_nextLineShowText(text) { + this.nextLine(); + this.showText(text); + }, + nextLineSetSpacingShowText: + function CanvasGraphics_nextLineSetSpacingShowText(wordSpacing, + charSpacing, + text) { + this.setWordSpacing(wordSpacing); + this.setCharSpacing(charSpacing); + this.nextLineShowText(text); + }, + + // Type3 fonts + setCharWidth: function CanvasGraphics_setCharWidth(xWidth, yWidth) { + // We can safely ignore this since the width should be the same + // as the width in the Widths array. + }, + setCharWidthAndBounds: function CanvasGraphics_setCharWidthAndBounds(xWidth, + yWidth, + llx, + lly, + urx, + ury) { + // TODO According to the spec we're also suppose to ignore any operators + // that set color or include images while processing this type3 font. + this.rectangle(llx, lly, urx - llx, ury - lly); + this.clip(); + this.endPath(); + }, + + // Color + setStrokeColorSpace: function CanvasGraphics_setStrokeColorSpace(raw) { + this.current.strokeColorSpace = ColorSpace.fromIR(raw); + }, + setFillColorSpace: function CanvasGraphics_setFillColorSpace(raw) { + this.current.fillColorSpace = ColorSpace.fromIR(raw); + }, + setStrokeColor: function CanvasGraphics_setStrokeColor(/*...*/) { + var cs = this.current.strokeColorSpace; + var rgbColor = cs.getRgb(arguments, 0); + var color = Util.makeCssRgb(rgbColor); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; + }, + getColorN_Pattern: function CanvasGraphics_getColorN_Pattern(IR, cs) { + if (IR[0] == 'TilingPattern') { + var args = IR[1]; + var base = cs.base; + var color; + if (base) { + var baseComps = base.numComps; + + color = base.getRgb(args, 0); + } + var pattern = new TilingPattern(IR, color, this.ctx, this.objs, + this.commonObjs, this.baseTransform); + } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') { + var pattern = Pattern.shadingFromIR(IR); + } else { + error('Unkown IR type ' + IR[0]); + } + return pattern; + }, + setStrokeColorN: function CanvasGraphics_setStrokeColorN(/*...*/) { + var cs = this.current.strokeColorSpace; + + if (cs.name == 'Pattern') { + this.current.strokeColor = this.getColorN_Pattern(arguments, cs); + } else { + this.setStrokeColor.apply(this, arguments); + } + }, + setFillColor: function CanvasGraphics_setFillColor(/*...*/) { + var cs = this.current.fillColorSpace; + var rgbColor = cs.getRgb(arguments, 0); + var color = Util.makeCssRgb(rgbColor); + this.ctx.fillStyle = color; + this.current.fillColor = color; + }, + setFillColorN: function CanvasGraphics_setFillColorN(/*...*/) { + var cs = this.current.fillColorSpace; + + if (cs.name == 'Pattern') { + this.current.fillColor = this.getColorN_Pattern(arguments, cs); + } else { + this.setFillColor.apply(this, arguments); + } + }, + setStrokeGray: function CanvasGraphics_setStrokeGray(gray) { + this.current.strokeColorSpace = ColorSpace.singletons.gray; + + var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0); + var color = Util.makeCssRgb(rgbColor); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; + }, + setFillGray: function CanvasGraphics_setFillGray(gray) { + this.current.fillColorSpace = ColorSpace.singletons.gray; + + var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0); + var color = Util.makeCssRgb(rgbColor); + this.ctx.fillStyle = color; + this.current.fillColor = color; + }, + setStrokeRGBColor: function CanvasGraphics_setStrokeRGBColor(r, g, b) { + this.current.strokeColorSpace = ColorSpace.singletons.rgb; + + var rgbColor = this.current.strokeColorSpace.getRgb(arguments, 0); + var color = Util.makeCssRgb(rgbColor); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; + }, + setFillRGBColor: function CanvasGraphics_setFillRGBColor(r, g, b) { + this.current.fillColorSpace = ColorSpace.singletons.rgb; + + var rgbColor = this.current.fillColorSpace.getRgb(arguments, 0); + var color = Util.makeCssRgb(rgbColor); + this.ctx.fillStyle = color; + this.current.fillColor = color; + }, + setStrokeCMYKColor: function CanvasGraphics_setStrokeCMYKColor(c, m, y, k) { + this.current.strokeColorSpace = ColorSpace.singletons.cmyk; + + var color = Util.makeCssCmyk(arguments); + this.ctx.strokeStyle = color; + this.current.strokeColor = color; + }, + setFillCMYKColor: function CanvasGraphics_setFillCMYKColor(c, m, y, k) { + this.current.fillColorSpace = ColorSpace.singletons.cmyk; + + var color = Util.makeCssCmyk(arguments); + this.ctx.fillStyle = color; + this.current.fillColor = color; + }, + + shadingFill: function CanvasGraphics_shadingFill(patternIR) { + var ctx = this.ctx; + + this.save(); + var pattern = Pattern.shadingFromIR(patternIR); + ctx.fillStyle = pattern.getPattern(ctx, this); + + var inv = ctx.mozCurrentTransformInverse; + if (inv) { + var canvas = ctx.canvas; + var width = canvas.width; + var height = canvas.height; + + var bl = Util.applyTransform([0, 0], inv); + var br = Util.applyTransform([0, height], inv); + var ul = Util.applyTransform([width, 0], inv); + var ur = Util.applyTransform([width, height], inv); + + var x0 = Math.min(bl[0], br[0], ul[0], ur[0]); + var y0 = Math.min(bl[1], br[1], ul[1], ur[1]); + var x1 = Math.max(bl[0], br[0], ul[0], ur[0]); + var y1 = Math.max(bl[1], br[1], ul[1], ur[1]); + + this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0); + } else { + // HACK to draw the gradient onto an infinite rectangle. + // PDF gradients are drawn across the entire image while + // Canvas only allows gradients to be drawn in a rectangle + // The following bug should allow us to remove this. + // https://bugzilla.mozilla.org/show_bug.cgi?id=664884 + + this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10); + } + + this.restore(); + }, + + // Images + beginInlineImage: function CanvasGraphics_beginInlineImage() { + error('Should not call beginInlineImage'); + }, + beginImageData: function CanvasGraphics_beginImageData() { + error('Should not call beginImageData'); + }, + + paintFormXObjectBegin: function CanvasGraphics_paintFormXObjectBegin(matrix, + bbox) { + this.save(); + this.baseTransformStack.push(this.baseTransform); + + if (matrix && isArray(matrix) && 6 == matrix.length) + this.transform.apply(this, matrix); + + this.baseTransform = this.ctx.mozCurrentTransform; + + if (bbox && isArray(bbox) && 4 == bbox.length) { + var width = bbox[2] - bbox[0]; + var height = bbox[3] - bbox[1]; + this.rectangle(bbox[0], bbox[1], width, height); + this.clip(); + this.endPath(); + } + }, + + paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() { + this.restore(); + this.baseTransform = this.baseTransformStack.pop(); + }, + + beginGroup: function CanvasGraphics_beginGroup(group) { + this.save(); + var currentCtx = this.ctx; + // TODO non-isolated groups - according to Rik at adobe non-isolated + // group results aren't usually that different and they even have tools + // that ignore this setting. Notes from Rik on implmenting: + // - When you encounter an transparency group, create a new canvas with + // the dimensions of the bbox + // - copy the content from the previous canvas to the new canvas + // - draw as usual + // - remove the backdrop alpha: + // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha + // value of your transparency group and 'alphaBackdrop' the alpha of the + // backdrop + // - remove background color: + // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew) + if (!group.isolated) { + info('TODO: Support non-isolated groups.'); + } + + // TODO knockout - supposedly possible with the clever use of compositing + // modes. + if (group.knockout) { + warn('Knockout groups not supported.'); + } + + var currentTransform = currentCtx.mozCurrentTransform; + if (group.matrix) { + currentCtx.transform.apply(currentCtx, group.matrix); + } + assert(group.bbox, 'Bounding box is required.'); + + // Based on the current transform figure out how big the bounding box + // will actually be. + var bounds = Util.getAxialAlignedBoundingBox( + group.bbox, + currentCtx.mozCurrentTransform); + // Clip the bounding box to the current canvas. + var canvasBounds = [0, + 0, + currentCtx.canvas.width, + currentCtx.canvas.height]; + bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0]; + // Use ceil in case we're between sizes so we don't create canvas that is + // too small and make the canvas at least 1x1 pixels. + var drawnWidth = Math.max(Math.ceil(bounds[2] - bounds[0]), 1); + var drawnHeight = Math.max(Math.ceil(bounds[3] - bounds[1]), 1); + + var scratchCanvas = CachedCanvases.getCanvas( + 'groupAt' + this.groupLevel, drawnWidth, drawnHeight, true); + var groupCtx = scratchCanvas.context; + // Since we created a new canvas that is just the size of the bounding box + // we have to translate the group ctx. + var offsetX = bounds[0]; + var offsetY = bounds[1]; + groupCtx.translate(-offsetX, -offsetY); + groupCtx.transform.apply(groupCtx, currentTransform); + + // Setup the current ctx so when the group is popped we draw it the right + // location. + currentCtx.setTransform(1, 0, 0, 1, 0, 0); + currentCtx.translate(offsetX, offsetY); + // The transparency group inherits all off the current graphics state + // except the blend mode, soft mask, and alpha constants. + copyCtxState(currentCtx, groupCtx); + this.ctx = groupCtx; + this.setGState([ + ['SMask', 'None'], + ['BM', 'Normal'], + ['ca', 1], + ['CA', 1] + ]); + this.groupStack.push(currentCtx); + this.groupLevel++; + }, + + endGroup: function CanvasGraphics_endGroup(group) { + this.groupLevel--; + var groupCtx = this.ctx; + this.ctx = this.groupStack.pop(); + // Turn off image smoothing to avoid sub pixel interpolation which can + // look kind of blurry for some pdfs. + if ('imageSmoothingEnabled' in this.ctx) { + this.ctx.imageSmoothingEnabled = false; + } else { + this.ctx.mozImageSmoothingEnabled = false; + } + this.ctx.drawImage(groupCtx.canvas, 0, 0); + this.restore(); + }, + + beginAnnotations: function CanvasGraphics_beginAnnotations() { + this.save(); + this.current = new CanvasExtraState(); + }, + + endAnnotations: function CanvasGraphics_endAnnotations() { + this.restore(); + }, + + beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform, + matrix) { + this.save(); + + if (rect && isArray(rect) && 4 == rect.length) { + var width = rect[2] - rect[0]; + var height = rect[3] - rect[1]; + this.rectangle(rect[0], rect[1], width, height); + this.clip(); + this.endPath(); + } + + this.transform.apply(this, transform); + this.transform.apply(this, matrix); + }, + + endAnnotation: function CanvasGraphics_endAnnotation() { + this.restore(); + }, + + paintJpegXObject: function CanvasGraphics_paintJpegXObject(objId, w, h) { + var domImage = this.objs.get(objId); + if (!domImage) { + error('Dependent image isn\'t ready yet'); + } + + this.save(); + + var ctx = this.ctx; + // scale the image to the unit square + ctx.scale(1 / w, -1 / h); + + ctx.drawImage(domImage, 0, 0, domImage.width, domImage.height, + 0, -h, w, h); + if (this.imageLayer) { + var currentTransform = ctx.mozCurrentTransformInverse; + var position = this.getCanvasPosition(0, 0); + this.imageLayer.appendImage({ + objId: objId, + left: position[0], + top: position[1], + width: w / currentTransform[0], + height: h / currentTransform[3] + }); + } + this.restore(); + }, + + paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) { + var ctx = this.ctx; + var width = img.width, height = img.height; + + var glyph = this.processingType3; + + if (COMPILE_TYPE3_GLYPHS && glyph && !('compiled' in glyph)) { + var MAX_SIZE_TO_COMPILE = 1000; + if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) { + glyph.compiled = + compileType3Glyph({data: img.data, width: width, height: height}); + } else { + glyph.compiled = null; + } + } + + if (glyph && glyph.compiled) { + glyph.compiled(ctx); + return; + } + + var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); + var maskCtx = maskCanvas.context; + maskCtx.save(); + + putBinaryImageMask(maskCtx, img); + + maskCtx.globalCompositeOperation = 'source-in'; + + var fillColor = this.current.fillColor; + maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && + fillColor.type === 'Pattern') ? + fillColor.getPattern(maskCtx, this) : fillColor; + maskCtx.fillRect(0, 0, width, height); + + maskCtx.restore(); + + this.paintInlineImageXObject(maskCanvas.canvas); + }, + + paintImageMaskXObjectGroup: + function CanvasGraphics_paintImageMaskXObjectGroup(images) { + var ctx = this.ctx; + + for (var i = 0, ii = images.length; i < ii; i++) { + var image = images[i]; + var width = image.width, height = image.height; + + var maskCanvas = CachedCanvases.getCanvas('maskCanvas', width, height); + var maskCtx = maskCanvas.context; + maskCtx.save(); + + putBinaryImageMask(maskCtx, image); + + maskCtx.globalCompositeOperation = 'source-in'; + + var fillColor = this.current.fillColor; + maskCtx.fillStyle = (fillColor && fillColor.hasOwnProperty('type') && + fillColor.type === 'Pattern') ? + fillColor.getPattern(maskCtx, this) : fillColor; + maskCtx.fillRect(0, 0, width, height); + + maskCtx.restore(); + + ctx.save(); + ctx.transform.apply(ctx, image.transform); + ctx.scale(1, -1); + ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, + 0, -1, 1, 1); + ctx.restore(); + } + }, + + paintImageXObject: function CanvasGraphics_paintImageXObject(objId) { + var imgData = this.objs.get(objId); + if (!imgData) + error('Dependent image isn\'t ready yet'); + + this.paintInlineImageXObject(imgData); + }, + + paintInlineImageXObject: + function CanvasGraphics_paintInlineImageXObject(imgData) { + var width = imgData.width; + var height = imgData.height; + var ctx = this.ctx; + + this.save(); + // scale the image to the unit square + ctx.scale(1 / width, -1 / height); + + var currentTransform = ctx.mozCurrentTransformInverse; + var a = currentTransform[0], b = currentTransform[1]; + var widthScale = Math.max(Math.sqrt(a * a + b * b), 1); + var c = currentTransform[2], d = currentTransform[3]; + var heightScale = Math.max(Math.sqrt(c * c + d * d), 1); + + var imgToPaint; + // instanceof HTMLElement does not work in jsdom node.js module + if (imgData instanceof HTMLElement || !imgData.data) { + imgToPaint = imgData; + } else { + var tmpCanvas = CachedCanvases.getCanvas('inlineImage', width, height); + var tmpCtx = tmpCanvas.context; + putBinaryImageData(tmpCtx, imgData); + imgToPaint = tmpCanvas.canvas; + } + + var paintWidth = width, paintHeight = height; + var tmpCanvasId = 'prescale1'; + // Vertial or horizontal scaling shall not be more than 2 to not loose the + // pixels during drawImage operation, painting on the temporary canvas(es) + // that are twice smaller in size + while ((widthScale > 2 && paintWidth > 1) || + (heightScale > 2 && paintHeight > 1)) { + var newWidth = paintWidth, newHeight = paintHeight; + if (widthScale > 2 && paintWidth > 1) { + newWidth = Math.ceil(paintWidth / 2); + widthScale /= paintWidth / newWidth; + } + if (heightScale > 2 && paintHeight > 1) { + newHeight = Math.ceil(paintHeight / 2); + heightScale /= paintHeight / newHeight; + } + var tmpCanvas = CachedCanvases.getCanvas(tmpCanvasId, + newWidth, newHeight); + tmpCtx = tmpCanvas.context; + tmpCtx.clearRect(0, 0, newWidth, newHeight); + tmpCtx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, + 0, 0, newWidth, newHeight); + imgToPaint = tmpCanvas.canvas; + paintWidth = newWidth; + paintHeight = newHeight; + tmpCanvasId = tmpCanvasId === 'prescale1' ? 'prescale2' : 'prescale1'; + } + ctx.drawImage(imgToPaint, 0, 0, paintWidth, paintHeight, + 0, -height, width, height); + + if (this.imageLayer) { + var position = this.getCanvasPosition(0, -height); + this.imageLayer.appendImage({ + imgData: imgData, + left: position[0], + top: position[1], + width: width / currentTransform[0], + height: height / currentTransform[3] + }); + } + this.restore(); + }, + + paintInlineImageXObjectGroup: + function CanvasGraphics_paintInlineImageXObjectGroup(imgData, map) { + var ctx = this.ctx; + var w = imgData.width; + var h = imgData.height; + + var tmpCanvas = CachedCanvases.getCanvas('inlineImage', w, h); + var tmpCtx = tmpCanvas.context; + putBinaryImageData(tmpCtx, imgData); + + for (var i = 0, ii = map.length; i < ii; i++) { + var entry = map[i]; + ctx.save(); + ctx.transform.apply(ctx, entry.transform); + ctx.scale(1, -1); + ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h, + 0, -1, 1, 1); + if (this.imageLayer) { + var position = this.getCanvasPosition(entry.x, entry.y); + this.imageLayer.appendImage({ + imgData: imgData, + left: position[0], + top: position[1], + width: w, + height: h + }); + } + ctx.restore(); + } + }, + + // Marked content + + markPoint: function CanvasGraphics_markPoint(tag) { + // TODO Marked content. + }, + markPointProps: function CanvasGraphics_markPointProps(tag, properties) { + // TODO Marked content. + }, + beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) { + // TODO Marked content. + }, + beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps( + tag, properties) { + // TODO Marked content. + }, + endMarkedContent: function CanvasGraphics_endMarkedContent() { + // TODO Marked content. + }, + + // Compatibility + + beginCompat: function CanvasGraphics_beginCompat() { + // TODO ignore undefined operators (should we do that anyway?) + }, + endCompat: function CanvasGraphics_endCompat() { + // TODO stop ignoring undefined operators + }, + + // Helper functions + + consumePath: function CanvasGraphics_consumePath() { + if (this.pendingClip) { + if (this.pendingClip == EO_CLIP) { + if ('mozFillRule' in this.ctx) { + this.ctx.mozFillRule = 'evenodd'; + this.ctx.clip(); + this.ctx.mozFillRule = 'nonzero'; + } else { + try { + this.ctx.clip('evenodd'); + } catch (ex) { + // shouldn't really happen, but browsers might think differently + this.ctx.clip(); + } + } + } else { + this.ctx.clip(); + } + this.pendingClip = null; + } + this.ctx.beginPath(); + }, + getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) { + var inverse = this.ctx.mozCurrentTransformInverse; + // max of the current horizontal and vertical scale + return Math.sqrt(Math.max( + (inverse[0] * inverse[0] + inverse[1] * inverse[1]), + (inverse[2] * inverse[2] + inverse[3] * inverse[3]))); + }, + getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) { + var transform = this.ctx.mozCurrentTransform; + return [ + transform[0] * x + transform[2] * y + transform[4], + transform[1] * x + transform[3] * y + transform[5] + ]; + } + }; + + for (var op in OPS) { + CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op]; + } + + return CanvasGraphics; +})(); + + + +PDFJS.disableFontFace = false; + +var FontLoader = { + insertRule: function fontLoaderInsertRule(rule) { + var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); + if (!styleElement) { + styleElement = document.createElement('style'); + styleElement.id = 'PDFJS_FONT_STYLE_TAG'; + document.documentElement.getElementsByTagName('head')[0].appendChild( + styleElement); + } + + var styleSheet = styleElement.sheet; + styleSheet.insertRule(rule, styleSheet.cssRules.length); + }, + clear: function fontLoaderClear() { + var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG'); + if (styleElement) { + styleElement.parentNode.removeChild(styleElement); + } + }, + get loadTestFont() { + // This is a CFF font with 1 glyph for '.' that fills its entire width and + // height. + return shadow(this, 'loadTestFont', atob( + 'T1RUTwALAIAAAwAwQ0ZGIDHtZg4AAAOYAAAAgUZGVE1lkzZwAAAEHAAAABxHREVGABQAFQ' + + 'AABDgAAAAeT1MvMlYNYwkAAAEgAAAAYGNtYXABDQLUAAACNAAAAUJoZWFk/xVFDQAAALwA' + + 'AAA2aGhlYQdkA+oAAAD0AAAAJGhtdHgD6AAAAAAEWAAAAAZtYXhwAAJQAAAAARgAAAAGbm' + + 'FtZVjmdH4AAAGAAAAAsXBvc3T/hgAzAAADeAAAACAAAQAAAAEAALZRFsRfDzz1AAsD6AAA' + + 'AADOBOTLAAAAAM4KHDwAAAAAA+gDIQAAAAgAAgAAAAAAAAABAAADIQAAAFoD6AAAAAAD6A' + + 'ABAAAAAAAAAAAAAAAAAAAAAQAAUAAAAgAAAAQD6AH0AAUAAAKKArwAAACMAooCvAAAAeAA' + + 'MQECAAACAAYJAAAAAAAAAAAAAQAAAAAAAAAAAAAAAFBmRWQAwAAuAC4DIP84AFoDIQAAAA' + + 'AAAQAAAAAAAAAAACAAIAABAAAADgCuAAEAAAAAAAAAAQAAAAEAAAAAAAEAAQAAAAEAAAAA' + + 'AAIAAQAAAAEAAAAAAAMAAQAAAAEAAAAAAAQAAQAAAAEAAAAAAAUAAQAAAAEAAAAAAAYAAQ' + + 'AAAAMAAQQJAAAAAgABAAMAAQQJAAEAAgABAAMAAQQJAAIAAgABAAMAAQQJAAMAAgABAAMA' + + 'AQQJAAQAAgABAAMAAQQJAAUAAgABAAMAAQQJAAYAAgABWABYAAAAAAAAAwAAAAMAAAAcAA' + + 'EAAAAAADwAAwABAAAAHAAEACAAAAAEAAQAAQAAAC7//wAAAC7////TAAEAAAAAAAABBgAA' + + 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + + 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAA' + + 'AAAAD/gwAyAAAAAQAAAAAAAAAAAAAAAAAAAAABAAQEAAEBAQJYAAEBASH4DwD4GwHEAvgc' + + 'A/gXBIwMAYuL+nz5tQXkD5j3CBLnEQACAQEBIVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWF' + + 'hYWFhYWFhYAAABAQAADwACAQEEE/t3Dov6fAH6fAT+fPp8+nwHDosMCvm1Cvm1DAz6fBQA' + + 'AAAAAAABAAAAAMmJbzEAAAAAzgTjFQAAAADOBOQpAAEAAAAAAAAADAAUAAQAAAABAAAAAg' + + 'ABAAAAAAAAAAAD6AAAAAAAAA==' + )); + }, + + loadTestFontId: 0, + + loadingContext: { + requests: [], + nextRequestId: 0 + }, + + isSyncFontLoadingSupported: (function detectSyncFontLoadingSupport() { + if (isWorker) + return false; + + // User agent string sniffing is bad, but there is no reliable way to tell + // if font is fully loaded and ready to be used with canvas. + var userAgent = window.navigator.userAgent; + var m = /Mozilla\/5.0.*?rv:(\d+).*? Gecko/.exec(userAgent); + if (m && m[1] >= 14) + return true; + // TODO other browsers + return false; + })(), + + bind: function fontLoaderBind(fonts, callback) { + assert(!isWorker, 'bind() shall be called from main thread'); + + var rules = [], fontsToLoad = []; + for (var i = 0, ii = fonts.length; i < ii; i++) { + var font = fonts[i]; + + // Add the font to the DOM only once or skip if the font + // is already loaded. + if (font.attached || font.loading === false) { + continue; + } + font.attached = true; + + var rule = font.bindDOM(); + if (rule) { + rules.push(rule); + fontsToLoad.push(font); + } + } + + var request = FontLoader.queueLoadingCallback(callback); + if (rules.length > 0 && !this.isSyncFontLoadingSupported) { + FontLoader.prepareFontLoadEvent(rules, fontsToLoad, request); + } else { + request.complete(); + } + }, + + queueLoadingCallback: function FontLoader_queueLoadingCallback(callback) { + function LoadLoader_completeRequest() { + assert(!request.end, 'completeRequest() cannot be called twice'); + request.end = Date.now(); + + // sending all completed requests in order how they were queued + while (context.requests.length > 0 && context.requests[0].end) { + var otherRequest = context.requests.shift(); + setTimeout(otherRequest.callback, 0); + } + } + + var context = FontLoader.loadingContext; + var requestId = 'pdfjs-font-loading-' + (context.nextRequestId++); + var request = { + id: requestId, + complete: LoadLoader_completeRequest, + callback: callback, + started: Date.now() + }; + context.requests.push(request); + return request; + }, + + prepareFontLoadEvent: function fontLoaderPrepareFontLoadEvent(rules, + fonts, + request) { + /** Hack begin */ + // There's currently no event when a font has finished downloading so the + // following code is a dirty hack to 'guess' when a font is + // ready. It's assumed fonts are loaded in order, so add a known test + // font after the desired fonts and then test for the loading of that + // test font. + + function int32(data, offset) { + return (data.charCodeAt(offset) << 24) | + (data.charCodeAt(offset + 1) << 16) | + (data.charCodeAt(offset + 2) << 8) | + (data.charCodeAt(offset + 3) & 0xff); + } + + function string32(value) { + return String.fromCharCode((value >> 24) & 0xff) + + String.fromCharCode((value >> 16) & 0xff) + + String.fromCharCode((value >> 8) & 0xff) + + String.fromCharCode(value & 0xff); + } + + function spliceString(s, offset, remove, insert) { + var chunk1 = data.substr(0, offset); + var chunk2 = data.substr(offset + remove); + return chunk1 + insert + chunk2; + } + + var i, ii; + + var canvas = document.createElement('canvas'); + canvas.width = 1; + canvas.height = 1; + var ctx = canvas.getContext('2d'); + + var called = 0; + function isFontReady(name, callback) { + called++; + // With setTimeout clamping this gives the font ~100ms to load. + if(called > 30) { + warn('Load test font never loaded.'); + callback(); + return; + } + ctx.font = '30px ' + name; + ctx.fillText('.', 0, 20); + var imageData = ctx.getImageData(0, 0, 1, 1); + if (imageData.data[3] > 0) { + callback(); + return; + } + setTimeout(isFontReady.bind(null, name, callback)); + } + + var loadTestFontId = 'lt' + Date.now() + this.loadTestFontId++; + // Chromium seems to cache fonts based on a hash of the actual font data, + // so the font must be modified for each load test else it will appear to + // be loaded already. + // TODO: This could maybe be made faster by avoiding the btoa of the full + // font by splitting it in chunks before hand and padding the font id. + var data = this.loadTestFont; + var COMMENT_OFFSET = 976; // has to be on 4 byte boundary (for checksum) + data = spliceString(data, COMMENT_OFFSET, loadTestFontId.length, + loadTestFontId); + // CFF checksum is important for IE, adjusting it + var CFF_CHECKSUM_OFFSET = 16; + var XXXX_VALUE = 0x58585858; // the "comment" filled with 'X' + var checksum = int32(data, CFF_CHECKSUM_OFFSET); + for (i = 0, ii = loadTestFontId.length - 3; i < ii; i += 4) { + checksum = (checksum - XXXX_VALUE + int32(loadTestFontId, i)) | 0; + } + if (i < loadTestFontId.length) { // align to 4 bytes boundary + checksum = (checksum - XXXX_VALUE + + int32(loadTestFontId + 'XXX', i)) | 0; + } + data = spliceString(data, CFF_CHECKSUM_OFFSET, 4, string32(checksum)); + + var url = 'url(data:font/opentype;base64,' + btoa(data) + ');'; + var rule = '@font-face { font-family:"' + loadTestFontId + '";src:' + + url + '}'; + FontLoader.insertRule(rule); + + var names = []; + for (i = 0, ii = fonts.length; i < ii; i++) { + names.push(fonts[i].loadedName); + } + names.push(loadTestFontId); + + var div = document.createElement('div'); + div.setAttribute('style', + 'visibility: hidden;' + + 'width: 10px; height: 10px;' + + 'position: absolute; top: 0px; left: 0px;'); + for (i = 0, ii = names.length; i < ii; ++i) { + var span = document.createElement('span'); + span.textContent = 'Hi'; + span.style.fontFamily = names[i]; + div.appendChild(span); + } + document.body.appendChild(div); + + isFontReady(loadTestFontId, function() { + document.body.removeChild(div); + request.complete(); + }); + /** Hack end */ + } +}; + +var FontFace = (function FontFaceClosure() { + function FontFace(name, file, properties) { + this.compiledGlyphs = {}; + if (arguments.length === 1) { + // importing translated data + var data = arguments[0]; + for (var i in data) { + this[i] = data[i]; + } + return; + } + } + FontFace.prototype = { + bindDOM: function FontFace_bindDOM() { + if (!this.data) + return null; + + if (PDFJS.disableFontFace) { + this.disableFontFace = true; + return null; + } + + var data = bytesToString(this.data); + var fontName = this.loadedName; + + // Add the font-face rule to the document + var url = ('url(data:' + this.mimetype + ';base64,' + + window.btoa(data) + ');'); + var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}'; + + FontLoader.insertRule(rule); + + if (PDFJS.pdfBug && 'FontInspector' in globalScope && + globalScope['FontInspector'].enabled) + globalScope['FontInspector'].fontAdded(this, url); + + return rule; + }, + getPathGenerator: function (objs, character) { + if (!(character in this.compiledGlyphs)) { + var js = objs.get(this.loadedName + '_path_' + character); + /*jshint -W054 */ + this.compiledGlyphs[character] = new Function('c', 'size', js); + } + return this.compiledGlyphs[character]; + } + }; + return FontFace; +})(); + + +}).call((typeof window === 'undefined') ? this : window); + +if (!PDFJS.workerSrc && typeof document !== 'undefined') { + // workerSrc is not set -- using last script url to define default location + PDFJS.workerSrc = (function () { + 'use strict'; + var scriptTagContainer = document.body || + document.getElementsByTagName('head')[0]; + var pdfjsSrc = scriptTagContainer.lastChild.src; + return pdfjsSrc && pdfjsSrc.replace(/\.js$/i, '.worker.js'); + })(); +} + + diff --git a/plugins/pdfviewer/viewer/pdf.min.js b/plugins/pdfviewer/viewer/pdf.min.js new file mode 100644 index 00000000..5688c2e9 --- /dev/null +++ b/plugins/pdfviewer/viewer/pdf.min.js @@ -0,0 +1,178 @@ +"undefined"===typeof PDFJS&&(("undefined"!==typeof window?window:this).PDFJS={});PDFJS.version="0.8.894";PDFJS.build="ac91047"; +(function(){var E,X;function K(b){PDFJS.verbosity>=PDFJS.VERBOSITY_LEVELS.infos&&console.log("Info: "+b)}function x(b){PDFJS.verbosity>=PDFJS.VERBOSITY_LEVELS.warnings&&console.log("Warning: "+b)}function m(b){if(1c)return a;switch(b.substr(0,c)){case "http":case "https":case "ftp":case "mailto":return!0;default:return!1}}function U(b,a,c){Object.defineProperty(b,a,{value:c, +enumerable:!0,configurable:!0,writable:!1});return c}function V(b){var a,c=b.length,e="";if("\u00fe"===b[0]&&"\u00ff"===b[1])for(a=2;a>=1}p=l= +0;0!==n[p]&&(f[0]=1,++l);for(e=1;e>2)+(n[p+1]?4:0)+(n[p-k+1]?8:0),h[z]&&(f[d+e]=h[z],++l),p++;n[p-k]!==n[p]&&(f[d+e]=n[p]?2:4,++l);if(1E3>4,f[k]&=p>>2|p<<2);n.push(k%g);n.push(k/g|0);--l}while(e!==k);s.push(n);--b}}return function(d){d.save();d.scale(1/a,-1/c);d.translate(0,-c);d.beginPath();for(var e=0,k=s.length;ea[2]&&(c[0]=a[2],c[2]=a[0]);a[1]>a[3]&& +(c[1]=a[3],c[3]=a[1]);return c};b.intersect=function(a,c){function e(a,c){return a-c}var d=[a[0],a[2],c[0],c[2]].sort(e),g=[a[1],a[3],c[1],c[3]].sort(e),f=[];a=b.normalizeRect(a);c=b.normalizeRect(c);if(d[0]===a[0]&&d[1]===c[0]||d[0]===c[0]&&d[1]===a[0])f[0]=d[1],f[2]=d[2];else return!1;if(g[0]===a[1]&&g[1]===c[1]||g[0]===c[1]&&g[1]===a[1])f[1]=g[1],f[3]=g[2];else return!1;return f};b.sign=function(a){return 0>a?-1:1};b.concatenateToArray=function(a,c){Array.prototype.push.apply(a,c)};b.prependToArray= +function(a,c){Array.prototype.unshift.apply(a,c)};b.extendObj=function(a,c){for(var e in c)a[e]=c[e]};b.getInheritableProperty=function(a,c){for(;a&&!a.has(c);)a=a.get("Parent");return a?a.get(c):null};b.inherit=function(a,c,e){a.prototype=Object.create(c.prototype);a.prototype.constructor=a;for(var d in e)a.prototype[d]=e[d]};b.loadScript=function(a,c){var e=document.createElement("script"),d=!1;e.setAttribute("src",a);c&&(e.onload=function(){d||c();d=!0});document.getElementsByTagName("head")[0].appendChild(e)}; +return b}();PDFJS.PageViewport=function(){function b(a,c,e,d,b,f){this.viewBox=a;this.scale=c;this.rotation=e;this.offsetX=d;this.offsetY=b;var h=(a[2]+a[0])/2,k=(a[3]+a[1])/2,l,n,p;e%=360;switch(0>e?e+360:e){case 180:e=-1;n=l=0;p=1;break;case 90:e=0;n=l=1;p=0;break;case 270:e=0;n=l=-1;p=0;break;default:e=1,n=l=0,p=-1}f&&(n=-n,p=-p);0===e?(d=Math.abs(k-a[1])*c+d,b=Math.abs(h-a[0])*c+b,f=Math.abs(a[3]-a[1])*c,a=Math.abs(a[2]-a[0])*c):(d=Math.abs(h-a[0])*c+d,b=Math.abs(k-a[1])*c+b,f=Math.abs(a[2]-a[0])* +c,a=Math.abs(a[3]-a[1])*c);this.transform=[e*c,l*c,n*c,p*c,d-e*c*h-n*c*k,b-l*c*h-p*c*k];this.width=f;this.height=a;this.fontScale=c}b.prototype={clone:function(a){a=a||{};var c="scale"in a?a.scale:this.scale,e="rotation"in a?a.rotation:this.rotation;return new b(this.viewBox.slice(),c,e,this.offsetX,this.offsetY,a.dontFlip)},convertToViewportPoint:function(a,c){return q.applyTransform([a,c],this.transform)},convertToViewportRectangle:function(a){var c=q.applyTransform([a[0],a[1]],this.transform); +a=q.applyTransform([a[2],a[3]],this.transform);return[c[0],c[1],a[0],a[1]]},convertToPdfPoint:function(a,c){return q.applyInverseTransform([a,c],this.transform)}};return b}();var qa=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,728,711,710,729,733,731,730,732,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8226,8224,8225,8230,8212,8211,402,8260,8249,8250, +8722,8240,8222,8220,8221,8216,8217,8218,8482,64257,64258,321,338,352,376,381,305,322,339,353,382,0,8364],G=PDFJS.LegacyPromise=function(){return function(){var b,a,c=new Promise(function(c,d){b=c;a=d});c.resolve=b;c.reject=a;return c}}();(function(){function b(c){this._status=a;this._handlers=[];c.call(this,this._resolve.bind(this),this._reject.bind(this))}if(t.Promise)"function"!==typeof t.Promise.all&&(t.Promise.all=function(a){var c=0,e=[],b,k,l=new t.Promise(function(a,c){b=a;k=c});a.forEach(function(a, +d){c++;a.then(function(a){e[d]=a;c--;0===c&&b(e)},k)});0===c&&b(e);return l}),"function"!==typeof t.Promise.resolve&&(t.Promise.resolve=function(a){return new t.Promise(function(c){c(a)})});else{var a=0,c=2,e={handlers:[],running:!1,unhandledRejections:[],pendingRejectionCheck:!1,scheduleHandlers:function(c){c._status!=a&&(this.handlers=this.handlers.concat(c._handlers),c._handlers=[],this.running||(this.running=!0,setTimeout(this.runHandlers.bind(this),0)))},runHandlers:function(){for(var a=Date.now()+ +1;0=a)break}0e&&(e=f.length)}d=0;for(b=a.length;d>2]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[(g& +3)<<4|f>>4]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[e+1>6:64]+"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[e+2f&&"DeviceGray"!==this.name&&"DeviceRGB"!==this.name){for(var h=8>=d?new Uint8Array(f):new Uint16Array(f),k=0;ka?0:255b?0:255f?0:255a?0:255a?0:a;b[f+1]=255e?0:e;b[f+2]=255h?0:h}function a(){this.name="DeviceCMYK";this.numComps=4;this.defaultColor=new Float32Array([0,0,0,1])}a.prototype={getRgb:function(a,e){var d=new Uint8Array(3);b(a,e,1,d,0);return d},getRgbItem:function(a,e,d,g){b(a,e,1,d,g)},getRgbBuffer:function(a,e,d,g,f,h){h=1/((1<>2)},isPassthrough:r.prototype.isPassthrough,createRgbBuffer:r.prototype.createRgbBuffer, +isDefaultDecode:function(a){return r.isDefaultDecode(a,this.numComps)},usesZeroToOneRange:!0};return a}(),va=function(){function b(a,e,d){this.name="CalGray";this.numComps=1;this.defaultColor=new Float32Array([0]);a||m("WhitePoint missing - required for color space CalGray");e=e||[0,0,0];this.XW=a[0];this.YW=a[1];this.ZW=a[2];this.XB=e[0];this.YB=e[1];this.ZB=e[2];this.G=d||1;(0>this.XW||0>this.ZW||1!==this.YW)&&m("Invalid WhitePoint components for "+this.name+", no fallback available");if(0>this.XB|| +0>this.YB||0>this.ZB)K("Invalid BlackPoint for "+this.name+", falling back to default"),this.XB=this.YB=this.ZB=0;0===this.XB&&0===this.YB&&0===this.ZB||x(this.name+", BlackPoint: XB: "+this.XB+", YB: "+this.YB+", ZB: "+this.ZB+", only default values are supported.");1>this.G&&(K("Invalid Gamma: "+this.G+" for "+this.name+", falling back to default"),this.G=1)}function a(a,e,d,b,f,h){e=Math.pow(e[d]*h,a.G);a=Math.max(116*Math.pow(a.YW*e,1/3)-16,0);b[f]=255*a/100;b[f+1]=255*a/100;b[f+2]=255*a/100} +b.prototype={getRgb:function(a,e){var d=new Uint8Array(3);this.getRgbItem(a,e,d,0);return d},getRgbItem:function(c,e,d,b){a(this,c,e,d,b,1)},getRgbBuffer:function(c,e,d,b,f,h){h=1/((1<this.XW||0>this.ZW||1!==this.YW)&&m("Invalid WhitePoint components, no fallback available");if(0>this.XB||0>this.YB||0>this.ZB)K("Invalid BlackPoint, falling back to default"),this.XB=this.YB=this.ZB= +0;if(this.amin>this.amax||this.bmin>this.bmax)K("Invalid Range, falling back to defaults"),this.amin=-100,this.amax=100,this.bmin=-100,this.bmax=100}function a(a){return a>=6/29?a*a*a:108/841*(a-4/29)}function c(c,d,b,f,h,k){var l=d[b],n=d[b+1];d=d[b+2];!1!==f&&(l=0+100*l/f,n=c.amin+n*(c.amax-c.amin)/f,d=c.bmin+d*(c.bmax-c.bmin)/f);n=n>c.amax?c.amax:nc.bmax?c.bmax:dc.ZW?(c=3.1339*n+-1.617*f+-0.4906* +l,d=-0.9785*n+1.916*f+0.0333*l,n=0.072*n+-0.229*f+1.4057*l):(c=3.2406*n+-1.5372*f+-0.4986*l,d=-0.9689*n+1.8758*f+0.0415*l,n=0.0557*n+-0.204*f+1.057*l);h[k]=255*Math.sqrt(0>c?0:1d?0:1n?0:1=f||0>=k)K("Bad shading domain.");else{for(l=d;l<=f;l+= +k)p=c.getRgb(n([l]),0),p=q.makeCssRgb(p),b.push([(l-d)/s,p]);d="transparent";a.has("Background")&&(p=c.getRgb(a.get("Background"),0),d=q.makeCssRgb(p));g||(b.unshift([0,d]),b[1][0]+=N.SMALL_NUMBER);h||(b[b.length-1][0]-=N.SMALL_NUMBER,b.push([1,d]));this.colorStops=b}}b.fromIR=function(a){var c=a[1],b=a[2],d=a[3],g=a[4],f=a[5],h=a[6];return{type:"Pattern",getPattern:function(a){var l;2==c?l=a.createLinearGradient(d[0],d[1],g[0],g[1]):c==E&&(l=a.createRadialGradient(d[0],d[1],f,g[0],g[1],h));a=0;for(var n= +b.length;a>a)*h);f&=(1<l?b=l:bc[p+1]&&(n=c[p+1]);l[k]=n}g.set(b,l);return l}}}}(),Ga=function(){function b(){this.cache={};this.total=0}b.prototype={has:function(a){return a in this.cache},get:function(a){return this.cache[a]},set:function(a,c){1024>this.total&&(this.cache[a]=c,this.total++)}};return b}(),Ha=function(){function b(a){this.stack=a||[]}b.prototype={push:function(a){100<=this.stack.length&&m("PostScript function stack overflow.");this.stack.push(a)},pop:function(){0>=this.stack.length&&m("PostScript function stack underflow."); +return this.stack.pop()},copy:function(a){100<=this.stack.length+a&&m("PostScript function stack overflow.");var c=this.stack,b=c.length-a;for(a-=1;0<=a;a--,b++)c.push(c[b])},index:function(a){this.push(this.stack[this.stack.length-a-1])},roll:function(a,c){var b=this.stack,d=b.length-a,g=b.length-1,f=d+(c-Math.floor(c/a)*a),h,k,l;h=d;for(k=g;h>f);break;case "ceiling":g=a.pop();a.push(Math.ceil(g));break;case "copy":g=a.pop();a.copy(g);break;case "cos":g=a.pop();a.push(Math.cos(g));break;case "cvi":g=a.pop()|0;a.push(g);break;case "cvr":break;case "div":f=a.pop();g=a.pop();a.push(g/f);break;case "dup":a.copy(1);break;case "eq":f=a.pop();g=a.pop();a.push(g==f);break;case "exch":a.roll(2,1);break;case "exp":f=a.pop();g=a.pop();a.push(Math.pow(g,f));break;case "false":a.push(!1);break;case "floor":g=a.pop(); +a.push(Math.floor(g));break;case "ge":f=a.pop();g=a.pop();a.push(g>=f);break;case "gt":f=a.pop();g=a.pop();a.push(g>f);break;case "idiv":f=a.pop();g=a.pop();a.push(g/f|0);break;case "index":g=a.pop();a.index(g);break;case "le":f=a.pop();g=a.pop();a.push(g<=f);break;case "ln":g=a.pop();a.push(Math.log(g));break;case "log":g=a.pop();a.push(Math.log(g)/Math.LN10);break;case "lt":f=a.pop();g=a.pop();a.push(gg?Math.ceil(g):Math.floor(g);a.push(g);break;case "xor":f=a.pop();g=a.pop();L(g)&&L(f)?a.push(g!=f):a.push(g^f);break;default:m("Unknown operator "+g)}return a.stack}};return b}(),Ea=function(){function b(a){this.lexer=a;this.operators=[];this.prev=this.token=null}b.prototype={nextToken:function(){this.prev=this.token;this.token=this.lexer.getToken()},accept:function(a){return this.token.type==a?(this.nextToken(),!0):!1},expect:function(a){if(this.accept(a))return!0; +m("Unexpected symbol: found "+this.token.type+" expected "+a+".")},parse:function(){this.nextToken();this.expect(v.LBRACE);this.parseBlock();this.expect(v.RBRACE);return this.operators},parseBlock:function(){for(;;)if(this.accept(v.NUMBER))this.operators.push(this.prev.value);else if(this.accept(v.OPERATOR))this.operators.push(this.prev.value);else if(this.accept(v.LBRACE))this.parseCondition();else break},parseCondition:function(){var a=this.operators.length;this.operators.push(null,null);this.parseBlock(); +this.expect(v.RBRACE);if(this.accept(v.IF))this.operators[a]=this.operators.length,this.operators[a+1]="jz";else if(this.accept(v.LBRACE)){var c=this.operators.length;this.operators.push(null,null);var b=this.operators.length;this.parseBlock();this.expect(v.RBRACE);this.expect(v.IFELSE);this.operators[c]=this.operators.length;this.operators[c+1]="j";this.operators[a]=b;this.operators[a+1]="jz"}else m("PS Function: error parsing conditional.")}};return b}(),v={LBRACE:0,RBRACE:1,NUMBER:2,OPERATOR:3, +IF:4,IFELSE:5},T=function(){function b(a,b){this.type=a;this.value=b}var a={};b.getOperator=function(c){var e=a[c];return e?e:a[c]=new b(v.OPERATOR,c)};b.LBRACE=new b(v.LBRACE,"{");b.RBRACE=new b(v.RBRACE,"}");b.IF=new b(v.IF,"IF");b.IFELSE=new b(v.IFELSE,"IFELSE");return b}(),Da=function(){function b(a){this.stream=a;this.nextChar()}b.prototype={nextChar:function(){return this.currentChar=this.stream.getByte()},getToken:function(){for(var a=!1,c=this.currentChar;;){if(0>c)return EOF;if(a){if(10=== +c||13===c)a=!1}else if(37==c)a=!0;else if(!Lexer.isSpace(c))break;c=this.nextChar()}switch(c|0){case 48:case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:case 43:case 45:case 46:return new T(v.NUMBER,this.getNumber());case 123:return this.nextChar(),T.LBRACE;case 125:return this.nextChar(),T.RBRACE}for(a=String.fromCharCode(c);0<=(c=this.nextChar())&&(65<=c&&90>=c||97<=c&&122>=c);)a+=String.fromCharCode(c);switch(a.toLowerCase()){case "if":return T.IF;case "ifelse":return T.IFELSE; +default:return T.getOperator(a)}},getNumber:function(){for(var a=this.currentChar,c=String.fromCharCode(a);0<=(a=this.nextChar());)if(48<=a&&57>=a||45===a||46===a)c+=String.fromCharCode(a);else break;a=parseFloat(c);isNaN(a)&&m("Invalid floating point number: "+a);return a}};return b}(),H=function(){function b(a,b,d){var g=q.getAxialAlignedBoundingBox(b,d);b=g[0];d=g[1];var f=g[2],g=g[3];if(b===f||d===g)return[1,0,0,1,a[0],a[1]];f=(a[2]-a[0])/(f-b);g=(a[3]-a[1])/(g-d);return[f,0,0,g,a[0]-b*f,a[1]- +d*g]}function a(a){if(a.data)this.data=a.data;else{var b=a.dict;a=this.data={};a.subtype=b.get("Subtype").name;var d=b.get("Rect");a.rect=q.normalizeRect(d);a.annotationFlags=b.get("F");d=b.get("C");D(d)&&3===d.length?a.color=d:a.color=[0,0,0];if(b.has("BS"))d=b.get("BS"),a.borderWidth=d.has("W")?d.get("W"):1;else if(d=b.get("Border")||[0,0,1],a.borderWidth=d[2]||0,(d=d[3])&&D(d)){var g=d.length;if(0c.fontDirection?"rtl":"ltr";a&&(g.fontWeight=a.black?a.bold?"bolder":"bold":a.bold?"bold":"normal",g.fontStyle=a.italic?"italic":"normal",c=a.loadedName,g.fontFamily=(c?'"'+c+'", ':"")+(a.fallbackName||"Helvetica, sans-serif"));b.appendChild(d);return b},getOperatorList:function(a){if(this.appearance)return H.prototype.getOperatorList.call(this,a);var c=new G, +b=new OperatorList,d=this.data,g=d.defaultAppearance;if(!g)return c.resolve(b),c;for(var f=Stream,h=g.length,k=new Uint8Array(h),l=0;lf;++f)h=a[f],k=g[f],h===y.setFont?(d.fontRefName=k[0],h=k[1],0>h?(d.fontDirection=-1,d.fontSize=-h):(d.fontDirection=1,d.fontSize=h)):h===y.setFillRGBColor?d.rgb=k:h===y.setFillGray&&(h=255*k[0],d.rgb=[h,h,h]);c.resolve(b);return c}}); +return b}(),Ja=function(){function b(a){H.call(this,a);if(!a.data){a=a.dict;var c=this.data,b=a.get("Contents"),d=a.get("T");c.content=V(b||"");c.title=V(d||"");c.name=a.has("Name")?a.get("Name").name:"Note"}}q.inherit(b,H,{getOperatorList:function(a){a=new G;a.resolve(new OperatorList);return a},hasHtml:function(){return!0},getHtmlElement:function(a){W&&m("getHtmlElement() shall be called from main thread");var c=this.data,b=c.rect;10>b[3]-b[1]&&(b[3]=b[1]+10);10>b[2]-b[0]&&(b[2]=b[0]+(b[3]-b[1])); +var d=this.getEmptyContainer("section",b);d.className="annotText";a=document.createElement("img");a.style.height=d.style.height;var g=c.name;a.src=PDFJS.imageResourcesPath+"annotation-"+g.toLowerCase()+".svg";a.alt="[{{type}} Annotation]";a.dataset.l10nId="text_annotation_type";a.dataset.l10nArgs=JSON.stringify({type:g});var f=document.createElement("div");f.setAttribute("hidden",!0);var g=document.createElement("h1"),h=document.createElement("p");f.style.left=Math.floor(b[2]-b[0])+"px";f.style.top= +"0px";g.textContent=c.title;if(c.content||c.title){for(var b=document.createElement("span"),c=c.content.split(/(?:\r\n?|\n)/),k=0,l=c.length;kf;++f)g[f]=Math.round(255*d[f]);c.style.borderColor=q.makeCssRgb(g);c.style.borderStyle="solid";d=a[3]-a[1]-2*b;c.style.width= +a[2]-a[0]-2*b+"px";c.style.height=d+"px";c.href=this.data.url||"";return c}});return b}();PDFJS.maxImageSize=void 0===PDFJS.maxImageSize?-1:PDFJS.maxImageSize;PDFJS.disableFontFace=void 0===PDFJS.disableFontFace?!1:PDFJS.disableFontFace;PDFJS.imageResourcesPath=void 0===PDFJS.imageResourcesPath?"":PDFJS.imageResourcesPath;PDFJS.disableWorker=void 0===PDFJS.disableWorker?!1:PDFJS.disableWorker;PDFJS.workerSrc=void 0===PDFJS.workerSrc?null:PDFJS.workerSrc;PDFJS.disableRange=void 0===PDFJS.disableRange? +!1:PDFJS.disableRange;PDFJS.disableAutoFetch=void 0===PDFJS.disableAutoFetch?!1:PDFJS.disableAutoFetch;PDFJS.pdfBug=void 0===PDFJS.pdfBug?!1:PDFJS.pdfBug;PDFJS.postMessageTransfers=void 0===PDFJS.postMessageTransfers?!0:PDFJS.postMessageTransfers;PDFJS.disableCreateObjectURL=void 0===PDFJS.disableCreateObjectURL?!1:PDFJS.disableCreateObjectURL;PDFJS.verbosity=void 0===PDFJS.verbosity?PDFJS.VERBOSITY_LEVELS.warnings:PDFJS.verbosity;PDFJS.getDocument=function(b,a,c,e){var d,g;"string"===typeof b?b= +{url:b}:"object"==typeof b&&null!==b&&void 0!==b&&"byteLength"in b?b={data:b}:"object"!==typeof b&&m("Invalid parameter in getDocument, need either Uint8Array, string or a parameter object");b.url||b.data||m("Invalid parameter array, need either .data or .url");var f={};for(d in b)f[d]="url"===d&&"undefined"!==typeof window?pa(window.location.href,b[d]):b[d];b=new PDFJS.LegacyPromise;d=new PDFJS.LegacyPromise;g=new La(b,d,a,e);b.then(function(){g.passwordCallback=c;g.fetchDocument(f)});return d}; +var Ma=function(){function b(a,c){this.pdfInfo=a;this.transport=c}b.prototype={get numPages(){return this.pdfInfo.numPages},get fingerprint(){return this.pdfInfo.fingerprint},get embeddedFontsUsed(){return this.transport.embeddedFontsUsed},getPage:function(a){return this.transport.getPage(a)},getPageIndex:function(a){return this.transport.getPageIndex(a)},getDestinations:function(){return this.transport.getDestinations()},getJavaScript:function(){var a=new PDFJS.LegacyPromise;a.resolve(this.pdfInfo.javaScript); +return a},getOutline:function(){var a=new PDFJS.LegacyPromise;a.resolve(this.pdfInfo.outline);return a},getMetadata:function(){var a=new PDFJS.LegacyPromise,c=this.pdfInfo.metadata;a.resolve({info:this.pdfInfo.info,metadata:c?new PDFJS.Metadata(c):null});return a},isEncrypted:function(){var a=new PDFJS.LegacyPromise;a.resolve(this.pdfInfo.encrypted);return a},getData:function(){var a=new PDFJS.LegacyPromise;this.transport.getData(a);return a},dataLoaded:function(){return this.transport.dataLoaded()}, +cleanup:function(){this.transport.startCleanup()},destroy:function(){this.transport.destroy()}};return b}(),Pa=function(){function b(a,c){this.pageInfo=a;this.transport=c;this.stats=new ua;this.stats.enabled=!!t.PDFJS.enableStats;this.commonObjs=c.commonObjs;this.objs=new ma;this.pendingDestroy=this.cleanupAfterRender=this.receivingOperatorList=!1;this.renderTasks=[]}b.prototype={get pageNumber(){return this.pageInfo.pageIndex+1},get rotate(){return this.pageInfo.rotate},get ref(){return this.pageInfo.ref}, +get view(){return this.pageInfo.view},getViewport:function(a,c){2>arguments.length&&(c=this.rotate);return new PDFJS.PageViewport(this.view,a,c,0,0)},getAnnotations:function(){if(this.annotationsPromise)return this.annotationsPromise;var a=new PDFJS.LegacyPromise;this.annotationsPromise=a;this.transport.getAnnotations(this.pageInfo.pageIndex);return a},render:function(a){function c(a){var c=f.renderTasks.indexOf(d);0<=c&&f.renderTasks.splice(c,1);f.cleanupAfterRender&&(f.pendingDestroy=!0);f._tryDestroy(); +a?g.promise.reject(a):g.promise.resolve();b.timeEnd("Rendering");b.timeEnd("Overall")}var b=this.stats;b.time("Overall");this.pendingDestroy=!1;this.displayReadyPromise||(this.receivingOperatorList=!0,this.displayReadyPromise=new G,this.operatorList={fnArray:[],argsArray:[],lastChunk:!1},this.stats.time("Page Request"),this.transport.messageHandler.send("RenderPageRequest",{pageIndex:this.pageNumber-1}));var d=new Na(c,a,this.objs,this.commonObjs,this.operatorList,this.pageNumber);this.renderTasks.push(d); +var g=new Oa(d),f=this;this.displayReadyPromise.then(function(a){f.pendingDestroy?c():(b.time("Rendering"),d.initalizeGraphics(a),d.operatorListChanged())},function(a){c(a)});return g},getTextContent:function(){var a=new PDFJS.LegacyPromise;this.transport.messageHandler.send("GetTextContent",{pageIndex:this.pageNumber-1},function(c){a.resolve(c)});return a},getOperationList:function(){var a=new PDFJS.LegacyPromise;a.resolve({dependencyFontsID:null,operatorList:null});return a},destroy:function(){this.pendingDestroy= +!0;this._tryDestroy()},_tryDestroy:function(){this.pendingDestroy&&0===this.renderTasks.length&&!this.receivingOperatorList&&(delete this.operatorList,delete this.displayReadyPromise,this.objs.clear(),this.pendingDestroy=!1)},_startRenderPage:function(a){this.displayReadyPromise.resolve(a)},_renderPageChunk:function(a){for(var c=0,b=a.length;c\\376\\377([^<]+)/g,function(a, +b){for(var c=b.replace(/\\([0-3])([0-7])([0-7])/g,function(a,b,c,d){return String.fromCharCode(64*b+8*c+1*d)}),f="",h=0;h"+f})}function a(a){"string"===typeof a?(a=b(a),a=(new DOMParser).parseFromString(a,"application/xml")):a instanceof Document||m("Metadata: Invalid metadata object");this.metaDocument=a;this.metadata={};this.parse()}a.prototype={parse:function(){var a=this.metaDocument.documentElement; +if("rdf:rdf"!==a.nodeName.toLowerCase())for(a=a.firstChild;a&&"rdf:rdf"!==a.nodeName.toLowerCase();)a=a.nextSibling;var b=a?a.nodeName.toLowerCase():null;if(a&&"rdf:rdf"===b&&a.hasChildNodes()){var a=a.childNodes,d,g,f,h,k,l;f=0;for(k=a.length;f>=1}a.putImageData(e,0,0)}var e= +["butt","round","square"],d=["miter","round","bevel"],g={},f={};b.prototype={beginDrawing:function(a,b){var c=this.ctx.canvas.width,d=this.ctx.canvas.height;b?this.ctx.clearRect(0,0,c,d):(this.ctx.mozOpaque=!0,this.ctx.save(),this.ctx.fillStyle="rgb(255, 255, 255)",this.ctx.fillRect(0,0,c,d),this.ctx.restore());c=a.transform;this.baseTransform=c.slice();this.ctx.save();this.ctx.transform.apply(this.ctx,c);this.textLayer&&this.textLayer.beginLayout();this.imageLayer&&this.imageLayer.beginLayout()}, +executeOperatorList:function(a,b,c,d){var e=a.argsArray;a=a.fnArray;b=b||0;var f=e.length;if(f==b)return b;for(var g=Date.now()+15,h=this.commonObjs,m=this.objs,q,u=Promise.resolve();;){if(d&&b===d.nextBreakPoint)return d.breakIt(b,c),b;q=a[b];if(q!==y.dependency)this[q].apply(this,e[b]);else{q=e[b];for(var r=0,t=q.length;rg)return u.then(c), +b}},endDrawing:function(){this.ctx.restore();J.clear();this.textLayer&&this.textLayer.endLayout();this.imageLayer&&this.imageLayer.endLayout()},setLineWidth:function(a){this.current.lineWidth=a;this.ctx.lineWidth=a},setLineCap:function(a){this.ctx.lineCap=e[a]},setLineJoin:function(a){this.ctx.lineJoin=d[a]},setMiterLimit:function(a){this.ctx.miterLimit=a},setDash:function(a,b){var c=this.ctx;"setLineDash"in c?(c.setLineDash(a),c.lineDashOffset=b):(c.mozDash=a,c.mozDashOffset=b)},setRenderingIntent:function(a){}, +setFlatness:function(a){},setGState:function(a){for(var b=0,c=a.length;bb?(b=-b,d.fontDirection=-1):d.fontDirection=1;this.current.font=c;this.current.fontSize=b;if(!c.coded){var d=c.black?c.bold?"bolder":"bold":c.bold?"bold":"normal",e=c.italic?"italic":"normal",c='"'+(c.loadedName||"sans-serif")+ +'", '+c.fallbackName,f=16<=b?b:16;this.current.fontSizeScale=16!=f?1:b/16;this.ctx.font=e+" "+d+" "+f+"px "+c}},setTextRenderingMode:function(a){this.current.textRenderingMode=a},setTextRise:function(a){this.current.textRise=a},moveText:function(a,b){this.current.x=this.current.lineX+=a;this.current.y=this.current.lineY+=b},setLeadingMoveText:function(a,b){this.setLeading(-b);this.moveText(a,b)},setTextMatrix:function(a,b,c,d,e,f){this.current.textMatrix=[a,b,c,d,e,f];this.current.x=this.current.lineX= +0;this.current.y=this.current.lineY=0},nextLine:function(){this.moveText(0,this.current.leading)},applyTextTransforms:function(){var a=this.ctx,b=this.current;a.transform.apply(a,b.textMatrix);a.translate(b.x,b.y+b.textRise);0a[c]){b=!0;break}return U(this,"isFontSubpixelAAEnabled", +b)},showText:function(a,b){var c=this.ctx,d=this.current,e=d.font,f=d.fontSize,g=d.fontSizeScale,h=d.charSpacing,m=d.wordSpacing,r=d.textHScale*d.fontDirection,u=d.fontMatrix||ba,M=a.length,t,x=this.textLayer&&!b?!0:!1,v=0,D=e.vertical,K=e.defaultVMetrics;if(e.coded){c.save();c.transform.apply(c,d.textMatrix);c.translate(d.x,d.y);c.scale(r,1);x&&(this.save(),c.scale(1,-1),t=this.createTextGeometry(),this.restore());for(var I=0;I=d&&1E3>=e?sa({data:a.data,width:d,height:e}):null);f&&f.compiled?f.compiled(b):(b=J.getCanvas("maskCanvas",d,e),f=b.context,f.save(),c(f,a),f.globalCompositeOperation="source-in",a=this.current.fillColor,f.fillStyle=a&&a.hasOwnProperty("type")&&"Pattern"===a.type?a.getPattern(f,this):a,f.fillRect(0,0,d,e),f.restore(),this.paintInlineImageXObject(b.canvas))}, +paintImageMaskXObjectGroup:function(a){for(var b=this.ctx,d=0,e=a.length;d>24&255)+String.fromCharCode(p>>16&255)+String.fromCharCode(p>>8&255)+String.fromCharCode(p&255));f="url(data:font/opentype;base64,"+btoa(m)+");";Q.insertRule('@font-face { font-family:"'+b+'";src:'+f+"}");p=[];f=0;for(h=a.length;f= INFOS) { - log('Info: ' + msg); - PDFJS.LogManager.notify('info', msg); + if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.infos) { + console.log('Info: ' + msg); } } -// Non-fatal warnings that should trigger the fallback UI. +// Non-fatal warnings. function warn(msg) { - if (verbosity >= WARNINGS) { - log('Warning: ' + msg); - PDFJS.LogManager.notify('warn', msg); + if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) { + console.log('Warning: ' + msg); } } @@ -115,22 +198,17 @@ function error(msg) { if (arguments.length > 1) { var logArguments = ['Error:']; logArguments.push.apply(logArguments, arguments); - log.apply(null, logArguments); + console.log.apply(console, logArguments); // Join the arguments into a single string for the lines below. msg = [].join.call(arguments, ' '); } else { - log('Error: ' + msg); + console.log('Error: ' + msg); } - log(backtrace()); - PDFJS.LogManager.notify('error', msg); + console.log(backtrace()); + UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown); throw new Error(msg); } -// Missing features that should trigger the fallback UI. -function TODO(what) { - warn('TODO: ' + what); -} - function backtrace() { try { throw new Error(); @@ -144,6 +222,31 @@ function assert(cond, msg) { error(msg); } +var UNSUPPORTED_FEATURES = PDFJS.UNSUPPORTED_FEATURES = { + unknown: 'unknown', + forms: 'forms', + javaScript: 'javaScript', + smask: 'smask', + shadingPattern: 'shadingPattern', + font: 'font' +}; + +var UnsupportedManager = PDFJS.UnsupportedManager = + (function UnsupportedManagerClosure() { + var listeners = []; + return { + listen: function (cb) { + listeners.push(cb); + }, + notify: function (featureId) { + warn('Unsupported feature "' + featureId + '"'); + for (var i = 0, ii = listeners.length; i < ii; i++) { + listeners[i](featureId); + } + } + }; +})(); + // Combines two URLs. The baseUrl shall be absolute URL. If the url is an // absolute URL, it will be returned as is. function combineUrl(baseUrl, url) { @@ -197,22 +300,6 @@ function assertWellFormed(cond, msg) { error(msg); } -var LogManager = PDFJS.LogManager = (function LogManagerClosure() { - var loggers = []; - return { - addLogger: function logManager_addLogger(logger) { - loggers.push(logger); - }, - notify: function(type, message) { - for (var i = 0, ii = loggers.length; i < ii; i++) { - var logger = loggers[i]; - if (logger[type]) - logger[type](message); - } - } - }; -})(); - function shadow(obj, prop, value) { Object.defineProperty(obj, prop, { value: value, enumerable: true, @@ -292,7 +379,7 @@ var MissingDataException = (function MissingDataExceptionClosure() { function MissingDataException(begin, end) { this.begin = begin; this.end = end; - this.message = 'Missing data [begin, end)'; + this.message = 'Missing data [' + begin + ', ' + end + ')'; } MissingDataException.prototype = new Error(); @@ -742,6 +829,24 @@ function isPDFFunction(v) { } /** + * Legacy support for PDFJS Promise implementation. + * TODO remove eventually + */ +var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() { + return function LegacyPromise() { + var resolve, reject; + var promise = new Promise(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; + }); + promise.resolve = resolve; + promise.reject = reject; + return promise; + }; +})(); + +/** + * Polyfill for Promises: * The following promise implementation tries to generally implment the * Promise/A+ spec. Some notable differences from other promise libaries are: * - There currently isn't a seperate deferred and promise object. @@ -750,7 +855,39 @@ function isPDFFunction(v) { * Based off of the work in: * https://bugzilla.mozilla.org/show_bug.cgi?id=810490 */ -var Promise = PDFJS.Promise = (function PromiseClosure() { +(function PromiseClosure() { + if (globalScope.Promise) { + // Promises existing in the DOM/Worker, checking presence of all/resolve + if (typeof globalScope.Promise.all !== 'function') { + globalScope.Promise.all = function (iterable) { + var count = 0, results = [], resolve, reject; + var promise = new globalScope.Promise(function (resolve_, reject_) { + resolve = resolve_; + reject = reject_; + }); + iterable.forEach(function (p, i) { + count++; + p.then(function (result) { + results[i] = result; + count--; + if (count === 0) { + resolve(results); + } + }, reject); + }); + if (count === 0) { + resolve(results); + } + return promise; + }; + } + if (typeof globalScope.Promise.resolve !== 'function') { + globalScope.Promise.resolve = function (x) { + return new globalScope.Promise(function (resolve) { resolve(x); }); + }; + } + return; + } var STATUS_PENDING = 0; var STATUS_RESOLVED = 1; var STATUS_REJECTED = 2; @@ -783,6 +920,8 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { }, runHandlers: function runHandlers() { + var RUN_TIMEOUT = 1; // ms + var timeoutAt = Date.now() + RUN_TIMEOUT; while (this.handlers.length > 0) { var handler = this.handlers.shift(); @@ -808,6 +947,14 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { } handler.nextPromise._updateStatus(nextStatus, nextValue); + if (Date.now() >= timeoutAt) { + break; + } + } + + if (this.handlers.length > 0) { + setTimeout(this.runHandlers.bind(this), 0); + return; } this.running = false; @@ -858,22 +1005,27 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { } }; - function Promise() { + function Promise(resolver) { this._status = STATUS_PENDING; this._handlers = []; + resolver.call(this, this._resolve.bind(this), this._reject.bind(this)); } /** * Builds a promise that is resolved when all the passed in promises are * resolved. - * @param {Promise[]} promises Array of promises to wait for. + * @param {array} array of data and/or promises to wait for. * @return {Promise} New dependant promise. */ Promise.all = function Promise_all(promises) { - var deferred = new Promise(); + var resolveAll, rejectAll; + var deferred = new Promise(function (resolve, reject) { + resolveAll = resolve; + rejectAll = reject; + }); var unresolved = promises.length; var results = []; if (unresolved === 0) { - deferred.resolve(results); + resolveAll(results); return deferred; } function reject(reason) { @@ -881,11 +1033,11 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { return; } results = []; - deferred.reject(reason); + rejectAll(reason); } for (var i = 0, ii = promises.length; i < ii; ++i) { var promise = promises[i]; - promise.then((function(i) { + var resolve = (function(i) { return function(value) { if (deferred._status === STATUS_REJECTED) { return; @@ -893,13 +1045,34 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { results[i] = value; unresolved--; if (unresolved === 0) - deferred.resolve(results); + resolveAll(results); }; - })(i), reject); + })(i); + if (Promise.isPromise(promise)) { + promise.then(resolve, reject); + } else { + resolve(promise); + } } return deferred; }; + /** + * Checks if the value is likely a promise (has a 'then' function). + * @return {boolean} true if x is thenable + */ + Promise.isPromise = function Promise_isPromise(value) { + return value && typeof value.then === 'function'; + }; + /** + * Creates resolved promise + * @param x resolve value + * @returns {Promise} + */ + Promise.resolve = function Promise_resolve(x) { + return new Promise(function (resolve) { resolve(x); }); + }; + Promise.prototype = { _status: null, _value: null, @@ -913,7 +1086,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { } if (status == STATUS_RESOLVED && - value && typeof(value.then) === 'function') { + Promise.isPromise(value)) { value.then(this._updateStatus.bind(this, STATUS_RESOLVED), this._updateStatus.bind(this, STATUS_REJECTED)); return; @@ -930,24 +1103,19 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { HandlerManager.scheduleHandlers(this); }, - get isResolved() { - return this._status === STATUS_RESOLVED; - }, - - get isRejected() { - return this._status === STATUS_REJECTED; - }, - - resolve: function Promise_resolve(value) { + _resolve: function Promise_resolve(value) { this._updateStatus(STATUS_RESOLVED, value); }, - reject: function Promise_reject(reason) { + _reject: function Promise_reject(reason) { this._updateStatus(STATUS_REJECTED, reason); }, then: function Promise_then(onResolve, onReject) { - var nextPromise = new Promise(); + var nextPromise = new Promise(function (resolve, reject) { + this.resolve = reject; + this.reject = reject; + }); this._handlers.push({ thisPromise: this, onResolve: onResolve, @@ -959,7 +1127,7 @@ var Promise = PDFJS.Promise = (function PromiseClosure() { } }; - return Promise; + globalScope.Promise = Promise; })(); var StatTimer = (function StatTimerClosure() { @@ -1016,7 +1184,7 @@ var StatTimer = (function StatTimerClosure() { })(); PDFJS.createBlob = function createBlob(data, contentType) { - if (typeof Blob === 'function') + if (typeof Blob !== 'undefined') return new Blob([data], { type: contentType }); // Blob builder is deprecated in FF14 and removed in FF18. var bb = new MozBlobBuilder(); @@ -1024,29 +1192,48 @@ PDFJS.createBlob = function createBlob(data, contentType) { return bb.getBlob(contentType); }; +PDFJS.createObjectURL = (function createObjectURLClosure() { + // Blob/createObjectURL is not available, falling back to data schema. + var digits = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + + return function createObjectURL(data, contentType) { + if (!PDFJS.disableCreateObjectURL && + typeof URL !== 'undefined' && URL.createObjectURL) { + var blob = PDFJS.createBlob(data, contentType); + return URL.createObjectURL(blob); + } + + var buffer = 'data:' + contentType + ';base64,'; + for (var i = 0, ii = data.length; i < ii; i += 3) { + var b1 = data[i] & 0xFF; + var b2 = data[i + 1] & 0xFF; + var b3 = data[i + 2] & 0xFF; + var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4); + var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64; + var d4 = i + 2 < ii ? (b3 & 0x3F) : 64; + buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4]; + } + return buffer; + }; +})(); + function MessageHandler(name, comObj) { this.name = name; this.comObj = comObj; this.callbackIndex = 1; + this.postMessageTransfers = true; var callbacks = this.callbacks = {}; var ah = this.actionHandler = {}; ah['console_log'] = [function ahConsoleLog(data) { - log.apply(null, data); + console.log.apply(console, data); }]; - // If there's no console available, console_error in the - // action handler will do nothing. - if ('console' in globalScope) { - ah['console_error'] = [function ahConsoleError(data) { - globalScope['console'].error.apply(null, data); - }]; - } else { - ah['console_error'] = [function ahConsoleError(data) { - log.apply(null, data); - }]; - } - ah['_warn'] = [function ah_Warn(data) { - warn(data); + ah['console_error'] = [function ahConsoleError(data) { + console.error.apply(console, data); + }]; + ah['_unsupported_feature'] = [function ah_unsupportedFeature(data) { + UnsupportedManager.notify(data); }]; comObj.onmessage = function messageHandlerComObjOnMessage(event) { @@ -1063,7 +1250,12 @@ function MessageHandler(name, comObj) { } else if (data.action in ah) { var action = ah[data.action]; if (data.callbackId) { - var promise = new Promise(); + var deferred = {}; + var promise = new Promise(function (resolve, reject) { + deferred.resolve = resolve; + deferred.reject = reject; + }); + deferred.promise = promise; promise.then(function(resolvedData) { comObj.postMessage({ isReply: true, @@ -1071,7 +1263,7 @@ function MessageHandler(name, comObj) { data: resolvedData }); }); - action[0].call(action[1], data.data, promise); + action[0].call(action[1], data.data, deferred); } else { action[0].call(action[1], data.data); } @@ -1094,8 +1286,9 @@ MessageHandler.prototype = { * @param {String} actionName Action to call. * @param {JSON} data JSON data to send. * @param {function} [callback] Optional callback that will handle a reply. + * @param {Array} [transfers] Optional list of transfers/ArrayBuffers */ - send: function messageHandlerSend(actionName, data, callback) { + send: function messageHandlerSend(actionName, data, callback, transfers) { var message = { action: actionName, data: data @@ -1105,19 +1298,889 @@ MessageHandler.prototype = { this.callbacks[callbackId] = callback; message.callbackId = callbackId; } - this.comObj.postMessage(message); + if (transfers && this.postMessageTransfers) { + this.comObj.postMessage(message, transfers); + } else { + this.comObj.postMessage(message); + } } }; -function loadJpegStream(id, imageData, objs) { +function loadJpegStream(id, imageUrl, objs) { var img = new Image(); img.onload = (function loadJpegStream_onloadClosure() { objs.resolve(id, img); }); - img.src = 'data:image/jpeg;base64,' + window.btoa(imageData); + img.src = imageUrl; } +var ColorSpace = (function ColorSpaceClosure() { + // Constructor should define this.numComps, this.defaultColor, this.name + function ColorSpace() { + error('should not call ColorSpace constructor'); + } + + ColorSpace.prototype = { + /** + * Converts the color value to the RGB color. The color components are + * located in the src array starting from the srcOffset. Returns the array + * of the rgb components, each value ranging from [0,255]. + */ + getRgb: function ColorSpace_getRgb(src, srcOffset) { + error('Should not call ColorSpace.getRgb'); + }, + /** + * Converts the color value to the RGB color, similar to the getRgb method. + * The result placed into the dest array starting from the destOffset. + */ + getRgbItem: function ColorSpace_getRgb(src, srcOffset, dest, destOffset) { + error('Should not call ColorSpace.getRgbItem'); + }, + /** + * Converts the specified number of the color values to the RGB colors. + * The colors are located in the src array starting from the srcOffset. + * The result is placed into the dest array starting from the destOffset. + * The src array items shall be in [0,2^bits) range, the dest array items + * will be in [0,255] range. + */ + getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + error('Should not call ColorSpace.getRgbBuffer'); + }, + /** + * Determines amount of the bytes is required to store the reslut of the + * conversion that done by the getRgbBuffer method. + */ + getOutputLength: function ColorSpace_getOutputLength(inputLength) { + error('Should not call ColorSpace.getOutputLength'); + }, + /** + * Returns true if source data will be equal the result/output data. + */ + isPassthrough: function ColorSpace_isPassthrough(bits) { + return false; + }, + /** + * Creates the output buffer and converts the specified number of the color + * values to the RGB colors, similar to the getRgbBuffer. + */ + createRgbBuffer: function ColorSpace_createRgbBuffer(src, srcOffset, + count, bits) { + if (this.isPassthrough(bits)) { + return src.subarray(srcOffset); + } + var dest = new Uint8Array(count * 3); + var numComponentColors = 1 << bits; + // Optimization: create a color map when there is just one component and + // we are converting more colors than the size of the color map. We + // don't build the map if the colorspace is gray or rgb since those + // methods are faster than building a map. This mainly offers big speed + // ups for indexed and alternate colorspaces. + if (this.numComps === 1 && count > numComponentColors && + this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') { + // TODO it may be worth while to cache the color map. While running + // testing I never hit a cache so I will leave that out for now (perhaps + // we are reparsing colorspaces too much?). + var allColors = bits <= 8 ? new Uint8Array(numComponentColors) : + new Uint16Array(numComponentColors); + for (var i = 0; i < numComponentColors; i++) { + allColors[i] = i; + } + var colorMap = new Uint8Array(numComponentColors * 3); + this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bits); + + var destOffset = 0; + for (var i = 0; i < count; ++i) { + var key = src[srcOffset++] * 3; + dest[destOffset++] = colorMap[key]; + dest[destOffset++] = colorMap[key + 1]; + dest[destOffset++] = colorMap[key + 2]; + } + return dest; + } + this.getRgbBuffer(src, srcOffset, count, dest, 0, bits); + return dest; + }, + /** + * True if the colorspace has components in the default range of [0, 1]. + * This should be true for all colorspaces except for lab color spaces + * which are [0,100], [-128, 127], [-128, 127]. + */ + usesZeroToOneRange: true + }; + + ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { + var IR = ColorSpace.parseToIR(cs, xref, res); + if (IR instanceof AlternateCS) + return IR; + + return ColorSpace.fromIR(IR); + }; + + ColorSpace.fromIR = function ColorSpace_fromIR(IR) { + var name = isArray(IR) ? IR[0] : IR; + + switch (name) { + case 'DeviceGrayCS': + return this.singletons.gray; + case 'DeviceRgbCS': + return this.singletons.rgb; + case 'DeviceCmykCS': + return this.singletons.cmyk; + case 'CalGrayCS': + var whitePoint = IR[1].WhitePoint; + var blackPoint = IR[1].BlackPoint; + var gamma = IR[1].Gamma; + return new CalGrayCS(whitePoint, blackPoint, gamma); + case 'PatternCS': + var basePatternCS = IR[1]; + if (basePatternCS) + basePatternCS = ColorSpace.fromIR(basePatternCS); + return new PatternCS(basePatternCS); + case 'IndexedCS': + var baseIndexedCS = IR[1]; + var hiVal = IR[2]; + var lookup = IR[3]; + return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); + case 'AlternateCS': + var numComps = IR[1]; + var alt = IR[2]; + var tintFnIR = IR[3]; + + return new AlternateCS(numComps, ColorSpace.fromIR(alt), + PDFFunction.fromIR(tintFnIR)); + case 'LabCS': + var whitePoint = IR[1].WhitePoint; + var blackPoint = IR[1].BlackPoint; + var range = IR[1].Range; + return new LabCS(whitePoint, blackPoint, range); + default: + error('Unkown name ' + name); + } + return null; + }; + + ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) { + if (isName(cs)) { + var colorSpaces = res.get('ColorSpace'); + if (isDict(colorSpaces)) { + var refcs = colorSpaces.get(cs.name); + if (refcs) + cs = refcs; + } + } + + cs = xref.fetchIfRef(cs); + var mode; + + if (isName(cs)) { + mode = cs.name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return 'DeviceGrayCS'; + case 'DeviceRGB': + case 'RGB': + return 'DeviceRgbCS'; + case 'DeviceCMYK': + case 'CMYK': + return 'DeviceCmykCS'; + case 'Pattern': + return ['PatternCS', null]; + default: + error('unrecognized colorspace ' + mode); + } + } else if (isArray(cs)) { + mode = cs[0].name; + this.mode = mode; + + switch (mode) { + case 'DeviceGray': + case 'G': + return 'DeviceGrayCS'; + case 'DeviceRGB': + case 'RGB': + return 'DeviceRgbCS'; + case 'DeviceCMYK': + case 'CMYK': + return 'DeviceCmykCS'; + case 'CalGray': + var params = cs[1].getAll(); + return ['CalGrayCS', params]; + case 'CalRGB': + return 'DeviceRgbCS'; + case 'ICCBased': + var stream = xref.fetchIfRef(cs[1]); + var dict = stream.dict; + var numComps = dict.get('N'); + if (numComps == 1) + return 'DeviceGrayCS'; + if (numComps == 3) + return 'DeviceRgbCS'; + if (numComps == 4) + return 'DeviceCmykCS'; + break; + case 'Pattern': + var basePatternCS = cs[1]; + if (basePatternCS) + basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); + return ['PatternCS', basePatternCS]; + case 'Indexed': + case 'I': + var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); + var hiVal = cs[2] + 1; + var lookup = xref.fetchIfRef(cs[3]); + if (isStream(lookup)) { + lookup = lookup.getBytes(); + } + return ['IndexedCS', baseIndexedCS, hiVal, lookup]; + case 'Separation': + case 'DeviceN': + var name = cs[1]; + var numComps = 1; + if (isName(name)) + numComps = 1; + else if (isArray(name)) + numComps = name.length; + var alt = ColorSpace.parseToIR(cs[2], xref, res); + var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); + return ['AlternateCS', numComps, alt, tintFnIR]; + case 'Lab': + var params = cs[1].getAll(); + return ['LabCS', params]; + default: + error('unimplemented color space object "' + mode + '"'); + } + } else { + error('unrecognized color space object: "' + cs + '"'); + } + return null; + }; + /** + * Checks if a decode map matches the default decode map for a color space. + * This handles the general decode maps where there are two values per + * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. + * This does not handle Lab, Indexed, or Pattern decode maps since they are + * slightly different. + * @param {Array} decode Decode map (usually from an image). + * @param {Number} n Number of components the color space has. + */ + ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { + if (!decode) + return true; + + if (n * 2 !== decode.length) { + warn('The decode map is not the correct length'); + return true; + } + for (var i = 0, ii = decode.length; i < ii; i += 2) { + if (decode[i] !== 0 || decode[i + 1] != 1) + return false; + } + return true; + }; + + ColorSpace.singletons = { + get gray() { + return shadow(this, 'gray', new DeviceGrayCS()); + }, + get rgb() { + return shadow(this, 'rgb', new DeviceRgbCS()); + }, + get cmyk() { + return shadow(this, 'cmyk', new DeviceCmykCS()); + } + }; + + return ColorSpace; +})(); + +/** + * Alternate color space handles both Separation and DeviceN color spaces. A + * Separation color space is actually just a DeviceN with one color component. + * Both color spaces use a tinting function to convert colors to a base color + * space. + */ +var AlternateCS = (function AlternateCSClosure() { + function AlternateCS(numComps, base, tintFn) { + this.name = 'Alternate'; + this.numComps = numComps; + this.defaultColor = new Float32Array(numComps); + for (var i = 0; i < numComps; ++i) { + this.defaultColor[i] = 1; + } + this.base = base; + this.tintFn = tintFn; + } + + AlternateCS.prototype = { + getRgb: function AlternateCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + }, + getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var baseNumComps = this.base.numComps; + var input = 'subarray' in src ? + src.subarray(srcOffset, srcOffset + this.numComps) : + Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); + var tinted = this.tintFn(input); + this.base.getRgbItem(tinted, 0, dest, destOffset); + }, + getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var tintFn = this.tintFn; + var base = this.base; + var scale = 1 / ((1 << bits) - 1); + var baseNumComps = base.numComps; + var usesZeroToOneRange = base.usesZeroToOneRange; + var isPassthrough = base.isPassthrough(8) || !usesZeroToOneRange; + var pos = isPassthrough ? destOffset : 0; + var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count); + var numComps = this.numComps; + + var scaled = new Float32Array(numComps); + for (var i = 0; i < count; i++) { + for (var j = 0; j < numComps; j++) { + scaled[j] = src[srcOffset++] * scale; + } + var tinted = tintFn(scaled); + if (usesZeroToOneRange) { + for (var j = 0; j < baseNumComps; j++) { + baseBuf[pos++] = tinted[j] * 255; + } + } else { + base.getRgbItem(tinted, 0, baseBuf, pos); + pos += baseNumComps; + } + } + if (!isPassthrough) { + base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8); + } + }, + getOutputLength: function AlternateCS_getOutputLength(inputLength) { + return this.base.getOutputLength(inputLength * + this.base.numComps / this.numComps); + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + + return AlternateCS; +})(); + +var PatternCS = (function PatternCSClosure() { + function PatternCS(baseCS) { + this.name = 'Pattern'; + this.base = baseCS; + } + PatternCS.prototype = {}; + + return PatternCS; +})(); + +var IndexedCS = (function IndexedCSClosure() { + function IndexedCS(base, highVal, lookup) { + this.name = 'Indexed'; + this.numComps = 1; + this.defaultColor = new Uint8Array([0]); + this.base = base; + this.highVal = highVal; + + var baseNumComps = base.numComps; + var length = baseNumComps * highVal; + var lookupArray; + + if (isStream(lookup)) { + lookupArray = new Uint8Array(length); + var bytes = lookup.getBytes(length); + lookupArray.set(bytes); + } else if (isString(lookup)) { + lookupArray = new Uint8Array(length); + for (var i = 0; i < length; ++i) + lookupArray[i] = lookup.charCodeAt(i); + } else if (lookup instanceof Uint8Array || lookup instanceof Array) { + lookupArray = lookup; + } else { + error('Unrecognized lookup table: ' + lookup); + } + this.lookup = lookupArray; + } + + IndexedCS.prototype = { + getRgb: function IndexedCS_getRgb(src, srcOffset) { + var numComps = this.base.numComps; + var start = src[srcOffset] * numComps; + return this.base.getRgb(this.lookup, start); + }, + getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var numComps = this.base.numComps; + var start = src[srcOffset] * numComps; + this.base.getRgbItem(this.lookup, start, dest, destOffset); + }, + getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset) { + var base = this.base; + var numComps = base.numComps; + var outputDelta = base.getOutputLength(numComps); + var lookup = this.lookup; + + for (var i = 0; i < count; ++i) { + var lookupPos = src[srcOffset++] * numComps; + base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8); + destOffset += outputDelta; + } + }, + getOutputLength: function IndexedCS_getOutputLength(inputLength) { + return this.base.getOutputLength(inputLength * this.base.numComps); + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) { + // indexed color maps shouldn't be changed + return true; + }, + usesZeroToOneRange: true + }; + return IndexedCS; +})(); + +var DeviceGrayCS = (function DeviceGrayCSClosure() { + function DeviceGrayCS() { + this.name = 'DeviceGray'; + this.numComps = 1; + this.defaultColor = new Float32Array([0]); + } + + DeviceGrayCS.prototype = { + getRgb: function DeviceGrayCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + }, + getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var c = (src[srcOffset] * 255) | 0; + c = c < 0 ? 0 : c > 255 ? 255 : c; + dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; + }, + getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var scale = 255 / ((1 << bits) - 1); + var j = srcOffset, q = destOffset; + for (var i = 0; i < count; ++i) { + var c = (scale * src[j++]) | 0; + dest[q++] = c; + dest[q++] = c; + dest[q++] = c; + } + }, + getOutputLength: function DeviceGrayCS_getOutputLength(inputLength) { + return inputLength * 3; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + return DeviceGrayCS; +})(); + +var DeviceRgbCS = (function DeviceRgbCSClosure() { + function DeviceRgbCS() { + this.name = 'DeviceRGB'; + this.numComps = 3; + this.defaultColor = new Float32Array([0, 0, 0]); + } + DeviceRgbCS.prototype = { + getRgb: function DeviceRgbCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + }, + getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, + dest, destOffset) { + var r = (src[srcOffset] * 255) | 0; + var g = (src[srcOffset + 1] * 255) | 0; + var b = (src[srcOffset + 2] * 255) | 0; + dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r; + dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g; + dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b; + }, + getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var length = count * 3; + if (bits == 8) { + dest.set(src.subarray(srcOffset, srcOffset + length), destOffset); + return; + } + var scale = 255 / ((1 << bits) - 1); + var j = srcOffset, q = destOffset; + for (var i = 0; i < length; ++i) { + dest[q++] = (scale * src[j++]) | 0; + } + }, + getOutputLength: function DeviceRgbCS_getOutputLength(inputLength) { + return inputLength; + }, + isPassthrough: function DeviceRgbCS_isPassthrough(bits) { + return bits == 8; + }, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + return DeviceRgbCS; +})(); + +var DeviceCmykCS = (function DeviceCmykCSClosure() { + // The coefficients below was found using numerical analysis: the method of + // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors, + // where color_value is the tabular value from the table of sampled RGB colors + // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding + // CMYK color conversion using the estimation below: + // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255 + function convertToRgb(src, srcOffset, srcScale, dest, destOffset) { + var c = src[srcOffset + 0] * srcScale; + var m = src[srcOffset + 1] * srcScale; + var y = src[srcOffset + 2] * srcScale; + var k = src[srcOffset + 3] * srcScale; + + var r = + c * (-4.387332384609988 * c + 54.48615194189176 * m + + 18.82290502165302 * y + 212.25662451639585 * k + + -285.2331026137004) + + m * (1.7149763477362134 * m - 5.6096736904047315 * y + + -17.873870861415444 * k - 5.497006427196366) + + y * (-2.5217340131683033 * y - 21.248923337353073 * k + + 17.5119270841813) + + k * (-21.86122147463605 * k - 189.48180835922747) + 255; + var g = + c * (8.841041422036149 * c + 60.118027045597366 * m + + 6.871425592049007 * y + 31.159100130055922 * k + + -79.2970844816548) + + m * (-15.310361306967817 * m + 17.575251261109482 * y + + 131.35250912493976 * k - 190.9453302588951) + + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + + k * (-20.737325471181034 * k - 187.80453709719578) + 255; + var b = + c * (0.8842522430003296 * c + 8.078677503112928 * m + + 30.89978309703729 * y - 0.23883238689178934 * k + + -14.183576799673286) + + m * (10.49593273432072 * m + 63.02378494754052 * y + + 50.606957656360734 * k - 112.23884253719248) + + y * (0.03296041114873217 * y + 115.60384449646641 * k + + -193.58209356861505) + + k * (-22.33816807309886 * k - 180.12613974708367) + 255; + + dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r; + dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; + dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; + } + + function DeviceCmykCS() { + this.name = 'DeviceCMYK'; + this.numComps = 4; + this.defaultColor = new Float32Array([0, 0, 0, 1]); + } + DeviceCmykCS.prototype = { + getRgb: function DeviceCmykCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + convertToRgb(src, srcOffset, 1, rgb, 0); + return rgb; + }, + getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, + dest, destOffset) { + convertToRgb(src, srcOffset, 1, dest, destOffset); + }, + getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var scale = 1 / ((1 << bits) - 1); + for (var i = 0; i < count; i++) { + convertToRgb(src, srcOffset, scale, dest, destOffset); + srcOffset += 4; + destOffset += 3; + } + }, + getOutputLength: function DeviceCmykCS_getOutputLength(inputLength) { + return (inputLength >> 2) * 3; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + + return DeviceCmykCS; +})(); + +// +// CalGrayCS: Based on "PDF Reference, Sixth Ed", p.245 +// +var CalGrayCS = (function CalGrayCSClosure() { + function CalGrayCS(whitePoint, blackPoint, gamma) { + this.name = 'CalGray'; + this.numComps = 1; + this.defaultColor = new Float32Array([0]); + + if (!whitePoint) { + error('WhitePoint missing - required for color space CalGray'); + } + blackPoint = blackPoint || [0, 0, 0]; + gamma = gamma || 1; + + // Translate arguments to spec variables. + this.XW = whitePoint[0]; + this.YW = whitePoint[1]; + this.ZW = whitePoint[2]; + + this.XB = blackPoint[0]; + this.YB = blackPoint[1]; + this.ZB = blackPoint[2]; + + this.G = gamma; + + // Validate variables as per spec. + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) { + error('Invalid WhitePoint components for ' + this.name + + ', no fallback available'); + } + + if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { + info('Invalid BlackPoint for ' + this.name + ', falling back to default'); + this.XB = this.YB = this.ZB = 0; + } + + if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) { + warn(this.name + ', BlackPoint: XB: ' + this.XB + ', YB: ' + this.YB + + ', ZB: ' + this.ZB + ', only default values are supported.'); + } + + if (this.G < 1) { + info('Invalid Gamma: ' + this.G + ' for ' + this.name + + ', falling back to default'); + this.G = 1; + } + } + + function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) { + // A represents a gray component of a calibrated gray space. + // A <---> AG in the spec + var A = src[srcOffset] * scale; + var AG = Math.pow(A, cs.G); + + // Computes intermediate variables M, L, N as per spec. + // Except if other than default BlackPoint values are used. + var M = cs.XW * AG; + var L = cs.YW * AG; + var N = cs.ZW * AG; + + // Decode XYZ, as per spec. + var X = M; + var Y = L; + var Z = N; + + // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4. + // This yields values in range [0, 100]. + var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0); + + // Convert values to rgb range [0, 255]. + dest[destOffset] = Lstar * 255 / 100; + dest[destOffset + 1] = Lstar * 255 / 100; + dest[destOffset + 2] = Lstar * 255 / 100; + } + + CalGrayCS.prototype = { + getRgb: function CalGrayCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + this.getRgbItem(src, srcOffset, rgb, 0); + return rgb; + }, + getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset, + dest, destOffset) { + convertToRgb(this, src, srcOffset, dest, destOffset, 1); + }, + getRgbBuffer: function CalGrayCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var scale = 1 / ((1 << bits) - 1); + + for (var i = 0; i < count; ++i) { + convertToRgb(this, src, srcOffset, dest, destOffset, scale); + srcOffset += 1; + destOffset += 3; + } + }, + getOutputLength: function CalGrayCS_getOutputLength(inputLength) { + return inputLength * 3; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + createRgbBuffer: ColorSpace.prototype.createRgbBuffer, + isDefaultDecode: function CalGrayCS_isDefaultDecode(decodeMap) { + return ColorSpace.isDefaultDecode(decodeMap, this.numComps); + }, + usesZeroToOneRange: true + }; + return CalGrayCS; +})(); + +// +// LabCS: Based on "PDF Reference, Sixth Ed", p.250 +// +var LabCS = (function LabCSClosure() { + function LabCS(whitePoint, blackPoint, range) { + this.name = 'Lab'; + this.numComps = 3; + this.defaultColor = new Float32Array([0, 0, 0]); + + if (!whitePoint) + error('WhitePoint missing - required for color space Lab'); + blackPoint = blackPoint || [0, 0, 0]; + range = range || [-100, 100, -100, 100]; + + // Translate args to spec variables + this.XW = whitePoint[0]; + this.YW = whitePoint[1]; + this.ZW = whitePoint[2]; + this.amin = range[0]; + this.amax = range[1]; + this.bmin = range[2]; + this.bmax = range[3]; + + // These are here just for completeness - the spec doesn't offer any + // formulas that use BlackPoint in Lab + this.XB = blackPoint[0]; + this.YB = blackPoint[1]; + this.ZB = blackPoint[2]; + + // Validate vars as per spec + if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) + error('Invalid WhitePoint components, no fallback available'); + + if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { + info('Invalid BlackPoint, falling back to default'); + this.XB = this.YB = this.ZB = 0; + } + + if (this.amin > this.amax || this.bmin > this.bmax) { + info('Invalid Range, falling back to defaults'); + this.amin = -100; + this.amax = 100; + this.bmin = -100; + this.bmax = 100; + } + } + + // Function g(x) from spec + function fn_g(x) { + if (x >= 6 / 29) + return x * x * x; + else + return (108 / 841) * (x - 4 / 29); + } + + function decode(value, high1, low2, high2) { + return low2 + (value) * (high2 - low2) / (high1); + } + + // If decoding is needed maxVal should be 2^bits per component - 1. + function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) { + // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] + // not the usual [0, 1]. If a command like setFillColor is used the src + // values will already be within the correct range. However, if we are + // converting an image we have to map the values to the correct range given + // above. + // Ls,as,bs <---> L*,a*,b* in the spec + var Ls = src[srcOffset]; + var as = src[srcOffset + 1]; + var bs = src[srcOffset + 2]; + if (maxVal !== false) { + Ls = decode(Ls, maxVal, 0, 100); + as = decode(as, maxVal, cs.amin, cs.amax); + bs = decode(bs, maxVal, cs.bmin, cs.bmax); + } + + // Adjust limits of 'as' and 'bs' + as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as; + bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs; + + // Computes intermediate variables X,Y,Z as per spec + var M = (Ls + 16) / 116; + var L = M + (as / 500); + var N = M - (bs / 200); + + var X = cs.XW * fn_g(L); + var Y = cs.YW * fn_g(M); + var Z = cs.ZW * fn_g(N); + + var r, g, b; + // Using different conversions for D50 and D65 white points, + // per http://www.color.org/srgb.pdf + if (cs.ZW < 1) { + // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249) + r = X * 3.1339 + Y * -1.6170 + Z * -0.4906; + g = X * -0.9785 + Y * 1.9160 + Z * 0.0333; + b = X * 0.0720 + Y * -0.2290 + Z * 1.4057; + } else { + // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888) + r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; + g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; + b = X * 0.0557 + Y * -0.2040 + Z * 1.0570; + } + // clamp color values to [0,1] range then convert to [0,255] range. + dest[destOffset] = Math.sqrt(r < 0 ? 0 : r > 1 ? 1 : r) * 255; + dest[destOffset + 1] = Math.sqrt(g < 0 ? 0 : g > 1 ? 1 : g) * 255; + dest[destOffset + 2] = Math.sqrt(b < 0 ? 0 : b > 1 ? 1 : b) * 255; + } + + LabCS.prototype = { + getRgb: function LabCS_getRgb(src, srcOffset) { + var rgb = new Uint8Array(3); + convertToRgb(this, src, srcOffset, false, rgb, 0); + return rgb; + }, + getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) { + convertToRgb(this, src, srcOffset, false, dest, destOffset); + }, + getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, + dest, destOffset, bits) { + var maxVal = (1 << bits) - 1; + for (var i = 0; i < count; i++) { + convertToRgb(this, src, srcOffset, maxVal, dest, destOffset); + srcOffset += 3; + destOffset += 3; + } + }, + getOutputLength: function LabCS_getOutputLength(inputLength) { + return inputLength; + }, + isPassthrough: ColorSpace.prototype.isPassthrough, + isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) { + // XXX: Decoding is handled with the lab conversion because of the strange + // ranges that are used. + return true; + }, + usesZeroToOneRange: false + }; + return LabCS; +})(); + + + var PatternType = { AXIAL: 2, RADIAL: 3 @@ -1153,7 +2216,7 @@ var Pattern = (function PatternClosure() { // Both radial and axial shadings are handled by RadialAxial shading. return new Shadings.RadialAxial(dict, matrix, xref, res); default: - TODO('Unsupported shading type: ' + type); + UnsupportedManager.notify(UNSUPPORTED_FEATURES.shadingPattern); return new Shadings.Dummy(); } }; @@ -1413,7 +2476,7 @@ var TilingPattern = (function TilingPatternClosure() { var commonObjs = this.commonObjs; var ctx = this.ctx; - TODO('TilingType: ' + tilingType); + info('TilingType: ' + tilingType); var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3]; @@ -1626,7 +2689,7 @@ var PDFFunction = (function PDFFunctionClosure() { if (order !== 1) { // No description how cubic spline interpolation works in PDF32000:2008 // As in poppler, ignoring order, linear interpolation may work as good - TODO('No support for cubic spline interpolation: ' + order); + info('No support for cubic spline interpolation: ' + order); } var encode = dict.get('Encode'); @@ -2475,6 +3538,29 @@ var Annotation = (function AnnotationClosure() { } else { var borderArray = dict.get('Border') || [0, 0, 1]; data.borderWidth = borderArray[2] || 0; + + // TODO: implement proper support for annotations with line dash patterns. + var dashArray = borderArray[3]; + if (dashArray && isArray(dashArray)) { + var dashArrayLength = dashArray.length; + if (dashArrayLength > 0) { + // According to the PDF specification: the elements in a dashArray + // shall be numbers that are nonnegative and not all equal to zero. + var isInvalid = false; + var numPositive = 0; + for (var i = 0; i < dashArrayLength; i++) { + if (!(+dashArray[i] >= 0)) { + isInvalid = true; + break; + } else if (dashArray[i] > 0) { + numPositive++; + } + } + if (isInvalid || numPositive === 0) { + data.borderWidth = 0; + } + } + } } this.appearance = getDefaultAppearance(dict); @@ -2519,7 +3605,7 @@ var Annotation = (function AnnotationClosure() { }, loadResources: function(keys) { - var promise = new Promise(); + var promise = new LegacyPromise(); this.appearance.dict.getAsync('Resources').then(function(resources) { if (!resources) { promise.resolve(); @@ -2538,7 +3624,7 @@ var Annotation = (function AnnotationClosure() { getOperatorList: function Annotation_getToOperatorList(evaluator) { - var promise = new Promise(); + var promise = new LegacyPromise(); if (!this.appearance) { promise.resolve(new OperatorList()); @@ -2566,9 +3652,9 @@ var Annotation = (function AnnotationClosure() { resourcesPromise.then(function(resources) { var opList = new OperatorList(); - opList.addOp('beginAnnotation', [data.rect, transform, matrix]); + opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); evaluator.getOperatorList(this.appearance, resources, opList); - opList.addOp('endAnnotation', []); + opList.addOp(OPS.endAnnotation, []); promise.resolve(opList); }.bind(this)); @@ -2644,7 +3730,7 @@ var Annotation = (function AnnotationClosure() { if (annotation.isViewable()) { return annotation; } else { - TODO('unimplemented annotation type: ' + subtype); + warn('unimplemented annotation type: ' + subtype); } }; @@ -2655,19 +3741,19 @@ var Annotation = (function AnnotationClosure() { annotationsReadyPromise.reject(e); } - var annotationsReadyPromise = new Promise(); + var annotationsReadyPromise = new LegacyPromise(); var annotationPromises = []; for (var i = 0, n = annotations.length; i < n; ++i) { annotationPromises.push(annotations[i].getOperatorList(partialEvaluator)); } Promise.all(annotationPromises).then(function(datas) { - opList.addOp('beginAnnotations', []); + opList.addOp(OPS.beginAnnotations, []); for (var i = 0, n = datas.length; i < n; ++i) { var annotOpList = datas[i]; opList.addOpList(annotOpList); } - opList.addOp('endAnnotations', []); + opList.addOp(OPS.endAnnotations, []); annotationsReadyPromise.resolve(); }, reject); @@ -2736,7 +3822,7 @@ var WidgetAnnotation = (function WidgetAnnotationClosure() { Util.inherit(WidgetAnnotation, Annotation, { isViewable: function WidgetAnnotation_isViewable() { if (this.data.fieldType === 'Sig') { - TODO('unimplemented annotation type: Widget signature'); + warn('unimplemented annotation type: Widget signature'); return false; } @@ -2817,7 +3903,7 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { return Annotation.prototype.getOperatorList.call(this, evaluator); } - var promise = new Promise(); + var promise = new LegacyPromise(); var opList = new OperatorList(); var data = this.data; @@ -2843,10 +3929,10 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { data.rgb = [0, 0, 0]; // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY! for (var i = 0, n = fnArray.length; i < n; ++i) { - var fnName = appearanceFnArray[i]; + var fnId = appearanceFnArray[i]; var args = appearanceArgsArray[i]; - if (fnName === 'setFont') { + if (fnId === OPS.setFont) { data.fontRefName = args[0]; var size = args[1]; if (size < 0) { @@ -2856,9 +3942,9 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() { data.fontDirection = 1; data.fontSize = size; } - } else if (fnName === 'setFillRGBColor') { + } else if (fnId === OPS.setFillRGBColor) { data.rgb = args; - } else if (fnName === 'setFillGray') { + } else if (fnId === OPS.setFillGray) { var rgbValue = args[0] * 255; data.rgb = [rgbValue, rgbValue, rgbValue]; } @@ -2894,7 +3980,7 @@ var TextAnnotation = (function TextAnnotationClosure() { Util.inherit(TextAnnotation, Annotation, { getOperatorList: function TextAnnotation_getOperatorList(evaluator) { - var promise = new Promise(); + var promise = new LegacyPromise(); promise.resolve(new OperatorList()); return promise; }, @@ -2994,7 +4080,7 @@ var LinkAnnotation = (function LinkAnnotationClosure() { if (action) { var linkType = action.get('S').name; if (linkType === 'URI') { - var url = action.get('URI'); + var url = addDefaultProtocolToUrl(action.get('URI')); // TODO: pdf spec mentions urls can be relative to a Base // entry in the dictionary. if (!isValidUrl(url, false)) { @@ -3021,7 +4107,7 @@ var LinkAnnotation = (function LinkAnnotationClosure() { } else if (linkType === 'Named') { data.action = action.get('N').name; } else { - TODO('unrecognized link type: ' + linkType); + warn('unrecognized link type: ' + linkType); } } else if (dict.has('Dest')) { // simple destination link @@ -3030,6 +4116,14 @@ var LinkAnnotation = (function LinkAnnotationClosure() { } } + // Lets URLs beginning with 'www.' default to using the 'http://' protocol. + function addDefaultProtocolToUrl(url) { + if (url.indexOf('www.') === 0) { + return ('http://' + url); + } + return url; + } + Util.inherit(LinkAnnotation, Annotation, { hasOperatorList: function LinkAnnotation_hasOperatorList() { return false; @@ -3078,6 +4172,7 @@ var NetworkManager = (function NetworkManagerClosure() { this.url = url; args = args || {}; this.httpHeaders = args.httpHeaders || {}; + this.withCredentials = args.withCredentials || false; this.getXhr = args.getXhr || function NetworkManager_getXhr() { return new XMLHttpRequest(); @@ -3126,6 +4221,7 @@ var NetworkManager = (function NetworkManagerClosure() { }; xhr.open('GET', this.url); + xhr.withCredentials = this.withCredentials; for (var property in this.httpHeaders) { var value = this.httpHeaders[property]; if (typeof value === 'undefined') { @@ -3278,6 +4374,7 @@ var ChunkedStream = (function ChunkedStreamClosure() { this.numChunksLoaded = 0; this.numChunks = Math.ceil(length / chunkSize); this.manager = manager; + this.initialDataLength = 0; } // required methods for a stream. if a particular stream does not @@ -3325,11 +4422,26 @@ var ChunkedStream = (function ChunkedStreamClosure() { } }, + onReceiveInitialData: function (data) { + this.bytes.set(data); + this.initialDataLength = data.length; + var endChunk = this.end === data.length ? + this.numChunks : Math.floor(data.length / this.chunkSize); + for (var i = 0; i < endChunk; i++) { + this.loadedChunks[i] = true; + ++this.numChunksLoaded; + } + }, + ensureRange: function ChunkedStream_ensureRange(begin, end) { if (begin >= end) { return; } + if (end <= this.initialDataLength) { + return; + } + var chunkSize = this.chunkSize; var beginChunk = Math.floor(begin / chunkSize); var endChunk = Math.floor((end - 1) / chunkSize) + 1; @@ -3470,7 +4582,8 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { }; this.networkManager = new NetworkManager(this.url, { getXhr: getXhr, - httpHeaders: args.httpHeaders + httpHeaders: args.httpHeaders, + withCredentials: args.withCredentials }); this.sendRequest = function ChunkedStreamManager_sendRequest(begin, end) { this.networkManager.requestRange(begin, end, { @@ -3486,11 +4599,26 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { this.requestsByChunk = {}; this.callbacksByRequest = {}; - this.loadedStream = new Promise(); + this.loadedStream = new LegacyPromise(); + if (args.initialData) { + this.setInitialData(args.initialData); + } } ChunkedStreamManager.prototype = { + setInitialData: function ChunkedStreamManager_setInitialData(data) { + this.stream.onReceiveInitialData(data); + if (this.stream.allChunksLoaded()) { + this.loadedStream.resolve(this.stream); + } else if (this.msgHandler) { + this.msgHandler.send('DocProgress', { + loaded: data.length, + total: this.length + }); + } + }, + onLoadedStream: function ChunkedStreamManager_getLoadedStream() { return this.loadedStream; }, @@ -3592,16 +4720,16 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { // chunks as possible groupChunks: function ChunkedStreamManager_groupChunks(chunks) { var groupedChunks = []; - var beginChunk; - var prevChunk; + var beginChunk = -1; + var prevChunk = -1; for (var i = 0; i < chunks.length; ++i) { var chunk = chunks[i]; - if (!beginChunk) { + if (beginChunk < 0) { beginChunk = chunk; } - if (prevChunk && prevChunk + 1 !== chunk) { + if (prevChunk >= 0 && prevChunk + 1 !== chunk) { groupedChunks.push({ beginChunk: beginChunk, endChunk: prevChunk + 1}); beginChunk = chunk; @@ -3747,6 +4875,10 @@ var BasePdfManager = (function BasePdfManagerClosure() { return this.pdfModel.getPage(pageIndex); }, + cleanup: function BasePdfManager_cleanup() { + return this.pdfModel.cleanup(); + }, + ensure: function BasePdfManager_ensure(obj, prop, args) { return new NotImplementedException(); }, @@ -3778,7 +4910,7 @@ var LocalPdfManager = (function LocalPdfManagerClosure() { function LocalPdfManager(data, password) { var stream = new Stream(data); this.pdfModel = new PDFDocument(this, stream, password); - this.loadedStream = new Promise(); + this.loadedStream = new LegacyPromise(); this.loadedStream.resolve(stream); } @@ -3787,7 +4919,7 @@ var LocalPdfManager = (function LocalPdfManagerClosure() { LocalPdfManager.prototype.ensure = function LocalPdfManager_ensure(obj, prop, args) { - var promise = new Promise(); + var promise = new LegacyPromise(); try { var value = obj[prop]; var result; @@ -3806,7 +4938,7 @@ var LocalPdfManager = (function LocalPdfManagerClosure() { LocalPdfManager.prototype.requestRange = function LocalPdfManager_requestRange(begin, end) { - var promise = new Promise(); + var promise = new LegacyPromise(); promise.resolve(); return promise; }; @@ -3839,8 +4971,10 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { var params = { msgHandler: msgHandler, httpHeaders: args.httpHeaders, + withCredentials: args.withCredentials, chunkedViewerLoading: args.chunkedViewerLoading, - disableAutoFetch: args.disableAutoFetch + disableAutoFetch: args.disableAutoFetch, + initialData: args.initialData }; this.streamManager = new ChunkedStreamManager(args.length, CHUNK_SIZE, args.url, params); @@ -3854,7 +4988,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { NetworkPdfManager.prototype.ensure = function NetworkPdfManager_ensure(obj, prop, args) { - var promise = new Promise(); + var promise = new LegacyPromise(); this.ensureHelper(promise, obj, prop, args); return promise; }; @@ -3885,7 +5019,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { NetworkPdfManager.prototype.requestRange = function NetworkPdfManager_requestRange(begin, end) { - var promise = new Promise(); + var promise = new LegacyPromise(); this.streamManager.requestRange(begin, end, function() { promise.resolve(); }); @@ -3914,12 +5048,13 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { var Page = (function PageClosure() { - function Page(pdfManager, xref, pageIndex, pageDict, ref) { + function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache) { this.pdfManager = pdfManager; this.pageIndex = pageIndex; this.pageDict = pageDict; this.xref = xref; this.ref = ref; + this.fontCache = fontCache; this.idCounters = { obj: 0 }; @@ -4011,7 +5146,7 @@ var Page = (function PageClosure() { // TODO: add async inheritPageProp and remove this. this.resourcesPromise = this.pdfManager.ensure(this, 'resources'); } - var promise = new Promise(); + var promise = new LegacyPromise(); this.resourcesPromise.then(function resourceSuccess() { var objectLoader = new ObjectLoader(this.resources.map, keys, @@ -4024,13 +5159,13 @@ var Page = (function PageClosure() { }, getOperatorList: function Page_getOperatorList(handler) { var self = this; - var promise = new Promise(); + var promise = new LegacyPromise(); function reject(e) { promise.reject(e); } - var pageListPromise = new Promise(); + var pageListPromise = new LegacyPromise(); var pdfManager = this.pdfManager; var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', @@ -4049,7 +5184,7 @@ var Page = (function PageClosure() { var partialEvaluator = new PartialEvaluator( pdfManager, this.xref, handler, this.pageIndex, 'p' + this.pageIndex + '_', - this.idCounters); + this.idCounters, this.fontCache); var dataPromises = Promise.all( [contentStreamPromise, resourcesPromise], reject); @@ -4073,7 +5208,6 @@ var Page = (function PageClosure() { var annotations = datas[1]; if (annotations.length === 0) { - PartialEvaluator.optimizeQueue(pageOpList); pageOpList.flush(true); promise.resolve(pageOpList); return; @@ -4082,7 +5216,6 @@ var Page = (function PageClosure() { var annotationsReadyPromise = Annotation.appendToOperatorList( annotations, pageOpList, pdfManager, partialEvaluator); annotationsReadyPromise.then(function () { - PartialEvaluator.optimizeQueue(pageOpList); pageOpList.flush(true); promise.resolve(pageOpList); }, reject); @@ -4098,7 +5231,7 @@ var Page = (function PageClosure() { var self = this; - var textContentPromise = new Promise(); + var textContentPromise = new LegacyPromise(); var pdfManager = this.pdfManager; var contentStreamPromise = pdfManager.ensure(this, 'getContentStream', @@ -4117,7 +5250,7 @@ var Page = (function PageClosure() { var partialEvaluator = new PartialEvaluator( pdfManager, self.xref, handler, self.pageIndex, 'p' + self.pageIndex + '_', - self.idCounters); + self.idCounters, self.fontCache); var bidiTexts = partialEvaluator.getTextContent(contentStream, self.resources); @@ -4370,33 +5503,27 @@ var PDFDocument = (function PDFDocumentClosure() { return shadow(this, 'documentInfo', docInfo); }, get fingerprint() { - var xref = this.xref, fileID; + var xref = this.xref, hash, fileID = ''; + if (xref.trailer.has('ID')) { - fileID = ''; - var id = xref.trailer.get('ID')[0]; - id.split('').forEach(function(el) { - fileID += Number(el.charCodeAt(0)).toString(16); - }); + hash = stringToBytes(xref.trailer.get('ID')[0]); } else { - // If we got no fileID, then we generate one, - // from the first 100 bytes of PDF - var data = this.stream.bytes.subarray(0, 100); - var hash = calculateMD5(data, 0, data.length); - fileID = ''; - for (var i = 0, length = hash.length; i < length; i++) { - fileID += Number(hash[i]).toString(16); - } + hash = calculateMD5(this.stream.bytes.subarray(0, 100), 0, 100); + } + + for (var i = 0, n = hash.length; i < n; i++) { + fileID += hash[i].toString(16); } return shadow(this, 'fingerprint', fileID); }, - traversePages: function PDFDocument_traversePages() { - this.catalog.traversePages(); - }, - getPage: function PDFDocument_getPage(pageIndex) { return this.catalog.getPage(pageIndex); + }, + + cleanup: function PDFDocument_cleanup() { + return this.catalog.cleanup(); } }; @@ -4479,7 +5606,7 @@ var Dict = (function DictClosure() { if (xref) { return xref.fetchIfRefAsync(value); } - promise = new Promise(); + promise = new LegacyPromise(); promise.resolve(value); return promise; } @@ -4488,7 +5615,7 @@ var Dict = (function DictClosure() { if (xref) { return xref.fetchIfRefAsync(value); } - promise = new Promise(); + promise = new LegacyPromise(); promise.resolve(value); return promise; } @@ -4496,7 +5623,7 @@ var Dict = (function DictClosure() { if (xref) { return xref.fetchIfRefAsync(value); } - promise = new Promise(); + promise = new LegacyPromise(); promise.resolve(value); return promise; }, @@ -4571,7 +5698,7 @@ var RefSet = (function RefSetClosure() { var RefSetCache = (function RefSetCacheClosure() { function RefSetCache() { - this.dict = {}; + this.dict = Object.create(null); } RefSetCache.prototype = { @@ -4585,6 +5712,16 @@ var RefSetCache = (function RefSetCacheClosure() { put: function RefSetCache_put(ref, obj) { this.dict['R' + ref.num + '.' + ref.gen] = obj; + }, + + forEach: function RefSetCache_forEach(fn, thisArg) { + for (var i in this.dict) { + fn.call(thisArg, this.dict[i]); + } + }, + + clear: function RefSetCache_clear() { + this.dict = Object.create(null); } }; @@ -4596,17 +5733,11 @@ var Catalog = (function CatalogClosure() { this.pdfManager = pdfManager; this.xref = xref; this.catDict = xref.getCatalogObj(); + this.fontCache = new RefSetCache(); assertWellFormed(isDict(this.catDict), 'catalog object is not a dictionary'); - // Stores state as we traverse the pages catalog so that we can resume - // parsing if an exception is thrown - this.traversePagesQueue = [{ - pagesDict: this.toplevelPagesDict, - posInKids: 0 - }]; this.pagePromises = []; - this.currPageIndex = 0; } Catalog.prototype = { @@ -4789,59 +5920,156 @@ var Catalog = (function CatalogClosure() { return shadow(this, 'javaScript', javaScript); }, + cleanup: function Catalog_cleanup() { + this.fontCache.forEach(function (font) { + delete font.sent; + delete font.translated; + }); + this.fontCache.clear(); + }, + getPage: function Catalog_getPage(pageIndex) { - if (pageIndex < 0 || pageIndex >= this.numPages || - (pageIndex|0) !== pageIndex) { - var pagePromise = new Promise(); - pagePromise.reject(new Error('Invalid page index')); - return pagePromise; - } if (!(pageIndex in this.pagePromises)) { - this.pagePromises[pageIndex] = new Promise(); + this.pagePromises[pageIndex] = this.getPageDict(pageIndex).then( + function (a) { + var dict = a[0]; + var ref = a[1]; + return new Page(this.pdfManager, this.xref, pageIndex, dict, ref, + this.fontCache); + }.bind(this) + ); } return this.pagePromises[pageIndex]; }, - // Traverses pages in DFS order so that pages are processed in increasing - // order - traversePages: function Catalog_traversePages() { - var queue = this.traversePagesQueue; - while (queue.length) { - var queueItem = queue[queue.length - 1]; - var pagesDict = queueItem.pagesDict; + getPageDict: function Catalog_getPageDict(pageIndex) { + var promise = new LegacyPromise(); + var nodesToVisit = [this.catDict.getRaw('Pages')]; + var currentPageIndex = 0; + var xref = this.xref; - var kids = pagesDict.get('Kids'); - assert(isArray(kids), 'page dictionary kids object is not an array'); - if (queueItem.posInKids >= kids.length) { - queue.pop(); - continue; - } - var kidRef = kids[queueItem.posInKids]; - assert(isRef(kidRef), 'page dictionary kid is not a reference'); + function next() { + while (nodesToVisit.length) { + var currentNode = nodesToVisit.pop(); - var kid = this.xref.fetch(kidRef); - if (isDict(kid, 'Page') || (isDict(kid) && !kid.has('Kids'))) { - var pageIndex = this.currPageIndex++; - var page = new Page(this.pdfManager, this.xref, pageIndex, kid, - kidRef); - if (!(pageIndex in this.pagePromises)) { - this.pagePromises[pageIndex] = new Promise(); + if (isRef(currentNode)) { + xref.fetchAsync(currentNode).then(function (obj) { + if ((isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids')))) { + if (pageIndex === currentPageIndex) { + promise.resolve([obj, currentNode]); + } else { + currentPageIndex++; + next(); + } + return; + } + nodesToVisit.push(obj); + next(); + }.bind(this), promise.reject.bind(promise)); + return; } - this.pagePromises[pageIndex].resolve(page); - } else { // must be a child page dictionary + // must be a child page dictionary assert( - isDict(kid), + isDict(currentNode), 'page dictionary kid reference points to wrong type of object' ); + var count = currentNode.get('Count'); + // Skip nodes where the page can't be. + if (currentPageIndex + count <= pageIndex) { + currentPageIndex += count; + continue; + } - queue.push({ - pagesDict: kid, - posInKids: 0 - }); + var kids = currentNode.get('Kids'); + assert(isArray(kids), 'page dictionary kids object is not an array'); + if (count === kids.length) { + // Nodes that don't have the page have been skipped and this is the + // bottom of the tree which means the page requested must be a + // descendant of this pages node. Ideally we would just resolve the + // promise with the page ref here, but there is the case where more + // pages nodes could link to single a page (see issue 3666 pdf). To + // handle this push it back on the queue so if it is a pages node it + // will be descended into. + nodesToVisit = [kids[pageIndex - currentPageIndex]]; + currentPageIndex = pageIndex; + continue; + } else { + for (var last = kids.length - 1; last >= 0; last--) { + nodesToVisit.push(kids[last]); + } + } } - ++queueItem.posInKids; + promise.reject('Page index ' + pageIndex + ' not found.'); } + next(); + return promise; + }, + + getPageIndex: function Catalog_getPageIndex(ref) { + // The page tree nodes have the count of all the leaves below them. To get + // how many pages are before we just have to walk up the tree and keep + // adding the count of siblings to the left of the node. + var xref = this.xref; + function pagesBeforeRef(kidRef) { + var total = 0; + var parentRef; + return xref.fetchAsync(kidRef).then(function (node) { + if (!node) { + return null; + } + parentRef = node.getRaw('Parent'); + return node.getAsync('Parent'); + }).then(function (parent) { + if (!parent) { + return null; + } + return parent.getAsync('Kids'); + }).then(function (kids) { + if (!kids) { + return null; + } + var kidPromises = []; + var found = false; + for (var i = 0; i < kids.length; i++) { + var kid = kids[i]; + assert(isRef(kid), 'kids must be an ref'); + if (kid.num == kidRef.num) { + found = true; + break; + } + kidPromises.push(xref.fetchAsync(kid).then(function (kid) { + if (kid.has('Count')) { + var count = kid.get('Count'); + total += count; + } else { // page leaf node + total++; + } + })); + } + if (!found) { + error('kid ref not found in parents kids'); + } + return Promise.all(kidPromises).then(function () { + return [total, parentRef]; + }); + }); + } + + var total = 0; + function next(ref) { + return pagesBeforeRef(ref).then(function (args) { + if (!args) { + return total; + } + var count = args[0]; + var parentRef = args[1]; + total += count; + return next(parentRef); + }); + } + + return next(ref); } }; @@ -5258,6 +6486,8 @@ var XRef = (function XRefClosure() { if (!dict) error('Failed to read XRef stream'); + } else { + error('Invalid XRef stream header'); } // Recursively get previous dictionary, if any @@ -5278,7 +6508,7 @@ var XRef = (function XRefClosure() { if (e instanceof MissingDataException) { throw e; } - log('(while reading XRef): ' + e); + info('(while reading XRef): ' + e); } if (recoveryMode) @@ -5399,14 +6629,14 @@ var XRef = (function XRefClosure() { }, fetchIfRefAsync: function XRef_fetchIfRefAsync(obj) { if (!isRef(obj)) { - var promise = new Promise(); + var promise = new LegacyPromise(); promise.resolve(obj); return promise; } return this.fetchAsync(obj); }, fetchAsync: function XRef_fetchAsync(ref, suppressEncryption) { - var promise = new Promise(); + var promise = new LegacyPromise(); var tryFetch = function (promise) { try { promise.resolve(this.fetch(ref, suppressEncryption)); @@ -5533,7 +6763,7 @@ var ObjectLoader = (function() { load: function ObjectLoader_load() { var keys = this.keys; - this.promise = new Promise(); + this.promise = new LegacyPromise(); // Don't walk the graph if all the data is already loaded. if (!(this.xref.stream instanceof ChunkedStream) || this.xref.stream.getMissingChunks().length === 0) { @@ -12660,756 +13890,6 @@ var CIDToUnicodeMaps = { -var ColorSpace = (function ColorSpaceClosure() { - // Constructor should define this.numComps, this.defaultColor, this.name - function ColorSpace() { - error('should not call ColorSpace constructor'); - } - - ColorSpace.prototype = { - /** - * Converts the color value to the RGB color. The color components are - * located in the src array starting from the srcOffset. Returns the array - * of the rgb components, each value ranging from [0,255]. - */ - getRgb: function ColorSpace_getRgb(src, srcOffset) { - error('Should not call ColorSpace.getRgb'); - }, - /** - * Converts the color value to the RGB color, similar to the getRgb method. - * The result placed into the dest array starting from the destOffset. - */ - getRgbItem: function ColorSpace_getRgb(src, srcOffset, dest, destOffset) { - error('Should not call ColorSpace.getRgbItem'); - }, - /** - * Converts the specified number of the color values to the RGB colors. - * The colors are located in the src array starting from the srcOffset. - * The result is placed into the dest array starting from the destOffset. - * The src array items shall be in [0,2^bits) range, the dest array items - * will be in [0,255] range. - */ - getRgbBuffer: function ColorSpace_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits) { - error('Should not call ColorSpace.getRgbBuffer'); - }, - /** - * Determines amount of the bytes is required to store the reslut of the - * conversion that done by the getRgbBuffer method. - */ - getOutputLength: function ColorSpace_getOutputLength(inputLength) { - error('Should not call ColorSpace.getOutputLength'); - }, - /** - * Returns true if source data will be equal the result/output data. - */ - isPassthrough: function ColorSpace_isPassthrough(bits) { - return false; - }, - /** - * Creates the output buffer and converts the specified number of the color - * values to the RGB colors, similar to the getRgbBuffer. - */ - createRgbBuffer: function ColorSpace_createRgbBuffer(src, srcOffset, - count, bits) { - if (this.isPassthrough(bits)) { - return src.subarray(srcOffset); - } - var dest = new Uint8Array(count * 3); - var numComponentColors = 1 << bits; - // Optimization: create a color map when there is just one component and - // we are converting more colors than the size of the color map. We - // don't build the map if the colorspace is gray or rgb since those - // methods are faster than building a map. This mainly offers big speed - // ups for indexed and alternate colorspaces. - if (this.numComps === 1 && count > numComponentColors && - this.name !== 'DeviceGray' && this.name !== 'DeviceRGB') { - // TODO it may be worth while to cache the color map. While running - // testing I never hit a cache so I will leave that out for now (perhaps - // we are reparsing colorspaces too much?). - var allColors = bits <= 8 ? new Uint8Array(numComponentColors) : - new Uint16Array(numComponentColors); - for (var i = 0; i < numComponentColors; i++) { - allColors[i] = i; - } - var colorMap = new Uint8Array(numComponentColors * 3); - this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bits); - - var destOffset = 0; - for (var i = 0; i < count; ++i) { - var key = src[srcOffset++] * 3; - dest[destOffset++] = colorMap[key]; - dest[destOffset++] = colorMap[key + 1]; - dest[destOffset++] = colorMap[key + 2]; - } - return dest; - } - this.getRgbBuffer(src, srcOffset, count, dest, 0, bits); - return dest; - }, - /** - * True if the colorspace has components in the default range of [0, 1]. - * This should be true for all colorspaces except for lab color spaces - * which are [0,100], [-128, 127], [-128, 127]. - */ - usesZeroToOneRange: true - }; - - ColorSpace.parse = function ColorSpace_parse(cs, xref, res) { - var IR = ColorSpace.parseToIR(cs, xref, res); - if (IR instanceof AlternateCS) - return IR; - - return ColorSpace.fromIR(IR); - }; - - ColorSpace.fromIR = function ColorSpace_fromIR(IR) { - var name = isArray(IR) ? IR[0] : IR; - - switch (name) { - case 'DeviceGrayCS': - return this.singletons.gray; - case 'DeviceRgbCS': - return this.singletons.rgb; - case 'DeviceCmykCS': - return this.singletons.cmyk; - case 'PatternCS': - var basePatternCS = IR[1]; - if (basePatternCS) - basePatternCS = ColorSpace.fromIR(basePatternCS); - return new PatternCS(basePatternCS); - case 'IndexedCS': - var baseIndexedCS = IR[1]; - var hiVal = IR[2]; - var lookup = IR[3]; - return new IndexedCS(ColorSpace.fromIR(baseIndexedCS), hiVal, lookup); - case 'AlternateCS': - var numComps = IR[1]; - var alt = IR[2]; - var tintFnIR = IR[3]; - - return new AlternateCS(numComps, ColorSpace.fromIR(alt), - PDFFunction.fromIR(tintFnIR)); - case 'LabCS': - var whitePoint = IR[1].WhitePoint; - var blackPoint = IR[1].BlackPoint; - var range = IR[1].Range; - return new LabCS(whitePoint, blackPoint, range); - default: - error('Unkown name ' + name); - } - return null; - }; - - ColorSpace.parseToIR = function ColorSpace_parseToIR(cs, xref, res) { - if (isName(cs)) { - var colorSpaces = res.get('ColorSpace'); - if (isDict(colorSpaces)) { - var refcs = colorSpaces.get(cs.name); - if (refcs) - cs = refcs; - } - } - - cs = xref.fetchIfRef(cs); - var mode; - - if (isName(cs)) { - mode = cs.name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return 'DeviceGrayCS'; - case 'DeviceRGB': - case 'RGB': - return 'DeviceRgbCS'; - case 'DeviceCMYK': - case 'CMYK': - return 'DeviceCmykCS'; - case 'Pattern': - return ['PatternCS', null]; - default: - error('unrecognized colorspace ' + mode); - } - } else if (isArray(cs)) { - mode = cs[0].name; - this.mode = mode; - - switch (mode) { - case 'DeviceGray': - case 'G': - return 'DeviceGrayCS'; - case 'DeviceRGB': - case 'RGB': - return 'DeviceRgbCS'; - case 'DeviceCMYK': - case 'CMYK': - return 'DeviceCmykCS'; - case 'CalGray': - return 'DeviceGrayCS'; - case 'CalRGB': - return 'DeviceRgbCS'; - case 'ICCBased': - var stream = xref.fetchIfRef(cs[1]); - var dict = stream.dict; - var numComps = dict.get('N'); - if (numComps == 1) - return 'DeviceGrayCS'; - if (numComps == 3) - return 'DeviceRgbCS'; - if (numComps == 4) - return 'DeviceCmykCS'; - break; - case 'Pattern': - var basePatternCS = cs[1]; - if (basePatternCS) - basePatternCS = ColorSpace.parseToIR(basePatternCS, xref, res); - return ['PatternCS', basePatternCS]; - case 'Indexed': - case 'I': - var baseIndexedCS = ColorSpace.parseToIR(cs[1], xref, res); - var hiVal = cs[2] + 1; - var lookup = xref.fetchIfRef(cs[3]); - if (isStream(lookup)) { - lookup = lookup.getBytes(); - } - return ['IndexedCS', baseIndexedCS, hiVal, lookup]; - case 'Separation': - case 'DeviceN': - var name = cs[1]; - var numComps = 1; - if (isName(name)) - numComps = 1; - else if (isArray(name)) - numComps = name.length; - var alt = ColorSpace.parseToIR(cs[2], xref, res); - var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3])); - return ['AlternateCS', numComps, alt, tintFnIR]; - case 'Lab': - var params = cs[1].getAll(); - return ['LabCS', params]; - default: - error('unimplemented color space object "' + mode + '"'); - } - } else { - error('unrecognized color space object: "' + cs + '"'); - } - return null; - }; - /** - * Checks if a decode map matches the default decode map for a color space. - * This handles the general decode maps where there are two values per - * component. e.g. [0, 1, 0, 1, 0, 1] for a RGB color. - * This does not handle Lab, Indexed, or Pattern decode maps since they are - * slightly different. - * @param {Array} decode Decode map (usually from an image). - * @param {Number} n Number of components the color space has. - */ - ColorSpace.isDefaultDecode = function ColorSpace_isDefaultDecode(decode, n) { - if (!decode) - return true; - - if (n * 2 !== decode.length) { - warn('The decode map is not the correct length'); - return true; - } - for (var i = 0, ii = decode.length; i < ii; i += 2) { - if (decode[i] !== 0 || decode[i + 1] != 1) - return false; - } - return true; - }; - - ColorSpace.singletons = { - get gray() { - return shadow(this, 'gray', new DeviceGrayCS()); - }, - get rgb() { - return shadow(this, 'rgb', new DeviceRgbCS()); - }, - get cmyk() { - return shadow(this, 'cmyk', new DeviceCmykCS()); - } - }; - - return ColorSpace; -})(); - -/** - * Alternate color space handles both Separation and DeviceN color spaces. A - * Separation color space is actually just a DeviceN with one color component. - * Both color spaces use a tinting function to convert colors to a base color - * space. - */ -var AlternateCS = (function AlternateCSClosure() { - function AlternateCS(numComps, base, tintFn) { - this.name = 'Alternate'; - this.numComps = numComps; - this.defaultColor = new Float32Array(numComps); - for (var i = 0; i < numComps; ++i) { - this.defaultColor[i] = 1; - } - this.base = base; - this.tintFn = tintFn; - } - - AlternateCS.prototype = { - getRgb: function AlternateCS_getRgb(src, srcOffset) { - var rgb = new Uint8Array(3); - this.getRgbItem(src, srcOffset, rgb, 0); - return rgb; - }, - getRgbItem: function AlternateCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var baseNumComps = this.base.numComps; - var input = 'subarray' in src ? - src.subarray(srcOffset, srcOffset + this.numComps) : - Array.prototype.slice.call(src, srcOffset, srcOffset + this.numComps); - var tinted = this.tintFn(input); - this.base.getRgbItem(tinted, 0, dest, destOffset); - }, - getRgbBuffer: function AlternateCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits) { - var tintFn = this.tintFn; - var base = this.base; - var scale = 1 / ((1 << bits) - 1); - var baseNumComps = base.numComps; - var usesZeroToOneRange = base.usesZeroToOneRange; - var isPassthrough = base.isPassthrough(8) || !usesZeroToOneRange; - var pos = isPassthrough ? destOffset : 0; - var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count); - var numComps = this.numComps; - - var scaled = new Float32Array(numComps); - for (var i = 0; i < count; i++) { - for (var j = 0; j < numComps; j++) { - scaled[j] = src[srcOffset++] * scale; - } - var tinted = tintFn(scaled); - if (usesZeroToOneRange) { - for (var j = 0; j < baseNumComps; j++) { - baseBuf[pos++] = tinted[j] * 255; - } - } else { - base.getRgbItem(tinted, 0, baseBuf, pos); - pos += baseNumComps; - } - } - if (!isPassthrough) { - base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8); - } - }, - getOutputLength: function AlternateCS_getOutputLength(inputLength) { - return this.base.getOutputLength(inputLength * - this.base.numComps / this.numComps); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - createRgbBuffer: ColorSpace.prototype.createRgbBuffer, - isDefaultDecode: function AlternateCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - - return AlternateCS; -})(); - -var PatternCS = (function PatternCSClosure() { - function PatternCS(baseCS) { - this.name = 'Pattern'; - this.base = baseCS; - } - PatternCS.prototype = {}; - - return PatternCS; -})(); - -var IndexedCS = (function IndexedCSClosure() { - function IndexedCS(base, highVal, lookup) { - this.name = 'Indexed'; - this.numComps = 1; - this.defaultColor = new Uint8Array([0]); - this.base = base; - this.highVal = highVal; - - var baseNumComps = base.numComps; - var length = baseNumComps * highVal; - var lookupArray; - - if (isStream(lookup)) { - lookupArray = new Uint8Array(length); - var bytes = lookup.getBytes(length); - lookupArray.set(bytes); - } else if (isString(lookup)) { - lookupArray = new Uint8Array(length); - for (var i = 0; i < length; ++i) - lookupArray[i] = lookup.charCodeAt(i); - } else if (lookup instanceof Uint8Array || lookup instanceof Array) { - lookupArray = lookup; - } else { - error('Unrecognized lookup table: ' + lookup); - } - this.lookup = lookupArray; - } - - IndexedCS.prototype = { - getRgb: function IndexedCS_getRgb(src, srcOffset) { - var numComps = this.base.numComps; - var start = src[srcOffset] * numComps; - return this.base.getRgb(this.lookup, start); - }, - getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var numComps = this.base.numComps; - var start = src[srcOffset] * numComps; - this.base.getRgbItem(this.lookup, start, dest, destOffset); - }, - getRgbBuffer: function IndexedCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset) { - var base = this.base; - var numComps = base.numComps; - var outputDelta = base.getOutputLength(numComps); - var lookup = this.lookup; - - for (var i = 0; i < count; ++i) { - var lookupPos = src[srcOffset++] * numComps; - base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8); - destOffset += outputDelta; - } - }, - getOutputLength: function IndexedCS_getOutputLength(inputLength) { - return this.base.getOutputLength(inputLength * this.base.numComps); - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - createRgbBuffer: ColorSpace.prototype.createRgbBuffer, - isDefaultDecode: function IndexedCS_isDefaultDecode(decodeMap) { - // indexed color maps shouldn't be changed - return true; - }, - usesZeroToOneRange: true - }; - return IndexedCS; -})(); - -var DeviceGrayCS = (function DeviceGrayCSClosure() { - function DeviceGrayCS() { - this.name = 'DeviceGray'; - this.numComps = 1; - this.defaultColor = new Float32Array([0]); - } - - DeviceGrayCS.prototype = { - getRgb: function DeviceGrayCS_getRgb(src, srcOffset) { - var rgb = new Uint8Array(3); - this.getRgbItem(src, srcOffset, rgb, 0); - return rgb; - }, - getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var c = (src[srcOffset] * 255) | 0; - c = c < 0 ? 0 : c > 255 ? 255 : c; - dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c; - }, - getRgbBuffer: function DeviceGrayCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits) { - var scale = 255 / ((1 << bits) - 1); - var j = srcOffset, q = destOffset; - for (var i = 0; i < count; ++i) { - var c = (scale * src[j++]) | 0; - dest[q++] = c; - dest[q++] = c; - dest[q++] = c; - } - }, - getOutputLength: function DeviceGrayCS_getOutputLength(inputLength) { - return inputLength * 3; - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - createRgbBuffer: ColorSpace.prototype.createRgbBuffer, - isDefaultDecode: function DeviceGrayCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - return DeviceGrayCS; -})(); - -var DeviceRgbCS = (function DeviceRgbCSClosure() { - function DeviceRgbCS() { - this.name = 'DeviceRGB'; - this.numComps = 3; - this.defaultColor = new Float32Array([0, 0, 0]); - } - DeviceRgbCS.prototype = { - getRgb: function DeviceRgbCS_getRgb(src, srcOffset) { - var rgb = new Uint8Array(3); - this.getRgbItem(src, srcOffset, rgb, 0); - return rgb; - }, - getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, - dest, destOffset) { - var r = (src[srcOffset] * 255) | 0; - var g = (src[srcOffset + 1] * 255) | 0; - var b = (src[srcOffset + 2] * 255) | 0; - dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r; - dest[destOffset + 1] = g < 0 ? 0 : g > 255 ? 255 : g; - dest[destOffset + 2] = b < 0 ? 0 : b > 255 ? 255 : b; - }, - getRgbBuffer: function DeviceRgbCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits) { - var length = count * 3; - if (bits == 8) { - dest.set(src.subarray(srcOffset, srcOffset + length), destOffset); - return; - } - var scale = 255 / ((1 << bits) - 1); - var j = srcOffset, q = destOffset; - for (var i = 0; i < length; ++i) { - dest[q++] = (scale * src[j++]) | 0; - } - }, - getOutputLength: function DeviceRgbCS_getOutputLength(inputLength) { - return inputLength; - }, - isPassthrough: function DeviceRgbCS_isPassthrough(bits) { - return bits == 8; - }, - createRgbBuffer: ColorSpace.prototype.createRgbBuffer, - isDefaultDecode: function DeviceRgbCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - return DeviceRgbCS; -})(); - -var DeviceCmykCS = (function DeviceCmykCSClosure() { - // The coefficients below was found using numerical analysis: the method of - // steepest descent for the sum((f_i - color_value_i)^2) for r/g/b colors, - // where color_value is the tabular value from the table of sampled RGB colors - // from CMYK US Web Coated (SWOP) colorspace, and f_i is the corresponding - // CMYK color conversion using the estimation below: - // f(A, B,.. N) = Acc+Bcm+Ccy+Dck+c+Fmm+Gmy+Hmk+Im+Jyy+Kyk+Ly+Mkk+Nk+255 - function convertToRgb(src, srcOffset, srcScale, dest, destOffset) { - var c = src[srcOffset + 0] * srcScale; - var m = src[srcOffset + 1] * srcScale; - var y = src[srcOffset + 2] * srcScale; - var k = src[srcOffset + 3] * srcScale; - - var r = - c * (-4.387332384609988 * c + 54.48615194189176 * m + - 18.82290502165302 * y + 212.25662451639585 * k + - -285.2331026137004) + - m * (1.7149763477362134 * m - 5.6096736904047315 * y + - -17.873870861415444 * k - 5.497006427196366) + - y * (-2.5217340131683033 * y - 21.248923337353073 * k + - 17.5119270841813) + - k * (-21.86122147463605 * k - 189.48180835922747) + 255; - var g = - c * (8.841041422036149 * c + 60.118027045597366 * m + - 6.871425592049007 * y + 31.159100130055922 * k + - -79.2970844816548) + - m * (-15.310361306967817 * m + 17.575251261109482 * y + - 131.35250912493976 * k - 190.9453302588951) + - y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + - k * (-20.737325471181034 * k - 187.80453709719578) + 255; - var b = - c * (0.8842522430003296 * c + 8.078677503112928 * m + - 30.89978309703729 * y - 0.23883238689178934 * k + - -14.183576799673286) + - m * (10.49593273432072 * m + 63.02378494754052 * y + - 50.606957656360734 * k - 112.23884253719248) + - y * (0.03296041114873217 * y + 115.60384449646641 * k + - -193.58209356861505) + - k * (-22.33816807309886 * k - 180.12613974708367) + 255; - - dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r; - dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g; - dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b; - } - - function DeviceCmykCS() { - this.name = 'DeviceCMYK'; - this.numComps = 4; - this.defaultColor = new Float32Array([0, 0, 0, 1]); - } - DeviceCmykCS.prototype = { - getRgb: function DeviceCmykCS_getRgb(src, srcOffset) { - var rgb = new Uint8Array(3); - convertToRgb(src, srcOffset, 1, rgb, 0); - return rgb; - }, - getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, - dest, destOffset) { - convertToRgb(src, srcOffset, 1, dest, destOffset); - }, - getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits) { - var scale = 1 / ((1 << bits) - 1); - for (var i = 0; i < count; i++) { - convertToRgb(src, srcOffset, scale, dest, destOffset); - srcOffset += 4; - destOffset += 3; - } - }, - getOutputLength: function DeviceCmykCS_getOutputLength(inputLength) { - return (inputLength >> 2) * 3; - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - createRgbBuffer: ColorSpace.prototype.createRgbBuffer, - isDefaultDecode: function DeviceCmykCS_isDefaultDecode(decodeMap) { - return ColorSpace.isDefaultDecode(decodeMap, this.numComps); - }, - usesZeroToOneRange: true - }; - - return DeviceCmykCS; -})(); - -// -// LabCS: Based on "PDF Reference, Sixth Ed", p.250 -// -var LabCS = (function LabCSClosure() { - function LabCS(whitePoint, blackPoint, range) { - this.name = 'Lab'; - this.numComps = 3; - this.defaultColor = new Float32Array([0, 0, 0]); - - if (!whitePoint) - error('WhitePoint missing - required for color space Lab'); - blackPoint = blackPoint || [0, 0, 0]; - range = range || [-100, 100, -100, 100]; - - // Translate args to spec variables - this.XW = whitePoint[0]; - this.YW = whitePoint[1]; - this.ZW = whitePoint[2]; - this.amin = range[0]; - this.amax = range[1]; - this.bmin = range[2]; - this.bmax = range[3]; - - // These are here just for completeness - the spec doesn't offer any - // formulas that use BlackPoint in Lab - this.XB = blackPoint[0]; - this.YB = blackPoint[1]; - this.ZB = blackPoint[2]; - - // Validate vars as per spec - if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) - error('Invalid WhitePoint components, no fallback available'); - - if (this.XB < 0 || this.YB < 0 || this.ZB < 0) { - info('Invalid BlackPoint, falling back to default'); - this.XB = this.YB = this.ZB = 0; - } - - if (this.amin > this.amax || this.bmin > this.bmax) { - info('Invalid Range, falling back to defaults'); - this.amin = -100; - this.amax = 100; - this.bmin = -100; - this.bmax = 100; - } - } - - // Function g(x) from spec - function fn_g(x) { - if (x >= 6 / 29) - return x * x * x; - else - return (108 / 841) * (x - 4 / 29); - } - - function decode(value, high1, low2, high2) { - return low2 + (value) * (high2 - low2) / (high1); - } - - // If decoding is needed maxVal should be 2^bits per component - 1. - function convertToRgb(cs, src, srcOffset, maxVal, dest, destOffset) { - // XXX: Lab input is in the range of [0, 100], [amin, amax], [bmin, bmax] - // not the usual [0, 1]. If a command like setFillColor is used the src - // values will already be within the correct range. However, if we are - // converting an image we have to map the values to the correct range given - // above. - // Ls,as,bs <---> L*,a*,b* in the spec - var Ls = src[srcOffset]; - var as = src[srcOffset + 1]; - var bs = src[srcOffset + 2]; - if (maxVal !== false) { - Ls = decode(Ls, maxVal, 0, 100); - as = decode(as, maxVal, cs.amin, cs.amax); - bs = decode(bs, maxVal, cs.bmin, cs.bmax); - } - - // Adjust limits of 'as' and 'bs' - as = as > cs.amax ? cs.amax : as < cs.amin ? cs.amin : as; - bs = bs > cs.bmax ? cs.bmax : bs < cs.bmin ? cs.bmin : bs; - - // Computes intermediate variables X,Y,Z as per spec - var M = (Ls + 16) / 116; - var L = M + (as / 500); - var N = M - (bs / 200); - - var X = cs.XW * fn_g(L); - var Y = cs.YW * fn_g(M); - var Z = cs.ZW * fn_g(N); - - var r, g, b; - // Using different conversions for D50 and D65 white points, - // per http://www.color.org/srgb.pdf - if (cs.ZW < 1) { - // Assuming D50 (X=0.9642, Y=1.00, Z=0.8249) - r = X * 3.1339 + Y * -1.6170 + Z * -0.4906; - g = X * -0.9785 + Y * 1.9160 + Z * 0.0333; - b = X * 0.0720 + Y * -0.2290 + Z * 1.4057; - } else { - // Assuming D65 (X=0.9505, Y=1.00, Z=1.0888) - r = X * 3.2406 + Y * -1.5372 + Z * -0.4986; - g = X * -0.9689 + Y * 1.8758 + Z * 0.0415; - b = X * 0.0557 + Y * -0.2040 + Z * 1.0570; - } - // clamp color values to [0,1] range then convert to [0,255] range. - dest[destOffset] = Math.sqrt(r < 0 ? 0 : r > 1 ? 1 : r) * 255; - dest[destOffset + 1] = Math.sqrt(g < 0 ? 0 : g > 1 ? 1 : g) * 255; - dest[destOffset + 2] = Math.sqrt(b < 0 ? 0 : b > 1 ? 1 : b) * 255; - } - - LabCS.prototype = { - getRgb: function LabCS_getRgb(src, srcOffset) { - var rgb = new Uint8Array(3); - convertToRgb(this, src, srcOffset, false, rgb, 0); - return rgb; - }, - getRgbItem: function LabCS_getRgbItem(src, srcOffset, dest, destOffset) { - convertToRgb(this, src, srcOffset, false, dest, destOffset); - }, - getRgbBuffer: function LabCS_getRgbBuffer(src, srcOffset, count, - dest, destOffset, bits) { - var maxVal = (1 << bits) - 1; - for (var i = 0; i < count; i++) { - convertToRgb(this, src, srcOffset, maxVal, dest, destOffset); - srcOffset += 3; - destOffset += 3; - } - }, - getOutputLength: function LabCS_getOutputLength(inputLength) { - return inputLength; - }, - isPassthrough: ColorSpace.prototype.isPassthrough, - isDefaultDecode: function LabCS_isDefaultDecode(decodeMap) { - // XXX: Decoding is handled with the lab conversion because of the strange - // ranges that are used. - return true; - }, - usesZeroToOneRange: false - }; - return LabCS; -})(); - - var ARCFourCipher = (function ARCFourCipherClosure() { function ARCFourCipher(key) { this.a = 0; @@ -13840,7 +14320,8 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { function prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata) { - var hashData = new Uint8Array(100), i = 0, j, n; + var hashDataSize = 40 + ownerPassword.length + fileId.length; + var hashData = new Uint8Array(hashDataSize), i = 0, j, n; if (password) { n = Math.min(32, password.length); for (; i < n; ++i) @@ -13955,8 +14436,8 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { keyLength < 40 || (keyLength % 8) !== 0) error('invalid key length'); // prepare keys - var ownerPassword = stringToBytes(dict.get('O')); - var userPassword = stringToBytes(dict.get('U')); + var ownerPassword = stringToBytes(dict.get('O')).subarray(0, 32); + var userPassword = stringToBytes(dict.get('U')).subarray(0, 32); var flags = dict.get('P'); var revision = dict.get('R'); var encryptMetadata = algorithm == 4 && // meaningful when V is 4 @@ -14067,7 +14548,7 @@ var CipherTransformFactory = (function CipherTransformFactoryClosure() { var PartialEvaluator = (function PartialEvaluatorClosure() { function PartialEvaluator(pdfManager, xref, handler, pageIndex, - uniquePrefix, idCounters) { + uniquePrefix, idCounters, fontCache) { this.state = new EvalState(); this.stateStack = []; @@ -14077,121 +14558,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { this.pageIndex = pageIndex; this.uniquePrefix = uniquePrefix; this.idCounters = idCounters; - this.fontCache = new RefSetCache(); + this.fontCache = fontCache; } - // Specifies properties for each command - // - // If variableArgs === true: [0, `numArgs`] expected - // If variableArgs === false: exactly `numArgs` expected - var OP_MAP = { - // Graphic state - w: { fnName: 'setLineWidth', numArgs: 1, variableArgs: false }, - J: { fnName: 'setLineCap', numArgs: 1, variableArgs: false }, - j: { fnName: 'setLineJoin', numArgs: 1, variableArgs: false }, - M: { fnName: 'setMiterLimit', numArgs: 1, variableArgs: false }, - d: { fnName: 'setDash', numArgs: 2, variableArgs: false }, - ri: { fnName: 'setRenderingIntent', numArgs: 1, variableArgs: false }, - i: { fnName: 'setFlatness', numArgs: 1, variableArgs: false }, - gs: { fnName: 'setGState', numArgs: 1, variableArgs: false }, - q: { fnName: 'save', numArgs: 0, variableArgs: false }, - Q: { fnName: 'restore', numArgs: 0, variableArgs: false }, - cm: { fnName: 'transform', numArgs: 6, variableArgs: false }, - - // Path - m: { fnName: 'moveTo', numArgs: 2, variableArgs: false }, - l: { fnName: 'lineTo', numArgs: 2, variableArgs: false }, - c: { fnName: 'curveTo', numArgs: 6, variableArgs: false }, - v: { fnName: 'curveTo2', numArgs: 4, variableArgs: false }, - y: { fnName: 'curveTo3', numArgs: 4, variableArgs: false }, - h: { fnName: 'closePath', numArgs: 0, variableArgs: false }, - re: { fnName: 'rectangle', numArgs: 4, variableArgs: false }, - S: { fnName: 'stroke', numArgs: 0, variableArgs: false }, - s: { fnName: 'closeStroke', numArgs: 0, variableArgs: false }, - f: { fnName: 'fill', numArgs: 0, variableArgs: false }, - F: { fnName: 'fill', numArgs: 0, variableArgs: false }, - 'f*': { fnName: 'eoFill', numArgs: 0, variableArgs: false }, - B: { fnName: 'fillStroke', numArgs: 0, variableArgs: false }, - 'B*': { fnName: 'eoFillStroke', numArgs: 0, variableArgs: false }, - b: { fnName: 'closeFillStroke', numArgs: 0, variableArgs: false }, - 'b*': { fnName: 'closeEOFillStroke', numArgs: 0, variableArgs: false }, - n: { fnName: 'endPath', numArgs: 0, variableArgs: false }, - - // Clipping - W: { fnName: 'clip', numArgs: 0, variableArgs: false }, - 'W*': { fnName: 'eoClip', numArgs: 0, variableArgs: false }, - - // Text - BT: { fnName: 'beginText', numArgs: 0, variableArgs: false }, - ET: { fnName: 'endText', numArgs: 0, variableArgs: false }, - Tc: { fnName: 'setCharSpacing', numArgs: 1, variableArgs: false }, - Tw: { fnName: 'setWordSpacing', numArgs: 1, variableArgs: false }, - Tz: { fnName: 'setHScale', numArgs: 1, variableArgs: false }, - TL: { fnName: 'setLeading', numArgs: 1, variableArgs: false }, - Tf: { fnName: 'setFont', numArgs: 2, variableArgs: false }, - Tr: { fnName: 'setTextRenderingMode', numArgs: 1, variableArgs: false }, - Ts: { fnName: 'setTextRise', numArgs: 1, variableArgs: false }, - Td: { fnName: 'moveText', numArgs: 2, variableArgs: false }, - TD: { fnName: 'setLeadingMoveText', numArgs: 2, variableArgs: false }, - Tm: { fnName: 'setTextMatrix', numArgs: 6, variableArgs: false }, - 'T*': { fnName: 'nextLine', numArgs: 0, variableArgs: false }, - Tj: { fnName: 'showText', numArgs: 1, variableArgs: false }, - TJ: { fnName: 'showSpacedText', numArgs: 1, variableArgs: false }, - '\'': { fnName: 'nextLineShowText', numArgs: 1, variableArgs: false }, - '"': { fnName: 'nextLineSetSpacingShowText', numArgs: 3, - variableArgs: false }, - - // Type3 fonts - d0: { fnName: 'setCharWidth', numArgs: 2, variableArgs: false }, - d1: { fnName: 'setCharWidthAndBounds', numArgs: 6, variableArgs: false }, - - // Color - CS: { fnName: 'setStrokeColorSpace', numArgs: 1, variableArgs: false }, - cs: { fnName: 'setFillColorSpace', numArgs: 1, variableArgs: false }, - SC: { fnName: 'setStrokeColor', numArgs: 4, variableArgs: true }, - SCN: { fnName: 'setStrokeColorN', numArgs: 33, variableArgs: true }, - sc: { fnName: 'setFillColor', numArgs: 4, variableArgs: true }, - scn: { fnName: 'setFillColorN', numArgs: 33, variableArgs: true }, - G: { fnName: 'setStrokeGray', numArgs: 1, variableArgs: false }, - g: { fnName: 'setFillGray', numArgs: 1, variableArgs: false }, - RG: { fnName: 'setStrokeRGBColor', numArgs: 3, variableArgs: false }, - rg: { fnName: 'setFillRGBColor', numArgs: 3, variableArgs: false }, - K: { fnName: 'setStrokeCMYKColor', numArgs: 4, variableArgs: false }, - k: { fnName: 'setFillCMYKColor', numArgs: 4, variableArgs: false }, - - // Shading - sh: { fnName: 'shadingFill', numArgs: 1, variableArgs: false }, - - // Images - BI: { fnName: 'beginInlineImage', numArgs: 0, variableArgs: false }, - ID: { fnName: 'beginImageData', numArgs: 0, variableArgs: false }, - EI: { fnName: 'endInlineImage', numArgs: 1, variableArgs: false }, - - // XObjects - Do: { fnName: 'paintXObject', numArgs: 1, variableArgs: false }, - MP: { fnName: 'markPoint', numArgs: 1, variableArgs: false }, - DP: { fnName: 'markPointProps', numArgs: 2, variableArgs: false }, - BMC: { fnName: 'beginMarkedContent', numArgs: 1, variableArgs: false }, - BDC: { fnName: 'beginMarkedContentProps', numArgs: 2, variableArgs: false }, - EMC: { fnName: 'endMarkedContent', numArgs: 0, variableArgs: false }, - - // Compatibility - BX: { fnName: 'beginCompat', numArgs: 0, variableArgs: false }, - EX: { fnName: 'endCompat', numArgs: 0, variableArgs: false }, - - // (reserved partial commands for the lexer) - BM: null, - BD: null, - 'true': null, - fa: null, - fal: null, - fals: null, - 'false': null, - nu: null, - nul: null, - 'null': null - }; - var TILING_PATTERN = 1, SHADING_PATTERN = 2; PartialEvaluator.prototype = { @@ -14237,7 +14606,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { buildFormXObject: function PartialEvaluator_buildFormXObject(resources, xobj, smask, - operatorList) { + operatorList, + state) { var self = this; var matrix = xobj.dict.get('Matrix'); @@ -14259,17 +14629,17 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // There is also a group colorspace, but since we put everything in // RGB I'm not sure we need it. } - operatorList.addOp('beginGroup', [groupOptions]); + operatorList.addOp(OPS.beginGroup, [groupOptions]); } - operatorList.addOp('paintFormXObjectBegin', [matrix, bbox]); + operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]); this.getOperatorList(xobj, xobj.dict.get('Resources') || resources, - operatorList); - operatorList.addOp('paintFormXObjectEnd', []); + operatorList, state); + operatorList.addOp(OPS.paintFormXObjectEnd, []); if (group) { - operatorList.addOp('endGroup', [groupOptions]); + operatorList.addOp(OPS.endGroup, [groupOptions]); } }, @@ -14300,9 +14670,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var decode = dict.get('Decode', 'D'); var inverseDecode = !!decode && decode[0] > 0; - operatorList.addOp('paintImageMaskXObject', - [PDFImage.createMask(imgArray, width, height, - inverseDecode)] + operatorList.addOp(OPS.paintImageMaskXObject, + [PDFImage.createMask(imgArray, width, height, inverseDecode)] ); return; } @@ -14318,7 +14687,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var imageObj = new PDFImage(this.xref, resources, image, inline, null, null); var imgData = imageObj.getImageData(); - operatorList.addOp('paintInlineImageXObject', [imgData]); + operatorList.addOp(OPS.paintInlineImageXObject, [imgData]); return; } @@ -14332,7 +14701,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (!softMask && !mask && image instanceof JpegStream && image.isNativelySupported(this.xref, resources)) { // These JPEGs don't need any more processing so we can just send it. - operatorList.addOp('paintJpegXObject', args); + operatorList.addOp(OPS.paintJpegXObject, args); this.handler.send( 'obj', [objId, this.pageIndex, 'JpegStream', image.getIR()]); return; @@ -14341,10 +14710,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { PDFImage.buildImage(function(imageObj) { var imgData = imageObj.getImageData(); - self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData]); + self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData], + null, [imgData.data.buffer]); }, self.handler, self.xref, resources, image, inline); - operatorList.addOp('paintImageXObject', args); + operatorList.addOp(OPS.paintImageXObject, args); }, handleTilingType: function PartialEvaluator_handleTilingType( @@ -14446,9 +14816,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { gStateObj.push([key, value]); break; case 'SMask': - // We support the default so don't trigger the TODO. + // We support the default so don't trigger a warning bar. if (!isName(value) || value.name != 'None') - TODO('graphic state operator ' + key); + UnsupportedManager.notify(UNSUPPORTED_FEATURES.smask); break; // Only generate info log messages for the following since // they are unlikey to have a big impact on the rendering. @@ -14483,7 +14853,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { setGStateForKey(gStateObj, key, value); } - operatorList.addOp('setGState', [gStateObj]); + operatorList.addOp(OPS.setGState, [gStateObj]); }, loadFont: function PartialEvaluator_loadFont(fontName, font, xref, @@ -14514,22 +14884,31 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { return this.fontCache.get(fontRef); } - font = xref.fetchIfRef(fontRef); if (!isDict(font)) { return errorFont(); } - this.fontCache.put(fontRef, font); + // Workaround for bad PDF generators that doesn't reference fonts + // properly, i.e. by not using an object identifier. + // Check if the fontRef is a Dict (as opposed to a standard object), + // in which case we don't cache the font and instead reference it by + // fontName in font.loadedName below. + var fontRefIsDict = isDict(fontRef); + if (!fontRefIsDict) { + this.fontCache.put(fontRef, font); + } // keep track of each font we translated so the caller can // load them asynchronously before calling display on a page - font.loadedName = 'g_font_' + fontRef.num + '_' + fontRef.gen; + font.loadedName = 'g_font_' + (fontRefIsDict ? + fontName.replace(/\W/g, '') : (fontRef.num + '_' + fontRef.gen)); if (!font.translated) { var translated; try { translated = this.translateFont(font, xref); } catch (e) { + UnsupportedManager.notify(UNSUPPORTED_FEATURES.font); translated = new ErrorFont(e instanceof Error ? e.message : e); } font.translated = translated; @@ -14562,7 +14941,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { getOperatorList: function PartialEvaluator_getOperatorList(stream, resources, - operatorList) { + operatorList, + evaluatorState) { var self = this; var xref = this.xref; @@ -14573,154 +14953,131 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { resources = resources || new Dict(); var xobjs = resources.get('XObject') || new Dict(); var patterns = resources.get('Pattern') || new Dict(); - // TODO(mduan): pass array of knownCommands rather than OP_MAP - // dictionary - var parser = new Parser(new Lexer(stream, OP_MAP), false, xref); + var preprocessor = new EvaluatorPreprocessor(stream, xref); + if (evaluatorState) { + preprocessor.setState(evaluatorState); + } - var promise = new Promise(); - var args = []; - nextOp: - while (true) { - - var obj = parser.getObj(); - - if (isEOF(obj)) { - break; - } - - if (isCmd(obj)) { - var cmd = obj.cmd; - - // Check that the command is valid - var opSpec = OP_MAP[cmd]; - if (!opSpec) { - warn('Unknown command "' + cmd + '"'); - continue; - } - - var fn = opSpec.fnName; - - // Validate the number of arguments for the command - if (opSpec.variableArgs) { - if (args.length > opSpec.numArgs) { - info('Command ' + fn + ': expected [0,' + opSpec.numArgs + - '] args, but received ' + args.length + ' args'); - } - } else { - if (args.length < opSpec.numArgs) { - // If we receive too few args, it's not possible to possible - // to execute the command, so skip the command - info('Command ' + fn + ': because expected ' + - opSpec.numArgs + ' args, but received ' + args.length + - ' args; skipping'); - args = []; - continue; - } else if (args.length > opSpec.numArgs) { - info('Command ' + fn + ': expected ' + opSpec.numArgs + - ' args, but received ' + args.length + ' args'); - } - } - - // TODO figure out how to type-check vararg functions - - if ((cmd == 'SCN' || cmd == 'scn') && - !args[args.length - 1].code) { - // compile tiling patterns - var patternName = args[args.length - 1]; - // SCN/scn applies patterns along with normal colors - var pattern; - if (isName(patternName) && - (pattern = patterns.get(patternName.name))) { - - var dict = isStream(pattern) ? pattern.dict : pattern; - var typeNum = dict.get('PatternType'); - - if (typeNum == TILING_PATTERN) { - self.handleTilingType(fn, args, resources, pattern, dict, - operatorList); - args = []; - continue; - } else if (typeNum == SHADING_PATTERN) { - var shading = dict.get('Shading'); - var matrix = dict.get('Matrix'); - var pattern = Pattern.parseShading(shading, matrix, xref, - resources); - args = pattern.getIR(); - } else { - error('Unkown PatternType ' + typeNum); - } - } - } else if (cmd == 'Do' && !args[0].code) { - // eagerly compile XForm objects - var name = args[0].name; - var xobj = xobjs.get(name); - if (xobj) { - assertWellFormed( - isStream(xobj), 'XObject should be a stream'); - - var type = xobj.dict.get('Subtype'); - assertWellFormed( - isName(type), - 'XObject should have a Name subtype' - ); - - if ('Form' == type.name) { - self.buildFormXObject(resources, xobj, null, operatorList); - args = []; - continue; - } else if ('Image' == type.name) { - self.buildPaintImageXObject(resources, xobj, false, - operatorList); - args = []; - continue; - } else { - error('Unhandled XObject subtype ' + type.name); - } - } - } else if (cmd == 'Tf') { // eagerly collect all fonts - var loadedName = self.handleSetFont(resources, args, null, - operatorList); - operatorList.addDependency(loadedName); - fn = 'setFont'; - args[0] = loadedName; - } else if (cmd == 'EI') { - self.buildPaintImageXObject(resources, args[0], true, operatorList); - args = []; - continue; - } else if (cmd === 'q') { // save - var old = this.state; - this.stateStack.push(this.state); - this.state = old.clone(); - } else if (cmd === 'Q') { // restore - var prev = this.stateStack.pop(); - if (prev) { - this.state = prev; - } - } else if (cmd === 'Tj') { // showText - args[0] = this.handleText(args[0]); - } else if (cmd === 'TJ') { // showSpacedText - var arr = args[0]; - var arrLength = arr.length; - for (var i = 0; i < arrLength; ++i) { - if (isString(arr[i])) { - arr[i] = this.handleText(arr[i]); - } - } - } else if (cmd === '\'') { // nextLineShowText - args[0] = this.handleText(args[0]); - } else if (cmd === '"') { // nextLineSetSpacingShowText - args[2] = this.handleText(args[2]); - } else if (cmd === 'Tr') { // setTextRenderingMode - this.state.textRenderingMode = args[0]; - } + var promise = new LegacyPromise(); + var operation; + while ((operation = preprocessor.read())) { + var args = operation.args; + var fn = operation.fn; switch (fn) { + case OPS.setStrokeColorN: + case OPS.setFillColorN: + if (args[args.length - 1].code) { + break; + } + // compile tiling patterns + var patternName = args[args.length - 1]; + // SCN/scn applies patterns along with normal colors + var pattern; + if (isName(patternName) && + (pattern = patterns.get(patternName.name))) { + + var dict = isStream(pattern) ? pattern.dict : pattern; + var typeNum = dict.get('PatternType'); + + if (typeNum == TILING_PATTERN) { + self.handleTilingType(fn, args, resources, pattern, dict, + operatorList); + args = []; + continue; + } else if (typeNum == SHADING_PATTERN) { + var shading = dict.get('Shading'); + var matrix = dict.get('Matrix'); + var pattern = Pattern.parseShading(shading, matrix, xref, + resources); + args = pattern.getIR(); + } else { + error('Unkown PatternType ' + typeNum); + } + } + break; + case OPS.paintXObject: + if (args[0].code) { + break; + } + // eagerly compile XForm objects + var name = args[0].name; + var xobj = xobjs.get(name); + if (xobj) { + assertWellFormed( + isStream(xobj), 'XObject should be a stream'); + + var type = xobj.dict.get('Subtype'); + assertWellFormed( + isName(type), + 'XObject should have a Name subtype' + ); + + if ('Form' == type.name) { + self.buildFormXObject(resources, xobj, null, operatorList, + preprocessor.getState()); + args = []; + continue; + } else if ('Image' == type.name) { + self.buildPaintImageXObject(resources, xobj, false, + operatorList); + args = []; + continue; + } else { + error('Unhandled XObject subtype ' + type.name); + } + } + break; + case OPS.setFont: + // eagerly collect all fonts + var loadedName = self.handleSetFont(resources, args, null, + operatorList); + operatorList.addDependency(loadedName); + args[0] = loadedName; + break; + case OPS.endInlineImage: + self.buildPaintImageXObject(resources, args[0], true, + operatorList); + args = []; + continue; + case OPS.save: + var old = this.state; + this.stateStack.push(this.state); + this.state = old.clone(); + break; + case OPS.restore: + var prev = this.stateStack.pop(); + if (prev) { + this.state = prev; + } + break; + case OPS.showText: + args[0] = this.handleText(args[0]); + break; + case OPS.showSpacedText: + var arr = args[0]; + var arrLength = arr.length; + for (var i = 0; i < arrLength; ++i) { + if (isString(arr[i])) { + arr[i] = this.handleText(arr[i]); + } + } + break; + case OPS.nextLineShowText: + args[0] = this.handleText(args[0]); + break; + case OPS.nextLineSetSpacingShowText: + args[2] = this.handleText(args[2]); + break; + case OPS.setTextRenderingMode: + this.state.textRenderingMode = args[0]; + break; // Parse the ColorSpace data to a raw format. - case 'setFillColorSpace': - case 'setStrokeColorSpace': + case OPS.setFillColorSpace: + case OPS.setStrokeColorSpace: args = [ColorSpace.parseToIR(args[0], xref, resources)]; break; - case 'shadingFill': + case OPS.shadingFill: var shadingRes = resources.get('Shading'); if (!shadingRes) error('No shading resource found'); @@ -14733,9 +15090,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { shading, null, xref, resources); var patternIR = shadingFill.getIR(); args = [patternIR]; - fn = 'shadingFill'; + fn = OPS.shadingFill; break; - case 'setGState': + case OPS.setGState: var dictName = args[0]; var extGState = resources.get('ExtGState'); @@ -14745,16 +15102,16 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var gState = extGState.get(dictName.name); self.setGState(resources, gState, operatorList); args = []; - continue nextOp; + continue; } // switch operatorList.addOp(fn, args); - args = []; - parser.saveState(); - } else if (obj !== null && obj !== undefined) { - args.push(obj instanceof Dict ? obj.getAll() : obj); - assertWellFormed(args.length <= 33, 'Too many arguments'); - } + } + + // some pdf don't close all restores inside object/form + // closing those for them + for (var i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) { + operatorList.addOp(OPS.restore, []); } return operatorList; @@ -14791,65 +15148,55 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd. var xobjs = null; - var parser = new Parser(new Lexer(stream), false); + var preprocessor = new EvaluatorPreprocessor(stream, xref); var res = resources; - var args = [], obj; var chunk = ''; var font = null; var charSpace = 0, wordSpace = 0; - while (!isEOF(obj = parser.getObj())) { - if (isCmd(obj)) { - var cmd = obj.cmd; - switch (cmd) { + var operation; + while ((operation = preprocessor.read())) { + var fn = operation.fn; + var args = operation.args; + switch (fn) { // TODO: Add support for SAVE/RESTORE and XFORM here. - case 'Tf': + case OPS.setFont: font = handleSetFont(args[0].name).translated; textState.fontSize = args[1]; break; - case 'Ts': + case OPS.setTextRise: textState.textRise = args[0]; break; - case 'Tz': + case OPS.setHScale: textState.textHScale = args[0] / 100; break; - case 'TL': + case OPS.setLeading: textState.leading = args[0]; break; - case 'Td': + case OPS.moveText: textState.translateTextMatrix(args[0], args[1]); break; - case 'TD': + case OPS.setLeadingMoveText: textState.leading = -args[1]; textState.translateTextMatrix(args[0], args[1]); break; - case 'T*': + case OPS.nextLine: textState.translateTextMatrix(0, -textState.leading); break; - case 'Tm': + case OPS.setTextMatrix: textState.setTextMatrix(args[0], args[1], args[2], args[3], args[4], args[5]); break; - case 'Tc': + case OPS.setCharSpacing: charSpace = args[0]; break; - case 'Tw': + case OPS.setWordSpacing: wordSpace = args[0]; break; - case 'q': - textState.push(); - break; - case 'Q': - textState.pop(); - break; - case 'BT': + case OPS.beginText: textState.initialiseTextObj(); break; - case 'cm': - textState.transformCTM(args[0], args[1], args[2], - args[3], args[4], args[5]); - break; - case 'TJ': + case OPS.showSpacedText: var items = args[0]; for (var j = 0, jj = items.length; j < jj; j++) { if (typeof items[j] === 'string') { @@ -14867,20 +15214,20 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } } break; - case 'Tj': + case OPS.showText: chunk += fontCharsToUnicode(args[0], font); break; - case '\'': + case OPS.nextLineShowText: // For search, adding a extra white space for line breaks would be // better here, but that causes too much spaces in the // text-selection divs. chunk += fontCharsToUnicode(args[0], font); break; - case '"': + case OPS.nextLineSetSpacingShowText: // Note comment in "'" chunk += fontCharsToUnicode(args[2], font); break; - case 'Do': + case OPS.paintXObject: // Set the chunk such that the following if won't add something // to the state. chunk = ''; @@ -14914,7 +15261,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { state ); break; - case 'gs': + case OPS.setGState: var dictName = args[0]; var extGState = resources.get('ExtGState'); @@ -14933,11 +15280,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { if (chunk !== '') { var bidiText = PDFJS.bidi(chunk, -1, font.vertical); - var renderParams = textState.calcRenderParams(); + var renderParams = textState.calcRenderParams(preprocessor.ctm); bidiText.x = renderParams.renderMatrix[4] - (textState.fontSize * renderParams.vScale * Math.sin(renderParams.angle)); bidiText.y = renderParams.renderMatrix[5] + (textState.fontSize * renderParams.vScale * Math.cos(renderParams.angle)); + var fontHeight = textState.fontSize * renderParams.vScale; + var fontAscent = font.ascent ? font.ascent * fontHeight : + font.descent ? (1 + font.descent) * fontHeight : fontHeight; + bidiText.x = renderParams.renderMatrix[4] - (fontAscent * + Math.sin(renderParams.angle)); + bidiText.y = renderParams.renderMatrix[5] + (fontAscent * + Math.cos(renderParams.angle)); if (bidiText.dir == 'ttb') { bidiText.x += renderParams.vScale / 2; bidiText.y -= renderParams.vScale; @@ -14946,12 +15300,6 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { chunk = ''; } - - args = []; - } else if (obj !== null && obj !== undefined) { - assertWellFormed(args.length <= 33, 'Too many arguments'); - args.push(obj); - } } // while return state; @@ -14999,10 +15347,11 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { Encodings.WinAnsiEncoding : Encodings.StandardEncoding; // The Symbolic attribute can be misused for regular fonts - // Heuristic: we have to check if the font is a standard one also + // Heuristic: we have to check if the font is a standard one and has + // Symbolic font name if (!!(flags & FontFlags.Symbolic)) { - baseEncoding = !properties.file ? Encodings.symbolsEncoding : - Encodings.MacRomanEncoding; + baseEncoding = !properties.file && /Symbol/i.test(properties.name) ? + Encodings.SymbolSetEncoding : Encodings.MacRomanEncoding; } if (dict.has('Encoding')) { var encoding = dict.get('Encoding'); @@ -15270,6 +15619,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var properties = { type: type.name, + name: baseFontName, widths: metrics.widths, defaultWidth: metrics.defaultWidth, flags: flags, @@ -15328,6 +15678,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var properties = { type: type.name, + name: fontName.name, subtype: subtype, file: fontFile, length1: length1, @@ -15371,6 +15722,18 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { PartialEvaluator.optimizeQueue = function PartialEvaluator_optimizeQueue(queue) { + function squash(array, index, howMany, element) { + if (isArray(array)) { + array.splice(index, howMany, element); + } else { + // Replace the element. + array[index] = element; + // Shift everything after the element up. + var sub = array.subarray(index + howMany); + array.set(sub, index + 1); + } + } + var fnArray = queue.fnArray, argsArray = queue.argsArray; // grouping paintInlineImageXObject's into paintInlineImageXObjectGroup // searching for (save, transform, paintInlineImageXObject, restore)+ @@ -15378,10 +15741,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { var MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200; var MAX_WIDTH = 1000; var IMAGE_PADDING = 1; - for (var i = 0, ii = fnArray.length; i < ii; i++) { - if (fnArray[i] === 'paintInlineImageXObject' && - fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' && - fnArray[i + 1] === 'restore') { + for (var i = 0, ii = argsArray.length; i < ii; i++) { + if (fnArray[i] === OPS.paintInlineImageXObject && + fnArray[i - 2] === OPS.save && fnArray[i - 1] === OPS.transform && + fnArray[i + 1] === OPS.restore) { var j = i - 2; for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) { } @@ -15446,21 +15809,21 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { } } // replacing queue items - fnArray.splice(j, count * 4, ['paintInlineImageXObjectGroup']); + squash(fnArray, j, count * 4, OPS.paintInlineImageXObjectGroup); argsArray.splice(j, count * 4, [{width: imgWidth, height: imgHeight, data: imgData}, map]); i = j; - ii = fnArray.length; + ii = argsArray.length; } } // grouping paintImageMaskXObject's into paintImageMaskXObjectGroup // searching for (save, transform, paintImageMaskXObject, restore)+ var MIN_IMAGES_IN_MASKS_BLOCK = 10; var MAX_IMAGES_IN_MASKS_BLOCK = 100; - for (var i = 0, ii = fnArray.length; i < ii; i++) { - if (fnArray[i] === 'paintImageMaskXObject' && - fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' && - fnArray[i + 1] === 'restore') { + for (var i = 0, ii = argsArray.length; i < ii; i++) { + if (fnArray[i] === OPS.paintImageMaskXObject && + fnArray[i - 2] === OPS.save && fnArray[i - 1] === OPS.transform && + fnArray[i + 1] === OPS.restore) { var j = i - 2; for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) { } @@ -15477,37 +15840,68 @@ var PartialEvaluator = (function PartialEvaluatorClosure() { height: maskParams.height, transform: transform}); } // replacing queue items - fnArray.splice(j, count * 4, ['paintImageMaskXObjectGroup']); + squash(fnArray, j, count * 4, OPS.paintImageMaskXObjectGroup); argsArray.splice(j, count * 4, [images]); i = j; - ii = fnArray.length; + ii = argsArray.length; } } }; - return PartialEvaluator; })(); - var OperatorList = (function OperatorListClosure() { var CHUNK_SIZE = 100; - function OperatorList(messageHandler, pageIndex) { + function getTransfers(queue) { + var transfers = []; + var fnArray = queue.fnArray, argsArray = queue.argsArray; + for (var i = 0, ii = queue.length; i < ii; i++) { + switch (fnArray[i]) { + case OPS.paintInlineImageXObject: + case OPS.paintInlineImageXObjectGroup: + case OPS.paintImageMaskXObject: + var arg = argsArray[i][0]; // first param in imgData + transfers.push(arg.data.buffer); + break; + } + } + return transfers; + } + + + function OperatorList(messageHandler, pageIndex) { this.messageHandler = messageHandler; - this.fnArray = []; + // When there isn't a message handler the fn array needs to be able to grow + // since we can't flush the operators. + if (messageHandler) { + this.fnArray = new Uint8Array(CHUNK_SIZE); + } else { + this.fnArray = []; + } this.argsArray = []; this.dependencies = {}, this.pageIndex = pageIndex; + this.fnIndex = 0; } OperatorList.prototype = { + get length() { + return this.argsArray.length; + }, + addOp: function(fn, args) { - this.fnArray.push(fn); - this.argsArray.push(args); - if (this.messageHandler && this.fnArray.length >= CHUNK_SIZE) { - this.flush(); + if (this.messageHandler) { + this.fnArray[this.fnIndex++] = fn; + this.argsArray.push(args); + if (this.fnIndex >= CHUNK_SIZE) { + this.flush(); + } + } else { + this.fnArray.push(fn); + this.argsArray.push(args); } }, @@ -15516,7 +15910,7 @@ var OperatorList = (function OperatorListClosure() { return; } this.dependencies[dependency] = true; - this.addOp('dependency', [dependency]); + this.addOp(OPS.dependency, [dependency]); }, addDependencies: function(dependencies) { @@ -15526,40 +15920,44 @@ var OperatorList = (function OperatorListClosure() { }, addOpList: function(opList) { - Util.concatenateToArray(this.fnArray, opList.fnArray); - Util.concatenateToArray(this.argsArray, opList.argsArray); Util.extendObj(this.dependencies, opList.dependencies); + for (var i = 0, ii = opList.length; i < ii; i++) { + this.addOp(opList.fnArray[i], opList.argsArray[i]); + } }, getIR: function() { return { fnArray: this.fnArray, - argsArray: this.argsArray + argsArray: this.argsArray, + length: this.length }; }, flush: function(lastChunk) { PartialEvaluator.optimizeQueue(this); + var transfers = getTransfers(this); this.messageHandler.send('RenderPageChunk', { operatorList: { fnArray: this.fnArray, argsArray: this.argsArray, - lastChunk: lastChunk + lastChunk: lastChunk, + length: this.length }, pageIndex: this.pageIndex - }); + }, null, transfers); this.dependencies = []; - this.fnArray = []; + this.fnIndex = 0; this.argsArray = []; } }; return OperatorList; })(); + var TextState = (function TextStateClosure() { function TextState() { this.fontSize = 0; - this.ctm = [1, 0, 0, 1, 0, 0]; this.textMatrix = [1, 0, 0, 1, 0, 0]; this.stateStack = []; //textState variables @@ -15568,12 +15966,6 @@ var TextState = (function TextStateClosure() { this.textRise = 0; } TextState.prototype = { - push: function TextState_push() { - this.stateStack.push(this.ctm.slice()); - }, - pop: function TextStae_pop() { - this.ctm = this.stateStack.pop(); - }, initialiseTextObj: function TextState_initialiseTextObj() { var m = this.textMatrix; m[0] = 1, m[1] = 0, m[2] = 0, m[3] = 1, m[4] = 0, m[5] = 0; @@ -15582,24 +15974,13 @@ var TextState = (function TextStateClosure() { var m = this.textMatrix; m[0] = a, m[1] = b, m[2] = c, m[3] = d, m[4] = e, m[5] = f; }, - transformCTM: function TextState_transformCTM(a, b, c, d, e, f) { - var m = this.ctm; - var m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3], m4 = m[4], m5 = m[5]; - m[0] = m0 * a + m2 * b; - m[1] = m1 * a + m3 * b; - m[2] = m0 * c + m2 * d; - m[3] = m1 * c + m3 * d; - m[4] = m0 * e + m2 * f + m4; - m[5] = m1 * e + m3 * f + m5; - }, translateTextMatrix: function TextState_translateTextMatrix(x, y) { var m = this.textMatrix; m[4] = m[0] * x + m[2] * y + m[4]; m[5] = m[1] * x + m[3] * y + m[5]; }, - calcRenderParams: function TextState_calcRenderingParams() { + calcRenderParams: function TextState_calcRenderingParams(cm) { var tm = this.textMatrix; - var cm = this.ctm; var a = this.fontSize; var b = a * this.textHScale; var c = this.textRise; @@ -15642,6 +16023,221 @@ var EvalState = (function EvalStateClosure() { return EvalState; })(); +var EvaluatorPreprocessor = (function EvaluatorPreprocessor() { + // Specifies properties for each command + // + // If variableArgs === true: [0, `numArgs`] expected + // If variableArgs === false: exactly `numArgs` expected + var OP_MAP = { + // Graphic state + w: { id: OPS.setLineWidth, numArgs: 1, variableArgs: false }, + J: { id: OPS.setLineCap, numArgs: 1, variableArgs: false }, + j: { id: OPS.setLineJoin, numArgs: 1, variableArgs: false }, + M: { id: OPS.setMiterLimit, numArgs: 1, variableArgs: false }, + d: { id: OPS.setDash, numArgs: 2, variableArgs: false }, + ri: { id: OPS.setRenderingIntent, numArgs: 1, variableArgs: false }, + i: { id: OPS.setFlatness, numArgs: 1, variableArgs: false }, + gs: { id: OPS.setGState, numArgs: 1, variableArgs: false }, + q: { id: OPS.save, numArgs: 0, variableArgs: false }, + Q: { id: OPS.restore, numArgs: 0, variableArgs: false }, + cm: { id: OPS.transform, numArgs: 6, variableArgs: false }, + + // Path + m: { id: OPS.moveTo, numArgs: 2, variableArgs: false }, + l: { id: OPS.lineTo, numArgs: 2, variableArgs: false }, + c: { id: OPS.curveTo, numArgs: 6, variableArgs: false }, + v: { id: OPS.curveTo2, numArgs: 4, variableArgs: false }, + y: { id: OPS.curveTo3, numArgs: 4, variableArgs: false }, + h: { id: OPS.closePath, numArgs: 0, variableArgs: false }, + re: { id: OPS.rectangle, numArgs: 4, variableArgs: false }, + S: { id: OPS.stroke, numArgs: 0, variableArgs: false }, + s: { id: OPS.closeStroke, numArgs: 0, variableArgs: false }, + f: { id: OPS.fill, numArgs: 0, variableArgs: false }, + F: { id: OPS.fill, numArgs: 0, variableArgs: false }, + 'f*': { id: OPS.eoFill, numArgs: 0, variableArgs: false }, + B: { id: OPS.fillStroke, numArgs: 0, variableArgs: false }, + 'B*': { id: OPS.eoFillStroke, numArgs: 0, variableArgs: false }, + b: { id: OPS.closeFillStroke, numArgs: 0, variableArgs: false }, + 'b*': { id: OPS.closeEOFillStroke, numArgs: 0, variableArgs: false }, + n: { id: OPS.endPath, numArgs: 0, variableArgs: false }, + + // Clipping + W: { id: OPS.clip, numArgs: 0, variableArgs: false }, + 'W*': { id: OPS.eoClip, numArgs: 0, variableArgs: false }, + + // Text + BT: { id: OPS.beginText, numArgs: 0, variableArgs: false }, + ET: { id: OPS.endText, numArgs: 0, variableArgs: false }, + Tc: { id: OPS.setCharSpacing, numArgs: 1, variableArgs: false }, + Tw: { id: OPS.setWordSpacing, numArgs: 1, variableArgs: false }, + Tz: { id: OPS.setHScale, numArgs: 1, variableArgs: false }, + TL: { id: OPS.setLeading, numArgs: 1, variableArgs: false }, + Tf: { id: OPS.setFont, numArgs: 2, variableArgs: false }, + Tr: { id: OPS.setTextRenderingMode, numArgs: 1, variableArgs: false }, + Ts: { id: OPS.setTextRise, numArgs: 1, variableArgs: false }, + Td: { id: OPS.moveText, numArgs: 2, variableArgs: false }, + TD: { id: OPS.setLeadingMoveText, numArgs: 2, variableArgs: false }, + Tm: { id: OPS.setTextMatrix, numArgs: 6, variableArgs: false }, + 'T*': { id: OPS.nextLine, numArgs: 0, variableArgs: false }, + Tj: { id: OPS.showText, numArgs: 1, variableArgs: false }, + TJ: { id: OPS.showSpacedText, numArgs: 1, variableArgs: false }, + '\'': { id: OPS.nextLineShowText, numArgs: 1, variableArgs: false }, + '"': { id: OPS.nextLineSetSpacingShowText, numArgs: 3, + variableArgs: false }, + + // Type3 fonts + d0: { id: OPS.setCharWidth, numArgs: 2, variableArgs: false }, + d1: { id: OPS.setCharWidthAndBounds, numArgs: 6, variableArgs: false }, + + // Color + CS: { id: OPS.setStrokeColorSpace, numArgs: 1, variableArgs: false }, + cs: { id: OPS.setFillColorSpace, numArgs: 1, variableArgs: false }, + SC: { id: OPS.setStrokeColor, numArgs: 4, variableArgs: true }, + SCN: { id: OPS.setStrokeColorN, numArgs: 33, variableArgs: true }, + sc: { id: OPS.setFillColor, numArgs: 4, variableArgs: true }, + scn: { id: OPS.setFillColorN, numArgs: 33, variableArgs: true }, + G: { id: OPS.setStrokeGray, numArgs: 1, variableArgs: false }, + g: { id: OPS.setFillGray, numArgs: 1, variableArgs: false }, + RG: { id: OPS.setStrokeRGBColor, numArgs: 3, variableArgs: false }, + rg: { id: OPS.setFillRGBColor, numArgs: 3, variableArgs: false }, + K: { id: OPS.setStrokeCMYKColor, numArgs: 4, variableArgs: false }, + k: { id: OPS.setFillCMYKColor, numArgs: 4, variableArgs: false }, + + // Shading + sh: { id: OPS.shadingFill, numArgs: 1, variableArgs: false }, + + // Images + BI: { id: OPS.beginInlineImage, numArgs: 0, variableArgs: false }, + ID: { id: OPS.beginImageData, numArgs: 0, variableArgs: false }, + EI: { id: OPS.endInlineImage, numArgs: 1, variableArgs: false }, + + // XObjects + Do: { id: OPS.paintXObject, numArgs: 1, variableArgs: false }, + MP: { id: OPS.markPoint, numArgs: 1, variableArgs: false }, + DP: { id: OPS.markPointProps, numArgs: 2, variableArgs: false }, + BMC: { id: OPS.beginMarkedContent, numArgs: 1, variableArgs: false }, + BDC: { id: OPS.beginMarkedContentProps, numArgs: 2, + variableArgs: false }, + EMC: { id: OPS.endMarkedContent, numArgs: 0, variableArgs: false }, + + // Compatibility + BX: { id: OPS.beginCompat, numArgs: 0, variableArgs: false }, + EX: { id: OPS.endCompat, numArgs: 0, variableArgs: false }, + + // (reserved partial commands for the lexer) + BM: null, + BD: null, + 'true': null, + fa: null, + fal: null, + fals: null, + 'false': null, + nu: null, + nul: null, + 'null': null + }; + + function EvaluatorPreprocessor(stream, xref) { + // TODO(mduan): pass array of knownCommands rather than OP_MAP + // dictionary + this.parser = new Parser(new Lexer(stream, OP_MAP), false, xref); + this.ctm = new Float32Array([1, 0, 0, 1, 0, 0]); + this.savedStates = []; + } + EvaluatorPreprocessor.prototype = { + get savedStatesDepth() { + return this.savedStates.length; + }, + read: function EvaluatorPreprocessor_read() { + var args = []; + while (true) { + var obj = this.parser.getObj(); + if (isEOF(obj)) { + return null; // no more commands + } + if (!isCmd(obj)) { + // argument + if (obj !== null && obj !== undefined) { + args.push(obj instanceof Dict ? obj.getAll() : obj); + assertWellFormed(args.length <= 33, 'Too many arguments'); + } + continue; + } + + var cmd = obj.cmd; + // Check that the command is valid + var opSpec = OP_MAP[cmd]; + if (!opSpec) { + warn('Unknown command "' + cmd + '"'); + continue; + } + + var fn = opSpec.id; + + // Validate the number of arguments for the command + if (opSpec.variableArgs) { + if (args.length > opSpec.numArgs) { + info('Command ' + fn + ': expected [0,' + opSpec.numArgs + + '] args, but received ' + args.length + ' args'); + } + } else { + if (args.length < opSpec.numArgs) { + // If we receive too few args, it's not possible to possible + // to execute the command, so skip the command + info('Command ' + fn + ': because expected ' + + opSpec.numArgs + ' args, but received ' + args.length + + ' args; skipping'); + args = []; + continue; + } else if (args.length > opSpec.numArgs) { + info('Command ' + fn + ': expected ' + opSpec.numArgs + + ' args, but received ' + args.length + ' args'); + } + } + + // TODO figure out how to type-check vararg functions + + this.preprocessCommand(fn, args); + + return {fn: fn, args: args}; + } + }, + getState: function EvaluatorPreprocessor_getState() { + return { + ctm: this.ctm + }; + }, + setState: function EvaluatorPreprocessor_setState(state) { + this.ctm = state.ctm; + }, + preprocessCommand: function EvaluatorPreprocessor_preprocessCommand(fn, + args) { + switch (fn | 0) { + case OPS.save: + this.savedStates.push(this.getState()); + break; + case OPS.restore: + var previousState = this.savedStates.pop(); + if (previousState) { + this.setState(previousState); + } + break; + case OPS.transform: + var ctm = this.ctm; + var m = new Float32Array(6); + m[0] = ctm[0] * args[0] + ctm[2] * args[1]; + m[1] = ctm[1] * args[0] + ctm[3] * args[1]; + m[2] = ctm[0] * args[2] + ctm[2] * args[3]; + m[3] = ctm[1] * args[2] + ctm[3] * args[3]; + m[4] = ctm[0] * args[4] + ctm[2] * args[5] + ctm[4]; + m[5] = ctm[1] * args[4] + ctm[3] * args[5] + ctm[5]; + this.ctm = m; + break; + } + } + }; + return EvaluatorPreprocessor; +})(); // Unicode Private Use Area @@ -15848,8 +16444,8 @@ var Encodings = { 'oacute', 'ocircumflex', 'otilde', 'odieresis', 'divide', 'oslash', 'ugrave', 'uacute', 'ucircumflex', 'udieresis', 'yacute', 'thorn', 'ydieresis'], - symbolsEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', '', - '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + SymbolSetEncoding: ['', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'space', 'exclam', 'universal', 'numbersign', 'existential', 'percent', 'ampersand', 'suchthat', 'parenleft', 'parenright', 'asteriskmath', 'plus', 'comma', 'minus', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four', @@ -16078,6 +16674,76 @@ var HalfwidthCMaps = { 'UniJIS-UCS2-HW-V': true }; +// Glyph map for well-known standard fonts. Sometimes Ghostscript uses CID fonts +// but does not embed the CID to GID mapping. The mapping is incomplete for all +// glyphs, but common for some set of the standard fonts. +var GlyphMapForStandardFonts = { + '2': 10, '3': 32, '4': 33, '5': 34, '6': 35, '7': 36, '8': 37, '9': 38, + '10': 39, '11': 40, '12': 41, '13': 42, '14': 43, '15': 44, '16': 173, + '17': 46, '18': 47, '19': 48, '20': 49, '21': 50, '22': 51, '23': 52, + '24': 53, '25': 54, '26': 55, '27': 56, '28': 57, '29': 58, '30': 894, + '31': 60, '32': 61, '33': 62, '34': 63, '35': 64, '36': 65, '37': 66, + '38': 67, '39': 68, '40': 69, '41': 70, '42': 71, '43': 72, '44': 73, + '45': 74, '46': 75, '47': 76, '48': 77, '49': 78, '50': 79, '51': 80, + '52': 81, '53': 82, '54': 83, '55': 84, '56': 85, '57': 86, '58': 87, + '59': 88, '60': 89, '61': 90, '62': 91, '63': 92, '64': 93, '65': 94, + '66': 95, '67': 96, '68': 97, '69': 98, '70': 99, '71': 100, '72': 101, + '73': 102, '74': 103, '75': 104, '76': 105, '77': 106, '78': 107, '79': 108, + '80': 109, '81': 110, '82': 111, '83': 112, '84': 113, '85': 114, '86': 115, + '87': 116, '88': 117, '89': 118, '90': 119, '91': 120, '92': 121, '93': 122, + '94': 123, '95': 124, '96': 125, '97': 126, '98': 196, '99': 197, '100': 199, + '101': 201, '102': 209, '103': 214, '104': 220, '105': 225, '106': 224, + '107': 226, '108': 228, '109': 227, '110': 229, '111': 231, '112': 233, + '113': 232, '114': 234, '115': 235, '116': 237, '117': 236, '118': 238, + '119': 239, '120': 241, '121': 243, '122': 242, '123': 244, '124': 246, + '125': 245, '126': 250, '127': 249, '128': 251, '129': 252, '130': 8224, + '131': 176, '132': 162, '133': 163, '134': 167, '135': 8226, '136': 182, + '137': 223, '138': 174, '139': 169, '140': 8482, '141': 180, '142': 168, + '143': 8800, '144': 198, '145': 216, '146': 8734, '147': 177, '148': 8804, + '149': 8805, '150': 165, '151': 181, '152': 8706, '153': 8721, '154': 8719, + '156': 8747, '157': 170, '158': 186, '159': 8486, '160': 230, '161': 248, + '162': 191, '163': 161, '164': 172, '165': 8730, '166': 402, '167': 8776, + '168': 8710, '169': 171, '170': 187, '171': 8230, '210': 218, '305': 963, + '306': 964, '307': 966, '308': 8215, '309': 8252, '310': 8319, '311': 8359, + '312': 8592, '313': 8593, '337': 9552, '493': 1039, '494': 1040, '705': 1524, + '706': 8362, '710': 64288, '711': 64298, '759': 1617, '761': 1776, + '763': 1778, '775': 1652, '777': 1764, '778': 1780, '779': 1781, '780': 1782, + '782': 771, '783': 64726, '786': 8363, '788': 8532, '790': 768, '791': 769, + '792': 768, '795': 803, '797': 64336, '798': 64337, '799': 64342, + '800': 64343, '801': 64344, '802': 64345, '803': 64362, '804': 64363, + '805': 64364, '2424': 7821, '2425': 7822, '2426': 7823, '2427': 7824, + '2428': 7825, '2429': 7826, '2430': 7827, '2433': 7682, '2678': 8045, + '2679': 8046, '2830': 1552, '2838': 686, '2840': 751, '2842': 753, + '2843': 754, '2844': 755, '2846': 757, '2856': 767, '2857': 848, '2858': 849, + '2862': 853, '2863': 854, '2864': 855, '2865': 861, '2866': 862, '2906': 7460, + '2908': 7462, '2909': 7463, '2910': 7464, '2912': 7466, '2913': 7467, + '2914': 7468, '2916': 7470, '2917': 7471, '2918': 7472, '2920': 7474, + '2921': 7475, '2922': 7476, '2924': 7478, '2925': 7479, '2926': 7480, + '2928': 7482, '2929': 7483, '2930': 7484, '2932': 7486, '2933': 7487, + '2934': 7488, '2936': 7490, '2937': 7491, '2938': 7492, '2940': 7494, + '2941': 7495, '2942': 7496, '2944': 7498, '2946': 7500, '2948': 7502, + '2950': 7504, '2951': 7505, '2952': 7506, '2954': 7508, '2955': 7509, + '2956': 7510, '2958': 7512, '2959': 7513, '2960': 7514, '2962': 7516, + '2963': 7517, '2964': 7518, '2966': 7520, '2967': 7521, '2968': 7522, + '2970': 7524, '2971': 7525, '2972': 7526, '2974': 7528, '2975': 7529, + '2976': 7530, '2978': 1537, '2979': 1538, '2980': 1539, '2982': 1549, + '2983': 1551, '2984': 1552, '2986': 1554, '2987': 1555, '2988': 1556, + '2990': 1623, '2991': 1624, '2995': 1775, '2999': 1791, '3002': 64290, + '3003': 64291, '3004': 64292, '3006': 64294, '3007': 64295, '3008': 64296, + '3011': 1900, '3014': 8223, '3015': 8244, '3017': 7532, '3018': 7533, + '3019': 7534, '3075': 7590, '3076': 7591, '3079': 7594, '3080': 7595, + '3083': 7598, '3084': 7599, '3087': 7602, '3088': 7603, '3091': 7606, + '3092': 7607, '3095': 7610, '3096': 7611, '3099': 7614, '3100': 7615, + '3103': 7618, '3104': 7619, '3107': 8337, '3108': 8338, '3116': 1884, + '3119': 1885, '3120': 1885, '3123': 1886, '3124': 1886, '3127': 1887, + '3128': 1887, '3131': 1888, '3132': 1888, '3135': 1889, '3136': 1889, + '3139': 1890, '3140': 1890, '3143': 1891, '3144': 1891, '3147': 1892, + '3148': 1892, '3153': 580, '3154': 581, '3157': 584, '3158': 585, '3161': 588, + '3162': 589, '3165': 891, '3166': 892, '3169': 1274, '3170': 1275, + '3173': 1278, '3174': 1279, '3181': 7622, '3182': 7623, '3282': 11799, + '3316': 578, '3379': 42785, '3393': 1159, '3416': 8377 +}; + var decodeBytes; if (typeof TextDecoder !== 'undefined') { // The encodings supported by TextDecoder can be found at: @@ -16117,7 +16783,7 @@ function sjis83pvToUnicode(str) { // TODO: 83pv has incompatible mappings in ed40..ee9c range. return decodeBytes(bytes, 'shift_jis', true); } catch (e) { - TODO('Unsupported 83pv character found'); + warn('Unsupported 83pv character found'); // Just retry without checking errors for now. return decodeBytes(bytes, 'shift_jis'); } @@ -16129,7 +16795,7 @@ function sjis90pvToUnicode(str) { // TODO: 90pv has incompatible mappings in 8740..879c and eb41..ee9c. return decodeBytes(bytes, 'shift_jis', true); } catch (e) { - TODO('Unsupported 90pv character found'); + warn('Unsupported 90pv character found'); // Just retry without checking errors for now. return decodeBytes(bytes, 'shift_jis'); } @@ -17805,6 +18471,8 @@ var Font = (function FontClosure() { this.wideChars = properties.wideChars; this.hasEncoding = properties.hasEncoding; this.cmap = properties.cmap; + this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS; + this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS; this.fontMatrix = properties.fontMatrix; if (properties.type == 'Type3') { @@ -17832,6 +18500,7 @@ var Font = (function FontClosure() { // The file data is not specified. Trying to fix the font name // to be used with the canvas.font. var fontName = name.replace(/[,_]/g, '-'); + var isStandardFont = fontName in stdFontMap; fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName; this.bold = (fontName.search(/bold/gi) != -1); @@ -17847,6 +18516,17 @@ var Font = (function FontClosure() { this.encoding = properties.baseEncoding; this.noUnicodeAdaptation = true; + if (isStandardFont && type === 'CIDFontType2' && + properties.cidEncoding.indexOf('Identity-') === 0) { + // Standard fonts might be embedded as CID font without glyph mapping. + // Building one based on GlyphMapForStandardFonts. + var map = []; + for (var code in GlyphMapForStandardFonts) { + map[+code] = GlyphMapForStandardFonts[code]; + } + this.toFontChar = map; + this.toUnicode = map; + } this.loadedName = fontName.split('-')[0]; this.loading = false; return; @@ -18699,6 +19379,17 @@ var Font = (function FontClosure() { error('cmap table has unsupported format: ' + format); } + // removing duplicate entries + mappings.sort(function (a, b) { + return a.charcode - b.charcode; + }); + for (var i = 1; i < mappings.length; i++) { + if (mappings[i - 1].charcode === mappings[i].charcode) { + mappings.splice(i, 1); + i--; + } + } + return { platformId: potentialTable.platformId, encodingId: potentialTable.encodingId, @@ -18774,8 +19465,8 @@ var Font = (function FontClosure() { for (var i = 0; i < flagsCount; i++) { var flag = glyf[j++]; if (flag & 0xC0) { - // reserved flags must be zero, rejecting - return 0; + // reserved flags must be zero, cleaning up + glyf[j - 1] = flag & 0x3F; } var xyLength = ((flag & 2) ? 1 : (flag & 16) ? 0 : 2) + ((flag & 4) ? 1 : (flag & 32) ? 0 : 2); @@ -18786,6 +19477,10 @@ var Font = (function FontClosure() { coordinatesLength += repeat * xyLength; } } + // glyph without coordinates will be rejected + if (coordinatesLength === 0) { + return 0; + } var glyphDataLength = j + coordinatesLength; if (glyphDataLength > glyf.length) { // not enough data for coordinates @@ -18884,6 +19579,13 @@ var Font = (function FontClosure() { }; } var locaData = loca.data; + var locaDataSize = itemSize * (1 + numGlyphs); + // is loca.data too short or long? + if (locaData.length !== locaDataSize) { + locaData = new Uint8Array(locaDataSize); + locaData.set(loca.data.subarray(0, locaDataSize)); + loca.data = locaData; + } // removing the invalid glyphs var oldGlyfData = glyf.data; var oldGlyfDataLength = oldGlyfData.length; @@ -18927,9 +19629,7 @@ var Font = (function FontClosure() { glyf.data.set(newGlyfData.subarray(0, writeOffset)); } glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset); - loca.data = new Uint8Array(locaData.length + itemSize); - loca.data.set(locaData); - itemEncode(loca.data, locaData.length, + itemEncode(loca.data, locaData.length - itemSize, writeOffset + firstEntryLength); } else { glyf.data = newGlyfData.subarray(0, writeOffset); @@ -19129,6 +19829,11 @@ var Font = (function FontClosure() { callstack.push({data: data, i: i, stackTop: stack.length - 1}); functionsCalled.push(funcId); var pc = ttContext.functionsDefined[funcId]; + if (!pc) { + warn('TT: CALL non-existent function'); + ttContext.hintsValid = false; + return; + } data = pc.data; i = pc.i; } @@ -19149,6 +19854,11 @@ var Font = (function FontClosure() { lastEndf = i; } else { var pc = callstack.pop(); + if (!pc) { + warn('TT: ENDF bad stack'); + ttContext.hintsValid = false; + return; + } var funcId = functionsCalled.pop(); data = pc.data; i = pc.i; @@ -19248,7 +19958,7 @@ var Font = (function FontClosure() { } } - function sanitizeTTPrograms(fpgm, prep) { + function sanitizeTTPrograms(fpgm, prep, cvt) { var ttContext = { functionsDefined: [], functionsUsed: [], @@ -19265,6 +19975,11 @@ var Font = (function FontClosure() { if (fpgm) { checkInvalidFunctions(ttContext, maxFunctionDefs); } + if (cvt && (cvt.length & 1)) { + var cvtData = new Uint8Array(cvt.length + 1); + cvtData.set(cvt.data); + cvt.data = cvtData; + } return ttContext.hintsValid; } @@ -19284,6 +19999,9 @@ var Font = (function FontClosure() { if (VALID_TABLES.indexOf(table.tag) < 0) { continue; // skipping table if it's not a required or optional table } + if (table.length === 0) { + continue; // skipping empty tables + } tables[table.tag] = table; } @@ -19318,7 +20036,14 @@ var Font = (function FontClosure() { var numGlyphs = int16(font.getBytes(2)); var maxFunctionDefs = 0; if (version >= 0x00010000 && tables.maxp.length >= 22) { - font.pos += 14; + // maxZones can be invalid + font.pos += 8; + var maxZones = int16(font.getBytes(2)); + if (maxZones > 2) { // reset to 2 if font has invalid maxZones + tables.maxp.data[14] = 0; + tables.maxp.data[15] = 2; + } + font.pos += 4; maxFunctionDefs = int16(font.getBytes(2)); } @@ -19333,10 +20058,11 @@ var Font = (function FontClosure() { } var hintsValid = sanitizeTTPrograms(tables.fpgm, tables.prep, - maxFunctionDefs); + tables['cvt '], maxFunctionDefs); if (!hintsValid) { delete tables.fpgm; delete tables.prep; + delete tables['cvt ']; } // Tables needs to be written by ascendant alphabetic order @@ -19370,7 +20096,6 @@ var Font = (function FontClosure() { if (isTrueType) { var isGlyphLocationsLong = int16([tables.head.data[50], tables.head.data[51]]); - sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry); } @@ -19916,7 +20641,7 @@ var Font = (function FontClosure() { var cidEncoding = properties.cidEncoding; if (properties.toUnicode) { if (cidEncoding && cidEncoding.indexOf('Identity-') !== 0) { - TODO('Need to create a reverse mapping from \'ToUnicode\' CMap'); + warn('Need to create a reverse mapping from \'ToUnicode\' CMap'); } return; // 'ToUnicode' CMap will be used } @@ -20174,8 +20899,10 @@ var Font = (function FontClosure() { var glyph = this.charToGlyph(charcode); glyphs.push(glyph); // placing null after each word break charcode (ASCII SPACE) - if (charcode == 0x20) + // Ignore occurences of 0x20 in multiple-byte codes. + if (length === 1 && chars.charCodeAt(i - 1) === 0x20) { glyphs.push(null); + } } } else { @@ -20817,13 +21544,21 @@ var Type1Parser = (function Type1ParserClosure() { for (var j = 0; j < size; j++) { var token = this.getToken(); - if (token === 'dup') { - var index = this.readInt(); - this.getToken(); // read in '/' - var glyph = this.getToken(); - encoding[index] = glyph; - this.getToken(); // read the in 'put' + // skipping till first dup or def (e.g. ignoring for statement) + while (token !== 'dup' && token !== 'def') { + token = this.getToken(); + if (token === null) { + return; // invalid header + } } + if (token === 'def') { + break; // read all array data + } + var index = this.readInt(); + this.getToken(); // read in '/' + var glyph = this.getToken(); + encoding[index] = glyph; + this.getToken(); // read the in 'put' } } if (properties.overridableEncoding && encoding) { @@ -20831,6 +21566,13 @@ var Type1Parser = (function Type1ParserClosure() { break; } break; + case 'FontBBox': + var fontBBox = this.readNumberArray(); + // adjusting ascent/descent + properties.ascent = fontBBox[3]; + properties.descent = fontBBox[1]; + properties.ascentScaled = true; + break; } } } @@ -20913,13 +21655,33 @@ var CFFStandardStrings = [ // Type1Font is also a CIDFontType0. var Type1Font = function Type1Font(name, file, properties) { + // Some bad generators embed pfb file as is, we have to strip 6-byte headers. + // Also, length1 and length2 might be off by 6 bytes as well. + // http://www.math.ubc.ca/~cass/piscript/type1.pdf + var PFB_HEADER_SIZE = 6; + var headerBlockLength = properties.length1; + var eexecBlockLength = properties.length2; + var pfbHeader = file.peekBytes(PFB_HEADER_SIZE); + var pfbHeaderPresent = pfbHeader[0] == 0x80 && pfbHeader[1] == 0x01; + if (pfbHeaderPresent) { + file.skip(PFB_HEADER_SIZE); + headerBlockLength = (pfbHeader[5] << 24) | (pfbHeader[4] << 16) | + (pfbHeader[3] << 8) | pfbHeader[2]; + } + // Get the data block containing glyphs and subrs informations - var headerBlock = new Stream(file.getBytes(properties.length1)); + var headerBlock = new Stream(file.getBytes(headerBlockLength)); var headerBlockParser = new Type1Parser(headerBlock); headerBlockParser.extractFontHeader(properties); + if (pfbHeaderPresent) { + pfbHeader = file.getBytes(PFB_HEADER_SIZE); + eexecBlockLength = (pfbHeader[5] << 24) | (pfbHeader[4] << 16) | + (pfbHeader[3] << 8) | pfbHeader[2]; + } + // Decrypt the data blocks and retrieve it's content - var eexecBlock = new Stream(file.getBytes(properties.length2)); + var eexecBlock = new Stream(file.getBytes(eexecBlockLength)); var eexecBlockParser = new Type1Parser(eexecBlock, true); var data = eexecBlockParser.extractFontProgram(); for (var info in data.properties) @@ -21151,6 +21913,9 @@ var CFFFont = (function CFFFontClosure() { var unassignedUnicodeItems = []; var inverseEncoding = []; var gidStart = 0; + if (charsets[0] === '.notdef') { + gidStart = 1; + } // According to section 9.7.4.2 CIDFontType0C glyph selection should be // handled differently. if (this.properties.subtype === 'CIDFontType0C') { @@ -21183,9 +21948,6 @@ var CFFFont = (function CFFFontClosure() { inverseEncoding[gid] = charcode | 0; } } - if (charsets[0] === '.notdef') { - gidStart = 1; - } } for (var i = gidStart, ii = charsets.length; i < ii; i++) { @@ -27560,7 +28322,7 @@ var PDFImage = (function PDFImageClosure() { if (image.getParams) { // JPX/JPEG2000 streams directly contain bits per component // and color space mode information. - TODO('get params from actual stream'); + warn('get params from actual stream'); // var bits = ... // var colorspace = ... } @@ -27593,7 +28355,7 @@ var PDFImage = (function PDFImageClosure() { if (!this.imageMask) { var colorSpace = dict.get('ColorSpace', 'CS'); if (!colorSpace) { - TODO('JPX images (which don"t require color spaces'); + warn('JPX images (which don"t require color spaces'); colorSpace = new Name('DeviceRGB'); } this.colorSpace = ColorSpace.parse(colorSpace, xref, res); @@ -27635,9 +28397,9 @@ var PDFImage = (function PDFImageClosure() { */ PDFImage.buildImage = function PDFImage_buildImage(callback, handler, xref, res, image, inline) { - var imageDataPromise = new Promise(); - var smaskPromise = new Promise(); - var maskPromise = new Promise(); + var imageDataPromise = new LegacyPromise(); + var smaskPromise = new LegacyPromise(); + var maskPromise = new LegacyPromise(); // The image data and smask data may not be ready yet, wait till both are // resolved. Promise.all([imageDataPromise, smaskPromise, maskPromise]).then( @@ -27715,26 +28477,21 @@ var PDFImage = (function PDFImageClosure() { PDFImage.createMask = function PDFImage_createMask(imgArray, width, height, inverseDecode) { - var buffer = new Uint8Array(width * height * 4); - var imgArrayPos = 0; - var i, j, mask, buf; - // removing making non-masked pixels transparent - var bufferPos = 3; // alpha component offset - for (i = 0; i < height; i++) { - mask = 0; - for (j = 0; j < width; j++) { - if (!mask) { - buf = imgArray[imgArrayPos++]; - mask = 128; - } - if (!(buf & mask) !== inverseDecode) { - buffer[bufferPos] = 255; - } - bufferPos += 4; - mask >>= 1; + // Copy imgArray into a typed array (inverting if necessary) so it can be + // transferred to the main thread. + var length = ((width + 7) >> 3) * height; + var data = new Uint8Array(length); + if (inverseDecode) { + for (var i = 0; i < length; i++) { + data[i] = ~imgArray[i]; + } + } else { + for (var i = 0; i < length; i++) { + data[i] = imgArray[i]; } } - return {data: buffer, width: width, height: height}; + + return {data: data, width: width, height: height}; }; PDFImage.prototype = { @@ -30963,21 +31720,6 @@ var Parser = (function ParserClosure() { } Parser.prototype = { - saveState: function Parser_saveState() { - this.state = { - buf1: this.buf1, - buf2: this.buf2, - streamPos: this.lexer.stream.pos - }; - }, - - restoreState: function Parser_restoreState() { - var state = this.state; - this.buf1 = state.buf1; - this.buf2 = state.buf2; - this.lexer.stream.pos = state.streamPos; - }, - refill: function Parser_refill() { this.buf1 = this.lexer.getObj(); this.buf2 = this.lexer.getObj(); @@ -32182,12 +32924,18 @@ var FlateStream = (function FlateStreamClosure() { var buffer = this.ensureBuffer(bufferLength + blockLen); var end = bufferLength + blockLen; this.bufferLength = end; - for (var n = bufferLength; n < end; ++n) { - if (typeof (b = bytes[bytesPos++]) == 'undefined') { + if (blockLen === 0) { + if (typeof bytes[bytesPos] == 'undefined') { this.eof = true; - break; } - buffer[n] = b; + } else { + for (var n = bufferLength; n < end; ++n) { + if (typeof (b = bytes[bytesPos++]) == 'undefined') { + this.eof = true; + break; + } + buffer[n] = b; + } } this.bytesPos = bytesPos; return; @@ -32506,7 +33254,7 @@ var JpegStream = (function JpegStreamClosure() { } }; JpegStream.prototype.getIR = function JpegStream_getIR() { - return bytesToString(this.bytes); + return PDFJS.createObjectURL(this.bytes, 'image/jpeg'); }; /** * Checks if the image can be decoded and displayed by the browser without any @@ -32650,6 +33398,16 @@ var Jbig2Stream = (function Jbig2StreamClosure() { var jbig2Image = new Jbig2Image(); var chunks = [], decodeParams = this.dict.get('DecodeParms'); + + // According to the PDF specification, DecodeParms can be either + // a dictionary, or an array whose elements are dictionaries. + if (isArray(decodeParams)) { + if (decodeParams.length > 1) { + warn('JBIG2 - \'DecodeParms\' array with multiple elements ' + + 'not supported.'); + } + decodeParams = decodeParams[0]; + } if (decodeParams && decodeParams.has('JBIG2Globals')) { var globalsStream = decodeParams.get('JBIG2Globals'); var globals = globalsStream.getBytes(); @@ -34002,7 +34760,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { var pdfManager; function loadDocument(recoveryMode) { - var loadDocumentPromise = new Promise(); + var loadDocumentPromise = new LegacyPromise(); var parseSuccess = function parseSuccess() { var numPagesPromise = pdfManager.ensureModel('numPages'); @@ -34046,7 +34804,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { } function getPdfManager(data) { - var pdfManagerPromise = new Promise(); + var pdfManagerPromise = new LegacyPromise(); var source = data.source; var disableRange = data.disableRange; @@ -34069,7 +34827,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { } var networkManager = new NetworkManager(source.url, { - httpHeaders: source.httpHeaders + httpHeaders: source.httpHeaders, + withCredentials: source.withCredentials }); var fullRequestXhrId = networkManager.requestFull({ onHeadersReceived: function onHeadersReceived() { @@ -34148,6 +34907,9 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { handler.send('test', false); return; } + // making sure postMessage transfers are working + var supportTransfers = data[0] === 255; + handler.postMessageTransfers = supportTransfers; // check if the response property is supported by xhr var xhr = new XMLHttpRequest(); var responseExists = 'response' in xhr; @@ -34161,14 +34923,16 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { handler.send('test', false); return; } - handler.send('test', true); + handler.send('test', { + supportTypedArray: true, + supportTransfers: supportTransfers + }); }); handler.on('GetDocRequest', function wphSetupDoc(data) { var onSuccess = function(doc) { handler.send('GetDoc', { pdfInfo: doc }); - pdfManager.ensureModel('traversePages', []).then(null, onFailure); }; var onFailure = function(e) { @@ -34200,6 +34964,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { PDFJS.maxImageSize = data.maxImageSize === undefined ? -1 : data.maxImageSize; PDFJS.disableFontFace = data.disableFontFace; + PDFJS.disableCreateObjectURL = data.disableCreateObjectURL; + PDFJS.verbosity = data.verbosity; getPdfManager(data).then(function pdfManagerReady() { loadDocument(false).then(onSuccess, function loadFailure(ex) { @@ -34208,7 +34974,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { if (ex instanceof PasswordException) { // after password exception prepare to receive a new password // to repeat loading - pdfManager.passwordChangedPromise = new Promise(); + pdfManager.passwordChangedPromise = + new LegacyPromise(); pdfManager.passwordChangedPromise.then(pdfManagerReady); } @@ -34245,24 +35012,31 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { }); }); + handler.on('GetPageIndex', function wphSetupGetPageIndex(data, deferred) { + var ref = new Ref(data.ref.num, data.ref.gen); + pdfManager.pdfModel.catalog.getPageIndex(ref).then(function (pageIndex) { + deferred.resolve(pageIndex); + }, deferred.reject); + }); + handler.on('GetDestinations', - function wphSetupGetDestinations(data, promise) { + function wphSetupGetDestinations(data, deferred) { pdfManager.ensureCatalog('destinations').then(function(destinations) { - promise.resolve(destinations); + deferred.resolve(destinations); }); } ); - handler.on('GetData', function wphSetupGetData(data, promise) { + handler.on('GetData', function wphSetupGetData(data, deferred) { pdfManager.requestLoadedStream(); pdfManager.onLoadedStream().then(function(stream) { - promise.resolve(stream.bytes); + deferred.resolve(stream.bytes); }); }); - handler.on('DataLoaded', function wphSetupDataLoaded(data, promise) { + handler.on('DataLoaded', function wphSetupDataLoaded(data, deferred) { pdfManager.onLoadedStream().then(function(stream) { - promise.resolve({ length: stream.bytes.byteLength }); + deferred.resolve({ length: stream.bytes.byteLength }); }); }); @@ -34291,7 +35065,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { // Pre compile the pdf page and fetch the fonts/images. page.getOperatorList(handler).then(function(operatorList) { - log('page=%d - getOperatorList: time=%dms, len=%d', pageNum, + info('page=%d - getOperatorList: time=%dms, len=%d', pageNum, Date.now() - start, operatorList.fnArray.length); }, function(e) { @@ -34327,24 +35101,29 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { }); }, this); - handler.on('GetTextContent', function wphExtractText(data, promise) { + handler.on('GetTextContent', function wphExtractText(data, deferred) { pdfManager.getPage(data.pageIndex).then(function(page) { var pageNum = data.pageIndex + 1; var start = Date.now(); page.extractTextContent().then(function(textContent) { - promise.resolve(textContent); - log('text indexing: page=%d - time=%dms', pageNum, + deferred.resolve(textContent); + info('text indexing: page=%d - time=%dms', pageNum, Date.now() - start); }, function (e) { // Skip errored pages - promise.reject(e); + deferred.reject(e); }); }); }); - handler.on('Terminate', function wphTerminate(data, promise) { + handler.on('Cleanup', function wphCleanup(data, deferred) { + pdfManager.cleanup(); + deferred.resolve(true); + }); + + handler.on('Terminate', function wphTerminate(data, deferred) { pdfManager.terminate(); - promise.resolve(); + deferred.resolve(); }); } }; @@ -34376,25 +35155,25 @@ var workerConsole = { timeEnd: function timeEnd(name) { var time = consoleTimer[name]; if (!time) { - error('Unkown timer name ' + name); + error('Unknown timer name ' + name); } this.log('Timer:', name, Date.now() - time); } }; + // Worker thread? if (typeof window === 'undefined') { - globalScope.console = workerConsole; + if (!('console' in globalScope)) { + globalScope.console = workerConsole; + } - // Add a logger so we can pass warnings on to the main thread, errors will - // throw an exception which will be forwarded on automatically. - PDFJS.LogManager.addLogger({ - warn: function(msg) { - globalScope.postMessage({ - action: '_warn', - data: msg - }); - } + // Listen for unsupported features so we can pass them on to the main thread. + PDFJS.UnsupportedManager.listen(function (msg) { + globalScope.postMessage({ + action: '_unsupported_feature', + data: msg + }); }); var handler = new MessageHandler('worker_processor', this); @@ -36076,6 +36855,19 @@ var JpxImage = (function JpxImageClosure() { } return ll; }; + Transform.prototype.expand = function expand(buffer, bufferPadding, step) { + // Section F.3.7 extending... using max extension of 4 + var i1 = bufferPadding - 1, j1 = bufferPadding + 1; + var i2 = bufferPadding + step - 2, j2 = bufferPadding + step; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + }; Transform.prototype.iterate = function Transform_iterate(ll, hl, lh, hh, u0, v0) { var llWidth = ll.width, llHeight = ll.height, llItems = ll.items; @@ -36129,18 +36921,7 @@ var JpxImage = (function JpxImageClosure() { for (var u = 0; u < width; u++, k++, l++) buffer[l] = items[k]; - // Section F.3.7 extending... using max extension of 4 - var i1 = bufferPadding - 1, j1 = bufferPadding + 1; - var i2 = bufferPadding + width - 2, j2 = bufferPadding + width; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - + this.expand(buffer, bufferPadding, width); this.filter(buffer, bufferPadding, width, u0, bufferOut); k = v * width; @@ -36164,18 +36945,7 @@ var JpxImage = (function JpxImageClosure() { for (var v = 0; v < height; v++, k += width, l++) buffer[l] = items[k]; - // Section F.3.7 extending... using max extension of 4 - var i1 = bufferPadding - 1, j1 = bufferPadding + 1; - var i2 = bufferPadding + height - 2, j2 = bufferPadding + height; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - + this.expand(buffer, bufferPadding, height); this.filter(buffer, bufferPadding, height, v0, bufferOut); k = u; diff --git a/plugins/pdfviewer/viewer/viewer.css b/plugins/pdfviewer/viewer/viewer.css index 1b367764..9c31d6a1 100644 --- a/plugins/pdfviewer/viewer/viewer.css +++ b/plugins/pdfviewer/viewer/viewer.css @@ -20,6 +20,8 @@ html { height: 100%; + /* Font size is needed to make the activity bar the correct size. */ + font-size: 10px; } body { @@ -45,8 +47,7 @@ select { #viewerContainer:-webkit-full-screen { top: 0px; border-top: 2px solid transparent; - background-color: #404040; - background-image: url(images/texture.png); + background-color: #000; width: 100%; height: 100%; overflow: hidden; @@ -56,8 +57,7 @@ select { #viewerContainer:-moz-full-screen { top: 0px; border-top: 2px solid transparent; - background-color: #404040; - background-image: url(images/texture.png); + background-color: #000; width: 100%; height: 100%; overflow: hidden; @@ -74,15 +74,13 @@ select { } #viewerContainer:-ms-fullscreen::-ms-backdrop { - background-color: #404040; - background-image: url(images/texture.png); + background-color: #000; } #viewerContainer:fullscreen { top: 0px; border-top: 2px solid transparent; - background-color: #404040; - background-image: url(images/texture.png); + background-color: #000; width: 100%; height: 100%; overflow: hidden; @@ -167,6 +165,7 @@ html[dir='rtl'] .innerCenter { #outerContainer { width: 100%; height: 100%; + position: relative; } #sidebarContainer { @@ -400,6 +399,7 @@ html[dir='rtl'] .secondaryToolbar { max-width: 200px; max-height: 400px; overflow-y: auto; + margin-bottom: -4px; } .doorHanger, @@ -894,20 +894,25 @@ html[dir="rtl"] .secondaryToolbarButton.print::before { content: url(images/toolbarButton-download.png); } -.toolbarButton.bookmark { +.toolbarButton.bookmark, +.secondaryToolbarButton.bookmark { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; - margin-top: 3px; padding-top: 4px; + text-decoration: none; +} +.secondaryToolbarButton.bookmark { + padding-top: 5px; } -#viewBookmark[href='#'] { +.bookmark[href='#'] { opacity: .5; pointer-events: none; } -.toolbarButton.bookmark::before { +.toolbarButton.bookmark::before, +.secondaryToolbarButton.bookmark::before { content: url(images/toolbarButton-bookmark.png); } @@ -944,9 +949,11 @@ html[dir="rtl"] .secondaryToolbarButton { padding-right: 24px; text-align: right; } - -#secondaryToolbarButtonContainer :last-child { - margin-bottom: 0; +html[dir="ltr"] .secondaryToolbarButton.bookmark { + padding-left: 27px; +} +html[dir="rtl"] .secondaryToolbarButton.bookmark { + padding-right: 27px; } html[dir="ltr"] .secondaryToolbarButton > span { @@ -972,6 +979,10 @@ html[dir="rtl"] .secondaryToolbarButton > span { content: url(images/secondaryToolbarButton-rotateCw.png); } +.secondaryToolbarButton.handTool::before { + content: url(images/secondaryToolbarButton-handTool.png); +} + .verticalToolbarSeparator { display: block; padding: 8px 0; @@ -1023,6 +1034,7 @@ html[dir='rtl'] .verticalToolbarSeparator { } .toolbarField.pageNumber { + -moz-appearance: textfield; /* hides the spinner in moz */ min-width: 16px; text-align: right; width: 40px; @@ -1070,6 +1082,11 @@ html[dir='rtl'] .verticalToolbarSeparator { .thumbnail { float: left; + margin-bottom: 5px; +} + +#thumbnailView > a:last-of-type > .thumbnail { + margin-bottom: 10px; } .thumbnail:not([data-loaded]) { @@ -1190,22 +1207,6 @@ html[dir='rtl'] .outlineItem > a { cursor: default; } -#findScrollView { - position: absolute; - top: 10px; - bottom: 10px; - left: 10px; - width: 280px; -} - -#sidebarControls { - position:absolute; - width: 180px; - height: 32px; - left: 15px; - bottom: 35px; -} - .canvasWrapper { overflow: hidden; } @@ -1417,6 +1418,10 @@ canvas { background: white; color: black; margin-top: 5px; + visibility: hidden; + position: fixed; + right: 0; + top: 0; } #PDFBug { @@ -1478,6 +1483,32 @@ canvas { color: black; } +.grab-to-pan-grab * { + cursor: url("images/grab.cur"), move !important; + cursor: -webkit-grab !important; + cursor: -moz-grab !important; + cursor: grab !important; +} +.grab-to-pan-grabbing, +.grab-to-pan-grabbing * { + cursor: url("images/grabbing.cur"), move !important; + cursor: -webkit-grabbing !important; + cursor: -moz-grabbing !important; + cursor: grabbing !important; +} +.grab-to-pan-grab input, +.grab-to-pan-grab textarea, +.grab-to-pan-grab button, +.grab-to-pan-grab button *, +.grab-to-pan-grab select, +.grab-to-pan-grab option { + cursor: auto !important; +} +.grab-to-pan-grab a[href], +.grab-to-pan-grab a[href] * { + cursor: pointer !important; +} + @page { margin: 0; } diff --git a/plugins/pdfviewer/viewer/viewer.html b/plugins/pdfviewer/viewer/viewer.html index 7608f1a7..f91077a5 100644 --- a/plugins/pdfviewer/viewer/viewer.html +++ b/plugins/pdfviewer/viewer/viewer.html @@ -1,4 +1,4 @@ - + - + - + @@ -88,27 +88,45 @@ limitations under the License. Presentation Mode + + + + + +
- -
- - + +
+ + @@ -134,7 +152,6 @@ limitations under the License. -
@@ -142,10 +159,22 @@ limitations under the License. Presentation Mode + + + + + +
+ + diff --git a/plugins/pdfviewer/viewer/viewer.js b/plugins/pdfviewer/viewer/viewer.js index 8500af44..b94a4443 100644 --- a/plugins/pdfviewer/viewer/viewer.js +++ b/plugins/pdfviewer/viewer/viewer.js @@ -1,93 +1,5449 @@ -var DEFAULT_URL=null,DEFAULT_SCALE="auto",DEFAULT_SCALE_DELTA=1.1,UNKNOWN_SCALE=0,CACHE_SIZE=20,CSS_UNITS=96/72,SCROLLBAR_PADDING=40,VERTICAL_PADDING=5,MIN_SCALE=0.25,MAX_SCALE=4,IMAGE_DIR="./images/",SETTINGS_MEMORY=20,ANNOT_MIN_SIZE=10,RenderingStates={INITIAL:0,RUNNING:1,PAUSED:2,FINISHED:3},FindStates={FIND_FOUND:0,FIND_NOTFOUND:1,FIND_WRAPPED:2,FIND_PENDING:3};PDFJS.workerSrc="pdf.js";var mozL10n=document.mozL10n||document.webL10n; -function getFileName(a){var b=a.indexOf("#"),c=a.indexOf("?"),b=Math.min(0a&&b.shift().destroy()}},ProgressBar=function(){function a(a,c){this.div=document.querySelector(a+" .progress");this.height=c.height||100;this.width=c.width||100;this.units=c.units||"%";this.div.style.height=this.height+this.units}a.prototype={updateBar:function(){if(this._indeterminate)this.div.classList.add("indeterminate");else{var a=this.width*this._percent/100;95=SETTINGS_MEMORY&&a.files.shift();for(var b,c=0,e=a.files.length;c=c||0>b.pageIdx)b.pageIdx=a?c-1:0,b.wrapped=!0},updateMatch:function(a){var b=FindStates.FIND_NOTFOUND,c=this.offset.wrapped;this.offset.wrapped=!1;a&&(a=this.selected.pageIdx,this.selected.pageIdx= -this.offset.pageIdx,this.selected.matchIdx=this.offset.matchIdx,b=c?FindStates.FIND_WRAPPED:FindStates.FIND_FOUND,-1!==a&&a!==this.selected.pageIdx&&this.updatePage(a));this.updateUIState(b,this.state.findPrevious);-1!==this.selected.pageIdx&&this.updatePage(this.selected.pageIdx,!0)},updateUIState:function(a,b){PDFView.supportsIntegratedFind?FirefoxCom.request("updateFindControlState",{result:a,findPrevious:b}):PDFFindBar.updateUIState(a,b)}},PDFFindBar={opened:!1,initialize:function(){this.bar= -document.getElementById("findbar");this.toggleButton=document.getElementById("viewFind");this.findField=document.getElementById("findInput");this.highlightAll=document.getElementById("findHighlightAll");this.caseSensitive=document.getElementById("findMatchCase");this.findMsg=document.getElementById("findMsg");this.findStatusIcon=document.getElementById("findStatusIcon");var a=this;this.toggleButton.addEventListener("click",function(){a.toggle()});this.findField.addEventListener("input",function(){a.dispatchEvent("")}); -this.bar.addEventListener("keydown",function(b){switch(b.keyCode){case 13:b.target===a.findField&&a.dispatchEvent("again",b.shiftKey);break;case 27:a.close()}});document.getElementById("findPrevious").addEventListener("click",function(){a.dispatchEvent("again",!0)});document.getElementById("findNext").addEventListener("click",function(){a.dispatchEvent("again",!1)});this.highlightAll.addEventListener("click",function(){a.dispatchEvent("highlightallchange")});this.caseSensitive.addEventListener("click", -function(){a.dispatchEvent("casesensitivitychange")})},dispatchEvent:function(a,b){var c=document.createEvent("CustomEvent");c.initCustomEvent("find"+a,!0,!0,{query:this.findField.value,caseSensitive:this.caseSensitive.checked,highlightAll:this.highlightAll.checked,findPrevious:b});return window.dispatchEvent(c)},updateUIState:function(a,b){var c=!1,d="",f="";switch(a){case FindStates.FIND_PENDING:f="pending";break;case FindStates.FIND_NOTFOUND:d=mozL10n.get("find_not_found",null,"Phrase not found"); -c=!0;break;case FindStates.FIND_WRAPPED:d=b?mozL10n.get("find_reached_top",null,"Reached top of document, continued from bottom"):mozL10n.get("find_reached_bottom",null,"Reached end of document, continued from top")}c?this.findField.classList.add("notFound"):this.findField.classList.remove("notFound");this.findField.setAttribute("data-status",f);this.findMsg.textContent=d},open:function(){this.opened||(this.opened=!0,this.toggleButton.classList.add("toggled"),this.bar.classList.remove("hidden"),this.findField.select(), -this.findField.focus())},close:function(){this.opened&&(this.opened=!1,this.toggleButton.classList.remove("toggled"),this.bar.classList.add("hidden"),PDFFindController.active=!1)},toggle:function(){this.opened?this.close():this.open()}},PDFView={pages:[],thumbnails:[],currentScale:UNKNOWN_SCALE,currentScaleValue:null,initialBookmark:document.location.hash.substring(1),startedTextExtraction:!1,pageText:[],container:null,thumbnailContainer:null,initialized:!1,fellback:!1,pdfDocument:null,sidebarOpen:!1, -pageViewScroll:null,thumbnailViewScroll:null,isFullscreen:!1,previousScale:null,pageRotation:0,mouseScrollTimeStamp:0,mouseScrollDelta:0,lastScroll:0,previousPageNumber:1,initialize:function(){var a=this,b=this.container=document.getElementById("viewerContainer");this.pageViewScroll={};this.watchScroll(b,this.pageViewScroll,updateViewarea);var c=this.thumbnailContainer=document.getElementById("thumbnailView");this.thumbnailViewScroll={};this.watchScroll(c,this.thumbnailViewScroll,this.renderHighestPriority.bind(this)); -PDFFindBar.initialize();PDFFindController.initialize();this.initialized=!0;b.addEventListener("scroll",function(){a.lastScroll=Date.now()},!1)},watchScroll:function(a,b,c){b.down=!0;b.lastY=a.scrollTop;a.addEventListener("scroll",function(d){d=a.scrollTop;var f=b.lastY;d>f?b.down=!0:da.clientWidth},initPassiveLoading:function(){PDFView.loadingBar||(PDFView.loadingBar=new ProgressBar("#loadingBar",{}));window.addEventListener("message",function(a){var b=a.data;if("object"===typeof b&&"pdfjsLoadAction"in b)switch(b.pdfjsLoadAction){case "progress":PDFView.progress(b.loaded/b.total);break;case "complete":if(!b.data){PDFView.error(mozL10n.get("loading_error", -null,"An error occurred while loading the PDF."),a);break}PDFView.open(b.data,0)}});FirefoxCom.requestSync("initPassiveLoading",null)},setTitleUsingUrl:function(a){this.url=a;try{this.setTitle(decodeURIComponent(getFileName(a))||a)}catch(b){this.setTitle(a)}},setTitle:function(a){document.title=a},open:function(a,b,c){var d={password:c};"string"===typeof a?(this.setTitleUsingUrl(a),d.url=a):a&&"byteLength"in a&&(d.data=a);PDFView.loadingBar||(PDFView.loadingBar=new ProgressBar("#loadingBar",{})); -this.pdfDocument=null;var f=this;f.loading=!0;PDFJS.getDocument(d).then(function(a){f.load(a,b);f.loading=!1},function(d,e){if(e&&"PasswordException"===e.name&&"needpassword"===e.code){var g=mozL10n.get("request_password",null,"PDF is protected by a password:");if((c=prompt(g))&&0this.pages.length&&(b=this.pages.length); -b&&(this.page=b,this.pages[b-1].scrollIntoView(a))}},getDestinationHash:function(a){if("string"===typeof a)return PDFView.getAnchorUrl("#"+escape(a));if(a instanceof Array){var b=a[0];if(b=b instanceof Object?this.pagesRefMap[b.num+" "+b.gen+" R"]:b+1){var b=PDFView.getAnchorUrl("#page="+b),c=a[1];"object"===typeof c&&"name"in c&&"XYZ"==c.name&&(b+="&zoom="+100*(a[4]||this.currentScale),a[2]||a[3])&&(b+=","+(a[2]||0)+","+(a[3]||0));return b}}return""},getAnchorUrl:function(a){return a},getOutputScale:function(){var a= -"devicePixelRatio"in window?window.devicePixelRatio:1;return{sx:a,sy:a,scaled:1!=a}},error:function(a,b){var c=mozL10n.get("error_version_info",{version:PDFJS.version||"?",build:PDFJS.build||"?"},"PDF.js v{{version}} (build: {{build}})")+"\n";b&&(c+=mozL10n.get("error_message",{message:b.message},"Message: {{message}}"),b.stack?c+="\n"+mozL10n.get("error_stack",{stack:b.stack},"Stack: {{stack}}"):(b.filename&&(c+="\n"+mozL10n.get("error_file",{file:b.filename},"File: {{file}}")),b.lineNumber&&(c+= -"\n"+mozL10n.get("error_line",{line:b.lineNumber},"Line: {{line}}"))));document.getElementById("loadingBox").setAttribute("hidden","true");var d=document.getElementById("errorWrapper");d.removeAttribute("hidden");document.getElementById("errorMessage").textContent=a;document.getElementById("errorClose").onclick=function(){d.setAttribute("hidden","true")};var f=document.getElementById("errorMoreInfo"),h=document.getElementById("errorShowMore"),e=document.getElementById("errorShowLess");h.onclick=function(){f.removeAttribute("hidden"); -h.setAttribute("hidden","true");e.removeAttribute("hidden")};e.onclick=function(){f.setAttribute("hidden","true");h.removeAttribute("hidden");e.setAttribute("hidden","true")};h.removeAttribute("hidden");e.setAttribute("hidden","true");f.value=c;f.rows=c.split("\n").length-1},progress:function(a){a=Math.round(100*a);PDFView.loadingBar.percent=a},load:function(a,b){function c(a,b){a.onAfterDraw=function(){b.setImage(a.canvas)}}this.pdfDocument=a;document.getElementById("errorWrapper").setAttribute("hidden", -"true");document.getElementById("loadingBox").setAttribute("hidden","true");document.getElementById("loading").textContent="";var d=document.getElementById("thumbnailView");for(d.parentNode.scrollTop=0;d.hasChildNodes();)d.removeChild(d.lastChild);"_loadingInterval"in d&&clearInterval(d._loadingInterval);for(var f=document.getElementById("viewer");f.hasChildNodes();)f.removeChild(f.lastChild);var h=a.numPages,e=a.fingerprint;document.getElementById("numPages").textContent=mozL10n.get("page_of",{pageCount:h}, -"of {{pageCount}}");document.getElementById("pageNumber").max=h;PDFView.documentFingerprint=e;var g=PDFView.store=new Settings(e),e=g.initializedPromise;this.pageRotation=0;var k=this.pages=[];this.pageText=[];this.startedTextExtraction=!1;for(var n={},l=this.thumbnails=[],m=[],s=1;s<=h;s++)m.push(a.getPage(s));var r=this,m=PDFJS.Promise.all(m);m.then(function(a){for(var p=1;p<=h;p++){var e=a[p-1],g=new PageView(f,e,p,b,e.stats,r.navigateTo.bind(r)),m=new ThumbnailView(d,e,p);c(g,m);k.push(g);l.push(m); -e=e.ref;n[e.num+" "+e.gen+" R"]=p}r.pagesRefMap=n});s=a.getDestinations();s.then(function(a){r.destinations=a});PDFJS.Promise.all([m,s,e]).then(function(){a.getOutline().then(function(a){r.outline=new DocumentOutlineView(a)});var c=null;if(g.get("exists",!1))var c=g.get("page","1"),d=g.get("zoom",PDFView.currentScale),f=g.get("scrollLeft","0"),e=g.get("scrollTop","0"),c="page="+c+"&zoom="+d+","+f+","+e;r.setInitialView(c,b)});a.getMetadata().then(function(b){var c=b.info;b=b.metadata;r.documentInfo= -c;r.metadata=b;console.log("PDF "+a.fingerprint+" ["+c.PDFFormatVersion+" "+(c.Producer||"-")+" / "+(c.Creator||"-")+"]"+(PDFJS.version?" (PDF.js: "+PDFJS.version+")":""));var d;b&&b.has("dc:title")&&(d=b.get("dc:title"));!d&&c&&c.Title&&(d=c.Title);d&&r.setTitle(d+" - "+document.title)})},setInitialView:function(a,b){this.currentScale=0;this.currentScaleValue=null;this.initialBookmark?(this.setHash(this.initialBookmark),this.initialBookmark=null):a?this.setHash(a):b&&(this.parseScale(b,!0),this.page= -1);PDFView.currentScale===UNKNOWN_SCALE&&this.parseScale(DEFAULT_SCALE,!0)},renderHighestPriority:function(){var a=this.getVisiblePages();(a=this.getHighestPriority(a,this.pages,this.pageViewScroll.down))?this.renderView(a,"page"):this.sidebarOpen&&(a=this.getVisibleThumbs(),(a=this.getHighestPriority(a,this.thumbnails,this.thumbnailViewScroll.down))&&this.renderView(a,"thumbnail"))},getHighestPriority:function(a,b,c){var d=a.views,f=d.length;if(0===f)return!1;for(var h=0;hh)break;d+=f.el.clientHeight}var k=[];if(this.isFullscreen)return c=this.pages[this.page-1],k.push({id:c.id,view:c}),{first:c,last:c,views:k};a=h+a.clientHeight;for(var n,l,m;e<= -g&&dc&&50>b-c||((0a||0>this.mouseScrollDelta&&0=c+e[b].str.length;)c+=e[b].str.length, -b++;b==e.length&&console.error("Could not find matching mapping");for(var s={begin:{divIdx:b,offset:m-c}},m=m+k;b!==g&&m>c+e[b].str.length;)c+=e[b].str.length,b++;s.end={divIdx:b,offset:m-c};n.push(s)}return n};this.renderMatches=function(a){function b(a,c){var d=a.divIdx,f=g[d];f.textContent="";var h=e[d].str.substring(0,a.offset),h=document.createTextNode(h);if(c){var d=n&&d===l,k=document.createElement("span");k.className=c+(d?" selected":"");k.appendChild(h);f.appendChild(k)}else f.appendChild(h)} -function c(a,b,d){var f=a.divIdx,h=g[f];a=e[f].str.substring(a.offset,b.offset);a=document.createTextNode(a);d?(b=document.createElement("span"),b.className=d,b.appendChild(a),h.appendChild(b)):h.appendChild(a)}if(0!==a.length){var e=this.textContent.bidiTexts,g=this.textDivs,k=null,n=this.pageIdx===PDFFindController.selected.pageIdx,l=PDFFindController.selected.matchIdx,m={divIdx:-1,offset:void 0},s=l,r=s+1;if(PDFFindController.state.highlightAll)s=0,r=a.length;else if(!n)return;for(;se.percent)break;if(e.id===PDFView.page){h=!0;break}}h||(c=b[0].id);PDFView.isFullscreen||(updateViewarea.inProgress=!0,PDFView.page=c,updateViewarea.inProgress=!1);var b=PDFView.currentScale,c=PDFView.currentScaleValue,g=c==b?100*b:c,k=a.id,b="#page="+k+("&zoom="+g),n=PDFView.pages[k- -1].getPagePoint(PDFView.container.scrollLeft,PDFView.container.scrollTop-a.y),b=b+(","+Math.round(n[0])+","+Math.round(n[1])),l=PDFView.store;l.initializedPromise.then(function(){l.set("exists",!0);l.set("page",k);l.set("zoom",g);l.set("scrollLeft",Math.round(n[0]));l.set("scrollTop",Math.round(n[1]))});PDFView.getAnchorUrl(b)}}} -window.addEventListener("resize",function(a){PDFView.initialized&&(document.getElementById("pageWidthOption").selected||document.getElementById("pageFitOption").selected||document.getElementById("pageAutoOption").selected)&&PDFView.parseScale(document.getElementById("scaleSelect").value);updateViewarea()});window.addEventListener("hashchange",function(a){PDFView.setHash(document.location.hash.substring(1))}); -window.addEventListener("change",function(a){var b=a.target.files;b&&0!=b.length&&(a=new FileReader,a.onload=function(a){a=new Uint8Array(a.target.result);PDFView.open(a,0)},b=b[0],a.readAsArrayBuffer(b),PDFView.setTitleUsingUrl(b.name))},!0);function selectScaleOption(a){for(var b=document.getElementById("scaleSelect").options,c=!1,d=0;d=c)&&scrollIntoView(b)}}document.getElementById("previous").disabled=1>=a;document.getElementById("next").disabled= -a>=PDFView.pages.length},!0);window.addEventListener("DOMMouseScroll",function(a){if(a.ctrlKey){a.preventDefault();var b=a.detail;a=0 0 ? anchor : url.length, + query > 0 ? query : url.length); + return url.substring(url.lastIndexOf('/', end) + 1, end); +} + +/** + * Returns scale factor for the canvas. It makes sense for the HiDPI displays. + * @return {Object} The object with horizontal (sx) and vertical (sy) + scales. The scaled property is set to false if scaling is + not required, true otherwise. + */ +function getOutputScale(ctx) { + var devicePixelRatio = window.devicePixelRatio || 1; + var backingStoreRatio = ctx.webkitBackingStorePixelRatio || + ctx.mozBackingStorePixelRatio || + ctx.msBackingStorePixelRatio || + ctx.oBackingStorePixelRatio || + ctx.backingStorePixelRatio || 1; + var pixelRatio = devicePixelRatio / backingStoreRatio; + return { + sx: pixelRatio, + sy: pixelRatio, + scaled: pixelRatio != 1 + }; +} + +/** + * Scrolls specified element into view of its parent. + * element {Object} The element to be visible. + * spot {Object} An object with optional top and left properties, + * specifying the offset from the top left edge. + */ +function scrollIntoView(element, spot) { + // Assuming offsetParent is available (it's not available when viewer is in + // hidden iframe or object). We have to scroll: if the offsetParent is not set + // producing the error. See also animationStartedClosure. + var parent = element.offsetParent; + var offsetY = element.offsetTop + element.clientTop; + var offsetX = element.offsetLeft + element.clientLeft; + if (!parent) { + console.error('offsetParent is not set -- cannot scroll'); + return; + } + while (parent.clientHeight === parent.scrollHeight) { + if (parent.dataset._scaleY) { + offsetY /= parent.dataset._scaleY; + offsetX /= parent.dataset._scaleX; + } + offsetY += parent.offsetTop; + offsetX += parent.offsetLeft; + parent = parent.offsetParent; + if (!parent) { + return; // no need to scroll + } + } + if (spot) { + if (spot.top !== undefined) { + offsetY += spot.top; + } + if (spot.left !== undefined) { + offsetX += spot.left; + parent.scrollLeft = offsetX; + } + } + parent.scrollTop = offsetY; +} + +/** + * Event handler to suppress context menu. + */ +function noContextMenuHandler(e) { + e.preventDefault(); +} + +/** + * Returns the filename or guessed filename from the url (see issue 3455). + * url {String} The original PDF location. + * @return {String} Guessed PDF file name. + */ +function getPDFFileNameFromURL(url) { + var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/; + // SCHEME HOST 1.PATH 2.QUERY 3.REF + // Pattern to get last matching NAME.pdf + var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i; + var splitURI = reURI.exec(url); + var suggestedFilename = reFilename.exec(splitURI[1]) || + reFilename.exec(splitURI[2]) || + reFilename.exec(splitURI[3]); + if (suggestedFilename) { + suggestedFilename = suggestedFilename[0]; + if (suggestedFilename.indexOf('%') != -1) { + // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf + try { + suggestedFilename = + reFilename.exec(decodeURIComponent(suggestedFilename))[0]; + } catch(e) { // Possible (extremely rare) errors: + // URIError "Malformed URI", e.g. for "%AA.pdf" + // TypeError "null has no properties", e.g. for "%2F.pdf" + } + } + } + return suggestedFilename || 'document.pdf'; +} + +var ProgressBar = (function ProgressBarClosure() { + + function clamp(v, min, max) { + return Math.min(Math.max(v, min), max); + } + + function ProgressBar(id, opts) { + + // Fetch the sub-elements for later. + this.div = document.querySelector(id + ' .progress'); + + // Get the loading bar element, so it can be resized to fit the viewer. + this.bar = this.div.parentNode; + + // Get options, with sensible defaults. + this.height = opts.height || 100; + this.width = opts.width || 100; + this.units = opts.units || '%'; + + // Initialize heights. + this.div.style.height = this.height + this.units; + this.percent = 0; + } + + ProgressBar.prototype = { + + updateBar: function ProgressBar_updateBar() { + if (this._indeterminate) { + this.div.classList.add('indeterminate'); + this.div.style.width = this.width + this.units; + return; + } + + this.div.classList.remove('indeterminate'); + var progressSize = this.width * this._percent / 100; + this.div.style.width = progressSize + this.units; + }, + + get percent() { + return this._percent; + }, + + set percent(val) { + this._indeterminate = isNaN(val); + this._percent = clamp(val, 0, 100); + this.updateBar(); + }, + + setWidth: function ProgressBar_setWidth(viewer) { + if (viewer) { + var container = viewer.parentNode; + var scrollbarWidth = container.offsetWidth - viewer.offsetWidth; + if (scrollbarWidth > 0) { + this.bar.setAttribute('style', 'width: calc(100% - ' + + scrollbarWidth + 'px);'); + } + } + }, + + hide: function ProgressBar_hide() { + this.bar.classList.add('hidden'); + this.bar.removeAttribute('style'); + } + }; + + return ProgressBar; +})(); + +var Cache = function cacheCache(size) { + var data = []; + this.push = function cachePush(view) { + var i = data.indexOf(view); + if (i >= 0) + data.splice(i); + data.push(view); + if (data.length > size) + data.shift().destroy(); + }; +}; + +var isLocalStorageEnabled = (function isLocalStorageEnabledClosure() { + // Feature test as per http://diveintohtml5.info/storage.html + // The additional localStorage call is to get around a FF quirk, see + // bug #495747 in bugzilla + try { + return ('localStorage' in window && window['localStorage'] !== null && + localStorage); + } catch (e) { + return false; + } +})(); + + + +var EXPORTED_SYMBOLS = ['DEFAULT_PREFERENCES']; + +var DEFAULT_PREFERENCES = { + showPreviousViewOnLoad: true, + defaultZoomValue: '', + ifAvailableShowOutlineOnLoad: false +}; + + +var Preferences = (function PreferencesClosure() { + function Preferences() { + this.prefs = {}; + this.isInitializedPromiseResolved = false; + this.initializedPromise = this.readFromStorage().then(function(prefObj) { + this.isInitializedPromiseResolved = true; + if (prefObj) { + this.prefs = prefObj; + } + }.bind(this)); + } + + Preferences.prototype = { + writeToStorage: function Preferences_writeToStorage(prefObj) { + return; + }, + + readFromStorage: function Preferences_readFromStorage() { + var readFromStoragePromise = Promise.resolve(); + return readFromStoragePromise; + }, + + reset: function Preferences_reset() { + if (this.isInitializedPromiseResolved) { + this.prefs = {}; + this.writeToStorage(this.prefs); + } + }, + + set: function Preferences_set(name, value) { + if (!this.isInitializedPromiseResolved) { + return; + } else if (DEFAULT_PREFERENCES[name] === undefined) { + console.error('Preferences_set: \'' + name + '\' is undefined.'); + return; + } else if (value === undefined) { + console.error('Preferences_set: no value is specified.'); + return; + } + var valueType = typeof value; + var defaultType = typeof DEFAULT_PREFERENCES[name]; + + if (valueType !== defaultType) { + if (valueType === 'number' && defaultType === 'string') { + value = value.toString(); + } else { + console.error('Preferences_set: \'' + value + '\' is a \"' + + valueType + '\", expected a \"' + defaultType + '\".'); + return; + } + } + this.prefs[name] = value; + this.writeToStorage(this.prefs); + }, + + get: function Preferences_get(name) { + var defaultPref = DEFAULT_PREFERENCES[name]; + + if (defaultPref === undefined) { + console.error('Preferences_get: \'' + name + '\' is undefined.'); + return; + } else if (this.isInitializedPromiseResolved) { + var pref = this.prefs[name]; + + if (pref !== undefined) { + return pref; + } + } + return defaultPref; + } + }; + + return Preferences; +})(); + + +Preferences.prototype.writeToStorage = function(prefObj) { + if (isLocalStorageEnabled) { + localStorage.setItem('preferences', JSON.stringify(prefObj)); + } +}; + +Preferences.prototype.readFromStorage = function() { + var readFromStoragePromise = new Promise(function (resolve) { + if (isLocalStorageEnabled) { + var readPrefs = JSON.parse(localStorage.getItem('preferences')); + resolve(readPrefs); + } + }); + return readFromStoragePromise; +}; + + +(function mozPrintCallbackPolyfillClosure() { + if ('mozPrintCallback' in document.createElement('canvas')) { + return; + } + // Cause positive result on feature-detection: + HTMLCanvasElement.prototype.mozPrintCallback = undefined; + + var canvases; // During print task: non-live NodeList of elements + var index; // Index of element that is being processed + + var print = window.print; + window.print = function print() { + if (canvases) { + console.warn('Ignored window.print() because of a pending print job.'); + return; + } + try { + dispatchEvent('beforeprint'); + } finally { + canvases = document.querySelectorAll('canvas'); + index = -1; + next(); + } + }; + + function dispatchEvent(eventType) { + var event = document.createEvent('CustomEvent'); + event.initCustomEvent(eventType, false, false, 'custom'); + window.dispatchEvent(event); + } + + function next() { + if (!canvases) { + return; // Print task cancelled by user (state reset in abort()) + } + + renderProgress(); + if (++index < canvases.length) { + var canvas = canvases[index]; + if (typeof canvas.mozPrintCallback === 'function') { + canvas.mozPrintCallback({ + context: canvas.getContext('2d'), + abort: abort, + done: next + }); + } else { + next(); + } + } else { + renderProgress(); + print.call(window); + setTimeout(abort, 20); // Tidy-up + } + } + + function abort() { + if (canvases) { + canvases = null; + renderProgress(); + dispatchEvent('afterprint'); + } + } + + function renderProgress() { + var progressContainer = document.getElementById('mozPrintCallback-shim'); + if (canvases) { + var progress = Math.round(100 * index / canvases.length); + var progressBar = progressContainer.querySelector('progress'); + var progressPerc = progressContainer.querySelector('.relative-progress'); + progressBar.value = progress; + progressPerc.textContent = progress + '%'; + progressContainer.removeAttribute('hidden'); + progressContainer.onclick = abort; + } else { + progressContainer.setAttribute('hidden', ''); + } + } + + var hasAttachEvent = !!document.attachEvent; + + window.addEventListener('keydown', function(event) { + if (event.keyCode === 80/*P*/ && (event.ctrlKey || event.metaKey)) { + window.print(); + if (hasAttachEvent) { + // Only attachEvent can cancel Ctrl + P dialog in IE <=10 + // attachEvent is gone in IE11, so the dialog will re-appear in IE11. + return; + } + event.preventDefault(); + if (event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } else { + event.stopPropagation(); + } + return; + } + if (event.keyCode === 27 && canvases) { // Esc + abort(); + } + }, true); + if (hasAttachEvent) { + document.attachEvent('onkeydown', function(event) { + event = event || window.event; + if (event.keyCode === 80/*P*/ && event.ctrlKey) { + event.keyCode = 0; + return false; + } + }); + } + + if ('onbeforeprint' in window) { + // Do not propagate before/afterprint events when they are not triggered + // from within this polyfill. (FF/IE). + var stopPropagationIfNeeded = function(event) { + if (event.detail !== 'custom' && event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } + }; + window.addEventListener('beforeprint', stopPropagationIfNeeded, false); + window.addEventListener('afterprint', stopPropagationIfNeeded, false); + } +})(); + + + +var DownloadManager = (function DownloadManagerClosure() { + + function download(blobUrl, filename) { + var a = document.createElement('a'); + if (a.click) { + // Use a.click() if available. Otherwise, Chrome might show + // "Unsafe JavaScript attempt to initiate a navigation change + // for frame with URL" and not open the PDF at all. + // Supported by (not mentioned = untested): + // - Firefox 6 - 19 (4- does not support a.click, 5 ignores a.click) + // - Chrome 19 - 26 (18- does not support a.click) + // - Opera 9 - 12.15 + // - Internet Explorer 6 - 10 + // - Safari 6 (5.1- does not support a.click) + a.href = blobUrl; + a.target = '_parent'; + // Use a.download if available. This increases the likelihood that + // the file is downloaded instead of opened by another PDF plugin. + if ('download' in a) { + a.download = filename; + } + // must be in the document for IE and recent Firefox versions. + // (otherwise .click() is ignored) + (document.body || document.documentElement).appendChild(a); + a.click(); + a.parentNode.removeChild(a); + } else { + if (window.top === window && + blobUrl.split('#')[0] === window.location.href.split('#')[0]) { + // If _parent == self, then opening an identical URL with different + // location hash will only cause a navigation, not a download. + var padCharacter = blobUrl.indexOf('?') === -1 ? '?' : '&'; + blobUrl = blobUrl.replace(/#|$/, padCharacter + '$&'); + } + window.open(blobUrl, '_parent'); + } + } + + function DownloadManager() {} + + DownloadManager.prototype = { + downloadUrl: function DownloadManager_downloadUrl(url, filename) { + if (!PDFJS.isValidUrl(url, true)) { + return; // restricted/invalid URL + } + + download(url + '#pdfjs.action=download', filename); + }, + + download: function DownloadManager_download(blob, url, filename) { + if (!URL) { + // URL.createObjectURL is not supported + this.downloadUrl(url, filename); + return; + } + + if (navigator.msSaveBlob) { + // IE10 / IE11 + if (!navigator.msSaveBlob(blob, filename)) { + this.downloadUrl(url, filename); + } + return; + } + + var blobUrl = URL.createObjectURL(blob); + download(blobUrl, filename); + } + }; + + return DownloadManager; +})(); + + + +var cache = new Cache(CACHE_SIZE); +var currentPageNumber = 1; + + +/** + * View History - This is a utility for saving various view parameters for + * recently opened files. + * + * The way that the view parameters are stored depends on how PDF.js is built, + * for 'node make ' the following cases exist: + * - FIREFOX or MOZCENTRAL - uses about:config. + * - B2G - uses asyncStorage. + * - GENERIC or CHROME - uses localStorage, if it is available. + */ +var ViewHistory = (function ViewHistoryClosure() { + function ViewHistory(fingerprint) { + this.fingerprint = fingerprint; + var initializedPromiseResolve; + this.isInitializedPromiseResolved = false; + this.initializedPromise = new Promise(function (resolve) { + initializedPromiseResolve = resolve; + }); + + var resolvePromise = (function ViewHistoryResolvePromise(db) { + this.isInitializedPromiseResolved = true; + this.initialize(db || '{}'); + initializedPromiseResolve(); + }).bind(this); + + + + if (isLocalStorageEnabled) { + resolvePromise(localStorage.getItem('database')); + } + } + + ViewHistory.prototype = { + initialize: function ViewHistory_initialize(database) { + database = JSON.parse(database); + if (!('files' in database)) { + database.files = []; + } + if (database.files.length >= VIEW_HISTORY_MEMORY) { + database.files.shift(); + } + var index; + for (var i = 0, length = database.files.length; i < length; i++) { + var branch = database.files[i]; + if (branch.fingerprint === this.fingerprint) { + index = i; + break; + } + } + if (typeof index !== 'number') { + index = database.files.push({fingerprint: this.fingerprint}) - 1; + } + this.file = database.files[index]; + this.database = database; + }, + + set: function ViewHistory_set(name, val) { + if (!this.isInitializedPromiseResolved) { + return; + } + var file = this.file; + file[name] = val; + var database = JSON.stringify(this.database); + + + + if (isLocalStorageEnabled) { + localStorage.setItem('database', database); + } + }, + + get: function ViewHistory_get(name, defaultValue) { + if (!this.isInitializedPromiseResolved) { + return defaultValue; + } + return this.file[name] || defaultValue; + } + }; + + return ViewHistory; +})(); + + +/* globals PDFFindController, FindStates, mozL10n */ + +/** + * Creates a "search bar" given set of DOM elements + * that act as controls for searching, or for setting + * search preferences in the UI. This object also sets + * up the appropriate events for the controls. Actual + * searching is done by PDFFindController + */ +var PDFFindBar = { + + opened: false, + bar: null, + toggleButton: null, + findField: null, + highlightAll: null, + caseSensitive: null, + findMsg: null, + findStatusIcon: null, + findPreviousButton: null, + findNextButton: null, + + initialize: function(options) { + if(typeof PDFFindController === 'undefined' || PDFFindController === null) { + throw 'PDFFindBar cannot be initialized ' + + 'without a PDFFindController instance.'; + } + + this.bar = options.bar; + this.toggleButton = options.toggleButton; + this.findField = options.findField; + this.highlightAll = options.highlightAllCheckbox; + this.caseSensitive = options.caseSensitiveCheckbox; + this.findMsg = options.findMsg; + this.findStatusIcon = options.findStatusIcon; + this.findPreviousButton = options.findPreviousButton; + this.findNextButton = options.findNextButton; + + var self = this; + this.toggleButton.addEventListener('click', function() { + self.toggle(); + }); + + this.findField.addEventListener('input', function() { + self.dispatchEvent(''); + }); + + this.bar.addEventListener('keydown', function(evt) { + switch (evt.keyCode) { + case 13: // Enter + if (evt.target === self.findField) { + self.dispatchEvent('again', evt.shiftKey); + } + break; + case 27: // Escape + self.close(); + break; + } + }); + + this.findPreviousButton.addEventListener('click', + function() { self.dispatchEvent('again', true); } + ); + + this.findNextButton.addEventListener('click', function() { + self.dispatchEvent('again', false); + }); + + this.highlightAll.addEventListener('click', function() { + self.dispatchEvent('highlightallchange'); + }); + + this.caseSensitive.addEventListener('click', function() { + self.dispatchEvent('casesensitivitychange'); + }); + }, + + dispatchEvent: function(aType, aFindPrevious) { + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('find' + aType, true, true, { + query: this.findField.value, + caseSensitive: this.caseSensitive.checked, + highlightAll: this.highlightAll.checked, + findPrevious: aFindPrevious + }); + return window.dispatchEvent(event); + }, + + updateUIState: function(state, previous) { + var notFound = false; + var findMsg = ''; + var status = ''; + + switch (state) { + case FindStates.FIND_FOUND: + break; + + case FindStates.FIND_PENDING: + status = 'pending'; + break; + + case FindStates.FIND_NOTFOUND: + findMsg = mozL10n.get('find_not_found', null, 'Phrase not found'); + notFound = true; + break; + + case FindStates.FIND_WRAPPED: + if (previous) { + findMsg = mozL10n.get('find_reached_top', null, + 'Reached top of document, continued from bottom'); + } else { + findMsg = mozL10n.get('find_reached_bottom', null, + 'Reached end of document, continued from top'); + } + break; + } + + if (notFound) { + this.findField.classList.add('notFound'); + } else { + this.findField.classList.remove('notFound'); + } + + this.findField.setAttribute('data-status', status); + this.findMsg.textContent = findMsg; + }, + + open: function() { + if (!this.opened) { + this.opened = true; + this.toggleButton.classList.add('toggled'); + this.bar.classList.remove('hidden'); + } + + this.findField.select(); + this.findField.focus(); + }, + + close: function() { + if (!this.opened) return; + + this.opened = false; + this.toggleButton.classList.remove('toggled'); + this.bar.classList.add('hidden'); + + PDFFindController.active = false; + }, + + toggle: function() { + if (this.opened) { + this.close(); + } else { + this.open(); + } + } +}; + + + +/* globals PDFFindBar, PDFJS, FindStates, FirefoxCom, Promise */ + +/** + * Provides a "search" or "find" functionality for the PDF. + * This object actually performs the search for a given string. + */ + +var PDFFindController = { + startedTextExtraction: false, + + extractTextPromises: [], + + pendingFindMatches: {}, + + // If active, find results will be highlighted. + active: false, + + // Stores the text for each page. + pageContents: [], + + pageMatches: [], + + // Currently selected match. + selected: { + pageIdx: -1, + matchIdx: -1 + }, + + // Where find algorithm currently is in the document. + offset: { + pageIdx: null, + matchIdx: null + }, + + resumePageIdx: null, + + resumeCallback: null, + + state: null, + + dirtyMatch: false, + + findTimeout: null, + + pdfPageSource: null, + + integratedFind: false, + + initialize: function(options) { + if(typeof PDFFindBar === 'undefined' || PDFFindBar === null) { + throw 'PDFFindController cannot be initialized ' + + 'without a PDFFindController instance'; + } + + this.pdfPageSource = options.pdfPageSource; + this.integratedFind = options.integratedFind; + + var events = [ + 'find', + 'findagain', + 'findhighlightallchange', + 'findcasesensitivitychange' + ]; + + this.firstPagePromise = new Promise(function (resolve) { + this.resolveFirstPage = resolve; + }.bind(this)); + this.handleEvent = this.handleEvent.bind(this); + + for (var i = 0; i < events.length; i++) { + window.addEventListener(events[i], this.handleEvent); + } + }, + + reset: function pdfFindControllerReset() { + this.startedTextExtraction = false; + this.extractTextPromises = []; + this.active = false; + }, + + calcFindMatch: function(pageIndex) { + var pageContent = this.pageContents[pageIndex]; + var query = this.state.query; + var caseSensitive = this.state.caseSensitive; + var queryLen = query.length; + + if (queryLen === 0) { + // Do nothing the matches should be wiped out already. + return; + } + + if (!caseSensitive) { + pageContent = pageContent.toLowerCase(); + query = query.toLowerCase(); + } + + var matches = []; + + var matchIdx = -queryLen; + while (true) { + matchIdx = pageContent.indexOf(query, matchIdx + queryLen); + if (matchIdx === -1) { + break; + } + + matches.push(matchIdx); + } + this.pageMatches[pageIndex] = matches; + this.updatePage(pageIndex); + if (this.resumePageIdx === pageIndex) { + var callback = this.resumeCallback; + this.resumePageIdx = null; + this.resumeCallback = null; + callback(); + } + }, + + extractText: function() { + if (this.startedTextExtraction) { + return; + } + this.startedTextExtraction = true; + + this.pageContents = []; + var extractTextPromisesResolves = []; + for (var i = 0, ii = this.pdfPageSource.pdfDocument.numPages; i < ii; i++) { + this.extractTextPromises.push(new Promise(function (resolve) { + extractTextPromisesResolves.push(resolve); + })); + } + + var self = this; + function extractPageText(pageIndex) { + self.pdfPageSource.pages[pageIndex].getTextContent().then( + function textContentResolved(data) { + // Build the find string. + var bidiTexts = data.bidiTexts; + var str = ''; + + for (var i = 0; i < bidiTexts.length; i++) { + str += bidiTexts[i].str; + } + + // Store the pageContent as a string. + self.pageContents.push(str); + + extractTextPromisesResolves[pageIndex](pageIndex); + if ((pageIndex + 1) < self.pdfPageSource.pages.length) + extractPageText(pageIndex + 1); + } + ); + } + extractPageText(0); + }, + + handleEvent: function(e) { + if (this.state === null || e.type !== 'findagain') { + this.dirtyMatch = true; + } + this.state = e.detail; + this.updateUIState(FindStates.FIND_PENDING); + + this.firstPagePromise.then(function() { + this.extractText(); + + clearTimeout(this.findTimeout); + if (e.type === 'find') { + // Only trigger the find action after 250ms of silence. + this.findTimeout = setTimeout(this.nextMatch.bind(this), 250); + } else { + this.nextMatch(); + } + }.bind(this)); + }, + + updatePage: function(idx) { + var page = this.pdfPageSource.pages[idx]; + + if (this.selected.pageIdx === idx) { + // If the page is selected, scroll the page into view, which triggers + // rendering the page, which adds the textLayer. Once the textLayer is + // build, it will scroll onto the selected match. + page.scrollIntoView(); + } + + if (page.textLayer) { + page.textLayer.updateMatches(); + } + }, + + nextMatch: function() { + var previous = this.state.findPrevious; + var currentPageIndex = this.pdfPageSource.page - 1; + var numPages = this.pdfPageSource.pages.length; + + this.active = true; + + if (this.dirtyMatch) { + // Need to recalculate the matches, reset everything. + this.dirtyMatch = false; + this.selected.pageIdx = this.selected.matchIdx = -1; + this.offset.pageIdx = currentPageIndex; + this.offset.matchIdx = null; + this.hadMatch = false; + this.resumeCallback = null; + this.resumePageIdx = null; + this.pageMatches = []; + var self = this; + + for (var i = 0; i < numPages; i++) { + // Wipe out any previous highlighted matches. + this.updatePage(i); + + // As soon as the text is extracted start finding the matches. + if (!(i in this.pendingFindMatches)) { + this.pendingFindMatches[i] = true; + this.extractTextPromises[i].then(function(pageIdx) { + delete self.pendingFindMatches[pageIdx]; + self.calcFindMatch(pageIdx); + }); + } + } + } + + // If there's no query there's no point in searching. + if (this.state.query === '') { + this.updateUIState(FindStates.FIND_FOUND); + return; + } + + // If we're waiting on a page, we return since we can't do anything else. + if (this.resumeCallback) { + return; + } + + var offset = this.offset; + // If there's already a matchIdx that means we are iterating through a + // page's matches. + if (offset.matchIdx !== null) { + var numPageMatches = this.pageMatches[offset.pageIdx].length; + if ((!previous && offset.matchIdx + 1 < numPageMatches) || + (previous && offset.matchIdx > 0)) { + // The simple case, we just have advance the matchIdx to select the next + // match on the page. + this.hadMatch = true; + offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1; + this.updateMatch(true); + return; + } + // We went beyond the current page's matches, so we advance to the next + // page. + this.advanceOffsetPage(previous); + } + // Start searching through the page. + this.nextPageMatch(); + }, + + nextPageMatch: function() { + if (this.resumePageIdx !== null) + console.error('There can only be one pending page.'); + + var matchesReady = function(matches) { + var offset = this.offset; + var numMatches = matches.length; + var previous = this.state.findPrevious; + if (numMatches) { + // There were matches for the page, so initialize the matchIdx. + this.hadMatch = true; + offset.matchIdx = previous ? numMatches - 1 : 0; + this.updateMatch(true); + } else { + // No matches attempt to search the next page. + this.advanceOffsetPage(previous); + if (offset.wrapped) { + offset.matchIdx = null; + if (!this.hadMatch) { + // No point in wrapping there were no matches. + this.updateMatch(false); + return; + } + } + // Search the next page. + this.nextPageMatch(); + } + }.bind(this); + + var pageIdx = this.offset.pageIdx; + var pageMatches = this.pageMatches; + if (!pageMatches[pageIdx]) { + // The matches aren't ready setup a callback so we can be notified, + // when they are ready. + this.resumeCallback = function() { + matchesReady(pageMatches[pageIdx]); + }; + this.resumePageIdx = pageIdx; + return; + } + // The matches are finished already. + matchesReady(pageMatches[pageIdx]); + }, + + advanceOffsetPage: function(previous) { + var offset = this.offset; + var numPages = this.extractTextPromises.length; + offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1; + offset.matchIdx = null; + if (offset.pageIdx >= numPages || offset.pageIdx < 0) { + offset.pageIdx = previous ? numPages - 1 : 0; + offset.wrapped = true; + return; + } + }, + + updateMatch: function(found) { + var state = FindStates.FIND_NOTFOUND; + var wrapped = this.offset.wrapped; + this.offset.wrapped = false; + if (found) { + var previousPage = this.selected.pageIdx; + this.selected.pageIdx = this.offset.pageIdx; + this.selected.matchIdx = this.offset.matchIdx; + state = wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND; + // Update the currently selected page to wipe out any selected matches. + if (previousPage !== -1 && previousPage !== this.selected.pageIdx) { + this.updatePage(previousPage); + } + } + this.updateUIState(state, this.state.findPrevious); + if (this.selected.pageIdx !== -1) { + this.updatePage(this.selected.pageIdx, true); + } + }, + + updateUIState: function(state, previous) { + if (this.integratedFind) { + FirefoxCom.request('updateFindControlState', + {result: state, findPrevious: previous}); + return; + } + PDFFindBar.updateUIState(state, previous); + } +}; + + + +var PDFHistory = { + initialized: false, + initialDestination: null, + + initialize: function pdfHistoryInitialize(fingerprint) { + if (PDFJS.disableHistory || PDFView.isViewerEmbedded) { + // The browsing history is only enabled when the viewer is standalone, + // i.e. not when it is embedded in a web page. + return; + } + this.initialized = true; + this.reInitialized = false; + this.allowHashChange = true; + this.historyUnlocked = true; + + this.previousHash = window.location.hash.substring(1); + this.currentBookmark = ''; + this.currentPage = 0; + this.updatePreviousBookmark = false; + this.previousBookmark = ''; + this.previousPage = 0; + this.nextHashParam = ''; + + this.fingerprint = fingerprint; + this.currentUid = this.uid = 0; + this.current = {}; + + var state = window.history.state; + if (this._isStateObjectDefined(state)) { + // This corresponds to navigating back to the document + // from another page in the browser history. + if (state.target.dest) { + this.initialDestination = state.target.dest; + } else { + PDFView.initialBookmark = state.target.hash; + } + this.currentUid = state.uid; + this.uid = state.uid + 1; + this.current = state.target; + } else { + // This corresponds to the loading of a new document. + if (state && state.fingerprint && + this.fingerprint !== state.fingerprint) { + // Reinitialize the browsing history when a new document + // is opened in the web viewer. + this.reInitialized = true; + } + this._pushOrReplaceState({ fingerprint: this.fingerprint }, true); + } + + var self = this; + window.addEventListener('popstate', function pdfHistoryPopstate(evt) { + evt.preventDefault(); + evt.stopPropagation(); + + if (!self.historyUnlocked) { + return; + } + if (evt.state) { + // Move back/forward in the history. + self._goTo(evt.state); + } else { + // Handle the user modifying the hash of a loaded document. + self.previousHash = window.location.hash.substring(1); + + // If the history is empty when the hash changes, + // update the previous entry in the browser history. + if (self.uid === 0) { + var previousParams = (self.previousHash && self.currentBookmark && + self.previousHash !== self.currentBookmark) ? + { hash: self.currentBookmark, page: self.currentPage } : + { page: 1 }; + self.historyUnlocked = false; + self.allowHashChange = false; + window.history.back(); + self._pushToHistory(previousParams, false, true); + window.history.forward(); + self.historyUnlocked = true; + } + self._pushToHistory({ hash: self.previousHash }, false, true); + self._updatePreviousBookmark(); + } + }, false); + + function pdfHistoryBeforeUnload() { + var previousParams = self._getPreviousParams(null, true); + if (previousParams) { + var replacePrevious = (!self.current.dest && + self.current.hash !== self.previousHash); + self._pushToHistory(previousParams, false, replacePrevious); + self._updatePreviousBookmark(); + } + // Remove the event listener when navigating away from the document, + // since 'beforeunload' prevents Firefox from caching the document. + window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false); + } + window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false); + + window.addEventListener('pageshow', function pdfHistoryPageShow(evt) { + // If the entire viewer (including the PDF file) is cached in the browser, + // we need to reattach the 'beforeunload' event listener since + // the 'DOMContentLoaded' event is not fired on 'pageshow'. + window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false); + }, false); + }, + + _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) { + return (state && state.uid >= 0 && + state.fingerprint && this.fingerprint === state.fingerprint && + state.target && state.target.hash) ? true : false; + }, + + _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj, + replace) { + if (replace) { + window.history.replaceState(stateObj, '', document.URL); + } else { + window.history.pushState(stateObj, '', document.URL); + } + }, + + get isHashChangeUnlocked() { + if (!this.initialized) { + return true; + } + // If the current hash changes when moving back/forward in the history, + // this will trigger a 'popstate' event *as well* as a 'hashchange' event. + // Since the hash generally won't correspond to the exact the position + // stored in the history's state object, triggering the 'hashchange' event + // can thus corrupt the browser history. + // + // When the hash changes during a 'popstate' event, we *only* prevent the + // first 'hashchange' event and immediately reset allowHashChange. + // If it is not reset, the user would not be able to change the hash. + + var temp = this.allowHashChange; + this.allowHashChange = true; + return temp; + }, + + _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() { + if (this.updatePreviousBookmark && + this.currentBookmark && this.currentPage) { + this.previousBookmark = this.currentBookmark; + this.previousPage = this.currentPage; + this.updatePreviousBookmark = false; + } + }, + + updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark, + pageNum) { + if (this.initialized) { + this.currentBookmark = bookmark.substring(1); + this.currentPage = pageNum | 0; + this._updatePreviousBookmark(); + } + }, + + updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) { + if (this.initialized) { + this.nextHashParam = param; + } + }, + + push: function pdfHistoryPush(params, isInitialBookmark) { + if (!(this.initialized && this.historyUnlocked)) { + return; + } + if (params.dest && !params.hash) { + params.hash = (this.current.hash && this.current.dest && + this.current.dest === params.dest) ? + this.current.hash : + PDFView.getDestinationHash(params.dest).split('#')[1]; + } + if (params.page) { + params.page |= 0; + } + if (isInitialBookmark) { + var target = window.history.state.target; + if (!target) { + // Invoked when the user specifies an initial bookmark, + // thus setting PDFView.initialBookmark, when the document is loaded. + this._pushToHistory(params, false); + this.previousHash = window.location.hash.substring(1); + } + this.updatePreviousBookmark = this.nextHashParam ? false : true; + if (target) { + // If the current document is reloaded, + // avoid creating duplicate entries in the history. + this._updatePreviousBookmark(); + } + return; + } + if (this.nextHashParam) { + if (this.nextHashParam === params.hash) { + this.nextHashParam = null; + this.updatePreviousBookmark = true; + return; + } else { + this.nextHashParam = null; + } + } + + if (params.hash) { + if (this.current.hash) { + if (this.current.hash !== params.hash) { + this._pushToHistory(params, true); + } else { + if (!this.current.page && params.page) { + this._pushToHistory(params, false, true); + } + this.updatePreviousBookmark = true; + } + } else { + this._pushToHistory(params, true); + } + } else if (this.current.page && params.page && + this.current.page !== params.page) { + this._pushToHistory(params, true); + } + }, + + _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage, + beforeUnload) { + if (!(this.currentBookmark && this.currentPage)) { + return null; + } else if (this.updatePreviousBookmark) { + this.updatePreviousBookmark = false; + } + if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) { + // Prevent the history from getting stuck in the current state, + // effectively preventing the user from going back/forward in the history. + // + // This happens if the current position in the document didn't change when + // the history was previously updated. The reasons for this are either: + // 1. The current zoom value is such that the document does not need to, + // or cannot, be scrolled to display the destination. + // 2. The previous destination is broken, and doesn't actally point to a + // position within the document. + // (This is either due to a bad PDF generator, or the user making a + // mistake when entering a destination in the hash parameters.) + return null; + } + if ((!this.current.dest && !onlyCheckPage) || beforeUnload) { + if (this.previousBookmark === this.currentBookmark) { + return null; + } + } else if (this.current.page || onlyCheckPage) { + if (this.previousPage === this.currentPage) { + return null; + } + } else { + return null; + } + var params = { hash: this.currentBookmark, page: this.currentPage }; + if (PresentationMode.active) { + params.hash = null; + } + return params; + }, + + _stateObj: function pdfHistory_stateObj(params) { + return { fingerprint: this.fingerprint, uid: this.uid, target: params }; + }, + + _pushToHistory: function pdfHistory_pushToHistory(params, + addPrevious, overwrite) { + if (!this.initialized) { + return; + } + if (!params.hash && params.page) { + params.hash = ('page=' + params.page); + } + if (addPrevious && !overwrite) { + var previousParams = this._getPreviousParams(); + if (previousParams) { + var replacePrevious = (!this.current.dest && + this.current.hash !== this.previousHash); + this._pushToHistory(previousParams, false, replacePrevious); + } + } + this._pushOrReplaceState(this._stateObj(params), + (overwrite || this.uid === 0)); + this.currentUid = this.uid++; + this.current = params; + this.updatePreviousBookmark = true; + }, + + _goTo: function pdfHistory_goTo(state) { + if (!(this.initialized && this.historyUnlocked && + this._isStateObjectDefined(state))) { + return; + } + if (!this.reInitialized && state.uid < this.currentUid) { + var previousParams = this._getPreviousParams(true); + if (previousParams) { + this._pushToHistory(this.current, false); + this._pushToHistory(previousParams, false); + this.currentUid = state.uid; + window.history.back(); + return; + } + } + this.historyUnlocked = false; + + if (state.target.dest) { + PDFView.navigateTo(state.target.dest); + } else { + PDFView.setHash(state.target.hash); + } + this.currentUid = state.uid; + if (state.uid > this.uid) { + this.uid = state.uid; + } + this.current = state.target; + this.updatePreviousBookmark = true; + + var currentHash = window.location.hash.substring(1); + if (this.previousHash !== currentHash) { + this.allowHashChange = false; + } + this.previousHash = currentHash; + + this.historyUnlocked = true; + }, + + back: function pdfHistoryBack() { + this.go(-1); + }, + + forward: function pdfHistoryForward() { + this.go(1); + }, + + go: function pdfHistoryGo(direction) { + if (this.initialized && this.historyUnlocked) { + var state = window.history.state; + if (direction === -1 && state && state.uid > 0) { + window.history.back(); + } else if (direction === 1 && state && state.uid < (this.uid - 1)) { + window.history.forward(); + } + } + } +}; + + +var SecondaryToolbar = { + opened: false, + previousContainerHeight: null, + newContainerHeight: null, + + initialize: function secondaryToolbarInitialize(options) { + this.toolbar = options.toolbar; + this.presentationMode = options.presentationMode; + this.buttonContainer = this.toolbar.firstElementChild; + + // Define the toolbar buttons. + this.toggleButton = options.toggleButton; + this.presentationModeButton = options.presentationModeButton; + this.openFile = options.openFile; + this.print = options.print; + this.download = options.download; + this.firstPage = options.firstPage; + this.lastPage = options.lastPage; + this.pageRotateCw = options.pageRotateCw; + this.pageRotateCcw = options.pageRotateCcw; + + // Attach the event listeners. + var elements = [ + // Button to toggle the visibility of the secondary toolbar: + { element: this.toggleButton, handler: this.toggle }, + // All items within the secondary toolbar + // (except for toggleHandTool, hand_tool.js is responsible for it): + { element: this.presentationModeButton, + handler: this.presentationModeClick }, + //{ element: this.openFile, handler: this.openFileClick }, + { element: this.print, handler: this.printClick }, + //{ element: this.download, handler: this.downloadClick }, + { element: this.firstPage, handler: this.firstPageClick }, + { element: this.lastPage, handler: this.lastPageClick }, + { element: this.pageRotateCw, handler: this.pageRotateCwClick }, + { element: this.pageRotateCcw, handler: this.pageRotateCcwClick } + ]; + + for (var item in elements) { + var element = elements[item].element; + if (element) { + element.addEventListener('click', elements[item].handler.bind(this)); + } + } + }, + + // Event handling functions. + presentationModeClick: function secondaryToolbarPresentationModeClick(evt) { + this.presentationMode.request(); + this.close(); + }, + + openFileClick: function secondaryToolbarOpenFileClick(evt) { + document.getElementById('fileInput').click(); + this.close(evt.target); + }, + + printClick: function secondaryToolbarPrintClick(evt) { + window.print(); + this.close(evt.target); + }, + + downloadClick: function secondaryToolbarDownloadClick(evt) { + PDFView.download(); + this.close(evt.target); + }, + + firstPageClick: function secondaryToolbarFirstPageClick(evt) { + PDFView.page = 1; + }, + + lastPageClick: function secondaryToolbarLastPageClick(evt) { + PDFView.page = PDFView.pdfDocument.numPages; + }, + + pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) { + PDFView.rotatePages(90); + }, + + pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) { + PDFView.rotatePages(-90); + }, + + // Misc. functions for interacting with the toolbar. + setMaxHeight: function secondaryToolbarSetMaxHeight(container) { + if (!container || !this.buttonContainer) { + return; + } + this.newContainerHeight = container.clientHeight; + if (this.previousContainerHeight === this.newContainerHeight) { + return; + } + this.buttonContainer.setAttribute('style', + 'max-height: ' + (this.newContainerHeight - SCROLLBAR_PADDING) + 'px;'); + this.previousContainerHeight = this.newContainerHeight; + }, + + open: function secondaryToolbarOpen() { + if (this.opened) { + return; + } + this.opened = true; + this.toggleButton.classList.add('toggled'); + this.toolbar.classList.remove('hidden'); + }, + + close: function secondaryToolbarClose(target) { + if (!this.opened) { + return; + } else if (target && !this.toolbar.contains(target)) { + return; + } + this.opened = false; + this.toolbar.classList.add('hidden'); + this.toggleButton.classList.remove('toggled'); + }, + + toggle: function secondaryToolbarToggle() { + if (this.opened) { + this.close(); + } else { + this.open(); + } + } +}; + + +var PasswordPrompt = { + visible: false, + updatePassword: null, + reason: null, + overlayContainer: null, + passwordField: null, + passwordText: null, + passwordSubmit: null, + passwordCancel: null, + + initialize: function secondaryToolbarInitialize(options) { + this.overlayContainer = options.overlayContainer; + this.passwordField = options.passwordField; + this.passwordText = options.passwordText; + this.passwordSubmit = options.passwordSubmit; + this.passwordCancel = options.passwordCancel; + + // Attach the event listeners. + this.passwordSubmit.addEventListener('click', + this.verifyPassword.bind(this)); + + this.passwordCancel.addEventListener('click', this.hide.bind(this)); + + this.passwordField.addEventListener('keydown', + function (e) { + if (e.keyCode === 13) { // Enter key + this.verifyPassword(); + } + }.bind(this)); + + this.overlayContainer.addEventListener('keydown', + function (e) { + if (e.keyCode === 27) { // Esc key + this.hide(); + } + }.bind(this)); + }, + + show: function passwordPromptShow() { + if (this.visible) { + return; + } + this.visible = true; + this.overlayContainer.classList.remove('hidden'); + this.passwordField.focus(); + + var promptString = mozL10n.get('password_label', null, + 'Enter the password to open this PDF file.'); + + if (this.reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD) { + promptString = mozL10n.get('password_invalid', null, + 'Invalid password. Please try again.'); + } + + this.passwordText.textContent = promptString; + }, + + hide: function passwordPromptClose() { + if (!this.visible) { + return; + } + this.visible = false; + this.passwordField.value = ''; + this.overlayContainer.classList.add('hidden'); + }, + + verifyPassword: function passwordPromptVerifyPassword() { + var password = this.passwordField.value; + if (password && password.length > 0) { + this.hide(); + return this.updatePassword(password); + } + } +}; + + +var DELAY_BEFORE_HIDING_CONTROLS = 3000; // in ms +var SELECTOR = 'presentationControls'; +var DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS = 1000; // in ms + +var PresentationMode = { + active: false, + args: null, + contextMenuOpen: false, + prevCoords: { x: null, y: null }, + + initialize: function presentationModeInitialize(options) { + this.container = options.container; + this.secondaryToolbar = options.secondaryToolbar; + + this.viewer = this.container.firstElementChild; + + this.firstPage = options.firstPage; + this.lastPage = options.lastPage; + this.pageRotateCw = options.pageRotateCw; + this.pageRotateCcw = options.pageRotateCcw; + + this.firstPage.addEventListener('click', function() { + this.contextMenuOpen = false; + this.secondaryToolbar.firstPageClick(); + }.bind(this)); + this.lastPage.addEventListener('click', function() { + this.contextMenuOpen = false; + this.secondaryToolbar.lastPageClick(); + }.bind(this)); + + this.pageRotateCw.addEventListener('click', function() { + this.contextMenuOpen = false; + this.secondaryToolbar.pageRotateCwClick(); + }.bind(this)); + this.pageRotateCcw.addEventListener('click', function() { + this.contextMenuOpen = false; + this.secondaryToolbar.pageRotateCcwClick(); + }.bind(this)); + }, + + get isFullscreen() { + return (document.fullscreenElement || + document.mozFullScreen || + document.webkitIsFullScreen || + document.msFullscreenElement); + }, + + /** + * Initialize a timeout that is used to reset PDFView.currentPosition when the + * browser transitions to fullscreen mode. Since resize events are triggered + * multiple times during the switch to fullscreen mode, this is necessary in + * order to prevent the page from being scrolled partially, or completely, + * out of view when Presentation Mode is enabled. + * Note: This is only an issue at certain zoom levels, e.g. 'page-width'. + */ + _setSwitchInProgress: function presentationMode_setSwitchInProgress() { + if (this.switchInProgress) { + clearTimeout(this.switchInProgress); + } + this.switchInProgress = setTimeout(function switchInProgressTimeout() { + delete this.switchInProgress; + }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS); + + PDFView.currentPosition = null; + }, + + _resetSwitchInProgress: function presentationMode_resetSwitchInProgress() { + if (this.switchInProgress) { + clearTimeout(this.switchInProgress); + delete this.switchInProgress; + } + }, + + request: function presentationModeRequest() { + if (!PDFView.supportsFullscreen || this.isFullscreen || + !this.viewer.hasChildNodes()) { + return false; + } + this._setSwitchInProgress(); + + if (this.container.requestFullscreen) { + this.container.requestFullscreen(); + } else if (this.container.mozRequestFullScreen) { + this.container.mozRequestFullScreen(); + } else if (this.container.webkitRequestFullScreen) { + this.container.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); + } else if (this.container.msRequestFullscreen) { + this.container.msRequestFullscreen(); + } else { + return false; + } + + this.args = { + page: PDFView.page, + previousScale: PDFView.currentScaleValue + }; + + return true; + }, + + enter: function presentationModeEnter() { + this.active = true; + this._resetSwitchInProgress(); + + // Ensure that the correct page is scrolled into view when entering + // Presentation Mode, by waiting until fullscreen mode in enabled. + // Note: This is only necessary in non-Mozilla browsers. + setTimeout(function enterPresentationModeTimeout() { + PDFView.page = this.args.page; + PDFView.setScale('page-fit', true); + }.bind(this), 0); + + window.addEventListener('mousemove', this.mouseMove, false); + window.addEventListener('mousedown', this.mouseDown, false); + window.addEventListener('contextmenu', this.contextMenu, false); + + this.showControls(); + HandTool.enterPresentationMode(); + this.contextMenuOpen = false; + this.container.setAttribute('contextmenu', 'viewerContextMenu'); + }, + + exit: function presentationModeExit() { + var page = PDFView.page; + + // Ensure that the correct page is scrolled into view when exiting + // Presentation Mode, by waiting until fullscreen mode is disabled. + // Note: This is only necessary in non-Mozilla browsers. + setTimeout(function exitPresentationModeTimeout() { + PDFView.setScale(this.args.previousScale); + PDFView.page = page; + // Keep Presentation Mode active until the page is scrolled into view, + // to prevent issues in non-Mozilla browsers. + this.active = false; + this.args = null; + }.bind(this), 0); + + window.removeEventListener('mousemove', this.mouseMove, false); + window.removeEventListener('mousedown', this.mouseDown, false); + window.removeEventListener('contextmenu', this.contextMenu, false); + + this.hideControls(); + PDFView.clearMouseScrollState(); + HandTool.exitPresentationMode(); + this.container.removeAttribute('contextmenu'); + this.contextMenuOpen = false; + + // Ensure that the thumbnail of the current page is visible + // when exiting presentation mode. + scrollIntoView(document.getElementById('thumbnailContainer' + page)); + }, + + showControls: function presentationModeShowControls() { + if (this.controlsTimeout) { + clearTimeout(this.controlsTimeout); + } else { + this.container.classList.add(SELECTOR); + } + this.controlsTimeout = setTimeout(function hideControlsTimeout() { + this.container.classList.remove(SELECTOR); + delete this.controlsTimeout; + }.bind(this), DELAY_BEFORE_HIDING_CONTROLS); + }, + + hideControls: function presentationModeHideControls() { + if (!this.controlsTimeout) { + return; + } + this.container.classList.remove(SELECTOR); + clearTimeout(this.controlsTimeout); + delete this.controlsTimeout; + }, + + mouseMove: function presentationModeMouseMove(evt) { + // Workaround for a bug in WebKit browsers that causes the 'mousemove' event + // to be fired when the cursor is changed. For details, see: + // http://code.google.com/p/chromium/issues/detail?id=103041. + + var currCoords = { x: evt.clientX, y: evt.clientY }; + var prevCoords = PresentationMode.prevCoords; + PresentationMode.prevCoords = currCoords; + + if (currCoords.x === prevCoords.x && currCoords.y === prevCoords.y) { + return; + } + PresentationMode.showControls(); + }, + + mouseDown: function presentationModeMouseDown(evt) { + var self = PresentationMode; + if (self.contextMenuOpen) { + self.contextMenuOpen = false; + evt.preventDefault(); + return; + } + + if (evt.button === 0) { + // Enable clicking of links in presentation mode. Please note: + // Only links pointing to destinations in the current PDF document work. + var isInternalLink = (evt.target.href && + evt.target.classList.contains('internalLink')); + if (!isInternalLink) { + // Unless an internal link was clicked, advance one page. + evt.preventDefault(); + PDFView.page += (evt.shiftKey ? -1 : 1); + } + } + }, + + contextMenu: function presentationModeContextMenu(evt) { + PresentationMode.contextMenuOpen = true; + } +}; + +(function presentationModeClosure() { + function presentationModeChange(e) { + if (PresentationMode.isFullscreen) { + PresentationMode.enter(); + } else { + PresentationMode.exit(); + } + } + + window.addEventListener('fullscreenchange', presentationModeChange, false); + window.addEventListener('mozfullscreenchange', presentationModeChange, false); + window.addEventListener('webkitfullscreenchange', presentationModeChange, + false); + window.addEventListener('MSFullscreenChange', presentationModeChange, false); +})(); + + +/* Copyright 2013 Rob Wu + * https://github.com/Rob--W/grab-to-pan.js + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +var GrabToPan = (function GrabToPanClosure() { + /** + * Construct a GrabToPan instance for a given HTML element. + * @param options.element {Element} + * @param options.ignoreTarget {function} optional. See `ignoreTarget(node)` + * @param options.onActiveChanged {function(boolean)} optional. Called + * when grab-to-pan is (de)activated. The first argument is a boolean that + * shows whether grab-to-pan is activated. + */ + function GrabToPan(options) { + this.element = options.element; + this.document = options.element.ownerDocument; + if (typeof options.ignoreTarget === 'function') { + this.ignoreTarget = options.ignoreTarget; + } + this.onActiveChanged = options.onActiveChanged; + + // Bind the contexts to ensure that `this` always points to + // the GrabToPan instance. + this.activate = this.activate.bind(this); + this.deactivate = this.deactivate.bind(this); + this.toggle = this.toggle.bind(this); + this._onmousedown = this._onmousedown.bind(this); + this._onmousemove = this._onmousemove.bind(this); + this._endPan = this._endPan.bind(this); + } + GrabToPan.prototype = { + /** + * Class name of element which can be grabbed + */ + CSS_CLASS_GRAB: 'grab-to-pan-grab', + /** + * Class name of element which is being dragged & panned + */ + CSS_CLASS_GRABBING: 'grab-to-pan-grabbing', + + /** + * Bind a mousedown event to the element to enable grab-detection. + */ + activate: function GrabToPan_activate() { + if (!this.active) { + this.active = true; + this.element.addEventListener('mousedown', this._onmousedown, true); + this.element.classList.add(this.CSS_CLASS_GRAB); + if (this.onActiveChanged) { + this.onActiveChanged(true); + } + } + }, + + /** + * Removes all events. Any pending pan session is immediately stopped. + */ + deactivate: function GrabToPan_deactivate() { + if (this.active) { + this.active = false; + this.element.removeEventListener('mousedown', this._onmousedown, true); + this._endPan(); + this.element.classList.remove(this.CSS_CLASS_GRAB); + if (this.onActiveChanged) { + this.onActiveChanged(false); + } + } + }, + + toggle: function GrabToPan_toggle() { + if (this.active) { + this.deactivate(); + } else { + this.activate(); + } + }, + + /** + * Whether to not pan if the target element is clicked. + * Override this method to change the default behaviour. + * + * @param node {Element} The target of the event + * @return {boolean} Whether to not react to the click event. + */ + ignoreTarget: function GrabToPan_ignoreTarget(node) { + // Use matchesSelector to check whether the clicked element + // is (a child of) an input element / link + return node[matchesSelector]( + 'a[href], a[href] *, input, textarea, button, button *, select, option' + ); + }, + + /** + * @private + */ + _onmousedown: function GrabToPan__onmousedown(event) { + if (event.button !== 0 || this.ignoreTarget(event.target)) { + return; + } + if (event.originalTarget) { + try { + /* jshint expr:true */ + event.originalTarget.tagName; + } catch (e) { + // Mozilla-specific: element is a scrollbar (XUL element) + return; + } + } + + this.scrollLeftStart = this.element.scrollLeft; + this.scrollTopStart = this.element.scrollTop; + this.clientXStart = event.clientX; + this.clientYStart = event.clientY; + this.document.addEventListener('mousemove', this._onmousemove, true); + this.document.addEventListener('mouseup', this._endPan, true); + // When a scroll event occurs before a mousemove, assume that the user + // dragged a scrollbar (necessary for Opera Presto, Safari and IE) + // (not needed for Chrome/Firefox) + this.element.addEventListener('scroll', this._endPan, true); + event.preventDefault(); + event.stopPropagation(); + this.element.classList.remove(this.CSS_CLASS_GRAB); + this.document.documentElement.classList.add(this.CSS_CLASS_GRABBING); + }, + + /** + * @private + */ + _onmousemove: function GrabToPan__onmousemove(event) { + this.element.removeEventListener('scroll', this._endPan, true); + if (isLeftMouseReleased(event)) { + this.document.removeEventListener('mousemove', this._onmousemove, true); + return; + } + var xDiff = event.clientX - this.clientXStart; + var yDiff = event.clientY - this.clientYStart; + this.element.scrollTop = this.scrollTopStart - yDiff; + this.element.scrollLeft = this.scrollLeftStart - xDiff; + }, + + /** + * @private + */ + _endPan: function GrabToPan__endPan() { + this.element.removeEventListener('scroll', this._endPan, true); + this.document.removeEventListener('mousemove', this._onmousemove, true); + this.document.removeEventListener('mouseup', this._endPan, true); + this.document.documentElement.classList.remove(this.CSS_CLASS_GRABBING); + this.element.classList.add(this.CSS_CLASS_GRAB); + } + }; + + // Get the correct (vendor-prefixed) name of the matches method. + var matchesSelector; + ['webkitM', 'mozM', 'msM', 'oM', 'm'].some(function(prefix) { + var name = prefix + 'atches'; + if (name in document.documentElement) { + matchesSelector = name; + } + name += 'Selector'; + if (name in document.documentElement) { + matchesSelector = name; + } + return matchesSelector; // If found, then truthy, and [].some() ends. + }); + + // Browser sniffing because it's impossible to feature-detect + // whether event.which for onmousemove is reliable + var isNotIEorIsIE10plus = !document.documentMode || document.documentMode > 9; + var chrome = window.chrome; + var isChrome15OrOpera15plus = chrome && (chrome.webstore || chrome.app); + // ^ Chrome 15+ ^ Opera 15+ + var isSafari6plus = /Apple/.test(navigator.vendor) && + /Version\/([6-9]\d*|[1-5]\d+)/.test(navigator.userAgent); + + /** + * Whether the left mouse is not pressed. + * @param event {MouseEvent} + * @return {boolean} True if the left mouse button is not pressed. + * False if unsure or if the left mouse button is pressed. + */ + function isLeftMouseReleased(event) { + if ('buttons' in event && isNotIEorIsIE10plus) { + // http://www.w3.org/TR/DOM-Level-3-Events/#events-MouseEvent-buttons + // Firefox 15+ + // Internet Explorer 10+ + return !(event.buttons | 1); + } + if (isChrome15OrOpera15plus || isSafari6plus) { + // Chrome 14+ + // Opera 15+ + // Safari 6.0+ + return event.which === 0; + } + } + + return GrabToPan; +})(); + +var HandTool = { + initialize: function handToolInitialize(options) { + var toggleHandTool = options.toggleHandTool; + this.handTool = new GrabToPan({ + element: options.container, + onActiveChanged: function(isActive) { + if (!toggleHandTool) { + return; + } + if (isActive) { + toggleHandTool.title = + mozL10n.get('hand_tool_disable.title', null, 'Disable hand tool'); + toggleHandTool.firstElementChild.textContent = + mozL10n.get('hand_tool_disable_label', null, 'Disable hand tool'); + } else { + toggleHandTool.title = + mozL10n.get('hand_tool_enable.title', null, 'Enable hand tool'); + toggleHandTool.firstElementChild.textContent = + mozL10n.get('hand_tool_enable_label', null, 'Enable hand tool'); + } + } + }); + if (toggleHandTool) { + toggleHandTool.addEventListener('click', this.handTool.toggle, false); + } + // TODO: Read global prefs and call this.handTool.activate() if needed. + }, + + toggle: function handToolToggle() { + this.handTool.toggle(); + }, + + enterPresentationMode: function handToolEnterPresentationMode() { + if (this.handTool.active) { + this.wasActive = true; + this.handTool.deactivate(); + } + }, + + exitPresentationMode: function handToolExitPresentationMode() { + if (this.wasActive) { + this.wasActive = null; + this.handTool.activate(); + } + } +}; + + +var PDFView = { + pages: [], + thumbnails: [], + currentScale: UNKNOWN_SCALE, + currentScaleValue: null, + initialBookmark: document.location.hash.substring(1), + container: null, + thumbnailContainer: null, + initialized: false, + fellback: false, + pdfDocument: null, + sidebarOpen: false, + pageViewScroll: null, + thumbnailViewScroll: null, + pageRotation: 0, + mouseScrollTimeStamp: 0, + mouseScrollDelta: 0, + lastScroll: 0, + previousPageNumber: 1, + isViewerEmbedded: (window.parent !== window), + idleTimeout: null, + currentPosition: null, + + // called once when the document is loaded + initialize: function pdfViewInitialize() { + var self = this; + var container = this.container = document.getElementById('viewerContainer'); + this.pageViewScroll = {}; + this.watchScroll(container, this.pageViewScroll, updateViewarea); + + var thumbnailContainer = this.thumbnailContainer = + document.getElementById('thumbnailView'); + this.thumbnailViewScroll = {}; + this.watchScroll(thumbnailContainer, this.thumbnailViewScroll, + this.renderHighestPriority.bind(this)); + + PDFFindBar.initialize({ + bar: document.getElementById('findbar'), + toggleButton: document.getElementById('viewFind'), + findField: document.getElementById('findInput'), + highlightAllCheckbox: document.getElementById('findHighlightAll'), + caseSensitiveCheckbox: document.getElementById('findMatchCase'), + findMsg: document.getElementById('findMsg'), + findStatusIcon: document.getElementById('findStatusIcon'), + findPreviousButton: document.getElementById('findPrevious'), + findNextButton: document.getElementById('findNext') + }); + + PDFFindController.initialize({ + pdfPageSource: this, + integratedFind: this.supportsIntegratedFind + }); + + HandTool.initialize({ + container: container, + toggleHandTool: document.getElementById('toggleHandTool') + }); + + SecondaryToolbar.initialize({ + toolbar: document.getElementById('secondaryToolbar'), + presentationMode: PresentationMode, + toggleButton: document.getElementById('secondaryToolbarToggle'), + presentationModeButton: + document.getElementById('secondaryPresentationMode'), + openFile: document.getElementById('secondaryOpenFile'), + print: document.getElementById('secondaryPrint'), + download: document.getElementById('secondaryDownload'), + firstPage: document.getElementById('firstPage'), + lastPage: document.getElementById('lastPage'), + pageRotateCw: document.getElementById('pageRotateCw'), + pageRotateCcw: document.getElementById('pageRotateCcw') + }); + + PasswordPrompt.initialize({ + overlayContainer: document.getElementById('overlayContainer'), + passwordField: document.getElementById('password'), + passwordText: document.getElementById('passwordText'), + passwordSubmit: document.getElementById('passwordSubmit'), + passwordCancel: document.getElementById('passwordCancel') + }); + + PresentationMode.initialize({ + container: container, + secondaryToolbar: SecondaryToolbar, + firstPage: document.getElementById('contextFirstPage'), + lastPage: document.getElementById('contextLastPage'), + pageRotateCw: document.getElementById('contextPageRotateCw'), + pageRotateCcw: document.getElementById('contextPageRotateCcw') + }); + + this.initialized = true; + container.addEventListener('scroll', function() { + self.lastScroll = Date.now(); + }, false); + }, + + getPage: function pdfViewGetPage(n) { + return this.pdfDocument.getPage(n); + }, + + // Helper function to keep track whether a div was scrolled up or down and + // then call a callback. + watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) { + state.down = true; + state.lastY = viewAreaElement.scrollTop; + viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) { + var currentY = viewAreaElement.scrollTop; + var lastY = state.lastY; + if (currentY > lastY) + state.down = true; + else if (currentY < lastY) + state.down = false; + // else do nothing and use previous value + state.lastY = currentY; + callback(); + }, true); + }, + + _setScaleUpdatePages: function pdfView_setScaleUpdatePages( + newScale, newValue, resetAutoSettings, noScroll) { + this.currentScaleValue = newValue; + if (newScale === this.currentScale) { + return; + } + for (var i = 0, ii = this.pages.length; i < ii; i++) { + this.pages[i].update(newScale); + } + this.currentScale = newScale; + + if (!noScroll) { + var page = this.page, dest; + if (this.currentPosition && !IGNORE_CURRENT_POSITION_ON_ZOOM) { + page = this.currentPosition.page; + dest = [null, { name: 'XYZ' }, this.currentPosition.left, + this.currentPosition.top, null]; + } + this.pages[page - 1].scrollIntoView(dest); + } + var event = document.createEvent('UIEvents'); + event.initUIEvent('scalechange', false, false, window, 0); + event.scale = newScale; + event.resetAutoSettings = resetAutoSettings; + window.dispatchEvent(event); + }, + + setScale: function pdfViewSetScale(value, resetAutoSettings, noScroll) { + if (value === 'custom') { + return; + } + var scale = parseFloat(value); + + if (scale > 0) { + this._setScaleUpdatePages(scale, value, true, noScroll); + } else { + var currentPage = this.pages[this.page - 1]; + if (!currentPage) { + return; + } + var pageWidthScale = (this.container.clientWidth - SCROLLBAR_PADDING) / + currentPage.width * currentPage.scale; + var pageHeightScale = (this.container.clientHeight - VERTICAL_PADDING) / + currentPage.height * currentPage.scale; + switch (value) { + case 'page-actual': + scale = 1; + break; + case 'page-width': + scale = pageWidthScale; + break; + case 'page-height': + scale = pageHeightScale; + break; + case 'page-fit': + scale = Math.min(pageWidthScale, pageHeightScale); + break; + case 'auto': + scale = Math.min(MAX_AUTO_SCALE, pageWidthScale); + break; + default: + console.error('pdfViewSetScale: \'' + value + + '\' is an unknown zoom value.'); + return; + } + this._setScaleUpdatePages(scale, value, resetAutoSettings, noScroll); + + selectScaleOption(value); + } + }, + + zoomIn: function pdfViewZoomIn(ticks) { + var newScale = this.currentScale; + do { + newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2); + newScale = Math.ceil(newScale * 10) / 10; + newScale = Math.min(MAX_SCALE, newScale); + } while (--ticks && newScale < MAX_SCALE); + this.setScale(newScale, true); + }, + + zoomOut: function pdfViewZoomOut(ticks) { + var newScale = this.currentScale; + do { + newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2); + newScale = Math.floor(newScale * 10) / 10; + newScale = Math.max(MIN_SCALE, newScale); + } while (--ticks && newScale > MIN_SCALE); + this.setScale(newScale, true); + }, + + set page(val) { + var pages = this.pages; + var event = document.createEvent('UIEvents'); + event.initUIEvent('pagechange', false, false, window, 0); + + if (!(0 < val && val <= pages.length)) { + this.previousPageNumber = val; + event.pageNumber = this.page; + window.dispatchEvent(event); + return; + } + + pages[val - 1].updateStats(); + this.previousPageNumber = currentPageNumber; + currentPageNumber = val; + event.pageNumber = val; + window.dispatchEvent(event); + + // checking if the this.page was called from the updateViewarea function: + // avoiding the creation of two "set page" method (internal and public) + if (updateViewarea.inProgress) { + return; + } + // Avoid scrolling the first page during loading + if (this.loading && val === 1) { + return; + } + pages[val - 1].scrollIntoView(); + }, + + get page() { + return currentPageNumber; + }, + + get supportsPrinting() { + var canvas = document.createElement('canvas'); + var value = 'mozPrintCallback' in canvas; + // shadow + Object.defineProperty(this, 'supportsPrinting', { value: value, + enumerable: true, + configurable: true, + writable: false }); + return value; + }, + + get supportsFullscreen() { + var doc = document.documentElement; + var support = doc.requestFullscreen || doc.mozRequestFullScreen || + doc.webkitRequestFullScreen || doc.msRequestFullscreen; + + if (document.fullscreenEnabled === false || + document.mozFullScreenEnabled === false || + document.webkitFullscreenEnabled === false || + document.msFullscreenEnabled === false) { + support = false; + } else if (this.isViewerEmbedded) { + // Need to check if the viewer is embedded as well, to prevent issues with + // presentation mode when the viewer is embedded in '' tags. + support = false; + } + + Object.defineProperty(this, 'supportsFullscreen', { value: support, + enumerable: true, + configurable: true, + writable: false }); + return support; + }, + + get supportsIntegratedFind() { + var support = false; + Object.defineProperty(this, 'supportsIntegratedFind', { value: support, + enumerable: true, + configurable: true, + writable: false }); + return support; + }, + + get supportsDocumentFonts() { + var support = true; + Object.defineProperty(this, 'supportsDocumentFonts', { value: support, + enumerable: true, + configurable: true, + writable: false }); + return support; + }, + + get supportsDocumentColors() { + var support = true; + Object.defineProperty(this, 'supportsDocumentColors', { value: support, + enumerable: true, + configurable: true, + writable: false }); + return support; + }, + + get loadingBar() { + var bar = new ProgressBar('#loadingBar', {}); + Object.defineProperty(this, 'loadingBar', { value: bar, + enumerable: true, + configurable: true, + writable: false }); + return bar; + }, + + get isHorizontalScrollbarEnabled() { + return (PresentationMode.active ? false : + (this.container.scrollWidth > this.container.clientWidth)); + }, + + initPassiveLoading: function pdfViewInitPassiveLoading() { + var pdfDataRangeTransport = { + rangeListeners: [], + progressListeners: [], + + addRangeListener: function PdfDataRangeTransport_addRangeListener( + listener) { + this.rangeListeners.push(listener); + }, + + addProgressListener: function PdfDataRangeTransport_addProgressListener( + listener) { + this.progressListeners.push(listener); + }, + + onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) { + var listeners = this.rangeListeners; + for (var i = 0, n = listeners.length; i < n; ++i) { + listeners[i](begin, chunk); + } + }, + + onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) { + var listeners = this.progressListeners; + for (var i = 0, n = listeners.length; i < n; ++i) { + listeners[i](loaded); + } + }, + + requestDataRange: function PdfDataRangeTransport_requestDataRange( + begin, end) { + FirefoxCom.request('requestDataRange', { begin: begin, end: end }); + } + }; + + window.addEventListener('message', function windowMessage(e) { + var args = e.data; + + if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) + return; + switch (args.pdfjsLoadAction) { + case 'supportsRangedLoading': + PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, { + length: args.length, + initialData: args.data + }); + break; + case 'range': + pdfDataRangeTransport.onDataRange(args.begin, args.chunk); + break; + case 'rangeProgress': + pdfDataRangeTransport.onDataProgress(args.loaded); + break; + case 'progress': + PDFView.progress(args.loaded / args.total); + break; + case 'complete': + if (!args.data) { + PDFView.error(mozL10n.get('loading_error', null, + 'An error occurred while loading the PDF.'), e); + break; + } + PDFView.open(args.data, 0); + break; + } + }); + FirefoxCom.requestSync('initPassiveLoading', null); + }, + + setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) { + this.url = url; + try { + this.setTitle(decodeURIComponent(getFileName(url)) || url); + } catch (e) { + // decodeURIComponent may throw URIError, + // fall back to using the unprocessed url in that case + this.setTitle(url); + } + }, + + setTitle: function pdfViewSetTitle(title) { + document.title = title; + }, + + // TODO(mack): This function signature should really be pdfViewOpen(url, args) + open: function pdfViewOpen(url, scale, password, + pdfDataRangeTransport, args) { + var parameters = {password: password}; + if (typeof url === 'string') { // URL + this.setTitleUsingUrl(url); + parameters.url = url; + } else if (url && 'byteLength' in url) { // ArrayBuffer + parameters.data = url; + } + if (args) { + for (var prop in args) { + parameters[prop] = args[prop]; + } + } + + this.pdfDocument = null; + var self = this; + self.loading = true; + var passwordNeeded = function passwordNeeded(updatePassword, reason) { + PasswordPrompt.updatePassword = updatePassword; + PasswordPrompt.reason = reason; + PasswordPrompt.show(); + }; + + function getDocumentProgress(progressData) { + self.progress(progressData.loaded / progressData.total); + } + + PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded, + getDocumentProgress).then( + function getDocumentCallback(pdfDocument) { + self.load(pdfDocument, scale); + self.loading = false; + }, + function getDocumentError(message, exception) { + var loadingErrorMessage = mozL10n.get('loading_error', null, + 'An error occurred while loading the PDF.'); + + if (exception && exception.name === 'InvalidPDFException') { + // change error message also for other builds + var loadingErrorMessage = mozL10n.get('invalid_file_error', null, + 'Invalid or corrupted PDF file.'); + } + + if (exception && exception.name === 'MissingPDFException') { + // special message for missing PDF's + var loadingErrorMessage = mozL10n.get('missing_file_error', null, + 'Missing PDF file.'); + + } + + var moreInfo = { + message: message + }; + self.error(loadingErrorMessage, moreInfo); + self.loading = false; + } + ); + }, + + download: function pdfViewDownload() { + function noData() { + downloadManager.downloadUrl(url, filename); + } + + var url = this.url.split('#')[0]; + var filename = getPDFFileNameFromURL(url); + var downloadManager = new DownloadManager(); + downloadManager.onerror = function (err) { + // This error won't really be helpful because it's likely the + // fallback won't work either (or is already open). + PDFView.error('PDF failed to download.'); + }; + + if (!this.pdfDocument) { // the PDF is not ready yet + noData(); + return; + } + + this.pdfDocument.getData().then( + function getDataSuccess(data) { + var blob = PDFJS.createBlob(data, 'application/pdf'); + downloadManager.download(blob, url, filename); + }, + noData // Error occurred try downloading with just the url. + ).then(null, noData); + }, + + fallback: function pdfViewFallback(featureId) { + return; + }, + + navigateTo: function pdfViewNavigateTo(dest) { + var destString = ''; + var self = this; + + var goToDestination = function(destRef) { + self.pendingRefStr = null; + // dest array looks like that: + var pageNumber = destRef instanceof Object ? + self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : + (destRef + 1); + if (pageNumber) { + if (pageNumber > self.pages.length) { + pageNumber = self.pages.length; + } + var currentPage = self.pages[pageNumber - 1]; + currentPage.scrollIntoView(dest); + + // Update the browsing history. + PDFHistory.push({ dest: dest, hash: destString, page: pageNumber }); + } else { + self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) { + var pageNum = pageIndex + 1; + self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum; + goToDestination(destRef); + }); + } + }; + + this.destinationsPromise.then(function() { + if (typeof dest === 'string') { + destString = dest; + dest = self.destinations[dest]; + } + if (!(dest instanceof Array)) { + return; // invalid destination + } + goToDestination(dest[0]); + }); + }, + + getDestinationHash: function pdfViewGetDestinationHash(dest) { + if (typeof dest === 'string') + return PDFView.getAnchorUrl('#' + escape(dest)); + if (dest instanceof Array) { + var destRef = dest[0]; // see navigateTo method for dest format + var pageNumber = destRef instanceof Object ? + this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : + (destRef + 1); + if (pageNumber) { + var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber); + var destKind = dest[1]; + if (typeof destKind === 'object' && 'name' in destKind && + destKind.name == 'XYZ') { + var scale = (dest[4] || this.currentScaleValue); + var scaleNumber = parseFloat(scale); + if (scaleNumber) { + scale = scaleNumber * 100; + } + pdfOpenParams += '&zoom=' + scale; + if (dest[2] || dest[3]) { + pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0); + } + } + return pdfOpenParams; + } + } + return ''; + }, + + /** + * Prefix the full url on anchor links to make sure that links are resolved + * relative to the current URL instead of the one defined in . + * @param {String} anchor The anchor hash, including the #. + */ + getAnchorUrl: function getAnchorUrl(anchor) { + return anchor; + }, + + /** + * Show the error box. + * @param {String} message A message that is human readable. + * @param {Object} moreInfo (optional) Further information about the error + * that is more technical. Should have a 'message' + * and optionally a 'stack' property. + */ + error: function pdfViewError(message, moreInfo) { + var moreInfoText = mozL10n.get('error_version_info', + {version: PDFJS.version || '?', build: PDFJS.build || '?'}, + 'PDF.js v{{version}} (build: {{build}})') + '\n'; + if (moreInfo) { + moreInfoText += + mozL10n.get('error_message', {message: moreInfo.message}, + 'Message: {{message}}'); + if (moreInfo.stack) { + moreInfoText += '\n' + + mozL10n.get('error_stack', {stack: moreInfo.stack}, + 'Stack: {{stack}}'); + } else { + if (moreInfo.filename) { + moreInfoText += '\n' + + mozL10n.get('error_file', {file: moreInfo.filename}, + 'File: {{file}}'); + } + if (moreInfo.lineNumber) { + moreInfoText += '\n' + + mozL10n.get('error_line', {line: moreInfo.lineNumber}, + 'Line: {{line}}'); + } + } + } + + var errorWrapper = document.getElementById('errorWrapper'); + errorWrapper.removeAttribute('hidden'); + + var errorMessage = document.getElementById('errorMessage'); + errorMessage.textContent = message; + + var closeButton = document.getElementById('errorClose'); + closeButton.onclick = function() { + errorWrapper.setAttribute('hidden', 'true'); + }; + + var errorMoreInfo = document.getElementById('errorMoreInfo'); + var moreInfoButton = document.getElementById('errorShowMore'); + var lessInfoButton = document.getElementById('errorShowLess'); + moreInfoButton.onclick = function() { + errorMoreInfo.removeAttribute('hidden'); + moreInfoButton.setAttribute('hidden', 'true'); + lessInfoButton.removeAttribute('hidden'); + errorMoreInfo.style.height = errorMoreInfo.scrollHeight + 'px'; + }; + lessInfoButton.onclick = function() { + errorMoreInfo.setAttribute('hidden', 'true'); + moreInfoButton.removeAttribute('hidden'); + lessInfoButton.setAttribute('hidden', 'true'); + }; + moreInfoButton.oncontextmenu = noContextMenuHandler; + lessInfoButton.oncontextmenu = noContextMenuHandler; + closeButton.oncontextmenu = noContextMenuHandler; + moreInfoButton.removeAttribute('hidden'); + lessInfoButton.setAttribute('hidden', 'true'); + errorMoreInfo.value = moreInfoText; + }, + + progress: function pdfViewProgress(level) { + var percent = Math.round(level * 100); + // When we transition from full request to range requests, it's possible + // that we discard some of the loaded data. This can cause the loading + // bar to move backwards. So prevent this by only updating the bar if it + // increases. + if (percent > PDFView.loadingBar.percent) { + PDFView.loadingBar.percent = percent; + } + }, + + load: function pdfViewLoad(pdfDocument, scale) { + var self = this; + var isOnePageRenderedResolved = false; + var resolveOnePageRendered = null; + var onePageRendered = new Promise(function (resolve) { + resolveOnePageRendered = resolve; + }); + function bindOnAfterDraw(pageView, thumbnailView) { + // when page is painted, using the image as thumbnail base + pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() { + if (!isOnePageRenderedResolved) { + isOnePageRenderedResolved = true; + resolveOnePageRendered(); + } + thumbnailView.setImage(pageView.canvas); + }; + } + + PDFFindController.reset(); + + this.pdfDocument = pdfDocument; + + var errorWrapper = document.getElementById('errorWrapper'); + errorWrapper.setAttribute('hidden', 'true'); + + pdfDocument.dataLoaded().then(function() { + PDFView.loadingBar.hide(); + var outerContainer = document.getElementById('outerContainer'); + outerContainer.classList.remove('loadingInProgress'); + }); + + var thumbsView = document.getElementById('thumbnailView'); + thumbsView.parentNode.scrollTop = 0; + + while (thumbsView.hasChildNodes()) + thumbsView.removeChild(thumbsView.lastChild); + + if ('_loadingInterval' in thumbsView) + clearInterval(thumbsView._loadingInterval); + + var container = document.getElementById('viewer'); + while (container.hasChildNodes()) + container.removeChild(container.lastChild); + + var pagesCount = pdfDocument.numPages; + + var id = pdfDocument.fingerprint; + document.getElementById('numPages').textContent = + mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}'); + document.getElementById('pageNumber').max = pagesCount; + + var prefs = PDFView.prefs = new Preferences(); + PDFView.documentFingerprint = id; + var store = PDFView.store = new ViewHistory(id); + + this.pageRotation = 0; + + var pages = this.pages = []; + var pagesRefMap = this.pagesRefMap = {}; + var thumbnails = this.thumbnails = []; + + var resolvePagesPromise; + var pagesPromise = new Promise(function (resolve) { + resolvePagesPromise = resolve; + }); + this.pagesPromise = pagesPromise; + + var firstPagePromise = pdfDocument.getPage(1); + + // Fetch a single page so we can get a viewport that will be the default + // viewport for all pages + firstPagePromise.then(function(pdfPage) { + var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS); + for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { + var viewportClone = viewport.clone(); + var pageView = new PageView(container, pageNum, scale, + self.navigateTo.bind(self), + viewportClone); + var thumbnailView = new ThumbnailView(thumbsView, pageNum, + viewportClone); + bindOnAfterDraw(pageView, thumbnailView); + pages.push(pageView); + thumbnails.push(thumbnailView); + } + + // Fetch all the pages since the viewport is needed before printing + // starts to create the correct size canvas. Wait until one page is + // rendered so we don't tie up too many resources early on. + onePageRendered.then(function () { + if (!PDFJS.disableAutoFetch) { + var getPagesLeft = pagesCount; + for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { + pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) { + var pageView = pages[pageNum - 1]; + if (!pageView.pdfPage) { + pageView.setPdfPage(pdfPage); + } + var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R'; + pagesRefMap[refStr] = pageNum; + getPagesLeft--; + if (!getPagesLeft) { + resolvePagesPromise(); + } + }.bind(null, pageNum)); + } + } else { + // XXX: Printing is semi-broken with auto fetch disabled. + resolvePagesPromise(); + } + }); + + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('documentload', true, true, {}); + window.dispatchEvent(event); + + PDFView.loadingBar.setWidth(container); + + PDFFindController.resolveFirstPage(); + }); + + var prefsPromise = prefs.initializedPromise; + var storePromise = store.initializedPromise; + Promise.all([firstPagePromise, prefsPromise, storePromise]). + then(function() { + var showPreviousViewOnLoad = prefs.get('showPreviousViewOnLoad'); + var defaultZoomValue = prefs.get('defaultZoomValue'); + + var storedHash = null; + if (showPreviousViewOnLoad && store.get('exists', false)) { + var pageNum = store.get('page', '1'); + var zoom = defaultZoomValue || store.get('zoom', PDFView.currentScale); + var left = store.get('scrollLeft', '0'); + var top = store.get('scrollTop', '0'); + + storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' + + left + ',' + top; + } else if (defaultZoomValue) { + storedHash = 'page=1&zoom=' + defaultZoomValue; + } + // Initialize the browsing history. + PDFHistory.initialize(self.documentFingerprint); + + self.setInitialView(storedHash, scale); + + // Make all navigation keys work on document load, + // unless the viewer is embedded in a web page. + if (!self.isViewerEmbedded) { + self.container.focus(); + } + }); + + pagesPromise.then(function() { + if (PDFView.supportsPrinting) { + pdfDocument.getJavaScript().then(function(javaScript) { + if (javaScript.length) { + console.warn('Warning: JavaScript is not supported'); + PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript); + } + // Hack to support auto printing. + var regex = /\bprint\s*\(/g; + for (var i = 0, ii = javaScript.length; i < ii; i++) { + var js = javaScript[i]; + if (js && regex.test(js)) { + setTimeout(function() { + window.print(); + }); + return; + } + } + }); + } + }); + + var destinationsPromise = + this.destinationsPromise = pdfDocument.getDestinations(); + destinationsPromise.then(function(destinations) { + self.destinations = destinations; + }); + + // outline depends on destinations and pagesRefMap + var promises = [pagesPromise, destinationsPromise, + PDFView.animationStartedPromise]; + Promise.all(promises).then(function() { + pdfDocument.getOutline().then(function(outline) { + self.outline = new DocumentOutlineView(outline); + document.getElementById('viewOutline').disabled = !outline; + + if (outline && prefs.get('ifAvailableShowOutlineOnLoad')) { + if (!self.sidebarOpen) { + document.getElementById('sidebarToggle').click(); + } + self.switchSidebarView('outline'); + } + }); + }); + + pdfDocument.getMetadata().then(function(data) { + var info = data.info, metadata = data.metadata; + self.documentInfo = info; + self.metadata = metadata; + + // Provides some basic debug information + console.log('PDF ' + pdfDocument.fingerprint + ' [' + + info.PDFFormatVersion + ' ' + (info.Producer || '-') + + ' / ' + (info.Creator || '-') + ']' + + (PDFJS.version ? ' (PDF.js: ' + PDFJS.version + ')' : '')); + + var pdfTitle; + if (metadata) { + if (metadata.has('dc:title')) + pdfTitle = metadata.get('dc:title'); + } + + if (!pdfTitle && info && info['Title']) + pdfTitle = info['Title']; + + if (pdfTitle) + self.setTitle(pdfTitle + ' - ' + document.title); + + if (info.IsAcroFormPresent) { + console.warn('Warning: AcroForm/XFA is not supported'); + PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.forms); + } + + }); + }, + + setInitialView: function pdfViewSetInitialView(storedHash, scale) { + // Reset the current scale, as otherwise the page's scale might not get + // updated if the zoom level stayed the same. + this.currentScale = 0; + this.currentScaleValue = null; + // When opening a new file (when one is already loaded in the viewer): + // Reset 'currentPageNumber', since otherwise the page's scale will be wrong + // if 'currentPageNumber' is larger than the number of pages in the file. + document.getElementById('pageNumber').value = currentPageNumber = 1; + // Reset the current position when loading a new file, + // to prevent displaying the wrong position in the document. + this.currentPosition = null; + + if (PDFHistory.initialDestination) { + this.navigateTo(PDFHistory.initialDestination); + PDFHistory.initialDestination = null; + } else if (this.initialBookmark) { + this.setHash(this.initialBookmark); + PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark); + this.initialBookmark = null; + } else if (storedHash) { + this.setHash(storedHash); + } else if (scale) { + this.setScale(scale, true); + this.page = 1; + } + + if (PDFView.currentScale === UNKNOWN_SCALE) { + // Scale was not initialized: invalid bookmark or scale was not specified. + // Setting the default one. + this.setScale(DEFAULT_SCALE, true); + } + }, + + renderHighestPriority: function pdfViewRenderHighestPriority() { + if (PDFView.idleTimeout) { + clearTimeout(PDFView.idleTimeout); + PDFView.idleTimeout = null; + } + + // Pages have a higher priority than thumbnails, so check them first. + var visiblePages = this.getVisiblePages(); + var pageView = this.getHighestPriority(visiblePages, this.pages, + this.pageViewScroll.down); + if (pageView) { + this.renderView(pageView, 'page'); + return; + } + // No pages needed rendering so check thumbnails. + if (this.sidebarOpen) { + var visibleThumbs = this.getVisibleThumbs(); + var thumbView = this.getHighestPriority(visibleThumbs, + this.thumbnails, + this.thumbnailViewScroll.down); + if (thumbView) { + this.renderView(thumbView, 'thumbnail'); + return; + } + } + + PDFView.idleTimeout = setTimeout(function () { + PDFView.cleanup(); + }, CLEANUP_TIMEOUT); + }, + + cleanup: function pdfViewCleanup() { + for (var i = 0, ii = this.pages.length; i < ii; i++) { + if (this.pages[i] && + this.pages[i].renderingState !== RenderingStates.FINISHED) { + this.pages[i].reset(); + } + } + this.pdfDocument.cleanup(); + }, + + getHighestPriority: function pdfViewGetHighestPriority(visible, views, + scrolledDown) { + // The state has changed figure out which page has the highest priority to + // render next (if any). + // Priority: + // 1 visible pages + // 2 if last scrolled down page after the visible pages + // 2 if last scrolled up page before the visible pages + var visibleViews = visible.views; + + var numVisible = visibleViews.length; + if (numVisible === 0) { + return false; + } + for (var i = 0; i < numVisible; ++i) { + var view = visibleViews[i].view; + if (!this.isViewFinished(view)) + return view; + } + + // All the visible views have rendered, try to render next/previous pages. + if (scrolledDown) { + var nextPageIndex = visible.last.id; + // ID's start at 1 so no need to add 1. + if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) + return views[nextPageIndex]; + } else { + var previousPageIndex = visible.first.id - 2; + if (views[previousPageIndex] && + !this.isViewFinished(views[previousPageIndex])) + return views[previousPageIndex]; + } + // Everything that needs to be rendered has been. + return false; + }, + + isViewFinished: function pdfViewIsViewFinished(view) { + return view.renderingState === RenderingStates.FINISHED; + }, + + // Render a page or thumbnail view. This calls the appropriate function based + // on the views state. If the view is already rendered it will return false. + renderView: function pdfViewRender(view, type) { + var state = view.renderingState; + switch (state) { + case RenderingStates.FINISHED: + return false; + case RenderingStates.PAUSED: + PDFView.highestPriorityPage = type + view.id; + view.resume(); + break; + case RenderingStates.RUNNING: + PDFView.highestPriorityPage = type + view.id; + break; + case RenderingStates.INITIAL: + PDFView.highestPriorityPage = type + view.id; + view.draw(this.renderHighestPriority.bind(this)); + break; + } + return true; + }, + + setHash: function pdfViewSetHash(hash) { + if (!hash) + return; + + if (hash.indexOf('=') >= 0) { + var params = PDFView.parseQueryString(hash); + // borrowing syntax from "Parameters for Opening PDF Files" + if ('nameddest' in params) { + PDFHistory.updateNextHashParam(params.nameddest); + PDFView.navigateTo(params.nameddest); + return; + } + var pageNumber, dest; + if ('page' in params) { + pageNumber = (params.page | 0) || 1; + } + if ('zoom' in params) { + var zoomArgs = params.zoom.split(','); // scale,left,top + // building destination array + + // If the zoom value, it has to get divided by 100. If it is a string, + // it should stay as it is. + var zoomArg = zoomArgs[0]; + var zoomArgNumber = parseFloat(zoomArg); + if (zoomArgNumber) { + zoomArg = zoomArgNumber / 100; + } + dest = [null, {name: 'XYZ'}, + zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null, + zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null, + zoomArg]; + } + if (dest) { + var currentPage = this.pages[(pageNumber || this.page) - 1]; + currentPage.scrollIntoView(dest); + } else if (pageNumber) { + this.page = pageNumber; // simple page + } + if ('pagemode' in params) { + var toggle = document.getElementById('sidebarToggle'); + if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks') { + if (!this.sidebarOpen) { + toggle.click(); + } + this.switchSidebarView(params.pagemode === 'thumbs' ? + 'thumbs' : 'outline'); + } else if (params.pagemode === 'none' && this.sidebarOpen) { + toggle.click(); + } + } + } else if (/^\d+$/.test(hash)) { // page number + this.page = hash; + } else { // named destination + PDFHistory.updateNextHashParam(unescape(hash)); + PDFView.navigateTo(unescape(hash)); + } + }, + + switchSidebarView: function pdfViewSwitchSidebarView(view) { + var thumbsView = document.getElementById('thumbnailView'); + var outlineView = document.getElementById('outlineView'); + + var thumbsButton = document.getElementById('viewThumbnail'); + var outlineButton = document.getElementById('viewOutline'); + + switch (view) { + case 'thumbs': + var wasOutlineViewVisible = thumbsView.classList.contains('hidden'); + + thumbsButton.classList.add('toggled'); + outlineButton.classList.remove('toggled'); + thumbsView.classList.remove('hidden'); + outlineView.classList.add('hidden'); + + PDFView.renderHighestPriority(); + + if (wasOutlineViewVisible) { + // Ensure that the thumbnail of the current page is visible + // when switching from the outline view. + scrollIntoView(document.getElementById('thumbnailContainer' + + this.page)); + } + break; + + case 'outline': + thumbsButton.classList.remove('toggled'); + outlineButton.classList.add('toggled'); + thumbsView.classList.add('hidden'); + outlineView.classList.remove('hidden'); + + if (outlineButton.getAttribute('disabled')) + return; + break; + } + }, + + getVisiblePages: function pdfViewGetVisiblePages() { + return this.getVisibleElements(this.container, this.pages, + !PresentationMode.active); + }, + + getVisibleThumbs: function pdfViewGetVisibleThumbs() { + return this.getVisibleElements(this.thumbnailContainer, this.thumbnails); + }, + + // Generic helper to find out what elements are visible within a scroll pane. + getVisibleElements: function pdfViewGetVisibleElements( + scrollEl, views, sortByVisibility) { + var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight; + var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth; + + var visible = [], view; + var currentHeight, viewHeight, hiddenHeight, percentHeight; + var currentWidth, viewWidth; + for (var i = 0, ii = views.length; i < ii; ++i) { + view = views[i]; + currentHeight = view.el.offsetTop + view.el.clientTop; + viewHeight = view.el.clientHeight; + if ((currentHeight + viewHeight) < top) { + continue; + } + if (currentHeight > bottom) { + break; + } + currentWidth = view.el.offsetLeft + view.el.clientLeft; + viewWidth = view.el.clientWidth; + if ((currentWidth + viewWidth) < left || currentWidth > right) { + continue; + } + hiddenHeight = Math.max(0, top - currentHeight) + + Math.max(0, currentHeight + viewHeight - bottom); + percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0; + + visible.push({ id: view.id, x: currentWidth, y: currentHeight, + view: view, percent: percentHeight }); + } + + var first = visible[0]; + var last = visible[visible.length - 1]; + + if (sortByVisibility) { + visible.sort(function(a, b) { + var pc = a.percent - b.percent; + if (Math.abs(pc) > 0.001) { + return -pc; + } + return a.id - b.id; // ensure stability + }); + } + return {first: first, last: last, views: visible}; + }, + + // Helper function to parse query string (e.g. ?param1=value&parm2=...). + parseQueryString: function pdfViewParseQueryString(query) { + var parts = query.split('&'); + var params = {}; + for (var i = 0, ii = parts.length; i < parts.length; ++i) { + var param = parts[i].split('='); + var key = param[0]; + var value = param.length > 1 ? param[1] : null; + params[decodeURIComponent(key)] = decodeURIComponent(value); + } + return params; + }, + + beforePrint: function pdfViewSetupBeforePrint() { + if (!this.supportsPrinting) { + var printMessage = mozL10n.get('printing_not_supported', null, + 'Warning: Printing is not fully supported by this browser.'); + this.error(printMessage); + return; + } + + var alertNotReady = false; + if (!this.pages.length) { + alertNotReady = true; + } else { + for (var i = 0, ii = this.pages.length; i < ii; ++i) { + if (!this.pages[i].pdfPage) { + alertNotReady = true; + break; + } + } + } + if (alertNotReady) { + var notReadyMessage = mozL10n.get('printing_not_ready', null, + 'Warning: The PDF is not fully loaded for printing.'); + window.alert(notReadyMessage); + return; + } + + var body = document.querySelector('body'); + body.setAttribute('data-mozPrintCallback', true); + for (var i = 0, ii = this.pages.length; i < ii; ++i) { + this.pages[i].beforePrint(); + } + }, + + afterPrint: function pdfViewSetupAfterPrint() { + var div = document.getElementById('printContainer'); + while (div.hasChildNodes()) + div.removeChild(div.lastChild); + }, + + rotatePages: function pdfViewRotatePages(delta) { + var currentPage = this.pages[this.page - 1]; + this.pageRotation = (this.pageRotation + 360 + delta) % 360; + + for (var i = 0, l = this.pages.length; i < l; i++) { + var page = this.pages[i]; + page.update(page.scale, this.pageRotation); + } + + for (var i = 0, l = this.thumbnails.length; i < l; i++) { + var thumb = this.thumbnails[i]; + thumb.update(this.pageRotation); + } + + this.setScale(this.currentScaleValue, true, true); + + this.renderHighestPriority(); + + if (currentPage) { + currentPage.scrollIntoView(); + } + }, + + /** + * This function flips the page in presentation mode if the user scrolls up + * or down with large enough motion and prevents page flipping too often. + * + * @this {PDFView} + * @param {number} mouseScrollDelta The delta value from the mouse event. + */ + mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) { + var MOUSE_SCROLL_COOLDOWN_TIME = 50; + + var currentTime = (new Date()).getTime(); + var storedTime = this.mouseScrollTimeStamp; + + // In case one page has already been flipped there is a cooldown time + // which has to expire before next page can be scrolled on to. + if (currentTime > storedTime && + currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) + return; + + // In case the user decides to scroll to the opposite direction than before + // clear the accumulated delta. + if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) || + (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) + this.clearMouseScrollState(); + + this.mouseScrollDelta += mouseScrollDelta; + + var PAGE_FLIP_THRESHOLD = 120; + if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) { + + var PageFlipDirection = { + UP: -1, + DOWN: 1 + }; + + // In presentation mode scroll one page at a time. + var pageFlipDirection = (this.mouseScrollDelta > 0) ? + PageFlipDirection.UP : + PageFlipDirection.DOWN; + this.clearMouseScrollState(); + var currentPage = this.page; + + // In case we are already on the first or the last page there is no need + // to do anything. + if ((currentPage == 1 && pageFlipDirection == PageFlipDirection.UP) || + (currentPage == this.pages.length && + pageFlipDirection == PageFlipDirection.DOWN)) + return; + + this.page += pageFlipDirection; + this.mouseScrollTimeStamp = currentTime; + } + }, + + /** + * This function clears the member attributes used with mouse scrolling in + * presentation mode. + * + * @this {PDFView} + */ + clearMouseScrollState: function pdfViewClearMouseScrollState() { + this.mouseScrollTimeStamp = 0; + this.mouseScrollDelta = 0; + } +}; + + +var PageView = function pageView(container, id, scale, + navigateTo, defaultViewport) { + this.id = id; + + this.rotation = 0; + this.scale = scale || 1.0; + this.viewport = defaultViewport; + this.pdfPageRotate = defaultViewport.rotation; + + this.renderingState = RenderingStates.INITIAL; + this.resume = null; + + this.textLayer = null; + + this.zoomLayer = null; + + this.annotationLayer = null; + + var anchor = document.createElement('a'); + anchor.name = '' + this.id; + + var div = this.el = document.createElement('div'); + div.id = 'pageContainer' + this.id; + div.className = 'page'; + div.style.width = Math.floor(this.viewport.width) + 'px'; + div.style.height = Math.floor(this.viewport.height) + 'px'; + + container.appendChild(anchor); + container.appendChild(div); + + this.setPdfPage = function pageViewSetPdfPage(pdfPage) { + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation); + this.stats = pdfPage.stats; + this.reset(); + }; + + this.destroy = function pageViewDestroy() { + this.zoomLayer = null; + this.reset(); + if (this.pdfPage) { + this.pdfPage.destroy(); + } + }; + + this.reset = function pageViewReset() { + if (this.renderTask) { + this.renderTask.cancel(); + } + this.resume = null; + this.renderingState = RenderingStates.INITIAL; + + div.style.width = Math.floor(this.viewport.width) + 'px'; + div.style.height = Math.floor(this.viewport.height) + 'px'; + + var childNodes = div.childNodes; + for (var i = div.childNodes.length - 1; i >= 0; i--) { + var node = childNodes[i]; + if (this.zoomLayer && this.zoomLayer === node) { + continue; + } + div.removeChild(node); + } + div.removeAttribute('data-loaded'); + + this.annotationLayer = null; + + delete this.canvas; + + this.loadingIconDiv = document.createElement('div'); + this.loadingIconDiv.className = 'loadingIcon'; + div.appendChild(this.loadingIconDiv); + }; + + this.update = function pageViewUpdate(scale, rotation) { + this.scale = scale || this.scale; + + if (typeof rotation !== 'undefined') { + this.rotation = rotation; + } + + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = this.viewport.clone({ + scale: this.scale * CSS_UNITS, + rotation: totalRotation + }); + + if (USE_ONLY_CSS_ZOOM && this.canvas) { + this.cssTransform(this.canvas); + return; + } else if (this.canvas && !this.zoomLayer) { + this.zoomLayer = this.canvas.parentNode; + this.zoomLayer.style.position = 'absolute'; + } + if (this.zoomLayer) { + this.cssTransform(this.zoomLayer.firstChild); + } + this.reset(); + }; + + this.cssTransform = function pageCssTransform(canvas) { + // Scale canvas, canvas wrapper, and page container. + var width = this.viewport.width; + var height = this.viewport.height; + canvas.style.width = canvas.parentNode.style.width = div.style.width = + Math.floor(width) + 'px'; + canvas.style.height = canvas.parentNode.style.height = div.style.height = + Math.floor(height) + 'px'; + // The canvas may have been originally rotated, so rotate relative to that. + var relativeRotation = this.viewport.rotation - canvas._viewport.rotation; + var absRotation = Math.abs(relativeRotation); + var scaleX = 1, scaleY = 1; + if (absRotation === 90 || absRotation === 270) { + // Scale x and y because of the rotation. + scaleX = height / width; + scaleY = width / height; + } + var cssTransform = 'rotate(' + relativeRotation + 'deg) ' + + 'scale(' + scaleX + ',' + scaleY + ')'; + CustomStyle.setProp('transform', canvas, cssTransform); + + if (this.textLayer) { + // Rotating the text layer is more complicated since the divs inside the + // the text layer are rotated. + // TODO: This could probably be simplified by drawing the text layer in + // one orientation then rotating overall. + var textRelativeRotation = this.viewport.rotation - + this.textLayer.viewport.rotation; + var textAbsRotation = Math.abs(textRelativeRotation); + var scale = (width / canvas.width); + if (textAbsRotation === 90 || textAbsRotation === 270) { + scale = width / canvas.height; + } + var textLayerDiv = this.textLayer.textLayerDiv; + var transX, transY; + switch (textAbsRotation) { + case 0: + transX = transY = 0; + break; + case 90: + transX = 0; + transY = '-' + textLayerDiv.style.height; + break; + case 180: + transX = '-' + textLayerDiv.style.width; + transY = '-' + textLayerDiv.style.height; + break; + case 270: + transX = '-' + textLayerDiv.style.width; + transY = 0; + break; + default: + console.error('Bad rotation value.'); + break; + } + CustomStyle.setProp('transform', textLayerDiv, + 'rotate(' + textAbsRotation + 'deg) ' + + 'scale(' + scale + ', ' + scale + ') ' + + 'translate(' + transX + ', ' + transY + ')'); + CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%'); + } + + if (USE_ONLY_CSS_ZOOM && this.annotationLayer) { + setupAnnotations(div, this.pdfPage, this.viewport); + } + }; + + Object.defineProperty(this, 'width', { + get: function PageView_getWidth() { + return this.viewport.width; + }, + enumerable: true + }); + + Object.defineProperty(this, 'height', { + get: function PageView_getHeight() { + return this.viewport.height; + }, + enumerable: true + }); + + var self = this; + + function setupAnnotations(pageDiv, pdfPage, viewport) { + + function bindLink(link, dest) { + link.href = PDFView.getDestinationHash(dest); + link.onclick = function pageViewSetupLinksOnclick() { + if (dest) { + PDFView.navigateTo(dest); + } + return false; + }; + if (dest) { + link.className = 'internalLink'; + } + } + + function bindNamedAction(link, action) { + link.href = PDFView.getAnchorUrl(''); + link.onclick = function pageViewSetupNamedActionOnClick() { + // See PDF reference, table 8.45 - Named action + switch (action) { + case 'GoToPage': + document.getElementById('pageNumber').focus(); + break; + + case 'GoBack': + PDFHistory.back(); + break; + + case 'GoForward': + PDFHistory.forward(); + break; + + case 'Find': + if (!PDFView.supportsIntegratedFind) { + PDFFindBar.toggle(); + } + break; + + case 'NextPage': + PDFView.page++; + break; + + case 'PrevPage': + PDFView.page--; + break; + + case 'LastPage': + PDFView.page = PDFView.pages.length; + break; + + case 'FirstPage': + PDFView.page = 1; + break; + + default: + break; // No action according to spec + } + return false; + }; + link.className = 'internalLink'; + } + + pdfPage.getAnnotations().then(function(annotationsData) { + if (self.annotationLayer) { + // If an annotationLayer already exists, delete it to avoid creating + // duplicate annotations when rapidly re-zooming the document. + pageDiv.removeChild(self.annotationLayer); + self.annotationLayer = null; + } + viewport = viewport.clone({ dontFlip: true }); + for (var i = 0; i < annotationsData.length; i++) { + var data = annotationsData[i]; + var annotation = PDFJS.Annotation.fromData(data); + if (!annotation || !annotation.hasHtml()) { + continue; + } + + var element = annotation.getHtmlElement(pdfPage.commonObjs); + mozL10n.translate(element); + + data = annotation.getData(); + var rect = data.rect; + var view = pdfPage.view; + rect = PDFJS.Util.normalizeRect([ + rect[0], + view[3] - rect[1] + view[1], + rect[2], + view[3] - rect[3] + view[1] + ]); + element.style.left = rect[0] + 'px'; + element.style.top = rect[1] + 'px'; + element.style.position = 'absolute'; + + var transform = viewport.transform; + var transformStr = 'matrix(' + transform.join(',') + ')'; + CustomStyle.setProp('transform', element, transformStr); + var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px'; + CustomStyle.setProp('transformOrigin', element, transformOriginStr); + + if (data.subtype === 'Link' && !data.url) { + if (data.action) { + bindNamedAction(element, data.action); + } else { + bindLink(element, ('dest' in data) ? data.dest : null); + } + } + + if (!self.annotationLayer) { + var annotationLayerDiv = document.createElement('div'); + annotationLayerDiv.className = 'annotationLayer'; + pageDiv.appendChild(annotationLayerDiv); + self.annotationLayer = annotationLayerDiv; + } + self.annotationLayer.appendChild(element); + } + }); + } + + this.getPagePoint = function pageViewGetPagePoint(x, y) { + return this.viewport.convertToPdfPoint(x, y); + }; + + this.scrollIntoView = function pageViewScrollIntoView(dest) { + if (PresentationMode.active) { // Avoid breaking presentation mode. + dest = null; + PDFView.setScale(PDFView.currentScaleValue, true, true); + } + if (!dest) { + scrollIntoView(div); + return; + } + + var x = 0, y = 0; + var width = 0, height = 0, widthScale, heightScale; + var changeOrientation = !!(this.rotation % 180); + var pageWidth = (changeOrientation ? this.height : this.width) / + this.scale / CSS_UNITS; + var pageHeight = (changeOrientation ? this.width : this.height) / + this.scale / CSS_UNITS; + var scale = 0; + switch (dest[1].name) { + case 'XYZ': + x = dest[2]; + y = dest[3]; + scale = dest[4]; + // If x and/or y coordinates are not supplied, default to + // _top_ left of the page (not the obvious bottom left, + // since aligning the bottom of the intended page with the + // top of the window is rarely helpful). + x = x !== null ? x : 0; + y = y !== null ? y : pageHeight; + break; + case 'Fit': + case 'FitB': + scale = 'page-fit'; + break; + case 'FitH': + case 'FitBH': + y = dest[2]; + scale = 'page-width'; + break; + case 'FitV': + case 'FitBV': + x = dest[2]; + width = pageWidth; + height = pageHeight; + scale = 'page-height'; + break; + case 'FitR': + x = dest[2]; + y = dest[3]; + width = dest[4] - x; + height = dest[5] - y; + widthScale = (PDFView.container.clientWidth - SCROLLBAR_PADDING) / + width / CSS_UNITS; + heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) / + height / CSS_UNITS; + scale = Math.min(Math.abs(widthScale), Math.abs(heightScale)); + break; + default: + return; + } + + if (scale && scale !== PDFView.currentScale) { + PDFView.setScale(scale, true, true); + } else if (PDFView.currentScale === UNKNOWN_SCALE) { + PDFView.setScale(DEFAULT_SCALE, true, true); + } + + if (scale === 'page-fit' && !dest[4]) { + scrollIntoView(div); + return; + } + + var boundingRect = [ + this.viewport.convertToViewportPoint(x, y), + this.viewport.convertToViewportPoint(x + width, y + height) + ]; + var left = Math.min(boundingRect[0][0], boundingRect[1][0]); + var top = Math.min(boundingRect[0][1], boundingRect[1][1]); + + scrollIntoView(div, { left: left, top: top }); + }; + + this.getTextContent = function pageviewGetTextContent() { + return PDFView.getPage(this.id).then(function(pdfPage) { + return pdfPage.getTextContent(); + }); + }; + + this.draw = function pageviewDraw(callback) { + var pdfPage = this.pdfPage; + + if (this.pagePdfPromise) { + return; + } + if (!pdfPage) { + var promise = PDFView.getPage(this.id); + promise.then(function(pdfPage) { + delete this.pagePdfPromise; + this.setPdfPage(pdfPage); + this.draw(callback); + }.bind(this)); + this.pagePdfPromise = promise; + return; + } + + if (this.renderingState !== RenderingStates.INITIAL) { + console.error('Must be in new state before drawing'); + } + + this.renderingState = RenderingStates.RUNNING; + + var viewport = this.viewport; + // Wrap the canvas so if it has a css transform for highdpi the overflow + // will be hidden in FF. + var canvasWrapper = document.createElement('div'); + canvasWrapper.style.width = div.style.width; + canvasWrapper.style.height = div.style.height; + canvasWrapper.classList.add('canvasWrapper'); + + var canvas = document.createElement('canvas'); + canvas.id = 'page' + this.id; + canvasWrapper.appendChild(canvas); + div.appendChild(canvasWrapper); + this.canvas = canvas; + + var scale = this.scale; + var ctx = canvas.getContext('2d'); + var outputScale = getOutputScale(ctx); + + if (USE_ONLY_CSS_ZOOM) { + var actualSizeViewport = viewport.clone({ scale: CSS_UNITS }); + // Use a scale that will make the canvas be the original intended size + // of the page. + outputScale.sx *= actualSizeViewport.width / viewport.width; + outputScale.sy *= actualSizeViewport.height / viewport.height; + outputScale.scaled = true; + } + + canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0; + canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0; + canvas.style.width = Math.floor(viewport.width) + 'px'; + canvas.style.height = Math.floor(viewport.height) + 'px'; + // Add the viewport so it's known what it was originally drawn with. + canvas._viewport = viewport; + + var textLayerDiv = null; + if (!PDFJS.disableTextLayer) { + textLayerDiv = document.createElement('div'); + textLayerDiv.className = 'textLayer'; + textLayerDiv.style.width = canvas.width + 'px'; + textLayerDiv.style.height = canvas.height + 'px'; + div.appendChild(textLayerDiv); + } + var textLayer = this.textLayer = + textLayerDiv ? new TextLayerBuilder({ + textLayerDiv: textLayerDiv, + pageIndex: this.id - 1, + lastScrollSource: PDFView, + viewport: this.viewport, + isViewerInPresentationMode: PresentationMode.active + }) : null; + // TODO(mack): use data attributes to store these + ctx._scaleX = outputScale.sx; + ctx._scaleY = outputScale.sy; + if (outputScale.scaled) { + ctx.scale(outputScale.sx, outputScale.sy); + } + if (outputScale.scaled && textLayerDiv) { + var cssScale = 'scale(' + (1 / outputScale.sx) + ', ' + + (1 / outputScale.sy) + ')'; + CustomStyle.setProp('transform' , textLayerDiv, cssScale); + CustomStyle.setProp('transformOrigin' , textLayerDiv, '0% 0%'); + textLayerDiv.dataset._scaleX = outputScale.sx; + textLayerDiv.dataset._scaleY = outputScale.sy; + } + + + // Rendering area + + var self = this; + function pageViewDrawCallback(error) { + // The renderTask may have been replaced by a new one, so only remove the + // reference to the renderTask if it matches the one that is triggering + // this callback. + if (renderTask === self.renderTask) { + self.renderTask = null; + } + + if (error === 'cancelled') { + return; + } + + self.renderingState = RenderingStates.FINISHED; + + if (self.loadingIconDiv) { + div.removeChild(self.loadingIconDiv); + delete self.loadingIconDiv; + } + + if (self.zoomLayer) { + div.removeChild(self.zoomLayer); + self.zoomLayer = null; + } + + if (error) { + PDFView.error(mozL10n.get('rendering_error', null, + 'An error occurred while rendering the page.'), error); + } + + self.stats = pdfPage.stats; + self.updateStats(); + if (self.onAfterDraw) { + self.onAfterDraw(); + } + + cache.push(self); + + var event = document.createEvent('CustomEvent'); + event.initCustomEvent('pagerender', true, true, { + pageNumber: pdfPage.pageNumber + }); + div.dispatchEvent(event); + + callback(); + } + + var renderContext = { + canvasContext: ctx, + viewport: this.viewport, + textLayer: textLayer, + continueCallback: function pdfViewcContinueCallback(cont) { + if (PDFView.highestPriorityPage !== 'page' + self.id) { + self.renderingState = RenderingStates.PAUSED; + self.resume = function resumeCallback() { + self.renderingState = RenderingStates.RUNNING; + cont(); + }; + return; + } + cont(); + } + }; + var renderTask = this.renderTask = this.pdfPage.render(renderContext); + + this.renderTask.promise.then( + function pdfPageRenderCallback() { + pageViewDrawCallback(null); + }, + function pdfPageRenderError(error) { + pageViewDrawCallback(error); + } + ); + + if (textLayer) { + this.getTextContent().then( + function textContentResolved(textContent) { + textLayer.setTextContent(textContent); + } + ); + } + + setupAnnotations(div, pdfPage, this.viewport); + div.setAttribute('data-loaded', true); + }; + + this.beforePrint = function pageViewBeforePrint() { + var pdfPage = this.pdfPage; + + var viewport = pdfPage.getViewport(1); + // Use the same hack we use for high dpi displays for printing to get better + // output until bug 811002 is fixed in FF. + var PRINT_OUTPUT_SCALE = 2; + var canvas = document.createElement('canvas'); + canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE; + canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE; + canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt'; + canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt'; + var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' + + (1 / PRINT_OUTPUT_SCALE) + ')'; + CustomStyle.setProp('transform' , canvas, cssScale); + CustomStyle.setProp('transformOrigin' , canvas, '0% 0%'); + + var printContainer = document.getElementById('printContainer'); + var canvasWrapper = document.createElement('div'); + canvasWrapper.style.width = viewport.width + 'pt'; + canvasWrapper.style.height = viewport.height + 'pt'; + canvasWrapper.appendChild(canvas); + printContainer.appendChild(canvasWrapper); + + var self = this; + canvas.mozPrintCallback = function(obj) { + var ctx = obj.context; + + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE); + + var renderContext = { + canvasContext: ctx, + viewport: viewport + }; + + pdfPage.render(renderContext).promise.then(function() { + // Tell the printEngine that rendering this canvas/page has finished. + obj.done(); + self.pdfPage.destroy(); + }, function(error) { + console.error(error); + // Tell the printEngine that rendering this canvas/page has failed. + // This will make the print proces stop. + if ('abort' in obj) { + obj.abort(); + } else { + obj.done(); + } + self.pdfPage.destroy(); + }); + }; + }; + + this.updateStats = function pageViewUpdateStats() { + if (!this.stats) { + return; + } + + if (PDFJS.pdfBug && Stats.enabled) { + var stats = this.stats; + Stats.add(this.id, stats); + } + }; +}; + + +var ThumbnailView = function thumbnailView(container, id, defaultViewport) { + var anchor = document.createElement('a'); + anchor.href = PDFView.getAnchorUrl('#page=' + id); + anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}'); + anchor.onclick = function stopNavigation() { + PDFView.page = id; + return false; + }; + + this.pdfPage = undefined; + this.viewport = defaultViewport; + this.pdfPageRotate = defaultViewport.rotation; + + this.rotation = 0; + this.pageWidth = this.viewport.width; + this.pageHeight = this.viewport.height; + this.pageRatio = this.pageWidth / this.pageHeight; + this.id = id; + + this.canvasWidth = 98; + this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight; + this.scale = (this.canvasWidth / this.pageWidth); + + var div = this.el = document.createElement('div'); + div.id = 'thumbnailContainer' + id; + div.className = 'thumbnail'; + + if (id === 1) { + // Highlight the thumbnail of the first page when no page number is + // specified (or exists in cache) when the document is loaded. + div.classList.add('selected'); + } + + var ring = document.createElement('div'); + ring.className = 'thumbnailSelectionRing'; + ring.style.width = this.canvasWidth + 'px'; + ring.style.height = this.canvasHeight + 'px'; + + div.appendChild(ring); + anchor.appendChild(div); + container.appendChild(anchor); + + this.hasImage = false; + this.renderingState = RenderingStates.INITIAL; + + this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) { + this.pdfPage = pdfPage; + this.pdfPageRotate = pdfPage.rotate; + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = pdfPage.getViewport(1, totalRotation); + this.update(); + }; + + this.update = function thumbnailViewUpdate(rotation) { + if (rotation !== undefined) { + this.rotation = rotation; + } + var totalRotation = (this.rotation + this.pdfPageRotate) % 360; + this.viewport = this.viewport.clone({ + scale: 1, + rotation: totalRotation + }); + this.pageWidth = this.viewport.width; + this.pageHeight = this.viewport.height; + this.pageRatio = this.pageWidth / this.pageHeight; + + this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight; + this.scale = (this.canvasWidth / this.pageWidth); + + div.removeAttribute('data-loaded'); + ring.textContent = ''; + ring.style.width = this.canvasWidth + 'px'; + ring.style.height = this.canvasHeight + 'px'; + + this.hasImage = false; + this.renderingState = RenderingStates.INITIAL; + this.resume = null; + }; + + this.getPageDrawContext = function thumbnailViewGetPageDrawContext() { + var canvas = document.createElement('canvas'); + canvas.id = 'thumbnail' + id; + + canvas.width = this.canvasWidth; + canvas.height = this.canvasHeight; + canvas.className = 'thumbnailImage'; + canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas', + {page: id}, 'Thumbnail of Page {{page}}')); + + div.setAttribute('data-loaded', true); + + ring.appendChild(canvas); + + var ctx = canvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight); + ctx.restore(); + return ctx; + }; + + this.drawingRequired = function thumbnailViewDrawingRequired() { + return !this.hasImage; + }; + + this.draw = function thumbnailViewDraw(callback) { + if (!this.pdfPage) { + var promise = PDFView.getPage(this.id); + promise.then(function(pdfPage) { + this.setPdfPage(pdfPage); + this.draw(callback); + }.bind(this)); + return; + } + + if (this.renderingState !== RenderingStates.INITIAL) { + console.error('Must be in new state before drawing'); + } + + this.renderingState = RenderingStates.RUNNING; + if (this.hasImage) { + callback(); + return; + } + + var self = this; + var ctx = this.getPageDrawContext(); + var drawViewport = this.viewport.clone({ scale: this.scale }); + var renderContext = { + canvasContext: ctx, + viewport: drawViewport, + continueCallback: function(cont) { + if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) { + self.renderingState = RenderingStates.PAUSED; + self.resume = function() { + self.renderingState = RenderingStates.RUNNING; + cont(); + }; + return; + } + cont(); + } + }; + this.pdfPage.render(renderContext).promise.then( + function pdfPageRenderCallback() { + self.renderingState = RenderingStates.FINISHED; + callback(); + }, + function pdfPageRenderError(error) { + self.renderingState = RenderingStates.FINISHED; + callback(); + } + ); + this.hasImage = true; + }; + + this.setImage = function thumbnailViewSetImage(img) { + if (!this.pdfPage) { + var promise = PDFView.getPage(this.id); + promise.then(function(pdfPage) { + this.setPdfPage(pdfPage); + this.setImage(img); + }.bind(this)); + return; + } + if (this.hasImage || !img) { + return; + } + this.renderingState = RenderingStates.FINISHED; + var ctx = this.getPageDrawContext(); + ctx.drawImage(img, 0, 0, img.width, img.height, + 0, 0, ctx.canvas.width, ctx.canvas.height); + + this.hasImage = true; + }; +}; + + +var FIND_SCROLL_OFFSET_TOP = -50; +var FIND_SCROLL_OFFSET_LEFT = -400; + +/** + * TextLayerBuilder provides text-selection + * functionality for the PDF. It does this + * by creating overlay divs over the PDF + * text. This divs contain text that matches + * the PDF text they are overlaying. This + * object also provides for a way to highlight + * text that is being searched for. + */ +var TextLayerBuilder = function textLayerBuilder(options) { + var textLayerFrag = document.createDocumentFragment(); + + this.textLayerDiv = options.textLayerDiv; + this.layoutDone = false; + this.divContentDone = false; + this.pageIdx = options.pageIndex; + this.matches = []; + this.lastScrollSource = options.lastScrollSource; + this.viewport = options.viewport; + this.isViewerInPresentationMode = options.isViewerInPresentationMode; + + if(typeof PDFFindController === 'undefined') { + window.PDFFindController = null; + } + + if(typeof this.lastScrollSource === 'undefined') { + this.lastScrollSource = null; + } + + this.beginLayout = function textLayerBuilderBeginLayout() { + this.textDivs = []; + this.renderingDone = false; + }; + + this.endLayout = function textLayerBuilderEndLayout() { + this.layoutDone = true; + this.insertDivContent(); + }; + + this.renderLayer = function textLayerBuilderRenderLayer() { + var self = this; + var textDivs = this.textDivs; + var bidiTexts = this.textContent.bidiTexts; + var textLayerDiv = this.textLayerDiv; + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + + // No point in rendering so many divs as it'd make the browser unusable + // even after the divs are rendered + var MAX_TEXT_DIVS_TO_RENDER = 100000; + if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) + return; + + for (var i = 0, ii = textDivs.length; i < ii; i++) { + var textDiv = textDivs[i]; + if ('isWhitespace' in textDiv.dataset) { + continue; + } + + ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily; + var width = ctx.measureText(textDiv.textContent).width; + + if (width > 0) { + textLayerFrag.appendChild(textDiv); + var textScale = textDiv.dataset.canvasWidth / width; + var rotation = textDiv.dataset.angle; + var transform = 'scale(' + textScale + ', 1)'; + transform = 'rotate(' + rotation + 'deg) ' + transform; + CustomStyle.setProp('transform' , textDiv, transform); + CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%'); + } + } + + textLayerDiv.appendChild(textLayerFrag); + this.renderingDone = true; + this.updateMatches(); + }; + + this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() { + // Schedule renderLayout() if user has been scrolling, otherwise + // run it right away + var RENDER_DELAY = 200; // in ms + var self = this; + var lastScroll = this.lastScrollSource === null ? + 0 : this.lastScrollSource.lastScroll; + + if (Date.now() - lastScroll > RENDER_DELAY) { + // Render right away + this.renderLayer(); + } else { + // Schedule + if (this.renderTimer) + clearTimeout(this.renderTimer); + this.renderTimer = setTimeout(function() { + self.setupRenderLayoutTimer(); + }, RENDER_DELAY); + } + }; + + this.appendText = function textLayerBuilderAppendText(geom) { + var textDiv = document.createElement('div'); + + // vScale and hScale already contain the scaling to pixel units + var fontHeight = geom.fontSize * Math.abs(geom.vScale); + textDiv.dataset.canvasWidth = geom.canvasWidth * Math.abs(geom.hScale); + textDiv.dataset.fontName = geom.fontName; + textDiv.dataset.angle = geom.angle * (180 / Math.PI); + + textDiv.style.fontSize = fontHeight + 'px'; + textDiv.style.fontFamily = geom.fontFamily; + var fontAscent = geom.ascent ? geom.ascent * fontHeight : + geom.descent ? (1 + geom.descent) * fontHeight : fontHeight; + textDiv.style.left = (geom.x + (fontAscent * Math.sin(geom.angle))) + 'px'; + textDiv.style.top = (geom.y - (fontAscent * Math.cos(geom.angle))) + 'px'; + + // The content of the div is set in the `setTextContent` function. + + this.textDivs.push(textDiv); + }; + + this.insertDivContent = function textLayerUpdateTextContent() { + // Only set the content of the divs once layout has finished, the content + // for the divs is available and content is not yet set on the divs. + if (!this.layoutDone || this.divContentDone || !this.textContent) + return; + + this.divContentDone = true; + + var textDivs = this.textDivs; + var bidiTexts = this.textContent.bidiTexts; + + for (var i = 0; i < bidiTexts.length; i++) { + var bidiText = bidiTexts[i]; + var textDiv = textDivs[i]; + if (!/\S/.test(bidiText.str)) { + textDiv.dataset.isWhitespace = true; + continue; + } + + textDiv.textContent = bidiText.str; + // TODO refactor text layer to use text content position + /** + * var arr = this.viewport.convertToViewportPoint(bidiText.x, bidiText.y); + * textDiv.style.left = arr[0] + 'px'; + * textDiv.style.top = arr[1] + 'px'; + */ + // bidiText.dir may be 'ttb' for vertical texts. + textDiv.dir = bidiText.dir; + } + + this.setupRenderLayoutTimer(); + }; + + this.setTextContent = function textLayerBuilderSetTextContent(textContent) { + this.textContent = textContent; + this.insertDivContent(); + }; + + this.convertMatches = function textLayerBuilderConvertMatches(matches) { + var i = 0; + var iIndex = 0; + var bidiTexts = this.textContent.bidiTexts; + var end = bidiTexts.length - 1; + var queryLen = PDFFindController === null ? + 0 : PDFFindController.state.query.length; + + var lastDivIdx = -1; + var pos; + + var ret = []; + + // Loop over all the matches. + for (var m = 0; m < matches.length; m++) { + var matchIdx = matches[m]; + // # Calculate the begin position. + + // Loop over the divIdxs. + while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) { + iIndex += bidiTexts[i].str.length; + i++; + } + + // TODO: Do proper handling here if something goes wrong. + if (i == bidiTexts.length) { + console.error('Could not find matching mapping'); + } + + var match = { + begin: { + divIdx: i, + offset: matchIdx - iIndex + } + }; + + // # Calculate the end position. + matchIdx += queryLen; + + // Somewhat same array as above, but use a > instead of >= to get the end + // position right. + while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) { + iIndex += bidiTexts[i].str.length; + i++; + } + + match.end = { + divIdx: i, + offset: matchIdx - iIndex + }; + ret.push(match); + } + + return ret; + }; + + this.renderMatches = function textLayerBuilder_renderMatches(matches) { + // Early exit if there is nothing to render. + if (matches.length === 0) { + return; + } + + var bidiTexts = this.textContent.bidiTexts; + var textDivs = this.textDivs; + var prevEnd = null; + var isSelectedPage = PDFFindController === null ? + false : (this.pageIdx === PDFFindController.selected.pageIdx); + + var selectedMatchIdx = PDFFindController === null ? + -1 : PDFFindController.selected.matchIdx; + + var highlightAll = PDFFindController === null ? + false : PDFFindController.state.highlightAll; + + var infty = { + divIdx: -1, + offset: undefined + }; + + function beginText(begin, className) { + var divIdx = begin.divIdx; + var div = textDivs[divIdx]; + div.textContent = ''; + + var content = bidiTexts[divIdx].str.substring(0, begin.offset); + var node = document.createTextNode(content); + if (className) { + var isSelected = isSelectedPage && + divIdx === selectedMatchIdx; + var span = document.createElement('span'); + span.className = className + (isSelected ? ' selected' : ''); + span.appendChild(node); + div.appendChild(span); + return; + } + div.appendChild(node); + } + + function appendText(from, to, className) { + var divIdx = from.divIdx; + var div = textDivs[divIdx]; + + var content = bidiTexts[divIdx].str.substring(from.offset, to.offset); + var node = document.createTextNode(content); + if (className) { + var span = document.createElement('span'); + span.className = className; + span.appendChild(node); + div.appendChild(span); + return; + } + div.appendChild(node); + } + + function highlightDiv(divIdx, className) { + textDivs[divIdx].className = className; + } + + var i0 = selectedMatchIdx, i1 = i0 + 1, i; + + if (highlightAll) { + i0 = 0; + i1 = matches.length; + } else if (!isSelectedPage) { + // Not highlighting all and this isn't the selected page, so do nothing. + return; + } + + for (i = i0; i < i1; i++) { + var match = matches[i]; + var begin = match.begin; + var end = match.end; + + var isSelected = isSelectedPage && i === selectedMatchIdx; + var highlightSuffix = (isSelected ? ' selected' : ''); + if (isSelected && !this.isViewerInPresentationMode) { + scrollIntoView(textDivs[begin.divIdx], { top: FIND_SCROLL_OFFSET_TOP, + left: FIND_SCROLL_OFFSET_LEFT }); + } + + // Match inside new div. + if (!prevEnd || begin.divIdx !== prevEnd.divIdx) { + // If there was a previous div, then add the text at the end + if (prevEnd !== null) { + appendText(prevEnd, infty); + } + // clears the divs and set the content until the begin point. + beginText(begin); + } else { + appendText(prevEnd, begin); + } + + if (begin.divIdx === end.divIdx) { + appendText(begin, end, 'highlight' + highlightSuffix); + } else { + appendText(begin, infty, 'highlight begin' + highlightSuffix); + for (var n = begin.divIdx + 1; n < end.divIdx; n++) { + highlightDiv(n, 'highlight middle' + highlightSuffix); + } + beginText(end, 'highlight end' + highlightSuffix); + } + prevEnd = end; + } + + if (prevEnd) { + appendText(prevEnd, infty); + } + }; + + this.updateMatches = function textLayerUpdateMatches() { + // Only show matches, once all rendering is done. + if (!this.renderingDone) + return; + + // Clear out all matches. + var matches = this.matches; + var textDivs = this.textDivs; + var bidiTexts = this.textContent.bidiTexts; + var clearedUntilDivIdx = -1; + + // Clear out all current matches. + for (var i = 0; i < matches.length; i++) { + var match = matches[i]; + var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx); + for (var n = begin; n <= match.end.divIdx; n++) { + var div = textDivs[n]; + div.textContent = bidiTexts[n].str; + div.className = ''; + } + clearedUntilDivIdx = match.end.divIdx + 1; + } + + if (PDFFindController === null || !PDFFindController.active) + return; + + // Convert the matches on the page controller into the match format used + // for the textLayer. + this.matches = matches = + this.convertMatches(PDFFindController === null ? + [] : (PDFFindController.pageMatches[this.pageIdx] || [])); + + this.renderMatches(this.matches); + }; +}; + + + +var DocumentOutlineView = function documentOutlineView(outline) { + var outlineView = document.getElementById('outlineView'); + var outlineButton = document.getElementById('viewOutline'); + while (outlineView.firstChild) + outlineView.removeChild(outlineView.firstChild); + + if (!outline) { + if (!outlineView.classList.contains('hidden')) + PDFView.switchSidebarView('thumbs'); + + return; + } + + function bindItemLink(domObj, item) { + domObj.href = PDFView.getDestinationHash(item.dest); + domObj.onclick = function documentOutlineViewOnclick(e) { + PDFView.navigateTo(item.dest); + return false; + }; + } + + + var queue = [{parent: outlineView, items: outline}]; + while (queue.length > 0) { + var levelData = queue.shift(); + var i, n = levelData.items.length; + for (i = 0; i < n; i++) { + var item = levelData.items[i]; + var div = document.createElement('div'); + div.className = 'outlineItem'; + var a = document.createElement('a'); + bindItemLink(a, item); + a.textContent = item.title; + div.appendChild(a); + + if (item.items.length > 0) { + var itemsDiv = document.createElement('div'); + itemsDiv.className = 'outlineItems'; + div.appendChild(itemsDiv); + queue.push({parent: itemsDiv, items: item.items}); + } + + levelData.parent.appendChild(div); + } + } +}; + + +document.addEventListener('DOMContentLoaded', function webViewerLoad(evt) { + PDFView.initialize(); + + var params = PDFView.parseQueryString(document.location.search.substring(1)); + var file = params.file || DEFAULT_URL; + + + var fileInput = document.createElement('input'); + fileInput.id = 'fileInput'; + fileInput.className = 'fileInput'; + fileInput.setAttribute('type', 'file'); + fileInput.oncontextmenu = noContextMenuHandler; + document.body.appendChild(fileInput); + + if (!window.File || !window.FileReader || !window.FileList || !window.Blob) { + //document.getElementById('openFile').setAttribute('hidden', 'true'); + //document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true'); + } else { + document.getElementById('fileInput').value = null; + } + + // Special debugging flags in the hash section of the URL. + var hash = document.location.hash.substring(1); + var hashParams = PDFView.parseQueryString(hash); + + if ('disableWorker' in hashParams) { + PDFJS.disableWorker = (hashParams['disableWorker'] === 'true'); + } + + if ('disableRange' in hashParams) { + PDFJS.disableRange = (hashParams['disableRange'] === 'true'); + } + + if ('disableAutoFetch' in hashParams) { + PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true'); + } + + if ('disableFontFace' in hashParams) { + PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true'); + } + + if ('disableHistory' in hashParams) { + PDFJS.disableHistory = (hashParams['disableHistory'] === 'true'); + } + + if ('useOnlyCssZoom' in hashParams) { + USE_ONLY_CSS_ZOOM = (hashParams['useOnlyCssZoom'] === 'true'); + } + + if ('verbosity' in hashParams) { + PDFJS.verbosity = hashParams['verbosity'] | 0; + } + + if ('ignoreCurrentPositionOnZoom' in hashParams) { + IGNORE_CURRENT_POSITION_ON_ZOOM = + (hashParams['ignoreCurrentPositionOnZoom'] === 'true'); + } + + var locale = navigator.language; + if ('locale' in hashParams) + locale = hashParams['locale']; + mozL10n.setLanguage(locale); + + if ('textLayer' in hashParams) { + switch (hashParams['textLayer']) { + case 'off': + PDFJS.disableTextLayer = true; + break; + case 'visible': + case 'shadow': + case 'hover': + var viewer = document.getElementById('viewer'); + viewer.classList.add('textLayer-' + hashParams['textLayer']); + break; + } + } + + if ('pdfBug' in hashParams) { + PDFJS.pdfBug = true; + var pdfBug = hashParams['pdfBug']; + var enabled = pdfBug.split(','); + PDFBug.enable(enabled); + PDFBug.init(); + } + + if (!PDFView.supportsPrinting) { + document.getElementById('print').classList.add('hidden'); + document.getElementById('secondaryPrint').classList.add('hidden'); + } + + if (!PDFView.supportsFullscreen) { + document.getElementById('presentationMode').classList.add('hidden'); + document.getElementById('secondaryPresentationMode'). + classList.add('hidden'); + } + + if (PDFView.supportsIntegratedFind) { + document.getElementById('viewFind').classList.add('hidden'); + } + + // Listen for unsuporrted features to trigger the fallback UI. + PDFJS.UnsupportedManager.listen(PDFView.fallback.bind(PDFView)); + + // Suppress context menus for some controls + document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler; + + var mainContainer = document.getElementById('mainContainer'); + var outerContainer = document.getElementById('outerContainer'); + mainContainer.addEventListener('transitionend', function(e) { + if (e.target == mainContainer) { + var event = document.createEvent('UIEvents'); + event.initUIEvent('resize', false, false, window, 0); + window.dispatchEvent(event); + outerContainer.classList.remove('sidebarMoving'); + } + }, true); + + document.getElementById('sidebarToggle').addEventListener('click', + function() { + this.classList.toggle('toggled'); + outerContainer.classList.add('sidebarMoving'); + outerContainer.classList.toggle('sidebarOpen'); + PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen'); + PDFView.renderHighestPriority(); + }); + + document.getElementById('viewThumbnail').addEventListener('click', + function() { + PDFView.switchSidebarView('thumbs'); + }); + + document.getElementById('viewOutline').addEventListener('click', + function() { + PDFView.switchSidebarView('outline'); + }); + + document.getElementById('previous').addEventListener('click', + function() { + PDFView.page--; + }); + + document.getElementById('next').addEventListener('click', + function() { + PDFView.page++; + }); + + document.getElementById('zoomIn').addEventListener('click', + function() { + PDFView.zoomIn(); + }); + + document.getElementById('zoomOut').addEventListener('click', + function() { + PDFView.zoomOut(); + }); + + document.getElementById('pageNumber').addEventListener('click', + function() { + this.select(); + }); + + document.getElementById('pageNumber').addEventListener('change', + function() { + // Handle the user inputting a floating point number. + PDFView.page = (this.value | 0); + + if (this.value !== (this.value | 0).toString()) { + this.value = PDFView.page; + } + }); + + document.getElementById('scaleSelect').addEventListener('change', + function() { + PDFView.setScale(this.value); + }); + + document.getElementById('presentationMode').addEventListener('click', + SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar)); + +// document.getElementById('openFile').addEventListener('click', +// SecondaryToolbar.openFileClick.bind(SecondaryToolbar)); + + document.getElementById('print').addEventListener('click', + SecondaryToolbar.printClick.bind(SecondaryToolbar)); + +// document.getElementById('download').addEventListener('click', +// SecondaryToolbar.downloadClick.bind(SecondaryToolbar)); + + + PDFView.open(file, 0); +}, true); + +function updateViewarea() { + + if (!PDFView.initialized) + return; + var visible = PDFView.getVisiblePages(); + var visiblePages = visible.views; + if (visiblePages.length === 0) { + return; + } + + PDFView.renderHighestPriority(); + + var currentId = PDFView.page; + var firstPage = visible.first; + + for (var i = 0, ii = visiblePages.length, stillFullyVisible = false; + i < ii; ++i) { + var page = visiblePages[i]; + + if (page.percent < 100) + break; + + if (page.id === PDFView.page) { + stillFullyVisible = true; + break; + } + } + + if (!stillFullyVisible) { + currentId = visiblePages[0].id; + } + + updateViewarea.inProgress = true; // used in "set page" + PDFView.page = currentId; + updateViewarea.inProgress = false; + + var currentScale = PDFView.currentScale; + var currentScaleValue = PDFView.currentScaleValue; + var normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ? + Math.round(currentScale * 10000) / 100 : currentScaleValue; + + var pageNumber = firstPage.id; + var pdfOpenParams = '#page=' + pageNumber; + pdfOpenParams += '&zoom=' + normalizedScaleValue; + var currentPage = PDFView.pages[pageNumber - 1]; + var container = PDFView.container; + var topLeft = currentPage.getPagePoint((container.scrollLeft - firstPage.x), + (container.scrollTop - firstPage.y)); + var intLeft = Math.round(topLeft[0]); + var intTop = Math.round(topLeft[1]); + pdfOpenParams += ',' + intLeft + ',' + intTop; + + if (PresentationMode.active || PresentationMode.switchInProgress) { + PDFView.currentPosition = null; + } else { + PDFView.currentPosition = { page: pageNumber, left: intLeft, top: intTop }; + } + + var store = PDFView.store; + store.initializedPromise.then(function() { + store.set('exists', true); + store.set('page', pageNumber); + store.set('zoom', normalizedScaleValue); + store.set('scrollLeft', intLeft); + store.set('scrollTop', intTop); + }); + var href = PDFView.getAnchorUrl(pdfOpenParams); + document.getElementById('viewBookmark').href = href; + document.getElementById('secondaryViewBookmark').href = href; + + // Update the current bookmark in the browsing history. + PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber); +} + +window.addEventListener('resize', function webViewerResize(evt) { + if (PDFView.initialized && + (document.getElementById('pageWidthOption').selected || + document.getElementById('pageFitOption').selected || + document.getElementById('pageAutoOption').selected)) { + PDFView.setScale(document.getElementById('scaleSelect').value); + } + updateViewarea(); + + // Set the 'max-height' CSS property of the secondary toolbar. + SecondaryToolbar.setMaxHeight(PDFView.container); +}); + +window.addEventListener('hashchange', function webViewerHashchange(evt) { + if (PDFHistory.isHashChangeUnlocked) { + PDFView.setHash(document.location.hash.substring(1)); + } +}); + +window.addEventListener('change', function webViewerChange(evt) { + var files = evt.target.files; + if (!files || files.length === 0) + return; + + // Read the local file into a Uint8Array. + var fileReader = new FileReader(); + fileReader.onload = function webViewerChangeFileReaderOnload(evt) { + var buffer = evt.target.result; + var uint8Array = new Uint8Array(buffer); + PDFView.open(uint8Array, 0); + }; + + var file = files[0]; + fileReader.readAsArrayBuffer(file); + PDFView.setTitleUsingUrl(file.name); + + // URL does not reflect proper document location - hiding some icons. + document.getElementById('viewBookmark').setAttribute('hidden', 'true'); + document.getElementById('secondaryViewBookmark'). + setAttribute('hidden', 'true'); + document.getElementById('download').setAttribute('hidden', 'true'); + document.getElementById('secondaryDownload').setAttribute('hidden', 'true'); +}, true); + +function selectScaleOption(value) { + var options = document.getElementById('scaleSelect').options; + var predefinedValueFound = false; + for (var i = 0; i < options.length; i++) { + var option = options[i]; + if (option.value != value) { + option.selected = false; + continue; + } + option.selected = true; + predefinedValueFound = true; + } + return predefinedValueFound; +} + +window.addEventListener('localized', function localized(evt) { + document.getElementsByTagName('html')[0].dir = mozL10n.getDirection(); + + PDFView.animationStartedPromise.then(function() { + // Adjust the width of the zoom box to fit the content. + // Note: This is only done if the zoom box is actually visible, + // since otherwise element.clientWidth will return 0. + var container = document.getElementById('scaleSelectContainer'); + if (container.clientWidth > 0) { + var select = document.getElementById('scaleSelect'); + select.setAttribute('style', 'min-width: inherit;'); + var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING; + select.setAttribute('style', 'min-width: ' + + (width + SCALE_SELECT_PADDING) + 'px;'); + container.setAttribute('style', 'min-width: ' + width + 'px; ' + + 'max-width: ' + width + 'px;'); + } + + // Set the 'max-height' CSS property of the secondary toolbar. + SecondaryToolbar.setMaxHeight(PDFView.container); + }); +}, true); + +window.addEventListener('scalechange', function scalechange(evt) { + document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE); + document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE); + + var customScaleOption = document.getElementById('customScaleOption'); + customScaleOption.selected = false; + + if (!evt.resetAutoSettings && + (document.getElementById('pageWidthOption').selected || + document.getElementById('pageFitOption').selected || + document.getElementById('pageAutoOption').selected)) { + updateViewarea(); + return; + } + + var predefinedValueFound = selectScaleOption('' + evt.scale); + if (!predefinedValueFound) { + customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%'; + customScaleOption.selected = true; + } + updateViewarea(); +}, true); + +window.addEventListener('pagechange', function pagechange(evt) { + var page = evt.pageNumber; + if (PDFView.previousPageNumber !== page) { + document.getElementById('pageNumber').value = page; + var selected = document.querySelector('.thumbnail.selected'); + if (selected) { + selected.classList.remove('selected'); + } + var thumbnail = document.getElementById('thumbnailContainer' + page); + thumbnail.classList.add('selected'); + var visibleThumbs = PDFView.getVisibleThumbs(); + var numVisibleThumbs = visibleThumbs.views.length; + + // If the thumbnail isn't currently visible, scroll it into view. + if (numVisibleThumbs > 0) { + var first = visibleThumbs.first.id; + // Account for only one thumbnail being visible. + var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first); + if (page <= first || page >= last) { + scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN }); + } + } + } + document.getElementById('previous').disabled = (page <= 1); + document.getElementById('next').disabled = (page >= PDFView.pages.length); +}, true); + +// Firefox specific event, so that we can prevent browser from zooming +window.addEventListener('DOMMouseScroll', function(evt) { + if (evt.ctrlKey) { + evt.preventDefault(); + + var ticks = evt.detail; + var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn'; + PDFView[direction](Math.abs(ticks)); + } else if (PresentationMode.active) { + var FIREFOX_DELTA_FACTOR = -40; + PDFView.mouseScroll(evt.detail * FIREFOX_DELTA_FACTOR); + } +}, false); + +window.addEventListener('click', function click(evt) { + if (!PresentationMode.active) { + if (SecondaryToolbar.opened && PDFView.container.contains(evt.target)) { + SecondaryToolbar.close(); + } + } else if (evt.button === 0) { + // Necessary since preventDefault() in 'mousedown' won't stop + // the event propagation in all circumstances in presentation mode. + evt.preventDefault(); + } +}, false); + +window.addEventListener('keydown', function keydown(evt) { + if (PasswordPrompt.visible) { + return; + } + + var handled = false; + var cmd = (evt.ctrlKey ? 1 : 0) | + (evt.altKey ? 2 : 0) | + (evt.shiftKey ? 4 : 0) | + (evt.metaKey ? 8 : 0); + + // First, handle the key bindings that are independent whether an input + // control is selected or not. + if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) { + // either CTRL or META key with optional SHIFT. + switch (evt.keyCode) { + case 70: // f + if (!PDFView.supportsIntegratedFind) { + PDFFindBar.open(); + handled = true; + } + break; + case 71: // g + if (!PDFView.supportsIntegratedFind) { + PDFFindBar.dispatchEvent('again', cmd === 5 || cmd === 12); + handled = true; + } + break; + case 61: // FF/Mac '=' + case 107: // FF '+' and '=' + case 187: // Chrome '+' + case 171: // FF with German keyboard + PDFView.zoomIn(); + handled = true; + break; + case 173: // FF/Mac '-' + case 109: // FF '-' + case 189: // Chrome '-' + PDFView.zoomOut(); + handled = true; + break; + case 48: // '0' + case 96: // '0' on Numpad of Swedish keyboard + // keeping it unhandled (to restore page zoom to 100%) + setTimeout(function () { + // ... and resetting the scale after browser adjusts its scale + PDFView.setScale(DEFAULT_SCALE, true); + }); + handled = false; + break; + } + } + + // CTRL+ALT or Option+Command + if (cmd === 3 || cmd === 10) { + switch (evt.keyCode) { + case 80: // p + SecondaryToolbar.presentationModeClick(); + handled = true; + break; + case 71: // g + // focuses input#pageNumber field + document.getElementById('pageNumber').select(); + handled = true; + break; + } + } + + if (handled) { + evt.preventDefault(); + return; + } + + // Some shortcuts should not get handled if a control/input element + // is selected. + var curElement = document.activeElement || document.querySelector(':focus'); + if (curElement && (curElement.tagName.toUpperCase() === 'INPUT' || + curElement.tagName.toUpperCase() === 'TEXTAREA' || + curElement.tagName.toUpperCase() === 'SELECT')) { + // Make sure that the secondary toolbar is closed when Escape is pressed. + if (evt.keyCode !== 27) { // 'Esc' + return; + } + } + var controlsElement = document.getElementById('toolbar'); + while (curElement) { + if (curElement === controlsElement && !PresentationMode.active) + return; // ignoring if the 'toolbar' element is focused + curElement = curElement.parentNode; + } + + if (cmd === 0) { // no control key pressed at all. + switch (evt.keyCode) { + case 38: // up arrow + case 33: // pg up + case 8: // backspace + if (!PresentationMode.active && + PDFView.currentScaleValue !== 'page-fit') { + break; + } + /* in presentation mode */ + /* falls through */ + case 37: // left arrow + // horizontal scrolling using arrow keys + if (PDFView.isHorizontalScrollbarEnabled) { + break; + } + /* falls through */ + case 75: // 'k' + case 80: // 'p' + PDFView.page--; + handled = true; + break; + case 27: // esc key + if (SecondaryToolbar.opened) { + SecondaryToolbar.close(); + handled = true; + } + if (!PDFView.supportsIntegratedFind && PDFFindBar.opened) { + PDFFindBar.close(); + handled = true; + } + break; + case 40: // down arrow + case 34: // pg down + case 32: // spacebar + if (!PresentationMode.active && + PDFView.currentScaleValue !== 'page-fit') { + break; + } + /* falls through */ + case 39: // right arrow + // horizontal scrolling using arrow keys + if (PDFView.isHorizontalScrollbarEnabled) { + break; + } + /* falls through */ + case 74: // 'j' + case 78: // 'n' + PDFView.page++; + handled = true; + break; + + case 36: // home + if (PresentationMode.active) { + PDFView.page = 1; + handled = true; + } + break; + case 35: // end + if (PresentationMode.active) { + PDFView.page = PDFView.pdfDocument.numPages; + handled = true; + } + break; + + case 72: // 'h' + if (!PresentationMode.active) { + HandTool.toggle(); + } + break; + case 82: // 'r' + PDFView.rotatePages(90); + break; + } + } + + if (cmd === 4) { // shift-key + switch (evt.keyCode) { + case 32: // spacebar + if (!PresentationMode.active && + PDFView.currentScaleValue !== 'page-fit') { + break; + } + PDFView.page--; + handled = true; + break; + + case 82: // 'r' + PDFView.rotatePages(-90); + break; + } + } + + if (cmd === 2) { // alt-key + switch (evt.keyCode) { + case 37: // left arrow + if (PresentationMode.active) { + PDFHistory.back(); + handled = true; + } + break; + case 39: // right arrow + if (PresentationMode.active) { + PDFHistory.forward(); + handled = true; + } + break; + } + } + + if (handled) { + evt.preventDefault(); + PDFView.clearMouseScrollState(); + } +}); + +window.addEventListener('beforeprint', function beforePrint(evt) { + PDFView.beforePrint(); +}); + +window.addEventListener('afterprint', function afterPrint(evt) { + PDFView.afterPrint(); +}); + +(function animationStartedClosure() { + // The offsetParent is not set until the pdf.js iframe or object is visible. + // Waiting for first animation. + var requestAnimationFrame = window.requestAnimationFrame || + window.mozRequestAnimationFrame || + window.webkitRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function startAtOnce(callback) { callback(); }; + PDFView.animationStartedPromise = new Promise(function (resolve) { + requestAnimationFrame(function onAnimationFrame() { + resolve(); + }); + }); +})(); + + diff --git a/plugins/pdfviewer/viewer/viewer.min.js b/plugins/pdfviewer/viewer/viewer.min.js new file mode 100644 index 00000000..e11ec84f --- /dev/null +++ b/plugins/pdfviewer/viewer/viewer.min.js @@ -0,0 +1,164 @@ +var DEFAULT_URL=null,DEFAULT_SCALE="auto",DEFAULT_SCALE_DELTA=1.1,UNKNOWN_SCALE=0,CACHE_SIZE=20,CSS_UNITS=96/72,SCROLLBAR_PADDING=40,VERTICAL_PADDING=5,MAX_AUTO_SCALE=1.25,MIN_SCALE=0.25,MAX_SCALE=4,VIEW_HISTORY_MEMORY=20,SCALE_SELECT_CONTAINER_PADDING=8,SCALE_SELECT_PADDING=22,THUMBNAIL_SCROLL_MARGIN=-19,USE_ONLY_CSS_ZOOM=!1,CLEANUP_TIMEOUT=3E4,IGNORE_CURRENT_POSITION_ON_ZOOM=!1,RenderingStates={INITIAL:0,RUNNING:1,PAUSED:2,FINISHED:3},FindStates={FIND_FOUND:0,FIND_NOTFOUND:1,FIND_WRAPPED:2,FIND_PENDING:3}; +PDFJS.imageResourcesPath="./images/";PDFJS.workerSrc="pdf.worker.js"; +var mozL10n=document.mozL10n||document.webL10n,CustomStyle=function(){function a(){}var b=["ms","Moz","Webkit","O"],c={};a.getProp=function(a,e){if(1==arguments.length&&"string"==typeof c[a])return c[a];e=e||document.documentElement;var g=e.style,h,f;if("string"==typeof g[a])return c[a]=a;f=a.charAt(0).toUpperCase()+a.slice(1);for(var k=0,s=b.length;ka&&b.shift().destroy()}},isLocalStorageEnabled=function(){try{return"localStorage"in +window&&null!==window.localStorage&&localStorage}catch(a){return!1}}(),EXPORTED_SYMBOLS=["DEFAULT_PREFERENCES"],DEFAULT_PREFERENCES={showPreviousViewOnLoad:!0,defaultZoomValue:"",ifAvailableShowOutlineOnLoad:!1},Preferences=function(){function a(){this.prefs={};this.isInitializedPromiseResolved=!1;this.initializedPromise=this.readFromStorage().then(function(a){this.isInitializedPromiseResolved=!0;a&&(this.prefs=a)}.bind(this))}a.prototype={writeToStorage:function(a){},readFromStorage:function(){return Promise.resolve()}, +reset:function(){this.isInitializedPromiseResolved&&(this.prefs={},this.writeToStorage(this.prefs))},set:function(a,c){if(this.isInitializedPromiseResolved)if(void 0===DEFAULT_PREFERENCES[a])console.error("Preferences_set: '"+a+"' is undefined.");else if(void 0===c)console.error("Preferences_set: no value is specified.");else{var d=typeof c,e=typeof DEFAULT_PREFERENCES[a];if(d!==e)if("number"===d&&"string"===e)c=c.toString();else{console.error("Preferences_set: '"+c+"' is a \""+d+'", expected a "'+ +e+'".');return}this.prefs[a]=c;this.writeToStorage(this.prefs)}},get:function(a){var c=DEFAULT_PREFERENCES[a];if(void 0===c)console.error("Preferences_get: '"+a+"' is undefined.");else return this.isInitializedPromiseResolved&&(a=this.prefs[a],void 0!==a)?a:c}};return a}();Preferences.prototype.writeToStorage=function(a){isLocalStorageEnabled&&localStorage.setItem("preferences",JSON.stringify(a))}; +Preferences.prototype.readFromStorage=function(){return new Promise(function(a){if(isLocalStorageEnabled){var b=JSON.parse(localStorage.getItem("preferences"));a(b)}})}; +(function(){function a(a){var b=document.createEvent("CustomEvent");b.initCustomEvent(a,!1,!1,"custom");window.dispatchEvent(b)}function b(){if(e)if(d(),++g=VIEW_HISTORY_MEMORY&&a.files.shift();for(var c,d=0,e=a.files.length;d=c||0>b.pageIdx)b.pageIdx=a?c-1:0,b.wrapped=!0},updateMatch:function(a){var b=FindStates.FIND_NOTFOUND,c=this.offset.wrapped;this.offset.wrapped=!1;a&&(a=this.selected.pageIdx,this.selected.pageIdx=this.offset.pageIdx,this.selected.matchIdx=this.offset.matchIdx, +b=c?FindStates.FIND_WRAPPED:FindStates.FIND_FOUND,-1!==a&&a!==this.selected.pageIdx&&this.updatePage(a));this.updateUIState(b,this.state.findPrevious);-1!==this.selected.pageIdx&&this.updatePage(this.selected.pageIdx,!0)},updateUIState:function(a,b){this.integratedFind?FirefoxCom.request("updateFindControlState",{result:a,findPrevious:b}):PDFFindBar.updateUIState(a,b)}},PDFHistory={initialized:!1,initialDestination:null,initialize:function(a){function b(){var a=c._getPreviousParams(null,!0);a&&(c._pushToHistory(a, +!1,!c.current.dest&&c.current.hash!==c.previousHash),c._updatePreviousBookmark());window.removeEventListener("beforeunload",b,!1)}if(!PDFJS.disableHistory&&!PDFView.isViewerEmbedded){this.initialized=!0;this.reInitialized=!1;this.historyUnlocked=this.allowHashChange=!0;this.previousHash=window.location.hash.substring(1);this.currentBookmark="";this.currentPage=0;this.updatePreviousBookmark=!1;this.previousBookmark="";this.previousPage=0;this.nextHashParam="";this.fingerprint=a;this.currentUid=this.uid= +0;this.current={};a=window.history.state;this._isStateObjectDefined(a)?(a.target.dest?this.initialDestination=a.target.dest:PDFView.initialBookmark=a.target.hash,this.currentUid=a.uid,this.uid=a.uid+1,this.current=a.target):(a&&a.fingerprint&&this.fingerprint!==a.fingerprint&&(this.reInitialized=!0),this._pushOrReplaceState({fingerprint:this.fingerprint},!0));var c=this;window.addEventListener("popstate",function(a){a.preventDefault();a.stopPropagation();c.historyUnlocked&&(a.state?c._goTo(a.state): +(c.previousHash=window.location.hash.substring(1),0===c.uid&&(a=c.previousHash&&c.currentBookmark&&c.previousHash!==c.currentBookmark?{hash:c.currentBookmark,page:c.currentPage}:{page:1},c.historyUnlocked=!1,c.allowHashChange=!1,window.history.back(),c._pushToHistory(a,!1,!0),window.history.forward(),c.historyUnlocked=!0),c._pushToHistory({hash:c.previousHash},!1,!0),c._updatePreviousBookmark()))},!1);window.addEventListener("beforeunload",b,!1);window.addEventListener("pageshow",function(a){window.addEventListener("beforeunload", +b,!1)},!1)}},_isStateObjectDefined:function(a){return a&&0<=a.uid&&a.fingerprint&&this.fingerprint===a.fingerprint&&a.target&&a.target.hash?!0:!1},_pushOrReplaceState:function(a,b){b?window.history.replaceState(a,"",document.URL):window.history.pushState(a,"",document.URL)},get isHashChangeUnlocked(){if(!this.initialized)return!0;var a=this.allowHashChange;this.allowHashChange=!0;return a},_updatePreviousBookmark:function(){this.updatePreviousBookmark&&this.currentBookmark&&this.currentPage&&(this.previousBookmark= +this.currentBookmark,this.previousPage=this.currentPage,this.updatePreviousBookmark=!1)},updateCurrentBookmark:function(a,b){this.initialized&&(this.currentBookmark=a.substring(1),this.currentPage=b|0,this._updatePreviousBookmark())},updateNextHashParam:function(a){this.initialized&&(this.nextHashParam=a)},push:function(a,b){if(this.initialized&&this.historyUnlocked)if(a.dest&&!a.hash&&(a.hash=this.current.hash&&this.current.dest&&this.current.dest===a.dest?this.current.hash:PDFView.getDestinationHash(a.dest).split("#")[1]), +a.page&&(a.page|=0),b){var c=window.history.state.target;c||(this._pushToHistory(a,!1),this.previousHash=window.location.hash.substring(1));this.updatePreviousBookmark=this.nextHashParam?!1:!0;c&&this._updatePreviousBookmark()}else{if(this.nextHashParam){if(this.nextHashParam===a.hash){this.nextHashParam=null;this.updatePreviousBookmark=!0;return}this.nextHashParam=null}a.hash?this.current.hash?this.current.hash!==a.hash?this._pushToHistory(a,!0):(!this.current.page&&a.page&&this._pushToHistory(a, +!1,!0),this.updatePreviousBookmark=!0):this._pushToHistory(a,!0):this.current.page&&a.page&&this.current.page!==a.page&&this._pushToHistory(a,!0)}},_getPreviousParams:function(a,b){if(!this.currentBookmark||!this.currentPage)return null;this.updatePreviousBookmark&&(this.updatePreviousBookmark=!1);if(0this.uid&& +(this.uid=a.uid);this.current=a.target;this.updatePreviousBookmark=!0;a=window.location.hash.substring(1);this.previousHash!==a&&(this.allowHashChange=!1);this.previousHash=a;this.historyUnlocked=!0}},back:function(){this.go(-1)},forward:function(){this.go(1)},go:function(a){if(this.initialized&&this.historyUnlocked){var b=window.history.state;-1===a&&b&&0e?b.down=!0:dMIN_SCALE);this.setScale(b,!0)},set page(a){var b=this.pages,c=document.createEvent("UIEvents");c.initUIEvent("pagechange",!1,!1,window,0);0this.container.clientWidth},initPassiveLoading:function(){var a={rangeListeners:[],progressListeners:[],addRangeListener:function(a){this.rangeListeners.push(a)},addProgressListener:function(a){this.progressListeners.push(a)},onDataRange:function(a,c){for(var d=this.rangeListeners,e=0,g=d.length;ec.pages.length&&(g=c.pages.length),c.pages[g-1].scrollIntoView(a), +PDFHistory.push({dest:a,hash:b,page:g})):c.pdfDocument.getPageIndex(e).then(function(a){c.pagesRefMap[e.num+" "+e.gen+" R"]=a+1;d(e)})};this.destinationsPromise.then(function(){"string"===typeof a&&(b=a,a=c.destinations[a]);a instanceof Array&&d(a[0])})},getDestinationHash:function(a){if("string"===typeof a)return PDFView.getAnchorUrl("#"+escape(a));if(a instanceof Array){var b=a[0];if(b=b instanceof Object?this.pagesRefMap[b.num+" "+b.gen+" R"]:b+1){var b=PDFView.getAnchorUrl("#page="+b),c=a[1]; +if("object"===typeof c&&"name"in c&&"XYZ"==c.name){var c=a[4]||this.currentScaleValue,d=parseFloat(c);d&&(c=100*d);b+="&zoom="+c;if(a[2]||a[3])b+=","+(a[2]||0)+","+(a[3]||0)}return b}}return""},getAnchorUrl:function(a){return a},error:function(a,b){var c=mozL10n.get("error_version_info",{version:PDFJS.version||"?",build:PDFJS.build||"?"},"PDF.js v{{version}} (build: {{build}})")+"\n";b&&(c+=mozL10n.get("error_message",{message:b.message},"Message: {{message}}"),b.stack?c+="\n"+mozL10n.get("error_stack", +{stack:b.stack},"Stack: {{stack}}"):(b.filename&&(c+="\n"+mozL10n.get("error_file",{file:b.filename},"File: {{file}}")),b.lineNumber&&(c+="\n"+mozL10n.get("error_line",{line:b.lineNumber},"Line: {{line}}"))));var d=document.getElementById("errorWrapper");d.removeAttribute("hidden");document.getElementById("errorMessage").textContent=a;var e=document.getElementById("errorClose");e.onclick=function(){d.setAttribute("hidden","true")};var g=document.getElementById("errorMoreInfo"),h=document.getElementById("errorShowMore"), +f=document.getElementById("errorShowLess");h.onclick=function(){g.removeAttribute("hidden");h.setAttribute("hidden","true");f.removeAttribute("hidden");g.style.height=g.scrollHeight+"px"};f.onclick=function(){g.setAttribute("hidden","true");h.removeAttribute("hidden");f.setAttribute("hidden","true")};h.oncontextmenu=noContextMenuHandler;f.oncontextmenu=noContextMenuHandler;e.oncontextmenu=noContextMenuHandler;h.removeAttribute("hidden");f.setAttribute("hidden","true");g.value=c},progress:function(a){a= +Math.round(100*a);a>PDFView.loadingBar.percent&&(PDFView.loadingBar.percent=a)},load:function(a,b){function c(a,b){a.onAfterDraw=function(){e||(e=!0,g());b.setImage(a.canvas)}}var d=this,e=!1,g=null,h=new Promise(function(a){g=a});PDFFindController.reset();this.pdfDocument=a;document.getElementById("errorWrapper").setAttribute("hidden","true");a.dataLoaded().then(function(){PDFView.loadingBar.hide();document.getElementById("outerContainer").classList.remove("loadingInProgress")});var f=document.getElementById("thumbnailView"); +for(f.parentNode.scrollTop=0;f.hasChildNodes();)f.removeChild(f.lastChild);"_loadingInterval"in f&&clearInterval(f._loadingInterval);for(var k=document.getElementById("viewer");k.hasChildNodes();)k.removeChild(k.lastChild);var s=a.numPages,n=a.fingerprint;document.getElementById("numPages").textContent=mozL10n.get("page_of",{pageCount:s},"of {{pageCount}}");document.getElementById("pageNumber").max=s;var q=PDFView.prefs=new Preferences;PDFView.documentFingerprint=n;var t=PDFView.store=new ViewHistory(n); +this.pageRotation=0;var u=this.pages=[],p=this.pagesRefMap={},l=this.thumbnails=[],r;this.pagesPromise=n=new Promise(function(a){r=a});var m=a.getPage(1);m.then(function(e){e=e.getViewport((b||1)*CSS_UNITS);for(var g=1;g<=s;++g){var n=e.clone(),q=new PageView(k,g,b,d.navigateTo.bind(d),n),n=new ThumbnailView(f,g,n);c(q,n);u.push(q);l.push(n)}h.then(function(){if(PDFJS.disableAutoFetch)r();else for(var b=s,c=1;c<=s;++c)a.getPage(c).then(function(a,c){var d=u[a-1];d.pdfPage||d.setPdfPage(c);p[c.ref.num+ +" "+c.ref.gen+" R"]=a;b--;b||r()}.bind(null,c))});e=document.createEvent("CustomEvent");e.initCustomEvent("documentload",!0,!0,{});window.dispatchEvent(e);PDFView.loadingBar.setWidth(k);PDFFindController.resolveFirstPage()});Promise.all([m,q.initializedPromise,t.initializedPromise]).then(function(){var a=q.get("showPreviousViewOnLoad"),c=q.get("defaultZoomValue"),e=null;if(a&&t.get("exists",!1))var a=t.get("page","1"),c=c||t.get("zoom",PDFView.currentScale),e=t.get("scrollLeft","0"),h=t.get("scrollTop", +"0"),e="page="+a+"&zoom="+c+","+e+","+h;else c&&(e="page=1&zoom="+c);PDFHistory.initialize(d.documentFingerprint);d.setInitialView(e,b);d.isViewerEmbedded||d.container.focus()});n.then(function(){PDFView.supportsPrinting&&a.getJavaScript().then(function(a){a.length&&(console.warn("Warning: JavaScript is not supported"),PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript));for(var b=/\bprint\s*\(/g,c=0,d=a.length;ce)break;q=f.el.offsetLeft+f.el.clientLeft;n=f.el.clientWidth;q+nh||(n=Math.max(0,d-k)+Math.max(0,k+s-e),s=100*(s-n)/s|0,a.push({id:f.id,x:q,y:k,view:f,percent:s}))}b=a[0];d=a[a.length-1];c&&a.sort(function(a,b){var c=a.percent-b.percent;return 0.0010c&&50>b-c||((0a||0>this.mouseScrollDelta&&0=e+g[b].str.length;)e+=g[b].str.length,b++;b==g.length&&console.error("Could not find matching mapping");for(var q={begin:{divIdx:b,offset:n-e}},n=n+f;b!==h&&n>e+g[b].str.length;)e+=g[b].str.length,b++;q.end={divIdx:b,offset:n-e};k.push(q)}return k};this.renderMatches=function(a){function b(a,c){var d=a.divIdx,e=h[d];e.textContent="";var f= +g[d].str.substring(0,a.offset),f=document.createTextNode(f);if(c){var d=k&&d===s,l=document.createElement("span");l.className=c+(d?" selected":"");l.appendChild(f);e.appendChild(l)}else e.appendChild(f)}function e(a,b,c){var d=a.divIdx,e=h[d];a=g[d].str.substring(a.offset,b.offset);a=document.createTextNode(a);c?(b=document.createElement("span"),b.className=c,b.appendChild(a),e.appendChild(b)):e.appendChild(a)}if(0!==a.length){var g=this.textContent.bidiTexts,h=this.textDivs,f=null,k=null===PDFFindController? +!1:this.pageIdx===PDFFindController.selected.pageIdx,s=null===PDFFindController?-1:PDFFindController.selected.matchIdx,n={divIdx:-1,offset:void 0},q=s,t=q+1;if(null===PDFFindController?0:PDFFindController.state.highlightAll)q=0,t=a.length;else if(!k)return;for(;qh.percent)break;if(h.id===PDFView.page){g=!0;break}}g||(c=b[0].id);updateViewarea.inProgress=!0;PDFView.page=c;updateViewarea.inProgress=!1;var b=PDFView.currentScale,c=PDFView.currentScaleValue,f=parseFloat(c)===b?Math.round(1E4*b)/100:c,k=a.id,b="#page="+k+("&zoom="+f),c=PDFView.container, +a=PDFView.pages[k-1].getPagePoint(c.scrollLeft-a.x,c.scrollTop-a.y),s=Math.round(a[0]),n=Math.round(a[1]),b=b+(","+s+","+n);PDFView.currentPosition=PresentationMode.active||PresentationMode.switchInProgress?null:{page:k,left:s,top:n};var q=PDFView.store;q.initializedPromise.then(function(){q.set("exists",!0);q.set("page",k);q.set("zoom",f);q.set("scrollLeft",s);q.set("scrollTop",n)});a=PDFView.getAnchorUrl(b);document.getElementById("viewBookmark").href=a;document.getElementById("secondaryViewBookmark").href= +a;PDFHistory.updateCurrentBookmark(b,k)}}}window.addEventListener("resize",function(a){PDFView.initialized&&(document.getElementById("pageWidthOption").selected||document.getElementById("pageFitOption").selected||document.getElementById("pageAutoOption").selected)&&PDFView.setScale(document.getElementById("scaleSelect").value);updateViewarea();SecondaryToolbar.setMaxHeight(PDFView.container)});window.addEventListener("hashchange",function(a){PDFHistory.isHashChangeUnlocked&&PDFView.setHash(document.location.hash.substring(1))}); +window.addEventListener("change",function(a){var b=a.target.files;b&&0!==b.length&&(a=new FileReader,a.onload=function(a){a=new Uint8Array(a.target.result);PDFView.open(a,0)},b=b[0],a.readAsArrayBuffer(b),PDFView.setTitleUsingUrl(b.name),document.getElementById("viewBookmark").setAttribute("hidden","true"),document.getElementById("secondaryViewBookmark").setAttribute("hidden","true"),document.getElementById("download").setAttribute("hidden","true"),document.getElementById("secondaryDownload").setAttribute("hidden", +"true"))},!0);function selectScaleOption(a){for(var b=document.getElementById("scaleSelect").options,c=!1,d=0;d=c)&&scrollIntoView(b,{top:THUMBNAIL_SCROLL_MARGIN})}}document.getElementById("previous").disabled=1>= +a;document.getElementById("next").disabled=a>=PDFView.pages.length},!0);window.addEventListener("DOMMouseScroll",function(a){a.ctrlKey?(a.preventDefault(),a=a.detail,PDFView[0