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

When saving some annotations with the same name, set the value in the parent #19054

Merged
merged 1 commit into from
Nov 17, 2024
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
50 changes: 28 additions & 22 deletions src/core/annotation.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
collectActions,
escapeString,
getInheritableProperty,
getParentToUpdate,
getRotationMatrix,
isNumberArray,
lookupMatrix,
Expand Down Expand Up @@ -2108,6 +2109,24 @@ class WidgetAnnotation extends Annotation {

amendSavedDict(annotationStorage, dict) {}

setValue(dict, value, xref, changes) {
const { dict: parentDict, ref: parentRef } = getParentToUpdate(
dict,
this.ref,
xref
);
if (!parentDict) {
dict.set("V", value);
} else if (!changes.has(parentRef)) {
const newParentDict = parentDict.clone();
newParentDict.set("V", value);
changes.put(parentRef, { data: newParentDict });
return newParentDict;
}

return null;
}

async save(evaluator, task, annotationStorage, changes) {
const storageEntry = annotationStorage?.get(this.data.id);
const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint);
Expand Down Expand Up @@ -2191,13 +2210,15 @@ class WidgetAnnotation extends Annotation {
value,
};

dict.set(
"V",
const newParentDict = this.setValue(
dict,
Array.isArray(value)
? value.map(stringToAsciiOrUTF16BE)
: stringToAsciiOrUTF16BE(value)
: stringToAsciiOrUTF16BE(value),
xref,
changes
);
this.amendSavedDict(annotationStorage, dict);
this.amendSavedDict(annotationStorage, newParentDict || dict);

const maybeMK = this._getMKDict(rotation);
if (maybeMK) {
Expand Down Expand Up @@ -3111,7 +3132,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
};

const name = Name.get(value ? this.data.exportValue : "Off");
dict.set("V", name);
this.setValue(dict, name, evaluator.xref, changes);

dict.set("AS", name);
dict.set("M", `D:${getModificationDate()}`);
if (flags !== undefined) {
Expand Down Expand Up @@ -3170,24 +3192,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
};

const name = Name.get(value ? this.data.buttonValue : "Off");

if (value) {
if (this.parent instanceof Ref) {
const parent = evaluator.xref.fetch(this.parent).clone();
parent.set("V", name);
changes.put(this.parent, {
data: parent,
xfa: null,
needAppearances: false,
});
} else if (this.parent instanceof Dict) {
this.parent.set("V", name);
}
}

if (!this.parent) {
// If there is no parent then we must set the value in the field.
dict.set("V", name);
this.setValue(dict, name, evaluator.xref, changes);
}

dict.set("AS", name);
Expand Down
31 changes: 31 additions & 0 deletions src/core/core_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,36 @@ function getInheritableProperty({
return values;
}

/**
* Get the parent dictionary to update when a property is set.
*
* @param {Dict} dict - Dictionary from where to start the traversal.
* @param {Ref} ref - The reference to the dictionary.
* @param {XRef} xref - The `XRef` instance.
*/
function getParentToUpdate(dict, ref, xref) {
const visited = new RefSet();
const firstDict = dict;
const result = { dict: null, ref: null };

while (dict instanceof Dict && !visited.has(ref)) {
visited.put(ref);
if (dict.has("T")) {
break;
}
ref = dict.getRaw("Parent");
if (!(ref instanceof Ref)) {
return result;
}
dict = xref.fetch(ref);
}
if (dict instanceof Dict && dict !== firstDict) {
result.dict = dict;
result.ref = ref;
}
return result;
}

// prettier-ignore
const ROMAN_NUMBER_MAP = [
"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM",
Expand Down Expand Up @@ -672,6 +702,7 @@ export {
getInheritableProperty,
getLookupTableFactory,
getNewAnnotationsMap,
getParentToUpdate,
getRotationMatrix,
getSizeInBytes,
isAscii,
Expand Down
81 changes: 79 additions & 2 deletions test/unit/annotation_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2145,6 +2145,81 @@ describe("annotation", function () {
);
});

it("should save the text in two fields with the same name", async function () {
const textWidget1Ref = Ref.get(123, 0);
const textWidget2Ref = Ref.get(124, 0);

const parentRef = Ref.get(125, 0);
textWidgetDict.set("Parent", parentRef);
const parentDict = new Dict();
parentDict.set("Kids", [textWidget1Ref, textWidget2Ref]);
parentDict.set("T", "foo");
const textWidget2Dict = textWidgetDict.clone();

const xref = new XRefMock([
{ ref: textWidget1Ref, data: textWidgetDict },
{ ref: textWidget2Ref, data: textWidget2Dict },
{ ref: parentRef, data: parentDict },
helvRefObj,
]);
partialEvaluator.xref = xref;
const task = new WorkerTask("test save");

const annotation1 = await AnnotationFactory.create(
xref,
textWidget1Ref,
annotationGlobalsMock,
idFactoryMock
);
const annotation2 = await AnnotationFactory.create(
xref,
textWidget2Ref,
annotationGlobalsMock,
idFactoryMock
);
const annotationStorage = new Map();
annotationStorage.set(annotation1.data.id, { value: "hello world" });
annotationStorage.set(annotation2.data.id, { value: "hello world" });
const changes = new RefSetCache();

await annotation1.save(
partialEvaluator,
task,
annotationStorage,
changes
);
await annotation2.save(
partialEvaluator,
task,
annotationStorage,
changes
);
const data = await writeChanges(changes, xref);
expect(data.length).toEqual(5);
const [, , data1, data2, parentData] = data;
expect(data1.ref).toEqual(textWidget1Ref);
expect(data2.ref).toEqual(textWidget2Ref);
expect(parentData.ref).toEqual(parentRef);

data1.data = data1.data.replace(/\(D:\d+\)/, "(date)");
data2.data = data2.data.replace(/\(D:\d+\)/, "(date)");
expect(data1.data).toEqual(
"123 0 obj\n" +
"<< /Type /Annot /Subtype /Widget /FT /Tx /DA (/Helv 5 Tf) /DR " +
"<< /Font << /Helv 314 0 R>>>> /Rect [0 0 32 10] " +
"/Parent 125 0 R /AP << /N 4 0 R>> /M (date)>>\nendobj\n"
);
expect(data2.data).toEqual(
"124 0 obj\n" +
"<< /Type /Annot /Subtype /Widget /FT /Tx /DA (/Helv 5 Tf) /DR " +
"<< /Font << /Helv 314 0 R>>>> /Rect [0 0 32 10] " +
"/Parent 125 0 R /AP << /N 5 0 R>> /M (date)>>\nendobj\n"
);
expect(parentData.data).toEqual(
"125 0 obj\n<< /Kids [123 0 R 124 0 R] /T (foo) /V (hello world)>>\nendobj\n"
);
});

it("should save rotated text", async function () {
const textWidgetRef = Ref.get(123, 0);
const xref = new XRefMock([
Expand Down Expand Up @@ -3080,6 +3155,7 @@ describe("annotation", function () {
const parentDict = new Dict();
parentDict.set("V", Name.get("Off"));
parentDict.set("Kids", [buttonWidgetRef]);
parentDict.set("T", "RadioGroup");
buttonWidgetDict.set("Parent", parentRef);

const xref = new XRefMock([
Expand Down Expand Up @@ -3116,7 +3192,7 @@ describe("annotation", function () {
);
expect(parentData.ref).toEqual(Ref.get(456, 0));
expect(parentData.data).toEqual(
"456 0 obj\n<< /V /Checked /Kids [123 0 R]>>\nendobj\n"
"456 0 obj\n<< /V /Checked /Kids [123 0 R] /T (RadioGroup)>>\nendobj\n"
);

annotationStorage.set(annotation.data.id, { value: false });
Expand All @@ -3142,6 +3218,7 @@ describe("annotation", function () {

const parentDict = new Dict();
parentDict.set("Kids", [buttonWidgetRef]);
parentDict.set("T", "RadioGroup");
buttonWidgetDict.set("Parent", parentRef);

const xref = new XRefMock([
Expand Down Expand Up @@ -3178,7 +3255,7 @@ describe("annotation", function () {
);
expect(parentData.ref).toEqual(Ref.get(456, 0));
expect(parentData.data).toEqual(
"456 0 obj\n<< /Kids [123 0 R] /V /Checked>>\nendobj\n"
"456 0 obj\n<< /Kids [123 0 R] /T (RadioGroup) /V /Checked>>\nendobj\n"
);
});

Expand Down