pdf_viewer.js 99 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135
  1. /* Copyright 2014 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. /*jshint globalstrict: false */
  16. /* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder, PDFLinkService,
  17. DefaultTextLayerFactory, AnnotationLayerBuilder, PDFHistory,
  18. DefaultAnnotationLayerFactory, DownloadManager, ProgressBar */
  19. // Initializing PDFJS global object (if still undefined)
  20. if (typeof PDFJS === 'undefined') {
  21. (typeof window !== 'undefined' ? window : this).PDFJS = {};
  22. }
  23. (function pdfViewerWrapper() {
  24. 'use strict';
  25. var CSS_UNITS = 96.0 / 72.0;
  26. var DEFAULT_SCALE_VALUE = 'auto';
  27. var DEFAULT_SCALE = 1.0;
  28. var UNKNOWN_SCALE = 0;
  29. var MAX_AUTO_SCALE = 1.25;
  30. var SCROLLBAR_PADDING = 40;
  31. var VERTICAL_PADDING = 5;
  32. /**
  33. * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
  34. * @return {Object} The object with horizontal (sx) and vertical (sy)
  35. scales. The scaled property is set to false if scaling is
  36. not required, true otherwise.
  37. */
  38. function getOutputScale(ctx) {
  39. var devicePixelRatio = window.devicePixelRatio || 1;
  40. var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
  41. ctx.mozBackingStorePixelRatio ||
  42. ctx.msBackingStorePixelRatio ||
  43. ctx.oBackingStorePixelRatio ||
  44. ctx.backingStorePixelRatio || 1;
  45. var pixelRatio = devicePixelRatio / backingStoreRatio;
  46. return {
  47. sx: pixelRatio,
  48. sy: pixelRatio,
  49. scaled: pixelRatio !== 1
  50. };
  51. }
  52. /**
  53. * Scrolls specified element into view of its parent.
  54. * @param {Object} element - The element to be visible.
  55. * @param {Object} spot - An object with optional top and left properties,
  56. * specifying the offset from the top left edge.
  57. * @param {boolean} skipOverflowHiddenElements - Ignore elements that have
  58. * the CSS rule `overflow: hidden;` set. The default is false.
  59. */
  60. function scrollIntoView(element, spot, skipOverflowHiddenElements) {
  61. // Assuming offsetParent is available (it's not available when viewer is in
  62. // hidden iframe or object). We have to scroll: if the offsetParent is not set
  63. // producing the error. See also animationStartedClosure.
  64. var parent = element.offsetParent;
  65. if (!parent) {
  66. console.error('offsetParent is not set -- cannot scroll');
  67. return;
  68. }
  69. var checkOverflow = skipOverflowHiddenElements || false;
  70. var offsetY = element.offsetTop + element.clientTop;
  71. var offsetX = element.offsetLeft + element.clientLeft;
  72. while (parent.clientHeight === parent.scrollHeight ||
  73. (checkOverflow && getComputedStyle(parent).overflow === 'hidden')) {
  74. if (parent.dataset._scaleY) {
  75. offsetY /= parent.dataset._scaleY;
  76. offsetX /= parent.dataset._scaleX;
  77. }
  78. offsetY += parent.offsetTop;
  79. offsetX += parent.offsetLeft;
  80. parent = parent.offsetParent;
  81. if (!parent) {
  82. return; // no need to scroll
  83. }
  84. }
  85. if (spot) {
  86. if (spot.top !== undefined) {
  87. offsetY += spot.top;
  88. }
  89. if (spot.left !== undefined) {
  90. offsetX += spot.left;
  91. parent.scrollLeft = offsetX;
  92. }
  93. }
  94. parent.scrollTop = offsetY;
  95. }
  96. /**
  97. * Helper function to start monitoring the scroll event and converting them into
  98. * PDF.js friendly one: with scroll debounce and scroll direction.
  99. */
  100. function watchScroll(viewAreaElement, callback) {
  101. var debounceScroll = function debounceScroll(evt) {
  102. if (rAF) {
  103. return;
  104. }
  105. // schedule an invocation of scroll for next animation frame.
  106. rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
  107. rAF = null;
  108. var currentY = viewAreaElement.scrollTop;
  109. var lastY = state.lastY;
  110. if (currentY !== lastY) {
  111. state.down = currentY > lastY;
  112. }
  113. state.lastY = currentY;
  114. callback(state);
  115. });
  116. };
  117. var state = {
  118. down: true,
  119. lastY: viewAreaElement.scrollTop,
  120. _eventHandler: debounceScroll
  121. };
  122. var rAF = null;
  123. viewAreaElement.addEventListener('scroll', debounceScroll, true);
  124. return state;
  125. }
  126. /**
  127. * Helper function to parse query string (e.g. ?param1=value&parm2=...).
  128. */
  129. function parseQueryString(query) {
  130. var parts = query.split('&');
  131. var params = {};
  132. for (var i = 0, ii = parts.length; i < ii; ++i) {
  133. var param = parts[i].split('=');
  134. var key = param[0].toLowerCase();
  135. var value = param.length > 1 ? param[1] : null;
  136. params[decodeURIComponent(key)] = decodeURIComponent(value);
  137. }
  138. return params;
  139. }
  140. /**
  141. * Use binary search to find the index of the first item in a given array which
  142. * passes a given condition. The items are expected to be sorted in the sense
  143. * that if the condition is true for one item in the array, then it is also true
  144. * for all following items.
  145. *
  146. * @returns {Number} Index of the first array element to pass the test,
  147. * or |items.length| if no such element exists.
  148. */
  149. function binarySearchFirstItem(items, condition) {
  150. var minIndex = 0;
  151. var maxIndex = items.length - 1;
  152. if (items.length === 0 || !condition(items[maxIndex])) {
  153. return items.length;
  154. }
  155. if (condition(items[minIndex])) {
  156. return minIndex;
  157. }
  158. while (minIndex < maxIndex) {
  159. var currentIndex = (minIndex + maxIndex) >> 1;
  160. var currentItem = items[currentIndex];
  161. if (condition(currentItem)) {
  162. maxIndex = currentIndex;
  163. } else {
  164. minIndex = currentIndex + 1;
  165. }
  166. }
  167. return minIndex; /* === maxIndex */
  168. }
  169. /**
  170. * Approximates float number as a fraction using Farey sequence (max order
  171. * of 8).
  172. * @param {number} x - Positive float number.
  173. * @returns {Array} Estimated fraction: the first array item is a numerator,
  174. * the second one is a denominator.
  175. */
  176. function approximateFraction(x) {
  177. // Fast paths for int numbers or their inversions.
  178. if (Math.floor(x) === x) {
  179. return [x, 1];
  180. }
  181. var xinv = 1 / x;
  182. var limit = 8;
  183. if (xinv > limit) {
  184. return [1, limit];
  185. } else if (Math.floor(xinv) === xinv) {
  186. return [1, xinv];
  187. }
  188. var x_ = x > 1 ? xinv : x;
  189. // a/b and c/d are neighbours in Farey sequence.
  190. var a = 0, b = 1, c = 1, d = 1;
  191. // Limiting search to order 8.
  192. while (true) {
  193. // Generating next term in sequence (order of q).
  194. var p = a + c, q = b + d;
  195. if (q > limit) {
  196. break;
  197. }
  198. if (x_ <= p / q) {
  199. c = p; d = q;
  200. } else {
  201. a = p; b = q;
  202. }
  203. }
  204. // Select closest of the neighbours to x.
  205. if (x_ - a / b < c / d - x_) {
  206. return x_ === x ? [a, b] : [b, a];
  207. } else {
  208. return x_ === x ? [c, d] : [d, c];
  209. }
  210. }
  211. function roundToDivide(x, div) {
  212. var r = x % div;
  213. return r === 0 ? x : Math.round(x - r + div);
  214. }
  215. /**
  216. * Generic helper to find out what elements are visible within a scroll pane.
  217. */
  218. function getVisibleElements(scrollEl, views, sortByVisibility) {
  219. var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
  220. var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
  221. function isElementBottomBelowViewTop(view) {
  222. var element = view.div;
  223. var elementBottom =
  224. element.offsetTop + element.clientTop + element.clientHeight;
  225. return elementBottom > top;
  226. }
  227. var visible = [], view, element;
  228. var currentHeight, viewHeight, hiddenHeight, percentHeight;
  229. var currentWidth, viewWidth;
  230. var firstVisibleElementInd = (views.length === 0) ? 0 :
  231. binarySearchFirstItem(views, isElementBottomBelowViewTop);
  232. for (var i = firstVisibleElementInd, ii = views.length; i < ii; i++) {
  233. view = views[i];
  234. element = view.div;
  235. currentHeight = element.offsetTop + element.clientTop;
  236. viewHeight = element.clientHeight;
  237. if (currentHeight > bottom) {
  238. break;
  239. }
  240. currentWidth = element.offsetLeft + element.clientLeft;
  241. viewWidth = element.clientWidth;
  242. if (currentWidth + viewWidth < left || currentWidth > right) {
  243. continue;
  244. }
  245. hiddenHeight = Math.max(0, top - currentHeight) +
  246. Math.max(0, currentHeight + viewHeight - bottom);
  247. percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
  248. visible.push({
  249. id: view.id,
  250. x: currentWidth,
  251. y: currentHeight,
  252. view: view,
  253. percent: percentHeight
  254. });
  255. }
  256. var first = visible[0];
  257. var last = visible[visible.length - 1];
  258. if (sortByVisibility) {
  259. visible.sort(function(a, b) {
  260. var pc = a.percent - b.percent;
  261. if (Math.abs(pc) > 0.001) {
  262. return -pc;
  263. }
  264. return a.id - b.id; // ensure stability
  265. });
  266. }
  267. return {first: first, last: last, views: visible};
  268. }
  269. /**
  270. * Event handler to suppress context menu.
  271. */
  272. function noContextMenuHandler(e) {
  273. e.preventDefault();
  274. }
  275. /**
  276. * Returns the filename or guessed filename from the url (see issue 3455).
  277. * url {String} The original PDF location.
  278. * @return {String} Guessed PDF file name.
  279. */
  280. function getPDFFileNameFromURL(url) {
  281. var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
  282. // SCHEME HOST 1.PATH 2.QUERY 3.REF
  283. // Pattern to get last matching NAME.pdf
  284. var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
  285. var splitURI = reURI.exec(url);
  286. var suggestedFilename = reFilename.exec(splitURI[1]) ||
  287. reFilename.exec(splitURI[2]) ||
  288. reFilename.exec(splitURI[3]);
  289. if (suggestedFilename) {
  290. suggestedFilename = suggestedFilename[0];
  291. if (suggestedFilename.indexOf('%') !== -1) {
  292. // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
  293. try {
  294. suggestedFilename =
  295. reFilename.exec(decodeURIComponent(suggestedFilename))[0];
  296. } catch(e) { // Possible (extremely rare) errors:
  297. // URIError "Malformed URI", e.g. for "%AA.pdf"
  298. // TypeError "null has no properties", e.g. for "%2F.pdf"
  299. }
  300. }
  301. }
  302. return suggestedFilename || 'document.pdf';
  303. }
  304. var ProgressBar = (function ProgressBarClosure() {
  305. function clamp(v, min, max) {
  306. return Math.min(Math.max(v, min), max);
  307. }
  308. function ProgressBar(id, opts) {
  309. this.visible = true;
  310. // Fetch the sub-elements for later.
  311. this.div = document.querySelector(id + ' .progress');
  312. // Get the loading bar element, so it can be resized to fit the viewer.
  313. this.bar = this.div.parentNode;
  314. // Get options, with sensible defaults.
  315. this.height = opts.height || 100;
  316. this.width = opts.width || 100;
  317. this.units = opts.units || '%';
  318. // Initialize heights.
  319. this.div.style.height = this.height + this.units;
  320. this.percent = 0;
  321. }
  322. ProgressBar.prototype = {
  323. updateBar: function ProgressBar_updateBar() {
  324. if (this._indeterminate) {
  325. this.div.classList.add('indeterminate');
  326. this.div.style.width = this.width + this.units;
  327. return;
  328. }
  329. this.div.classList.remove('indeterminate');
  330. var progressSize = this.width * this._percent / 100;
  331. this.div.style.width = progressSize + this.units;
  332. },
  333. get percent() {
  334. return this._percent;
  335. },
  336. set percent(val) {
  337. this._indeterminate = isNaN(val);
  338. this._percent = clamp(val, 0, 100);
  339. this.updateBar();
  340. },
  341. setWidth: function ProgressBar_setWidth(viewer) {
  342. if (viewer) {
  343. var container = viewer.parentNode;
  344. var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
  345. if (scrollbarWidth > 0) {
  346. this.bar.setAttribute('style', 'width: calc(100% - ' +
  347. scrollbarWidth + 'px);');
  348. }
  349. }
  350. },
  351. hide: function ProgressBar_hide() {
  352. if (!this.visible) {
  353. return;
  354. }
  355. this.visible = false;
  356. this.bar.classList.add('hidden');
  357. document.body.classList.remove('loadingInProgress');
  358. },
  359. show: function ProgressBar_show() {
  360. if (this.visible) {
  361. return;
  362. }
  363. this.visible = true;
  364. document.body.classList.add('loadingInProgress');
  365. this.bar.classList.remove('hidden');
  366. }
  367. };
  368. return ProgressBar;
  369. })();
  370. /**
  371. * Performs navigation functions inside PDF, such as opening specified page,
  372. * or destination.
  373. * @class
  374. * @implements {IPDFLinkService}
  375. */
  376. var PDFLinkService = (function () {
  377. /**
  378. * @constructs PDFLinkService
  379. */
  380. function PDFLinkService() {
  381. this.baseUrl = null;
  382. this.pdfDocument = null;
  383. this.pdfViewer = null;
  384. this.pdfHistory = null;
  385. this._pagesRefCache = null;
  386. }
  387. PDFLinkService.prototype = {
  388. setDocument: function PDFLinkService_setDocument(pdfDocument, baseUrl) {
  389. this.baseUrl = baseUrl;
  390. this.pdfDocument = pdfDocument;
  391. this._pagesRefCache = Object.create(null);
  392. },
  393. setViewer: function PDFLinkService_setViewer(pdfViewer) {
  394. this.pdfViewer = pdfViewer;
  395. },
  396. setHistory: function PDFLinkService_setHistory(pdfHistory) {
  397. this.pdfHistory = pdfHistory;
  398. },
  399. /**
  400. * @returns {number}
  401. */
  402. get pagesCount() {
  403. return this.pdfDocument.numPages;
  404. },
  405. /**
  406. * @returns {number}
  407. */
  408. get page() {
  409. return this.pdfViewer.currentPageNumber;
  410. },
  411. /**
  412. * @param {number} value
  413. */
  414. set page(value) {
  415. this.pdfViewer.currentPageNumber = value;
  416. },
  417. /**
  418. * @param dest - The PDF destination object.
  419. */
  420. navigateTo: function PDFLinkService_navigateTo(dest) {
  421. var destString = '';
  422. var self = this;
  423. var goToDestination = function(destRef) {
  424. // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
  425. var pageNumber = destRef instanceof Object ?
  426. self._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
  427. (destRef + 1);
  428. if (pageNumber) {
  429. if (pageNumber > self.pagesCount) {
  430. pageNumber = self.pagesCount;
  431. }
  432. self.pdfViewer.scrollPageIntoView(pageNumber, dest);
  433. if (self.pdfHistory) {
  434. // Update the browsing history.
  435. self.pdfHistory.push({
  436. dest: dest,
  437. hash: destString,
  438. page: pageNumber
  439. });
  440. }
  441. } else {
  442. self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
  443. var pageNum = pageIndex + 1;
  444. var cacheKey = destRef.num + ' ' + destRef.gen + ' R';
  445. self._pagesRefCache[cacheKey] = pageNum;
  446. goToDestination(destRef);
  447. });
  448. }
  449. };
  450. var destinationPromise;
  451. if (typeof dest === 'string') {
  452. destString = dest;
  453. destinationPromise = this.pdfDocument.getDestination(dest);
  454. } else {
  455. destinationPromise = Promise.resolve(dest);
  456. }
  457. destinationPromise.then(function(destination) {
  458. dest = destination;
  459. if (!(destination instanceof Array)) {
  460. return; // invalid destination
  461. }
  462. goToDestination(destination[0]);
  463. });
  464. },
  465. /**
  466. * @param dest - The PDF destination object.
  467. * @returns {string} The hyperlink to the PDF object.
  468. */
  469. getDestinationHash: function PDFLinkService_getDestinationHash(dest) {
  470. if (typeof dest === 'string') {
  471. return this.getAnchorUrl('#' + escape(dest));
  472. }
  473. if (dest instanceof Array) {
  474. var destRef = dest[0]; // see navigateTo method for dest format
  475. var pageNumber = destRef instanceof Object ?
  476. this._pagesRefCache[destRef.num + ' ' + destRef.gen + ' R'] :
  477. (destRef + 1);
  478. if (pageNumber) {
  479. var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
  480. var destKind = dest[1];
  481. if (typeof destKind === 'object' && 'name' in destKind &&
  482. destKind.name === 'XYZ') {
  483. var scale = (dest[4] || this.pdfViewer.currentScaleValue);
  484. var scaleNumber = parseFloat(scale);
  485. if (scaleNumber) {
  486. scale = scaleNumber * 100;
  487. }
  488. pdfOpenParams += '&zoom=' + scale;
  489. if (dest[2] || dest[3]) {
  490. pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
  491. }
  492. }
  493. return pdfOpenParams;
  494. }
  495. }
  496. return this.getAnchorUrl('');
  497. },
  498. /**
  499. * Prefix the full url on anchor links to make sure that links are resolved
  500. * relative to the current URL instead of the one defined in <base href>.
  501. * @param {String} anchor The anchor hash, including the #.
  502. * @returns {string} The hyperlink to the PDF object.
  503. */
  504. getAnchorUrl: function PDFLinkService_getAnchorUrl(anchor) {
  505. return (this.baseUrl || '') + anchor;
  506. },
  507. /**
  508. * @param {string} hash
  509. */
  510. setHash: function PDFLinkService_setHash(hash) {
  511. if (hash.indexOf('=') >= 0) {
  512. var params = parseQueryString(hash);
  513. // borrowing syntax from "Parameters for Opening PDF Files"
  514. if ('nameddest' in params) {
  515. if (this.pdfHistory) {
  516. this.pdfHistory.updateNextHashParam(params.nameddest);
  517. }
  518. this.navigateTo(params.nameddest);
  519. return;
  520. }
  521. var pageNumber, dest;
  522. if ('page' in params) {
  523. pageNumber = (params.page | 0) || 1;
  524. }
  525. if ('zoom' in params) {
  526. // Build the destination array.
  527. var zoomArgs = params.zoom.split(','); // scale,left,top
  528. var zoomArg = zoomArgs[0];
  529. var zoomArgNumber = parseFloat(zoomArg);
  530. if (zoomArg.indexOf('Fit') === -1) {
  531. // If the zoomArg is a number, it has to get divided by 100. If it's
  532. // a string, it should stay as it is.
  533. dest = [null, { name: 'XYZ' },
  534. zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
  535. zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
  536. (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
  537. } else {
  538. if (zoomArg === 'Fit' || zoomArg === 'FitB') {
  539. dest = [null, { name: zoomArg }];
  540. } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
  541. (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
  542. dest = [null, { name: zoomArg },
  543. zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
  544. } else if (zoomArg === 'FitR') {
  545. if (zoomArgs.length !== 5) {
  546. console.error('PDFLinkService_setHash: ' +
  547. 'Not enough parameters for \'FitR\'.');
  548. } else {
  549. dest = [null, { name: zoomArg },
  550. (zoomArgs[1] | 0), (zoomArgs[2] | 0),
  551. (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
  552. }
  553. } else {
  554. console.error('PDFLinkService_setHash: \'' + zoomArg +
  555. '\' is not a valid zoom value.');
  556. }
  557. }
  558. }
  559. if (dest) {
  560. this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
  561. } else if (pageNumber) {
  562. this.page = pageNumber; // simple page
  563. }
  564. if ('pagemode' in params) {
  565. var event = document.createEvent('CustomEvent');
  566. event.initCustomEvent('pagemode', true, true, {
  567. mode: params.pagemode,
  568. });
  569. this.pdfViewer.container.dispatchEvent(event);
  570. }
  571. } else if (/^\d+$/.test(hash)) { // page number
  572. this.page = hash;
  573. } else { // named destination
  574. if (this.pdfHistory) {
  575. this.pdfHistory.updateNextHashParam(unescape(hash));
  576. }
  577. this.navigateTo(unescape(hash));
  578. }
  579. },
  580. /**
  581. * @param {string} action
  582. */
  583. executeNamedAction: function PDFLinkService_executeNamedAction(action) {
  584. // See PDF reference, table 8.45 - Named action
  585. switch (action) {
  586. case 'GoBack':
  587. if (this.pdfHistory) {
  588. this.pdfHistory.back();
  589. }
  590. break;
  591. case 'GoForward':
  592. if (this.pdfHistory) {
  593. this.pdfHistory.forward();
  594. }
  595. break;
  596. case 'NextPage':
  597. this.page++;
  598. break;
  599. case 'PrevPage':
  600. this.page--;
  601. break;
  602. case 'LastPage':
  603. this.page = this.pagesCount;
  604. break;
  605. case 'FirstPage':
  606. this.page = 1;
  607. break;
  608. default:
  609. break; // No action according to spec
  610. }
  611. var event = document.createEvent('CustomEvent');
  612. event.initCustomEvent('namedaction', true, true, {
  613. action: action
  614. });
  615. this.pdfViewer.container.dispatchEvent(event);
  616. },
  617. /**
  618. * @param {number} pageNum - page number.
  619. * @param {Object} pageRef - reference to the page.
  620. */
  621. cachePageRef: function PDFLinkService_cachePageRef(pageNum, pageRef) {
  622. var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
  623. this._pagesRefCache[refStr] = pageNum;
  624. }
  625. };
  626. return PDFLinkService;
  627. })();
  628. var PresentationModeState = {
  629. UNKNOWN: 0,
  630. NORMAL: 1,
  631. CHANGING: 2,
  632. FULLSCREEN: 3,
  633. };
  634. var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
  635. var DEFAULT_CACHE_SIZE = 10;
  636. var CLEANUP_TIMEOUT = 30000;
  637. var RenderingStates = {
  638. INITIAL: 0,
  639. RUNNING: 1,
  640. PAUSED: 2,
  641. FINISHED: 3
  642. };
  643. /**
  644. * Controls rendering of the views for pages and thumbnails.
  645. * @class
  646. */
  647. var PDFRenderingQueue = (function PDFRenderingQueueClosure() {
  648. /**
  649. * @constructs
  650. */
  651. function PDFRenderingQueue() {
  652. this.pdfViewer = null;
  653. this.pdfThumbnailViewer = null;
  654. this.onIdle = null;
  655. this.highestPriorityPage = null;
  656. this.idleTimeout = null;
  657. this.printing = false;
  658. this.isThumbnailViewEnabled = false;
  659. }
  660. PDFRenderingQueue.prototype = /** @lends PDFRenderingQueue.prototype */ {
  661. /**
  662. * @param {PDFViewer} pdfViewer
  663. */
  664. setViewer: function PDFRenderingQueue_setViewer(pdfViewer) {
  665. this.pdfViewer = pdfViewer;
  666. },
  667. /**
  668. * @param {PDFThumbnailViewer} pdfThumbnailViewer
  669. */
  670. setThumbnailViewer:
  671. function PDFRenderingQueue_setThumbnailViewer(pdfThumbnailViewer) {
  672. this.pdfThumbnailViewer = pdfThumbnailViewer;
  673. },
  674. /**
  675. * @param {IRenderableView} view
  676. * @returns {boolean}
  677. */
  678. isHighestPriority: function PDFRenderingQueue_isHighestPriority(view) {
  679. return this.highestPriorityPage === view.renderingId;
  680. },
  681. renderHighestPriority: function
  682. PDFRenderingQueue_renderHighestPriority(currentlyVisiblePages) {
  683. if (this.idleTimeout) {
  684. clearTimeout(this.idleTimeout);
  685. this.idleTimeout = null;
  686. }
  687. // Pages have a higher priority than thumbnails, so check them first.
  688. if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
  689. return;
  690. }
  691. // No pages needed rendering so check thumbnails.
  692. if (this.pdfThumbnailViewer && this.isThumbnailViewEnabled) {
  693. if (this.pdfThumbnailViewer.forceRendering()) {
  694. return;
  695. }
  696. }
  697. if (this.printing) {
  698. // If printing is currently ongoing do not reschedule cleanup.
  699. return;
  700. }
  701. if (this.onIdle) {
  702. this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
  703. }
  704. },
  705. getHighestPriority: function
  706. PDFRenderingQueue_getHighestPriority(visible, views, scrolledDown) {
  707. // The state has changed figure out which page has the highest priority to
  708. // render next (if any).
  709. // Priority:
  710. // 1 visible pages
  711. // 2 if last scrolled down page after the visible pages
  712. // 2 if last scrolled up page before the visible pages
  713. var visibleViews = visible.views;
  714. var numVisible = visibleViews.length;
  715. if (numVisible === 0) {
  716. return false;
  717. }
  718. for (var i = 0; i < numVisible; ++i) {
  719. var view = visibleViews[i].view;
  720. if (!this.isViewFinished(view)) {
  721. return view;
  722. }
  723. }
  724. // All the visible views have rendered, try to render next/previous pages.
  725. if (scrolledDown) {
  726. var nextPageIndex = visible.last.id;
  727. // ID's start at 1 so no need to add 1.
  728. if (views[nextPageIndex] &&
  729. !this.isViewFinished(views[nextPageIndex])) {
  730. return views[nextPageIndex];
  731. }
  732. } else {
  733. var previousPageIndex = visible.first.id - 2;
  734. if (views[previousPageIndex] &&
  735. !this.isViewFinished(views[previousPageIndex])) {
  736. return views[previousPageIndex];
  737. }
  738. }
  739. // Everything that needs to be rendered has been.
  740. return null;
  741. },
  742. /**
  743. * @param {IRenderableView} view
  744. * @returns {boolean}
  745. */
  746. isViewFinished: function PDFRenderingQueue_isViewFinished(view) {
  747. return view.renderingState === RenderingStates.FINISHED;
  748. },
  749. /**
  750. * Render a page or thumbnail view. This calls the appropriate function
  751. * based on the views state. If the view is already rendered it will return
  752. * false.
  753. * @param {IRenderableView} view
  754. */
  755. renderView: function PDFRenderingQueue_renderView(view) {
  756. var state = view.renderingState;
  757. switch (state) {
  758. case RenderingStates.FINISHED:
  759. return false;
  760. case RenderingStates.PAUSED:
  761. this.highestPriorityPage = view.renderingId;
  762. view.resume();
  763. break;
  764. case RenderingStates.RUNNING:
  765. this.highestPriorityPage = view.renderingId;
  766. break;
  767. case RenderingStates.INITIAL:
  768. this.highestPriorityPage = view.renderingId;
  769. var continueRendering = function () {
  770. this.renderHighestPriority();
  771. }.bind(this);
  772. view.draw().then(continueRendering, continueRendering);
  773. break;
  774. }
  775. return true;
  776. },
  777. };
  778. return PDFRenderingQueue;
  779. })();
  780. var TEXT_LAYER_RENDER_DELAY = 200; // ms
  781. /**
  782. * @typedef {Object} PDFPageViewOptions
  783. * @property {HTMLDivElement} container - The viewer element.
  784. * @property {number} id - The page unique ID (normally its number).
  785. * @property {number} scale - The page scale display.
  786. * @property {PageViewport} defaultViewport - The page viewport.
  787. * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
  788. * @property {IPDFTextLayerFactory} textLayerFactory
  789. * @property {IPDFAnnotationLayerFactory} annotationLayerFactory
  790. */
  791. /**
  792. * @class
  793. * @implements {IRenderableView}
  794. */
  795. var PDFPageView = (function PDFPageViewClosure() {
  796. /**
  797. * @constructs PDFPageView
  798. * @param {PDFPageViewOptions} options
  799. */
  800. function PDFPageView(options) {
  801. var container = options.container;
  802. var id = options.id;
  803. var scale = options.scale;
  804. var defaultViewport = options.defaultViewport;
  805. var renderingQueue = options.renderingQueue;
  806. var textLayerFactory = options.textLayerFactory;
  807. var annotationLayerFactory = options.annotationLayerFactory;
  808. this.id = id;
  809. this.renderingId = 'page' + id;
  810. this.rotation = 0;
  811. this.scale = scale || DEFAULT_SCALE;
  812. this.viewport = defaultViewport;
  813. this.pdfPageRotate = defaultViewport.rotation;
  814. this.hasRestrictedScaling = false;
  815. this.renderingQueue = renderingQueue;
  816. this.textLayerFactory = textLayerFactory;
  817. this.annotationLayerFactory = annotationLayerFactory;
  818. this.renderingState = RenderingStates.INITIAL;
  819. this.resume = null;
  820. this.onBeforeDraw = null;
  821. this.onAfterDraw = null;
  822. this.textLayer = null;
  823. this.zoomLayer = null;
  824. this.annotationLayer = null;
  825. var div = document.createElement('div');
  826. div.id = 'pageContainer' + this.id;
  827. div.className = 'page';
  828. div.style.width = Math.floor(this.viewport.width) + 'px';
  829. div.style.height = Math.floor(this.viewport.height) + 'px';
  830. div.setAttribute('data-page-number', this.id);
  831. this.div = div;
  832. container.appendChild(div);
  833. }
  834. PDFPageView.prototype = {
  835. setPdfPage: function PDFPageView_setPdfPage(pdfPage) {
  836. this.pdfPage = pdfPage;
  837. this.pdfPageRotate = pdfPage.rotate;
  838. var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  839. this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS,
  840. totalRotation);
  841. this.stats = pdfPage.stats;
  842. this.reset();
  843. },
  844. destroy: function PDFPageView_destroy() {
  845. this.zoomLayer = null;
  846. this.reset();
  847. if (this.pdfPage) {
  848. this.pdfPage.cleanup();
  849. }
  850. },
  851. reset: function PDFPageView_reset(keepZoomLayer, keepAnnotations) {
  852. if (this.renderTask) {
  853. this.renderTask.cancel();
  854. }
  855. this.resume = null;
  856. this.renderingState = RenderingStates.INITIAL;
  857. var div = this.div;
  858. div.style.width = Math.floor(this.viewport.width) + 'px';
  859. div.style.height = Math.floor(this.viewport.height) + 'px';
  860. var childNodes = div.childNodes;
  861. var currentZoomLayerNode = (keepZoomLayer && this.zoomLayer) || null;
  862. var currentAnnotationNode = (keepAnnotations && this.annotationLayer &&
  863. this.annotationLayer.div) || null;
  864. for (var i = childNodes.length - 1; i >= 0; i--) {
  865. var node = childNodes[i];
  866. if (currentZoomLayerNode === node || currentAnnotationNode === node) {
  867. continue;
  868. }
  869. div.removeChild(node);
  870. }
  871. div.removeAttribute('data-loaded');
  872. if (currentAnnotationNode) {
  873. // Hide annotationLayer until all elements are resized
  874. // so they are not displayed on the already-resized page
  875. this.annotationLayer.hide();
  876. } else {
  877. this.annotationLayer = null;
  878. }
  879. if (this.canvas && !currentZoomLayerNode) {
  880. // Zeroing the width and height causes Firefox to release graphics
  881. // resources immediately, which can greatly reduce memory consumption.
  882. this.canvas.width = 0;
  883. this.canvas.height = 0;
  884. delete this.canvas;
  885. }
  886. this.loadingIconDiv = document.createElement('div');
  887. this.loadingIconDiv.className = 'loadingIcon';
  888. div.appendChild(this.loadingIconDiv);
  889. },
  890. update: function PDFPageView_update(scale, rotation) {
  891. this.scale = scale || this.scale;
  892. if (typeof rotation !== 'undefined') {
  893. this.rotation = rotation;
  894. }
  895. var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  896. this.viewport = this.viewport.clone({
  897. scale: this.scale * CSS_UNITS,
  898. rotation: totalRotation
  899. });
  900. var isScalingRestricted = false;
  901. if (this.canvas && PDFJS.maxCanvasPixels > 0) {
  902. var outputScale = this.outputScale;
  903. var pixelsInViewport = this.viewport.width * this.viewport.height;
  904. var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
  905. if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
  906. ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
  907. PDFJS.maxCanvasPixels) {
  908. isScalingRestricted = true;
  909. }
  910. }
  911. if (this.canvas) {
  912. if (PDFJS.useOnlyCssZoom ||
  913. (this.hasRestrictedScaling && isScalingRestricted)) {
  914. this.cssTransform(this.canvas, true);
  915. var event = document.createEvent('CustomEvent');
  916. event.initCustomEvent('pagerendered', true, true, {
  917. pageNumber: this.id,
  918. cssTransform: true,
  919. });
  920. this.div.dispatchEvent(event);
  921. return;
  922. }
  923. if (!this.zoomLayer) {
  924. this.zoomLayer = this.canvas.parentNode;
  925. this.zoomLayer.style.position = 'absolute';
  926. }
  927. }
  928. if (this.zoomLayer) {
  929. this.cssTransform(this.zoomLayer.firstChild);
  930. }
  931. this.reset(/* keepZoomLayer = */ true, /* keepAnnotations = */ true);
  932. },
  933. /**
  934. * Called when moved in the parent's container.
  935. */
  936. updatePosition: function PDFPageView_updatePosition() {
  937. if (this.textLayer) {
  938. this.textLayer.render(TEXT_LAYER_RENDER_DELAY);
  939. }
  940. },
  941. cssTransform: function PDFPageView_transform(canvas, redrawAnnotations) {
  942. var CustomStyle = PDFJS.CustomStyle;
  943. // Scale canvas, canvas wrapper, and page container.
  944. var width = this.viewport.width;
  945. var height = this.viewport.height;
  946. var div = this.div;
  947. canvas.style.width = canvas.parentNode.style.width = div.style.width =
  948. Math.floor(width) + 'px';
  949. canvas.style.height = canvas.parentNode.style.height = div.style.height =
  950. Math.floor(height) + 'px';
  951. // The canvas may have been originally rotated, rotate relative to that.
  952. var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
  953. var absRotation = Math.abs(relativeRotation);
  954. var scaleX = 1, scaleY = 1;
  955. if (absRotation === 90 || absRotation === 270) {
  956. // Scale x and y because of the rotation.
  957. scaleX = height / width;
  958. scaleY = width / height;
  959. }
  960. var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
  961. 'scale(' + scaleX + ',' + scaleY + ')';
  962. CustomStyle.setProp('transform', canvas, cssTransform);
  963. if (this.textLayer) {
  964. // Rotating the text layer is more complicated since the divs inside the
  965. // the text layer are rotated.
  966. // TODO: This could probably be simplified by drawing the text layer in
  967. // one orientation then rotating overall.
  968. var textLayerViewport = this.textLayer.viewport;
  969. var textRelativeRotation = this.viewport.rotation -
  970. textLayerViewport.rotation;
  971. var textAbsRotation = Math.abs(textRelativeRotation);
  972. var scale = width / textLayerViewport.width;
  973. if (textAbsRotation === 90 || textAbsRotation === 270) {
  974. scale = width / textLayerViewport.height;
  975. }
  976. var textLayerDiv = this.textLayer.textLayerDiv;
  977. var transX, transY;
  978. switch (textAbsRotation) {
  979. case 0:
  980. transX = transY = 0;
  981. break;
  982. case 90:
  983. transX = 0;
  984. transY = '-' + textLayerDiv.style.height;
  985. break;
  986. case 180:
  987. transX = '-' + textLayerDiv.style.width;
  988. transY = '-' + textLayerDiv.style.height;
  989. break;
  990. case 270:
  991. transX = '-' + textLayerDiv.style.width;
  992. transY = 0;
  993. break;
  994. default:
  995. console.error('Bad rotation value.');
  996. break;
  997. }
  998. CustomStyle.setProp('transform', textLayerDiv,
  999. 'rotate(' + textAbsRotation + 'deg) ' +
  1000. 'scale(' + scale + ', ' + scale + ') ' +
  1001. 'translate(' + transX + ', ' + transY + ')');
  1002. CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
  1003. }
  1004. if (redrawAnnotations && this.annotationLayer) {
  1005. this.annotationLayer.render(this.viewport, 'display');
  1006. }
  1007. },
  1008. get width() {
  1009. return this.viewport.width;
  1010. },
  1011. get height() {
  1012. return this.viewport.height;
  1013. },
  1014. getPagePoint: function PDFPageView_getPagePoint(x, y) {
  1015. return this.viewport.convertToPdfPoint(x, y);
  1016. },
  1017. draw: function PDFPageView_draw() {
  1018. if (this.renderingState !== RenderingStates.INITIAL) {
  1019. console.error('Must be in new state before drawing');
  1020. }
  1021. this.renderingState = RenderingStates.RUNNING;
  1022. var pdfPage = this.pdfPage;
  1023. var viewport = this.viewport;
  1024. var div = this.div;
  1025. // Wrap the canvas so if it has a css transform for highdpi the overflow
  1026. // will be hidden in FF.
  1027. var canvasWrapper = document.createElement('div');
  1028. canvasWrapper.style.width = div.style.width;
  1029. canvasWrapper.style.height = div.style.height;
  1030. canvasWrapper.classList.add('canvasWrapper');
  1031. var canvas = document.createElement('canvas');
  1032. canvas.id = 'page' + this.id;
  1033. // Keep the canvas hidden until the first draw callback, or until drawing
  1034. // is complete when `!this.renderingQueue`, to prevent black flickering.
  1035. canvas.setAttribute('hidden', 'hidden');
  1036. var isCanvasHidden = true;
  1037. canvasWrapper.appendChild(canvas);
  1038. if (this.annotationLayer && this.annotationLayer.div) {
  1039. // annotationLayer needs to stay on top
  1040. div.insertBefore(canvasWrapper, this.annotationLayer.div);
  1041. } else {
  1042. div.appendChild(canvasWrapper);
  1043. }
  1044. this.canvas = canvas;
  1045. var ctx = canvas.getContext('2d', {alpha: false});
  1046. var outputScale = getOutputScale(ctx);
  1047. this.outputScale = outputScale;
  1048. if (PDFJS.useOnlyCssZoom) {
  1049. var actualSizeViewport = viewport.clone({scale: CSS_UNITS});
  1050. // Use a scale that will make the canvas be the original intended size
  1051. // of the page.
  1052. outputScale.sx *= actualSizeViewport.width / viewport.width;
  1053. outputScale.sy *= actualSizeViewport.height / viewport.height;
  1054. outputScale.scaled = true;
  1055. }
  1056. if (PDFJS.maxCanvasPixels > 0) {
  1057. var pixelsInViewport = viewport.width * viewport.height;
  1058. var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
  1059. if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
  1060. outputScale.sx = maxScale;
  1061. outputScale.sy = maxScale;
  1062. outputScale.scaled = true;
  1063. this.hasRestrictedScaling = true;
  1064. } else {
  1065. this.hasRestrictedScaling = false;
  1066. }
  1067. }
  1068. var sfx = approximateFraction(outputScale.sx);
  1069. var sfy = approximateFraction(outputScale.sy);
  1070. canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
  1071. canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
  1072. canvas.style.width = roundToDivide(viewport.width, sfx[1]) + 'px';
  1073. canvas.style.height = roundToDivide(viewport.height, sfy[1]) + 'px';
  1074. // Add the viewport so it's known what it was originally drawn with.
  1075. canvas._viewport = viewport;
  1076. var textLayerDiv = null;
  1077. var textLayer = null;
  1078. if (this.textLayerFactory) {
  1079. textLayerDiv = document.createElement('div');
  1080. textLayerDiv.className = 'textLayer';
  1081. textLayerDiv.style.width = canvasWrapper.style.width;
  1082. textLayerDiv.style.height = canvasWrapper.style.height;
  1083. if (this.annotationLayer && this.annotationLayer.div) {
  1084. // annotationLayer needs to stay on top
  1085. div.insertBefore(textLayerDiv, this.annotationLayer.div);
  1086. } else {
  1087. div.appendChild(textLayerDiv);
  1088. }
  1089. textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv,
  1090. this.id - 1,
  1091. this.viewport);
  1092. }
  1093. this.textLayer = textLayer;
  1094. var resolveRenderPromise, rejectRenderPromise;
  1095. var promise = new Promise(function (resolve, reject) {
  1096. resolveRenderPromise = resolve;
  1097. rejectRenderPromise = reject;
  1098. });
  1099. // Rendering area
  1100. var self = this;
  1101. function pageViewDrawCallback(error) {
  1102. // The renderTask may have been replaced by a new one, so only remove
  1103. // the reference to the renderTask if it matches the one that is
  1104. // triggering this callback.
  1105. if (renderTask === self.renderTask) {
  1106. self.renderTask = null;
  1107. }
  1108. if (error === 'cancelled') {
  1109. rejectRenderPromise(error);
  1110. return;
  1111. }
  1112. self.renderingState = RenderingStates.FINISHED;
  1113. if (isCanvasHidden) {
  1114. self.canvas.removeAttribute('hidden');
  1115. isCanvasHidden = false;
  1116. }
  1117. if (self.loadingIconDiv) {
  1118. div.removeChild(self.loadingIconDiv);
  1119. delete self.loadingIconDiv;
  1120. }
  1121. if (self.zoomLayer) {
  1122. // Zeroing the width and height causes Firefox to release graphics
  1123. // resources immediately, which can greatly reduce memory consumption.
  1124. var zoomLayerCanvas = self.zoomLayer.firstChild;
  1125. zoomLayerCanvas.width = 0;
  1126. zoomLayerCanvas.height = 0;
  1127. div.removeChild(self.zoomLayer);
  1128. self.zoomLayer = null;
  1129. }
  1130. self.error = error;
  1131. self.stats = pdfPage.stats;
  1132. if (self.onAfterDraw) {
  1133. self.onAfterDraw();
  1134. }
  1135. var event = document.createEvent('CustomEvent');
  1136. event.initCustomEvent('pagerendered', true, true, {
  1137. pageNumber: self.id,
  1138. cssTransform: false,
  1139. });
  1140. div.dispatchEvent(event);
  1141. if (!error) {
  1142. resolveRenderPromise(undefined);
  1143. } else {
  1144. rejectRenderPromise(error);
  1145. }
  1146. }
  1147. var renderContinueCallback = null;
  1148. if (this.renderingQueue) {
  1149. renderContinueCallback = function renderContinueCallback(cont) {
  1150. if (!self.renderingQueue.isHighestPriority(self)) {
  1151. self.renderingState = RenderingStates.PAUSED;
  1152. self.resume = function resumeCallback() {
  1153. self.renderingState = RenderingStates.RUNNING;
  1154. cont();
  1155. };
  1156. return;
  1157. }
  1158. if (isCanvasHidden) {
  1159. self.canvas.removeAttribute('hidden');
  1160. isCanvasHidden = false;
  1161. }
  1162. cont();
  1163. };
  1164. }
  1165. var transform = !outputScale.scaled ? null :
  1166. [outputScale.sx, 0, 0, outputScale.sy, 0, 0];
  1167. var renderContext = {
  1168. canvasContext: ctx,
  1169. transform: transform,
  1170. viewport: this.viewport,
  1171. // intent: 'default', // === 'display'
  1172. };
  1173. var renderTask = this.renderTask = this.pdfPage.render(renderContext);
  1174. renderTask.onContinue = renderContinueCallback;
  1175. this.renderTask.promise.then(
  1176. function pdfPageRenderCallback() {
  1177. pageViewDrawCallback(null);
  1178. if (textLayer) {
  1179. self.pdfPage.getTextContent({ normalizeWhitespace: true }).then(
  1180. function textContentResolved(textContent) {
  1181. textLayer.setTextContent(textContent);
  1182. textLayer.render(TEXT_LAYER_RENDER_DELAY);
  1183. }
  1184. );
  1185. }
  1186. },
  1187. function pdfPageRenderError(error) {
  1188. pageViewDrawCallback(error);
  1189. }
  1190. );
  1191. if (this.annotationLayerFactory) {
  1192. if (!this.annotationLayer) {
  1193. this.annotationLayer = this.annotationLayerFactory.
  1194. createAnnotationLayerBuilder(div, this.pdfPage);
  1195. }
  1196. this.annotationLayer.render(this.viewport, 'display');
  1197. }
  1198. div.setAttribute('data-loaded', true);
  1199. if (self.onBeforeDraw) {
  1200. self.onBeforeDraw();
  1201. }
  1202. return promise;
  1203. },
  1204. beforePrint: function PDFPageView_beforePrint() {
  1205. var CustomStyle = PDFJS.CustomStyle;
  1206. var pdfPage = this.pdfPage;
  1207. var viewport = pdfPage.getViewport(1);
  1208. // Use the same hack we use for high dpi displays for printing to get
  1209. // better output until bug 811002 is fixed in FF.
  1210. var PRINT_OUTPUT_SCALE = 2;
  1211. var canvas = document.createElement('canvas');
  1212. // The logical size of the canvas.
  1213. canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
  1214. canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
  1215. // The rendered size of the canvas, relative to the size of canvasWrapper.
  1216. canvas.style.width = (PRINT_OUTPUT_SCALE * 100) + '%';
  1217. canvas.style.height = (PRINT_OUTPUT_SCALE * 100) + '%';
  1218. var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
  1219. (1 / PRINT_OUTPUT_SCALE) + ')';
  1220. CustomStyle.setProp('transform' , canvas, cssScale);
  1221. CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
  1222. var printContainer = document.getElementById('printContainer');
  1223. var canvasWrapper = document.createElement('div');
  1224. canvasWrapper.style.width = viewport.width + 'pt';
  1225. canvasWrapper.style.height = viewport.height + 'pt';
  1226. canvasWrapper.appendChild(canvas);
  1227. printContainer.appendChild(canvasWrapper);
  1228. canvas.mozPrintCallback = function(obj) {
  1229. var ctx = obj.context;
  1230. ctx.save();
  1231. ctx.fillStyle = 'rgb(255, 255, 255)';
  1232. ctx.fillRect(0, 0, canvas.width, canvas.height);
  1233. ctx.restore();
  1234. // Used by the mozCurrentTransform polyfill in src/display/canvas.js.
  1235. ctx._transformMatrix =
  1236. [PRINT_OUTPUT_SCALE, 0, 0, PRINT_OUTPUT_SCALE, 0, 0];
  1237. ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
  1238. var renderContext = {
  1239. canvasContext: ctx,
  1240. viewport: viewport,
  1241. intent: 'print'
  1242. };
  1243. pdfPage.render(renderContext).promise.then(function() {
  1244. // Tell the printEngine that rendering this canvas/page has finished.
  1245. obj.done();
  1246. }, function(error) {
  1247. console.error(error);
  1248. // Tell the printEngine that rendering this canvas/page has failed.
  1249. // This will make the print proces stop.
  1250. if ('abort' in obj) {
  1251. obj.abort();
  1252. } else {
  1253. obj.done();
  1254. }
  1255. });
  1256. };
  1257. },
  1258. };
  1259. return PDFPageView;
  1260. })();
  1261. /**
  1262. * @typedef {Object} TextLayerBuilderOptions
  1263. * @property {HTMLDivElement} textLayerDiv - The text layer container.
  1264. * @property {number} pageIndex - The page index.
  1265. * @property {PageViewport} viewport - The viewport of the text layer.
  1266. * @property {PDFFindController} findController
  1267. */
  1268. /**
  1269. * TextLayerBuilder provides text-selection functionality for the PDF.
  1270. * It does this by creating overlay divs over the PDF text. These divs
  1271. * contain text that matches the PDF text they are overlaying. This object
  1272. * also provides a way to highlight text that is being searched for.
  1273. * @class
  1274. */
  1275. var TextLayerBuilder = (function TextLayerBuilderClosure() {
  1276. function TextLayerBuilder(options) {
  1277. this.textLayerDiv = options.textLayerDiv;
  1278. this.renderingDone = false;
  1279. this.divContentDone = false;
  1280. this.pageIdx = options.pageIndex;
  1281. this.pageNumber = this.pageIdx + 1;
  1282. this.matches = [];
  1283. this.viewport = options.viewport;
  1284. this.textDivs = [];
  1285. this.findController = options.findController || null;
  1286. this.textLayerRenderTask = null;
  1287. this._bindMouse();
  1288. }
  1289. TextLayerBuilder.prototype = {
  1290. _finishRendering: function TextLayerBuilder_finishRendering() {
  1291. this.renderingDone = true;
  1292. var endOfContent = document.createElement('div');
  1293. endOfContent.className = 'endOfContent';
  1294. this.textLayerDiv.appendChild(endOfContent);
  1295. var event = document.createEvent('CustomEvent');
  1296. event.initCustomEvent('textlayerrendered', true, true, {
  1297. pageNumber: this.pageNumber
  1298. });
  1299. this.textLayerDiv.dispatchEvent(event);
  1300. },
  1301. /**
  1302. * Renders the text layer.
  1303. * @param {number} timeout (optional) if specified, the rendering waits
  1304. * for specified amount of ms.
  1305. */
  1306. render: function TextLayerBuilder_render(timeout) {
  1307. if (!this.divContentDone || this.renderingDone) {
  1308. return;
  1309. }
  1310. if (this.textLayerRenderTask) {
  1311. this.textLayerRenderTask.cancel();
  1312. this.textLayerRenderTask = null;
  1313. }
  1314. this.textDivs = [];
  1315. var textLayerFrag = document.createDocumentFragment();
  1316. this.textLayerRenderTask = PDFJS.renderTextLayer({
  1317. textContent: this.textContent,
  1318. container: textLayerFrag,
  1319. viewport: this.viewport,
  1320. textDivs: this.textDivs,
  1321. timeout: timeout
  1322. });
  1323. this.textLayerRenderTask.promise.then(function () {
  1324. this.textLayerDiv.appendChild(textLayerFrag);
  1325. this._finishRendering();
  1326. this.updateMatches();
  1327. }.bind(this), function (reason) {
  1328. // canceled or failed to render text layer -- skipping errors
  1329. });
  1330. },
  1331. setTextContent: function TextLayerBuilder_setTextContent(textContent) {
  1332. if (this.textLayerRenderTask) {
  1333. this.textLayerRenderTask.cancel();
  1334. this.textLayerRenderTask = null;
  1335. }
  1336. this.textContent = textContent;
  1337. this.divContentDone = true;
  1338. },
  1339. convertMatches: function TextLayerBuilder_convertMatches(matches) {
  1340. var i = 0;
  1341. var iIndex = 0;
  1342. var bidiTexts = this.textContent.items;
  1343. var end = bidiTexts.length - 1;
  1344. var queryLen = (this.findController === null ?
  1345. 0 : this.findController.state.query.length);
  1346. var ret = [];
  1347. for (var m = 0, len = matches.length; m < len; m++) {
  1348. // Calculate the start position.
  1349. var matchIdx = matches[m];
  1350. // Loop over the divIdxs.
  1351. while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
  1352. iIndex += bidiTexts[i].str.length;
  1353. i++;
  1354. }
  1355. if (i === bidiTexts.length) {
  1356. console.error('Could not find a matching mapping');
  1357. }
  1358. var match = {
  1359. begin: {
  1360. divIdx: i,
  1361. offset: matchIdx - iIndex
  1362. }
  1363. };
  1364. // Calculate the end position.
  1365. matchIdx += queryLen;
  1366. // Somewhat the same array as above, but use > instead of >= to get
  1367. // the end position right.
  1368. while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
  1369. iIndex += bidiTexts[i].str.length;
  1370. i++;
  1371. }
  1372. match.end = {
  1373. divIdx: i,
  1374. offset: matchIdx - iIndex
  1375. };
  1376. ret.push(match);
  1377. }
  1378. return ret;
  1379. },
  1380. renderMatches: function TextLayerBuilder_renderMatches(matches) {
  1381. // Early exit if there is nothing to render.
  1382. if (matches.length === 0) {
  1383. return;
  1384. }
  1385. var bidiTexts = this.textContent.items;
  1386. var textDivs = this.textDivs;
  1387. var prevEnd = null;
  1388. var pageIdx = this.pageIdx;
  1389. var isSelectedPage = (this.findController === null ?
  1390. false : (pageIdx === this.findController.selected.pageIdx));
  1391. var selectedMatchIdx = (this.findController === null ?
  1392. -1 : this.findController.selected.matchIdx);
  1393. var highlightAll = (this.findController === null ?
  1394. false : this.findController.state.highlightAll);
  1395. var infinity = {
  1396. divIdx: -1,
  1397. offset: undefined
  1398. };
  1399. function beginText(begin, className) {
  1400. var divIdx = begin.divIdx;
  1401. textDivs[divIdx].textContent = '';
  1402. appendTextToDiv(divIdx, 0, begin.offset, className);
  1403. }
  1404. function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
  1405. var div = textDivs[divIdx];
  1406. var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
  1407. var node = document.createTextNode(content);
  1408. if (className) {
  1409. var span = document.createElement('span');
  1410. span.className = className;
  1411. span.appendChild(node);
  1412. div.appendChild(span);
  1413. return;
  1414. }
  1415. div.appendChild(node);
  1416. }
  1417. var i0 = selectedMatchIdx, i1 = i0 + 1;
  1418. if (highlightAll) {
  1419. i0 = 0;
  1420. i1 = matches.length;
  1421. } else if (!isSelectedPage) {
  1422. // Not highlighting all and this isn't the selected page, so do nothing.
  1423. return;
  1424. }
  1425. for (var i = i0; i < i1; i++) {
  1426. var match = matches[i];
  1427. var begin = match.begin;
  1428. var end = match.end;
  1429. var isSelected = (isSelectedPage && i === selectedMatchIdx);
  1430. var highlightSuffix = (isSelected ? ' selected' : '');
  1431. if (this.findController) {
  1432. this.findController.updateMatchPosition(pageIdx, i, textDivs,
  1433. begin.divIdx, end.divIdx);
  1434. }
  1435. // Match inside new div.
  1436. if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
  1437. // If there was a previous div, then add the text at the end.
  1438. if (prevEnd !== null) {
  1439. appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
  1440. }
  1441. // Clear the divs and set the content until the starting point.
  1442. beginText(begin);
  1443. } else {
  1444. appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
  1445. }
  1446. if (begin.divIdx === end.divIdx) {
  1447. appendTextToDiv(begin.divIdx, begin.offset, end.offset,
  1448. 'highlight' + highlightSuffix);
  1449. } else {
  1450. appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
  1451. 'highlight begin' + highlightSuffix);
  1452. for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
  1453. textDivs[n0].className = 'highlight middle' + highlightSuffix;
  1454. }
  1455. beginText(end, 'highlight end' + highlightSuffix);
  1456. }
  1457. prevEnd = end;
  1458. }
  1459. if (prevEnd) {
  1460. appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
  1461. }
  1462. },
  1463. updateMatches: function TextLayerBuilder_updateMatches() {
  1464. // Only show matches when all rendering is done.
  1465. if (!this.renderingDone) {
  1466. return;
  1467. }
  1468. // Clear all matches.
  1469. var matches = this.matches;
  1470. var textDivs = this.textDivs;
  1471. var bidiTexts = this.textContent.items;
  1472. var clearedUntilDivIdx = -1;
  1473. // Clear all current matches.
  1474. for (var i = 0, len = matches.length; i < len; i++) {
  1475. var match = matches[i];
  1476. var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
  1477. for (var n = begin, end = match.end.divIdx; n <= end; n++) {
  1478. var div = textDivs[n];
  1479. div.textContent = bidiTexts[n].str;
  1480. div.className = '';
  1481. }
  1482. clearedUntilDivIdx = match.end.divIdx + 1;
  1483. }
  1484. if (this.findController === null || !this.findController.active) {
  1485. return;
  1486. }
  1487. // Convert the matches on the page controller into the match format
  1488. // used for the textLayer.
  1489. this.matches = this.convertMatches(this.findController === null ?
  1490. [] : (this.findController.pageMatches[this.pageIdx] || []));
  1491. this.renderMatches(this.matches);
  1492. },
  1493. /**
  1494. * Fixes text selection: adds additional div where mouse was clicked.
  1495. * This reduces flickering of the content if mouse slowly dragged down/up.
  1496. * @private
  1497. */
  1498. _bindMouse: function TextLayerBuilder_bindMouse() {
  1499. var div = this.textLayerDiv;
  1500. div.addEventListener('mousedown', function (e) {
  1501. var end = div.querySelector('.endOfContent');
  1502. if (!end) {
  1503. return;
  1504. }
  1505. // On non-Firefox browsers, the selection will feel better if the height
  1506. // of the endOfContent div will be adjusted to start at mouse click
  1507. // location -- this will avoid flickering when selections moves up.
  1508. // However it does not work when selection started on empty space.
  1509. var adjustTop = e.target !== div;
  1510. if (adjustTop) {
  1511. var divBounds = div.getBoundingClientRect();
  1512. var r = Math.max(0, (e.pageY - divBounds.top) / divBounds.height);
  1513. end.style.top = (r * 100).toFixed(2) + '%';
  1514. }
  1515. end.classList.add('active');
  1516. });
  1517. div.addEventListener('mouseup', function (e) {
  1518. var end = div.querySelector('.endOfContent');
  1519. if (!end) {
  1520. return;
  1521. }
  1522. end.style.top = '';
  1523. end.classList.remove('active');
  1524. });
  1525. },
  1526. };
  1527. return TextLayerBuilder;
  1528. })();
  1529. /**
  1530. * @constructor
  1531. * @implements IPDFTextLayerFactory
  1532. */
  1533. function DefaultTextLayerFactory() {}
  1534. DefaultTextLayerFactory.prototype = {
  1535. /**
  1536. * @param {HTMLDivElement} textLayerDiv
  1537. * @param {number} pageIndex
  1538. * @param {PageViewport} viewport
  1539. * @returns {TextLayerBuilder}
  1540. */
  1541. createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
  1542. return new TextLayerBuilder({
  1543. textLayerDiv: textLayerDiv,
  1544. pageIndex: pageIndex,
  1545. viewport: viewport
  1546. });
  1547. }
  1548. };
  1549. /**
  1550. * @typedef {Object} AnnotationLayerBuilderOptions
  1551. * @property {HTMLDivElement} pageDiv
  1552. * @property {PDFPage} pdfPage
  1553. * @property {IPDFLinkService} linkService
  1554. * @property {DownloadManager} downloadManager
  1555. */
  1556. /**
  1557. * @class
  1558. */
  1559. var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() {
  1560. /**
  1561. * @param {AnnotationLayerBuilderOptions} options
  1562. * @constructs AnnotationLayerBuilder
  1563. */
  1564. function AnnotationLayerBuilder(options) {
  1565. this.pageDiv = options.pageDiv;
  1566. this.pdfPage = options.pdfPage;
  1567. this.linkService = options.linkService;
  1568. this.downloadManager = options.downloadManager;
  1569. this.div = null;
  1570. }
  1571. AnnotationLayerBuilder.prototype =
  1572. /** @lends AnnotationLayerBuilder.prototype */ {
  1573. /**
  1574. * @param {PageViewport} viewport
  1575. * @param {string} intent (default value is 'display')
  1576. */
  1577. render: function AnnotationLayerBuilder_render(viewport, intent) {
  1578. var self = this;
  1579. var parameters = {
  1580. intent: (intent === undefined ? 'display' : intent),
  1581. };
  1582. this.pdfPage.getAnnotations(parameters).then(function (annotations) {
  1583. viewport = viewport.clone({ dontFlip: true });
  1584. parameters = {
  1585. viewport: viewport,
  1586. div: self.div,
  1587. annotations: annotations,
  1588. page: self.pdfPage,
  1589. linkService: self.linkService,
  1590. downloadManager: self.downloadManager
  1591. };
  1592. if (self.div) {
  1593. // If an annotationLayer already exists, refresh its children's
  1594. // transformation matrices.
  1595. PDFJS.AnnotationLayer.update(parameters);
  1596. } else {
  1597. // Create an annotation layer div and render the annotations
  1598. // if there is at least one annotation.
  1599. if (annotations.length === 0) {
  1600. return;
  1601. }
  1602. self.div = document.createElement('div');
  1603. self.div.className = 'annotationLayer';
  1604. self.pageDiv.appendChild(self.div);
  1605. parameters.div = self.div;
  1606. PDFJS.AnnotationLayer.render(parameters);
  1607. if (typeof mozL10n !== 'undefined') {
  1608. mozL10n.translate(self.div);
  1609. }
  1610. }
  1611. });
  1612. },
  1613. hide: function AnnotationLayerBuilder_hide() {
  1614. if (!this.div) {
  1615. return;
  1616. }
  1617. this.div.setAttribute('hidden', 'true');
  1618. }
  1619. };
  1620. return AnnotationLayerBuilder;
  1621. })();
  1622. /**
  1623. * @constructor
  1624. * @implements IPDFAnnotationLayerFactory
  1625. */
  1626. function DefaultAnnotationLayerFactory() {}
  1627. DefaultAnnotationLayerFactory.prototype = {
  1628. /**
  1629. * @param {HTMLDivElement} pageDiv
  1630. * @param {PDFPage} pdfPage
  1631. * @returns {AnnotationLayerBuilder}
  1632. */
  1633. createAnnotationLayerBuilder: function (pageDiv, pdfPage) {
  1634. return new AnnotationLayerBuilder({
  1635. pageDiv: pageDiv,
  1636. pdfPage: pdfPage,
  1637. linkService: new SimpleLinkService(),
  1638. });
  1639. }
  1640. };
  1641. /**
  1642. * @typedef {Object} PDFViewerOptions
  1643. * @property {HTMLDivElement} container - The container for the viewer element.
  1644. * @property {HTMLDivElement} viewer - (optional) The viewer element.
  1645. * @property {IPDFLinkService} linkService - The navigation/linking service.
  1646. * @property {DownloadManager} downloadManager - (optional) The download
  1647. * manager component.
  1648. * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
  1649. * queue object.
  1650. * @property {boolean} removePageBorders - (optional) Removes the border shadow
  1651. * around the pages. The default is false.
  1652. */
  1653. /**
  1654. * Simple viewer control to display PDF content/pages.
  1655. * @class
  1656. * @implements {IRenderableView}
  1657. */
  1658. var PDFViewer = (function pdfViewer() {
  1659. function PDFPageViewBuffer(size) {
  1660. var data = [];
  1661. this.push = function cachePush(view) {
  1662. var i = data.indexOf(view);
  1663. if (i >= 0) {
  1664. data.splice(i, 1);
  1665. }
  1666. data.push(view);
  1667. if (data.length > size) {
  1668. data.shift().destroy();
  1669. }
  1670. };
  1671. this.resize = function (newSize) {
  1672. size = newSize;
  1673. while (data.length > size) {
  1674. data.shift().destroy();
  1675. }
  1676. };
  1677. }
  1678. function isSameScale(oldScale, newScale) {
  1679. if (newScale === oldScale) {
  1680. return true;
  1681. }
  1682. if (Math.abs(newScale - oldScale) < 1e-15) {
  1683. // Prevent unnecessary re-rendering of all pages when the scale
  1684. // changes only because of limited numerical precision.
  1685. return true;
  1686. }
  1687. return false;
  1688. }
  1689. /**
  1690. * @constructs PDFViewer
  1691. * @param {PDFViewerOptions} options
  1692. */
  1693. function PDFViewer(options) {
  1694. this.container = options.container;
  1695. this.viewer = options.viewer || options.container.firstElementChild;
  1696. this.linkService = options.linkService || new SimpleLinkService();
  1697. this.downloadManager = options.downloadManager || null;
  1698. this.removePageBorders = options.removePageBorders || false;
  1699. this.defaultRenderingQueue = !options.renderingQueue;
  1700. if (this.defaultRenderingQueue) {
  1701. // Custom rendering queue is not specified, using default one
  1702. this.renderingQueue = new PDFRenderingQueue();
  1703. this.renderingQueue.setViewer(this);
  1704. } else {
  1705. this.renderingQueue = options.renderingQueue;
  1706. }
  1707. this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
  1708. this.updateInProgress = false;
  1709. this.presentationModeState = PresentationModeState.UNKNOWN;
  1710. this._resetView();
  1711. if (this.removePageBorders) {
  1712. this.viewer.classList.add('removePageBorders');
  1713. }
  1714. }
  1715. PDFViewer.prototype = /** @lends PDFViewer.prototype */{
  1716. get pagesCount() {
  1717. return this._pages.length;
  1718. },
  1719. getPageView: function (index) {
  1720. return this._pages[index];
  1721. },
  1722. get currentPageNumber() {
  1723. return this._currentPageNumber;
  1724. },
  1725. set currentPageNumber(val) {
  1726. if (!this.pdfDocument) {
  1727. this._currentPageNumber = val;
  1728. return;
  1729. }
  1730. var event = document.createEvent('UIEvents');
  1731. event.initUIEvent('pagechange', true, true, window, 0);
  1732. event.updateInProgress = this.updateInProgress;
  1733. if (!(0 < val && val <= this.pagesCount)) {
  1734. event.pageNumber = this._currentPageNumber;
  1735. event.previousPageNumber = val;
  1736. this.container.dispatchEvent(event);
  1737. return;
  1738. }
  1739. event.previousPageNumber = this._currentPageNumber;
  1740. this._currentPageNumber = val;
  1741. event.pageNumber = val;
  1742. this.container.dispatchEvent(event);
  1743. // Check if the caller is `PDFViewer_update`, to avoid breaking scrolling.
  1744. if (this.updateInProgress) {
  1745. return;
  1746. }
  1747. this.scrollPageIntoView(val);
  1748. },
  1749. /**
  1750. * @returns {number}
  1751. */
  1752. get currentScale() {
  1753. return this._currentScale !== UNKNOWN_SCALE ? this._currentScale :
  1754. DEFAULT_SCALE;
  1755. },
  1756. /**
  1757. * @param {number} val - Scale of the pages in percents.
  1758. */
  1759. set currentScale(val) {
  1760. if (isNaN(val)) {
  1761. throw new Error('Invalid numeric scale');
  1762. }
  1763. if (!this.pdfDocument) {
  1764. this._currentScale = val;
  1765. this._currentScaleValue = val !== UNKNOWN_SCALE ? val.toString() : null;
  1766. return;
  1767. }
  1768. this._setScale(val, false);
  1769. },
  1770. /**
  1771. * @returns {string}
  1772. */
  1773. get currentScaleValue() {
  1774. return this._currentScaleValue;
  1775. },
  1776. /**
  1777. * @param val - The scale of the pages (in percent or predefined value).
  1778. */
  1779. set currentScaleValue(val) {
  1780. if (!this.pdfDocument) {
  1781. this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
  1782. this._currentScaleValue = val;
  1783. return;
  1784. }
  1785. this._setScale(val, false);
  1786. },
  1787. /**
  1788. * @returns {number}
  1789. */
  1790. get pagesRotation() {
  1791. return this._pagesRotation;
  1792. },
  1793. /**
  1794. * @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
  1795. */
  1796. set pagesRotation(rotation) {
  1797. this._pagesRotation = rotation;
  1798. for (var i = 0, l = this._pages.length; i < l; i++) {
  1799. var pageView = this._pages[i];
  1800. pageView.update(pageView.scale, rotation);
  1801. }
  1802. this._setScale(this._currentScaleValue, true);
  1803. if (this.defaultRenderingQueue) {
  1804. this.update();
  1805. }
  1806. },
  1807. /**
  1808. * @param pdfDocument {PDFDocument}
  1809. */
  1810. setDocument: function (pdfDocument) {
  1811. if (this.pdfDocument) {
  1812. this._resetView();
  1813. }
  1814. this.pdfDocument = pdfDocument;
  1815. if (!pdfDocument) {
  1816. return;
  1817. }
  1818. var pagesCount = pdfDocument.numPages;
  1819. var self = this;
  1820. var resolvePagesPromise;
  1821. var pagesPromise = new Promise(function (resolve) {
  1822. resolvePagesPromise = resolve;
  1823. });
  1824. this.pagesPromise = pagesPromise;
  1825. pagesPromise.then(function () {
  1826. var event = document.createEvent('CustomEvent');
  1827. event.initCustomEvent('pagesloaded', true, true, {
  1828. pagesCount: pagesCount
  1829. });
  1830. self.container.dispatchEvent(event);
  1831. });
  1832. var isOnePageRenderedResolved = false;
  1833. var resolveOnePageRendered = null;
  1834. var onePageRendered = new Promise(function (resolve) {
  1835. resolveOnePageRendered = resolve;
  1836. });
  1837. this.onePageRendered = onePageRendered;
  1838. var bindOnAfterAndBeforeDraw = function (pageView) {
  1839. pageView.onBeforeDraw = function pdfViewLoadOnBeforeDraw() {
  1840. // Add the page to the buffer at the start of drawing. That way it can
  1841. // be evicted from the buffer and destroyed even if we pause its
  1842. // rendering.
  1843. self._buffer.push(this);
  1844. };
  1845. // when page is painted, using the image as thumbnail base
  1846. pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
  1847. if (!isOnePageRenderedResolved) {
  1848. isOnePageRenderedResolved = true;
  1849. resolveOnePageRendered();
  1850. }
  1851. };
  1852. };
  1853. var firstPagePromise = pdfDocument.getPage(1);
  1854. this.firstPagePromise = firstPagePromise;
  1855. // Fetch a single page so we can get a viewport that will be the default
  1856. // viewport for all pages
  1857. return firstPagePromise.then(function(pdfPage) {
  1858. var scale = this.currentScale;
  1859. var viewport = pdfPage.getViewport(scale * CSS_UNITS);
  1860. for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
  1861. var textLayerFactory = null;
  1862. if (!PDFJS.disableTextLayer) {
  1863. textLayerFactory = this;
  1864. }
  1865. var pageView = new PDFPageView({
  1866. container: this.viewer,
  1867. id: pageNum,
  1868. scale: scale,
  1869. defaultViewport: viewport.clone(),
  1870. renderingQueue: this.renderingQueue,
  1871. textLayerFactory: textLayerFactory,
  1872. annotationLayerFactory: this
  1873. });
  1874. bindOnAfterAndBeforeDraw(pageView);
  1875. this._pages.push(pageView);
  1876. }
  1877. var linkService = this.linkService;
  1878. // Fetch all the pages since the viewport is needed before printing
  1879. // starts to create the correct size canvas. Wait until one page is
  1880. // rendered so we don't tie up too many resources early on.
  1881. onePageRendered.then(function () {
  1882. if (!PDFJS.disableAutoFetch) {
  1883. var getPagesLeft = pagesCount;
  1884. for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
  1885. pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
  1886. var pageView = self._pages[pageNum - 1];
  1887. if (!pageView.pdfPage) {
  1888. pageView.setPdfPage(pdfPage);
  1889. }
  1890. linkService.cachePageRef(pageNum, pdfPage.ref);
  1891. getPagesLeft--;
  1892. if (!getPagesLeft) {
  1893. resolvePagesPromise();
  1894. }
  1895. }.bind(null, pageNum));
  1896. }
  1897. } else {
  1898. // XXX: Printing is semi-broken with auto fetch disabled.
  1899. resolvePagesPromise();
  1900. }
  1901. });
  1902. var event = document.createEvent('CustomEvent');
  1903. event.initCustomEvent('pagesinit', true, true, null);
  1904. self.container.dispatchEvent(event);
  1905. if (this.defaultRenderingQueue) {
  1906. this.update();
  1907. }
  1908. if (this.findController) {
  1909. this.findController.resolveFirstPage();
  1910. }
  1911. }.bind(this));
  1912. },
  1913. _resetView: function () {
  1914. this._pages = [];
  1915. this._currentPageNumber = 1;
  1916. this._currentScale = UNKNOWN_SCALE;
  1917. this._currentScaleValue = null;
  1918. this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
  1919. this._location = null;
  1920. this._pagesRotation = 0;
  1921. this._pagesRequests = [];
  1922. var container = this.viewer;
  1923. while (container.hasChildNodes()) {
  1924. container.removeChild(container.lastChild);
  1925. }
  1926. },
  1927. _scrollUpdate: function PDFViewer_scrollUpdate() {
  1928. if (this.pagesCount === 0) {
  1929. return;
  1930. }
  1931. this.update();
  1932. for (var i = 0, ii = this._pages.length; i < ii; i++) {
  1933. this._pages[i].updatePosition();
  1934. }
  1935. },
  1936. _setScaleDispatchEvent: function pdfViewer_setScaleDispatchEvent(
  1937. newScale, newValue, preset) {
  1938. var event = document.createEvent('UIEvents');
  1939. event.initUIEvent('scalechange', true, true, window, 0);
  1940. event.scale = newScale;
  1941. if (preset) {
  1942. event.presetValue = newValue;
  1943. }
  1944. this.container.dispatchEvent(event);
  1945. },
  1946. _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
  1947. newScale, newValue, noScroll, preset) {
  1948. this._currentScaleValue = newValue;
  1949. if (isSameScale(this._currentScale, newScale)) {
  1950. if (preset) {
  1951. this._setScaleDispatchEvent(newScale, newValue, true);
  1952. }
  1953. return;
  1954. }
  1955. for (var i = 0, ii = this._pages.length; i < ii; i++) {
  1956. this._pages[i].update(newScale);
  1957. }
  1958. this._currentScale = newScale;
  1959. if (!noScroll) {
  1960. var page = this._currentPageNumber, dest;
  1961. if (this._location && !IGNORE_CURRENT_POSITION_ON_ZOOM &&
  1962. !(this.isInPresentationMode || this.isChangingPresentationMode)) {
  1963. page = this._location.pageNumber;
  1964. dest = [null, { name: 'XYZ' }, this._location.left,
  1965. this._location.top, null];
  1966. }
  1967. this.scrollPageIntoView(page, dest);
  1968. }
  1969. this._setScaleDispatchEvent(newScale, newValue, preset);
  1970. if (this.defaultRenderingQueue) {
  1971. this.update();
  1972. }
  1973. },
  1974. _setScale: function pdfViewer_setScale(value, noScroll) {
  1975. var scale = parseFloat(value);
  1976. if (scale > 0) {
  1977. this._setScaleUpdatePages(scale, value, noScroll, false);
  1978. } else {
  1979. var currentPage = this._pages[this._currentPageNumber - 1];
  1980. if (!currentPage) {
  1981. return;
  1982. }
  1983. var hPadding = (this.isInPresentationMode || this.removePageBorders) ?
  1984. 0 : SCROLLBAR_PADDING;
  1985. var vPadding = (this.isInPresentationMode || this.removePageBorders) ?
  1986. 0 : VERTICAL_PADDING;
  1987. var pageWidthScale = (this.container.clientWidth - hPadding) /
  1988. currentPage.width * currentPage.scale;
  1989. var pageHeightScale = (this.container.clientHeight - vPadding) /
  1990. currentPage.height * currentPage.scale;
  1991. switch (value) {
  1992. case 'page-actual':
  1993. scale = 1;
  1994. break;
  1995. case 'page-width':
  1996. scale = pageWidthScale;
  1997. break;
  1998. case 'page-height':
  1999. scale = pageHeightScale;
  2000. break;
  2001. case 'page-fit':
  2002. scale = Math.min(pageWidthScale, pageHeightScale);
  2003. break;
  2004. case 'auto':
  2005. var isLandscape = (currentPage.width > currentPage.height);
  2006. // For pages in landscape mode, fit the page height to the viewer
  2007. // *unless* the page would thus become too wide to fit horizontally.
  2008. var horizontalScale = isLandscape ?
  2009. Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
  2010. scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
  2011. break;
  2012. default:
  2013. console.error('pdfViewSetScale: \'' + value +
  2014. '\' is an unknown zoom value.');
  2015. return;
  2016. }
  2017. this._setScaleUpdatePages(scale, value, noScroll, true);
  2018. }
  2019. },
  2020. /**
  2021. * Scrolls page into view.
  2022. * @param {number} pageNumber
  2023. * @param {Array} dest - (optional) original PDF destination array:
  2024. * <page-ref> </XYZ|FitXXX> <args..>
  2025. */
  2026. scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
  2027. dest) {
  2028. if (!this.pdfDocument) {
  2029. return;
  2030. }
  2031. var pageView = this._pages[pageNumber - 1];
  2032. if (this.isInPresentationMode) {
  2033. if (this._currentPageNumber !== pageView.id) {
  2034. // Avoid breaking getVisiblePages in presentation mode.
  2035. this.currentPageNumber = pageView.id;
  2036. return;
  2037. }
  2038. dest = null;
  2039. // Fixes the case when PDF has different page sizes.
  2040. this._setScale(this._currentScaleValue, true);
  2041. }
  2042. if (!dest) {
  2043. scrollIntoView(pageView.div);
  2044. return;
  2045. }
  2046. var x = 0, y = 0;
  2047. var width = 0, height = 0, widthScale, heightScale;
  2048. var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
  2049. var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
  2050. pageView.scale / CSS_UNITS;
  2051. var pageHeight = (changeOrientation ? pageView.width : pageView.height) /
  2052. pageView.scale / CSS_UNITS;
  2053. var scale = 0;
  2054. switch (dest[1].name) {
  2055. case 'XYZ':
  2056. x = dest[2];
  2057. y = dest[3];
  2058. scale = dest[4];
  2059. // If x and/or y coordinates are not supplied, default to
  2060. // _top_ left of the page (not the obvious bottom left,
  2061. // since aligning the bottom of the intended page with the
  2062. // top of the window is rarely helpful).
  2063. x = x !== null ? x : 0;
  2064. y = y !== null ? y : pageHeight;
  2065. break;
  2066. case 'Fit':
  2067. case 'FitB':
  2068. scale = 'page-fit';
  2069. break;
  2070. case 'FitH':
  2071. case 'FitBH':
  2072. y = dest[2];
  2073. scale = 'page-width';
  2074. // According to the PDF spec, section 12.3.2.2, a `null` value in the
  2075. // parameter should maintain the position relative to the new page.
  2076. if (y === null && this._location) {
  2077. x = this._location.left;
  2078. y = this._location.top;
  2079. }
  2080. break;
  2081. case 'FitV':
  2082. case 'FitBV':
  2083. x = dest[2];
  2084. width = pageWidth;
  2085. height = pageHeight;
  2086. scale = 'page-height';
  2087. break;
  2088. case 'FitR':
  2089. x = dest[2];
  2090. y = dest[3];
  2091. width = dest[4] - x;
  2092. height = dest[5] - y;
  2093. var hPadding = this.removePageBorders ? 0 : SCROLLBAR_PADDING;
  2094. var vPadding = this.removePageBorders ? 0 : VERTICAL_PADDING;
  2095. widthScale = (this.container.clientWidth - hPadding) /
  2096. width / CSS_UNITS;
  2097. heightScale = (this.container.clientHeight - vPadding) /
  2098. height / CSS_UNITS;
  2099. scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
  2100. break;
  2101. default:
  2102. return;
  2103. }
  2104. if (scale && scale !== this._currentScale) {
  2105. this.currentScaleValue = scale;
  2106. } else if (this._currentScale === UNKNOWN_SCALE) {
  2107. this.currentScaleValue = DEFAULT_SCALE_VALUE;
  2108. }
  2109. if (scale === 'page-fit' && !dest[4]) {
  2110. scrollIntoView(pageView.div);
  2111. return;
  2112. }
  2113. var boundingRect = [
  2114. pageView.viewport.convertToViewportPoint(x, y),
  2115. pageView.viewport.convertToViewportPoint(x + width, y + height)
  2116. ];
  2117. var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
  2118. var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
  2119. scrollIntoView(pageView.div, { left: left, top: top });
  2120. },
  2121. _updateLocation: function (firstPage) {
  2122. var currentScale = this._currentScale;
  2123. var currentScaleValue = this._currentScaleValue;
  2124. var normalizedScaleValue =
  2125. parseFloat(currentScaleValue) === currentScale ?
  2126. Math.round(currentScale * 10000) / 100 : currentScaleValue;
  2127. var pageNumber = firstPage.id;
  2128. var pdfOpenParams = '#page=' + pageNumber;
  2129. pdfOpenParams += '&zoom=' + normalizedScaleValue;
  2130. var currentPageView = this._pages[pageNumber - 1];
  2131. var container = this.container;
  2132. var topLeft = currentPageView.getPagePoint(
  2133. (container.scrollLeft - firstPage.x),
  2134. (container.scrollTop - firstPage.y));
  2135. var intLeft = Math.round(topLeft[0]);
  2136. var intTop = Math.round(topLeft[1]);
  2137. pdfOpenParams += ',' + intLeft + ',' + intTop;
  2138. this._location = {
  2139. pageNumber: pageNumber,
  2140. scale: normalizedScaleValue,
  2141. top: intTop,
  2142. left: intLeft,
  2143. pdfOpenParams: pdfOpenParams
  2144. };
  2145. },
  2146. update: function PDFViewer_update() {
  2147. var visible = this._getVisiblePages();
  2148. var visiblePages = visible.views;
  2149. if (visiblePages.length === 0) {
  2150. return;
  2151. }
  2152. this.updateInProgress = true;
  2153. var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
  2154. 2 * visiblePages.length + 1);
  2155. this._buffer.resize(suggestedCacheSize);
  2156. this.renderingQueue.renderHighestPriority(visible);
  2157. var currentId = this._currentPageNumber;
  2158. var firstPage = visible.first;
  2159. for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
  2160. i < ii; ++i) {
  2161. var page = visiblePages[i];
  2162. if (page.percent < 100) {
  2163. break;
  2164. }
  2165. if (page.id === currentId) {
  2166. stillFullyVisible = true;
  2167. break;
  2168. }
  2169. }
  2170. if (!stillFullyVisible) {
  2171. currentId = visiblePages[0].id;
  2172. }
  2173. if (!this.isInPresentationMode) {
  2174. this.currentPageNumber = currentId;
  2175. }
  2176. this._updateLocation(firstPage);
  2177. this.updateInProgress = false;
  2178. var event = document.createEvent('UIEvents');
  2179. event.initUIEvent('updateviewarea', true, true, window, 0);
  2180. event.location = this._location;
  2181. this.container.dispatchEvent(event);
  2182. },
  2183. containsElement: function (element) {
  2184. return this.container.contains(element);
  2185. },
  2186. focus: function () {
  2187. this.container.focus();
  2188. },
  2189. get isInPresentationMode() {
  2190. return this.presentationModeState === PresentationModeState.FULLSCREEN;
  2191. },
  2192. get isChangingPresentationMode() {
  2193. return this.presentationModeState === PresentationModeState.CHANGING;
  2194. },
  2195. get isHorizontalScrollbarEnabled() {
  2196. return (this.isInPresentationMode ?
  2197. false : (this.container.scrollWidth > this.container.clientWidth));
  2198. },
  2199. _getVisiblePages: function () {
  2200. if (!this.isInPresentationMode) {
  2201. return getVisibleElements(this.container, this._pages, true);
  2202. } else {
  2203. // The algorithm in getVisibleElements doesn't work in all browsers and
  2204. // configurations when presentation mode is active.
  2205. var visible = [];
  2206. var currentPage = this._pages[this._currentPageNumber - 1];
  2207. visible.push({ id: currentPage.id, view: currentPage });
  2208. return { first: currentPage, last: currentPage, views: visible };
  2209. }
  2210. },
  2211. cleanup: function () {
  2212. for (var i = 0, ii = this._pages.length; i < ii; i++) {
  2213. if (this._pages[i] &&
  2214. this._pages[i].renderingState !== RenderingStates.FINISHED) {
  2215. this._pages[i].reset();
  2216. }
  2217. }
  2218. },
  2219. /**
  2220. * @param {PDFPageView} pageView
  2221. * @returns {PDFPage}
  2222. * @private
  2223. */
  2224. _ensurePdfPageLoaded: function (pageView) {
  2225. if (pageView.pdfPage) {
  2226. return Promise.resolve(pageView.pdfPage);
  2227. }
  2228. var pageNumber = pageView.id;
  2229. if (this._pagesRequests[pageNumber]) {
  2230. return this._pagesRequests[pageNumber];
  2231. }
  2232. var promise = this.pdfDocument.getPage(pageNumber).then(
  2233. function (pdfPage) {
  2234. pageView.setPdfPage(pdfPage);
  2235. this._pagesRequests[pageNumber] = null;
  2236. return pdfPage;
  2237. }.bind(this));
  2238. this._pagesRequests[pageNumber] = promise;
  2239. return promise;
  2240. },
  2241. forceRendering: function (currentlyVisiblePages) {
  2242. var visiblePages = currentlyVisiblePages || this._getVisiblePages();
  2243. var pageView = this.renderingQueue.getHighestPriority(visiblePages,
  2244. this._pages,
  2245. this.scroll.down);
  2246. if (pageView) {
  2247. this._ensurePdfPageLoaded(pageView).then(function () {
  2248. this.renderingQueue.renderView(pageView);
  2249. }.bind(this));
  2250. return true;
  2251. }
  2252. return false;
  2253. },
  2254. getPageTextContent: function (pageIndex) {
  2255. return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
  2256. return page.getTextContent({ normalizeWhitespace: true });
  2257. });
  2258. },
  2259. /**
  2260. * @param {HTMLDivElement} textLayerDiv
  2261. * @param {number} pageIndex
  2262. * @param {PageViewport} viewport
  2263. * @returns {TextLayerBuilder}
  2264. */
  2265. createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
  2266. return new TextLayerBuilder({
  2267. textLayerDiv: textLayerDiv,
  2268. pageIndex: pageIndex,
  2269. viewport: viewport,
  2270. findController: this.isInPresentationMode ? null : this.findController
  2271. });
  2272. },
  2273. /**
  2274. * @param {HTMLDivElement} pageDiv
  2275. * @param {PDFPage} pdfPage
  2276. * @returns {AnnotationLayerBuilder}
  2277. */
  2278. createAnnotationLayerBuilder: function (pageDiv, pdfPage) {
  2279. return new AnnotationLayerBuilder({
  2280. pageDiv: pageDiv,
  2281. pdfPage: pdfPage,
  2282. linkService: this.linkService,
  2283. downloadManager: this.downloadManager
  2284. });
  2285. },
  2286. setFindController: function (findController) {
  2287. this.findController = findController;
  2288. },
  2289. };
  2290. return PDFViewer;
  2291. })();
  2292. var SimpleLinkService = (function SimpleLinkServiceClosure() {
  2293. function SimpleLinkService() {}
  2294. SimpleLinkService.prototype = {
  2295. /**
  2296. * @returns {number}
  2297. */
  2298. get page() {
  2299. return 0;
  2300. },
  2301. /**
  2302. * @param {number} value
  2303. */
  2304. set page(value) {},
  2305. /**
  2306. * @param dest - The PDF destination object.
  2307. */
  2308. navigateTo: function (dest) {},
  2309. /**
  2310. * @param dest - The PDF destination object.
  2311. * @returns {string} The hyperlink to the PDF object.
  2312. */
  2313. getDestinationHash: function (dest) {
  2314. return '#';
  2315. },
  2316. /**
  2317. * @param hash - The PDF parameters/hash.
  2318. * @returns {string} The hyperlink to the PDF object.
  2319. */
  2320. getAnchorUrl: function (hash) {
  2321. return '#';
  2322. },
  2323. /**
  2324. * @param {string} hash
  2325. */
  2326. setHash: function (hash) {},
  2327. /**
  2328. * @param {string} action
  2329. */
  2330. executeNamedAction: function (action) {},
  2331. /**
  2332. * @param {number} pageNum - page number.
  2333. * @param {Object} pageRef - reference to the page.
  2334. */
  2335. cachePageRef: function (pageNum, pageRef) {}
  2336. };
  2337. return SimpleLinkService;
  2338. })();
  2339. var PDFHistory = (function () {
  2340. function PDFHistory(options) {
  2341. this.linkService = options.linkService;
  2342. this.initialized = false;
  2343. this.initialDestination = null;
  2344. this.initialBookmark = null;
  2345. }
  2346. PDFHistory.prototype = {
  2347. /**
  2348. * @param {string} fingerprint
  2349. * @param {IPDFLinkService} linkService
  2350. */
  2351. initialize: function pdfHistoryInitialize(fingerprint) {
  2352. this.initialized = true;
  2353. this.reInitialized = false;
  2354. this.allowHashChange = true;
  2355. this.historyUnlocked = true;
  2356. this.isViewerInPresentationMode = false;
  2357. this.previousHash = window.location.hash.substring(1);
  2358. this.currentBookmark = '';
  2359. this.currentPage = 0;
  2360. this.updatePreviousBookmark = false;
  2361. this.previousBookmark = '';
  2362. this.previousPage = 0;
  2363. this.nextHashParam = '';
  2364. this.fingerprint = fingerprint;
  2365. this.currentUid = this.uid = 0;
  2366. this.current = {};
  2367. var state = window.history.state;
  2368. if (this._isStateObjectDefined(state)) {
  2369. // This corresponds to navigating back to the document
  2370. // from another page in the browser history.
  2371. if (state.target.dest) {
  2372. this.initialDestination = state.target.dest;
  2373. } else {
  2374. this.initialBookmark = state.target.hash;
  2375. }
  2376. this.currentUid = state.uid;
  2377. this.uid = state.uid + 1;
  2378. this.current = state.target;
  2379. } else {
  2380. // This corresponds to the loading of a new document.
  2381. if (state && state.fingerprint &&
  2382. this.fingerprint !== state.fingerprint) {
  2383. // Reinitialize the browsing history when a new document
  2384. // is opened in the web viewer.
  2385. this.reInitialized = true;
  2386. }
  2387. this._pushOrReplaceState({fingerprint: this.fingerprint}, true);
  2388. }
  2389. var self = this;
  2390. window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
  2391. if (!self.historyUnlocked) {
  2392. return;
  2393. }
  2394. if (evt.state) {
  2395. // Move back/forward in the history.
  2396. self._goTo(evt.state);
  2397. return;
  2398. }
  2399. // If the state is not set, then the user tried to navigate to a
  2400. // different hash by manually editing the URL and pressing Enter, or by
  2401. // clicking on an in-page link (e.g. the "current view" link).
  2402. // Save the current view state to the browser history.
  2403. // Note: In Firefox, history.null could also be null after an in-page
  2404. // navigation to the same URL, and without dispatching the popstate
  2405. // event: https://bugzilla.mozilla.org/show_bug.cgi?id=1183881
  2406. if (self.uid === 0) {
  2407. // Replace the previous state if it was not explicitly set.
  2408. var previousParams = (self.previousHash && self.currentBookmark &&
  2409. self.previousHash !== self.currentBookmark) ?
  2410. {hash: self.currentBookmark, page: self.currentPage} :
  2411. {page: 1};
  2412. replacePreviousHistoryState(previousParams, function() {
  2413. updateHistoryWithCurrentHash();
  2414. });
  2415. } else {
  2416. updateHistoryWithCurrentHash();
  2417. }
  2418. }, false);
  2419. function updateHistoryWithCurrentHash() {
  2420. self.previousHash = window.location.hash.slice(1);
  2421. self._pushToHistory({hash: self.previousHash}, false, true);
  2422. self._updatePreviousBookmark();
  2423. }
  2424. function replacePreviousHistoryState(params, callback) {
  2425. // To modify the previous history entry, the following happens:
  2426. // 1. history.back()
  2427. // 2. _pushToHistory, which calls history.replaceState( ... )
  2428. // 3. history.forward()
  2429. // Because a navigation via the history API does not immediately update
  2430. // the history state, the popstate event is used for synchronization.
  2431. self.historyUnlocked = false;
  2432. // Suppress the hashchange event to avoid side effects caused by
  2433. // navigating back and forward.
  2434. self.allowHashChange = false;
  2435. window.addEventListener('popstate', rewriteHistoryAfterBack);
  2436. history.back();
  2437. function rewriteHistoryAfterBack() {
  2438. window.removeEventListener('popstate', rewriteHistoryAfterBack);
  2439. window.addEventListener('popstate', rewriteHistoryAfterForward);
  2440. self._pushToHistory(params, false, true);
  2441. history.forward();
  2442. }
  2443. function rewriteHistoryAfterForward() {
  2444. window.removeEventListener('popstate', rewriteHistoryAfterForward);
  2445. self.allowHashChange = true;
  2446. self.historyUnlocked = true;
  2447. callback();
  2448. }
  2449. }
  2450. function pdfHistoryBeforeUnload() {
  2451. var previousParams = self._getPreviousParams(null, true);
  2452. if (previousParams) {
  2453. var replacePrevious = (!self.current.dest &&
  2454. self.current.hash !== self.previousHash);
  2455. self._pushToHistory(previousParams, false, replacePrevious);
  2456. self._updatePreviousBookmark();
  2457. }
  2458. // Remove the event listener when navigating away from the document,
  2459. // since 'beforeunload' prevents Firefox from caching the document.
  2460. window.removeEventListener('beforeunload', pdfHistoryBeforeUnload,
  2461. false);
  2462. }
  2463. window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
  2464. window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
  2465. // If the entire viewer (including the PDF file) is cached in
  2466. // the browser, we need to reattach the 'beforeunload' event listener
  2467. // since the 'DOMContentLoaded' event is not fired on 'pageshow'.
  2468. window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
  2469. }, false);
  2470. window.addEventListener('presentationmodechanged', function(e) {
  2471. self.isViewerInPresentationMode = !!e.detail.active;
  2472. });
  2473. },
  2474. clearHistoryState: function pdfHistory_clearHistoryState() {
  2475. this._pushOrReplaceState(null, true);
  2476. },
  2477. _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
  2478. return (state && state.uid >= 0 &&
  2479. state.fingerprint && this.fingerprint === state.fingerprint &&
  2480. state.target && state.target.hash) ? true : false;
  2481. },
  2482. _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
  2483. replace) {
  2484. if (replace) {
  2485. window.history.replaceState(stateObj, '');
  2486. } else {
  2487. window.history.pushState(stateObj, '');
  2488. }
  2489. },
  2490. get isHashChangeUnlocked() {
  2491. if (!this.initialized) {
  2492. return true;
  2493. }
  2494. return this.allowHashChange;
  2495. },
  2496. _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
  2497. if (this.updatePreviousBookmark &&
  2498. this.currentBookmark && this.currentPage) {
  2499. this.previousBookmark = this.currentBookmark;
  2500. this.previousPage = this.currentPage;
  2501. this.updatePreviousBookmark = false;
  2502. }
  2503. },
  2504. updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
  2505. pageNum) {
  2506. if (this.initialized) {
  2507. this.currentBookmark = bookmark.substring(1);
  2508. this.currentPage = pageNum | 0;
  2509. this._updatePreviousBookmark();
  2510. }
  2511. },
  2512. updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
  2513. if (this.initialized) {
  2514. this.nextHashParam = param;
  2515. }
  2516. },
  2517. push: function pdfHistoryPush(params, isInitialBookmark) {
  2518. if (!(this.initialized && this.historyUnlocked)) {
  2519. return;
  2520. }
  2521. if (params.dest && !params.hash) {
  2522. params.hash = (this.current.hash && this.current.dest &&
  2523. this.current.dest === params.dest) ?
  2524. this.current.hash :
  2525. this.linkService.getDestinationHash(params.dest).split('#')[1];
  2526. }
  2527. if (params.page) {
  2528. params.page |= 0;
  2529. }
  2530. if (isInitialBookmark) {
  2531. var target = window.history.state.target;
  2532. if (!target) {
  2533. // Invoked when the user specifies an initial bookmark,
  2534. // thus setting initialBookmark, when the document is loaded.
  2535. this._pushToHistory(params, false);
  2536. this.previousHash = window.location.hash.substring(1);
  2537. }
  2538. this.updatePreviousBookmark = this.nextHashParam ? false : true;
  2539. if (target) {
  2540. // If the current document is reloaded,
  2541. // avoid creating duplicate entries in the history.
  2542. this._updatePreviousBookmark();
  2543. }
  2544. return;
  2545. }
  2546. if (this.nextHashParam) {
  2547. if (this.nextHashParam === params.hash) {
  2548. this.nextHashParam = null;
  2549. this.updatePreviousBookmark = true;
  2550. return;
  2551. } else {
  2552. this.nextHashParam = null;
  2553. }
  2554. }
  2555. if (params.hash) {
  2556. if (this.current.hash) {
  2557. if (this.current.hash !== params.hash) {
  2558. this._pushToHistory(params, true);
  2559. } else {
  2560. if (!this.current.page && params.page) {
  2561. this._pushToHistory(params, false, true);
  2562. }
  2563. this.updatePreviousBookmark = true;
  2564. }
  2565. } else {
  2566. this._pushToHistory(params, true);
  2567. }
  2568. } else if (this.current.page && params.page &&
  2569. this.current.page !== params.page) {
  2570. this._pushToHistory(params, true);
  2571. }
  2572. },
  2573. _getPreviousParams: function pdfHistory_getPreviousParams(onlyCheckPage,
  2574. beforeUnload) {
  2575. if (!(this.currentBookmark && this.currentPage)) {
  2576. return null;
  2577. } else if (this.updatePreviousBookmark) {
  2578. this.updatePreviousBookmark = false;
  2579. }
  2580. if (this.uid > 0 && !(this.previousBookmark && this.previousPage)) {
  2581. // Prevent the history from getting stuck in the current state,
  2582. // effectively preventing the user from going back/forward in
  2583. // the history.
  2584. //
  2585. // This happens if the current position in the document didn't change
  2586. // when the history was previously updated. The reasons for this are
  2587. // either:
  2588. // 1. The current zoom value is such that the document does not need to,
  2589. // or cannot, be scrolled to display the destination.
  2590. // 2. The previous destination is broken, and doesn't actally point to a
  2591. // position within the document.
  2592. // (This is either due to a bad PDF generator, or the user making a
  2593. // mistake when entering a destination in the hash parameters.)
  2594. return null;
  2595. }
  2596. if ((!this.current.dest && !onlyCheckPage) || beforeUnload) {
  2597. if (this.previousBookmark === this.currentBookmark) {
  2598. return null;
  2599. }
  2600. } else if (this.current.page || onlyCheckPage) {
  2601. if (this.previousPage === this.currentPage) {
  2602. return null;
  2603. }
  2604. } else {
  2605. return null;
  2606. }
  2607. var params = {hash: this.currentBookmark, page: this.currentPage};
  2608. if (this.isViewerInPresentationMode) {
  2609. params.hash = null;
  2610. }
  2611. return params;
  2612. },
  2613. _stateObj: function pdfHistory_stateObj(params) {
  2614. return {fingerprint: this.fingerprint, uid: this.uid, target: params};
  2615. },
  2616. _pushToHistory: function pdfHistory_pushToHistory(params,
  2617. addPrevious, overwrite) {
  2618. if (!this.initialized) {
  2619. return;
  2620. }
  2621. if (!params.hash && params.page) {
  2622. params.hash = ('page=' + params.page);
  2623. }
  2624. if (addPrevious && !overwrite) {
  2625. var previousParams = this._getPreviousParams();
  2626. if (previousParams) {
  2627. var replacePrevious = (!this.current.dest &&
  2628. this.current.hash !== this.previousHash);
  2629. this._pushToHistory(previousParams, false, replacePrevious);
  2630. }
  2631. }
  2632. this._pushOrReplaceState(this._stateObj(params),
  2633. (overwrite || this.uid === 0));
  2634. this.currentUid = this.uid++;
  2635. this.current = params;
  2636. this.updatePreviousBookmark = true;
  2637. },
  2638. _goTo: function pdfHistory_goTo(state) {
  2639. if (!(this.initialized && this.historyUnlocked &&
  2640. this._isStateObjectDefined(state))) {
  2641. return;
  2642. }
  2643. if (!this.reInitialized && state.uid < this.currentUid) {
  2644. var previousParams = this._getPreviousParams(true);
  2645. if (previousParams) {
  2646. this._pushToHistory(this.current, false);
  2647. this._pushToHistory(previousParams, false);
  2648. this.currentUid = state.uid;
  2649. window.history.back();
  2650. return;
  2651. }
  2652. }
  2653. this.historyUnlocked = false;
  2654. if (state.target.dest) {
  2655. this.linkService.navigateTo(state.target.dest);
  2656. } else {
  2657. this.linkService.setHash(state.target.hash);
  2658. }
  2659. this.currentUid = state.uid;
  2660. if (state.uid > this.uid) {
  2661. this.uid = state.uid;
  2662. }
  2663. this.current = state.target;
  2664. this.updatePreviousBookmark = true;
  2665. var currentHash = window.location.hash.substring(1);
  2666. if (this.previousHash !== currentHash) {
  2667. this.allowHashChange = false;
  2668. }
  2669. this.previousHash = currentHash;
  2670. this.historyUnlocked = true;
  2671. },
  2672. back: function pdfHistoryBack() {
  2673. this.go(-1);
  2674. },
  2675. forward: function pdfHistoryForward() {
  2676. this.go(1);
  2677. },
  2678. go: function pdfHistoryGo(direction) {
  2679. if (this.initialized && this.historyUnlocked) {
  2680. var state = window.history.state;
  2681. if (direction === -1 && state && state.uid > 0) {
  2682. window.history.back();
  2683. } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
  2684. window.history.forward();
  2685. }
  2686. }
  2687. }
  2688. };
  2689. return PDFHistory;
  2690. })();
  2691. var DownloadManager = (function DownloadManagerClosure() {
  2692. function download(blobUrl, filename) {
  2693. var a = document.createElement('a');
  2694. if (a.click) {
  2695. // Use a.click() if available. Otherwise, Chrome might show
  2696. // "Unsafe JavaScript attempt to initiate a navigation change
  2697. // for frame with URL" and not open the PDF at all.
  2698. // Supported by (not mentioned = untested):
  2699. // - Firefox 6 - 19 (4- does not support a.click, 5 ignores a.click)
  2700. // - Chrome 19 - 26 (18- does not support a.click)
  2701. // - Opera 9 - 12.15
  2702. // - Internet Explorer 6 - 10
  2703. // - Safari 6 (5.1- does not support a.click)
  2704. a.href = blobUrl;
  2705. a.target = '_parent';
  2706. // Use a.download if available. This increases the likelihood that
  2707. // the file is downloaded instead of opened by another PDF plugin.
  2708. if ('download' in a) {
  2709. a.download = filename;
  2710. }
  2711. // <a> must be in the document for IE and recent Firefox versions.
  2712. // (otherwise .click() is ignored)
  2713. (document.body || document.documentElement).appendChild(a);
  2714. a.click();
  2715. a.parentNode.removeChild(a);
  2716. } else {
  2717. if (window.top === window &&
  2718. blobUrl.split('#')[0] === window.location.href.split('#')[0]) {
  2719. // If _parent == self, then opening an identical URL with different
  2720. // location hash will only cause a navigation, not a download.
  2721. var padCharacter = blobUrl.indexOf('?') === -1 ? '?' : '&';
  2722. blobUrl = blobUrl.replace(/#|$/, padCharacter + '$&');
  2723. }
  2724. window.open(blobUrl, '_parent');
  2725. }
  2726. }
  2727. function DownloadManager() {}
  2728. DownloadManager.prototype = {
  2729. downloadUrl: function DownloadManager_downloadUrl(url, filename) {
  2730. if (!PDFJS.isValidUrl(url, true)) {
  2731. return; // restricted/invalid URL
  2732. }
  2733. download(url + '#pdfjs.action=download', filename);
  2734. },
  2735. downloadData: function DownloadManager_downloadData(data, filename,
  2736. contentType) {
  2737. if (navigator.msSaveBlob) { // IE10 and above
  2738. return navigator.msSaveBlob(new Blob([data], { type: contentType }),
  2739. filename);
  2740. }
  2741. var blobUrl = PDFJS.createObjectURL(data, contentType);
  2742. download(blobUrl, filename);
  2743. },
  2744. download: function DownloadManager_download(blob, url, filename) {
  2745. if (!URL) {
  2746. // URL.createObjectURL is not supported
  2747. this.downloadUrl(url, filename);
  2748. return;
  2749. }
  2750. if (navigator.msSaveBlob) {
  2751. // IE10 / IE11
  2752. if (!navigator.msSaveBlob(blob, filename)) {
  2753. this.downloadUrl(url, filename);
  2754. }
  2755. return;
  2756. }
  2757. var blobUrl = URL.createObjectURL(blob);
  2758. download(blobUrl, filename);
  2759. }
  2760. };
  2761. return DownloadManager;
  2762. })();
  2763. PDFJS.PDFViewer = PDFViewer;
  2764. PDFJS.PDFPageView = PDFPageView;
  2765. PDFJS.PDFLinkService = PDFLinkService;
  2766. PDFJS.TextLayerBuilder = TextLayerBuilder;
  2767. PDFJS.DefaultTextLayerFactory = DefaultTextLayerFactory;
  2768. PDFJS.AnnotationLayerBuilder = AnnotationLayerBuilder;
  2769. PDFJS.DefaultAnnotationLayerFactory = DefaultAnnotationLayerFactory;
  2770. PDFJS.PDFHistory = PDFHistory;
  2771. PDFJS.DownloadManager = DownloadManager;
  2772. PDFJS.ProgressBar = ProgressBar;
  2773. }).call((typeof window === 'undefined') ? this : window);