pdf_viewer.js 68 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209
  1. /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
  3. /* Copyright 2014 Mozilla Foundation
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /*jshint globalstrict: false */
  18. /* globals PDFJS, PDFViewer */
  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 = 'auto';
  27. var UNKNOWN_SCALE = 0;
  28. var MAX_AUTO_SCALE = 1.25;
  29. var SCROLLBAR_PADDING = 40;
  30. var VERTICAL_PADDING = 5;
  31. var DEFAULT_CACHE_SIZE = 10;
  32. // optimised CSS custom property getter/setter
  33. var CustomStyle = (function CustomStyleClosure() {
  34. // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
  35. // animate-css-transforms-firefox-webkit.html
  36. // in some versions of IE9 it is critical that ms appear in this list
  37. // before Moz
  38. var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
  39. var _cache = {};
  40. function CustomStyle() {}
  41. CustomStyle.getProp = function get(propName, element) {
  42. // check cache only when no element is given
  43. if (arguments.length === 1 && typeof _cache[propName] === 'string') {
  44. return _cache[propName];
  45. }
  46. element = element || document.documentElement;
  47. var style = element.style, prefixed, uPropName;
  48. // test standard property first
  49. if (typeof style[propName] === 'string') {
  50. return (_cache[propName] = propName);
  51. }
  52. // capitalize
  53. uPropName = propName.charAt(0).toUpperCase() + propName.slice(1);
  54. // test vendor specific properties
  55. for (var i = 0, l = prefixes.length; i < l; i++) {
  56. prefixed = prefixes[i] + uPropName;
  57. if (typeof style[prefixed] === 'string') {
  58. return (_cache[propName] = prefixed);
  59. }
  60. }
  61. //if all fails then set to undefined
  62. return (_cache[propName] = 'undefined');
  63. };
  64. CustomStyle.setProp = function set(propName, element, str) {
  65. var prop = this.getProp(propName);
  66. if (prop !== 'undefined') {
  67. element.style[prop] = str;
  68. }
  69. };
  70. return CustomStyle;
  71. })();
  72. function getFileName(url) {
  73. var anchor = url.indexOf('#');
  74. var query = url.indexOf('?');
  75. var end = Math.min(
  76. anchor > 0 ? anchor : url.length,
  77. query > 0 ? query : url.length);
  78. return url.substring(url.lastIndexOf('/', end) + 1, end);
  79. }
  80. /**
  81. * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
  82. * @return {Object} The object with horizontal (sx) and vertical (sy)
  83. scales. The scaled property is set to false if scaling is
  84. not required, true otherwise.
  85. */
  86. function getOutputScale(ctx) {
  87. var devicePixelRatio = window.devicePixelRatio || 1;
  88. var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
  89. ctx.mozBackingStorePixelRatio ||
  90. ctx.msBackingStorePixelRatio ||
  91. ctx.oBackingStorePixelRatio ||
  92. ctx.backingStorePixelRatio || 1;
  93. var pixelRatio = devicePixelRatio / backingStoreRatio;
  94. return {
  95. sx: pixelRatio,
  96. sy: pixelRatio,
  97. scaled: pixelRatio !== 1
  98. };
  99. }
  100. /**
  101. * Scrolls specified element into view of its parent.
  102. * element {Object} The element to be visible.
  103. * spot {Object} An object with optional top and left properties,
  104. * specifying the offset from the top left edge.
  105. */
  106. function scrollIntoView(element, spot) {
  107. // Assuming offsetParent is available (it's not available when viewer is in
  108. // hidden iframe or object). We have to scroll: if the offsetParent is not set
  109. // producing the error. See also animationStartedClosure.
  110. var parent = element.offsetParent;
  111. var offsetY = element.offsetTop + element.clientTop;
  112. var offsetX = element.offsetLeft + element.clientLeft;
  113. if (!parent) {
  114. console.error('offsetParent is not set -- cannot scroll');
  115. return;
  116. }
  117. while (parent.clientHeight === parent.scrollHeight) {
  118. if (parent.dataset._scaleY) {
  119. offsetY /= parent.dataset._scaleY;
  120. offsetX /= parent.dataset._scaleX;
  121. }
  122. offsetY += parent.offsetTop;
  123. offsetX += parent.offsetLeft;
  124. parent = parent.offsetParent;
  125. if (!parent) {
  126. return; // no need to scroll
  127. }
  128. }
  129. if (spot) {
  130. if (spot.top !== undefined) {
  131. offsetY += spot.top;
  132. }
  133. if (spot.left !== undefined) {
  134. offsetX += spot.left;
  135. parent.scrollLeft = offsetX;
  136. }
  137. }
  138. parent.scrollTop = offsetY;
  139. }
  140. /**
  141. * Helper function to start monitoring the scroll event and converting them into
  142. * PDF.js friendly one: with scroll debounce and scroll direction.
  143. */
  144. function watchScroll(viewAreaElement, callback) {
  145. var debounceScroll = function debounceScroll(evt) {
  146. if (rAF) {
  147. return;
  148. }
  149. // schedule an invocation of scroll for next animation frame.
  150. rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
  151. rAF = null;
  152. var currentY = viewAreaElement.scrollTop;
  153. var lastY = state.lastY;
  154. if (currentY > lastY) {
  155. state.down = true;
  156. } else if (currentY < lastY) {
  157. state.down = false;
  158. }
  159. state.lastY = currentY;
  160. // else do nothing and use previous value
  161. callback(state);
  162. });
  163. };
  164. var state = {
  165. down: true,
  166. lastY: viewAreaElement.scrollTop,
  167. _eventHandler: debounceScroll
  168. };
  169. var rAF = null;
  170. viewAreaElement.addEventListener('scroll', debounceScroll, true);
  171. return state;
  172. }
  173. /**
  174. * Generic helper to find out what elements are visible within a scroll pane.
  175. */
  176. function getVisibleElements(scrollEl, views, sortByVisibility) {
  177. var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
  178. var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
  179. var visible = [], view;
  180. var currentHeight, viewHeight, hiddenHeight, percentHeight;
  181. var currentWidth, viewWidth;
  182. for (var i = 0, ii = views.length; i < ii; ++i) {
  183. view = views[i];
  184. currentHeight = view.el.offsetTop + view.el.clientTop;
  185. viewHeight = view.el.clientHeight;
  186. if ((currentHeight + viewHeight) < top) {
  187. continue;
  188. }
  189. if (currentHeight > bottom) {
  190. break;
  191. }
  192. currentWidth = view.el.offsetLeft + view.el.clientLeft;
  193. viewWidth = view.el.clientWidth;
  194. if ((currentWidth + viewWidth) < left || currentWidth > right) {
  195. continue;
  196. }
  197. hiddenHeight = Math.max(0, top - currentHeight) +
  198. Math.max(0, currentHeight + viewHeight - bottom);
  199. percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
  200. visible.push({ id: view.id, x: currentWidth, y: currentHeight,
  201. view: view, percent: percentHeight });
  202. }
  203. var first = visible[0];
  204. var last = visible[visible.length - 1];
  205. if (sortByVisibility) {
  206. visible.sort(function(a, b) {
  207. var pc = a.percent - b.percent;
  208. if (Math.abs(pc) > 0.001) {
  209. return -pc;
  210. }
  211. return a.id - b.id; // ensure stability
  212. });
  213. }
  214. return {first: first, last: last, views: visible};
  215. }
  216. /**
  217. * Event handler to suppress context menu.
  218. */
  219. function noContextMenuHandler(e) {
  220. e.preventDefault();
  221. }
  222. /**
  223. * Returns the filename or guessed filename from the url (see issue 3455).
  224. * url {String} The original PDF location.
  225. * @return {String} Guessed PDF file name.
  226. */
  227. function getPDFFileNameFromURL(url) {
  228. var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
  229. // SCHEME HOST 1.PATH 2.QUERY 3.REF
  230. // Pattern to get last matching NAME.pdf
  231. var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
  232. var splitURI = reURI.exec(url);
  233. var suggestedFilename = reFilename.exec(splitURI[1]) ||
  234. reFilename.exec(splitURI[2]) ||
  235. reFilename.exec(splitURI[3]);
  236. if (suggestedFilename) {
  237. suggestedFilename = suggestedFilename[0];
  238. if (suggestedFilename.indexOf('%') !== -1) {
  239. // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
  240. try {
  241. suggestedFilename =
  242. reFilename.exec(decodeURIComponent(suggestedFilename))[0];
  243. } catch(e) { // Possible (extremely rare) errors:
  244. // URIError "Malformed URI", e.g. for "%AA.pdf"
  245. // TypeError "null has no properties", e.g. for "%2F.pdf"
  246. }
  247. }
  248. }
  249. return suggestedFilename || 'document.pdf';
  250. }
  251. var ProgressBar = (function ProgressBarClosure() {
  252. function clamp(v, min, max) {
  253. return Math.min(Math.max(v, min), max);
  254. }
  255. function ProgressBar(id, opts) {
  256. this.visible = true;
  257. // Fetch the sub-elements for later.
  258. this.div = document.querySelector(id + ' .progress');
  259. // Get the loading bar element, so it can be resized to fit the viewer.
  260. this.bar = this.div.parentNode;
  261. // Get options, with sensible defaults.
  262. this.height = opts.height || 100;
  263. this.width = opts.width || 100;
  264. this.units = opts.units || '%';
  265. // Initialize heights.
  266. this.div.style.height = this.height + this.units;
  267. this.percent = 0;
  268. }
  269. ProgressBar.prototype = {
  270. updateBar: function ProgressBar_updateBar() {
  271. if (this._indeterminate) {
  272. this.div.classList.add('indeterminate');
  273. this.div.style.width = this.width + this.units;
  274. return;
  275. }
  276. this.div.classList.remove('indeterminate');
  277. var progressSize = this.width * this._percent / 100;
  278. this.div.style.width = progressSize + this.units;
  279. },
  280. get percent() {
  281. return this._percent;
  282. },
  283. set percent(val) {
  284. this._indeterminate = isNaN(val);
  285. this._percent = clamp(val, 0, 100);
  286. this.updateBar();
  287. },
  288. setWidth: function ProgressBar_setWidth(viewer) {
  289. if (viewer) {
  290. var container = viewer.parentNode;
  291. var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
  292. if (scrollbarWidth > 0) {
  293. this.bar.setAttribute('style', 'width: calc(100% - ' +
  294. scrollbarWidth + 'px);');
  295. }
  296. }
  297. },
  298. hide: function ProgressBar_hide() {
  299. if (!this.visible) {
  300. return;
  301. }
  302. this.visible = false;
  303. this.bar.classList.add('hidden');
  304. document.body.classList.remove('loadingInProgress');
  305. },
  306. show: function ProgressBar_show() {
  307. if (this.visible) {
  308. return;
  309. }
  310. this.visible = true;
  311. document.body.classList.add('loadingInProgress');
  312. this.bar.classList.remove('hidden');
  313. }
  314. };
  315. return ProgressBar;
  316. })();
  317. var Cache = function cacheCache(size) {
  318. var data = [];
  319. this.push = function cachePush(view) {
  320. var i = data.indexOf(view);
  321. if (i >= 0) {
  322. data.splice(i, 1);
  323. }
  324. data.push(view);
  325. if (data.length > size) {
  326. data.shift().destroy();
  327. }
  328. };
  329. this.resize = function (newSize) {
  330. size = newSize;
  331. while (data.length > size) {
  332. data.shift().destroy();
  333. }
  334. };
  335. };
  336. var PresentationModeState = {
  337. UNKNOWN: 0,
  338. NORMAL: 1,
  339. CHANGING: 2,
  340. FULLSCREEN: 3,
  341. };
  342. var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
  343. var CLEANUP_TIMEOUT = 30000;
  344. var RenderingStates = {
  345. INITIAL: 0,
  346. RUNNING: 1,
  347. PAUSED: 2,
  348. FINISHED: 3
  349. };
  350. /**
  351. * Controls rendering of the views for pages and thumbnails.
  352. * @class
  353. */
  354. var PDFRenderingQueue = (function PDFRenderingQueueClosure() {
  355. /**
  356. * @constructs
  357. */
  358. function PDFRenderingQueue() {
  359. this.pdfViewer = null;
  360. this.pdfThumbnailViewer = null;
  361. this.onIdle = null;
  362. this.highestPriorityPage = null;
  363. this.idleTimeout = null;
  364. this.printing = false;
  365. this.isThumbnailViewEnabled = false;
  366. }
  367. PDFRenderingQueue.prototype = /** @lends PDFRenderingQueue.prototype */ {
  368. /**
  369. * @param {PDFViewer} pdfViewer
  370. */
  371. setViewer: function PDFRenderingQueue_setViewer(pdfViewer) {
  372. this.pdfViewer = pdfViewer;
  373. },
  374. /**
  375. * @param {PDFThumbnailViewer} pdfThumbnailViewer
  376. */
  377. setThumbnailViewer:
  378. function PDFRenderingQueue_setThumbnailViewer(pdfThumbnailViewer) {
  379. this.pdfThumbnailViewer = pdfThumbnailViewer;
  380. },
  381. /**
  382. * @param {IRenderableView} view
  383. * @returns {boolean}
  384. */
  385. isHighestPriority: function PDFRenderingQueue_isHighestPriority(view) {
  386. return this.highestPriorityPage === view.renderingId;
  387. },
  388. renderHighestPriority: function
  389. PDFRenderingQueue_renderHighestPriority(currentlyVisiblePages) {
  390. if (this.idleTimeout) {
  391. clearTimeout(this.idleTimeout);
  392. this.idleTimeout = null;
  393. }
  394. // Pages have a higher priority than thumbnails, so check them first.
  395. if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
  396. return;
  397. }
  398. // No pages needed rendering so check thumbnails.
  399. if (this.pdfThumbnailViewer && this.isThumbnailViewEnabled) {
  400. if (this.pdfThumbnailViewer.forceRendering()) {
  401. return;
  402. }
  403. }
  404. if (this.printing) {
  405. // If printing is currently ongoing do not reschedule cleanup.
  406. return;
  407. }
  408. if (this.onIdle) {
  409. this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
  410. }
  411. },
  412. getHighestPriority: function
  413. PDFRenderingQueue_getHighestPriority(visible, views, scrolledDown) {
  414. // The state has changed figure out which page has the highest priority to
  415. // render next (if any).
  416. // Priority:
  417. // 1 visible pages
  418. // 2 if last scrolled down page after the visible pages
  419. // 2 if last scrolled up page before the visible pages
  420. var visibleViews = visible.views;
  421. var numVisible = visibleViews.length;
  422. if (numVisible === 0) {
  423. return false;
  424. }
  425. for (var i = 0; i < numVisible; ++i) {
  426. var view = visibleViews[i].view;
  427. if (!this.isViewFinished(view)) {
  428. return view;
  429. }
  430. }
  431. // All the visible views have rendered, try to render next/previous pages.
  432. if (scrolledDown) {
  433. var nextPageIndex = visible.last.id;
  434. // ID's start at 1 so no need to add 1.
  435. if (views[nextPageIndex] &&
  436. !this.isViewFinished(views[nextPageIndex])) {
  437. return views[nextPageIndex];
  438. }
  439. } else {
  440. var previousPageIndex = visible.first.id - 2;
  441. if (views[previousPageIndex] &&
  442. !this.isViewFinished(views[previousPageIndex])) {
  443. return views[previousPageIndex];
  444. }
  445. }
  446. // Everything that needs to be rendered has been.
  447. return null;
  448. },
  449. /**
  450. * @param {IRenderableView} view
  451. * @returns {boolean}
  452. */
  453. isViewFinished: function PDFRenderingQueue_isViewFinished(view) {
  454. return view.renderingState === RenderingStates.FINISHED;
  455. },
  456. /**
  457. * Render a page or thumbnail view. This calls the appropriate function
  458. * based on the views state. If the view is already rendered it will return
  459. * false.
  460. * @param {IRenderableView} view
  461. */
  462. renderView: function PDFRenderingQueue_renderView(view) {
  463. var state = view.renderingState;
  464. switch (state) {
  465. case RenderingStates.FINISHED:
  466. return false;
  467. case RenderingStates.PAUSED:
  468. this.highestPriorityPage = view.renderingId;
  469. view.resume();
  470. break;
  471. case RenderingStates.RUNNING:
  472. this.highestPriorityPage = view.renderingId;
  473. break;
  474. case RenderingStates.INITIAL:
  475. this.highestPriorityPage = view.renderingId;
  476. view.draw(this.renderHighestPriority.bind(this));
  477. break;
  478. }
  479. return true;
  480. },
  481. };
  482. return PDFRenderingQueue;
  483. })();
  484. /**
  485. * @constructor
  486. * @param {HTMLDivElement} container - The viewer element.
  487. * @param {number} id - The page unique ID (normally its number).
  488. * @param {number} scale - The page scale display.
  489. * @param {PageViewport} defaultViewport - The page viewport.
  490. * @param {IPDFLinkService} linkService - The navigation/linking service.
  491. * @param {PDFRenderingQueue} renderingQueue - The rendering queue object.
  492. * @param {Cache} cache - The page cache.
  493. * @param {PDFPageSource} pageSource
  494. * @param {PDFViewer} viewer
  495. *
  496. * @implements {IRenderableView}
  497. */
  498. var PageView = function pageView(container, id, scale, defaultViewport,
  499. linkService, renderingQueue, cache,
  500. pageSource, viewer) {
  501. this.id = id;
  502. this.renderingId = 'page' + id;
  503. this.rotation = 0;
  504. this.scale = scale || 1.0;
  505. this.viewport = defaultViewport;
  506. this.pdfPageRotate = defaultViewport.rotation;
  507. this.hasRestrictedScaling = false;
  508. this.linkService = linkService;
  509. this.renderingQueue = renderingQueue;
  510. this.cache = cache;
  511. this.pageSource = pageSource;
  512. this.viewer = viewer;
  513. this.renderingState = RenderingStates.INITIAL;
  514. this.resume = null;
  515. this.textLayer = null;
  516. this.zoomLayer = null;
  517. this.annotationLayer = null;
  518. var anchor = document.createElement('a');
  519. anchor.name = '' + this.id;
  520. var div = this.el = document.createElement('div');
  521. div.id = 'pageContainer' + this.id;
  522. div.className = 'page';
  523. div.style.width = Math.floor(this.viewport.width) + 'px';
  524. div.style.height = Math.floor(this.viewport.height) + 'px';
  525. container.appendChild(anchor);
  526. container.appendChild(div);
  527. this.setPdfPage = function pageViewSetPdfPage(pdfPage) {
  528. this.pdfPage = pdfPage;
  529. this.pdfPageRotate = pdfPage.rotate;
  530. var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  531. this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
  532. this.stats = pdfPage.stats;
  533. this.reset();
  534. };
  535. this.destroy = function pageViewDestroy() {
  536. this.zoomLayer = null;
  537. this.reset();
  538. if (this.pdfPage) {
  539. this.pdfPage.destroy();
  540. }
  541. };
  542. this.reset = function pageViewReset(keepAnnotations) {
  543. if (this.renderTask) {
  544. this.renderTask.cancel();
  545. }
  546. this.resume = null;
  547. this.renderingState = RenderingStates.INITIAL;
  548. div.style.width = Math.floor(this.viewport.width) + 'px';
  549. div.style.height = Math.floor(this.viewport.height) + 'px';
  550. var childNodes = div.childNodes;
  551. for (var i = div.childNodes.length - 1; i >= 0; i--) {
  552. var node = childNodes[i];
  553. if ((this.zoomLayer && this.zoomLayer === node) ||
  554. (keepAnnotations && this.annotationLayer === node)) {
  555. continue;
  556. }
  557. div.removeChild(node);
  558. }
  559. div.removeAttribute('data-loaded');
  560. if (keepAnnotations) {
  561. if (this.annotationLayer) {
  562. // Hide annotationLayer until all elements are resized
  563. // so they are not displayed on the already-resized page
  564. this.annotationLayer.setAttribute('hidden', 'true');
  565. }
  566. } else {
  567. this.annotationLayer = null;
  568. }
  569. if (this.canvas) {
  570. // Zeroing the width and height causes Firefox to release graphics
  571. // resources immediately, which can greatly reduce memory consumption.
  572. this.canvas.width = 0;
  573. this.canvas.height = 0;
  574. delete this.canvas;
  575. }
  576. this.loadingIconDiv = document.createElement('div');
  577. this.loadingIconDiv.className = 'loadingIcon';
  578. div.appendChild(this.loadingIconDiv);
  579. };
  580. this.update = function pageViewUpdate(scale, rotation) {
  581. this.scale = scale || this.scale;
  582. if (typeof rotation !== 'undefined') {
  583. this.rotation = rotation;
  584. }
  585. var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
  586. this.viewport = this.viewport.clone({
  587. scale: this.scale * CSS_UNITS,
  588. rotation: totalRotation
  589. });
  590. var isScalingRestricted = false;
  591. if (this.canvas && PDFJS.maxCanvasPixels > 0) {
  592. var ctx = this.canvas.getContext('2d');
  593. var outputScale = getOutputScale(ctx);
  594. var pixelsInViewport = this.viewport.width * this.viewport.height;
  595. var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
  596. if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
  597. ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
  598. PDFJS.maxCanvasPixels) {
  599. isScalingRestricted = true;
  600. }
  601. }
  602. if (this.canvas &&
  603. (PDFJS.useOnlyCssZoom ||
  604. (this.hasRestrictedScaling && isScalingRestricted))) {
  605. this.cssTransform(this.canvas, true);
  606. return;
  607. } else if (this.canvas && !this.zoomLayer) {
  608. this.zoomLayer = this.canvas.parentNode;
  609. this.zoomLayer.style.position = 'absolute';
  610. }
  611. if (this.zoomLayer) {
  612. this.cssTransform(this.zoomLayer.firstChild);
  613. }
  614. this.reset(true);
  615. };
  616. this.cssTransform = function pageCssTransform(canvas, redrawAnnotations) {
  617. // Scale canvas, canvas wrapper, and page container.
  618. var width = this.viewport.width;
  619. var height = this.viewport.height;
  620. canvas.style.width = canvas.parentNode.style.width = div.style.width =
  621. Math.floor(width) + 'px';
  622. canvas.style.height = canvas.parentNode.style.height = div.style.height =
  623. Math.floor(height) + 'px';
  624. // The canvas may have been originally rotated, so rotate relative to that.
  625. var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
  626. var absRotation = Math.abs(relativeRotation);
  627. var scaleX = 1, scaleY = 1;
  628. if (absRotation === 90 || absRotation === 270) {
  629. // Scale x and y because of the rotation.
  630. scaleX = height / width;
  631. scaleY = width / height;
  632. }
  633. var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
  634. 'scale(' + scaleX + ',' + scaleY + ')';
  635. CustomStyle.setProp('transform', canvas, cssTransform);
  636. if (this.textLayer) {
  637. // Rotating the text layer is more complicated since the divs inside the
  638. // the text layer are rotated.
  639. // TODO: This could probably be simplified by drawing the text layer in
  640. // one orientation then rotating overall.
  641. var textLayerViewport = this.textLayer.viewport;
  642. var textRelativeRotation = this.viewport.rotation -
  643. textLayerViewport.rotation;
  644. var textAbsRotation = Math.abs(textRelativeRotation);
  645. var scale = width / textLayerViewport.width;
  646. if (textAbsRotation === 90 || textAbsRotation === 270) {
  647. scale = width / textLayerViewport.height;
  648. }
  649. var textLayerDiv = this.textLayer.textLayerDiv;
  650. var transX, transY;
  651. switch (textAbsRotation) {
  652. case 0:
  653. transX = transY = 0;
  654. break;
  655. case 90:
  656. transX = 0;
  657. transY = '-' + textLayerDiv.style.height;
  658. break;
  659. case 180:
  660. transX = '-' + textLayerDiv.style.width;
  661. transY = '-' + textLayerDiv.style.height;
  662. break;
  663. case 270:
  664. transX = '-' + textLayerDiv.style.width;
  665. transY = 0;
  666. break;
  667. default:
  668. console.error('Bad rotation value.');
  669. break;
  670. }
  671. CustomStyle.setProp('transform', textLayerDiv,
  672. 'rotate(' + textAbsRotation + 'deg) ' +
  673. 'scale(' + scale + ', ' + scale + ') ' +
  674. 'translate(' + transX + ', ' + transY + ')');
  675. CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
  676. }
  677. if (redrawAnnotations && this.annotationLayer) {
  678. setupAnnotations(div, this.pdfPage, this.viewport);
  679. }
  680. };
  681. Object.defineProperty(this, 'width', {
  682. get: function PageView_getWidth() {
  683. return this.viewport.width;
  684. },
  685. enumerable: true
  686. });
  687. Object.defineProperty(this, 'height', {
  688. get: function PageView_getHeight() {
  689. return this.viewport.height;
  690. },
  691. enumerable: true
  692. });
  693. var self = this;
  694. function setupAnnotations(pageDiv, pdfPage, viewport) {
  695. function bindLink(link, dest) {
  696. link.href = linkService.getDestinationHash(dest);
  697. link.onclick = function pageViewSetupLinksOnclick() {
  698. if (dest) {
  699. linkService.navigateTo(dest);
  700. }
  701. return false;
  702. };
  703. if (dest) {
  704. link.className = 'internalLink';
  705. }
  706. }
  707. function bindNamedAction(link, action) {
  708. link.href = linkService.getAnchorUrl('');
  709. link.onclick = function pageViewSetupNamedActionOnClick() {
  710. linkService.executeNamedAction(action);
  711. return false;
  712. };
  713. link.className = 'internalLink';
  714. }
  715. pdfPage.getAnnotations().then(function(annotationsData) {
  716. viewport = viewport.clone({ dontFlip: true });
  717. var transform = viewport.transform;
  718. var transformStr = 'matrix(' + transform.join(',') + ')';
  719. var data, element, i, ii;
  720. if (self.annotationLayer) {
  721. // If an annotationLayer already exists, refresh its children's
  722. // transformation matrices
  723. for (i = 0, ii = annotationsData.length; i < ii; i++) {
  724. data = annotationsData[i];
  725. element = self.annotationLayer.querySelector(
  726. '[data-annotation-id="' + data.id + '"]');
  727. if (element) {
  728. CustomStyle.setProp('transform', element, transformStr);
  729. }
  730. }
  731. // See this.reset()
  732. self.annotationLayer.removeAttribute('hidden');
  733. } else {
  734. for (i = 0, ii = annotationsData.length; i < ii; i++) {
  735. data = annotationsData[i];
  736. if (!data || !data.hasHtml) {
  737. continue;
  738. }
  739. element = PDFJS.AnnotationUtils.getHtmlElement(data,
  740. pdfPage.commonObjs);
  741. element.setAttribute('data-annotation-id', data.id);
  742. mozL10n.translate(element);
  743. var rect = data.rect;
  744. var view = pdfPage.view;
  745. rect = PDFJS.Util.normalizeRect([
  746. rect[0],
  747. view[3] - rect[1] + view[1],
  748. rect[2],
  749. view[3] - rect[3] + view[1]
  750. ]);
  751. element.style.left = rect[0] + 'px';
  752. element.style.top = rect[1] + 'px';
  753. element.style.position = 'absolute';
  754. CustomStyle.setProp('transform', element, transformStr);
  755. var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
  756. CustomStyle.setProp('transformOrigin', element, transformOriginStr);
  757. if (data.subtype === 'Link' && !data.url) {
  758. var link = element.getElementsByTagName('a')[0];
  759. if (link) {
  760. if (data.action) {
  761. bindNamedAction(link, data.action);
  762. } else {
  763. bindLink(link, ('dest' in data) ? data.dest : null);
  764. }
  765. }
  766. }
  767. if (!self.annotationLayer) {
  768. var annotationLayerDiv = document.createElement('div');
  769. annotationLayerDiv.className = 'annotationLayer';
  770. pageDiv.appendChild(annotationLayerDiv);
  771. self.annotationLayer = annotationLayerDiv;
  772. }
  773. self.annotationLayer.appendChild(element);
  774. }
  775. }
  776. });
  777. }
  778. this.getPagePoint = function pageViewGetPagePoint(x, y) {
  779. return this.viewport.convertToPdfPoint(x, y);
  780. };
  781. this.draw = function pageviewDraw(callback) {
  782. var pdfPage = this.pdfPage;
  783. if (this.pagePdfPromise) {
  784. return;
  785. }
  786. if (!pdfPage) {
  787. var promise = this.pageSource.getPage();
  788. promise.then(function(pdfPage) {
  789. delete this.pagePdfPromise;
  790. this.setPdfPage(pdfPage);
  791. this.draw(callback);
  792. }.bind(this));
  793. this.pagePdfPromise = promise;
  794. return;
  795. }
  796. if (this.renderingState !== RenderingStates.INITIAL) {
  797. console.error('Must be in new state before drawing');
  798. }
  799. this.renderingState = RenderingStates.RUNNING;
  800. var viewport = this.viewport;
  801. // Wrap the canvas so if it has a css transform for highdpi the overflow
  802. // will be hidden in FF.
  803. var canvasWrapper = document.createElement('div');
  804. canvasWrapper.style.width = div.style.width;
  805. canvasWrapper.style.height = div.style.height;
  806. canvasWrapper.classList.add('canvasWrapper');
  807. var canvas = document.createElement('canvas');
  808. canvas.id = 'page' + this.id;
  809. canvasWrapper.appendChild(canvas);
  810. if (this.annotationLayer) {
  811. // annotationLayer needs to stay on top
  812. div.insertBefore(canvasWrapper, this.annotationLayer);
  813. } else {
  814. div.appendChild(canvasWrapper);
  815. }
  816. this.canvas = canvas;
  817. var ctx = canvas.getContext('2d');
  818. var outputScale = getOutputScale(ctx);
  819. if (PDFJS.useOnlyCssZoom) {
  820. var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
  821. // Use a scale that will make the canvas be the original intended size
  822. // of the page.
  823. outputScale.sx *= actualSizeViewport.width / viewport.width;
  824. outputScale.sy *= actualSizeViewport.height / viewport.height;
  825. outputScale.scaled = true;
  826. }
  827. if (PDFJS.maxCanvasPixels > 0) {
  828. var pixelsInViewport = viewport.width * viewport.height;
  829. var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
  830. if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
  831. outputScale.sx = maxScale;
  832. outputScale.sy = maxScale;
  833. outputScale.scaled = true;
  834. this.hasRestrictedScaling = true;
  835. } else {
  836. this.hasRestrictedScaling = false;
  837. }
  838. }
  839. canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
  840. canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
  841. canvas.style.width = Math.floor(viewport.width) + 'px';
  842. canvas.style.height = Math.floor(viewport.height) + 'px';
  843. // Add the viewport so it's known what it was originally drawn with.
  844. canvas._viewport = viewport;
  845. var textLayerDiv = null;
  846. var textLayer = null;
  847. if (!PDFJS.disableTextLayer) {
  848. textLayerDiv = document.createElement('div');
  849. textLayerDiv.className = 'textLayer';
  850. textLayerDiv.style.width = canvas.style.width;
  851. textLayerDiv.style.height = canvas.style.height;
  852. if (this.annotationLayer) {
  853. // annotationLayer needs to stay on top
  854. div.insertBefore(textLayerDiv, this.annotationLayer);
  855. } else {
  856. div.appendChild(textLayerDiv);
  857. }
  858. textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, this.id - 1,
  859. this.viewport);
  860. }
  861. this.textLayer = textLayer;
  862. // TODO(mack): use data attributes to store these
  863. ctx._scaleX = outputScale.sx;
  864. ctx._scaleY = outputScale.sy;
  865. if (outputScale.scaled) {
  866. ctx.scale(outputScale.sx, outputScale.sy);
  867. }
  868. // Rendering area
  869. var self = this;
  870. function pageViewDrawCallback(error) {
  871. // The renderTask may have been replaced by a new one, so only remove the
  872. // reference to the renderTask if it matches the one that is triggering
  873. // this callback.
  874. if (renderTask === self.renderTask) {
  875. self.renderTask = null;
  876. }
  877. if (error === 'cancelled') {
  878. return;
  879. }
  880. self.renderingState = RenderingStates.FINISHED;
  881. if (self.loadingIconDiv) {
  882. div.removeChild(self.loadingIconDiv);
  883. delete self.loadingIconDiv;
  884. }
  885. if (self.zoomLayer) {
  886. div.removeChild(self.zoomLayer);
  887. self.zoomLayer = null;
  888. }
  889. self.error = error;
  890. self.stats = pdfPage.stats;
  891. self.updateStats();
  892. if (self.onAfterDraw) {
  893. self.onAfterDraw();
  894. }
  895. var event = document.createEvent('CustomEvent');
  896. event.initCustomEvent('pagerender', true, true, {
  897. pageNumber: pdfPage.pageNumber
  898. });
  899. div.dispatchEvent(event);
  900. callback();
  901. }
  902. var renderContext = {
  903. canvasContext: ctx,
  904. viewport: this.viewport,
  905. // intent: 'default', // === 'display'
  906. continueCallback: function pdfViewcContinueCallback(cont) {
  907. if (!self.renderingQueue.isHighestPriority(self)) {
  908. self.renderingState = RenderingStates.PAUSED;
  909. self.resume = function resumeCallback() {
  910. self.renderingState = RenderingStates.RUNNING;
  911. cont();
  912. };
  913. return;
  914. }
  915. cont();
  916. }
  917. };
  918. var renderTask = this.renderTask = this.pdfPage.render(renderContext);
  919. this.renderTask.promise.then(
  920. function pdfPageRenderCallback() {
  921. pageViewDrawCallback(null);
  922. if (textLayer) {
  923. self.pdfPage.getTextContent().then(
  924. function textContentResolved(textContent) {
  925. textLayer.setTextContent(textContent);
  926. }
  927. );
  928. }
  929. },
  930. function pdfPageRenderError(error) {
  931. pageViewDrawCallback(error);
  932. }
  933. );
  934. setupAnnotations(div, pdfPage, this.viewport);
  935. div.setAttribute('data-loaded', true);
  936. // Add the page to the cache at the start of drawing. That way it can be
  937. // evicted from the cache and destroyed even if we pause its rendering.
  938. cache.push(this);
  939. };
  940. this.beforePrint = function pageViewBeforePrint() {
  941. var pdfPage = this.pdfPage;
  942. var viewport = pdfPage.getViewport(1);
  943. // Use the same hack we use for high dpi displays for printing to get better
  944. // output until bug 811002 is fixed in FF.
  945. var PRINT_OUTPUT_SCALE = 2;
  946. var canvas = document.createElement('canvas');
  947. canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
  948. canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
  949. canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt';
  950. canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt';
  951. var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
  952. (1 / PRINT_OUTPUT_SCALE) + ')';
  953. CustomStyle.setProp('transform' , canvas, cssScale);
  954. CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
  955. var printContainer = document.getElementById('printContainer');
  956. var canvasWrapper = document.createElement('div');
  957. canvasWrapper.style.width = viewport.width + 'pt';
  958. canvasWrapper.style.height = viewport.height + 'pt';
  959. canvasWrapper.appendChild(canvas);
  960. printContainer.appendChild(canvasWrapper);
  961. canvas.mozPrintCallback = function(obj) {
  962. var ctx = obj.context;
  963. ctx.save();
  964. ctx.fillStyle = 'rgb(255, 255, 255)';
  965. ctx.fillRect(0, 0, canvas.width, canvas.height);
  966. ctx.restore();
  967. ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
  968. var renderContext = {
  969. canvasContext: ctx,
  970. viewport: viewport,
  971. intent: 'print'
  972. };
  973. pdfPage.render(renderContext).promise.then(function() {
  974. // Tell the printEngine that rendering this canvas/page has finished.
  975. obj.done();
  976. }, function(error) {
  977. console.error(error);
  978. // Tell the printEngine that rendering this canvas/page has failed.
  979. // This will make the print proces stop.
  980. if ('abort' in obj) {
  981. obj.abort();
  982. } else {
  983. obj.done();
  984. }
  985. });
  986. };
  987. };
  988. this.updateStats = function pageViewUpdateStats() {
  989. if (!this.stats) {
  990. return;
  991. }
  992. if (PDFJS.pdfBug && Stats.enabled) {
  993. var stats = this.stats;
  994. Stats.add(this.id, stats);
  995. }
  996. };
  997. };
  998. var FIND_SCROLL_OFFSET_TOP = -50;
  999. var FIND_SCROLL_OFFSET_LEFT = -400;
  1000. var MAX_TEXT_DIVS_TO_RENDER = 100000;
  1001. var RENDER_DELAY = 200; // ms
  1002. var NonWhitespaceRegexp = /\S/;
  1003. function isAllWhitespace(str) {
  1004. return !NonWhitespaceRegexp.test(str);
  1005. }
  1006. /**
  1007. * @typedef {Object} TextLayerBuilderOptions
  1008. * @property {HTMLDivElement} textLayerDiv - The text layer container.
  1009. * @property {number} pageIndex - The page index.
  1010. * @property {PageViewport} viewport - The viewport of the text layer.
  1011. * @property {ILastScrollSource} lastScrollSource - The object that records when
  1012. * last time scroll happened.
  1013. * @property {boolean} isViewerInPresentationMode
  1014. * @property {PDFFindController} findController
  1015. */
  1016. /**
  1017. * TextLayerBuilder provides text-selection functionality for the PDF.
  1018. * It does this by creating overlay divs over the PDF text. These divs
  1019. * contain text that matches the PDF text they are overlaying. This object
  1020. * also provides a way to highlight text that is being searched for.
  1021. * @class
  1022. */
  1023. var TextLayerBuilder = (function TextLayerBuilderClosure() {
  1024. function TextLayerBuilder(options) {
  1025. this.textLayerDiv = options.textLayerDiv;
  1026. this.layoutDone = false;
  1027. this.divContentDone = false;
  1028. this.pageIdx = options.pageIndex;
  1029. this.matches = [];
  1030. this.lastScrollSource = options.lastScrollSource || null;
  1031. this.viewport = options.viewport;
  1032. this.isViewerInPresentationMode = options.isViewerInPresentationMode;
  1033. this.textDivs = [];
  1034. this.findController = options.findController || null;
  1035. }
  1036. TextLayerBuilder.prototype = {
  1037. renderLayer: function TextLayerBuilder_renderLayer() {
  1038. var textLayerFrag = document.createDocumentFragment();
  1039. var textDivs = this.textDivs;
  1040. var textDivsLength = textDivs.length;
  1041. var canvas = document.createElement('canvas');
  1042. var ctx = canvas.getContext('2d');
  1043. // No point in rendering many divs as it would make the browser
  1044. // unusable even after the divs are rendered.
  1045. if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
  1046. return;
  1047. }
  1048. var lastFontSize;
  1049. var lastFontFamily;
  1050. for (var i = 0; i < textDivsLength; i++) {
  1051. var textDiv = textDivs[i];
  1052. if (textDiv.dataset.isWhitespace !== undefined) {
  1053. continue;
  1054. }
  1055. var fontSize = textDiv.style.fontSize;
  1056. var fontFamily = textDiv.style.fontFamily;
  1057. // Only build font string and set to context if different from last.
  1058. if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
  1059. ctx.font = fontSize + ' ' + fontFamily;
  1060. lastFontSize = fontSize;
  1061. lastFontFamily = fontFamily;
  1062. }
  1063. var width = ctx.measureText(textDiv.textContent).width;
  1064. if (width > 0) {
  1065. textLayerFrag.appendChild(textDiv);
  1066. var transform;
  1067. if (textDiv.dataset.canvasWidth !== undefined) {
  1068. // Dataset values come of type string.
  1069. var textScale = textDiv.dataset.canvasWidth / width;
  1070. transform = 'scaleX(' + textScale + ')';
  1071. } else {
  1072. transform = '';
  1073. }
  1074. var rotation = textDiv.dataset.angle;
  1075. if (rotation) {
  1076. transform = 'rotate(' + rotation + 'deg) ' + transform;
  1077. }
  1078. if (transform) {
  1079. CustomStyle.setProp('transform' , textDiv, transform);
  1080. }
  1081. }
  1082. }
  1083. this.textLayerDiv.appendChild(textLayerFrag);
  1084. this.renderingDone = true;
  1085. this.updateMatches();
  1086. },
  1087. setupRenderLayoutTimer:
  1088. function TextLayerBuilder_setupRenderLayoutTimer() {
  1089. // Schedule renderLayout() if the user has been scrolling,
  1090. // otherwise run it right away.
  1091. var self = this;
  1092. var lastScroll = (this.lastScrollSource === null ?
  1093. 0 : this.lastScrollSource.lastScroll);
  1094. if (Date.now() - lastScroll > RENDER_DELAY) { // Render right away
  1095. this.renderLayer();
  1096. } else { // Schedule
  1097. if (this.renderTimer) {
  1098. clearTimeout(this.renderTimer);
  1099. }
  1100. this.renderTimer = setTimeout(function() {
  1101. self.setupRenderLayoutTimer();
  1102. }, RENDER_DELAY);
  1103. }
  1104. },
  1105. appendText: function TextLayerBuilder_appendText(geom, styles) {
  1106. var style = styles[geom.fontName];
  1107. var textDiv = document.createElement('div');
  1108. this.textDivs.push(textDiv);
  1109. if (isAllWhitespace(geom.str)) {
  1110. textDiv.dataset.isWhitespace = true;
  1111. return;
  1112. }
  1113. var tx = PDFJS.Util.transform(this.viewport.transform, geom.transform);
  1114. var angle = Math.atan2(tx[1], tx[0]);
  1115. if (style.vertical) {
  1116. angle += Math.PI / 2;
  1117. }
  1118. var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
  1119. var fontAscent = fontHeight;
  1120. if (style.ascent) {
  1121. fontAscent = style.ascent * fontAscent;
  1122. } else if (style.descent) {
  1123. fontAscent = (1 + style.descent) * fontAscent;
  1124. }
  1125. var left;
  1126. var top;
  1127. if (angle === 0) {
  1128. left = tx[4];
  1129. top = tx[5] - fontAscent;
  1130. } else {
  1131. left = tx[4] + (fontAscent * Math.sin(angle));
  1132. top = tx[5] - (fontAscent * Math.cos(angle));
  1133. }
  1134. textDiv.style.left = left + 'px';
  1135. textDiv.style.top = top + 'px';
  1136. textDiv.style.fontSize = fontHeight + 'px';
  1137. textDiv.style.fontFamily = style.fontFamily;
  1138. textDiv.textContent = geom.str;
  1139. // |fontName| is only used by the Font Inspector. This test will succeed
  1140. // when e.g. the Font Inspector is off but the Stepper is on, but it's
  1141. // not worth the effort to do a more accurate test.
  1142. if (PDFJS.pdfBug) {
  1143. textDiv.dataset.fontName = geom.fontName;
  1144. }
  1145. // Storing into dataset will convert number into string.
  1146. if (angle !== 0) {
  1147. textDiv.dataset.angle = angle * (180 / Math.PI);
  1148. }
  1149. // We don't bother scaling single-char text divs, because it has very
  1150. // little effect on text highlighting. This makes scrolling on docs with
  1151. // lots of such divs a lot faster.
  1152. if (textDiv.textContent.length > 1) {
  1153. if (style.vertical) {
  1154. textDiv.dataset.canvasWidth = geom.height * this.viewport.scale;
  1155. } else {
  1156. textDiv.dataset.canvasWidth = geom.width * this.viewport.scale;
  1157. }
  1158. }
  1159. },
  1160. setTextContent: function TextLayerBuilder_setTextContent(textContent) {
  1161. this.textContent = textContent;
  1162. var textItems = textContent.items;
  1163. for (var i = 0, len = textItems.length; i < len; i++) {
  1164. this.appendText(textItems[i], textContent.styles);
  1165. }
  1166. this.divContentDone = true;
  1167. this.setupRenderLayoutTimer();
  1168. },
  1169. convertMatches: function TextLayerBuilder_convertMatches(matches) {
  1170. var i = 0;
  1171. var iIndex = 0;
  1172. var bidiTexts = this.textContent.items;
  1173. var end = bidiTexts.length - 1;
  1174. var queryLen = (this.findController === null ?
  1175. 0 : this.findController.state.query.length);
  1176. var ret = [];
  1177. for (var m = 0, len = matches.length; m < len; m++) {
  1178. // Calculate the start position.
  1179. var matchIdx = matches[m];
  1180. // Loop over the divIdxs.
  1181. while (i !== end && matchIdx >= (iIndex + bidiTexts[i].str.length)) {
  1182. iIndex += bidiTexts[i].str.length;
  1183. i++;
  1184. }
  1185. if (i === bidiTexts.length) {
  1186. console.error('Could not find a matching mapping');
  1187. }
  1188. var match = {
  1189. begin: {
  1190. divIdx: i,
  1191. offset: matchIdx - iIndex
  1192. }
  1193. };
  1194. // Calculate the end position.
  1195. matchIdx += queryLen;
  1196. // Somewhat the same array as above, but use > instead of >= to get
  1197. // the end position right.
  1198. while (i !== end && matchIdx > (iIndex + bidiTexts[i].str.length)) {
  1199. iIndex += bidiTexts[i].str.length;
  1200. i++;
  1201. }
  1202. match.end = {
  1203. divIdx: i,
  1204. offset: matchIdx - iIndex
  1205. };
  1206. ret.push(match);
  1207. }
  1208. return ret;
  1209. },
  1210. renderMatches: function TextLayerBuilder_renderMatches(matches) {
  1211. // Early exit if there is nothing to render.
  1212. if (matches.length === 0) {
  1213. return;
  1214. }
  1215. var bidiTexts = this.textContent.items;
  1216. var textDivs = this.textDivs;
  1217. var prevEnd = null;
  1218. var isSelectedPage = (this.findController === null ?
  1219. false : (this.pageIdx === this.findController.selected.pageIdx));
  1220. var selectedMatchIdx = (this.findController === null ?
  1221. -1 : this.findController.selected.matchIdx);
  1222. var highlightAll = (this.findController === null ?
  1223. false : this.findController.state.highlightAll);
  1224. var infinity = {
  1225. divIdx: -1,
  1226. offset: undefined
  1227. };
  1228. function beginText(begin, className) {
  1229. var divIdx = begin.divIdx;
  1230. textDivs[divIdx].textContent = '';
  1231. appendTextToDiv(divIdx, 0, begin.offset, className);
  1232. }
  1233. function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
  1234. var div = textDivs[divIdx];
  1235. var content = bidiTexts[divIdx].str.substring(fromOffset, toOffset);
  1236. var node = document.createTextNode(content);
  1237. if (className) {
  1238. var span = document.createElement('span');
  1239. span.className = className;
  1240. span.appendChild(node);
  1241. div.appendChild(span);
  1242. return;
  1243. }
  1244. div.appendChild(node);
  1245. }
  1246. var i0 = selectedMatchIdx, i1 = i0 + 1;
  1247. if (highlightAll) {
  1248. i0 = 0;
  1249. i1 = matches.length;
  1250. } else if (!isSelectedPage) {
  1251. // Not highlighting all and this isn't the selected page, so do nothing.
  1252. return;
  1253. }
  1254. for (var i = i0; i < i1; i++) {
  1255. var match = matches[i];
  1256. var begin = match.begin;
  1257. var end = match.end;
  1258. var isSelected = (isSelectedPage && i === selectedMatchIdx);
  1259. var highlightSuffix = (isSelected ? ' selected' : '');
  1260. if (isSelected && !this.isViewerInPresentationMode) {
  1261. scrollIntoView(textDivs[begin.divIdx],
  1262. { top: FIND_SCROLL_OFFSET_TOP,
  1263. left: FIND_SCROLL_OFFSET_LEFT });
  1264. }
  1265. // Match inside new div.
  1266. if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
  1267. // If there was a previous div, then add the text at the end.
  1268. if (prevEnd !== null) {
  1269. appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
  1270. }
  1271. // Clear the divs and set the content until the starting point.
  1272. beginText(begin);
  1273. } else {
  1274. appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
  1275. }
  1276. if (begin.divIdx === end.divIdx) {
  1277. appendTextToDiv(begin.divIdx, begin.offset, end.offset,
  1278. 'highlight' + highlightSuffix);
  1279. } else {
  1280. appendTextToDiv(begin.divIdx, begin.offset, infinity.offset,
  1281. 'highlight begin' + highlightSuffix);
  1282. for (var n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
  1283. textDivs[n0].className = 'highlight middle' + highlightSuffix;
  1284. }
  1285. beginText(end, 'highlight end' + highlightSuffix);
  1286. }
  1287. prevEnd = end;
  1288. }
  1289. if (prevEnd) {
  1290. appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
  1291. }
  1292. },
  1293. updateMatches: function TextLayerBuilder_updateMatches() {
  1294. // Only show matches when all rendering is done.
  1295. if (!this.renderingDone) {
  1296. return;
  1297. }
  1298. // Clear all matches.
  1299. var matches = this.matches;
  1300. var textDivs = this.textDivs;
  1301. var bidiTexts = this.textContent.items;
  1302. var clearedUntilDivIdx = -1;
  1303. // Clear all current matches.
  1304. for (var i = 0, len = matches.length; i < len; i++) {
  1305. var match = matches[i];
  1306. var begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
  1307. for (var n = begin, end = match.end.divIdx; n <= end; n++) {
  1308. var div = textDivs[n];
  1309. div.textContent = bidiTexts[n].str;
  1310. div.className = '';
  1311. }
  1312. clearedUntilDivIdx = match.end.divIdx + 1;
  1313. }
  1314. if (this.findController === null || !this.findController.active) {
  1315. return;
  1316. }
  1317. // Convert the matches on the page controller into the match format
  1318. // used for the textLayer.
  1319. this.matches = this.convertMatches(this.findController === null ?
  1320. [] : (this.findController.pageMatches[this.pageIdx] || []));
  1321. this.renderMatches(this.matches);
  1322. }
  1323. };
  1324. return TextLayerBuilder;
  1325. })();
  1326. /**
  1327. * @typedef {Object} PDFViewerOptions
  1328. * @property {HTMLDivElement} container - The container for the viewer element.
  1329. * @property {HTMLDivElement} viewer - (optional) The viewer element.
  1330. * @property {IPDFLinkService} linkService - The navigation/linking service.
  1331. * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
  1332. * queue object.
  1333. */
  1334. /**
  1335. * Simple viewer control to display PDF content/pages.
  1336. * @class
  1337. * @implements {ILastScrollSource}
  1338. * @implements {IRenderableView}
  1339. */
  1340. var PDFViewer = (function pdfViewer() {
  1341. /**
  1342. * @constructs PDFViewer
  1343. * @param {PDFViewerOptions} options
  1344. */
  1345. function PDFViewer(options) {
  1346. this.container = options.container;
  1347. this.viewer = options.viewer || options.container.firstElementChild;
  1348. this.linkService = options.linkService || new SimpleLinkService(this);
  1349. this.defaultRenderingQueue = !options.renderingQueue;
  1350. if (this.defaultRenderingQueue) {
  1351. // Custom rendering queue is not specified, using default one
  1352. this.renderingQueue = new PDFRenderingQueue();
  1353. this.renderingQueue.setViewer(this);
  1354. } else {
  1355. this.renderingQueue = options.renderingQueue;
  1356. }
  1357. this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
  1358. this.lastScroll = 0;
  1359. this.updateInProgress = false;
  1360. this.presentationModeState = PresentationModeState.UNKNOWN;
  1361. this._resetView();
  1362. }
  1363. PDFViewer.prototype = /** @lends PDFViewer.prototype */{
  1364. get pagesCount() {
  1365. return this.pages.length;
  1366. },
  1367. getPageView: function (index) {
  1368. return this.pages[index];
  1369. },
  1370. get currentPageNumber() {
  1371. return this._currentPageNumber;
  1372. },
  1373. set currentPageNumber(val) {
  1374. if (!this.pdfDocument) {
  1375. this._currentPageNumber = val;
  1376. return;
  1377. }
  1378. var event = document.createEvent('UIEvents');
  1379. event.initUIEvent('pagechange', true, true, window, 0);
  1380. event.updateInProgress = this.updateInProgress;
  1381. if (!(0 < val && val <= this.pagesCount)) {
  1382. event.pageNumber = this._currentPageNumber;
  1383. event.previousPageNumber = val;
  1384. this.container.dispatchEvent(event);
  1385. return;
  1386. }
  1387. this.pages[val - 1].updateStats();
  1388. event.previousPageNumber = this._currentPageNumber;
  1389. this._currentPageNumber = val;
  1390. event.pageNumber = val;
  1391. this.container.dispatchEvent(event);
  1392. },
  1393. /**
  1394. * @returns {number}
  1395. */
  1396. get currentScale() {
  1397. return this._currentScale;
  1398. },
  1399. /**
  1400. * @param {number} val - Scale of the pages in percents.
  1401. */
  1402. set currentScale(val) {
  1403. if (isNaN(val)) {
  1404. throw new Error('Invalid numeric scale');
  1405. }
  1406. if (!this.pdfDocument) {
  1407. this._currentScale = val;
  1408. this._currentScaleValue = val.toString();
  1409. return;
  1410. }
  1411. this._setScale(val, false);
  1412. },
  1413. /**
  1414. * @returns {string}
  1415. */
  1416. get currentScaleValue() {
  1417. return this._currentScaleValue;
  1418. },
  1419. /**
  1420. * @param val - The scale of the pages (in percent or predefined value).
  1421. */
  1422. set currentScaleValue(val) {
  1423. if (!this.pdfDocument) {
  1424. this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
  1425. this._currentScaleValue = val;
  1426. return;
  1427. }
  1428. this._setScale(val, false);
  1429. },
  1430. /**
  1431. * @returns {number}
  1432. */
  1433. get pagesRotation() {
  1434. return this._pagesRotation;
  1435. },
  1436. /**
  1437. * @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
  1438. */
  1439. set pagesRotation(rotation) {
  1440. this._pagesRotation = rotation;
  1441. for (var i = 0, l = this.pages.length; i < l; i++) {
  1442. var page = this.pages[i];
  1443. page.update(page.scale, rotation);
  1444. }
  1445. this._setScale(this._currentScaleValue, true);
  1446. },
  1447. /**
  1448. * @param pdfDocument {PDFDocument}
  1449. */
  1450. setDocument: function (pdfDocument) {
  1451. if (this.pdfDocument) {
  1452. this._resetView();
  1453. }
  1454. this.pdfDocument = pdfDocument;
  1455. if (!pdfDocument) {
  1456. return;
  1457. }
  1458. var pagesCount = pdfDocument.numPages;
  1459. var pagesRefMap = this.pagesRefMap = {};
  1460. var self = this;
  1461. var resolvePagesPromise;
  1462. var pagesPromise = new Promise(function (resolve) {
  1463. resolvePagesPromise = resolve;
  1464. });
  1465. this.pagesPromise = pagesPromise;
  1466. pagesPromise.then(function () {
  1467. var event = document.createEvent('CustomEvent');
  1468. event.initCustomEvent('pagesloaded', true, true, {
  1469. pagesCount: pagesCount
  1470. });
  1471. self.container.dispatchEvent(event);
  1472. });
  1473. var isOnePageRenderedResolved = false;
  1474. var resolveOnePageRendered = null;
  1475. var onePageRendered = new Promise(function (resolve) {
  1476. resolveOnePageRendered = resolve;
  1477. });
  1478. this.onePageRendered = onePageRendered;
  1479. var bindOnAfterDraw = function (pageView) {
  1480. // when page is painted, using the image as thumbnail base
  1481. pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
  1482. if (!isOnePageRenderedResolved) {
  1483. isOnePageRenderedResolved = true;
  1484. resolveOnePageRendered();
  1485. }
  1486. var event = document.createEvent('CustomEvent');
  1487. event.initCustomEvent('pagerendered', true, true, {
  1488. pageNumber: pageView.id
  1489. });
  1490. self.container.dispatchEvent(event);
  1491. };
  1492. };
  1493. var firstPagePromise = pdfDocument.getPage(1);
  1494. this.firstPagePromise = firstPagePromise;
  1495. // Fetch a single page so we can get a viewport that will be the default
  1496. // viewport for all pages
  1497. return firstPagePromise.then(function(pdfPage) {
  1498. var scale = this._currentScale || 1.0;
  1499. var viewport = pdfPage.getViewport(scale * CSS_UNITS);
  1500. for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
  1501. var pageSource = new PDFPageSource(pdfDocument, pageNum);
  1502. var pageView = new PageView(this.viewer, pageNum, scale,
  1503. viewport.clone(), this.linkService,
  1504. this.renderingQueue, this.cache,
  1505. pageSource, this);
  1506. bindOnAfterDraw(pageView);
  1507. this.pages.push(pageView);
  1508. }
  1509. // Fetch all the pages since the viewport is needed before printing
  1510. // starts to create the correct size canvas. Wait until one page is
  1511. // rendered so we don't tie up too many resources early on.
  1512. onePageRendered.then(function () {
  1513. if (!PDFJS.disableAutoFetch) {
  1514. var getPagesLeft = pagesCount;
  1515. for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
  1516. pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
  1517. var pageView = self.pages[pageNum - 1];
  1518. if (!pageView.pdfPage) {
  1519. pageView.setPdfPage(pdfPage);
  1520. }
  1521. var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
  1522. pagesRefMap[refStr] = pageNum;
  1523. getPagesLeft--;
  1524. if (!getPagesLeft) {
  1525. resolvePagesPromise();
  1526. }
  1527. }.bind(null, pageNum));
  1528. }
  1529. } else {
  1530. // XXX: Printing is semi-broken with auto fetch disabled.
  1531. resolvePagesPromise();
  1532. }
  1533. });
  1534. var event = document.createEvent('CustomEvent');
  1535. event.initCustomEvent('pagesinit', true, true, null);
  1536. self.container.dispatchEvent(event);
  1537. if (this.defaultRenderingQueue) {
  1538. this.update();
  1539. }
  1540. }.bind(this));
  1541. },
  1542. _resetView: function () {
  1543. this.cache = new Cache(DEFAULT_CACHE_SIZE);
  1544. this.pages = [];
  1545. this._currentPageNumber = 1;
  1546. this._currentScale = UNKNOWN_SCALE;
  1547. this._currentScaleValue = null;
  1548. this.location = null;
  1549. this._pagesRotation = 0;
  1550. var container = this.viewer;
  1551. while (container.hasChildNodes()) {
  1552. container.removeChild(container.lastChild);
  1553. }
  1554. },
  1555. _scrollUpdate: function () {
  1556. this.lastScroll = Date.now();
  1557. if (this.pagesCount === 0) {
  1558. return;
  1559. }
  1560. this.update();
  1561. },
  1562. _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
  1563. newScale, newValue, noScroll, preset) {
  1564. this._currentScaleValue = newValue;
  1565. if (newScale === this._currentScale) {
  1566. return;
  1567. }
  1568. for (var i = 0, ii = this.pages.length; i < ii; i++) {
  1569. this.pages[i].update(newScale);
  1570. }
  1571. this._currentScale = newScale;
  1572. if (!noScroll) {
  1573. var page = this._currentPageNumber, dest;
  1574. var inPresentationMode =
  1575. this.presentationModeState === PresentationModeState.CHANGING ||
  1576. this.presentationModeState === PresentationModeState.FULLSCREEN;
  1577. if (this.location && !inPresentationMode &&
  1578. !IGNORE_CURRENT_POSITION_ON_ZOOM) {
  1579. page = this.location.pageNumber;
  1580. dest = [null, { name: 'XYZ' }, this.location.left,
  1581. this.location.top, null];
  1582. }
  1583. this.scrollPageIntoView(page, dest);
  1584. }
  1585. var event = document.createEvent('UIEvents');
  1586. event.initUIEvent('scalechange', true, true, window, 0);
  1587. event.scale = newScale;
  1588. if (preset) {
  1589. event.presetValue = newValue;
  1590. }
  1591. this.container.dispatchEvent(event);
  1592. },
  1593. _setScale: function pdfViewer_setScale(value, noScroll) {
  1594. if (value === 'custom') {
  1595. return;
  1596. }
  1597. var scale = parseFloat(value);
  1598. if (scale > 0) {
  1599. this._setScaleUpdatePages(scale, value, noScroll, false);
  1600. } else {
  1601. var currentPage = this.pages[this._currentPageNumber - 1];
  1602. if (!currentPage) {
  1603. return;
  1604. }
  1605. var inPresentationMode =
  1606. this.presentationModeState === PresentationModeState.FULLSCREEN;
  1607. var hPadding = inPresentationMode ? 0 : SCROLLBAR_PADDING;
  1608. var vPadding = inPresentationMode ? 0 : VERTICAL_PADDING;
  1609. var pageWidthScale = (this.container.clientWidth - hPadding) /
  1610. currentPage.width * currentPage.scale;
  1611. var pageHeightScale = (this.container.clientHeight - vPadding) /
  1612. currentPage.height * currentPage.scale;
  1613. switch (value) {
  1614. case 'page-actual':
  1615. scale = 1;
  1616. break;
  1617. case 'page-width':
  1618. scale = pageWidthScale;
  1619. break;
  1620. case 'page-height':
  1621. scale = pageHeightScale;
  1622. break;
  1623. case 'page-fit':
  1624. scale = Math.min(pageWidthScale, pageHeightScale);
  1625. break;
  1626. case 'auto':
  1627. var isLandscape = (currentPage.width > currentPage.height);
  1628. // For pages in landscape mode, fit the page height to the viewer
  1629. // *unless* the page would thus become too wide to fit horizontally.
  1630. var horizontalScale = isLandscape ?
  1631. Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
  1632. scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
  1633. break;
  1634. default:
  1635. console.error('pdfViewSetScale: \'' + value +
  1636. '\' is an unknown zoom value.');
  1637. return;
  1638. }
  1639. this._setScaleUpdatePages(scale, value, noScroll, true);
  1640. }
  1641. },
  1642. /**
  1643. * Scrolls page into view.
  1644. * @param {number} pageNumber
  1645. * @param {Array} dest - (optional) original PDF destination array:
  1646. * <page-ref> </XYZ|FitXXX> <args..>
  1647. */
  1648. scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
  1649. dest) {
  1650. var pageView = this.pages[pageNumber - 1];
  1651. var pageViewDiv = pageView.el;
  1652. if (this.presentationModeState ===
  1653. PresentationModeState.FULLSCREEN) {
  1654. if (this.linkService.page !== pageView.id) {
  1655. // Avoid breaking getVisiblePages in presentation mode.
  1656. this.linkService.page = pageView.id;
  1657. return;
  1658. }
  1659. dest = null;
  1660. // Fixes the case when PDF has different page sizes.
  1661. this._setScale(this.currentScaleValue, true);
  1662. }
  1663. if (!dest) {
  1664. scrollIntoView(pageViewDiv);
  1665. return;
  1666. }
  1667. var x = 0, y = 0;
  1668. var width = 0, height = 0, widthScale, heightScale;
  1669. var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
  1670. var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
  1671. pageView.scale / CSS_UNITS;
  1672. var pageHeight = (changeOrientation ? pageView.width : pageView.height) /
  1673. pageView.scale / CSS_UNITS;
  1674. var scale = 0;
  1675. switch (dest[1].name) {
  1676. case 'XYZ':
  1677. x = dest[2];
  1678. y = dest[3];
  1679. scale = dest[4];
  1680. // If x and/or y coordinates are not supplied, default to
  1681. // _top_ left of the page (not the obvious bottom left,
  1682. // since aligning the bottom of the intended page with the
  1683. // top of the window is rarely helpful).
  1684. x = x !== null ? x : 0;
  1685. y = y !== null ? y : pageHeight;
  1686. break;
  1687. case 'Fit':
  1688. case 'FitB':
  1689. scale = 'page-fit';
  1690. break;
  1691. case 'FitH':
  1692. case 'FitBH':
  1693. y = dest[2];
  1694. scale = 'page-width';
  1695. break;
  1696. case 'FitV':
  1697. case 'FitBV':
  1698. x = dest[2];
  1699. width = pageWidth;
  1700. height = pageHeight;
  1701. scale = 'page-height';
  1702. break;
  1703. case 'FitR':
  1704. x = dest[2];
  1705. y = dest[3];
  1706. width = dest[4] - x;
  1707. height = dest[5] - y;
  1708. var viewerContainer = this.container;
  1709. widthScale = (viewerContainer.clientWidth - SCROLLBAR_PADDING) /
  1710. width / CSS_UNITS;
  1711. heightScale = (viewerContainer.clientHeight - SCROLLBAR_PADDING) /
  1712. height / CSS_UNITS;
  1713. scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
  1714. break;
  1715. default:
  1716. return;
  1717. }
  1718. if (scale && scale !== this.currentScale) {
  1719. this.currentScaleValue = scale;
  1720. } else if (this.currentScale === UNKNOWN_SCALE) {
  1721. this.currentScaleValue = DEFAULT_SCALE;
  1722. }
  1723. if (scale === 'page-fit' && !dest[4]) {
  1724. scrollIntoView(pageViewDiv);
  1725. return;
  1726. }
  1727. var boundingRect = [
  1728. pageView.viewport.convertToViewportPoint(x, y),
  1729. pageView.viewport.convertToViewportPoint(x + width, y + height)
  1730. ];
  1731. var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
  1732. var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
  1733. scrollIntoView(pageViewDiv, { left: left, top: top });
  1734. },
  1735. _updateLocation: function (firstPage) {
  1736. var currentScale = this._currentScale;
  1737. var currentScaleValue = this._currentScaleValue;
  1738. var normalizedScaleValue =
  1739. parseFloat(currentScaleValue) === currentScale ?
  1740. Math.round(currentScale * 10000) / 100 : currentScaleValue;
  1741. var pageNumber = firstPage.id;
  1742. var pdfOpenParams = '#page=' + pageNumber;
  1743. pdfOpenParams += '&zoom=' + normalizedScaleValue;
  1744. var currentPageView = this.pages[pageNumber - 1];
  1745. var container = this.container;
  1746. var topLeft = currentPageView.getPagePoint(
  1747. (container.scrollLeft - firstPage.x),
  1748. (container.scrollTop - firstPage.y));
  1749. var intLeft = Math.round(topLeft[0]);
  1750. var intTop = Math.round(topLeft[1]);
  1751. pdfOpenParams += ',' + intLeft + ',' + intTop;
  1752. this.location = {
  1753. pageNumber: pageNumber,
  1754. scale: normalizedScaleValue,
  1755. top: intTop,
  1756. left: intLeft,
  1757. pdfOpenParams: pdfOpenParams
  1758. };
  1759. },
  1760. update: function () {
  1761. var visible = this._getVisiblePages();
  1762. var visiblePages = visible.views;
  1763. if (visiblePages.length === 0) {
  1764. return;
  1765. }
  1766. this.updateInProgress = true;
  1767. var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
  1768. 2 * visiblePages.length + 1);
  1769. this.cache.resize(suggestedCacheSize);
  1770. this.renderingQueue.renderHighestPriority(visible);
  1771. var currentId = this.currentPageNumber;
  1772. var firstPage = visible.first;
  1773. for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
  1774. i < ii; ++i) {
  1775. var page = visiblePages[i];
  1776. if (page.percent < 100) {
  1777. break;
  1778. }
  1779. if (page.id === currentId) {
  1780. stillFullyVisible = true;
  1781. break;
  1782. }
  1783. }
  1784. if (!stillFullyVisible) {
  1785. currentId = visiblePages[0].id;
  1786. }
  1787. if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
  1788. this.currentPageNumber = currentId;
  1789. }
  1790. this._updateLocation(firstPage);
  1791. this.updateInProgress = false;
  1792. var event = document.createEvent('UIEvents');
  1793. event.initUIEvent('updateviewarea', true, true, window, 0);
  1794. this.container.dispatchEvent(event);
  1795. },
  1796. containsElement: function (element) {
  1797. return this.container.contains(element);
  1798. },
  1799. focus: function () {
  1800. this.container.focus();
  1801. },
  1802. blur: function () {
  1803. this.container.blur();
  1804. },
  1805. get isHorizontalScrollbarEnabled() {
  1806. return (this.presentationModeState === PresentationModeState.FULLSCREEN ?
  1807. false : (this.container.scrollWidth > this.container.clientWidth));
  1808. },
  1809. _getVisiblePages: function () {
  1810. if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
  1811. return getVisibleElements(this.container, this.pages, true);
  1812. } else {
  1813. // The algorithm in getVisibleElements doesn't work in all browsers and
  1814. // configurations when presentation mode is active.
  1815. var visible = [];
  1816. var currentPage = this.pages[this._currentPageNumber - 1];
  1817. visible.push({ id: currentPage.id, view: currentPage });
  1818. return { first: currentPage, last: currentPage, views: visible };
  1819. }
  1820. },
  1821. cleanup: function () {
  1822. for (var i = 0, ii = this.pages.length; i < ii; i++) {
  1823. if (this.pages[i] &&
  1824. this.pages[i].renderingState !== RenderingStates.FINISHED) {
  1825. this.pages[i].reset();
  1826. }
  1827. }
  1828. },
  1829. forceRendering: function (currentlyVisiblePages) {
  1830. var visiblePages = currentlyVisiblePages || this._getVisiblePages();
  1831. var pageView = this.renderingQueue.getHighestPriority(visiblePages,
  1832. this.pages,
  1833. this.scroll.down);
  1834. if (pageView) {
  1835. this.renderingQueue.renderView(pageView);
  1836. return true;
  1837. }
  1838. return false;
  1839. },
  1840. getPageTextContent: function (pageIndex) {
  1841. return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
  1842. return page.getTextContent();
  1843. });
  1844. },
  1845. /**
  1846. * @param textLayerDiv {HTMLDivElement}
  1847. * @param pageIndex {number}
  1848. * @param viewport {PageViewport}
  1849. * @returns {TextLayerBuilder}
  1850. */
  1851. createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
  1852. var isViewerInPresentationMode =
  1853. this.presentationModeState === PresentationModeState.FULLSCREEN;
  1854. return new TextLayerBuilder({
  1855. textLayerDiv: textLayerDiv,
  1856. pageIndex: pageIndex,
  1857. viewport: viewport,
  1858. lastScrollSource: this,
  1859. isViewerInPresentationMode: isViewerInPresentationMode,
  1860. findController: this.findController
  1861. });
  1862. },
  1863. setFindController: function (findController) {
  1864. this.findController = findController;
  1865. },
  1866. };
  1867. return PDFViewer;
  1868. })();
  1869. var SimpleLinkService = (function SimpleLinkServiceClosure() {
  1870. function SimpleLinkService(pdfViewer) {
  1871. this.pdfViewer = pdfViewer;
  1872. }
  1873. SimpleLinkService.prototype = {
  1874. /**
  1875. * @returns {number}
  1876. */
  1877. get page() {
  1878. return this.pdfViewer.currentPageNumber;
  1879. },
  1880. /**
  1881. * @param {number} value
  1882. */
  1883. set page(value) {
  1884. this.pdfViewer.currentPageNumber = value;
  1885. },
  1886. /**
  1887. * @param dest - The PDF destination object.
  1888. */
  1889. navigateTo: function (dest) {},
  1890. /**
  1891. * @param dest - The PDF destination object.
  1892. * @returns {string} The hyperlink to the PDF object.
  1893. */
  1894. getDestinationHash: function (dest) {
  1895. return '#';
  1896. },
  1897. /**
  1898. * @param hash - The PDF parameters/hash.
  1899. * @returns {string} The hyperlink to the PDF object.
  1900. */
  1901. getAnchorUrl: function (hash) {
  1902. return '#';
  1903. },
  1904. /**
  1905. * @param {string} hash
  1906. */
  1907. setHash: function (hash) {},
  1908. /**
  1909. * @param {string} action
  1910. */
  1911. executeNamedAction: function (action) {},
  1912. };
  1913. return SimpleLinkService;
  1914. })();
  1915. /**
  1916. * PDFPage object source.
  1917. * @class
  1918. */
  1919. var PDFPageSource = (function PDFPageSourceClosure() {
  1920. /**
  1921. * @constructs
  1922. * @param {PDFDocument} pdfDocument
  1923. * @param {number} pageNumber
  1924. * @constructor
  1925. */
  1926. function PDFPageSource(pdfDocument, pageNumber) {
  1927. this.pdfDocument = pdfDocument;
  1928. this.pageNumber = pageNumber;
  1929. }
  1930. PDFPageSource.prototype = /** @lends PDFPageSource.prototype */ {
  1931. /**
  1932. * @returns {Promise<PDFPage>}
  1933. */
  1934. getPage: function () {
  1935. return this.pdfDocument.getPage(this.pageNumber);
  1936. }
  1937. };
  1938. return PDFPageSource;
  1939. })();
  1940. PDFJS.PDFViewer = PDFViewer;
  1941. }).call((typeof window === 'undefined') ? this : window);