Skip to content

Commit c992b8e

Browse files
committed
Ensure that all necessary /Font resources are included when saving a WidgetAnnotation-instance (issue 12294)
This patch contains a possible approach for fixing issue 12294, which compared to other PRs is purposely limited to the affected `WidgetAnnotation` code. As mentioned elsewhere, considering that we're (at least for now) trying to fix *one specific* case, I think that we should avoid modifying the `Dict` primitive[1] and/or avoid a solution that (indirectly) modifies an existing `Dict`-instance[2]. This patch simply fixes the issue at hand, since that seems easiest for now, and I'd suggest that we worry about a more general approach if/when that actually becomes necessary. Hence the solution implemented here, for `WidgetAnnotation`, is to simply use a combination of the local *and* AcroForm /DR resources during OperatorList-parsing to ensure that things work correctly regardless of where a particular /Font resource is found. For saving of form-data, on the other hand, we want to avoid increasing the file-size unnecessarily and need to be smarter than just merging all of the available resources. To achive this, a new `WidgetAnnotation._getSaveFieldResources` method will when necessary produce a combined resources `Dict` with only the minimum amount of data from the AcroForm /DR resources included. --- [1] You want to avoid anything that could cause the general `Dict` implementation to become slower, or more complex, just for handling an edge-case in my opinion. [2] If an existing `Dict`-instance is modified unexpectedly, that could very easily lead to problems elsewhere since e.g. `Dict`-instances created during parsing are not expected to be changed.
1 parent 741ce4f commit c992b8e

File tree

3 files changed

+81
-15
lines changed

3 files changed

+81
-15
lines changed

src/core/annotation.js

+68-15
Original file line numberDiff line numberDiff line change
@@ -885,10 +885,18 @@ class WidgetAnnotation extends Annotation {
885885
"";
886886
const fieldType = getInheritableProperty({ dict, key: "FT" });
887887
data.fieldType = isName(fieldType) ? fieldType.name : null;
888-
this.fieldResources =
889-
getInheritableProperty({ dict, key: "DR" }) ||
890-
params.acroForm.get("DR") ||
891-
Dict.empty;
888+
889+
const localResources = getInheritableProperty({ dict, key: "DR" });
890+
const acroFormResources = params.acroForm.get("DR");
891+
this._fieldResources = {
892+
localResources,
893+
acroFormResources,
894+
mergedResources: Dict.merge({
895+
xref: params.xref,
896+
dictArray: [localResources, acroFormResources],
897+
mergeSubDicts: true,
898+
}),
899+
};
892900

893901
data.fieldFlags = getInheritableProperty({ dict, key: "Ff" });
894902
if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
@@ -1043,7 +1051,7 @@ class WidgetAnnotation extends Annotation {
10431051
.getOperatorList({
10441052
stream,
10451053
task,
1046-
resources: this.fieldResources,
1054+
resources: this._fieldResources.mergedResources,
10471055
operatorList,
10481056
})
10491057
.then(function () {
@@ -1067,8 +1075,9 @@ class WidgetAnnotation extends Annotation {
10671075
if (appearance === null) {
10681076
return null;
10691077
}
1078+
const { xref } = evaluator;
10701079

1071-
const dict = evaluator.xref.fetchIfRef(this.ref);
1080+
const dict = xref.fetchIfRef(this.ref);
10721081
if (!isDict(dict)) {
10731082
return null;
10741083
}
@@ -1086,11 +1095,11 @@ class WidgetAnnotation extends Annotation {
10861095
value,
10871096
};
10881097

1089-
const newRef = evaluator.xref.getNewRef();
1090-
const AP = new Dict(evaluator.xref);
1098+
const newRef = xref.getNewRef();
1099+
const AP = new Dict(xref);
10911100
AP.set("N", newRef);
10921101

1093-
const encrypt = evaluator.xref.encrypt;
1102+
const encrypt = xref.encrypt;
10941103
let originalTransform = null;
10951104
let newTransform = null;
10961105
if (encrypt) {
@@ -1106,10 +1115,10 @@ class WidgetAnnotation extends Annotation {
11061115
dict.set("AP", AP);
11071116
dict.set("M", `D:${getModificationDate()}`);
11081117

1109-
const appearanceDict = new Dict(evaluator.xref);
1118+
const appearanceDict = new Dict(xref);
11101119
appearanceDict.set("Length", appearance.length);
11111120
appearanceDict.set("Subtype", Name.get("Form"));
1112-
appearanceDict.set("Resources", this.fieldResources);
1121+
appearanceDict.set("Resources", this._getSaveFieldResources(xref));
11131122
appearanceDict.set("BBox", bbox);
11141123

11151124
const bufferOriginal = [`${this.ref.num} ${this.ref.gen} obj\n`];
@@ -1132,6 +1141,8 @@ class WidgetAnnotation extends Annotation {
11321141
}
11331142

11341143
async _getAppearance(evaluator, task, annotationStorage) {
1144+
this._fontName = null;
1145+
11351146
const isPassword = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD);
11361147
if (!annotationStorage || isPassword) {
11371148
return null;
@@ -1148,9 +1159,8 @@ class WidgetAnnotation extends Annotation {
11481159

11491160
const fontInfo = await this._getFontData(evaluator, task);
11501161
const [font, fontName] = fontInfo;
1151-
let fontSize = fontInfo[2];
1152-
1153-
fontSize = this._computeFontSize(font, fontName, fontSize, totalHeight);
1162+
const fontSize = this._computeFontSize(...fontInfo, totalHeight);
1163+
this._fontName = fontName;
11541164

11551165
let descent = font.descent;
11561166
if (isNaN(descent)) {
@@ -1226,7 +1236,7 @@ class WidgetAnnotation extends Annotation {
12261236
await evaluator.getOperatorList({
12271237
stream: new StringStream(this.data.defaultAppearance),
12281238
task,
1229-
resources: this.fieldResources,
1239+
resources: this._fieldResources.mergedResources,
12301240
operatorList,
12311241
initialState,
12321242
});
@@ -1280,6 +1290,49 @@ class WidgetAnnotation extends Annotation {
12801290

12811291
return `${shift} ${vPadding} Td (${escapeString(text)}) Tj`;
12821292
}
1293+
1294+
/**
1295+
* @private
1296+
*/
1297+
_getSaveFieldResources(xref) {
1298+
if (
1299+
typeof PDFJSDev === "undefined" ||
1300+
PDFJSDev.test("!PRODUCTION || TESTING")
1301+
) {
1302+
assert(
1303+
this._fontName !== undefined,
1304+
"Expected `_getAppearance()` to have been called."
1305+
);
1306+
}
1307+
const { localResources, acroFormResources } = this._fieldResources;
1308+
1309+
if (!this._fontName) {
1310+
return localResources || Dict.empty;
1311+
}
1312+
if (localResources instanceof Dict) {
1313+
const localFont = localResources.get("Font");
1314+
if (localFont instanceof Dict && localFont.has(this._fontName)) {
1315+
return localResources;
1316+
}
1317+
}
1318+
if (acroFormResources instanceof Dict) {
1319+
const acroFormFont = acroFormResources.get("Font");
1320+
if (acroFormFont instanceof Dict && acroFormFont.has(this._fontName)) {
1321+
const subFontDict = new Dict(xref);
1322+
subFontDict.set(this._fontName, acroFormFont.getRaw(this._fontName));
1323+
1324+
const subResourcesDict = new Dict(xref);
1325+
subResourcesDict.set("Font", subFontDict);
1326+
1327+
return Dict.merge({
1328+
xref,
1329+
dictArray: [subResourcesDict, localResources],
1330+
mergeSubDicts: true,
1331+
});
1332+
}
1333+
}
1334+
return localResources || Dict.empty;
1335+
}
12831336
}
12841337

12851338
class TextWidgetAnnotation extends WidgetAnnotation {

test/pdfs/issue12294.pdf.link

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
https://web.archive.org/web/20200914130729/https://www.nta.go.jp/taxes/tetsuzuki/shinsei/annai/joyaku/annai/pdf2/250.pdf

test/test_manifest.json

+12
Original file line numberDiff line numberDiff line change
@@ -2780,6 +2780,18 @@
27802780
"rounds": 1,
27812781
"type": "eq"
27822782
},
2783+
{ "id": "issue12294-print",
2784+
"file": "pdfs/issue12294.pdf",
2785+
"md5": "a0ac5e03be38b5fb7a7a615e30024b28",
2786+
"rounds": 1,
2787+
"lastPage": 1,
2788+
"link": true,
2789+
"type": "eq",
2790+
"print": true,
2791+
"annotationStorage": {
2792+
"2795R": "氏 名 又 は 名 称 Full name"
2793+
}
2794+
},
27832795
{ "id": "issue7598",
27842796
"file": "pdfs/issue7598.pdf",
27852797
"md5": "c5bc5a779bfcb4b234f853231b56cf60",

0 commit comments

Comments
 (0)