From c992b8e460d16a10b813e15d0dfa46aeaf5a701d Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 10 Sep 2020 10:21:34 +0200 Subject: [PATCH] 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. --- src/core/annotation.js | 83 ++++++++++++++++++++++++++++------- test/pdfs/issue12294.pdf.link | 1 + test/test_manifest.json | 12 +++++ 3 files changed, 81 insertions(+), 15 deletions(-) create mode 100644 test/pdfs/issue12294.pdf.link diff --git a/src/core/annotation.js b/src/core/annotation.js index b43ae5a7b9e4d..f149b98f71928 100644 --- a/src/core/annotation.js +++ b/src/core/annotation.js @@ -885,10 +885,18 @@ class WidgetAnnotation extends Annotation { ""; const fieldType = getInheritableProperty({ dict, key: "FT" }); data.fieldType = isName(fieldType) ? fieldType.name : null; - this.fieldResources = - getInheritableProperty({ dict, key: "DR" }) || - params.acroForm.get("DR") || - Dict.empty; + + const localResources = getInheritableProperty({ dict, key: "DR" }); + const acroFormResources = params.acroForm.get("DR"); + this._fieldResources = { + localResources, + acroFormResources, + mergedResources: Dict.merge({ + xref: params.xref, + dictArray: [localResources, acroFormResources], + mergeSubDicts: true, + }), + }; data.fieldFlags = getInheritableProperty({ dict, key: "Ff" }); if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) { @@ -1043,7 +1051,7 @@ class WidgetAnnotation extends Annotation { .getOperatorList({ stream, task, - resources: this.fieldResources, + resources: this._fieldResources.mergedResources, operatorList, }) .then(function () { @@ -1067,8 +1075,9 @@ class WidgetAnnotation extends Annotation { if (appearance === null) { return null; } + const { xref } = evaluator; - const dict = evaluator.xref.fetchIfRef(this.ref); + const dict = xref.fetchIfRef(this.ref); if (!isDict(dict)) { return null; } @@ -1086,11 +1095,11 @@ class WidgetAnnotation extends Annotation { value, }; - const newRef = evaluator.xref.getNewRef(); - const AP = new Dict(evaluator.xref); + const newRef = xref.getNewRef(); + const AP = new Dict(xref); AP.set("N", newRef); - const encrypt = evaluator.xref.encrypt; + const encrypt = xref.encrypt; let originalTransform = null; let newTransform = null; if (encrypt) { @@ -1106,10 +1115,10 @@ class WidgetAnnotation extends Annotation { dict.set("AP", AP); dict.set("M", `D:${getModificationDate()}`); - const appearanceDict = new Dict(evaluator.xref); + const appearanceDict = new Dict(xref); appearanceDict.set("Length", appearance.length); appearanceDict.set("Subtype", Name.get("Form")); - appearanceDict.set("Resources", this.fieldResources); + appearanceDict.set("Resources", this._getSaveFieldResources(xref)); appearanceDict.set("BBox", bbox); const bufferOriginal = [`${this.ref.num} ${this.ref.gen} obj\n`]; @@ -1132,6 +1141,8 @@ class WidgetAnnotation extends Annotation { } async _getAppearance(evaluator, task, annotationStorage) { + this._fontName = null; + const isPassword = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD); if (!annotationStorage || isPassword) { return null; @@ -1148,9 +1159,8 @@ class WidgetAnnotation extends Annotation { const fontInfo = await this._getFontData(evaluator, task); const [font, fontName] = fontInfo; - let fontSize = fontInfo[2]; - - fontSize = this._computeFontSize(font, fontName, fontSize, totalHeight); + const fontSize = this._computeFontSize(...fontInfo, totalHeight); + this._fontName = fontName; let descent = font.descent; if (isNaN(descent)) { @@ -1226,7 +1236,7 @@ class WidgetAnnotation extends Annotation { await evaluator.getOperatorList({ stream: new StringStream(this.data.defaultAppearance), task, - resources: this.fieldResources, + resources: this._fieldResources.mergedResources, operatorList, initialState, }); @@ -1280,6 +1290,49 @@ class WidgetAnnotation extends Annotation { return `${shift} ${vPadding} Td (${escapeString(text)}) Tj`; } + + /** + * @private + */ + _getSaveFieldResources(xref) { + if ( + typeof PDFJSDev === "undefined" || + PDFJSDev.test("!PRODUCTION || TESTING") + ) { + assert( + this._fontName !== undefined, + "Expected `_getAppearance()` to have been called." + ); + } + const { localResources, acroFormResources } = this._fieldResources; + + if (!this._fontName) { + return localResources || Dict.empty; + } + if (localResources instanceof Dict) { + const localFont = localResources.get("Font"); + if (localFont instanceof Dict && localFont.has(this._fontName)) { + return localResources; + } + } + if (acroFormResources instanceof Dict) { + const acroFormFont = acroFormResources.get("Font"); + if (acroFormFont instanceof Dict && acroFormFont.has(this._fontName)) { + const subFontDict = new Dict(xref); + subFontDict.set(this._fontName, acroFormFont.getRaw(this._fontName)); + + const subResourcesDict = new Dict(xref); + subResourcesDict.set("Font", subFontDict); + + return Dict.merge({ + xref, + dictArray: [subResourcesDict, localResources], + mergeSubDicts: true, + }); + } + } + return localResources || Dict.empty; + } } class TextWidgetAnnotation extends WidgetAnnotation { diff --git a/test/pdfs/issue12294.pdf.link b/test/pdfs/issue12294.pdf.link new file mode 100644 index 0000000000000..79a00e57e95a5 --- /dev/null +++ b/test/pdfs/issue12294.pdf.link @@ -0,0 +1 @@ +https://web.archive.org/web/20200914130729/https://www.nta.go.jp/taxes/tetsuzuki/shinsei/annai/joyaku/annai/pdf2/250.pdf diff --git a/test/test_manifest.json b/test/test_manifest.json index 68124264ef86b..26e4fe3a5745d 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -2780,6 +2780,18 @@ "rounds": 1, "type": "eq" }, + { "id": "issue12294-print", + "file": "pdfs/issue12294.pdf", + "md5": "a0ac5e03be38b5fb7a7a615e30024b28", + "rounds": 1, + "lastPage": 1, + "link": true, + "type": "eq", + "print": true, + "annotationStorage": { + "2795R": "氏 名 又 は 名 称 Full name" + } + }, { "id": "issue7598", "file": "pdfs/issue7598.pdf", "md5": "c5bc5a779bfcb4b234f853231b56cf60",