Skip to content

Commit

Permalink
Merge pull request mozilla#19054 from calixteman/issue18630
Browse files Browse the repository at this point in the history
When saving some annotations with the same name, set the value in the parent
  • Loading branch information
calixteman authored Nov 17, 2024
2 parents e2bbcb5 + 2da5865 commit 8a8b69f
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 24 deletions.
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

0 comments on commit 8a8b69f

Please sign in to comment.