PdfListView.js 29 KB

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