[Editor] Add the possibility to update an existing annotation with some new properties when saving or printing

This commit is contained in:
Calixte Denizet 2023-06-05 15:07:28 +02:00
parent f8a84a6f03
commit 1a047f843c
6 changed files with 167 additions and 6 deletions

View File

@ -264,6 +264,9 @@ class AnnotationFactory {
const promises = []; const promises = [];
for (const annotation of annotations) { for (const annotation of annotations) {
if (annotation.deleted) {
continue;
}
switch (annotation.annotationType) { switch (annotation.annotationType) {
case AnnotationEditorType.FREETEXT: case AnnotationEditorType.FREETEXT:
if (!baseFontRef) { if (!baseFontRef) {
@ -308,6 +311,9 @@ class AnnotationFactory {
const { isOffscreenCanvasSupported } = evaluator.options; const { isOffscreenCanvasSupported } = evaluator.options;
const promises = []; const promises = [];
for (const annotation of annotations) { for (const annotation of annotations) {
if (annotation.deleted) {
continue;
}
switch (annotation.annotationType) { switch (annotation.annotationType) {
case AnnotationEditorType.FREETEXT: case AnnotationEditorType.FREETEXT:
promises.push( promises.push(
@ -466,6 +472,7 @@ class Annotation {
const MK = dict.get("MK"); const MK = dict.get("MK");
this.setBorderAndBackgroundColors(MK); this.setBorderAndBackgroundColors(MK);
this.setRotation(MK); this.setRotation(MK);
this.ref = params.ref instanceof Ref ? params.ref : null;
this._streams = []; this._streams = [];
if (this.appearance) { if (this.appearance) {
@ -1467,7 +1474,7 @@ class MarkupAnnotation extends Annotation {
} }
static async createNewAnnotation(xref, annotation, dependencies, params) { static async createNewAnnotation(xref, annotation, dependencies, params) {
const annotationRef = xref.getNewTemporaryRef(); const annotationRef = annotation.ref || xref.getNewTemporaryRef();
const ap = await this.createNewAppearanceStream(annotation, xref, params); const ap = await this.createNewAppearanceStream(annotation, xref, params);
const buffer = []; const buffer = [];
let annotationDict; let annotationDict;
@ -1497,11 +1504,17 @@ class MarkupAnnotation extends Annotation {
const ap = await this.createNewAppearanceStream(annotation, xref, params); const ap = await this.createNewAppearanceStream(annotation, xref, params);
const annotationDict = this.createNewDict(annotation, xref, { ap }); const annotationDict = this.createNewDict(annotation, xref, { ap });
return new this.prototype.constructor({ const newAnnotation = new this.prototype.constructor({
dict: annotationDict, dict: annotationDict,
xref, xref,
isOffscreenCanvasSupported: params.isOffscreenCanvasSupported, isOffscreenCanvasSupported: params.isOffscreenCanvasSupported,
}); });
if (annotation.ref) {
newAnnotation.ref = newAnnotation.refToReplace = annotation.ref;
}
return newAnnotation;
} }
} }
@ -1511,7 +1524,6 @@ class WidgetAnnotation extends Annotation {
const { dict, xref } = params; const { dict, xref } = params;
const data = this.data; const data = this.data;
this.ref = params.ref;
this._needAppearances = params.needAppearances; this._needAppearances = params.needAppearances;
data.annotationType = AnnotationType.WIDGET; data.annotationType = AnnotationType.WIDGET;

View File

@ -41,7 +41,7 @@ import {
XRefEntryException, XRefEntryException,
XRefParseException, XRefParseException,
} from "./core_utils.js"; } from "./core_utils.js";
import { Dict, isName, Name, Ref } from "./primitives.js"; import { Dict, isName, isRefsEqual, Name, Ref, RefSet } from "./primitives.js";
import { getXfaFontDict, getXfaFontName } from "./xfa_fonts.js"; import { getXfaFontDict, getXfaFontName } from "./xfa_fonts.js";
import { BaseStream } from "./base_stream.js"; import { BaseStream } from "./base_stream.js";
import { calculateMD5 } from "./crypto.js"; import { calculateMD5 } from "./crypto.js";
@ -258,6 +258,24 @@ class Page {
); );
} }
#replaceIdByRef(annotations, deletedAnnotations) {
for (const annotation of annotations) {
if (annotation.id) {
const ref = Ref.fromString(annotation.id);
if (!ref) {
warn(`A non-linked annotation cannot be modified: ${annotation.id}`);
continue;
}
if (annotation.deleted) {
deletedAnnotations.put(ref);
continue;
}
annotation.ref = ref;
delete annotation.id;
}
}
}
async saveNewAnnotations(handler, task, annotations) { async saveNewAnnotations(handler, task, annotations) {
if (this.xfaFactory) { if (this.xfaFactory) {
throw new Error("XFA: Cannot save new annotations."); throw new Error("XFA: Cannot save new annotations.");
@ -276,8 +294,13 @@ class Page {
options: this.evaluatorOptions, options: this.evaluatorOptions,
}); });
const deletedAnnotations = new RefSet();
this.#replaceIdByRef(annotations, deletedAnnotations);
const pageDict = this.pageDict; const pageDict = this.pageDict;
const annotationsArray = this.annotations.slice(); const annotationsArray = this.annotations.filter(
a => !(a instanceof Ref && deletedAnnotations.has(a))
);
const newData = await AnnotationFactory.saveNewAnnotations( const newData = await AnnotationFactory.saveNewAnnotations(
partialEvaluator, partialEvaluator,
task, task,
@ -401,11 +424,14 @@ class Page {
const newAnnotationsByPage = !this.xfaFactory const newAnnotationsByPage = !this.xfaFactory
? getNewAnnotationsMap(annotationStorage) ? getNewAnnotationsMap(annotationStorage)
: null; : null;
let deletedAnnotations = null;
let newAnnotationsPromise = Promise.resolve(null); let newAnnotationsPromise = Promise.resolve(null);
if (newAnnotationsByPage) { if (newAnnotationsByPage) {
const newAnnotations = newAnnotationsByPage.get(this.pageIndex); const newAnnotations = newAnnotationsByPage.get(this.pageIndex);
if (newAnnotations) { if (newAnnotations) {
deletedAnnotations = new RefSet();
this.#replaceIdByRef(newAnnotations, deletedAnnotations);
newAnnotationsPromise = AnnotationFactory.printNewAnnotations( newAnnotationsPromise = AnnotationFactory.printNewAnnotations(
partialEvaluator, partialEvaluator,
task, task,
@ -446,6 +472,25 @@ class Page {
newAnnotationsPromise, newAnnotationsPromise,
]).then(function ([pageOpList, annotations, newAnnotations]) { ]).then(function ([pageOpList, annotations, newAnnotations]) {
if (newAnnotations) { if (newAnnotations) {
// Some annotations can already exist (if it has the refToReplace
// property). In this case, we replace the old annotation by the new
// one.
annotations = annotations.filter(
a => !(a.ref && deletedAnnotations.has(a.ref))
);
for (let i = 0, ii = newAnnotations.length; i < ii; i++) {
const newAnnotation = newAnnotations[i];
if (newAnnotation.refToReplace) {
const j = annotations.findIndex(
a => a.ref && isRefsEqual(a.ref, newAnnotation.refToReplace)
);
if (j >= 0) {
annotations.splice(j, 1, newAnnotation);
newAnnotations.splice(i--, 1);
ii--;
}
}
}
annotations = annotations.concat(newAnnotations); annotations = annotations.concat(newAnnotations);
} }
if ( if (

View File

@ -279,6 +279,23 @@ class Ref {
return `${this.num}R${this.gen}`; return `${this.num}R${this.gen}`;
} }
static fromString(str) {
const ref = RefCache[str];
if (ref) {
return ref;
}
const m = /^(\d+)R(\d*)$/.exec(str);
if (!m || m[1] === "0") {
return null;
}
// eslint-disable-next-line no-restricted-syntax
return (RefCache[str] = new Ref(
parseInt(m[1]),
!m[2] ? 0 : parseInt(m[2])
));
}
static get(num, gen) { static get(num, gen) {
const key = gen === 0 ? `${num}R` : `${num}R${gen}`; const key = gen === 0 ? `${num}R` : `${num}R${gen}`;
// eslint-disable-next-line no-restricted-syntax // eslint-disable-next-line no-restricted-syntax

View File

@ -599,3 +599,4 @@
!bug1529502.pdf !bug1529502.pdf
!issue16500.pdf !issue16500.pdf
!issue16538.pdf !issue16538.pdf
!freetexts.pdf

BIN
test/pdfs/freetexts.pdf Executable file

Binary file not shown.

View File

@ -7720,5 +7720,91 @@
"md5": "35b691c3a343f4531bd287b001b67a77", "md5": "35b691c3a343f4531bd287b001b67a77",
"rounds": 1, "rounds": 1,
"type": "eq" "type": "eq"
} },
{
"id": "freetexts-editor-print",
"file": "pdfs/freetexts.pdf",
"md5": "da1310a25ab796c1201810070d5032a3",
"rounds": 1,
"lastPage": 1,
"type": "eq",
"print": true,
"annotationStorage": {
"pdfjs_internal_editor_0": {
"annotationType": 3,
"color": [0, 0, 255],
"fontSize": 21,
"value": "The content must have been changed",
"pageIndex": 0,
"rect": [ 92.58600000000003, 415.2426115258789, 449.1110015258789, 447.59261],
"rotation": 0,
"id": "36R"
}
}
},
{
"id": "freetexts-editor-save",
"file": "pdfs/freetexts.pdf",
"md5": "da1310a25ab796c1201810070d5032a3",
"rounds": 1,
"lastPage": 1,
"type": "eq",
"save": true,
"print": true,
"annotationStorage": {
"pdfjs_internal_editor_0": {
"annotationType": 3,
"color": [255, 0, 0],
"fontSize": 21,
"value": "The content must have been changed",
"pageIndex": 0,
"rect": [ 92.58600000000003, 415.2426115258789, 449.1110015258789, 447.59261],
"rotation": 0,
"id": "36R"
}
}
},
{
"id": "freetexts-delete-editor-print",
"file": "pdfs/freetexts.pdf",
"md5": "da1310a25ab796c1201810070d5032a3",
"rounds": 1,
"lastPage": 1,
"type": "eq",
"print": true,
"annotationStorage": {
"pdfjs_internal_editor_0": {
"pageIndex": 0,
"deleted": true,
"id": "36R"
},
"pdfjs_internal_editor_1": {
"pageIndex": 0,
"deleted": true,
"id": "53R"
}
}
},
{
"id": "freetexts-delete-editor-save",
"file": "pdfs/freetexts.pdf",
"md5": "da1310a25ab796c1201810070d5032a3",
"rounds": 1,
"lastPage": 1,
"type": "eq",
"save": true,
"print": true,
"annotationStorage": {
"pdfjs_internal_editor_0": {
"pageIndex": 0,
"deleted": true,
"id": "36R"
},
"pdfjs_internal_editor_1": {
"pageIndex": 0,
"deleted": true,
"id": "53R"
}
}
}
] ]