pdf_rendering_queue.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. /* Copyright 2012 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. /** @typedef {import("./interfaces").IRenderableView} IRenderableView */
  16. /** @typedef {import("./pdf_viewer").PDFViewer} PDFViewer */
  17. // eslint-disable-next-line max-len
  18. /** @typedef {import("./pdf_thumbnail_viewer").PDFThumbnailViewer} PDFThumbnailViewer */
  19. import { RenderingCancelledException } from "pdfjs-lib";
  20. import { RenderingStates } from "./ui_utils.js";
  21. const CLEANUP_TIMEOUT = 30000;
  22. /**
  23. * Controls rendering of the views for pages and thumbnails.
  24. */
  25. class PDFRenderingQueue {
  26. constructor() {
  27. this.pdfViewer = null;
  28. this.pdfThumbnailViewer = null;
  29. this.onIdle = null;
  30. this.highestPriorityPage = null;
  31. /** @type {number} */
  32. this.idleTimeout = null;
  33. this.printing = false;
  34. this.isThumbnailViewEnabled = false;
  35. }
  36. /**
  37. * @param {PDFViewer} pdfViewer
  38. */
  39. setViewer(pdfViewer) {
  40. this.pdfViewer = pdfViewer;
  41. }
  42. /**
  43. * @param {PDFThumbnailViewer} pdfThumbnailViewer
  44. */
  45. setThumbnailViewer(pdfThumbnailViewer) {
  46. this.pdfThumbnailViewer = pdfThumbnailViewer;
  47. }
  48. /**
  49. * @param {IRenderableView} view
  50. * @returns {boolean}
  51. */
  52. isHighestPriority(view) {
  53. return this.highestPriorityPage === view.renderingId;
  54. }
  55. /**
  56. * @returns {boolean}
  57. */
  58. hasViewer() {
  59. return !!this.pdfViewer;
  60. }
  61. /**
  62. * @param {Object} currentlyVisiblePages
  63. */
  64. renderHighestPriority(currentlyVisiblePages) {
  65. if (this.idleTimeout) {
  66. clearTimeout(this.idleTimeout);
  67. this.idleTimeout = null;
  68. }
  69. // Pages have a higher priority than thumbnails, so check them first.
  70. if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
  71. return;
  72. }
  73. // No pages needed rendering, so check thumbnails.
  74. if (
  75. this.isThumbnailViewEnabled &&
  76. this.pdfThumbnailViewer?.forceRendering()
  77. ) {
  78. return;
  79. }
  80. if (this.printing) {
  81. // If printing is currently ongoing do not reschedule cleanup.
  82. return;
  83. }
  84. if (this.onIdle) {
  85. this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
  86. }
  87. }
  88. /**
  89. * @param {Object} visible
  90. * @param {Array} views
  91. * @param {boolean} scrolledDown
  92. * @param {boolean} [preRenderExtra]
  93. */
  94. getHighestPriority(visible, views, scrolledDown, preRenderExtra = false) {
  95. /**
  96. * The state has changed. Figure out which page has the highest priority to
  97. * render next (if any).
  98. *
  99. * Priority:
  100. * 1. visible pages
  101. * 2. if last scrolled down, the page after the visible pages, or
  102. * if last scrolled up, the page before the visible pages
  103. */
  104. const visibleViews = visible.views,
  105. numVisible = visibleViews.length;
  106. if (numVisible === 0) {
  107. return null;
  108. }
  109. for (let i = 0; i < numVisible; i++) {
  110. const view = visibleViews[i].view;
  111. if (!this.isViewFinished(view)) {
  112. return view;
  113. }
  114. }
  115. const firstId = visible.first.id,
  116. lastId = visible.last.id;
  117. // All the visible views have rendered; try to handle any "holes" in the
  118. // page layout (can happen e.g. with spreadModes at higher zoom levels).
  119. if (lastId - firstId + 1 > numVisible) {
  120. const visibleIds = visible.ids;
  121. for (let i = 1, ii = lastId - firstId; i < ii; i++) {
  122. const holeId = scrolledDown ? firstId + i : lastId - i;
  123. if (visibleIds.has(holeId)) {
  124. continue;
  125. }
  126. const holeView = views[holeId - 1];
  127. if (!this.isViewFinished(holeView)) {
  128. return holeView;
  129. }
  130. }
  131. }
  132. // All the visible views have rendered; try to render next/previous page.
  133. // (IDs start at 1, so no need to add 1 when `scrolledDown === true`.)
  134. let preRenderIndex = scrolledDown ? lastId : firstId - 2;
  135. let preRenderView = views[preRenderIndex];
  136. if (preRenderView && !this.isViewFinished(preRenderView)) {
  137. return preRenderView;
  138. }
  139. if (preRenderExtra) {
  140. preRenderIndex += scrolledDown ? 1 : -1;
  141. preRenderView = views[preRenderIndex];
  142. if (preRenderView && !this.isViewFinished(preRenderView)) {
  143. return preRenderView;
  144. }
  145. }
  146. // Everything that needs to be rendered has been.
  147. return null;
  148. }
  149. /**
  150. * @param {IRenderableView} view
  151. * @returns {boolean}
  152. */
  153. isViewFinished(view) {
  154. return view.renderingState === RenderingStates.FINISHED;
  155. }
  156. /**
  157. * Render a page or thumbnail view. This calls the appropriate function
  158. * based on the views state. If the view is already rendered it will return
  159. * `false`.
  160. *
  161. * @param {IRenderableView} view
  162. */
  163. renderView(view) {
  164. switch (view.renderingState) {
  165. case RenderingStates.FINISHED:
  166. return false;
  167. case RenderingStates.PAUSED:
  168. this.highestPriorityPage = view.renderingId;
  169. view.resume();
  170. break;
  171. case RenderingStates.RUNNING:
  172. this.highestPriorityPage = view.renderingId;
  173. break;
  174. case RenderingStates.INITIAL:
  175. this.highestPriorityPage = view.renderingId;
  176. view
  177. .draw()
  178. .finally(() => {
  179. this.renderHighestPriority();
  180. })
  181. .catch(reason => {
  182. if (reason instanceof RenderingCancelledException) {
  183. return;
  184. }
  185. console.error(`renderView: "${reason}"`);
  186. });
  187. break;
  188. }
  189. return true;
  190. }
  191. }
  192. export { PDFRenderingQueue };