Inline PDFPageView.paintOnCanvas
in the draw
method, now that SVG-rendering is removed
This commit is contained in:
parent
92cf183f56
commit
484da62f50
@ -26,7 +26,6 @@
|
||||
import {
|
||||
AbortException,
|
||||
AnnotationMode,
|
||||
createPromiseCapability,
|
||||
PixelsPerInch,
|
||||
RenderingCancelledException,
|
||||
setLayerDimensions,
|
||||
@ -119,6 +118,8 @@ class PDFPageView {
|
||||
|
||||
#previousRotation = null;
|
||||
|
||||
#renderError = null;
|
||||
|
||||
#renderingState = RenderingStates.INITIAL;
|
||||
|
||||
#useThumbnailCanvas = {
|
||||
@ -126,6 +127,8 @@ class PDFPageView {
|
||||
regularAnnotations: true,
|
||||
};
|
||||
|
||||
#viewportMap = new WeakMap();
|
||||
|
||||
/**
|
||||
* @param {PDFPageViewOptions} options
|
||||
*/
|
||||
@ -160,10 +163,8 @@ class PDFPageView {
|
||||
this.renderingQueue = options.renderingQueue;
|
||||
this.l10n = options.l10n || NullL10n;
|
||||
|
||||
this.paintTask = null;
|
||||
this.paintedViewportMap = new WeakMap();
|
||||
this.renderTask = null;
|
||||
this.resume = null;
|
||||
this._renderError = null;
|
||||
if (
|
||||
typeof PDFJSDev === "undefined" ||
|
||||
PDFJSDev.test("!PRODUCTION || GENERIC")
|
||||
@ -430,7 +431,7 @@ class PDFPageView {
|
||||
return;
|
||||
}
|
||||
const zoomLayerCanvas = this.zoomLayer.firstChild;
|
||||
this.paintedViewportMap.delete(zoomLayerCanvas);
|
||||
this.#viewportMap.delete(zoomLayerCanvas);
|
||||
// Zeroing the width and height causes Firefox to release graphics
|
||||
// resources immediately, which can greatly reduce memory consumption.
|
||||
zoomLayerCanvas.width = 0;
|
||||
@ -502,7 +503,7 @@ class PDFPageView {
|
||||
|
||||
if (!zoomLayerNode) {
|
||||
if (this.canvas) {
|
||||
this.paintedViewportMap.delete(this.canvas);
|
||||
this.#viewportMap.delete(this.canvas);
|
||||
// Zeroing the width and height causes Firefox to release graphics
|
||||
// resources immediately, which can greatly reduce memory consumption.
|
||||
this.canvas.width = 0;
|
||||
@ -559,10 +560,10 @@ class PDFPageView {
|
||||
|
||||
let isScalingRestricted = false;
|
||||
if (this.canvas && this.maxCanvasPixels > 0) {
|
||||
const outputScale = this.outputScale;
|
||||
const { width, height } = this.viewport;
|
||||
const { sx, sy } = this.outputScale;
|
||||
if (
|
||||
((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
|
||||
((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
|
||||
((Math.floor(width) * sx) | 0) * ((Math.floor(height) * sy) | 0) >
|
||||
this.maxCanvasPixels
|
||||
) {
|
||||
isScalingRestricted = true;
|
||||
@ -608,7 +609,7 @@ class PDFPageView {
|
||||
pageNumber: this.id,
|
||||
cssTransform: true,
|
||||
timestamp: performance.now(),
|
||||
error: this._renderError,
|
||||
error: this.#renderError,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -640,9 +641,9 @@ class PDFPageView {
|
||||
keepTextLayer = false,
|
||||
cancelExtraDelay = 0,
|
||||
} = {}) {
|
||||
if (this.paintTask) {
|
||||
this.paintTask.cancel(cancelExtraDelay);
|
||||
this.paintTask = null;
|
||||
if (this.renderTask) {
|
||||
this.renderTask.cancel(cancelExtraDelay);
|
||||
this.renderTask = null;
|
||||
}
|
||||
this.resume = null;
|
||||
|
||||
@ -697,7 +698,7 @@ class PDFPageView {
|
||||
style.width = style.height = "";
|
||||
}
|
||||
|
||||
const originalViewport = this.paintedViewportMap.get(target);
|
||||
const originalViewport = this.#viewportMap.get(target);
|
||||
if (this.viewport !== originalViewport) {
|
||||
// The canvas may have been originally rotated; rotate relative to that.
|
||||
const relativeRotation =
|
||||
@ -746,33 +747,33 @@ class PDFPageView {
|
||||
return this.viewport.convertToPdfPoint(x, y);
|
||||
}
|
||||
|
||||
async #finishPaintTask(paintTask, error = null) {
|
||||
// The paintTask may have been replaced by a new one, so only remove
|
||||
// the reference to the paintTask if it matches the one that is
|
||||
async #finishRenderTask(renderTask, error = null) {
|
||||
// The renderTask may have been replaced by a new one, so only remove
|
||||
// the reference to the renderTask if it matches the one that is
|
||||
// triggering this callback.
|
||||
if (paintTask === this.paintTask) {
|
||||
this.paintTask = null;
|
||||
if (renderTask === this.renderTask) {
|
||||
this.renderTask = null;
|
||||
}
|
||||
|
||||
if (error instanceof RenderingCancelledException) {
|
||||
this._renderError = null;
|
||||
this.#renderError = null;
|
||||
return;
|
||||
}
|
||||
this._renderError = error;
|
||||
this.#renderError = error;
|
||||
|
||||
this.renderingState = RenderingStates.FINISHED;
|
||||
this._resetZoomLayer(/* removeFromDOM = */ true);
|
||||
|
||||
// Ensure that the thumbnails won't become partially (or fully) blank,
|
||||
// for documents that contain interactive form elements.
|
||||
this.#useThumbnailCanvas.regularAnnotations = !paintTask.separateAnnots;
|
||||
this.#useThumbnailCanvas.regularAnnotations = !renderTask.separateAnnots;
|
||||
|
||||
this.eventBus.dispatch("pagerendered", {
|
||||
source: this,
|
||||
pageNumber: this.id,
|
||||
cssTransform: false,
|
||||
timestamp: performance.now(),
|
||||
error: this._renderError,
|
||||
error: this.#renderError,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
@ -785,7 +786,7 @@ class PDFPageView {
|
||||
console.error("Must be in new state before drawing");
|
||||
this.reset(); // Ensure that we reset all state to prevent issues.
|
||||
}
|
||||
const { div, pdfPage } = this;
|
||||
const { div, l10n, pageColors, pdfPage, viewport } = this;
|
||||
|
||||
if (!pdfPage) {
|
||||
this.renderingState = RenderingStates.FINISHED;
|
||||
@ -837,7 +838,7 @@ class PDFPageView {
|
||||
renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS,
|
||||
linkService,
|
||||
downloadManager,
|
||||
l10n: this.l10n,
|
||||
l10n,
|
||||
enableScripting,
|
||||
hasJSActionsPromise,
|
||||
fieldObjectsPromise,
|
||||
@ -846,53 +847,127 @@ class PDFPageView {
|
||||
});
|
||||
}
|
||||
|
||||
let renderContinueCallback = null;
|
||||
if (this.renderingQueue) {
|
||||
renderContinueCallback = cont => {
|
||||
if (!this.renderingQueue.isHighestPriority(this)) {
|
||||
this.renderingState = RenderingStates.PAUSED;
|
||||
this.resume = () => {
|
||||
this.renderingState = RenderingStates.RUNNING;
|
||||
cont();
|
||||
};
|
||||
return;
|
||||
}
|
||||
cont();
|
||||
};
|
||||
const renderContinueCallback = cont => {
|
||||
showCanvas?.(false);
|
||||
if (this.renderingQueue && !this.renderingQueue.isHighestPriority(this)) {
|
||||
this.renderingState = RenderingStates.PAUSED;
|
||||
this.resume = () => {
|
||||
this.renderingState = RenderingStates.RUNNING;
|
||||
cont();
|
||||
};
|
||||
return;
|
||||
}
|
||||
cont();
|
||||
};
|
||||
|
||||
const { width, height } = viewport;
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.setAttribute("role", "presentation");
|
||||
|
||||
// Keep the canvas hidden until the first draw callback, or until drawing
|
||||
// is complete when `!this.renderingQueue`, to prevent black flickering.
|
||||
canvas.hidden = true;
|
||||
const hasHCM = !!(pageColors?.background && pageColors?.foreground);
|
||||
|
||||
let showCanvas = isLastShow => {
|
||||
// In HCM, a final filter is applied on the canvas which means that
|
||||
// before it's applied we've normal colors. Consequently, to avoid to have
|
||||
// a final flash we just display it once all the drawing is done.
|
||||
if (!hasHCM || isLastShow) {
|
||||
canvas.hidden = false;
|
||||
showCanvas = null; // Only invoke the function once.
|
||||
}
|
||||
};
|
||||
canvasWrapper.append(canvas);
|
||||
this.canvas = canvas;
|
||||
|
||||
const ctx = canvas.getContext("2d", { alpha: false });
|
||||
const outputScale = (this.outputScale = new OutputScale());
|
||||
|
||||
if (this.useOnlyCssZoom) {
|
||||
const actualSizeViewport = viewport.clone({
|
||||
scale: PixelsPerInch.PDF_TO_CSS_UNITS,
|
||||
});
|
||||
// Use a scale that makes the canvas have the originally intended size
|
||||
// of the page.
|
||||
outputScale.sx *= actualSizeViewport.width / width;
|
||||
outputScale.sy *= actualSizeViewport.height / height;
|
||||
}
|
||||
|
||||
const paintTask = this.paintOnCanvas(canvasWrapper);
|
||||
paintTask.onRenderContinue = renderContinueCallback;
|
||||
this.paintTask = paintTask;
|
||||
if (this.maxCanvasPixels > 0) {
|
||||
const pixelsInViewport = width * height;
|
||||
const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport);
|
||||
if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
|
||||
outputScale.sx = maxScale;
|
||||
outputScale.sy = maxScale;
|
||||
this.hasRestrictedScaling = true;
|
||||
} else {
|
||||
this.hasRestrictedScaling = false;
|
||||
}
|
||||
}
|
||||
const sfx = approximateFraction(outputScale.sx);
|
||||
const sfy = approximateFraction(outputScale.sy);
|
||||
|
||||
const resultPromise = paintTask.promise.then(
|
||||
() => {
|
||||
return this.#finishPaintTask(paintTask).then(async () => {
|
||||
this.#renderTextLayer();
|
||||
canvas.width = roundToDivide(width * outputScale.sx, sfx[0]);
|
||||
canvas.height = roundToDivide(height * outputScale.sy, sfy[0]);
|
||||
const { style } = canvas;
|
||||
style.width = roundToDivide(width, sfx[1]) + "px";
|
||||
style.height = roundToDivide(height, sfy[1]) + "px";
|
||||
|
||||
if (this.annotationLayer) {
|
||||
await this.#renderAnnotationLayer();
|
||||
// Add the viewport so it's known what it was originally drawn with.
|
||||
this.#viewportMap.set(canvas, viewport);
|
||||
|
||||
// Rendering area
|
||||
const transform = outputScale.scaled
|
||||
? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]
|
||||
: null;
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
transform,
|
||||
viewport,
|
||||
annotationMode: this.#annotationMode,
|
||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||
annotationCanvasMap: this._annotationCanvasMap,
|
||||
pageColors,
|
||||
};
|
||||
const renderTask = (this.renderTask = this.pdfPage.render(renderContext));
|
||||
renderTask.onContinue = renderContinueCallback;
|
||||
|
||||
const resultPromise = renderTask.promise.then(
|
||||
async () => {
|
||||
showCanvas?.(true);
|
||||
await this.#finishRenderTask(renderTask);
|
||||
|
||||
this.#renderTextLayer();
|
||||
|
||||
if (this.annotationLayer) {
|
||||
await this.#renderAnnotationLayer();
|
||||
}
|
||||
|
||||
if (!this.annotationEditorLayer) {
|
||||
const { annotationEditorUIManager } = this.#layerProperties();
|
||||
|
||||
if (!annotationEditorUIManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.annotationEditorLayer) {
|
||||
const { annotationEditorUIManager } = this.#layerProperties();
|
||||
|
||||
if (!annotationEditorUIManager) {
|
||||
return;
|
||||
}
|
||||
this.annotationEditorLayer = new AnnotationEditorLayerBuilder({
|
||||
uiManager: annotationEditorUIManager,
|
||||
pageDiv: div,
|
||||
pdfPage,
|
||||
l10n: this.l10n,
|
||||
accessibilityManager: this._accessibilityManager,
|
||||
});
|
||||
}
|
||||
this.#renderAnnotationEditorLayer();
|
||||
});
|
||||
this.annotationEditorLayer = new AnnotationEditorLayerBuilder({
|
||||
uiManager: annotationEditorUIManager,
|
||||
pageDiv: div,
|
||||
pdfPage,
|
||||
l10n,
|
||||
accessibilityManager: this._accessibilityManager,
|
||||
});
|
||||
}
|
||||
this.#renderAnnotationEditorLayer();
|
||||
},
|
||||
error => {
|
||||
return this.#finishPaintTask(paintTask, error);
|
||||
// When zooming with a `drawingDelay` set, avoid temporarily showing
|
||||
// a black canvas if rendering was cancelled before the `onContinue`-
|
||||
// callback had been invoked at least once.
|
||||
if (!(error instanceof RenderingCancelledException)) {
|
||||
showCanvas?.(true);
|
||||
}
|
||||
return this.#finishRenderTask(renderTask, error);
|
||||
}
|
||||
);
|
||||
|
||||
@ -922,123 +997,6 @@ class PDFPageView {
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
paintOnCanvas(canvasWrapper) {
|
||||
const renderCapability = createPromiseCapability();
|
||||
const result = {
|
||||
promise: renderCapability.promise,
|
||||
onRenderContinue(cont) {
|
||||
cont();
|
||||
},
|
||||
cancel(extraDelay = 0) {
|
||||
renderTask.cancel(extraDelay);
|
||||
},
|
||||
get separateAnnots() {
|
||||
return renderTask.separateAnnots;
|
||||
},
|
||||
};
|
||||
|
||||
const viewport = this.viewport;
|
||||
const { width, height } = viewport;
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.setAttribute("role", "presentation");
|
||||
|
||||
// Keep the canvas hidden until the first draw callback, or until drawing
|
||||
// is complete when `!this.renderingQueue`, to prevent black flickering.
|
||||
canvas.hidden = true;
|
||||
const hasHCM = !!(
|
||||
this.pageColors?.background && this.pageColors?.foreground
|
||||
);
|
||||
let showCanvas = isLastShow => {
|
||||
// In HCM, a final filter is applied on the canvas which means that
|
||||
// before it's applied we've normal colors. Consequently, to avoid to have
|
||||
// a final flash we just display it once all the drawing is done.
|
||||
if (!hasHCM || isLastShow) {
|
||||
canvas.hidden = false;
|
||||
showCanvas = null; // Only invoke the function once.
|
||||
}
|
||||
};
|
||||
|
||||
canvasWrapper.append(canvas);
|
||||
this.canvas = canvas;
|
||||
|
||||
const ctx = canvas.getContext("2d", { alpha: false });
|
||||
const outputScale = (this.outputScale = new OutputScale());
|
||||
|
||||
if (this.useOnlyCssZoom) {
|
||||
const actualSizeViewport = viewport.clone({
|
||||
scale: PixelsPerInch.PDF_TO_CSS_UNITS,
|
||||
});
|
||||
// Use a scale that makes the canvas have the originally intended size
|
||||
// of the page.
|
||||
outputScale.sx *= actualSizeViewport.width / width;
|
||||
outputScale.sy *= actualSizeViewport.height / height;
|
||||
}
|
||||
|
||||
if (this.maxCanvasPixels > 0) {
|
||||
const pixelsInViewport = width * height;
|
||||
const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport);
|
||||
if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
|
||||
outputScale.sx = maxScale;
|
||||
outputScale.sy = maxScale;
|
||||
this.hasRestrictedScaling = true;
|
||||
} else {
|
||||
this.hasRestrictedScaling = false;
|
||||
}
|
||||
}
|
||||
|
||||
const sfx = approximateFraction(outputScale.sx);
|
||||
const sfy = approximateFraction(outputScale.sy);
|
||||
|
||||
canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
|
||||
canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
|
||||
const { style } = canvas;
|
||||
style.width = roundToDivide(viewport.width, sfx[1]) + "px";
|
||||
style.height = roundToDivide(viewport.height, sfy[1]) + "px";
|
||||
|
||||
// Add the viewport so it's known what it was originally drawn with.
|
||||
this.paintedViewportMap.set(canvas, viewport);
|
||||
|
||||
// Rendering area
|
||||
const transform = outputScale.scaled
|
||||
? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]
|
||||
: null;
|
||||
const renderContext = {
|
||||
canvasContext: ctx,
|
||||
transform,
|
||||
viewport,
|
||||
annotationMode: this.#annotationMode,
|
||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||
annotationCanvasMap: this._annotationCanvasMap,
|
||||
pageColors: this.pageColors,
|
||||
};
|
||||
const renderTask = this.pdfPage.render(renderContext);
|
||||
renderTask.onContinue = function (cont) {
|
||||
showCanvas?.(false);
|
||||
if (result.onRenderContinue) {
|
||||
result.onRenderContinue(cont);
|
||||
} else {
|
||||
cont();
|
||||
}
|
||||
};
|
||||
|
||||
renderTask.promise.then(
|
||||
function () {
|
||||
showCanvas?.(true);
|
||||
renderCapability.resolve();
|
||||
},
|
||||
function (error) {
|
||||
// When zooming with a `drawingDelay` set, avoid temporarily showing
|
||||
// a black canvas if rendering was cancelled before the `onContinue`-
|
||||
// callback had been invoked at least once.
|
||||
if (!(error instanceof RenderingCancelledException)) {
|
||||
showCanvas?.(true);
|
||||
}
|
||||
renderCapability.reject(error);
|
||||
}
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string|null} label
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user