Skip to content

Commit 5423ab7

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

File tree

3 files changed

+139
-24
lines changed

3 files changed

+139
-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

+80-2
Original file line numberDiff line numberDiff line change
@@ -2145,6 +2145,82 @@ 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+
console.log(data);
2199+
expect(data.length).toEqual(5);
2200+
const [, , data1, data2, parentData] = data;
2201+
expect(data1.ref).toEqual(textWidget1Ref);
2202+
expect(data2.ref).toEqual(textWidget2Ref);
2203+
expect(parentData.ref).toEqual(parentRef);
2204+
2205+
data1.data = data1.data.replace(/\(D:\d+\)/, "(date)");
2206+
data2.data = data2.data.replace(/\(D:\d+\)/, "(date)");
2207+
expect(data1.data).toEqual(
2208+
"123 0 obj\n" +
2209+
"<< /Type /Annot /Subtype /Widget /FT /Tx /DA (/Helv 5 Tf) /DR " +
2210+
"<< /Font << /Helv 314 0 R>>>> /Rect [0 0 32 10] " +
2211+
"/Parent 125 0 R /AP << /N 4 0 R>> /M (date)>>\nendobj\n"
2212+
);
2213+
expect(data2.data).toEqual(
2214+
"124 0 obj\n" +
2215+
"<< /Type /Annot /Subtype /Widget /FT /Tx /DA (/Helv 5 Tf) /DR " +
2216+
"<< /Font << /Helv 314 0 R>>>> /Rect [0 0 32 10] " +
2217+
"/Parent 125 0 R /AP << /N 5 0 R>> /M (date)>>\nendobj\n"
2218+
);
2219+
expect(parentData.data).toEqual(
2220+
"125 0 obj\n<< /Kids [123 0 R 124 0 R] /T (foo) /V (hello world)>>\nendobj\n"
2221+
);
2222+
});
2223+
21482224
it("should save rotated text", async function () {
21492225
const textWidgetRef = Ref.get(123, 0);
21502226
const xref = new XRefMock([
@@ -3080,6 +3156,7 @@ describe("annotation", function () {
30803156
const parentDict = new Dict();
30813157
parentDict.set("V", Name.get("Off"));
30823158
parentDict.set("Kids", [buttonWidgetRef]);
3159+
parentDict.set("T", "RadioGroup");
30833160
buttonWidgetDict.set("Parent", parentRef);
30843161

30853162
const xref = new XRefMock([
@@ -3116,7 +3193,7 @@ describe("annotation", function () {
31163193
);
31173194
expect(parentData.ref).toEqual(Ref.get(456, 0));
31183195
expect(parentData.data).toEqual(
3119-
"456 0 obj\n<< /V /Checked /Kids [123 0 R]>>\nendobj\n"
3196+
"456 0 obj\n<< /V /Checked /Kids [123 0 R] /T (RadioGroup)>>\nendobj\n"
31203197
);
31213198

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

31433220
const parentDict = new Dict();
31443221
parentDict.set("Kids", [buttonWidgetRef]);
3222+
parentDict.set("T", "RadioGroup");
31453223
buttonWidgetDict.set("Parent", parentRef);
31463224

31473225
const xref = new XRefMock([
@@ -3178,7 +3256,7 @@ describe("annotation", function () {
31783256
);
31793257
expect(parentData.ref).toEqual(Ref.get(456, 0));
31803258
expect(parentData.data).toEqual(
3181-
"456 0 obj\n<< /Kids [123 0 R] /V /Checked>>\nendobj\n"
3259+
"456 0 obj\n<< /Kids [123 0 R] /T (RadioGroup) /V /Checked>>\nendobj\n"
31823260
);
31833261
});
31843262

0 commit comments

Comments
 (0)