PdfListView.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972
  1. (function (name, context, definition) {
  2. if (typeof module != 'undefined' && module.exports) module.exports = definition()
  3. else if (typeof define == 'function' && define.amd) define(definition)
  4. else context[name] = definition()
  5. })('PDFListView', this, function (name, context) {
  6. //#expand __BUNDLE__
  7. function Logger() {
  8. this.logLevel = Logger.INFO;
  9. var self = this;
  10. if (typeof(console) == "object" && typeof(console.log) == "function") {
  11. this.debug = function() {
  12. if (self.logLevel <= Logger.DEBUG) {
  13. console.log.apply(console, arguments);
  14. }
  15. };
  16. this.info = function() {
  17. if (self.logLevel <= Logger.INFO) {
  18. console.log.apply(console, arguments);
  19. }
  20. };
  21. this.error = function() {
  22. if (self.logLevel <= Logger.ERROR) {
  23. console.log.apply(console, arguments);
  24. }
  25. };
  26. } else {
  27. this.debug = this.info = this.error = function nop() {};
  28. }
  29. }
  30. Logger.DEBUG = 0;
  31. Logger.INFO = 1;
  32. Logger.ERROR = 2;
  33. var logger = new Logger();
  34. function _flat(arr) {
  35. var res = arr.reduce(function(a, b) {
  36. return a.concat(b);
  37. });
  38. return res;
  39. }
  40. function failDumper(err) {
  41. logger.error(err);
  42. }
  43. if (typeof(PDFJS) === "undefined") {
  44. logger.error("PDF.js is not yet loaded.");
  45. }
  46. // -----------------------------------------------------------------------------
  47. /**
  48. * Wrapper around the raw PDF.JS document.
  49. */
  50. function Document(url, password, onProgress) {
  51. this.pdfDocument = null;
  52. this.pages = null;
  53. var parameters = {password: password};
  54. if (typeof url === 'string') { // URL
  55. parameters.url = url;
  56. } else if (url && 'byteLength' in url) { // ArrayBuffer
  57. parameters.data = url;
  58. }
  59. this.initialized = new PDFJS.LegacyPromise();
  60. PDFJS.getDocument(parameters, null, null, onProgress).then(this.loadPages.bind(this), failDumper);
  61. }
  62. Document.prototype.loadPages = function(pdfDocument) {
  63. this.pdfDocument = pdfDocument;
  64. var pagesCount = this.pagesCount = pdfDocument.numPages;
  65. var pagePromises = [];
  66. for (var i = 1; i <= pagesCount; i++) {
  67. pagePromises.push(pdfDocument.getPage(i));
  68. }
  69. this.pageRefMap = pageRefMap = {};
  70. var pagesPromise = Promise.all(pagePromises);
  71. var doc = this
  72. pagesPromise.then(function(promisedPages) {
  73. doc.pages = promisedPages.map(function(pdfPage, i) {
  74. var pageRef = pdfPage.ref
  75. pageRefMap[pageRef.num + ' ' + pageRef.gen] = i;
  76. return new Page(pdfPage);
  77. });
  78. });
  79. var destinationsPromise = pdfDocument.getDestinations();
  80. destinationsPromise.then(function(destinations) {
  81. doc.destinations = destinations;
  82. });
  83. Promise.all([pagesPromise, destinationsPromise]).then(function() {
  84. doc.initialized.resolve();
  85. }, failDumper);
  86. };
  87. /**
  88. * Handles the rendering. Multiple ListViews can be bound to a RenderController.
  89. * In that case, the RenderController figures out what's page has the highest
  90. * priority to render
  91. */
  92. function RenderController() {
  93. this.listViews = [];
  94. this.renderList = [];
  95. }
  96. RenderController.prototype = {
  97. addListView: function(listView) {
  98. // TODO: assert listView not already in list of this.listView
  99. this.listViews.push(listView);
  100. },
  101. updateRenderList: function() {
  102. this.renderList = _flat(this.listViews.map(function(listView) {
  103. return listView.getPagesToRender();
  104. }));
  105. // TODO: Some "highest-priority" sorting algorithm on the renderList.
  106. this.doRender();
  107. },
  108. pageToRender: function() {
  109. if (this.renderList.length === 0) return null;
  110. return this.renderList[0];
  111. },
  112. doRender: function() {
  113. var pageToRender = this.pageToRender();
  114. if (!pageToRender) return;
  115. pageToRender.render(this);
  116. },
  117. finishedRendering: function(pageView) {
  118. var idx = this.renderList.indexOf(pageView);
  119. pageView.callAfterRenderedCallbacks();
  120. // If the finished pageView is in the list of pages to render,
  121. // then remove it from the list and render start rendering the
  122. // next page.
  123. if (idx !== -1) {
  124. this.renderList.splice(idx, 1);
  125. this.doRender();
  126. }
  127. },
  128. onResize: function() {
  129. var renderAgain = false
  130. this.listViews.map(function(listView) {
  131. if (listView.calculateScale()) {
  132. listView.layout();
  133. renderAgain = true;
  134. }
  135. });
  136. if (renderAgain) {
  137. this.updateRenderList();
  138. }
  139. }
  140. };
  141. var LAYOUT_SINGLE = 'layout_single';
  142. var SCALE_MODE_AUTO = 'scale_mode_auto';
  143. var SCALE_MODE_VALUE = 'scale_mode_value';
  144. var SCALE_MODE_FIT_WIDTH = 'scale_mode_fit_width';
  145. var SCALE_MODE_FIT_HEIGHT = 'scale_mode_fit_height';
  146. /**
  147. * Main view that holds the single pageContainer/pageViews of the pdfDoc.
  148. */
  149. function ListView(dom, options) {
  150. this.dom = dom;
  151. this.options = options;
  152. this.pageLayout = LAYOUT_SINGLE;
  153. this.scaleMode = SCALE_MODE_VALUE;
  154. this.scale = 1.0;
  155. this.pageWidthOffset = 0;
  156. this.pageHeightOffset = 0;
  157. this.pageViews = [];
  158. this.containerViews = [];
  159. }
  160. ListView.prototype = {
  161. setDocument: function(pdfDoc) {
  162. this.clearPages();
  163. this.pdfDoc = pdfDoc;
  164. this.assignPagesToContainer();
  165. this.layout();
  166. },
  167. clearPages: function() {
  168. var self = this;
  169. this.containerViews.map(function(container) {
  170. self.dom.removeChild(container.dom);
  171. });
  172. this.pageViews = [];
  173. this.containerViews = [];
  174. },
  175. assignPagesToContainer: function() {
  176. // TODO: Handle multiple layout types here. For now, assume to have one page
  177. // per pageContainer.
  178. this.pdfDoc.pages.map(function(page) {
  179. var pageView = new PageView(page, this);
  180. // TODO: Switch over to a proper event handler
  181. var that = this;
  182. var index = that.pageViews.length;
  183. pageView.ondblclick = function(e) {
  184. e.page = index;
  185. if (that.ondblclick) {
  186. that.ondblclick.call(that, e);
  187. }
  188. }
  189. this.pageViews.push(pageView);
  190. var container = new PageContainerView(this);
  191. container.setPageView(pageView, 0);
  192. this.containerViews.push(container);
  193. this.dom.appendChild(container.dom);
  194. }, this);
  195. },
  196. layout: function() {
  197. this.savePdfPosition();
  198. this.containerViews.forEach(function(containerView) {
  199. containerView.layout();
  200. });
  201. this.restorePdfPosition();
  202. },
  203. getScale: function() {
  204. return this.scale;
  205. },
  206. getScaleMode: function() {
  207. return this.scaleMode;
  208. },
  209. setScaleMode: function(scaleMode, scale) {
  210. this.scaleMode = scaleMode;
  211. if (scaleMode == SCALE_MODE_VALUE) {
  212. this.scale = scale;
  213. }
  214. this.calculateScale();
  215. this.layout();
  216. },
  217. setScale: function(scale) {
  218. this.setScaleMode(SCALE_MODE_VALUE, scale);
  219. },
  220. setToAutoScale: function() {
  221. this.setScaleMode(SCALE_MODE_AUTO);
  222. },
  223. setToFitWidth: function() {
  224. this.setScaleMode(SCALE_MODE_FIT_WIDTH);
  225. },
  226. setToFitHeight: function() {
  227. this.setScaleMode(SCALE_MODE_FIT_HEIGHT);
  228. },
  229. // Calculates the new scale. Returns `true` if the scale changed.
  230. calculateScale: function() {
  231. var newScale = this.scale;
  232. var oldScale = newScale;
  233. var scaleMode = this.scaleMode;
  234. if (scaleMode === SCALE_MODE_FIT_WIDTH || scaleMode === SCALE_MODE_AUTO) {
  235. var clientWidth = this.dom.clientWidth;
  236. if (clientWidth == 0) {
  237. logger.debug("LIST VIEW NOT VISIBLE")
  238. return false;
  239. }
  240. var maxNormalWidth = 0;
  241. this.containerViews.forEach(function(containerView) {
  242. maxNormalWidth = Math.max(maxNormalWidth, containerView.normalWidth);
  243. });
  244. var scale = (clientWidth - this.pageWidthOffset)/maxNormalWidth;
  245. if (scaleMode === SCALE_MODE_AUTO) {
  246. scale = Math.min(1.0, scale);
  247. }
  248. newScale = scale;
  249. } else if (scaleMode === SCALE_MODE_FIT_HEIGHT) {
  250. var clientHeight = this.dom.clientHeight;
  251. if (clientHeight == 0) {
  252. logger.debug("LIST VIEW NOT VISIBLE")
  253. return false;
  254. }
  255. var maxNormalHeight = 0;
  256. this.containerViews.forEach(function(containerView) {
  257. maxNormalHeight = Math.max(maxNormalHeight, containerView.normalHeight);
  258. });
  259. newScale = (clientHeight - this.pageHeightOffset)/maxNormalHeight;
  260. }
  261. this.scale = newScale;
  262. return newScale !== oldScale;
  263. },
  264. getPagesToRender: function() {
  265. // Cache these results to avoid dom access.
  266. this.scrollTop = this.dom.scrollTop;
  267. this.scrollBottom = this.scrollTop + this.dom.clientHeight;
  268. // TODO: For now, this only returns the visible pages and not
  269. // +1/-1 one to render in advance.
  270. return this.pageViews.filter(function(pageView) {
  271. var isVisible = pageView.isVisible();
  272. if (isVisible && !pageView.isRendered) {
  273. return true;
  274. }
  275. });
  276. },
  277. navigateTo: function(destRef) {
  278. var destination = this.pdfDoc.destinations[destRef]
  279. if (typeof destination !== "object") {
  280. return;
  281. }
  282. logger.debug("NAVIGATING TO", destination);
  283. var pageRef = destination[0];
  284. var pageNumber = this.pdfDoc.pageRefMap[pageRef.num + ' ' + pageRef.gen];
  285. if (typeof pageNumber !== "number") {
  286. return;
  287. }
  288. logger.debug("PAGE NUMBER", pageNumber);
  289. var pageView = this.pageViews[pageNumber];
  290. var destinationType = destination[1].name;
  291. switch(destinationType) {
  292. case "XYZ":
  293. var x = destination[2];
  294. var y = destination[3];
  295. break;
  296. default:
  297. // TODO
  298. return;
  299. }
  300. var position = pageView.getPdfPositionInViewer(x,y);
  301. this.dom.scrollTop = position.top;
  302. this.dom.scrollLeft = position.left;
  303. },
  304. savePdfPosition: function() {
  305. this.pdfPosition = this.getPdfPosition();
  306. logger.debug("SAVED PDF POSITION", this.pdfPosition);
  307. },
  308. restorePdfPosition: function() {
  309. logger.debug("RESTORING PDF POSITION", this.pdfPosition);
  310. this.setPdfPosition(this.pdfPosition);
  311. },
  312. getPdfPosition: function(fromTop) {
  313. var pdfPosition = null;
  314. for (var i = 0; i < this.pageViews.length; i++) {
  315. var pageView = this.pageViews[i];
  316. var pdfOffset = pageView.getUppermostVisiblePdfOffset();
  317. if (pdfOffset !== null) {
  318. if (fromTop) {
  319. pdfOffset = pageView.normalHeight - pdfOffset;
  320. }
  321. pdfPosition = {
  322. page: i,
  323. offset: {
  324. top: pdfOffset,
  325. left: 0 // TODO
  326. }
  327. }
  328. break;
  329. }
  330. }
  331. return pdfPosition;
  332. },
  333. setPdfPosition: function(pdfPosition, fromTop) {
  334. if (typeof pdfPosition !== "undefined" && pdfPosition != null) {
  335. var offset = pdfPosition.offset;
  336. var page_index = pdfPosition.page;
  337. var pageView = this.pageViews[page_index];
  338. if (fromTop) {
  339. offset.top = pageView.normalHeight - offset.top;
  340. }
  341. var position = pageView.getPdfPositionInViewer(offset.left, offset.top);
  342. this.dom.scrollTop = position.top;
  343. }
  344. },
  345. setHighlights: function(highlights, fromTop) {
  346. for (i = 0; i < highlights.length; i++) {
  347. var pageIndex = highlights[i].page;
  348. var pageView = this.pageViews[pageIndex];
  349. (function(pageView, highlight) {
  350. pageView.doWhenRendered(function() {
  351. pageView.addHighlight(highlight.highlight, fromTop);
  352. });
  353. })(pageView, highlights[i]);
  354. }
  355. },
  356. clearHighlights: function() {
  357. for (var i = 0; i < this.pageViews.length; i++) {
  358. var pageView = this.pageViews[i].clearHighlights();
  359. }
  360. }
  361. };
  362. /*
  363. * A PageContainerView holds multiple PageViews. E.g. in a two-page layout,
  364. * every pageContainerView holds two PageViews and is responsible to layout
  365. * them.
  366. */
  367. function PageContainerView(listView) {
  368. this.listView = listView;
  369. var dom = this.dom = document.createElement('div');
  370. dom.className = 'plv-page-container page-container';
  371. this.pages = [];
  372. }
  373. PageContainerView.prototype = {
  374. setPageView: function(pageView, idx) {
  375. // TODO: handle case if there is already a page here
  376. this.pages[idx] = pageView;
  377. // TODO: handle page idx properly
  378. this.dom.appendChild(pageView.dom);
  379. },
  380. removePageView: function(idx) {
  381. // TODO: check if idx is set on page[]
  382. this.dom.removeChild(this.pages[idx].dom);
  383. },
  384. layout: function() {
  385. var scale = this.listView.scale;
  386. var normalWidth = 0;
  387. var normalHeight = 0;
  388. this.pages.forEach(function(pageView) {
  389. pageView.layout();
  390. normalWidth += pageView.normalWidth;
  391. normalHeight = Math.max(pageView.normalHeight, normalHeight);
  392. });
  393. this.normalWidth = normalWidth;
  394. this.normalHeight = normalHeight;
  395. this.dom.style.width = (normalWidth * scale) + 'px';
  396. this.dom.style.height = (normalHeight * scale) + 'px';
  397. }
  398. };
  399. var RenderingStates = {
  400. INITIAL: 0,
  401. RUNNING: 1,
  402. PAUSED: 2,
  403. FINISHED: 3
  404. };
  405. var idCounter = 0;
  406. /**
  407. * The view for a single page.
  408. */
  409. function PageView(page, listView) {
  410. this.page = page;
  411. this.listView = listView;
  412. this.id = idCounter++;
  413. this.number = this.page.number;
  414. this.rotation = 0;
  415. this.isRendered = false;
  416. this.renderState = RenderingStates.INITIAL;
  417. this.afterRenderCallbacks = [];
  418. var dom = this.dom = document.createElement('div');
  419. dom.className = "plv-page-view page-view";
  420. var that = this;
  421. dom.ondblclick = function(e) {
  422. var layerX = e.layerX;
  423. var layerY = e.layerY;
  424. var element = e.target;
  425. while (element.offsetParent && element.offsetParent !== dom) {
  426. layerX = layerX + element.offsetLeft;
  427. layerY = layerY + element.offsetTop;
  428. element = element.offsetParent;
  429. }
  430. var pdfPoint = that.viewport.convertToPdfPoint(layerX, layerY);
  431. var event = {
  432. x: pdfPoint[0],
  433. y: that.normalHeight - pdfPoint[1]
  434. };
  435. if (that.ondblclick) {
  436. that.ondblclick.call(that.listView, event)
  437. }
  438. }
  439. this.createNewCanvas();
  440. }
  441. PageView.prototype = {
  442. layout: function() {
  443. var scale = this.listView.scale;
  444. var viewport = this.viewport =
  445. this.page.pdfPage.getViewport(scale, this.rotation);
  446. this.normalWidth = viewport.width / scale;
  447. this.normalHeight = viewport.height / scale;
  448. // Only change the width/height property of the canvas if it really
  449. // changed. Every assignment to the width/height property clears the
  450. // content of the canvas.
  451. var outputScale = this.getOutputScale();
  452. var scaledWidth = (Math.floor(viewport.width) * outputScale.sx) | 0;
  453. var scaledHeight = (Math.floor(viewport.height) * outputScale.sy) | 0;
  454. var newWidth = Math.floor(viewport.width);
  455. var newHeight = Math.floor(viewport.height);
  456. if (this.canvas.width !== newWidth) {
  457. this.canvas.width = scaledWidth;
  458. this.canvas.style.width = newWidth + 'px';
  459. this.resetRenderState();
  460. }
  461. if (this.canvas.height !== newHeight) {
  462. this.canvas.height = scaledHeight;
  463. this.canvas.style.height = newHeight + 'px';
  464. this.resetRenderState();
  465. }
  466. if(outputScale.scaled){
  467. var ctx = this.getCanvasContext()
  468. ctx.scale(outputScale.sx, outputScale.sy)
  469. }
  470. this.width = viewport.width;
  471. this.height = viewport.height;
  472. },
  473. isVisible: function() {
  474. var listView = this.listView;
  475. var dom = this.dom;
  476. var offsetTop = dom.offsetTop;
  477. var offsetBottom = offsetTop + this.height;
  478. return offsetBottom >= listView.scrollTop &&
  479. offsetTop <= listView.scrollBottom;
  480. },
  481. resetRenderState: function() {
  482. this.renderState = RenderingStates.INITIAL;
  483. this.isRendered = false;
  484. if (this.textLayerDiv) {
  485. this.dom.removeChild(this.textLayerDiv);
  486. delete this.textLayerDiv;
  487. }
  488. if (this.annotationsLayerDiv) {
  489. this.dom.removeChild(this.annotationsLayerDiv);
  490. delete this.annotationsLayerDiv;
  491. }
  492. },
  493. render: function(renderController) {
  494. return this.page.render(this, renderController);
  495. },
  496. getCanvasContext: function() {
  497. return this.canvas.getContext('2d');
  498. },
  499. /**
  500. * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
  501. * @return {Object} The object with horizontal (sx) and vertical (sy)
  502. scales. The scaled property is set to false if scaling is
  503. not required, true otherwise.
  504. */
  505. getOutputScale: function(){
  506. var ctx = this.getCanvasContext()
  507. var devicePixelRatio = window.devicePixelRatio || 1;
  508. var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
  509. ctx.mozBackingStorePixelRatio ||
  510. ctx.msBackingStorePixelRatio ||
  511. ctx.oBackingStorePixelRatio ||
  512. ctx.backingStorePixelRatio || 1;
  513. var pixelRatio = devicePixelRatio / backingStoreRatio;
  514. return {
  515. sx: pixelRatio,
  516. sy: pixelRatio,
  517. scaled: pixelRatio != 1
  518. };
  519. },
  520. createNewCanvas: function() {
  521. if (this.canvas) {
  522. this.dom.removeChild(this.canvas);
  523. }
  524. var canvas = this.canvas = document.createElement('canvas');
  525. this.dom.appendChild(canvas);
  526. this.layout();
  527. },
  528. pdfPositionToPixels: function(x, y) {
  529. return this.viewport.convertToViewportPoint(x, y);
  530. },
  531. getCanvasPositionInViewer: function() {
  532. return {
  533. left: this.canvas.offsetLeft + this.dom.offsetLeft,
  534. top: this.canvas.offsetTop + this.dom.offsetTop
  535. }
  536. },
  537. getPdfPositionInViewer: function(x, y) {
  538. pageOffset = this.pdfPositionToPixels(x, y);
  539. canvasOffset = this.getCanvasPositionInViewer();
  540. return {
  541. left: canvasOffset.left + pageOffset[0],
  542. top: canvasOffset.top + pageOffset[1]
  543. }
  544. },
  545. getUppermostVisibleCanvasOffset: function() {
  546. var pagePosition = this.getCanvasPositionInViewer();
  547. var pageHeight = this.canvas.height;
  548. var viewportTop = this.listView.dom.scrollTop;
  549. var viewportHeight = this.listView.dom.clientHeight;
  550. // Check if the top of the page is showing, i.e:
  551. // _______________
  552. // | |
  553. // | ........ |
  554. // | . . |
  555. // ----.------.---
  556. // . .
  557. // ........
  558. var topVisible = (pagePosition.top > viewportTop && pagePosition.top < viewportTop + viewportHeight);
  559. // Check if at least some of the page is showing, i.e:
  560. // ........ ........
  561. // ____.______.___ ---.------.---
  562. // | . . | or | . . |
  563. // | . . | | . . |
  564. // | ........ | | . . |
  565. // --------------- ---.------.---
  566. // ........
  567. var someContentVisible = (pagePosition.top < viewportTop && pagePosition.top + pageHeight > viewportTop);
  568. if (topVisible) {
  569. return 0;
  570. } else if (someContentVisible) {
  571. return viewportTop - pagePosition.top;
  572. } else {
  573. return null;
  574. }
  575. },
  576. getUppermostVisiblePdfOffset: function() {
  577. var canvasOffset = this.getUppermostVisibleCanvasOffset();
  578. if (canvasOffset === null) {
  579. return null;
  580. }
  581. var pdfOffset = this.viewport.convertToPdfPoint(0, canvasOffset);
  582. return pdfOffset[1];
  583. },
  584. clearHighlights: function() {
  585. if (this.highlightsLayer) {
  586. this.highlightsLayer.clearHighlights();
  587. }
  588. },
  589. addHighlight: function(highlight, fromTop) {
  590. if (this.highlightsLayer) {
  591. var top = highlight.top;
  592. var left = highlight.left;
  593. var width = highlight.width;
  594. var height = highlight.height;
  595. if (fromTop) {
  596. top = this.normalHeight - top;
  597. }
  598. this.highlightsLayer.addHighlight(left, top, width, height);
  599. }
  600. },
  601. doWhenRendered: function(callback) {
  602. if (this.isRendered) {
  603. callback()
  604. } else {
  605. this.afterRenderCallbacks.push(callback);
  606. }
  607. },
  608. callAfterRenderedCallbacks: function () {
  609. var callbacks = this.afterRenderCallbacks;
  610. for (var i = 0; i < callbacks.length; i++) {
  611. callbacks[i]();
  612. }
  613. this.afterRenderCallbacks = [];
  614. }
  615. };
  616. /**
  617. * An abstraction around the raw page object of PDF.JS, that also handles the
  618. * rendering logic of (maybe multiple) pageView(s) that are based on this page.
  619. */
  620. function Page(pdfPage, number) {
  621. this.number = number;
  622. this.pdfPage = pdfPage;
  623. this.renderContextList = {};
  624. }
  625. Page.prototype = {
  626. render: function(pageView, renderController) {
  627. var renderContext;
  628. // FEATURE: If the page was rendered already once, then use the old
  629. // version as a placeholder until the new version is rendered at the
  630. // expected quality.
  631. // FEATURE: If the page can be rendered at low quality (thumbnail) and
  632. // there is already a higher resolution rendering, then use this one
  633. // instead of rerendering from scratch again.
  634. // PageView is not layouted.
  635. if (!pageView.viewport) return;
  636. // Nothing todo.
  637. if (pageView.isRendered) return;
  638. // Not most important page to render ATM.
  639. if (renderController.pageToRender() !== pageView) return;
  640. var self = this;
  641. var viewport;
  642. if (renderContext = this.renderContextList[pageView.id]) {
  643. viewport = renderContext.viewport;
  644. // TODO: handle rotation
  645. if (viewport.height !== pageView.viewport.height ||
  646. viewport.height !== pageView.viewport.height)
  647. {
  648. // The viewport changed -> need to rerender.
  649. renderContext.abandon = true;
  650. delete self.renderContextList[pageView.id];
  651. pageView.createNewCanvas();
  652. self.render(pageView, renderController);
  653. } else if (renderContext.state === RenderingStates.PAUSED) {
  654. // There is already a not finished renderState ->
  655. logger.debug('RESUME', pageView.id);
  656. renderContext.resume();
  657. }
  658. }
  659. if (!renderContext) {
  660. viewport = pageView.viewport;
  661. // No rendering data yet -> create a new renderContext and start
  662. // the rendering process.
  663. var textLayer;
  664. var textLayerBuilder = pageView.listView.options.textLayerBuilder;
  665. if (textLayerBuilder) {
  666. var textLayerDiv = pageView.textLayerDiv = document.createElement("div");
  667. textLayerDiv.className = 'plv-text-layer text-layer';
  668. pageView.dom.appendChild(textLayerDiv);
  669. textLayer = new textLayerBuilder(textLayerDiv);
  670. this.pdfPage.getTextContent().then(
  671. function(textContent) {
  672. textLayer.setTextContent(textContent);
  673. }
  674. );
  675. }
  676. var annotationsLayerBuilder = pageView.listView.options.annotationsLayerBuilder;
  677. if (annotationsLayerBuilder) {
  678. var annotationsLayerDiv = pageView.annotationsLayerDiv = document.createElement("div");
  679. annotationsLayerDiv.className = 'plv-annotations-layer annotations-layer';
  680. pageView.dom.appendChild(annotationsLayerDiv);
  681. var annotationsLayer = new annotationsLayerBuilder(pageView, annotationsLayerDiv);
  682. this.pdfPage.getAnnotations().then(
  683. function(annotations) {
  684. annotationsLayer.setAnnotations(annotations)
  685. }
  686. );
  687. }
  688. var highlightsLayerBuilder = pageView.listView.options.highlightsLayerBuilder;
  689. if (highlightsLayerBuilder) {
  690. var highlightsLayerDiv = pageView.highlightsLayerDiv = document.createElement("div");
  691. highlightsLayerDiv.className = 'plv-highlights-layer highlights-layer';
  692. pageView.dom.appendChild(highlightsLayerDiv);
  693. pageView.highlightsLayer = new highlightsLayerBuilder(pageView, highlightsLayerDiv);
  694. }
  695. renderContext = {
  696. canvasContext: pageView.getCanvasContext(),
  697. viewport: viewport,
  698. textLayer: textLayer,
  699. continueCallback: function pdfViewContinueCallback(cont) {
  700. if (renderContext.abandon) {
  701. logger.debug("ABANDON", pageView.id);
  702. return;
  703. }
  704. if (renderController.pageToRender() !== pageView) {
  705. logger.debug('PAUSE', pageView.id);
  706. renderContext.state = RenderingStates.PAUSED;
  707. renderContext.resume = function resumeCallback() {
  708. renderContext.state = RenderingStates.RUNNING;
  709. cont();
  710. };
  711. return;
  712. }
  713. logger.debug('CONT', pageView.id);
  714. cont();
  715. }
  716. };
  717. this.renderContextList[pageView.id] = renderContext;
  718. logger.debug("BEGIN", pageView.id);
  719. renderContext.renderPromise = this.pdfPage.render(renderContext).promise;
  720. renderContext.renderPromise.then(
  721. function pdfPageRenderCallback() {
  722. logger.debug('DONE', pageView.id);
  723. pageView.isRendered = true;
  724. renderController.finishedRendering(pageView);
  725. },
  726. failDumper
  727. );
  728. }
  729. return renderContext.renderPromise;
  730. }
  731. };
  732. function PDFListView(mainDiv, options) {
  733. if (typeof(options) != "object") {
  734. options = {};
  735. }
  736. if (typeof(options.logLevel) != "number") {
  737. options.logLevel = Logger.INFO;
  738. }
  739. logger.logLevel = options.logLevel;
  740. var self = this;
  741. this.listView = new ListView(mainDiv, options);
  742. this.listView.ondblclick = function(e) {
  743. if (options.ondblclick) {
  744. options.ondblclick.call(self, e);
  745. }
  746. }
  747. this.renderController = new RenderController();
  748. this.renderController.addListView(this.listView);
  749. this.renderController.updateRenderList();
  750. mainDiv.addEventListener('scroll', function() {
  751. // This will update the list AND start rendering if needed.
  752. self.renderController.updateRenderList();
  753. });
  754. window.addEventListener('resize', function() {
  755. // Check if the scale changed due to the resizing.
  756. if (self.listView.calculateScale()) {
  757. // Update the layout and start rendering. Changing the layout
  758. // of the PageView makes it rendering stop.
  759. self.listView.layout();
  760. self.renderController.updateRenderList();
  761. }
  762. });
  763. }
  764. PDFListView.prototype = {
  765. loadPdf: function(url, onProgress) {
  766. this.doc = new Document(url, null, onProgress);
  767. var self = this;
  768. var promise = this.doc.initialized;
  769. promise.then(function() {
  770. logger.debug('LOADED');
  771. self.listView.setDocument(self.doc);
  772. self.renderController.updateRenderList();
  773. }, failDumper);
  774. return promise;
  775. },
  776. getScale: function() {
  777. return this.listView.getScale();
  778. },
  779. getScaleMode: function() {
  780. return this.listView.getScaleMode()
  781. },
  782. setScaleMode: function(scaleMode, scale) {
  783. this.listView.setScaleMode(scaleMode, scale);
  784. this.renderController.updateRenderList();
  785. },
  786. setScale: function(scale) {
  787. this.listView.setScale(scale);
  788. this.renderController.updateRenderList();
  789. },
  790. setToAutoScale: function() {
  791. this.listView.setToAutoScale();
  792. this.renderController.updateRenderList();
  793. },
  794. setToFitWidth: function() {
  795. this.listView.setToFitWidth();
  796. this.renderController.updateRenderList();
  797. },
  798. setToFitHeight: function() {
  799. this.listView.setToFitHeight();
  800. this.renderController.updateRenderList();
  801. },
  802. onResize: function() {
  803. this.renderController.onResize();
  804. },
  805. getPdfPosition: function(fromTop) {
  806. return this.listView.getPdfPosition(fromTop);
  807. },
  808. setPdfPosition: function(pdfPosition, fromTop) {
  809. this.listView.setPdfPosition(pdfPosition, fromTop);
  810. },
  811. setHighlights: function(highlights, fromTop) {
  812. this.listView.setHighlights(highlights, fromTop);
  813. },
  814. clearHighlights: function() {
  815. this.listView.clearHighlights();
  816. }
  817. };
  818. PDFListView.Logger = Logger;
  819. return PDFListView;
  820. });