1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-19 14:48:08 +02:00

[Editor] Don't try to use an non-existing canvas when rendering an invisible existing stamp editor

It fixes #19239.

When the canvas isn't existing the editor has no image: it's fine because the editor is invisible.
Once it's made visible, the canvas is set when the annotation layer has been rendered.
This commit is contained in:
Calixte Denizet 2025-01-09 18:10:47 +01:00
parent f1166f480f
commit 06f72d5662
7 changed files with 160 additions and 15 deletions

View file

@ -3301,6 +3301,22 @@ class AnnotationLayer {
} else {
firstChild.after(canvas);
}
const editableAnnotation = this.#editableAnnotations.get(id);
if (!editableAnnotation) {
continue;
}
if (editableAnnotation._hasNoCanvas) {
// The canvas wasn't available when the annotation was created.
this._annotationEditorUIManager?.setMissingCanvas(
id,
element.id,
canvas
);
editableAnnotation._hasNoCanvas = false;
} else {
editableAnnotation.canvas = canvas;
}
}
this.#annotationCanvasMap.clear();
}

View file

@ -40,6 +40,8 @@ class StampEditor extends AnnotationEditor {
#canvas = null;
#missingCanvas = false;
#resizeTimeoutId = null;
#isSvg = false;
@ -352,7 +354,8 @@ class StampEditor extends AnnotationEditor {
this.#bitmap ||
this.#bitmapUrl ||
this.#bitmapFile ||
this.#bitmapId
this.#bitmapId ||
this.#missingCanvas
);
}
@ -379,10 +382,12 @@ class StampEditor extends AnnotationEditor {
this.addAltTextButton();
if (this.#bitmap) {
this.#createCanvas();
} else {
this.#getBitmap();
if (!this.#missingCanvas) {
if (this.#bitmap) {
this.#createCanvas();
} else {
this.#getBitmap();
}
}
if (this.width && !this.annotationElementId) {
@ -401,6 +406,22 @@ class StampEditor extends AnnotationEditor {
return this.div;
}
setCanvas(annotationElementId, canvas) {
const { id: bitmapId, bitmap } = this._uiManager.imageManager.getFromCanvas(
annotationElementId,
canvas
);
canvas.remove();
if (bitmapId && this._uiManager.imageManager.isValidId(bitmapId)) {
this.#bitmapId = bitmapId;
if (bitmap) {
this.#bitmap = bitmap;
}
this.#missingCanvas = false;
this.#createCanvas();
}
}
/** @inheritdoc */
_onResized() {
// We used a CSS-zoom during the resizing, but now it's resized we can
@ -752,6 +773,7 @@ class StampEditor extends AnnotationEditor {
/** @inheritdoc */
static async deserialize(data, parent, uiManager) {
let initialData = null;
let missingCanvas = false;
if (data instanceof StampAnnotationElement) {
const {
data: { rect, rotation, id, structParent, popupRef },
@ -759,13 +781,20 @@ class StampEditor extends AnnotationEditor {
parent: {
page: { pageNumber },
},
canvas,
} = data;
const canvas = container.querySelector("canvas");
const imageData = uiManager.imageManager.getFromCanvas(
container.id,
canvas
);
canvas.remove();
let bitmapId, bitmap;
if (canvas) {
delete data.canvas;
({ id: bitmapId, bitmap } = uiManager.imageManager.getFromCanvas(
container.id,
canvas
));
canvas.remove();
} else {
missingCanvas = true;
data._hasNoCanvas = true;
}
// When switching to edit mode, we wait for the structure tree to be
// ready (see pdf_viewer.js), so it's fine to use getAriaAttributesSync.
@ -776,8 +805,8 @@ class StampEditor extends AnnotationEditor {
initialData = data = {
annotationType: AnnotationEditorType.STAMP,
bitmapId: imageData.id,
bitmap: imageData.bitmap,
bitmapId,
bitmap,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
@ -795,7 +824,10 @@ class StampEditor extends AnnotationEditor {
const editor = await super.deserialize(data, parent, uiManager);
const { rect, bitmap, bitmapUrl, bitmapId, isSvg, accessibilityData } =
data;
if (bitmapId && uiManager.imageManager.isValidId(bitmapId)) {
if (missingCanvas) {
uiManager.addMissingCanvas(data.id, editor);
editor.#missingCanvas = true;
} else if (bitmapId && uiManager.imageManager.isValidId(bitmapId)) {
editor.#bitmapId = bitmapId;
if (bitmap) {
editor.#bitmap = bitmap;

View file

@ -654,6 +654,8 @@ class AnnotationEditorUIManager {
#mainHighlightColorPicker = null;
#missingCanvases = null;
#mlManager = null;
#mode = AnnotationEditorType.NONE;
@ -898,6 +900,7 @@ class AnnotationEditorUIManager {
this.#allLayers.clear();
this.#allEditors.clear();
this.#editorsToRescale.clear();
this.#missingCanvases?.clear();
this.#activeEditor = null;
this.#selectedEditors.clear();
this.#commandManager.destroy();
@ -1711,6 +1714,10 @@ class AnnotationEditorUIManager {
this.#updateModeCapability.resolve();
}
isInEditingMode() {
return this.#mode !== AnnotationEditorType.NONE;
}
addNewEditorFromKeyboard() {
if (this.currentLayer.canCreateNewEmptyEditor()) {
this.currentLayer.addNewEditor();
@ -1887,6 +1894,9 @@ class AnnotationEditorUIManager {
}, 0);
}
this.#allEditors.delete(editor.id);
if (editor.annotationElementId) {
this.#missingCanvases?.delete(editor.annotationElementId);
}
this.unselect(editor);
if (
!editor.annotationElementId ||
@ -2514,6 +2524,19 @@ class AnnotationEditorUIManager {
}
editor.renderAnnotationElement(annotation);
}
setMissingCanvas(annotationId, annotationElementId, canvas) {
const editor = this.#missingCanvases?.get(annotationId);
if (!editor) {
return;
}
editor.setCanvas(annotationElementId, canvas);
this.#missingCanvases.delete(annotationId);
}
addMissingCanvas(annotationId, editor) {
(this.#missingCanvases ||= new Map()).set(annotationId, editor);
}
}
export {

View file

@ -1745,4 +1745,73 @@ describe("Stamp Editor", () => {
);
});
});
describe("Switch to edit mode a pdf with an existing stamp annotation on an invisible and rendered page", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("issue19239.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must move on the second page", async () => {
await Promise.all(
pages.map(async ([, page]) => {
const pageOneSelector = `.page[data-page-number = "1"]`;
const pageTwoSelector = `.page[data-page-number = "2"]`;
await scrollIntoView(page, pageTwoSelector);
await page.waitForSelector(pageOneSelector, {
visible: false,
});
await switchToStamp(page);
await scrollIntoView(page, pageOneSelector);
await page.waitForSelector(
`${pageOneSelector} .annotationEditorLayer canvas`,
{ visible: true }
);
})
);
});
});
describe("Switch to edit mode a pdf with an existing stamp annotation on an invisible and unrendered page", () => {
let pages;
beforeAll(async () => {
pages = await loadAndWait("issue19239.pdf", ".annotationEditorLayer");
});
afterAll(async () => {
await closePages(pages);
});
it("must move on the last page", async () => {
await Promise.all(
pages.map(async ([, page]) => {
const twoToFourteen = Array.from(new Array(13).keys(), n => n + 2);
for (const pageNumber of twoToFourteen) {
const pageSelector = `.page[data-page-number = "${pageNumber}"]`;
await scrollIntoView(page, pageSelector);
}
await switchToStamp(page);
const thirteenToOne = Array.from(new Array(13).keys(), n => 13 - n);
for (const pageNumber of thirteenToOne) {
const pageSelector = `.page[data-page-number = "${pageNumber}"]`;
await scrollIntoView(page, pageSelector);
}
await page.waitForSelector(
`.page[data-page-number = "1"] .annotationEditorLayer canvas`,
{ visible: true }
);
})
);
});
});
});

View file

@ -693,3 +693,4 @@
!issue19182.pdf
!issue18911.pdf
!issue19207.pdf
!issue19239.pdf

BIN
test/pdfs/issue19239.pdf Executable file

Binary file not shown.

View file

@ -588,10 +588,14 @@ class PDFPageView {
}
toggleEditingMode(isEditing) {
// The page can be invisible, consequently there's no annotation layer and
// we can't know if there are editable annotations.
// So to avoid any issue when the page is rendered the #isEditing flag must
// be set.
this.#isEditing = isEditing;
if (!this.hasEditableAnnotations()) {
return;
}
this.#isEditing = isEditing;
this.reset({
keepAnnotationLayer: true,
keepAnnotationEditorLayer: true,