[Editor] Add the ability to get all the editable annotations in a pdf document
We want to be able to show all the comments in a pdf even if the pages where they are haven't been rendered. And it'll help to fix the issue #18915.
This commit is contained in:
parent
dd560ee453
commit
9e5ee1e5a7
@ -122,6 +122,7 @@ class AnnotationFactory {
|
||||
* @param {Object} idFactory
|
||||
* @param {boolean} [collectFields]
|
||||
* @param {Object} [orphanFields]
|
||||
* @param {Array<string>} [collectByType]
|
||||
* @param {Object} [pageRef]
|
||||
* @returns {Promise} A promise that is resolved with an {Annotation}
|
||||
* instance.
|
||||
@ -133,6 +134,7 @@ class AnnotationFactory {
|
||||
idFactory,
|
||||
collectFields,
|
||||
orphanFields,
|
||||
collectByType,
|
||||
pageRef
|
||||
) {
|
||||
const pageIndex = collectFields
|
||||
@ -146,6 +148,7 @@ class AnnotationFactory {
|
||||
idFactory,
|
||||
collectFields,
|
||||
orphanFields,
|
||||
collectByType,
|
||||
pageIndex,
|
||||
pageRef,
|
||||
]);
|
||||
@ -161,6 +164,7 @@ class AnnotationFactory {
|
||||
idFactory,
|
||||
collectFields = false,
|
||||
orphanFields = null,
|
||||
collectByType = null,
|
||||
pageIndex = null,
|
||||
pageRef = null
|
||||
) {
|
||||
@ -169,14 +173,21 @@ class AnnotationFactory {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { acroForm, pdfManager } = annotationGlobals;
|
||||
const id =
|
||||
ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`;
|
||||
|
||||
// Determine the annotation's subtype.
|
||||
let subtype = dict.get("Subtype");
|
||||
subtype = subtype instanceof Name ? subtype.name : null;
|
||||
|
||||
if (
|
||||
collectByType &&
|
||||
!collectByType.has(AnnotationType[subtype.toUpperCase()])
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { acroForm, pdfManager } = annotationGlobals;
|
||||
const id =
|
||||
ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`;
|
||||
|
||||
// Return the right annotation object based on the subtype and field type.
|
||||
const parameters = {
|
||||
xref,
|
||||
|
||||
@ -802,6 +802,7 @@ class Page {
|
||||
this._localIdFactory,
|
||||
/* collectFields */ false,
|
||||
orphanFields,
|
||||
/* collectByType */ null,
|
||||
this.ref
|
||||
).catch(function (reason) {
|
||||
warn(`_parsedAnnotations: "${reason}".`);
|
||||
@ -849,6 +850,51 @@ class Page {
|
||||
);
|
||||
return shadow(this, "jsActions", actions);
|
||||
}
|
||||
|
||||
async collectAnnotationsByType(
|
||||
handler,
|
||||
task,
|
||||
types,
|
||||
promises,
|
||||
annotationGlobals
|
||||
) {
|
||||
const annots = await this.pdfManager.ensure(this, "annotations");
|
||||
const { pageIndex } = this;
|
||||
for (const annotationRef of annots) {
|
||||
promises.push(
|
||||
AnnotationFactory.create(
|
||||
this.xref,
|
||||
annotationRef,
|
||||
annotationGlobals,
|
||||
this._localIdFactory,
|
||||
/* collectFields */ false,
|
||||
/* orphanFields */ null,
|
||||
/* collectByType */ types,
|
||||
this.ref
|
||||
)
|
||||
.then(async annotation => {
|
||||
if (!annotation) {
|
||||
return null;
|
||||
}
|
||||
annotation.data.pageIndex = pageIndex;
|
||||
if (annotation.hasTextContent && annotation.viewable) {
|
||||
const partialEvaluator = this.#createPartialEvaluator(handler);
|
||||
await annotation.extractTextContent(partialEvaluator, task, [
|
||||
-Infinity,
|
||||
-Infinity,
|
||||
Infinity,
|
||||
Infinity,
|
||||
]);
|
||||
}
|
||||
return annotation.data;
|
||||
})
|
||||
.catch(function (reason) {
|
||||
warn(`collectAnnotationsByType: "${reason}".`);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PDF_HEADER_SIGNATURE = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]);
|
||||
@ -1881,6 +1927,7 @@ class PDFDocument {
|
||||
/* idFactory = */ null,
|
||||
/* collectFields */ true,
|
||||
orphanFields,
|
||||
/* collectByType */ null,
|
||||
/* pageRef */ null
|
||||
)
|
||||
.then(annotation => annotation?.getFieldObject())
|
||||
|
||||
@ -447,6 +447,57 @@ class WorkerMessageHandler {
|
||||
.then(page => pdfManager.ensure(page, "jsActions"));
|
||||
});
|
||||
|
||||
handler.on(
|
||||
"GetAnnotationsByType",
|
||||
async function ({ types, pageIndexesToSkip }) {
|
||||
const [numPages, annotationGlobals] = await Promise.all([
|
||||
pdfManager.ensureDoc("numPages"),
|
||||
pdfManager.ensureDoc("annotationGlobals"),
|
||||
]);
|
||||
|
||||
if (!annotationGlobals) {
|
||||
return null;
|
||||
}
|
||||
const pagePromises = [];
|
||||
const annotationPromises = [];
|
||||
let task = null;
|
||||
try {
|
||||
for (let i = 0, ii = numPages; i < ii; i++) {
|
||||
if (pageIndexesToSkip?.has(i)) {
|
||||
continue;
|
||||
}
|
||||
if (!task) {
|
||||
task = new WorkerTask("GetAnnotationsByType");
|
||||
startWorkerTask(task);
|
||||
}
|
||||
pagePromises.push(
|
||||
pdfManager.getPage(i).then(async page => {
|
||||
if (!page) {
|
||||
return [];
|
||||
}
|
||||
return (
|
||||
page.collectAnnotationsByType(
|
||||
handler,
|
||||
task,
|
||||
types,
|
||||
annotationPromises,
|
||||
annotationGlobals
|
||||
) || []
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
await Promise.all(pagePromises);
|
||||
const annotations = await Promise.all(annotationPromises);
|
||||
return annotations.filter(a => !!a);
|
||||
} finally {
|
||||
if (task) {
|
||||
finishWorkerTask(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
handler.on("GetOutline", function (data) {
|
||||
return pdfManager.ensureCatalog("documentOutline");
|
||||
});
|
||||
|
||||
@ -906,6 +906,16 @@ class PDFDocumentProxy {
|
||||
return this._transport.getAttachments();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Set<number>} types - The annotation types to retrieve.
|
||||
* @param {Set<number>} pageIndexesToSkip
|
||||
* @returns {Promise<Array<Object>>} A promise that is resolved with a list of
|
||||
* annotations data.
|
||||
*/
|
||||
getAnnotationsByType(types, pageIndexesToSkip) {
|
||||
return this._transport.getAnnotationsByType(types, pageIndexesToSkip);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object | null>} A promise that is resolved with
|
||||
* an {Object} with the JavaScript actions:
|
||||
@ -2944,6 +2954,13 @@ class WorkerTransport {
|
||||
return this.messageHandler.sendWithPromise("GetAttachments", null);
|
||||
}
|
||||
|
||||
getAnnotationsByType(types, pageIndexesToSkip) {
|
||||
return this.messageHandler.sendWithPromise("GetAnnotationsByType", {
|
||||
types,
|
||||
pageIndexesToSkip,
|
||||
});
|
||||
}
|
||||
|
||||
getDocJSActions() {
|
||||
return this.#cacheSimpleMethod("GetDocJSActions");
|
||||
}
|
||||
|
||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -740,3 +740,4 @@
|
||||
!dates_save.pdf
|
||||
!print_protection.pdf
|
||||
!tracemonkey_with_annotations.pdf
|
||||
!tracemonkey_with_editable_annotations.pdf
|
||||
|
||||
BIN
test/pdfs/tracemonkey_with_editable_annotations.pdf
Executable file
BIN
test/pdfs/tracemonkey_with_editable_annotations.pdf
Executable file
Binary file not shown.
@ -3205,6 +3205,58 @@ describe("api", function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Get annotations by their types in the document", function () {
|
||||
it("gets editable annotations", async function () {
|
||||
const loadingTask = getDocument(
|
||||
buildGetDocumentParams("tracemonkey_with_editable_annotations.pdf")
|
||||
);
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
|
||||
// Get all the editable annotations in the document.
|
||||
let editableAnnotations = (
|
||||
await pdfDoc.getAnnotationsByType(
|
||||
new Set([
|
||||
AnnotationType.FREETEXT,
|
||||
AnnotationType.STAMP,
|
||||
AnnotationType.INK,
|
||||
AnnotationType.HIGHLIGHT,
|
||||
]),
|
||||
null
|
||||
)
|
||||
).map(annotation => ({
|
||||
id: annotation.id,
|
||||
subtype: annotation.subtype,
|
||||
pageIndex: annotation.pageIndex,
|
||||
}));
|
||||
editableAnnotations.sort((a, b) => a.id.localeCompare(b.id));
|
||||
expect(editableAnnotations).toEqual([
|
||||
{ id: "1000R", subtype: "FreeText", pageIndex: 12 },
|
||||
{ id: "1001R", subtype: "Stamp", pageIndex: 12 },
|
||||
{ id: "1011R", subtype: "Stamp", pageIndex: 13 },
|
||||
{ id: "997R", subtype: "Ink", pageIndex: 13 },
|
||||
{ id: "998R", subtype: "Highlight", pageIndex: 13 },
|
||||
]);
|
||||
|
||||
// Get all the editable annotations but the ones on page 12.
|
||||
editableAnnotations = (
|
||||
await pdfDoc.getAnnotationsByType(
|
||||
new Set([AnnotationType.STAMP, AnnotationType.HIGHLIGHT]),
|
||||
new Set([12])
|
||||
)
|
||||
).map(annotation => ({
|
||||
id: annotation.id,
|
||||
subtype: annotation.subtype,
|
||||
pageIndex: annotation.pageIndex,
|
||||
}));
|
||||
editableAnnotations.sort((a, b) => a.id.localeCompare(b.id));
|
||||
expect(editableAnnotations).toEqual([
|
||||
{ id: "1011R", subtype: "Stamp", pageIndex: 13 },
|
||||
{ id: "998R", subtype: "Highlight", pageIndex: 13 },
|
||||
]);
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Page", function () {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user