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.
This commit is contained in:
		
							parent
							
								
									741ce4f7fc
								
							
						
					
					
						commit
						c992b8e460
					
				| @ -885,10 +885,18 @@ class WidgetAnnotation extends Annotation { | |||||||
|       ""; |       ""; | ||||||
|     const fieldType = getInheritableProperty({ dict, key: "FT" }); |     const fieldType = getInheritableProperty({ dict, key: "FT" }); | ||||||
|     data.fieldType = isName(fieldType) ? fieldType.name : null; |     data.fieldType = isName(fieldType) ? fieldType.name : null; | ||||||
|     this.fieldResources = | 
 | ||||||
|       getInheritableProperty({ dict, key: "DR" }) || |     const localResources = getInheritableProperty({ dict, key: "DR" }); | ||||||
|       params.acroForm.get("DR") || |     const acroFormResources = params.acroForm.get("DR"); | ||||||
|       Dict.empty; |     this._fieldResources = { | ||||||
|  |       localResources, | ||||||
|  |       acroFormResources, | ||||||
|  |       mergedResources: Dict.merge({ | ||||||
|  |         xref: params.xref, | ||||||
|  |         dictArray: [localResources, acroFormResources], | ||||||
|  |         mergeSubDicts: true, | ||||||
|  |       }), | ||||||
|  |     }; | ||||||
| 
 | 
 | ||||||
|     data.fieldFlags = getInheritableProperty({ dict, key: "Ff" }); |     data.fieldFlags = getInheritableProperty({ dict, key: "Ff" }); | ||||||
|     if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) { |     if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) { | ||||||
| @ -1043,7 +1051,7 @@ class WidgetAnnotation extends Annotation { | |||||||
|           .getOperatorList({ |           .getOperatorList({ | ||||||
|             stream, |             stream, | ||||||
|             task, |             task, | ||||||
|             resources: this.fieldResources, |             resources: this._fieldResources.mergedResources, | ||||||
|             operatorList, |             operatorList, | ||||||
|           }) |           }) | ||||||
|           .then(function () { |           .then(function () { | ||||||
| @ -1067,8 +1075,9 @@ class WidgetAnnotation extends Annotation { | |||||||
|     if (appearance === null) { |     if (appearance === null) { | ||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
|  |     const { xref } = evaluator; | ||||||
| 
 | 
 | ||||||
|     const dict = evaluator.xref.fetchIfRef(this.ref); |     const dict = xref.fetchIfRef(this.ref); | ||||||
|     if (!isDict(dict)) { |     if (!isDict(dict)) { | ||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
| @ -1086,11 +1095,11 @@ class WidgetAnnotation extends Annotation { | |||||||
|       value, |       value, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     const newRef = evaluator.xref.getNewRef(); |     const newRef = xref.getNewRef(); | ||||||
|     const AP = new Dict(evaluator.xref); |     const AP = new Dict(xref); | ||||||
|     AP.set("N", newRef); |     AP.set("N", newRef); | ||||||
| 
 | 
 | ||||||
|     const encrypt = evaluator.xref.encrypt; |     const encrypt = xref.encrypt; | ||||||
|     let originalTransform = null; |     let originalTransform = null; | ||||||
|     let newTransform = null; |     let newTransform = null; | ||||||
|     if (encrypt) { |     if (encrypt) { | ||||||
| @ -1106,10 +1115,10 @@ class WidgetAnnotation extends Annotation { | |||||||
|     dict.set("AP", AP); |     dict.set("AP", AP); | ||||||
|     dict.set("M", `D:${getModificationDate()}`); |     dict.set("M", `D:${getModificationDate()}`); | ||||||
| 
 | 
 | ||||||
|     const appearanceDict = new Dict(evaluator.xref); |     const appearanceDict = new Dict(xref); | ||||||
|     appearanceDict.set("Length", appearance.length); |     appearanceDict.set("Length", appearance.length); | ||||||
|     appearanceDict.set("Subtype", Name.get("Form")); |     appearanceDict.set("Subtype", Name.get("Form")); | ||||||
|     appearanceDict.set("Resources", this.fieldResources); |     appearanceDict.set("Resources", this._getSaveFieldResources(xref)); | ||||||
|     appearanceDict.set("BBox", bbox); |     appearanceDict.set("BBox", bbox); | ||||||
| 
 | 
 | ||||||
|     const bufferOriginal = [`${this.ref.num} ${this.ref.gen} obj\n`]; |     const bufferOriginal = [`${this.ref.num} ${this.ref.gen} obj\n`]; | ||||||
| @ -1132,6 +1141,8 @@ class WidgetAnnotation extends Annotation { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async _getAppearance(evaluator, task, annotationStorage) { |   async _getAppearance(evaluator, task, annotationStorage) { | ||||||
|  |     this._fontName = null; | ||||||
|  | 
 | ||||||
|     const isPassword = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD); |     const isPassword = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD); | ||||||
|     if (!annotationStorage || isPassword) { |     if (!annotationStorage || isPassword) { | ||||||
|       return null; |       return null; | ||||||
| @ -1148,9 +1159,8 @@ class WidgetAnnotation extends Annotation { | |||||||
| 
 | 
 | ||||||
|     const fontInfo = await this._getFontData(evaluator, task); |     const fontInfo = await this._getFontData(evaluator, task); | ||||||
|     const [font, fontName] = fontInfo; |     const [font, fontName] = fontInfo; | ||||||
|     let fontSize = fontInfo[2]; |     const fontSize = this._computeFontSize(...fontInfo, totalHeight); | ||||||
| 
 |     this._fontName = fontName; | ||||||
|     fontSize = this._computeFontSize(font, fontName, fontSize, totalHeight); |  | ||||||
| 
 | 
 | ||||||
|     let descent = font.descent; |     let descent = font.descent; | ||||||
|     if (isNaN(descent)) { |     if (isNaN(descent)) { | ||||||
| @ -1226,7 +1236,7 @@ class WidgetAnnotation extends Annotation { | |||||||
|     await evaluator.getOperatorList({ |     await evaluator.getOperatorList({ | ||||||
|       stream: new StringStream(this.data.defaultAppearance), |       stream: new StringStream(this.data.defaultAppearance), | ||||||
|       task, |       task, | ||||||
|       resources: this.fieldResources, |       resources: this._fieldResources.mergedResources, | ||||||
|       operatorList, |       operatorList, | ||||||
|       initialState, |       initialState, | ||||||
|     }); |     }); | ||||||
| @ -1280,6 +1290,49 @@ class WidgetAnnotation extends Annotation { | |||||||
| 
 | 
 | ||||||
|     return `${shift} ${vPadding} Td (${escapeString(text)}) Tj`; |     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 { | class TextWidgetAnnotation extends WidgetAnnotation { | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								test/pdfs/issue12294.pdf.link
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								test/pdfs/issue12294.pdf.link
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | https://web.archive.org/web/20200914130729/https://www.nta.go.jp/taxes/tetsuzuki/shinsei/annai/joyaku/annai/pdf2/250.pdf | ||||||
| @ -2780,6 +2780,18 @@ | |||||||
|        "rounds": 1, |        "rounds": 1, | ||||||
|        "type": "eq" |        "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", |     {  "id": "issue7598", | ||||||
|        "file": "pdfs/issue7598.pdf", |        "file": "pdfs/issue7598.pdf", | ||||||
|        "md5": "c5bc5a779bfcb4b234f853231b56cf60", |        "md5": "c5bc5a779bfcb4b234f853231b56cf60", | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user