[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 {Object} idFactory
|
||||||
* @param {boolean} [collectFields]
|
* @param {boolean} [collectFields]
|
||||||
* @param {Object} [orphanFields]
|
* @param {Object} [orphanFields]
|
||||||
|
* @param {Array<string>} [collectByType]
|
||||||
* @param {Object} [pageRef]
|
* @param {Object} [pageRef]
|
||||||
* @returns {Promise} A promise that is resolved with an {Annotation}
|
* @returns {Promise} A promise that is resolved with an {Annotation}
|
||||||
* instance.
|
* instance.
|
||||||
@ -133,6 +134,7 @@ class AnnotationFactory {
|
|||||||
idFactory,
|
idFactory,
|
||||||
collectFields,
|
collectFields,
|
||||||
orphanFields,
|
orphanFields,
|
||||||
|
collectByType,
|
||||||
pageRef
|
pageRef
|
||||||
) {
|
) {
|
||||||
const pageIndex = collectFields
|
const pageIndex = collectFields
|
||||||
@ -146,6 +148,7 @@ class AnnotationFactory {
|
|||||||
idFactory,
|
idFactory,
|
||||||
collectFields,
|
collectFields,
|
||||||
orphanFields,
|
orphanFields,
|
||||||
|
collectByType,
|
||||||
pageIndex,
|
pageIndex,
|
||||||
pageRef,
|
pageRef,
|
||||||
]);
|
]);
|
||||||
@ -161,6 +164,7 @@ class AnnotationFactory {
|
|||||||
idFactory,
|
idFactory,
|
||||||
collectFields = false,
|
collectFields = false,
|
||||||
orphanFields = null,
|
orphanFields = null,
|
||||||
|
collectByType = null,
|
||||||
pageIndex = null,
|
pageIndex = null,
|
||||||
pageRef = null
|
pageRef = null
|
||||||
) {
|
) {
|
||||||
@ -169,14 +173,21 @@ class AnnotationFactory {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { acroForm, pdfManager } = annotationGlobals;
|
|
||||||
const id =
|
|
||||||
ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`;
|
|
||||||
|
|
||||||
// Determine the annotation's subtype.
|
// Determine the annotation's subtype.
|
||||||
let subtype = dict.get("Subtype");
|
let subtype = dict.get("Subtype");
|
||||||
subtype = subtype instanceof Name ? subtype.name : null;
|
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.
|
// Return the right annotation object based on the subtype and field type.
|
||||||
const parameters = {
|
const parameters = {
|
||||||
xref,
|
xref,
|
||||||
|
|||||||
@ -802,6 +802,7 @@ class Page {
|
|||||||
this._localIdFactory,
|
this._localIdFactory,
|
||||||
/* collectFields */ false,
|
/* collectFields */ false,
|
||||||
orphanFields,
|
orphanFields,
|
||||||
|
/* collectByType */ null,
|
||||||
this.ref
|
this.ref
|
||||||
).catch(function (reason) {
|
).catch(function (reason) {
|
||||||
warn(`_parsedAnnotations: "${reason}".`);
|
warn(`_parsedAnnotations: "${reason}".`);
|
||||||
@ -849,6 +850,51 @@ class Page {
|
|||||||
);
|
);
|
||||||
return shadow(this, "jsActions", actions);
|
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]);
|
const PDF_HEADER_SIGNATURE = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]);
|
||||||
@ -1881,6 +1927,7 @@ class PDFDocument {
|
|||||||
/* idFactory = */ null,
|
/* idFactory = */ null,
|
||||||
/* collectFields */ true,
|
/* collectFields */ true,
|
||||||
orphanFields,
|
orphanFields,
|
||||||
|
/* collectByType */ null,
|
||||||
/* pageRef */ null
|
/* pageRef */ null
|
||||||
)
|
)
|
||||||
.then(annotation => annotation?.getFieldObject())
|
.then(annotation => annotation?.getFieldObject())
|
||||||
|
|||||||
@ -447,6 +447,57 @@ class WorkerMessageHandler {
|
|||||||
.then(page => pdfManager.ensure(page, "jsActions"));
|
.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) {
|
handler.on("GetOutline", function (data) {
|
||||||
return pdfManager.ensureCatalog("documentOutline");
|
return pdfManager.ensureCatalog("documentOutline");
|
||||||
});
|
});
|
||||||
|
|||||||
@ -906,6 +906,16 @@ class PDFDocumentProxy {
|
|||||||
return this._transport.getAttachments();
|
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
|
* @returns {Promise<Object | null>} A promise that is resolved with
|
||||||
* an {Object} with the JavaScript actions:
|
* an {Object} with the JavaScript actions:
|
||||||
@ -2944,6 +2954,13 @@ class WorkerTransport {
|
|||||||
return this.messageHandler.sendWithPromise("GetAttachments", null);
|
return this.messageHandler.sendWithPromise("GetAttachments", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAnnotationsByType(types, pageIndexesToSkip) {
|
||||||
|
return this.messageHandler.sendWithPromise("GetAnnotationsByType", {
|
||||||
|
types,
|
||||||
|
pageIndexesToSkip,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getDocJSActions() {
|
getDocJSActions() {
|
||||||
return this.#cacheSimpleMethod("GetDocJSActions");
|
return this.#cacheSimpleMethod("GetDocJSActions");
|
||||||
}
|
}
|
||||||
|
|||||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -740,3 +740,4 @@
|
|||||||
!dates_save.pdf
|
!dates_save.pdf
|
||||||
!print_protection.pdf
|
!print_protection.pdf
|
||||||
!tracemonkey_with_annotations.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 () {
|
describe("Page", function () {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user