Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ class AnnotationFactory {
const promises = [];

for (const annotation of annotations) {
if (annotation.deleted) {
continue;
}
switch (annotation.annotationType) {
case AnnotationEditorType.FREETEXT:
if (!baseFontRef) {
Expand Down Expand Up @@ -308,6 +311,9 @@ class AnnotationFactory {
const { isOffscreenCanvasSupported } = evaluator.options;
const promises = [];
for (const annotation of annotations) {
if (annotation.deleted) {
continue;
}
switch (annotation.annotationType) {
case AnnotationEditorType.FREETEXT:
promises.push(
Expand Down Expand Up @@ -466,6 +472,7 @@ class Annotation {
const MK = dict.get("MK");
this.setBorderAndBackgroundColors(MK);
this.setRotation(MK);
this.ref = params.ref instanceof Ref ? params.ref : null;

this._streams = [];
if (this.appearance) {
Expand Down Expand Up @@ -1467,7 +1474,7 @@ class MarkupAnnotation extends Annotation {
}

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 buffer = [];
let annotationDict;
Expand Down Expand Up @@ -1497,11 +1504,17 @@ class MarkupAnnotation extends Annotation {
const ap = await this.createNewAppearanceStream(annotation, xref, params);
const annotationDict = this.createNewDict(annotation, xref, { ap });

return new this.prototype.constructor({
const newAnnotation = new this.prototype.constructor({
dict: annotationDict,
xref,
isOffscreenCanvasSupported: params.isOffscreenCanvasSupported,
});

if (annotation.ref) {
newAnnotation.ref = newAnnotation.refToReplace = annotation.ref;
}

return newAnnotation;
}
}

Expand All @@ -1511,7 +1524,6 @@ class WidgetAnnotation extends Annotation {

const { dict, xref } = params;
const data = this.data;
this.ref = params.ref;
this._needAppearances = params.needAppearances;

data.annotationType = AnnotationType.WIDGET;
Expand Down
49 changes: 47 additions & 2 deletions src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import {
XRefEntryException,
XRefParseException,
} 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 { BaseStream } from "./base_stream.js";
import { calculateMD5 } from "./crypto.js";
Expand Down Expand Up @@ -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) {
if (this.xfaFactory) {
throw new Error("XFA: Cannot save new annotations.");
Expand All @@ -276,8 +294,13 @@ class Page {
options: this.evaluatorOptions,
});

const deletedAnnotations = new RefSet();
this.#replaceIdByRef(annotations, deletedAnnotations);

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(
partialEvaluator,
task,
Expand Down Expand Up @@ -401,11 +424,14 @@ class Page {
const newAnnotationsByPage = !this.xfaFactory
? getNewAnnotationsMap(annotationStorage)
: null;
let deletedAnnotations = null;

let newAnnotationsPromise = Promise.resolve(null);
if (newAnnotationsByPage) {
const newAnnotations = newAnnotationsByPage.get(this.pageIndex);
if (newAnnotations) {
deletedAnnotations = new RefSet();
this.#replaceIdByRef(newAnnotations, deletedAnnotations);
newAnnotationsPromise = AnnotationFactory.printNewAnnotations(
partialEvaluator,
task,
Expand Down Expand Up @@ -446,6 +472,25 @@ class Page {
newAnnotationsPromise,
]).then(function ([pageOpList, annotations, 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);
Snuffleupagus marked this conversation as resolved.
Show resolved Hide resolved
newAnnotations.splice(i--, 1);
ii--;
}
}
}
annotations = annotations.concat(newAnnotations);
}
if (
Expand Down
17 changes: 17 additions & 0 deletions src/core/primitives.js
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,23 @@ class Ref {
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) {
const key = gen === 0 ? `${num}R` : `${num}R${gen}`;
// eslint-disable-next-line no-restricted-syntax
Expand Down
1 change: 1 addition & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -599,3 +599,4 @@
!bug1529502.pdf
!issue16500.pdf
!issue16538.pdf
!freetexts.pdf
Binary file added test/pdfs/freetexts.pdf
Binary file not shown.
88 changes: 87 additions & 1 deletion test/test_manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7720,5 +7720,91 @@
"md5": "35b691c3a343f4531bd287b001b67a77",
"rounds": 1,
"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"
}
}
}
]