mirror of
https://github.com/zen-browser/pdf.js.git
synced 2025-07-07 17:05:38 +02:00
[api-minor][Editor] When switching to editing mode, redraw pages containing editable annotations
Right now, editable annotations are using their own canvas when they're drawn, but it induces several issues: - if the annotation has to be composed with the page then the canvas must be correctly composed with its parent. That means we should move the canvas under canvasWrapper and we should extract composing info from the drawing instructions... Currently it's the case with highlight annotations. - we use some extra memory for those canvas even if the user will never edit them, which the case for example when opening a pdf in Fenix. So with this patch, all the editable annotations are drawn on the canvas. When the user switches to editing mode, then the pages with some editable annotations are redrawn but without them: they'll be replaced by their counterpart in the annotation editor layer.
This commit is contained in:
parent
75129fd61a
commit
64635f3b35
13 changed files with 358 additions and 103 deletions
|
@ -680,6 +680,7 @@ class Annotation {
|
||||||
hasOwnCanvas: false,
|
hasOwnCanvas: false,
|
||||||
noRotate: !!(this.flags & AnnotationFlag.NOROTATE),
|
noRotate: !!(this.flags & AnnotationFlag.NOROTATE),
|
||||||
noHTML: isLocked && isContentLocked,
|
noHTML: isLocked && isContentLocked,
|
||||||
|
isEditable: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (params.collectFields) {
|
if (params.collectFields) {
|
||||||
|
@ -776,6 +777,10 @@ class Annotation {
|
||||||
return this.printable;
|
return this.printable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mustBeViewedWhenEditing() {
|
||||||
|
return !this.data.isEditable;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
*/
|
*/
|
||||||
|
@ -3802,7 +3807,8 @@ class FreeTextAnnotation extends MarkupAnnotation {
|
||||||
// It uses its own canvas in order to be hidden if edited.
|
// It uses its own canvas in order to be hidden if edited.
|
||||||
// But if it has the noHTML flag, it means that we don't want to be able
|
// But if it has the noHTML flag, it means that we don't want to be able
|
||||||
// to modify it so we can just draw it on the main canvas.
|
// to modify it so we can just draw it on the main canvas.
|
||||||
this.data.hasOwnCanvas = !this.data.noHTML;
|
this.data.hasOwnCanvas = this.data.noRotate;
|
||||||
|
this.data.isEditable = !this.data.noHTML;
|
||||||
// We want to be able to add mouse listeners to the annotation.
|
// We want to be able to add mouse listeners to the annotation.
|
||||||
this.data.noHTML = false;
|
this.data.noHTML = false;
|
||||||
|
|
||||||
|
|
|
@ -411,6 +411,8 @@ class Page {
|
||||||
intent,
|
intent,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
annotationStorage = null,
|
annotationStorage = null,
|
||||||
|
isEditing = false,
|
||||||
|
modifiedIds = null,
|
||||||
}) {
|
}) {
|
||||||
const contentStreamPromise = this.getContentStream();
|
const contentStreamPromise = this.getContentStream();
|
||||||
const resourcesPromise = this.loadResources([
|
const resourcesPromise = this.loadResources([
|
||||||
|
@ -579,7 +581,9 @@ class Page {
|
||||||
if (
|
if (
|
||||||
intentAny ||
|
intentAny ||
|
||||||
(intentDisplay &&
|
(intentDisplay &&
|
||||||
annotation.mustBeViewed(annotationStorage, renderForms)) ||
|
annotation.mustBeViewed(annotationStorage, renderForms) &&
|
||||||
|
((isEditing && annotation.mustBeViewedWhenEditing()) ||
|
||||||
|
(!isEditing && !modifiedIds?.has(annotation.data.id)))) ||
|
||||||
(intentPrint && annotation.mustBePrinted(annotationStorage))
|
(intentPrint && annotation.mustBePrinted(annotationStorage))
|
||||||
) {
|
) {
|
||||||
opListPromises.push(
|
opListPromises.push(
|
||||||
|
|
|
@ -752,6 +752,8 @@ class WorkerMessageHandler {
|
||||||
intent: data.intent,
|
intent: data.intent,
|
||||||
cacheKey: data.cacheKey,
|
cacheKey: data.cacheKey,
|
||||||
annotationStorage: data.annotationStorage,
|
annotationStorage: data.annotationStorage,
|
||||||
|
isEditing: data.isEditing,
|
||||||
|
modifiedIds: data.modifiedIds,
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
function (operatorListInfo) {
|
function (operatorListInfo) {
|
||||||
|
|
|
@ -198,6 +198,10 @@ class AnnotationElement {
|
||||||
return !!(titleObj?.str || contentsObj?.str || richText?.str);
|
return !!(titleObj?.str || contentsObj?.str || richText?.str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get _isEditable() {
|
||||||
|
return this.data.isEditable;
|
||||||
|
}
|
||||||
|
|
||||||
get hasPopupData() {
|
get hasPopupData() {
|
||||||
return AnnotationElement._hasPopupData(this.data);
|
return AnnotationElement._hasPopupData(this.data);
|
||||||
}
|
}
|
||||||
|
@ -734,10 +738,6 @@ class AnnotationElement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get _isEditable() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_editOnDoubleClick() {
|
_editOnDoubleClick() {
|
||||||
if (!this._isEditable) {
|
if (!this._isEditable) {
|
||||||
return;
|
return;
|
||||||
|
@ -2530,10 +2530,6 @@ class FreeTextAnnotationElement extends AnnotationElement {
|
||||||
|
|
||||||
return this.container;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
get _isEditable() {
|
|
||||||
return this.data.hasOwnCanvas;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class LineAnnotationElement extends AnnotationElement {
|
class LineAnnotationElement extends AnnotationElement {
|
||||||
|
@ -3107,6 +3103,10 @@ class AnnotationLayer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasEditableAnnotations() {
|
||||||
|
return this.#editableAnnotations.size > 0;
|
||||||
|
}
|
||||||
|
|
||||||
#appendElement(element, id) {
|
#appendElement(element, id) {
|
||||||
const contentElement = element.firstChild || element;
|
const contentElement = element.firstChild || element;
|
||||||
contentElement.id = `${AnnotationPrefix}${id}`;
|
contentElement.id = `${AnnotationPrefix}${id}`;
|
||||||
|
@ -3188,7 +3188,7 @@ class AnnotationLayer {
|
||||||
}
|
}
|
||||||
this.#appendElement(rendered, data.id);
|
this.#appendElement(rendered, data.id);
|
||||||
|
|
||||||
if (element.annotationEditorType > 0) {
|
if (element._isEditable) {
|
||||||
this.#editableAnnotations.set(element.data.id, element);
|
this.#editableAnnotations.set(element.data.id, element);
|
||||||
this._annotationEditorUIManager?.renderAnnotationElement(element);
|
this._annotationEditorUIManager?.renderAnnotationElement(element);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { objectFromMap, unreachable } from "../shared/util.js";
|
import { objectFromMap, shadow, unreachable } from "../shared/util.js";
|
||||||
import { AnnotationEditor } from "./editor/editor.js";
|
import { AnnotationEditor } from "./editor/editor.js";
|
||||||
import { MurmurHash3_64 } from "../shared/murmurhash3.js";
|
import { MurmurHash3_64 } from "../shared/murmurhash3.js";
|
||||||
|
|
||||||
|
@ -29,6 +29,8 @@ const SerializableEmpty = Object.freeze({
|
||||||
class AnnotationStorage {
|
class AnnotationStorage {
|
||||||
#modified = false;
|
#modified = false;
|
||||||
|
|
||||||
|
#modifiedIds = null;
|
||||||
|
|
||||||
#storage = new Map();
|
#storage = new Map();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -248,6 +250,34 @@ class AnnotationStorage {
|
||||||
}
|
}
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetModifiedIds() {
|
||||||
|
this.#modifiedIds = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {{ids: Set<string>, hash: string}}
|
||||||
|
*/
|
||||||
|
get modifiedIds() {
|
||||||
|
if (this.#modifiedIds) {
|
||||||
|
return this.#modifiedIds;
|
||||||
|
}
|
||||||
|
const ids = [];
|
||||||
|
for (const value of this.#storage.values()) {
|
||||||
|
if (
|
||||||
|
!(value instanceof AnnotationEditor) ||
|
||||||
|
!value.annotationElementId ||
|
||||||
|
!value.serialize()
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ids.push(value.annotationElementId);
|
||||||
|
}
|
||||||
|
return (this.#modifiedIds = {
|
||||||
|
ids: new Set(ids),
|
||||||
|
hash: ids.join(","),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -282,6 +312,13 @@ class PrintAnnotationStorage extends AnnotationStorage {
|
||||||
get serializable() {
|
get serializable() {
|
||||||
return this.#serializable;
|
return this.#serializable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get modifiedIds() {
|
||||||
|
return shadow(this, "modifiedIds", {
|
||||||
|
ids: new Set(),
|
||||||
|
hash: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { AnnotationStorage, PrintAnnotationStorage, SerializableEmpty };
|
export { AnnotationStorage, PrintAnnotationStorage, SerializableEmpty };
|
||||||
|
|
|
@ -1227,6 +1227,7 @@ class PDFDocumentProxy {
|
||||||
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap] - Map some
|
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap] - Map some
|
||||||
* annotation ids with canvases used to render them.
|
* annotation ids with canvases used to render them.
|
||||||
* @property {PrintAnnotationStorage} [printAnnotationStorage]
|
* @property {PrintAnnotationStorage} [printAnnotationStorage]
|
||||||
|
* @property {boolean} [isEditing] - Render the page in editing mode.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1248,6 +1249,7 @@ class PDFDocumentProxy {
|
||||||
* from the {@link AnnotationStorage}-instance; useful e.g. for printing.
|
* from the {@link AnnotationStorage}-instance; useful e.g. for printing.
|
||||||
* The default value is `AnnotationMode.ENABLE`.
|
* The default value is `AnnotationMode.ENABLE`.
|
||||||
* @property {PrintAnnotationStorage} [printAnnotationStorage]
|
* @property {PrintAnnotationStorage} [printAnnotationStorage]
|
||||||
|
* @property {boolean} [isEditing] - Render the page in editing mode.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1420,13 +1422,15 @@ class PDFPageProxy {
|
||||||
annotationCanvasMap = null,
|
annotationCanvasMap = null,
|
||||||
pageColors = null,
|
pageColors = null,
|
||||||
printAnnotationStorage = null,
|
printAnnotationStorage = null,
|
||||||
|
isEditing = false,
|
||||||
}) {
|
}) {
|
||||||
this._stats?.time("Overall");
|
this._stats?.time("Overall");
|
||||||
|
|
||||||
const intentArgs = this._transport.getRenderingIntent(
|
const intentArgs = this._transport.getRenderingIntent(
|
||||||
intent,
|
intent,
|
||||||
annotationMode,
|
annotationMode,
|
||||||
printAnnotationStorage
|
printAnnotationStorage,
|
||||||
|
isEditing
|
||||||
);
|
);
|
||||||
const { renderingIntent, cacheKey } = intentArgs;
|
const { renderingIntent, cacheKey } = intentArgs;
|
||||||
// If there was a pending destroy, cancel it so no cleanup happens during
|
// If there was a pending destroy, cancel it so no cleanup happens during
|
||||||
|
@ -1560,6 +1564,7 @@ class PDFPageProxy {
|
||||||
intent = "display",
|
intent = "display",
|
||||||
annotationMode = AnnotationMode.ENABLE,
|
annotationMode = AnnotationMode.ENABLE,
|
||||||
printAnnotationStorage = null,
|
printAnnotationStorage = null,
|
||||||
|
isEditing = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) {
|
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) {
|
||||||
throw new Error("Not implemented: getOperatorList");
|
throw new Error("Not implemented: getOperatorList");
|
||||||
|
@ -1576,6 +1581,7 @@ class PDFPageProxy {
|
||||||
intent,
|
intent,
|
||||||
annotationMode,
|
annotationMode,
|
||||||
printAnnotationStorage,
|
printAnnotationStorage,
|
||||||
|
isEditing,
|
||||||
/* isOpList = */ true
|
/* isOpList = */ true
|
||||||
);
|
);
|
||||||
let intentState = this._intentStates.get(intentArgs.cacheKey);
|
let intentState = this._intentStates.get(intentArgs.cacheKey);
|
||||||
|
@ -1812,6 +1818,8 @@ class PDFPageProxy {
|
||||||
renderingIntent,
|
renderingIntent,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
annotationStorageSerializable,
|
annotationStorageSerializable,
|
||||||
|
isEditing,
|
||||||
|
modifiedIds,
|
||||||
}) {
|
}) {
|
||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
|
||||||
assert(
|
assert(
|
||||||
|
@ -1828,6 +1836,8 @@ class PDFPageProxy {
|
||||||
intent: renderingIntent,
|
intent: renderingIntent,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
annotationStorage: map,
|
annotationStorage: map,
|
||||||
|
isEditing,
|
||||||
|
modifiedIds,
|
||||||
},
|
},
|
||||||
transfer
|
transfer
|
||||||
);
|
);
|
||||||
|
@ -2420,6 +2430,7 @@ class WorkerTransport {
|
||||||
intent,
|
intent,
|
||||||
annotationMode = AnnotationMode.ENABLE,
|
annotationMode = AnnotationMode.ENABLE,
|
||||||
printAnnotationStorage = null,
|
printAnnotationStorage = null,
|
||||||
|
isEditing = false,
|
||||||
isOpList = false
|
isOpList = false
|
||||||
) {
|
) {
|
||||||
let renderingIntent = RenderingIntentFlag.DISPLAY; // Default value.
|
let renderingIntent = RenderingIntentFlag.DISPLAY; // Default value.
|
||||||
|
@ -2438,6 +2449,12 @@ class WorkerTransport {
|
||||||
warn(`getRenderingIntent - invalid intent: ${intent}`);
|
warn(`getRenderingIntent - invalid intent: ${intent}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const annotationStorage =
|
||||||
|
renderingIntent & RenderingIntentFlag.PRINT &&
|
||||||
|
printAnnotationStorage instanceof PrintAnnotationStorage
|
||||||
|
? printAnnotationStorage
|
||||||
|
: this.annotationStorage;
|
||||||
|
|
||||||
switch (annotationMode) {
|
switch (annotationMode) {
|
||||||
case AnnotationMode.DISABLE:
|
case AnnotationMode.DISABLE:
|
||||||
renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE;
|
renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE;
|
||||||
|
@ -2450,12 +2467,6 @@ class WorkerTransport {
|
||||||
case AnnotationMode.ENABLE_STORAGE:
|
case AnnotationMode.ENABLE_STORAGE:
|
||||||
renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE;
|
renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE;
|
||||||
|
|
||||||
const annotationStorage =
|
|
||||||
renderingIntent & RenderingIntentFlag.PRINT &&
|
|
||||||
printAnnotationStorage instanceof PrintAnnotationStorage
|
|
||||||
? printAnnotationStorage
|
|
||||||
: this.annotationStorage;
|
|
||||||
|
|
||||||
annotationStorageSerializable = annotationStorage.serializable;
|
annotationStorageSerializable = annotationStorage.serializable;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -2466,10 +2477,22 @@ class WorkerTransport {
|
||||||
renderingIntent += RenderingIntentFlag.OPLIST;
|
renderingIntent += RenderingIntentFlag.OPLIST;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { ids: modifiedIds, hash: modifiedIdsHash } =
|
||||||
|
annotationStorage.modifiedIds;
|
||||||
|
|
||||||
|
const cacheKeyBuf = [
|
||||||
|
renderingIntent,
|
||||||
|
annotationStorageSerializable.hash,
|
||||||
|
isEditing ? 1 : 0,
|
||||||
|
modifiedIdsHash,
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
renderingIntent,
|
renderingIntent,
|
||||||
cacheKey: `${renderingIntent}_${annotationStorageSerializable.hash}`,
|
cacheKey: cacheKeyBuf.join("_"),
|
||||||
annotationStorageSerializable,
|
annotationStorageSerializable,
|
||||||
|
isEditing,
|
||||||
|
modifiedIds,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -503,6 +503,14 @@ describe("ResetForm action", () => {
|
||||||
it("must check that the Ink annotation has a popup", async () => {
|
it("must check that the Ink annotation has a popup", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
|
if (browserName) {
|
||||||
|
// TODO
|
||||||
|
pending(
|
||||||
|
"Re-enable this test when the Ink annotation has been made editable."
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
`document.querySelector("[data-annotation-id='25R']").hidden === false`
|
`document.querySelector("[data-annotation-id='25R']").hidden === false`
|
||||||
);
|
);
|
||||||
|
|
|
@ -45,6 +45,7 @@ import {
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
switchToEditor,
|
switchToEditor,
|
||||||
waitForAnnotationEditorLayer,
|
waitForAnnotationEditorLayer,
|
||||||
|
waitForAnnotationModeChanged,
|
||||||
waitForSelectedEditor,
|
waitForSelectedEditor,
|
||||||
waitForSerialized,
|
waitForSerialized,
|
||||||
waitForStorageEntries,
|
waitForStorageEntries,
|
||||||
|
@ -987,6 +988,29 @@ describe("FreeText Editor", () => {
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
await switchToFreeText(page);
|
await switchToFreeText(page);
|
||||||
|
|
||||||
|
const isEditorWhite = editorRect =>
|
||||||
|
page.evaluate(rect => {
|
||||||
|
const canvas = document.querySelector(".canvasWrapper canvas");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
rect ||= {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: canvas.width,
|
||||||
|
height: canvas.height,
|
||||||
|
};
|
||||||
|
const { data } = ctx.getImageData(
|
||||||
|
rect.x,
|
||||||
|
rect.y,
|
||||||
|
rect.width,
|
||||||
|
rect.height
|
||||||
|
);
|
||||||
|
return data.every(x => x === 0xff);
|
||||||
|
}, editorRect);
|
||||||
|
|
||||||
|
// The page has been re-rendered but with no freetext annotations.
|
||||||
|
let isWhite = await isEditorWhite();
|
||||||
|
expect(isWhite).withContext(`In ${browserName}`).toBeTrue();
|
||||||
|
|
||||||
let editorIds = await getEditors(page, "freeText");
|
let editorIds = await getEditors(page, "freeText");
|
||||||
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(6);
|
||||||
|
|
||||||
|
@ -1041,11 +1065,9 @@ describe("FreeText Editor", () => {
|
||||||
// canvas.
|
// canvas.
|
||||||
editorIds = await getEditors(page, "freeText");
|
editorIds = await getEditors(page, "freeText");
|
||||||
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1);
|
expect(editorIds.length).withContext(`In ${browserName}`).toEqual(1);
|
||||||
const hidden = await page.$eval(
|
|
||||||
"[data-annotation-id='26R'] canvas",
|
isWhite = await isEditorWhite(editorRect);
|
||||||
el => getComputedStyle(el).display === "none"
|
expect(isWhite).withContext(`In ${browserName}`).toBeTrue();
|
||||||
);
|
|
||||||
expect(hidden).withContext(`In ${browserName}`).toBeTrue();
|
|
||||||
|
|
||||||
// Check we've now a div containing the text.
|
// Check we've now a div containing the text.
|
||||||
const newDivText = await page.$eval(
|
const newDivText = await page.$eval(
|
||||||
|
@ -1288,10 +1310,12 @@ describe("FreeText Editor", () => {
|
||||||
await closePages(pages);
|
await closePages(pages);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("must move an annotation", async () => {
|
it("must edit an annotation", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
|
const modeChangedHandle = await waitForAnnotationModeChanged(page);
|
||||||
await page.click("[data-annotation-id='26R']", { count: 2 });
|
await page.click("[data-annotation-id='26R']", { count: 2 });
|
||||||
|
await awaitPromise(modeChangedHandle);
|
||||||
await page.waitForSelector(`${getEditorSelector(0)}-editor`);
|
await page.waitForSelector(`${getEditorSelector(0)}-editor`);
|
||||||
|
|
||||||
const [focusedId, editable] = await page.evaluate(() => {
|
const [focusedId, editable] = await page.evaluate(() => {
|
||||||
|
@ -1347,6 +1371,7 @@ describe("FreeText Editor", () => {
|
||||||
|
|
||||||
// TODO: remove this when we switch to BiDi.
|
// TODO: remove this when we switch to BiDi.
|
||||||
await hover(page, "[data-annotation-id='23R']");
|
await hover(page, "[data-annotation-id='23R']");
|
||||||
|
|
||||||
// Wait for the popup to be displayed.
|
// Wait for the popup to be displayed.
|
||||||
await page.waitForFunction(
|
await page.waitForFunction(
|
||||||
() =>
|
() =>
|
||||||
|
@ -1588,12 +1613,6 @@ describe("FreeText Editor", () => {
|
||||||
it("must open an existing annotation and check that the position are good", async () => {
|
it("must open an existing annotation and check that the position are good", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
await switchToFreeText(page);
|
|
||||||
|
|
||||||
await page.evaluate(() => {
|
|
||||||
document.getElementById("editorFreeTextParamsToolbar").remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
const toBinary = buf => {
|
const toBinary = buf => {
|
||||||
for (let i = 0; i < buf.length; i += 4) {
|
for (let i = 0; i < buf.length; i += 4) {
|
||||||
const gray =
|
const gray =
|
||||||
|
@ -1646,8 +1665,12 @@ describe("FreeText Editor", () => {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const n of [0, 1, 2, 3, 4]) {
|
const firstPixelsAnnotations = new Map();
|
||||||
const rect = await getRect(page, getEditorSelector(n));
|
|
||||||
|
// [26, 32, ...] are the annotation ids
|
||||||
|
for (const n of [26, 32, 42, 57, 35, 1]) {
|
||||||
|
const id = `${n}R`;
|
||||||
|
const rect = await getRect(page, `[data-annotation-id="${id}"]`);
|
||||||
const editorPng = await page.screenshot({
|
const editorPng = await page.screenshot({
|
||||||
clip: rect,
|
clip: rect,
|
||||||
type: "png",
|
type: "png",
|
||||||
|
@ -1658,33 +1681,33 @@ describe("FreeText Editor", () => {
|
||||||
editorImage.width,
|
editorImage.width,
|
||||||
editorImage.height
|
editorImage.height
|
||||||
);
|
);
|
||||||
|
firstPixelsAnnotations.set(id, { editorFirstPix, rect });
|
||||||
|
}
|
||||||
|
|
||||||
|
await switchToFreeText(page);
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.getElementById("editorFreeTextParamsToolbar").remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const n of [0, 1, 2, 3, 4]) {
|
||||||
const annotationId = await page.evaluate(N => {
|
const annotationId = await page.evaluate(N => {
|
||||||
const editor = document.getElementById(
|
const editor = document.getElementById(
|
||||||
`pdfjs_internal_editor_${N}`
|
`pdfjs_internal_editor_${N}`
|
||||||
);
|
);
|
||||||
const annId = editor.getAttribute("annotation-id");
|
return editor.getAttribute("annotation-id");
|
||||||
const annotation = document.querySelector(
|
|
||||||
`[data-annotation-id="${annId}"]`
|
|
||||||
);
|
|
||||||
editor.hidden = true;
|
|
||||||
annotation.hidden = false;
|
|
||||||
return annId;
|
|
||||||
}, n);
|
}, n);
|
||||||
await page.waitForSelector(`${getEditorSelector(n)}[hidden]`);
|
const { editorFirstPix: annotationFirstPix, rect } =
|
||||||
await page.waitForSelector(
|
firstPixelsAnnotations.get(annotationId);
|
||||||
`[data-annotation-id="${annotationId}"]:not([hidden])`
|
const editorPng = await page.screenshot({
|
||||||
);
|
|
||||||
|
|
||||||
const annotationPng = await page.screenshot({
|
|
||||||
clip: rect,
|
clip: rect,
|
||||||
type: "png",
|
type: "png",
|
||||||
});
|
});
|
||||||
const annotationImage = PNG.sync.read(annotationPng);
|
const editorImage = PNG.sync.read(editorPng);
|
||||||
const annotationFirstPix = getFirstPixel(
|
const editorFirstPix = getFirstPixel(
|
||||||
annotationImage.data,
|
editorImage.data,
|
||||||
annotationImage.width,
|
editorImage.width,
|
||||||
annotationImage.height
|
editorImage.height
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -1719,12 +1742,6 @@ describe("FreeText Editor", () => {
|
||||||
it("must open an existing rotated annotation and check that the position are good", async () => {
|
it("must open an existing rotated annotation and check that the position are good", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
await switchToFreeText(page);
|
|
||||||
|
|
||||||
await page.evaluate(() => {
|
|
||||||
document.getElementById("editorFreeTextParamsToolbar").remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
const toBinary = buf => {
|
const toBinary = buf => {
|
||||||
for (let i = 0; i < buf.length; i += 4) {
|
for (let i = 0; i < buf.length; i += 4) {
|
||||||
const gray =
|
const gray =
|
||||||
|
@ -1806,13 +1823,15 @@ describe("FreeText Editor", () => {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const firstPixelsAnnotations = new Map();
|
||||||
for (const [n, start] of [
|
for (const [n, start] of [
|
||||||
[0, "BL"],
|
[17, "BL"],
|
||||||
[1, "BR"],
|
[18, "BR"],
|
||||||
[2, "TR"],
|
[19, "TR"],
|
||||||
[3, "TL"],
|
[20, "TL"],
|
||||||
]) {
|
]) {
|
||||||
const rect = await getRect(page, getEditorSelector(n));
|
const id = `${n}R`;
|
||||||
|
const rect = await getRect(page, `[data-annotation-id="${id}"]`);
|
||||||
const editorPng = await page.screenshot({
|
const editorPng = await page.screenshot({
|
||||||
clip: rect,
|
clip: rect,
|
||||||
type: "png",
|
type: "png",
|
||||||
|
@ -1824,33 +1843,38 @@ describe("FreeText Editor", () => {
|
||||||
editorImage.height,
|
editorImage.height,
|
||||||
start
|
start
|
||||||
);
|
);
|
||||||
|
firstPixelsAnnotations.set(id, { editorFirstPix, rect });
|
||||||
|
}
|
||||||
|
|
||||||
|
await switchToFreeText(page);
|
||||||
|
|
||||||
|
await page.evaluate(() => {
|
||||||
|
document.getElementById("editorFreeTextParamsToolbar").remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [n, start] of [
|
||||||
|
[0, "BL"],
|
||||||
|
[1, "BR"],
|
||||||
|
[2, "TR"],
|
||||||
|
[3, "TL"],
|
||||||
|
]) {
|
||||||
const annotationId = await page.evaluate(N => {
|
const annotationId = await page.evaluate(N => {
|
||||||
const editor = document.getElementById(
|
const editor = document.getElementById(
|
||||||
`pdfjs_internal_editor_${N}`
|
`pdfjs_internal_editor_${N}`
|
||||||
);
|
);
|
||||||
const annId = editor.getAttribute("annotation-id");
|
return editor.getAttribute("annotation-id");
|
||||||
const annotation = document.querySelector(
|
|
||||||
`[data-annotation-id="${annId}"]`
|
|
||||||
);
|
|
||||||
editor.hidden = true;
|
|
||||||
annotation.hidden = false;
|
|
||||||
return annId;
|
|
||||||
}, n);
|
}, n);
|
||||||
await page.waitForSelector(`${getEditorSelector(n)}[hidden]`);
|
const { editorFirstPix: annotationFirstPix, rect } =
|
||||||
await page.waitForSelector(
|
firstPixelsAnnotations.get(annotationId);
|
||||||
`[data-annotation-id="${annotationId}"]:not([hidden])`
|
const editorPng = await page.screenshot({
|
||||||
);
|
|
||||||
|
|
||||||
const annotationPng = await page.screenshot({
|
|
||||||
clip: rect,
|
clip: rect,
|
||||||
type: "png",
|
type: "png",
|
||||||
});
|
});
|
||||||
const annotationImage = PNG.sync.read(annotationPng);
|
const editorImage = PNG.sync.read(editorPng);
|
||||||
const annotationFirstPix = getFirstPixel(
|
const editorFirstPix = getFirstPixel(
|
||||||
annotationImage.data,
|
editorImage.data,
|
||||||
annotationImage.width,
|
editorImage.width,
|
||||||
annotationImage.height,
|
editorImage.height,
|
||||||
start
|
start
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3552,13 +3576,6 @@ describe("FreeText Editor", () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.waitForSelector("[data-annotation-id='998R'] canvas");
|
|
||||||
let hidden = await page.$eval(
|
|
||||||
"[data-annotation-id='998R'] canvas",
|
|
||||||
el => getComputedStyle(el).display === "none"
|
|
||||||
);
|
|
||||||
expect(hidden).withContext(`In ${browserName}`).toBeTrue();
|
|
||||||
|
|
||||||
// Check we've now a div containing the text.
|
// Check we've now a div containing the text.
|
||||||
await page.waitForSelector(
|
await page.waitForSelector(
|
||||||
"[data-annotation-id='998R'] div.annotationContent"
|
"[data-annotation-id='998R'] div.annotationContent"
|
||||||
|
@ -3571,6 +3588,24 @@ describe("FreeText Editor", () => {
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual("Hello World and edited in Firefox");
|
.toEqual("Hello World and edited in Firefox");
|
||||||
|
|
||||||
|
// Check that the canvas has nothing drawn at the annotation position.
|
||||||
|
await page.$eval(
|
||||||
|
"[data-annotation-id='998R']",
|
||||||
|
el => (el.hidden = true)
|
||||||
|
);
|
||||||
|
let editorPng = await page.screenshot({
|
||||||
|
clip: editorRect,
|
||||||
|
type: "png",
|
||||||
|
});
|
||||||
|
await page.$eval(
|
||||||
|
"[data-annotation-id='998R']",
|
||||||
|
el => (el.hidden = false)
|
||||||
|
);
|
||||||
|
let editorImage = PNG.sync.read(editorPng);
|
||||||
|
expect(editorImage.data.every(x => x === 0xff))
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeTrue();
|
||||||
|
|
||||||
const oneToThirteen = Array.from(new Array(13).keys(), n => n + 2);
|
const oneToThirteen = Array.from(new Array(13).keys(), n => n + 2);
|
||||||
for (const pageNumber of oneToThirteen) {
|
for (const pageNumber of oneToThirteen) {
|
||||||
await scrollIntoView(
|
await scrollIntoView(
|
||||||
|
@ -3587,6 +3622,19 @@ describe("FreeText Editor", () => {
|
||||||
await switchToFreeText(page, /* disable = */ true);
|
await switchToFreeText(page, /* disable = */ true);
|
||||||
|
|
||||||
const thirteenToOne = Array.from(new Array(13).keys(), n => 13 - n);
|
const thirteenToOne = Array.from(new Array(13).keys(), n => 13 - n);
|
||||||
|
const handlePromise = await createPromise(page, resolve => {
|
||||||
|
const callback = e => {
|
||||||
|
if (e.source.id === 1) {
|
||||||
|
window.PDFViewerApplication.eventBus.off(
|
||||||
|
"pagerendered",
|
||||||
|
callback
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.PDFViewerApplication.eventBus.on("pagerendered", callback);
|
||||||
|
});
|
||||||
|
|
||||||
for (const pageNumber of thirteenToOne) {
|
for (const pageNumber of thirteenToOne) {
|
||||||
await scrollIntoView(
|
await scrollIntoView(
|
||||||
page,
|
page,
|
||||||
|
@ -3594,12 +3642,16 @@ describe("FreeText Editor", () => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await page.waitForSelector("[data-annotation-id='998R'] canvas");
|
await awaitPromise(handlePromise);
|
||||||
hidden = await page.$eval(
|
|
||||||
"[data-annotation-id='998R'] canvas",
|
editorPng = await page.screenshot({
|
||||||
el => getComputedStyle(el).display === "none"
|
clip: editorRect,
|
||||||
);
|
type: "png",
|
||||||
expect(hidden).withContext(`In ${browserName}`).toBeFalse();
|
});
|
||||||
|
editorImage = PNG.sync.read(editorPng);
|
||||||
|
expect(editorImage.data.every(x => x === 0xff))
|
||||||
|
.withContext(`In ${browserName}`)
|
||||||
|
.toBeFalse();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -564,14 +564,14 @@ describe("Stamp Editor", () => {
|
||||||
for (let i = 0; i < pages1.length; i++) {
|
for (let i = 0; i < pages1.length; i++) {
|
||||||
const [, page1] = pages1[i];
|
const [, page1] = pages1[i];
|
||||||
await page1.bringToFront();
|
await page1.bringToFront();
|
||||||
await page1.click("#editorStamp");
|
await switchToStamp(page1);
|
||||||
|
|
||||||
await copyImage(page1, "../images/firefox_logo.png", 0);
|
await copyImage(page1, "../images/firefox_logo.png", 0);
|
||||||
await copy(page1);
|
await copy(page1);
|
||||||
|
|
||||||
const [, page2] = pages2[i];
|
const [, page2] = pages2[i];
|
||||||
await page2.bringToFront();
|
await page2.bringToFront();
|
||||||
await page2.click("#editorStamp");
|
await switchToStamp(page2);
|
||||||
|
|
||||||
await paste(page2);
|
await paste(page2);
|
||||||
|
|
||||||
|
|
|
@ -447,11 +447,30 @@ function waitForAnnotationEditorLayer(page) {
|
||||||
return createPromise(page, resolve => {
|
return createPromise(page, resolve => {
|
||||||
window.PDFViewerApplication.eventBus.on(
|
window.PDFViewerApplication.eventBus.on(
|
||||||
"annotationeditorlayerrendered",
|
"annotationeditorlayerrendered",
|
||||||
resolve
|
resolve,
|
||||||
|
{ once: true }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function waitForAnnotationModeChanged(page) {
|
||||||
|
return createPromise(page, resolve => {
|
||||||
|
window.PDFViewerApplication.eventBus.on(
|
||||||
|
"annotationeditormodechanged",
|
||||||
|
resolve,
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForPageRendered(page) {
|
||||||
|
return createPromise(page, resolve => {
|
||||||
|
window.PDFViewerApplication.eventBus.on("pagerendered", resolve, {
|
||||||
|
once: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function scrollIntoView(page, selector) {
|
async function scrollIntoView(page, selector) {
|
||||||
const handle = await page.evaluateHandle(
|
const handle = await page.evaluateHandle(
|
||||||
sel => [
|
sel => [
|
||||||
|
@ -695,8 +714,10 @@ export {
|
||||||
serializeBitmapDimensions,
|
serializeBitmapDimensions,
|
||||||
switchToEditor,
|
switchToEditor,
|
||||||
waitForAnnotationEditorLayer,
|
waitForAnnotationEditorLayer,
|
||||||
|
waitForAnnotationModeChanged,
|
||||||
waitForEntryInStorage,
|
waitForEntryInStorage,
|
||||||
waitForEvent,
|
waitForEvent,
|
||||||
|
waitForPageRendered,
|
||||||
waitForSandboxTrip,
|
waitForSandboxTrip,
|
||||||
waitForSelectedEditor,
|
waitForSelectedEditor,
|
||||||
waitForSerialized,
|
waitForSerialized,
|
||||||
|
|
|
@ -182,6 +182,10 @@ class AnnotationLayerBuilder {
|
||||||
this.div.hidden = true;
|
this.div.hidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasEditableAnnotations() {
|
||||||
|
return !!this.annotationLayer?.hasEditableAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
#updatePresentationModeState(state) {
|
#updatePresentationModeState(state) {
|
||||||
if (!this.div) {
|
if (!this.div) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -119,6 +119,8 @@ class PDFPageView {
|
||||||
|
|
||||||
#hasRestrictedScaling = false;
|
#hasRestrictedScaling = false;
|
||||||
|
|
||||||
|
#isEditing = false;
|
||||||
|
|
||||||
#layerProperties = null;
|
#layerProperties = null;
|
||||||
|
|
||||||
#loadingId = null;
|
#loadingId = null;
|
||||||
|
@ -354,6 +356,10 @@ class PDFPageView {
|
||||||
this.pdfPage?.cleanup();
|
this.pdfPage?.cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasEditableAnnotations() {
|
||||||
|
return !!this.annotationLayer?.hasEditableAnnotations();
|
||||||
|
}
|
||||||
|
|
||||||
get _textHighlighter() {
|
get _textHighlighter() {
|
||||||
return shadow(
|
return shadow(
|
||||||
this,
|
this,
|
||||||
|
@ -582,6 +588,20 @@ class PDFPageView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleEditingMode(isEditing) {
|
||||||
|
if (!this.hasEditableAnnotations()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.#isEditing = isEditing;
|
||||||
|
this.reset({
|
||||||
|
keepZoomLayer: true,
|
||||||
|
keepAnnotationLayer: true,
|
||||||
|
keepAnnotationEditorLayer: true,
|
||||||
|
keepXfaLayer: true,
|
||||||
|
keepTextLayer: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} PDFPageViewUpdateParameters
|
* @typedef {Object} PDFPageViewUpdateParameters
|
||||||
* @property {number} [scale] The new scale, if specified.
|
* @property {number} [scale] The new scale, if specified.
|
||||||
|
@ -1037,6 +1057,7 @@ class PDFPageView {
|
||||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||||
annotationCanvasMap: this._annotationCanvasMap,
|
annotationCanvasMap: this._annotationCanvasMap,
|
||||||
pageColors,
|
pageColors,
|
||||||
|
isEditing: this.#isEditing,
|
||||||
};
|
};
|
||||||
const renderTask = (this.renderTask = pdfPage.render(renderContext));
|
const renderTask = (this.renderTask = pdfPage.render(renderContext));
|
||||||
renderTask.onContinue = renderContinueCallback;
|
renderTask.onContinue = renderContinueCallback;
|
||||||
|
|
|
@ -223,6 +223,10 @@ class PDFViewer {
|
||||||
|
|
||||||
#mlManager = null;
|
#mlManager = null;
|
||||||
|
|
||||||
|
#onPageRenderedCallback = null;
|
||||||
|
|
||||||
|
#switchAnnotationEditorModeTimeoutId = null;
|
||||||
|
|
||||||
#getAllTextInProgress = false;
|
#getAllTextInProgress = false;
|
||||||
|
|
||||||
#hiddenCopyElement = null;
|
#hiddenCopyElement = null;
|
||||||
|
@ -1117,6 +1121,10 @@ class PDFViewer {
|
||||||
|
|
||||||
this.#hiddenCopyElement?.remove();
|
this.#hiddenCopyElement?.remove();
|
||||||
this.#hiddenCopyElement = null;
|
this.#hiddenCopyElement = null;
|
||||||
|
|
||||||
|
this.#onPageRenderedCallback = null;
|
||||||
|
clearTimeout(this.#switchAnnotationEditorModeTimeoutId);
|
||||||
|
this.#switchAnnotationEditorModeTimeoutId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ensurePageViewVisible() {
|
#ensurePageViewVisible() {
|
||||||
|
@ -1653,6 +1661,32 @@ class PDFViewer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#switchToEditAnnotationMode() {
|
||||||
|
const visible = this._getVisiblePages();
|
||||||
|
const pagesToRefresh = [];
|
||||||
|
const { ids, views } = visible;
|
||||||
|
for (const page of views) {
|
||||||
|
const { view } = page;
|
||||||
|
if (!view.hasEditableAnnotations()) {
|
||||||
|
ids.delete(view.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
pagesToRefresh.push(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pagesToRefresh.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
this.renderingQueue.renderHighestPriority({
|
||||||
|
first: pagesToRefresh[0],
|
||||||
|
last: pagesToRefresh.at(-1),
|
||||||
|
views: pagesToRefresh,
|
||||||
|
ids,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ids;
|
||||||
|
}
|
||||||
|
|
||||||
containsElement(element) {
|
containsElement(element) {
|
||||||
return this.container.contains(element);
|
return this.container.contains(element);
|
||||||
}
|
}
|
||||||
|
@ -2259,13 +2293,56 @@ class PDFViewer {
|
||||||
if (!this.pdfDocument) {
|
if (!this.pdfDocument) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.#annotationEditorMode = mode;
|
|
||||||
this.eventBus.dispatch("annotationeditormodechanged", {
|
|
||||||
source: this,
|
|
||||||
mode,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard);
|
const { eventBus } = this;
|
||||||
|
const updater = () => {
|
||||||
|
if (this.#onPageRenderedCallback) {
|
||||||
|
eventBus._off("pagerendered", this.#onPageRenderedCallback);
|
||||||
|
this.#onPageRenderedCallback = null;
|
||||||
|
}
|
||||||
|
if (this.#switchAnnotationEditorModeTimeoutId !== null) {
|
||||||
|
clearTimeout(this.#switchAnnotationEditorModeTimeoutId);
|
||||||
|
this.#switchAnnotationEditorModeTimeoutId = null;
|
||||||
|
}
|
||||||
|
this.#annotationEditorMode = mode;
|
||||||
|
eventBus.dispatch("annotationeditormodechanged", {
|
||||||
|
source: this,
|
||||||
|
mode,
|
||||||
|
});
|
||||||
|
this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
mode === AnnotationEditorType.NONE ||
|
||||||
|
this.#annotationEditorMode === AnnotationEditorType.NONE
|
||||||
|
) {
|
||||||
|
const isEditing = mode !== AnnotationEditorType.NONE;
|
||||||
|
if (!isEditing) {
|
||||||
|
this.pdfDocument.annotationStorage.resetModifiedIds();
|
||||||
|
}
|
||||||
|
for (const pageView of this._pages) {
|
||||||
|
pageView.toggleEditingMode(isEditing);
|
||||||
|
}
|
||||||
|
// We must call #switchToEditAnnotationMode unconditionally to ensure that
|
||||||
|
// page is rendered if it's useful or not.
|
||||||
|
const idsToRefresh = this.#switchToEditAnnotationMode();
|
||||||
|
if (isEditing && editId && idsToRefresh) {
|
||||||
|
// We're editing an existing annotation so we must switch to editing
|
||||||
|
// mode when the rendering is done.
|
||||||
|
const { signal } = this.#eventAbortController;
|
||||||
|
this.#onPageRenderedCallback = ({ pageNumber }) => {
|
||||||
|
idsToRefresh.delete(pageNumber);
|
||||||
|
if (idsToRefresh.size === 0) {
|
||||||
|
eventBus._off("pagerendered", this.#onPageRenderedCallback);
|
||||||
|
this.#onPageRenderedCallback = null;
|
||||||
|
this.#switchAnnotationEditorModeTimeoutId = setTimeout(updater, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
eventBus._on("pagerendered", this.#onPageRenderedCallback, { signal });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updater();
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line accessor-pairs
|
// eslint-disable-next-line accessor-pairs
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue