[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:
parent
f1166f480f
commit
06f72d5662
@ -3301,6 +3301,22 @@ class AnnotationLayer {
|
|||||||
} else {
|
} else {
|
||||||
firstChild.after(canvas);
|
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();
|
this.#annotationCanvasMap.clear();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -40,6 +40,8 @@ class StampEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
#canvas = null;
|
#canvas = null;
|
||||||
|
|
||||||
|
#missingCanvas = false;
|
||||||
|
|
||||||
#resizeTimeoutId = null;
|
#resizeTimeoutId = null;
|
||||||
|
|
||||||
#isSvg = false;
|
#isSvg = false;
|
||||||
@ -352,7 +354,8 @@ class StampEditor extends AnnotationEditor {
|
|||||||
this.#bitmap ||
|
this.#bitmap ||
|
||||||
this.#bitmapUrl ||
|
this.#bitmapUrl ||
|
||||||
this.#bitmapFile ||
|
this.#bitmapFile ||
|
||||||
this.#bitmapId
|
this.#bitmapId ||
|
||||||
|
this.#missingCanvas
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,11 +382,13 @@ class StampEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
this.addAltTextButton();
|
this.addAltTextButton();
|
||||||
|
|
||||||
|
if (!this.#missingCanvas) {
|
||||||
if (this.#bitmap) {
|
if (this.#bitmap) {
|
||||||
this.#createCanvas();
|
this.#createCanvas();
|
||||||
} else {
|
} else {
|
||||||
this.#getBitmap();
|
this.#getBitmap();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.width && !this.annotationElementId) {
|
if (this.width && !this.annotationElementId) {
|
||||||
// This editor was created in using copy (ctrl+c).
|
// This editor was created in using copy (ctrl+c).
|
||||||
@ -401,6 +406,22 @@ class StampEditor extends AnnotationEditor {
|
|||||||
return this.div;
|
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 */
|
/** @inheritdoc */
|
||||||
_onResized() {
|
_onResized() {
|
||||||
// We used a CSS-zoom during the resizing, but now it's resized we can
|
// We used a CSS-zoom during the resizing, but now it's resized we can
|
||||||
@ -752,6 +773,7 @@ class StampEditor extends AnnotationEditor {
|
|||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
static async deserialize(data, parent, uiManager) {
|
static async deserialize(data, parent, uiManager) {
|
||||||
let initialData = null;
|
let initialData = null;
|
||||||
|
let missingCanvas = false;
|
||||||
if (data instanceof StampAnnotationElement) {
|
if (data instanceof StampAnnotationElement) {
|
||||||
const {
|
const {
|
||||||
data: { rect, rotation, id, structParent, popupRef },
|
data: { rect, rotation, id, structParent, popupRef },
|
||||||
@ -759,13 +781,20 @@ class StampEditor extends AnnotationEditor {
|
|||||||
parent: {
|
parent: {
|
||||||
page: { pageNumber },
|
page: { pageNumber },
|
||||||
},
|
},
|
||||||
|
canvas,
|
||||||
} = data;
|
} = data;
|
||||||
const canvas = container.querySelector("canvas");
|
let bitmapId, bitmap;
|
||||||
const imageData = uiManager.imageManager.getFromCanvas(
|
if (canvas) {
|
||||||
|
delete data.canvas;
|
||||||
|
({ id: bitmapId, bitmap } = uiManager.imageManager.getFromCanvas(
|
||||||
container.id,
|
container.id,
|
||||||
canvas
|
canvas
|
||||||
);
|
));
|
||||||
canvas.remove();
|
canvas.remove();
|
||||||
|
} else {
|
||||||
|
missingCanvas = true;
|
||||||
|
data._hasNoCanvas = true;
|
||||||
|
}
|
||||||
|
|
||||||
// When switching to edit mode, we wait for the structure tree to be
|
// 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.
|
// ready (see pdf_viewer.js), so it's fine to use getAriaAttributesSync.
|
||||||
@ -776,8 +805,8 @@ class StampEditor extends AnnotationEditor {
|
|||||||
|
|
||||||
initialData = data = {
|
initialData = data = {
|
||||||
annotationType: AnnotationEditorType.STAMP,
|
annotationType: AnnotationEditorType.STAMP,
|
||||||
bitmapId: imageData.id,
|
bitmapId,
|
||||||
bitmap: imageData.bitmap,
|
bitmap,
|
||||||
pageIndex: pageNumber - 1,
|
pageIndex: pageNumber - 1,
|
||||||
rect: rect.slice(0),
|
rect: rect.slice(0),
|
||||||
rotation,
|
rotation,
|
||||||
@ -795,7 +824,10 @@ class StampEditor extends AnnotationEditor {
|
|||||||
const editor = await super.deserialize(data, parent, uiManager);
|
const editor = await super.deserialize(data, parent, uiManager);
|
||||||
const { rect, bitmap, bitmapUrl, bitmapId, isSvg, accessibilityData } =
|
const { rect, bitmap, bitmapUrl, bitmapId, isSvg, accessibilityData } =
|
||||||
data;
|
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;
|
editor.#bitmapId = bitmapId;
|
||||||
if (bitmap) {
|
if (bitmap) {
|
||||||
editor.#bitmap = bitmap;
|
editor.#bitmap = bitmap;
|
||||||
|
|||||||
@ -654,6 +654,8 @@ class AnnotationEditorUIManager {
|
|||||||
|
|
||||||
#mainHighlightColorPicker = null;
|
#mainHighlightColorPicker = null;
|
||||||
|
|
||||||
|
#missingCanvases = null;
|
||||||
|
|
||||||
#mlManager = null;
|
#mlManager = null;
|
||||||
|
|
||||||
#mode = AnnotationEditorType.NONE;
|
#mode = AnnotationEditorType.NONE;
|
||||||
@ -898,6 +900,7 @@ class AnnotationEditorUIManager {
|
|||||||
this.#allLayers.clear();
|
this.#allLayers.clear();
|
||||||
this.#allEditors.clear();
|
this.#allEditors.clear();
|
||||||
this.#editorsToRescale.clear();
|
this.#editorsToRescale.clear();
|
||||||
|
this.#missingCanvases?.clear();
|
||||||
this.#activeEditor = null;
|
this.#activeEditor = null;
|
||||||
this.#selectedEditors.clear();
|
this.#selectedEditors.clear();
|
||||||
this.#commandManager.destroy();
|
this.#commandManager.destroy();
|
||||||
@ -1711,6 +1714,10 @@ class AnnotationEditorUIManager {
|
|||||||
this.#updateModeCapability.resolve();
|
this.#updateModeCapability.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isInEditingMode() {
|
||||||
|
return this.#mode !== AnnotationEditorType.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
addNewEditorFromKeyboard() {
|
addNewEditorFromKeyboard() {
|
||||||
if (this.currentLayer.canCreateNewEmptyEditor()) {
|
if (this.currentLayer.canCreateNewEmptyEditor()) {
|
||||||
this.currentLayer.addNewEditor();
|
this.currentLayer.addNewEditor();
|
||||||
@ -1887,6 +1894,9 @@ class AnnotationEditorUIManager {
|
|||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
this.#allEditors.delete(editor.id);
|
this.#allEditors.delete(editor.id);
|
||||||
|
if (editor.annotationElementId) {
|
||||||
|
this.#missingCanvases?.delete(editor.annotationElementId);
|
||||||
|
}
|
||||||
this.unselect(editor);
|
this.unselect(editor);
|
||||||
if (
|
if (
|
||||||
!editor.annotationElementId ||
|
!editor.annotationElementId ||
|
||||||
@ -2514,6 +2524,19 @@ class AnnotationEditorUIManager {
|
|||||||
}
|
}
|
||||||
editor.renderAnnotationElement(annotation);
|
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 {
|
export {
|
||||||
|
|||||||
@ -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 }
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -693,3 +693,4 @@
|
|||||||
!issue19182.pdf
|
!issue19182.pdf
|
||||||
!issue18911.pdf
|
!issue18911.pdf
|
||||||
!issue19207.pdf
|
!issue19207.pdf
|
||||||
|
!issue19239.pdf
|
||||||
|
|||||||
BIN
test/pdfs/issue19239.pdf
Executable file
BIN
test/pdfs/issue19239.pdf
Executable file
Binary file not shown.
@ -588,10 +588,14 @@ class PDFPageView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleEditingMode(isEditing) {
|
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()) {
|
if (!this.hasEditableAnnotations()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#isEditing = isEditing;
|
|
||||||
this.reset({
|
this.reset({
|
||||||
keepAnnotationLayer: true,
|
keepAnnotationLayer: true,
|
||||||
keepAnnotationEditorLayer: true,
|
keepAnnotationEditorLayer: true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user