Skip to content

Commit 65dd6b0

Browse files
authored
Merge pull request #20167 from calixteman/get_all_editable_annotations
[Editor] Add the ability to get all the editable annotations in a pdf document
2 parents dd560ee + 9e5ee1e commit 65dd6b0

File tree

7 files changed

+183
-4
lines changed

7 files changed

+183
-4
lines changed

src/core/annotation.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ class AnnotationFactory {
122122
* @param {Object} idFactory
123123
* @param {boolean} [collectFields]
124124
* @param {Object} [orphanFields]
125+
* @param {Array<string>} [collectByType]
125126
* @param {Object} [pageRef]
126127
* @returns {Promise} A promise that is resolved with an {Annotation}
127128
* instance.
@@ -133,6 +134,7 @@ class AnnotationFactory {
133134
idFactory,
134135
collectFields,
135136
orphanFields,
137+
collectByType,
136138
pageRef
137139
) {
138140
const pageIndex = collectFields
@@ -146,6 +148,7 @@ class AnnotationFactory {
146148
idFactory,
147149
collectFields,
148150
orphanFields,
151+
collectByType,
149152
pageIndex,
150153
pageRef,
151154
]);
@@ -161,6 +164,7 @@ class AnnotationFactory {
161164
idFactory,
162165
collectFields = false,
163166
orphanFields = null,
167+
collectByType = null,
164168
pageIndex = null,
165169
pageRef = null
166170
) {
@@ -169,14 +173,21 @@ class AnnotationFactory {
169173
return undefined;
170174
}
171175

172-
const { acroForm, pdfManager } = annotationGlobals;
173-
const id =
174-
ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`;
175-
176176
// Determine the annotation's subtype.
177177
let subtype = dict.get("Subtype");
178178
subtype = subtype instanceof Name ? subtype.name : null;
179179

180+
if (
181+
collectByType &&
182+
!collectByType.has(AnnotationType[subtype.toUpperCase()])
183+
) {
184+
return null;
185+
}
186+
187+
const { acroForm, pdfManager } = annotationGlobals;
188+
const id =
189+
ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`;
190+
180191
// Return the right annotation object based on the subtype and field type.
181192
const parameters = {
182193
xref,

src/core/document.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,7 @@ class Page {
802802
this._localIdFactory,
803803
/* collectFields */ false,
804804
orphanFields,
805+
/* collectByType */ null,
805806
this.ref
806807
).catch(function (reason) {
807808
warn(`_parsedAnnotations: "${reason}".`);
@@ -849,6 +850,51 @@ class Page {
849850
);
850851
return shadow(this, "jsActions", actions);
851852
}
853+
854+
async collectAnnotationsByType(
855+
handler,
856+
task,
857+
types,
858+
promises,
859+
annotationGlobals
860+
) {
861+
const annots = await this.pdfManager.ensure(this, "annotations");
862+
const { pageIndex } = this;
863+
for (const annotationRef of annots) {
864+
promises.push(
865+
AnnotationFactory.create(
866+
this.xref,
867+
annotationRef,
868+
annotationGlobals,
869+
this._localIdFactory,
870+
/* collectFields */ false,
871+
/* orphanFields */ null,
872+
/* collectByType */ types,
873+
this.ref
874+
)
875+
.then(async annotation => {
876+
if (!annotation) {
877+
return null;
878+
}
879+
annotation.data.pageIndex = pageIndex;
880+
if (annotation.hasTextContent && annotation.viewable) {
881+
const partialEvaluator = this.#createPartialEvaluator(handler);
882+
await annotation.extractTextContent(partialEvaluator, task, [
883+
-Infinity,
884+
-Infinity,
885+
Infinity,
886+
Infinity,
887+
]);
888+
}
889+
return annotation.data;
890+
})
891+
.catch(function (reason) {
892+
warn(`collectAnnotationsByType: "${reason}".`);
893+
return null;
894+
})
895+
);
896+
}
897+
}
852898
}
853899

854900
const PDF_HEADER_SIGNATURE = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]);
@@ -1881,6 +1927,7 @@ class PDFDocument {
18811927
/* idFactory = */ null,
18821928
/* collectFields */ true,
18831929
orphanFields,
1930+
/* collectByType */ null,
18841931
/* pageRef */ null
18851932
)
18861933
.then(annotation => annotation?.getFieldObject())

src/core/worker.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,57 @@ class WorkerMessageHandler {
447447
.then(page => pdfManager.ensure(page, "jsActions"));
448448
});
449449

450+
handler.on(
451+
"GetAnnotationsByType",
452+
async function ({ types, pageIndexesToSkip }) {
453+
const [numPages, annotationGlobals] = await Promise.all([
454+
pdfManager.ensureDoc("numPages"),
455+
pdfManager.ensureDoc("annotationGlobals"),
456+
]);
457+
458+
if (!annotationGlobals) {
459+
return null;
460+
}
461+
const pagePromises = [];
462+
const annotationPromises = [];
463+
let task = null;
464+
try {
465+
for (let i = 0, ii = numPages; i < ii; i++) {
466+
if (pageIndexesToSkip?.has(i)) {
467+
continue;
468+
}
469+
if (!task) {
470+
task = new WorkerTask("GetAnnotationsByType");
471+
startWorkerTask(task);
472+
}
473+
pagePromises.push(
474+
pdfManager.getPage(i).then(async page => {
475+
if (!page) {
476+
return [];
477+
}
478+
return (
479+
page.collectAnnotationsByType(
480+
handler,
481+
task,
482+
types,
483+
annotationPromises,
484+
annotationGlobals
485+
) || []
486+
);
487+
})
488+
);
489+
}
490+
await Promise.all(pagePromises);
491+
const annotations = await Promise.all(annotationPromises);
492+
return annotations.filter(a => !!a);
493+
} finally {
494+
if (task) {
495+
finishWorkerTask(task);
496+
}
497+
}
498+
}
499+
);
500+
450501
handler.on("GetOutline", function (data) {
451502
return pdfManager.ensureCatalog("documentOutline");
452503
});

src/display/api.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,16 @@ class PDFDocumentProxy {
906906
return this._transport.getAttachments();
907907
}
908908

909+
/**
910+
* @param {Set<number>} types - The annotation types to retrieve.
911+
* @param {Set<number>} pageIndexesToSkip
912+
* @returns {Promise<Array<Object>>} A promise that is resolved with a list of
913+
* annotations data.
914+
*/
915+
getAnnotationsByType(types, pageIndexesToSkip) {
916+
return this._transport.getAnnotationsByType(types, pageIndexesToSkip);
917+
}
918+
909919
/**
910920
* @returns {Promise<Object | null>} A promise that is resolved with
911921
* an {Object} with the JavaScript actions:
@@ -2944,6 +2954,13 @@ class WorkerTransport {
29442954
return this.messageHandler.sendWithPromise("GetAttachments", null);
29452955
}
29462956

2957+
getAnnotationsByType(types, pageIndexesToSkip) {
2958+
return this.messageHandler.sendWithPromise("GetAnnotationsByType", {
2959+
types,
2960+
pageIndexesToSkip,
2961+
});
2962+
}
2963+
29472964
getDocJSActions() {
29482965
return this.#cacheSimpleMethod("GetDocJSActions");
29492966
}

test/pdfs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,3 +740,4 @@
740740
!dates_save.pdf
741741
!print_protection.pdf
742742
!tracemonkey_with_annotations.pdf
743+
!tracemonkey_with_editable_annotations.pdf
Binary file not shown.

test/unit/api_spec.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3205,6 +3205,58 @@ describe("api", function () {
32053205
});
32063206
});
32073207
});
3208+
3209+
describe("Get annotations by their types in the document", function () {
3210+
it("gets editable annotations", async function () {
3211+
const loadingTask = getDocument(
3212+
buildGetDocumentParams("tracemonkey_with_editable_annotations.pdf")
3213+
);
3214+
const pdfDoc = await loadingTask.promise;
3215+
3216+
// Get all the editable annotations in the document.
3217+
let editableAnnotations = (
3218+
await pdfDoc.getAnnotationsByType(
3219+
new Set([
3220+
AnnotationType.FREETEXT,
3221+
AnnotationType.STAMP,
3222+
AnnotationType.INK,
3223+
AnnotationType.HIGHLIGHT,
3224+
]),
3225+
null
3226+
)
3227+
).map(annotation => ({
3228+
id: annotation.id,
3229+
subtype: annotation.subtype,
3230+
pageIndex: annotation.pageIndex,
3231+
}));
3232+
editableAnnotations.sort((a, b) => a.id.localeCompare(b.id));
3233+
expect(editableAnnotations).toEqual([
3234+
{ id: "1000R", subtype: "FreeText", pageIndex: 12 },
3235+
{ id: "1001R", subtype: "Stamp", pageIndex: 12 },
3236+
{ id: "1011R", subtype: "Stamp", pageIndex: 13 },
3237+
{ id: "997R", subtype: "Ink", pageIndex: 13 },
3238+
{ id: "998R", subtype: "Highlight", pageIndex: 13 },
3239+
]);
3240+
3241+
// Get all the editable annotations but the ones on page 12.
3242+
editableAnnotations = (
3243+
await pdfDoc.getAnnotationsByType(
3244+
new Set([AnnotationType.STAMP, AnnotationType.HIGHLIGHT]),
3245+
new Set([12])
3246+
)
3247+
).map(annotation => ({
3248+
id: annotation.id,
3249+
subtype: annotation.subtype,
3250+
pageIndex: annotation.pageIndex,
3251+
}));
3252+
editableAnnotations.sort((a, b) => a.id.localeCompare(b.id));
3253+
expect(editableAnnotations).toEqual([
3254+
{ id: "1011R", subtype: "Stamp", pageIndex: 13 },
3255+
{ id: "998R", subtype: "Highlight", pageIndex: 13 },
3256+
]);
3257+
await loadingTask.destroy();
3258+
});
3259+
});
32083260
});
32093261

32103262
describe("Page", function () {

0 commit comments

Comments
 (0)