Skip to content

Commit

Permalink
[api-minor][Editor] When switching to editing mode, redraw pages cont…
Browse files Browse the repository at this point in the history
…aining 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.
  • Loading branch information
calixteman committed May 29, 2024
1 parent 24e12d5 commit 2b5eac1
Show file tree
Hide file tree
Showing 14 changed files with 345 additions and 98 deletions.
8 changes: 7 additions & 1 deletion src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ class Annotation {
hasOwnCanvas: false,
noRotate: !!(this.flags & AnnotationFlag.NOROTATE),
noHTML: isLocked && isContentLocked,
isEditable: false,
};

if (params.collectFields) {
Expand Down Expand Up @@ -789,6 +790,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
14 changes: 12 additions & 2 deletions src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,8 @@ class Page {
});
});

const modifiedIds = this.pdfManager.modifiedIds;

// Fetch the page's annotations and add their operator lists to the
// page's operator list to render them.
return Promise.all([
Expand Down Expand Up @@ -570,7 +572,10 @@ class Page {
const renderForms = !!(intent & RenderingIntentFlag.ANNOTATIONS_FORMS),
intentAny = !!(intent & RenderingIntentFlag.ANY),
intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY),
intentPrint = !!(intent & RenderingIntentFlag.PRINT);
intentPrint = !!(intent & RenderingIntentFlag.PRINT),
intentDisplayEdit = !!(
intent & RenderingIntentFlag.ANNOTATIONS_DISPLAY_EDIT
);

// Collect the operator list promises for the annotations. Each promise
// is resolved with the complete operator list for a single annotation.
Expand All @@ -579,7 +584,11 @@ class Page {
if (
intentAny ||
(intentDisplay &&
annotation.mustBeViewed(annotationStorage, renderForms)) ||
((intentDisplayEdit &&
annotation.mustBeViewedWhenEditing(annotationStorage)) ||
(!intentDisplayEdit &&
annotation.mustBeViewed(annotationStorage, renderForms) &&
!modifiedIds?.has(annotation.data.id)))) ||
(intentPrint && annotation.mustBePrinted(annotationStorage))
) {
opListPromises.push(
Expand Down Expand Up @@ -910,6 +919,7 @@ class PDFDocument {
this.xref = new XRef(stream, pdfManager);
this._pagePromises = new Map();
this._version = null;
this.modifiedIds = null;

const idCounters = {
font: 0,
Expand Down
5 changes: 5 additions & 0 deletions src/core/pdf_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class BasePdfManager {
this._docId = args.docId;
this._password = args.password;
this.enableXfa = args.enableXfa;
this.modifiedIds = null;

// Check `OffscreenCanvas` support once, rather than repeatedly throughout
// the worker-thread code.
Expand Down Expand Up @@ -127,6 +128,10 @@ class BasePdfManager {
terminate(reason) {
unreachable("Abstract method `terminate` called");
}

endEditingSession(ids) {
this.modifiedIds = ids ? new Set(ids) : null;
}
}

class LocalPdfManager extends BasePdfManager {
Expand Down
4 changes: 4 additions & 0 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,10 @@ class WorkerMessageHandler {
docParams = null; // we don't need docParams anymore -- saving memory.
});

handler.on("EndEditingSession", function (ids) {
return pdfManager.endEditingSession(ids);
});

if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
handler.on("GetXFADatasets", function (data) {
return pdfManager.ensureDoc("xfaDatasets");
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 @@ -728,10 +732,6 @@ class AnnotationElement {
}
}

get _isEditable() {
return false;
}

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

return this.container;
}

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

class LineAnnotationElement extends AnnotationElement {
Expand Down Expand Up @@ -3094,6 +3090,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 @@ -3175,7 +3175,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
15 changes: 15 additions & 0 deletions src/display/annotation_storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,21 @@ class AnnotationStorage {
}
return stats;
}

get modifiedIds() {
let ids = null;
for (const value of this.#storage.values()) {
if (
!(value instanceof AnnotationEditor) ||
!value.annotationElementId ||
!value.serialize()
) {
continue;
}
(ids ||= []).push(value.annotationElementId);
}
return ids;
}
}

/**
Expand Down
31 changes: 28 additions & 3 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
AbortException,
AnnotationMode,
assert,
EditingStatus,
getVerbosityLevel,
info,
InvalidPDFException,
Expand Down Expand Up @@ -1103,6 +1104,13 @@ class PDFDocumentProxy {
getCalculationOrderIds() {
return this._transport.getCalculationOrderIds();
}

/**
* Ends the editing session for the current document.
*/
endEditingSession() {
return this._transport.endEditingSession();
}
}

/**
Expand Down Expand Up @@ -1229,6 +1237,7 @@ class PDFDocumentProxy {
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap] - Map some
* annotation ids with canvases used to render them.
* @property {PrintAnnotationStorage} [printAnnotationStorage]
* @property {number} [editingStatus] - Controls if the page is in editing mode.
*/

/**
Expand Down Expand Up @@ -1422,13 +1431,16 @@ class PDFPageProxy {
annotationCanvasMap = null,
pageColors = null,
printAnnotationStorage = null,
editingStatus = EditingStatus.NONE,
}) {
this._stats?.time("Overall");

const intentArgs = this._transport.getRenderingIntent(
intent,
annotationMode,
printAnnotationStorage
printAnnotationStorage,
/* isOpList = */ false,
editingStatus
);
const { renderingIntent, cacheKey } = intentArgs;
// If there was a pending destroy, cancel it so no cleanup happens during
Expand All @@ -1440,7 +1452,8 @@ class PDFPageProxy {
optionalContentConfigPromise ||=
this._transport.getOptionalContentConfig(renderingIntent);

let intentState = this._intentStates.get(cacheKey);
let intentState =
editingStatus !== EditingStatus.END && this._intentStates.get(cacheKey);
if (!intentState) {
intentState = Object.create(null);
this._intentStates.set(cacheKey, intentState);
Expand Down Expand Up @@ -2427,7 +2440,8 @@ class WorkerTransport {
intent,
annotationMode = AnnotationMode.ENABLE,
printAnnotationStorage = null,
isOpList = false
isOpList = false,
editingStatus = EditingStatus.NONE
) {
let renderingIntent = RenderingIntentFlag.DISPLAY; // Default value.
let annotationStorageSerializable = SerializableEmpty;
Expand Down Expand Up @@ -2473,6 +2487,10 @@ class WorkerTransport {
renderingIntent += RenderingIntentFlag.OPLIST;
}

if (editingStatus === EditingStatus.START) {
renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISPLAY_EDIT;
}

return {
renderingIntent,
cacheKey: `${renderingIntent}_${annotationStorageSerializable.hash}`,
Expand Down Expand Up @@ -3098,6 +3116,13 @@ class WorkerTransport {
const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`;
return this.#pageRefCache.get(refStr) ?? null;
}

endEditingSession() {
this.messageHandler.sendWithPromise(
"EndEditingSession",
this.annotationStorage.modifiedIds
);
}
}

const INITIAL_DATA = Symbol("INITIAL_DATA");
Expand Down
8 changes: 8 additions & 0 deletions src/shared/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const RenderingIntentFlag = {
ANNOTATIONS_FORMS: 0x10,
ANNOTATIONS_STORAGE: 0x20,
ANNOTATIONS_DISABLE: 0x40,
ANNOTATIONS_DISPLAY_EDIT: 0x80,
OPLIST: 0x100,
};

Expand All @@ -66,6 +67,12 @@ const AnnotationMode = {
ENABLE_STORAGE: 3,
};

const EditingStatus = {
NONE: 0,
START: 1,
END: 2,
};

const AnnotationEditorPrefix = "pdfjs_internal_editor_";

const AnnotationEditorType = {
Expand Down Expand Up @@ -1105,6 +1112,7 @@ export {
CMapCompressionType,
createValidAbsoluteUrl,
DocumentActionEventType,
EditingStatus,
FeatureTest,
FONT_IDENTITY_MATRIX,
FontRenderOps,
Expand Down
Loading

0 comments on commit 2b5eac1

Please sign in to comment.