Skip to content

Commit 885bca1

Browse files
committed
When saving some annotations with the same name, set the value in the parent
It fixes #18630.
1 parent 9bf9bbd commit 885bca1

File tree

3 files changed

+138
-24
lines changed

3 files changed

+138
-24
lines changed

src/core/annotation.js

+28-22
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
collectActions,
4343
escapeString,
4444
getInheritableProperty,
45+
getParentToUpdate,
4546
getRotationMatrix,
4647
isNumberArray,
4748
lookupMatrix,
@@ -2108,6 +2109,24 @@ class WidgetAnnotation extends Annotation {
21082109

21092110
amendSavedDict(annotationStorage, dict) {}
21102111

2112+
setValue(dict, value, xref, changes) {
2113+
const { dict: parentDict, ref: parentRef } = getParentToUpdate(
2114+
dict,
2115+
this.ref,
2116+
xref
2117+
);
2118+
if (!parentDict) {
2119+
dict.set("V", value);
2120+
} else if (!changes.has(parentRef)) {
2121+
const newParentDict = parentDict.clone();
2122+
newParentDict.set("V", value);
2123+
changes.put(parentRef, { data: newParentDict });
2124+
return newParentDict;
2125+
}
2126+
2127+
return null;
2128+
}
2129+
21112130
async save(evaluator, task, annotationStorage, changes) {
21122131
const storageEntry = annotationStorage?.get(this.data.id);
21132132
const flags = this._buildFlags(storageEntry?.noView, storageEntry?.noPrint);
@@ -2191,13 +2210,15 @@ class WidgetAnnotation extends Annotation {
21912210
value,
21922211
};
21932212

2194-
dict.set(
2195-
"V",
2213+
const newParentDict = this.setValue(
2214+
dict,
21962215
Array.isArray(value)
21972216
? value.map(stringToAsciiOrUTF16BE)
2198-
: stringToAsciiOrUTF16BE(value)
2217+
: stringToAsciiOrUTF16BE(value),
2218+
xref,
2219+
changes
21992220
);
2200-
this.amendSavedDict(annotationStorage, dict);
2221+
this.amendSavedDict(annotationStorage, newParentDict || dict);
22012222

22022223
const maybeMK = this._getMKDict(rotation);
22032224
if (maybeMK) {
@@ -3111,7 +3132,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
31113132
};
31123133

31133134
const name = Name.get(value ? this.data.exportValue : "Off");
3114-
dict.set("V", name);
3135+
this.setValue(dict, name, evaluator.xref, changes);
3136+
31153137
dict.set("AS", name);
31163138
dict.set("M", `D:${getModificationDate()}`);
31173139
if (flags !== undefined) {
@@ -3170,24 +3192,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
31703192
};
31713193

31723194
const name = Name.get(value ? this.data.buttonValue : "Off");
3173-
31743195
if (value) {
3175-
if (this.parent instanceof Ref) {
3176-
const parent = evaluator.xref.fetch(this.parent).clone();
3177-
parent.set("V", name);
3178-
changes.put(this.parent, {
3179-
data: parent,
3180-
xfa: null,
3181-
needAppearances: false,
3182-
});
3183-
} else if (this.parent instanceof Dict) {
3184-
this.parent.set("V", name);
3185-
}
3186-
}
3187-
3188-
if (!this.parent) {
3189-
// If there is no parent then we must set the value in the field.
3190-
dict.set("V", name);
3196+
this.setValue(dict, name, evaluator.xref, changes);
31913197
}
31923198

31933199
dict.set("AS", name);

src/core/core_utils.js

+31
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,36 @@ function getInheritableProperty({
146146
return values;
147147
}
148148

149+
/**
150+
* Get the parent dictionary to update when a property is set.
151+
*
152+
* @param {Dict} dict - Dictionary from where to start the traversal.
153+
* @param {Ref} ref - The reference to the dictionary.
154+
* @param {XRef} xref - The `XRef` instance.
155+
*/
156+
function getParentToUpdate(dict, ref, xref) {
157+
const visited = new RefSet();
158+
const firstDict = dict;
159+
const result = { dict: null, ref: null };
160+
161+
while (dict instanceof Dict && !visited.has(ref)) {
162+
visited.put(ref);
163+
if (dict.has("T")) {
164+
break;
165+
}
166+
ref = dict.getRaw("Parent");
167+
if (!(ref instanceof Ref)) {
168+
return result;
169+
}
170+
dict = xref.fetch(ref);
171+
}
172+
if (dict !== firstDict) {
173+
result.dict = dict;
174+
result.ref = ref;
175+
}
176+
return result;
177+
}
178+
149179
// prettier-ignore
150180
const ROMAN_NUMBER_MAP = [
151181
"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM",
@@ -672,6 +702,7 @@ export {
672702
getInheritableProperty,
673703
getLookupTableFactory,
674704
getNewAnnotationsMap,
705+
getParentToUpdate,
675706
getRotationMatrix,
676707
getSizeInBytes,
677708
isAscii,

test/unit/annotation_spec.js

+79-2
Original file line numberDiff line numberDiff line change
@@ -2145,6 +2145,81 @@ describe("annotation", function () {
21452145
);
21462146
});
21472147

2148+
it("should save the text in two fields with the same name", async function () {
2149+
const textWidget1Ref = Ref.get(123, 0);
2150+
const textWidget2Ref = Ref.get(124, 0);
2151+
2152+
const parentRef = Ref.get(125, 0);
2153+
textWidgetDict.set("Parent", parentRef);
2154+
const parentDict = new Dict();
2155+
parentDict.set("Kids", [textWidget1Ref, textWidget2Ref]);
2156+
parentDict.set("T", "foo");
2157+
const textWidget2Dict = textWidgetDict.clone();
2158+
2159+
const xref = new XRefMock([
2160+
{ ref: textWidget1Ref, data: textWidgetDict },
2161+
{ ref: textWidget2Ref, data: textWidget2Dict },
2162+
{ ref: parentRef, data: parentDict },
2163+
helvRefObj,
2164+
]);
2165+
partialEvaluator.xref = xref;
2166+
const task = new WorkerTask("test save");
2167+
2168+
const annotation1 = await AnnotationFactory.create(
2169+
xref,
2170+
textWidget1Ref,
2171+
annotationGlobalsMock,
2172+
idFactoryMock
2173+
);
2174+
const annotation2 = await AnnotationFactory.create(
2175+
xref,
2176+
textWidget2Ref,
2177+
annotationGlobalsMock,
2178+
idFactoryMock
2179+
);
2180+
const annotationStorage = new Map();
2181+
annotationStorage.set(annotation1.data.id, { value: "hello world" });
2182+
annotationStorage.set(annotation2.data.id, { value: "hello world" });
2183+
const changes = new RefSetCache();
2184+
2185+
await annotation1.save(
2186+
partialEvaluator,
2187+
task,
2188+
annotationStorage,
2189+
changes
2190+
);
2191+
await annotation2.save(
2192+
partialEvaluator,
2193+
task,
2194+
annotationStorage,
2195+
changes
2196+
);
2197+
const data = await writeChanges(changes, xref);
2198+
expect(data.length).toEqual(5);
2199+
const [, , data1, data2, parentData] = data;
2200+
expect(data1.ref).toEqual(textWidget1Ref);
2201+
expect(data2.ref).toEqual(textWidget2Ref);
2202+
expect(parentData.ref).toEqual(parentRef);
2203+
2204+
data1.data = data1.data.replace(/\(D:\d+\)/, "(date)");
2205+
data2.data = data2.data.replace(/\(D:\d+\)/, "(date)");
2206+
expect(data1.data).toEqual(
2207+
"123 0 obj\n" +
2208+
"<< /Type /Annot /Subtype /Widget /FT /Tx /DA (/Helv 5 Tf) /DR " +
2209+
"<< /Font << /Helv 314 0 R>>>> /Rect [0 0 32 10] " +
2210+
"/Parent 125 0 R /AP << /N 4 0 R>> /M (date)>>\nendobj\n"
2211+
);
2212+
expect(data2.data).toEqual(
2213+
"124 0 obj\n" +
2214+
"<< /Type /Annot /Subtype /Widget /FT /Tx /DA (/Helv 5 Tf) /DR " +
2215+
"<< /Font << /Helv 314 0 R>>>> /Rect [0 0 32 10] " +
2216+
"/Parent 125 0 R /AP << /N 5 0 R>> /M (date)>>\nendobj\n"
2217+
);
2218+
expect(parentData.data).toEqual(
2219+
"125 0 obj\n<< /Kids [123 0 R 124 0 R] /T (foo) /V (hello world)>>\nendobj\n"
2220+
);
2221+
});
2222+
21482223
it("should save rotated text", async function () {
21492224
const textWidgetRef = Ref.get(123, 0);
21502225
const xref = new XRefMock([
@@ -3080,6 +3155,7 @@ describe("annotation", function () {
30803155
const parentDict = new Dict();
30813156
parentDict.set("V", Name.get("Off"));
30823157
parentDict.set("Kids", [buttonWidgetRef]);
3158+
parentDict.set("T", "RadioGroup");
30833159
buttonWidgetDict.set("Parent", parentRef);
30843160

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

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

31433219
const parentDict = new Dict();
31443220
parentDict.set("Kids", [buttonWidgetRef]);
3221+
parentDict.set("T", "RadioGroup");
31453222
buttonWidgetDict.set("Parent", parentRef);
31463223

31473224
const xref = new XRefMock([
@@ -3178,7 +3255,7 @@ describe("annotation", function () {
31783255
);
31793256
expect(parentData.ref).toEqual(Ref.get(456, 0));
31803257
expect(parentData.data).toEqual(
3181-
"456 0 obj\n<< /Kids [123 0 R] /V /Checked>>\nendobj\n"
3258+
"456 0 obj\n<< /Kids [123 0 R] /T (RadioGroup) /V /Checked>>\nendobj\n"
31823259
);
31833260
});
31843261

0 commit comments

Comments
 (0)