Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[api-minor][Editor] When switching to editing mode, redraw pages containing editable annotations (bug 1883884) #18134

Merged
merged 1 commit into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ class Annotation {
hasOwnCanvas: false,
noRotate: !!(this.flags & AnnotationFlag.NOROTATE),
noHTML: isLocked && isContentLocked,
isEditable: false,
};

if (params.collectFields) {
Expand Down Expand Up @@ -776,6 +777,10 @@ class Annotation {
return this.printable;
}

mustBeViewedWhenEditing() {
return !this.data.isEditable;
}

/**
* @type {boolean}
*/
Expand Down Expand Up @@ -3802,7 +3807,8 @@ class FreeTextAnnotation extends MarkupAnnotation {
// 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
// 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.
this.data.noHTML = false;

Expand Down
6 changes: 5 additions & 1 deletion src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ class Page {
intent,
cacheKey,
annotationStorage = null,
isEditing = false,
modifiedIds = null,
}) {
const contentStreamPromise = this.getContentStream();
const resourcesPromise = this.loadResources([
Expand Down Expand Up @@ -579,7 +581,9 @@ class Page {
if (
intentAny ||
(intentDisplay &&
annotation.mustBeViewed(annotationStorage, renderForms)) ||
annotation.mustBeViewed(annotationStorage, renderForms) &&
((isEditing && annotation.mustBeViewedWhenEditing()) ||
(!isEditing && !modifiedIds?.has(annotation.data.id)))) ||
(intentPrint && annotation.mustBePrinted(annotationStorage))
) {
opListPromises.push(
Expand Down
2 changes: 2 additions & 0 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,8 @@ class WorkerMessageHandler {
intent: data.intent,
cacheKey: data.cacheKey,
annotationStorage: data.annotationStorage,
isEditing: data.isEditing,
modifiedIds: data.modifiedIds,
})
.then(
function (operatorListInfo) {
Expand Down
18 changes: 9 additions & 9 deletions src/display/annotation_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ class AnnotationElement {
return !!(titleObj?.str || contentsObj?.str || richText?.str);
}

get _isEditable() {
return this.data.isEditable;
}

get hasPopupData() {
return AnnotationElement._hasPopupData(this.data);
}
Expand Down Expand Up @@ -734,10 +738,6 @@ class AnnotationElement {
}
}

get _isEditable() {
return false;
}

_editOnDoubleClick() {
if (!this._isEditable) {
return;
Expand Down Expand Up @@ -2530,10 +2530,6 @@ class FreeTextAnnotationElement extends AnnotationElement {

return this.container;
}

get _isEditable() {
return this.data.hasOwnCanvas;
}
}

class LineAnnotationElement extends AnnotationElement {
Expand Down Expand Up @@ -3107,6 +3103,10 @@ class AnnotationLayer {
}
}

hasEditableAnnotations() {
return this.#editableAnnotations.size > 0;
}

#appendElement(element, id) {
const contentElement = element.firstChild || element;
contentElement.id = `${AnnotationPrefix}${id}`;
Expand Down Expand Up @@ -3188,7 +3188,7 @@ class AnnotationLayer {
}
this.#appendElement(rendered, data.id);

if (element.annotationEditorType > 0) {
if (element._isEditable) {
this.#editableAnnotations.set(element.data.id, element);
this._annotationEditorUIManager?.renderAnnotationElement(element);
}
Expand Down
39 changes: 38 additions & 1 deletion src/display/annotation_storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* 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 { MurmurHash3_64 } from "../shared/murmurhash3.js";

Expand All @@ -29,6 +29,8 @@ const SerializableEmpty = Object.freeze({
class AnnotationStorage {
#modified = false;

#modifiedIds = null;

#storage = new Map();

constructor() {
Expand Down Expand Up @@ -248,6 +250,34 @@ class AnnotationStorage {
}
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(","),
});
}
}

/**
Expand Down Expand Up @@ -282,6 +312,13 @@ class PrintAnnotationStorage extends AnnotationStorage {
get serializable() {
return this.#serializable;
}

get modifiedIds() {
return shadow(this, "modifiedIds", {
ids: new Set(),
hash: "",
});
}
}

export { AnnotationStorage, PrintAnnotationStorage, SerializableEmpty };
39 changes: 31 additions & 8 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1227,6 +1227,7 @@ class PDFDocumentProxy {
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap] - Map some
* annotation ids with canvases used to render them.
* @property {PrintAnnotationStorage} [printAnnotationStorage]
* @property {boolean} [isEditing] - Render the page in editing mode.
*/

/**
Expand All @@ -1248,6 +1249,7 @@ class PDFDocumentProxy {
* from the {@link AnnotationStorage}-instance; useful e.g. for printing.
* The default value is `AnnotationMode.ENABLE`.
* @property {PrintAnnotationStorage} [printAnnotationStorage]
* @property {boolean} [isEditing] - Render the page in editing mode.
*/

/**
Expand Down Expand Up @@ -1420,13 +1422,15 @@ class PDFPageProxy {
annotationCanvasMap = null,
pageColors = null,
printAnnotationStorage = null,
isEditing = false,
}) {
this._stats?.time("Overall");

const intentArgs = this._transport.getRenderingIntent(
intent,
annotationMode,
printAnnotationStorage
printAnnotationStorage,
isEditing
);
const { renderingIntent, cacheKey } = intentArgs;
// If there was a pending destroy, cancel it so no cleanup happens during
Expand Down Expand Up @@ -1560,6 +1564,7 @@ class PDFPageProxy {
intent = "display",
annotationMode = AnnotationMode.ENABLE,
printAnnotationStorage = null,
isEditing = false,
} = {}) {
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("GENERIC")) {
throw new Error("Not implemented: getOperatorList");
Expand All @@ -1576,6 +1581,7 @@ class PDFPageProxy {
intent,
annotationMode,
printAnnotationStorage,
isEditing,
/* isOpList = */ true
);
let intentState = this._intentStates.get(intentArgs.cacheKey);
Expand Down Expand Up @@ -1812,6 +1818,8 @@ class PDFPageProxy {
renderingIntent,
cacheKey,
annotationStorageSerializable,
isEditing,
modifiedIds,
}) {
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
assert(
Expand All @@ -1828,6 +1836,8 @@ class PDFPageProxy {
intent: renderingIntent,
cacheKey,
annotationStorage: map,
isEditing,
modifiedIds,
},
transfer
);
Expand Down Expand Up @@ -2420,6 +2430,7 @@ class WorkerTransport {
intent,
annotationMode = AnnotationMode.ENABLE,
printAnnotationStorage = null,
isEditing = false,
isOpList = false
) {
let renderingIntent = RenderingIntentFlag.DISPLAY; // Default value.
Expand All @@ -2438,6 +2449,12 @@ class WorkerTransport {
warn(`getRenderingIntent - invalid intent: ${intent}`);
}

const annotationStorage =
renderingIntent & RenderingIntentFlag.PRINT &&
printAnnotationStorage instanceof PrintAnnotationStorage
? printAnnotationStorage
: this.annotationStorage;

switch (annotationMode) {
case AnnotationMode.DISABLE:
renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE;
Expand All @@ -2450,12 +2467,6 @@ class WorkerTransport {
case AnnotationMode.ENABLE_STORAGE:
renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE;

const annotationStorage =
renderingIntent & RenderingIntentFlag.PRINT &&
printAnnotationStorage instanceof PrintAnnotationStorage
? printAnnotationStorage
: this.annotationStorage;

annotationStorageSerializable = annotationStorage.serializable;
break;
default:
Expand All @@ -2466,10 +2477,22 @@ class WorkerTransport {
renderingIntent += RenderingIntentFlag.OPLIST;
}

const { ids: modifiedIds, hash: modifiedIdsHash } =
annotationStorage.modifiedIds;

const cacheKeyBuf = [
renderingIntent,
annotationStorageSerializable.hash,
isEditing ? 1 : 0,
modifiedIdsHash,
];

return {
renderingIntent,
cacheKey: `${renderingIntent}_${annotationStorageSerializable.hash}`,
cacheKey: cacheKeyBuf.join("_"),
annotationStorageSerializable,
isEditing,
modifiedIds,
};
}

Expand Down
8 changes: 8 additions & 0 deletions test/integration/annotation_spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,14 @@ describe("ResetForm action", () => {
it("must check that the Ink annotation has a popup", async () => {
await Promise.all(
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(
`document.querySelector("[data-annotation-id='25R']").hidden === false`
);
Expand Down
Loading