2014-09-16 01:18:28 +09:00
|
|
|
/* Copyright 2012 Mozilla Foundation
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2021-12-15 07:59:17 +09:00
|
|
|
/** @typedef {import("./interfaces").IRenderableView} IRenderableView */
|
|
|
|
/** @typedef {import("./pdf_viewer").PDFViewer} PDFViewer */
|
|
|
|
// eslint-disable-next-line max-len
|
|
|
|
/** @typedef {import("./pdf_thumbnail_viewer").PDFThumbnailViewer} PDFThumbnailViewer */
|
|
|
|
|
2020-09-23 21:11:45 +09:00
|
|
|
import { RenderingCancelledException } from "pdfjs-lib";
|
2021-12-15 21:54:29 +09:00
|
|
|
import { RenderingStates } from "./ui_utils.js";
|
2020-09-23 21:11:45 +09:00
|
|
|
|
2017-04-27 23:23:07 +09:00
|
|
|
const CLEANUP_TIMEOUT = 30000;
|
2014-09-16 05:46:01 +09:00
|
|
|
|
2014-09-21 02:21:49 +09:00
|
|
|
/**
|
|
|
|
* Controls rendering of the views for pages and thumbnails.
|
|
|
|
*/
|
2017-04-27 23:23:07 +09:00
|
|
|
class PDFRenderingQueue {
|
|
|
|
constructor() {
|
2014-09-16 01:18:28 +09:00
|
|
|
this.pdfViewer = null;
|
|
|
|
this.pdfThumbnailViewer = null;
|
|
|
|
this.onIdle = null;
|
|
|
|
this.highestPriorityPage = null;
|
Fix Viewer API definitions and include in CI
The Viewer API definitions do not compile because of missing imports and
anonymous objects are typed as `Object`. These issues were not caught
during CI because the test project was not compiling anything from the
Viewer API.
As an example of the first problem:
```
/**
* @implements MyInterface
*/
export class MyClass {
...
}
```
will generate a broken definition that doesn’t import MyInterface:
```
/**
* @implements MyInterface
*/
export class MyClass implements MyInterface {
...
}
```
This can be fixed by adding a typedef jsdoc to specify the import:
```
/** @typedef {import("./otherFile").MyInterface} MyInterface */
```
See https://github.com/jsdoc/jsdoc/issues/1537 and
https://github.com/microsoft/TypeScript/issues/22160 for more details.
As an example of the second problem:
```
/**
* Gets the size of the specified page, converted from PDF units to inches.
* @param {Object} An Object containing the properties: {Array} `view`,
* {number} `userUnit`, and {number} `rotate`.
*/
function getPageSizeInches({ view, userUnit, rotate }) {
...
}
```
generates the broken definition:
```
function getPageSizeInches({ view, userUnit, rotate }: Object) {
...
}
```
The jsdoc should specify the type of each nested property:
```
/**
* Gets the size of the specified page, converted from PDF units to inches.
* @param {Object} options An object containing the properties: {Array} `view`,
* {number} `userUnit`, and {number} `rotate`.
* @param {number[]} options.view
* @param {number} options.userUnit
* @param {number} options.rotate
*/
```
2021-08-26 07:44:06 +09:00
|
|
|
/** @type {number} */
|
2014-09-16 01:18:28 +09:00
|
|
|
this.idleTimeout = null;
|
|
|
|
this.printing = false;
|
|
|
|
this.isThumbnailViewEnabled = false;
|
2023-05-30 23:48:04 +09:00
|
|
|
|
|
|
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
|
|
|
Object.defineProperty(this, "hasViewer", {
|
|
|
|
value: () => !!this.pdfViewer,
|
|
|
|
});
|
|
|
|
}
|
2014-09-16 01:18:28 +09:00
|
|
|
}
|
|
|
|
|
2017-04-27 23:23:07 +09:00
|
|
|
/**
|
|
|
|
* @param {PDFViewer} pdfViewer
|
|
|
|
*/
|
|
|
|
setViewer(pdfViewer) {
|
|
|
|
this.pdfViewer = pdfViewer;
|
|
|
|
}
|
2014-09-16 01:18:28 +09:00
|
|
|
|
2017-04-27 23:23:07 +09:00
|
|
|
/**
|
|
|
|
* @param {PDFThumbnailViewer} pdfThumbnailViewer
|
|
|
|
*/
|
|
|
|
setThumbnailViewer(pdfThumbnailViewer) {
|
|
|
|
this.pdfThumbnailViewer = pdfThumbnailViewer;
|
|
|
|
}
|
2014-09-16 01:18:28 +09:00
|
|
|
|
2017-04-27 23:23:07 +09:00
|
|
|
/**
|
|
|
|
* @param {IRenderableView} view
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
isHighestPriority(view) {
|
|
|
|
return this.highestPriorityPage === view.renderingId;
|
|
|
|
}
|
2014-09-16 01:18:28 +09:00
|
|
|
|
2021-08-05 18:32:45 +09:00
|
|
|
/**
|
2017-04-27 23:23:07 +09:00
|
|
|
* @param {Object} currentlyVisiblePages
|
|
|
|
*/
|
|
|
|
renderHighestPriority(currentlyVisiblePages) {
|
|
|
|
if (this.idleTimeout) {
|
|
|
|
clearTimeout(this.idleTimeout);
|
|
|
|
this.idleTimeout = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pages have a higher priority than thumbnails, so check them first.
|
|
|
|
if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// No pages needed rendering, so check thumbnails.
|
2021-10-24 02:59:14 +09:00
|
|
|
if (
|
|
|
|
this.isThumbnailViewEnabled &&
|
|
|
|
this.pdfThumbnailViewer?.forceRendering()
|
|
|
|
) {
|
|
|
|
return;
|
2017-04-27 23:23:07 +09:00
|
|
|
}
|
2014-09-16 01:18:28 +09:00
|
|
|
|
2017-04-27 23:23:07 +09:00
|
|
|
if (this.printing) {
|
|
|
|
// If printing is currently ongoing do not reschedule cleanup.
|
|
|
|
return;
|
|
|
|
}
|
2014-09-16 01:18:28 +09:00
|
|
|
|
2017-04-27 23:23:07 +09:00
|
|
|
if (this.onIdle) {
|
|
|
|
this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {Object} visible
|
|
|
|
* @param {Array} views
|
|
|
|
* @param {boolean} scrolledDown
|
2021-10-02 18:43:58 +09:00
|
|
|
* @param {boolean} [preRenderExtra]
|
2017-04-27 23:23:07 +09:00
|
|
|
*/
|
2021-10-02 18:43:58 +09:00
|
|
|
getHighestPriority(visible, views, scrolledDown, preRenderExtra = false) {
|
2017-04-27 23:23:07 +09:00
|
|
|
/**
|
|
|
|
* The state has changed. Figure out which page has the highest priority to
|
|
|
|
* render next (if any).
|
|
|
|
*
|
|
|
|
* Priority:
|
|
|
|
* 1. visible pages
|
|
|
|
* 2. if last scrolled down, the page after the visible pages, or
|
|
|
|
* if last scrolled up, the page before the visible pages
|
|
|
|
*/
|
2021-11-03 17:28:52 +09:00
|
|
|
const visibleViews = visible.views,
|
|
|
|
numVisible = visibleViews.length;
|
2017-04-27 23:23:07 +09:00
|
|
|
|
|
|
|
if (numVisible === 0) {
|
2019-02-25 20:20:51 +09:00
|
|
|
return null;
|
2017-04-27 23:23:07 +09:00
|
|
|
}
|
2021-11-03 17:28:52 +09:00
|
|
|
for (let i = 0; i < numVisible; i++) {
|
2019-12-27 08:22:32 +09:00
|
|
|
const view = visibleViews[i].view;
|
2017-04-27 23:23:07 +09:00
|
|
|
if (!this.isViewFinished(view)) {
|
|
|
|
return view;
|
2014-09-16 01:18:28 +09:00
|
|
|
}
|
2017-04-27 23:23:07 +09:00
|
|
|
}
|
2021-10-13 22:42:20 +09:00
|
|
|
const firstId = visible.first.id,
|
|
|
|
lastId = visible.last.id;
|
|
|
|
|
|
|
|
// All the visible views have rendered; try to handle any "holes" in the
|
|
|
|
// page layout (can happen e.g. with spreadModes at higher zoom levels).
|
2021-11-03 17:28:52 +09:00
|
|
|
if (lastId - firstId + 1 > numVisible) {
|
2021-11-01 19:52:45 +09:00
|
|
|
const visibleIds = visible.ids;
|
2021-10-18 20:41:10 +09:00
|
|
|
for (let i = 1, ii = lastId - firstId; i < ii; i++) {
|
2021-11-01 19:52:45 +09:00
|
|
|
const holeId = scrolledDown ? firstId + i : lastId - i;
|
|
|
|
if (visibleIds.has(holeId)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const holeView = views[holeId - 1];
|
2021-10-13 22:42:20 +09:00
|
|
|
if (!this.isViewFinished(holeView)) {
|
|
|
|
return holeView;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-27 23:23:07 +09:00
|
|
|
|
2021-10-02 18:24:29 +09:00
|
|
|
// All the visible views have rendered; try to render next/previous page.
|
|
|
|
// (IDs start at 1, so no need to add 1 when `scrolledDown === true`.)
|
2021-10-13 22:42:20 +09:00
|
|
|
let preRenderIndex = scrolledDown ? lastId : firstId - 2;
|
2021-10-02 18:43:58 +09:00
|
|
|
let preRenderView = views[preRenderIndex];
|
2021-10-02 18:24:29 +09:00
|
|
|
|
|
|
|
if (preRenderView && !this.isViewFinished(preRenderView)) {
|
|
|
|
return preRenderView;
|
2017-04-27 23:23:07 +09:00
|
|
|
}
|
2021-10-02 18:43:58 +09:00
|
|
|
if (preRenderExtra) {
|
|
|
|
preRenderIndex += scrolledDown ? 1 : -1;
|
|
|
|
preRenderView = views[preRenderIndex];
|
|
|
|
|
|
|
|
if (preRenderView && !this.isViewFinished(preRenderView)) {
|
|
|
|
return preRenderView;
|
|
|
|
}
|
|
|
|
}
|
2017-04-27 23:23:07 +09:00
|
|
|
// Everything that needs to be rendered has been.
|
|
|
|
return null;
|
|
|
|
}
|
2014-09-16 01:18:28 +09:00
|
|
|
|
2017-04-27 23:23:07 +09:00
|
|
|
/**
|
|
|
|
* @param {IRenderableView} view
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
isViewFinished(view) {
|
|
|
|
return view.renderingState === RenderingStates.FINISHED;
|
|
|
|
}
|
2014-09-16 01:18:28 +09:00
|
|
|
|
2017-04-27 23:23:07 +09:00
|
|
|
/**
|
|
|
|
* Render a page or thumbnail view. This calls the appropriate function
|
|
|
|
* based on the views state. If the view is already rendered it will return
|
|
|
|
* `false`.
|
|
|
|
*
|
|
|
|
* @param {IRenderableView} view
|
|
|
|
*/
|
|
|
|
renderView(view) {
|
|
|
|
switch (view.renderingState) {
|
|
|
|
case RenderingStates.FINISHED:
|
|
|
|
return false;
|
|
|
|
case RenderingStates.PAUSED:
|
|
|
|
this.highestPriorityPage = view.renderingId;
|
|
|
|
view.resume();
|
|
|
|
break;
|
|
|
|
case RenderingStates.RUNNING:
|
|
|
|
this.highestPriorityPage = view.renderingId;
|
|
|
|
break;
|
|
|
|
case RenderingStates.INITIAL:
|
|
|
|
this.highestPriorityPage = view.renderingId;
|
2020-02-09 01:43:53 +09:00
|
|
|
view
|
|
|
|
.draw()
|
|
|
|
.finally(() => {
|
|
|
|
this.renderHighestPriority();
|
|
|
|
})
|
|
|
|
.catch(reason => {
|
2020-09-23 21:11:45 +09:00
|
|
|
if (reason instanceof RenderingCancelledException) {
|
|
|
|
return;
|
|
|
|
}
|
2020-02-09 01:43:53 +09:00
|
|
|
console.error(`renderView: "${reason}"`);
|
|
|
|
});
|
2017-04-27 23:23:07 +09:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2016-04-09 02:34:27 +09:00
|
|
|
|
2021-12-15 21:54:29 +09:00
|
|
|
export { PDFRenderingQueue };
|