When zooming replace the css-zoomed canvas by the new one only when rendering is finished
It fixes #18622. It avoids to recreate a canvasWrapper element in order minimize the DOM operations.
This commit is contained in:
parent
ef6ecee34c
commit
f3038406b1
@ -276,9 +276,20 @@ describe("PDF viewer", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
await page.evaluate(() => {
|
const handle = await waitForPageRendered(page);
|
||||||
window.PDFViewerApplication.pdfViewer.currentScale = 0.5;
|
if (
|
||||||
});
|
await page.evaluate(() => {
|
||||||
|
if (
|
||||||
|
window.PDFViewerApplication.pdfViewer.currentScale !== 0.5
|
||||||
|
) {
|
||||||
|
window.PDFViewerApplication.pdfViewer.currentScale = 0.5;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
await awaitPromise(handle);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -317,12 +328,14 @@ describe("PDF viewer", () => {
|
|||||||
const originalCanvasSize = await getCanvasSize(page);
|
const originalCanvasSize = await getCanvasSize(page);
|
||||||
const factor = 2;
|
const factor = 2;
|
||||||
|
|
||||||
|
const handle = await waitForPageRendered(page);
|
||||||
await page.evaluate(scaleFactor => {
|
await page.evaluate(scaleFactor => {
|
||||||
window.PDFViewerApplication.pdfViewer.increaseScale({
|
window.PDFViewerApplication.pdfViewer.increaseScale({
|
||||||
drawingDelay: 0,
|
drawingDelay: 0,
|
||||||
scaleFactor,
|
scaleFactor,
|
||||||
});
|
});
|
||||||
}, factor);
|
}, factor);
|
||||||
|
await awaitPromise(handle);
|
||||||
|
|
||||||
const canvasSize = await getCanvasSize(page);
|
const canvasSize = await getCanvasSize(page);
|
||||||
|
|
||||||
@ -343,12 +356,14 @@ describe("PDF viewer", () => {
|
|||||||
const originalCanvasSize = await getCanvasSize(page);
|
const originalCanvasSize = await getCanvasSize(page);
|
||||||
const factor = 4;
|
const factor = 4;
|
||||||
|
|
||||||
|
const handle = await waitForPageRendered(page);
|
||||||
await page.evaluate(scaleFactor => {
|
await page.evaluate(scaleFactor => {
|
||||||
window.PDFViewerApplication.pdfViewer.increaseScale({
|
window.PDFViewerApplication.pdfViewer.increaseScale({
|
||||||
drawingDelay: 0,
|
drawingDelay: 0,
|
||||||
scaleFactor,
|
scaleFactor,
|
||||||
});
|
});
|
||||||
}, factor);
|
}, factor);
|
||||||
|
await awaitPromise(handle);
|
||||||
|
|
||||||
const canvasSize = await getCanvasSize(page);
|
const canvasSize = await getCanvasSize(page);
|
||||||
|
|
||||||
|
|||||||
@ -116,6 +116,8 @@ const LAYERS_ORDER = new Map([
|
|||||||
class PDFPageView {
|
class PDFPageView {
|
||||||
#annotationMode = AnnotationMode.ENABLE_FORMS;
|
#annotationMode = AnnotationMode.ENABLE_FORMS;
|
||||||
|
|
||||||
|
#canvasWrapper = null;
|
||||||
|
|
||||||
#enableHWA = false;
|
#enableHWA = false;
|
||||||
|
|
||||||
#hasRestrictedScaling = false;
|
#hasRestrictedScaling = false;
|
||||||
@ -126,6 +128,8 @@ class PDFPageView {
|
|||||||
|
|
||||||
#loadingId = null;
|
#loadingId = null;
|
||||||
|
|
||||||
|
#originalViewport = null;
|
||||||
|
|
||||||
#previousRotation = null;
|
#previousRotation = null;
|
||||||
|
|
||||||
#scaleRoundX = 1;
|
#scaleRoundX = 1;
|
||||||
@ -144,8 +148,6 @@ class PDFPageView {
|
|||||||
regularAnnotations: true,
|
regularAnnotations: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
#viewportMap = new WeakMap();
|
|
||||||
|
|
||||||
#layers = [null, null, null, null];
|
#layers = [null, null, null, null];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,7 +197,6 @@ class PDFPageView {
|
|||||||
this.annotationLayer = null;
|
this.annotationLayer = null;
|
||||||
this.annotationEditorLayer = null;
|
this.annotationEditorLayer = null;
|
||||||
this.textLayer = null;
|
this.textLayer = null;
|
||||||
this.zoomLayer = null;
|
|
||||||
this.xfaLayer = null;
|
this.xfaLayer = null;
|
||||||
this.structTreeLayer = null;
|
this.structTreeLayer = null;
|
||||||
this.drawLayer = null;
|
this.drawLayer = null;
|
||||||
@ -508,33 +509,23 @@ class PDFPageView {
|
|||||||
this._textHighlighter.enable();
|
this._textHighlighter.enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
#resetCanvas() {
|
||||||
* @private
|
const { canvas } = this;
|
||||||
*/
|
if (!canvas) {
|
||||||
_resetZoomLayer(removeFromDOM = false) {
|
|
||||||
if (!this.zoomLayer) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const zoomLayerCanvas = this.zoomLayer.firstChild;
|
canvas.remove();
|
||||||
this.#viewportMap.delete(zoomLayerCanvas);
|
canvas.width = canvas.height = 0;
|
||||||
// Zeroing the width and height causes Firefox to release graphics
|
this.canvas = null;
|
||||||
// resources immediately, which can greatly reduce memory consumption.
|
this.#originalViewport = null;
|
||||||
zoomLayerCanvas.width = 0;
|
|
||||||
zoomLayerCanvas.height = 0;
|
|
||||||
|
|
||||||
if (removeFromDOM) {
|
|
||||||
// Note: `ChildNode.remove` doesn't throw if the parent node is undefined.
|
|
||||||
this.zoomLayer.remove();
|
|
||||||
}
|
|
||||||
this.zoomLayer = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reset({
|
reset({
|
||||||
keepZoomLayer = false,
|
|
||||||
keepAnnotationLayer = false,
|
keepAnnotationLayer = false,
|
||||||
keepAnnotationEditorLayer = false,
|
keepAnnotationEditorLayer = false,
|
||||||
keepXfaLayer = false,
|
keepXfaLayer = false,
|
||||||
keepTextLayer = false,
|
keepTextLayer = false,
|
||||||
|
keepCanvasWrapper = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.cancelRendering({
|
this.cancelRendering({
|
||||||
keepAnnotationLayer,
|
keepAnnotationLayer,
|
||||||
@ -547,21 +538,21 @@ class PDFPageView {
|
|||||||
const div = this.div;
|
const div = this.div;
|
||||||
|
|
||||||
const childNodes = div.childNodes,
|
const childNodes = div.childNodes,
|
||||||
zoomLayerNode = (keepZoomLayer && this.zoomLayer) || null,
|
|
||||||
annotationLayerNode =
|
annotationLayerNode =
|
||||||
(keepAnnotationLayer && this.annotationLayer?.div) || null,
|
(keepAnnotationLayer && this.annotationLayer?.div) || null,
|
||||||
annotationEditorLayerNode =
|
annotationEditorLayerNode =
|
||||||
(keepAnnotationEditorLayer && this.annotationEditorLayer?.div) || null,
|
(keepAnnotationEditorLayer && this.annotationEditorLayer?.div) || null,
|
||||||
xfaLayerNode = (keepXfaLayer && this.xfaLayer?.div) || null,
|
xfaLayerNode = (keepXfaLayer && this.xfaLayer?.div) || null,
|
||||||
textLayerNode = (keepTextLayer && this.textLayer?.div) || null;
|
textLayerNode = (keepTextLayer && this.textLayer?.div) || null,
|
||||||
|
canvasWrapperNode = (keepCanvasWrapper && this.#canvasWrapper) || null;
|
||||||
for (let i = childNodes.length - 1; i >= 0; i--) {
|
for (let i = childNodes.length - 1; i >= 0; i--) {
|
||||||
const node = childNodes[i];
|
const node = childNodes[i];
|
||||||
switch (node) {
|
switch (node) {
|
||||||
case zoomLayerNode:
|
|
||||||
case annotationLayerNode:
|
case annotationLayerNode:
|
||||||
case annotationEditorLayerNode:
|
case annotationEditorLayerNode:
|
||||||
case xfaLayerNode:
|
case xfaLayerNode:
|
||||||
case textLayerNode:
|
case textLayerNode:
|
||||||
|
case canvasWrapperNode:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
node.remove();
|
node.remove();
|
||||||
@ -590,16 +581,9 @@ class PDFPageView {
|
|||||||
}
|
}
|
||||||
this.structTreeLayer?.hide();
|
this.structTreeLayer?.hide();
|
||||||
|
|
||||||
if (!zoomLayerNode) {
|
if (!keepCanvasWrapper && this.#canvasWrapper) {
|
||||||
if (this.canvas) {
|
this.#canvasWrapper = null;
|
||||||
this.#viewportMap.delete(this.canvas);
|
this.#resetCanvas();
|
||||||
// Zeroing the width and height causes Firefox to release graphics
|
|
||||||
// resources immediately, which can greatly reduce memory consumption.
|
|
||||||
this.canvas.width = 0;
|
|
||||||
this.canvas.height = 0;
|
|
||||||
delete this.canvas;
|
|
||||||
}
|
|
||||||
this._resetZoomLayer();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -609,11 +593,11 @@ class PDFPageView {
|
|||||||
}
|
}
|
||||||
this.#isEditing = isEditing;
|
this.#isEditing = isEditing;
|
||||||
this.reset({
|
this.reset({
|
||||||
keepZoomLayer: true,
|
|
||||||
keepAnnotationLayer: true,
|
keepAnnotationLayer: true,
|
||||||
keepAnnotationEditorLayer: true,
|
keepAnnotationEditorLayer: true,
|
||||||
keepXfaLayer: true,
|
keepXfaLayer: true,
|
||||||
keepTextLayer: true,
|
keepTextLayer: true,
|
||||||
|
keepCanvasWrapper: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -697,7 +681,6 @@ class PDFPageView {
|
|||||||
this.renderingState !== RenderingStates.FINISHED
|
this.renderingState !== RenderingStates.FINISHED
|
||||||
) {
|
) {
|
||||||
this.cancelRendering({
|
this.cancelRendering({
|
||||||
keepZoomLayer: true,
|
|
||||||
keepAnnotationLayer: true,
|
keepAnnotationLayer: true,
|
||||||
keepAnnotationEditorLayer: true,
|
keepAnnotationEditorLayer: true,
|
||||||
keepXfaLayer: true,
|
keepXfaLayer: true,
|
||||||
@ -715,7 +698,6 @@ class PDFPageView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.cssTransform({
|
this.cssTransform({
|
||||||
target: this.canvas,
|
|
||||||
redrawAnnotationLayer: true,
|
redrawAnnotationLayer: true,
|
||||||
redrawAnnotationEditorLayer: true,
|
redrawAnnotationEditorLayer: true,
|
||||||
redrawXfaLayer: true,
|
redrawXfaLayer: true,
|
||||||
@ -737,20 +719,14 @@ class PDFPageView {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.zoomLayer && !this.canvas.hidden) {
|
|
||||||
this.zoomLayer = this.canvas.parentNode;
|
|
||||||
this.zoomLayer.style.position = "absolute";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.zoomLayer) {
|
|
||||||
this.cssTransform({ target: this.zoomLayer.firstChild });
|
|
||||||
}
|
}
|
||||||
|
this.cssTransform({});
|
||||||
this.reset({
|
this.reset({
|
||||||
keepZoomLayer: true,
|
|
||||||
keepAnnotationLayer: true,
|
keepAnnotationLayer: true,
|
||||||
keepAnnotationEditorLayer: true,
|
keepAnnotationEditorLayer: true,
|
||||||
keepXfaLayer: true,
|
keepXfaLayer: true,
|
||||||
keepTextLayer: true,
|
keepTextLayer: true,
|
||||||
|
keepCanvasWrapper: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -805,41 +781,32 @@ class PDFPageView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cssTransform({
|
cssTransform({
|
||||||
target,
|
|
||||||
redrawAnnotationLayer = false,
|
redrawAnnotationLayer = false,
|
||||||
redrawAnnotationEditorLayer = false,
|
redrawAnnotationEditorLayer = false,
|
||||||
redrawXfaLayer = false,
|
redrawXfaLayer = false,
|
||||||
redrawTextLayer = false,
|
redrawTextLayer = false,
|
||||||
hideTextLayer = false,
|
hideTextLayer = false,
|
||||||
}) {
|
}) {
|
||||||
// Scale target (canvas), its wrapper and page container.
|
const { canvas } = this;
|
||||||
if (
|
if (!canvas) {
|
||||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
return;
|
||||||
!(target instanceof HTMLCanvasElement)
|
|
||||||
) {
|
|
||||||
throw new Error("Expected `target` to be a canvas.");
|
|
||||||
}
|
|
||||||
if (!target.hasAttribute("zooming")) {
|
|
||||||
target.setAttribute("zooming", true);
|
|
||||||
const { style } = target;
|
|
||||||
style.width = style.height = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const originalViewport = this.#viewportMap.get(target);
|
const originalViewport = this.#originalViewport;
|
||||||
if (this.viewport !== originalViewport) {
|
if (this.viewport !== originalViewport) {
|
||||||
// The canvas may have been originally rotated; rotate relative to that.
|
// The canvas may have been originally rotated; rotate relative to that.
|
||||||
const relativeRotation =
|
const relativeRotation =
|
||||||
this.viewport.rotation - originalViewport.rotation;
|
(360 + this.viewport.rotation - originalViewport.rotation) % 360;
|
||||||
const absRotation = Math.abs(relativeRotation);
|
if (relativeRotation === 90 || relativeRotation === 270) {
|
||||||
let scaleX = 1,
|
|
||||||
scaleY = 1;
|
|
||||||
if (absRotation === 90 || absRotation === 270) {
|
|
||||||
const { width, height } = this.viewport;
|
const { width, height } = this.viewport;
|
||||||
// Scale x and y because of the rotation.
|
// Scale x and y because of the rotation.
|
||||||
scaleX = height / width;
|
const scaleX = height / width;
|
||||||
scaleY = width / height;
|
const scaleY = width / height;
|
||||||
|
canvas.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX},${scaleY})`;
|
||||||
|
} else {
|
||||||
|
canvas.style.transform =
|
||||||
|
relativeRotation === 0 ? "" : `rotate(${relativeRotation}deg)`;
|
||||||
}
|
}
|
||||||
target.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX}, ${scaleY})`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redrawAnnotationLayer && this.annotationLayer) {
|
if (redrawAnnotationLayer && this.annotationLayer) {
|
||||||
@ -892,7 +859,6 @@ class PDFPageView {
|
|||||||
this.#renderError = error;
|
this.#renderError = error;
|
||||||
|
|
||||||
this.renderingState = RenderingStates.FINISHED;
|
this.renderingState = RenderingStates.FINISHED;
|
||||||
this._resetZoomLayer(/* removeFromDOM = */ true);
|
|
||||||
|
|
||||||
// Ensure that the thumbnails won't become partially (or fully) blank,
|
// Ensure that the thumbnails won't become partially (or fully) blank,
|
||||||
// for documents that contain interactive form elements.
|
// for documents that contain interactive form elements.
|
||||||
@ -927,9 +893,12 @@ class PDFPageView {
|
|||||||
|
|
||||||
// Wrap the canvas so that if it has a CSS transform for high DPI the
|
// Wrap the canvas so that if it has a CSS transform for high DPI the
|
||||||
// overflow will be hidden in Firefox.
|
// overflow will be hidden in Firefox.
|
||||||
const canvasWrapper = document.createElement("div");
|
let canvasWrapper = this.#canvasWrapper;
|
||||||
canvasWrapper.classList.add("canvasWrapper");
|
if (!canvasWrapper) {
|
||||||
this.#addLayer(canvasWrapper, "canvasWrapper");
|
canvasWrapper = this.#canvasWrapper = document.createElement("div");
|
||||||
|
canvasWrapper.classList.add("canvasWrapper");
|
||||||
|
this.#addLayer(canvasWrapper, "canvasWrapper");
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!this.textLayer &&
|
!this.textLayer &&
|
||||||
@ -1004,22 +973,37 @@ class PDFPageView {
|
|||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement("canvas");
|
||||||
canvas.setAttribute("role", "presentation");
|
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);
|
const hasHCM = !!(pageColors?.background && pageColors?.foreground);
|
||||||
|
const prevCanvas = this.canvas;
|
||||||
|
const updateOnFirstShow = !prevCanvas && !hasHCM;
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.#originalViewport = viewport;
|
||||||
|
|
||||||
let showCanvas = isLastShow => {
|
let showCanvas = isLastShow => {
|
||||||
// In HCM, a final filter is applied on the canvas which means that
|
if (updateOnFirstShow) {
|
||||||
// before it's applied we've normal colors. Consequently, to avoid to have
|
// Don't add the canvas until the first draw callback, or until
|
||||||
// a final flash we just display it once all the drawing is done.
|
// drawing is complete when `!this.renderingQueue`, to prevent black
|
||||||
if (!hasHCM || isLastShow) {
|
// flickering.
|
||||||
canvas.hidden = false;
|
canvasWrapper.append(canvas);
|
||||||
showCanvas = null; // Only invoke the function once.
|
showCanvas = null;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (!isLastShow) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevCanvas) {
|
||||||
|
prevCanvas.replaceWith(canvas);
|
||||||
|
prevCanvas.width = prevCanvas.height = 0;
|
||||||
|
} else {
|
||||||
|
// 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.
|
||||||
|
canvasWrapper.append(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
showCanvas = null;
|
||||||
};
|
};
|
||||||
canvasWrapper.append(canvas);
|
|
||||||
this.canvas = canvas;
|
|
||||||
|
|
||||||
const ctx = canvas.getContext("2d", {
|
const ctx = canvas.getContext("2d", {
|
||||||
alpha: false,
|
alpha: false,
|
||||||
@ -1073,9 +1057,6 @@ class PDFPageView {
|
|||||||
this.#scaleRoundY = sfy[1];
|
this.#scaleRoundY = sfy[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the viewport so it's known what it was originally drawn with.
|
|
||||||
this.#viewportMap.set(canvas, viewport);
|
|
||||||
|
|
||||||
// Rendering area
|
// Rendering area
|
||||||
const transform = outputScale.scaled
|
const transform = outputScale.scaled
|
||||||
? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]
|
? [outputScale.sx, 0, 0, outputScale.sy, 0, 0]
|
||||||
@ -1141,6 +1122,9 @@ class PDFPageView {
|
|||||||
// callback had been invoked at least once.
|
// callback had been invoked at least once.
|
||||||
if (!(error instanceof RenderingCancelledException)) {
|
if (!(error instanceof RenderingCancelledException)) {
|
||||||
showCanvas?.(true);
|
showCanvas?.(true);
|
||||||
|
} else {
|
||||||
|
prevCanvas?.remove();
|
||||||
|
this.#resetCanvas();
|
||||||
}
|
}
|
||||||
return this.#finishRenderTask(renderTask, error);
|
return this.#finishRenderTask(renderTask, error);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,19 +83,14 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
contain: content;
|
||||||
&[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[zooming] {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.structTree {
|
.structTree {
|
||||||
contain: strict;
|
contain: strict;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user