XFA -- Load fonts permanently from the pdf
- Different fonts can be used in xfa and some of them are embedded in the pdf. - Load all the fonts in window.document. Update src/core/document.js Co-authored-by: Jonas Jenwald <jonas.jenwald@gmail.com> Update src/core/worker.js Co-authored-by: Jonas Jenwald <jonas.jenwald@gmail.com>
This commit is contained in:
		
							parent
							
								
									6cf3070008
								
							
						
					
					
						commit
						7e9579045f
					
				| @ -19,6 +19,7 @@ import { | ||||
|   bytesToString, | ||||
|   objectSize, | ||||
|   stringToPDFString, | ||||
|   warn, | ||||
| } from "../shared/util.js"; | ||||
| import { Dict, isName, isRef, isStream, RefSet } from "./primitives.js"; | ||||
| 
 | ||||
| @ -376,6 +377,70 @@ function encodeToXmlString(str) { | ||||
|   return buffer.join(""); | ||||
| } | ||||
| 
 | ||||
| function validateCSSFont(cssFontInfo) { | ||||
|   // See https://developer.mozilla.org/en-US/docs/Web/CSS/font-style.
 | ||||
|   const DEFAULT_CSS_FONT_OBLIQUE = "14"; | ||||
|   // See https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight.
 | ||||
|   const DEFAULT_CSS_FONT_WEIGHT = "400"; | ||||
|   const CSS_FONT_WEIGHT_VALUES = new Set([ | ||||
|     "100", | ||||
|     "200", | ||||
|     "300", | ||||
|     "400", | ||||
|     "500", | ||||
|     "600", | ||||
|     "700", | ||||
|     "800", | ||||
|     "900", | ||||
|     "1000", | ||||
|     "normal", | ||||
|     "bold", | ||||
|     "bolder", | ||||
|     "lighter", | ||||
|   ]); | ||||
| 
 | ||||
|   const { fontFamily, fontWeight, italicAngle } = cssFontInfo; | ||||
| 
 | ||||
|   // See https://developer.mozilla.org/en-US/docs/Web/CSS/string.
 | ||||
|   if (/^".*"$/.test(fontFamily)) { | ||||
|     if (/[^\\]"/.test(fontFamily.slice(1, fontFamily.length - 1))) { | ||||
|       warn(`XFA - FontFamily contains some unescaped ": ${fontFamily}.`); | ||||
|       return false; | ||||
|     } | ||||
|   } else if (/^'.*'$/.test(fontFamily)) { | ||||
|     if (/[^\\]'/.test(fontFamily.slice(1, fontFamily.length - 1))) { | ||||
|       warn(`XFA - FontFamily contains some unescaped ': ${fontFamily}.`); | ||||
|       return false; | ||||
|     } | ||||
|   } else { | ||||
|     // See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident.
 | ||||
|     for (const ident of fontFamily.split(/[ \t]+/)) { | ||||
|       if ( | ||||
|         /^([0-9]|(-([0-9]|-)))/.test(ident) || | ||||
|         !/^[a-zA-Z0-9\-_\\]+$/.test(ident) | ||||
|       ) { | ||||
|         warn( | ||||
|           `XFA - FontFamily contains some invalid <custom-ident>: ${fontFamily}.` | ||||
|         ); | ||||
|         return false; | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const weight = fontWeight ? fontWeight.toString() : ""; | ||||
|   cssFontInfo.fontWeight = CSS_FONT_WEIGHT_VALUES.has(weight) | ||||
|     ? weight | ||||
|     : DEFAULT_CSS_FONT_WEIGHT; | ||||
| 
 | ||||
|   const angle = parseFloat(italicAngle); | ||||
|   cssFontInfo.italicAngle = | ||||
|     isNaN(angle) || angle < -90 || angle > 90 | ||||
|       ? DEFAULT_CSS_FONT_OBLIQUE | ||||
|       : italicAngle.toString(); | ||||
| 
 | ||||
|   return true; | ||||
| } | ||||
| 
 | ||||
| export { | ||||
|   collectActions, | ||||
|   encodeToXmlString, | ||||
| @ -391,6 +456,7 @@ export { | ||||
|   readUint16, | ||||
|   readUint32, | ||||
|   toRomanNumerals, | ||||
|   validateCSSFont, | ||||
|   XRefEntryException, | ||||
|   XRefParseException, | ||||
| }; | ||||
|  | ||||
| @ -42,6 +42,7 @@ import { | ||||
|   isName, | ||||
|   isRef, | ||||
|   isStream, | ||||
|   Name, | ||||
|   Ref, | ||||
| } from "./primitives.js"; | ||||
| import { | ||||
| @ -49,6 +50,7 @@ import { | ||||
|   getInheritableProperty, | ||||
|   isWhiteSpace, | ||||
|   MissingDataException, | ||||
|   validateCSSFont, | ||||
|   XRefEntryException, | ||||
|   XRefParseException, | ||||
| } from "./core_utils.js"; | ||||
| @ -854,6 +856,71 @@ class PDFDocument { | ||||
|     return this.xfaFactory !== null; | ||||
|   } | ||||
| 
 | ||||
|   async loadXfaFonts(handler, task) { | ||||
|     const acroForm = await this.pdfManager.ensureCatalog("acroForm"); | ||||
| 
 | ||||
|     const resources = await acroForm.getAsync("DR"); | ||||
|     if (!(resources instanceof Dict)) { | ||||
|       return; | ||||
|     } | ||||
|     const objectLoader = new ObjectLoader(resources, ["Font"], this.xref); | ||||
|     await objectLoader.load(); | ||||
| 
 | ||||
|     const fontRes = resources.get("Font"); | ||||
|     if (!(fontRes instanceof Dict)) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     const partialEvaluator = new PartialEvaluator({ | ||||
|       xref: this.xref, | ||||
|       handler, | ||||
|       pageIndex: -1, | ||||
|       idFactory: this._globalIdFactory, | ||||
|       fontCache: this.catalog.fontCache, | ||||
|       builtInCMapCache: this.catalog.builtInCMapCache, | ||||
|     }); | ||||
|     const operatorList = new OperatorList(); | ||||
|     const initialState = { | ||||
|       font: null, | ||||
|       clone() { | ||||
|         return this; | ||||
|       }, | ||||
|     }; | ||||
| 
 | ||||
|     const fonts = new Map(); | ||||
|     fontRes.forEach((fontName, font) => { | ||||
|       fonts.set(fontName, font); | ||||
|     }); | ||||
|     const promises = []; | ||||
| 
 | ||||
|     for (const [fontName, font] of fonts) { | ||||
|       const descriptor = font.get("FontDescriptor"); | ||||
|       if (descriptor instanceof Dict) { | ||||
|         const fontFamily = descriptor.get("FontFamily"); | ||||
|         const fontWeight = descriptor.get("FontWeight"); | ||||
|         const italicAngle = descriptor.get("ItalicAngle"); | ||||
|         const cssFontInfo = { fontFamily, fontWeight, italicAngle }; | ||||
| 
 | ||||
|         if (!validateCSSFont(cssFontInfo)) { | ||||
|           continue; | ||||
|         } | ||||
| 
 | ||||
|         const promise = partialEvaluator.handleSetFont( | ||||
|           resources, | ||||
|           [Name.get(fontName), 1], | ||||
|           /* fontRef = */ null, | ||||
|           operatorList, | ||||
|           task, | ||||
|           initialState, | ||||
|           /* fallbackFontDict = */ null, | ||||
|           /* cssFontInfo = */ cssFontInfo | ||||
|         ); | ||||
|         promises.push(promise.catch(() => {})); | ||||
|       } | ||||
|     } | ||||
|     await Promise.all(promises); | ||||
|   } | ||||
| 
 | ||||
|   get formInfo() { | ||||
|     const formInfo = { | ||||
|       hasFields: false, | ||||
|  | ||||
| @ -792,12 +792,19 @@ class PartialEvaluator { | ||||
|     operatorList, | ||||
|     task, | ||||
|     state, | ||||
|     fallbackFontDict = null | ||||
|     fallbackFontDict = null, | ||||
|     cssFontInfo = null | ||||
|   ) { | ||||
|     const fontName = | ||||
|       fontArgs && fontArgs[0] instanceof Name ? fontArgs[0].name : null; | ||||
| 
 | ||||
|     return this.loadFont(fontName, fontRef, resources, fallbackFontDict) | ||||
|     return this.loadFont( | ||||
|       fontName, | ||||
|       fontRef, | ||||
|       resources, | ||||
|       fallbackFontDict, | ||||
|       cssFontInfo | ||||
|     ) | ||||
|       .then(translated => { | ||||
|         if (!translated.font.isType3Font) { | ||||
|           return translated; | ||||
| @ -986,7 +993,13 @@ class PartialEvaluator { | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   loadFont(fontName, font, resources, fallbackFontDict = null) { | ||||
|   loadFont( | ||||
|     fontName, | ||||
|     font, | ||||
|     resources, | ||||
|     fallbackFontDict = null, | ||||
|     cssFontInfo = null | ||||
|   ) { | ||||
|     const errorFont = async () => { | ||||
|       return new TranslatedFont({ | ||||
|         loadedName: "g_font_error", | ||||
| @ -1055,6 +1068,7 @@ class PartialEvaluator { | ||||
|     let preEvaluatedFont; | ||||
|     try { | ||||
|       preEvaluatedFont = this.preEvaluateFont(font); | ||||
|       preEvaluatedFont.cssFontInfo = cssFontInfo; | ||||
|     } catch (reason) { | ||||
|       warn(`loadFont - preEvaluateFont failed: "${reason}".`); | ||||
|       return errorFont(); | ||||
| @ -3529,6 +3543,7 @@ class PartialEvaluator { | ||||
|       flags: descriptor.get("Flags"), | ||||
|       italicAngle: descriptor.get("ItalicAngle"), | ||||
|       isType3Font: false, | ||||
|       cssFontInfo: preEvaluatedFont.cssFontInfo, | ||||
|     }; | ||||
| 
 | ||||
|     if (composite) { | ||||
|  | ||||
| @ -95,6 +95,7 @@ const EXPORT_DATA_PROPERTIES = [ | ||||
|   "bold", | ||||
|   "charProcOperatorList", | ||||
|   "composite", | ||||
|   "cssFontInfo", | ||||
|   "data", | ||||
|   "defaultVMetrics", | ||||
|   "defaultWidth", | ||||
| @ -565,6 +566,7 @@ var Font = (function FontClosure() { | ||||
|     this.loadedName = properties.loadedName; | ||||
|     this.isType3Font = properties.isType3Font; | ||||
|     this.missingFile = false; | ||||
|     this.cssFontInfo = properties.cssFontInfo; | ||||
| 
 | ||||
|     this.glyphCache = Object.create(null); | ||||
| 
 | ||||
| @ -2963,23 +2965,31 @@ var Font = (function FontClosure() { | ||||
|         glyphZeroId = 0; | ||||
|       } | ||||
| 
 | ||||
|       // Converting glyphs and ids into font's cmap table
 | ||||
|       var newMapping = adjustMapping(charCodeToGlyphId, hasGlyph, glyphZeroId); | ||||
|       this.toFontChar = newMapping.toFontChar; | ||||
|       tables.cmap = { | ||||
|         tag: "cmap", | ||||
|         data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphsOut), | ||||
|       }; | ||||
| 
 | ||||
|       if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) { | ||||
|         tables["OS/2"] = { | ||||
|           tag: "OS/2", | ||||
|           data: createOS2Table( | ||||
|             properties, | ||||
|             newMapping.charCodeToGlyphId, | ||||
|             metricsOverride | ||||
|           ), | ||||
|       // When `cssFontInfo` is set, the font is used to render text in the HTML
 | ||||
|       // view (e.g. with Xfa) so nothing must be moved in the private area use.
 | ||||
|       if (!properties.cssFontInfo) { | ||||
|         // Converting glyphs and ids into font's cmap table
 | ||||
|         var newMapping = adjustMapping( | ||||
|           charCodeToGlyphId, | ||||
|           hasGlyph, | ||||
|           glyphZeroId | ||||
|         ); | ||||
|         this.toFontChar = newMapping.toFontChar; | ||||
|         tables.cmap = { | ||||
|           tag: "cmap", | ||||
|           data: createCmapTable(newMapping.charCodeToGlyphId, numGlyphsOut), | ||||
|         }; | ||||
| 
 | ||||
|         if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) { | ||||
|           tables["OS/2"] = { | ||||
|             tag: "OS/2", | ||||
|             data: createOS2Table( | ||||
|               properties, | ||||
|               newMapping.charCodeToGlyphId, | ||||
|               metricsOverride | ||||
|             ), | ||||
|           }; | ||||
|         } | ||||
|       } | ||||
| 
 | ||||
|       if (!isTrueType) { | ||||
|  | ||||
| @ -73,6 +73,10 @@ class BasePdfManager { | ||||
|     return this.pdfDocument.fontFallback(id, handler); | ||||
|   } | ||||
| 
 | ||||
|   loadXfaFonts(handler, task) { | ||||
|     return this.pdfDocument.loadXfaFonts(handler, task); | ||||
|   } | ||||
| 
 | ||||
|   cleanup(manuallyTriggered = false) { | ||||
|     return this.pdfDocument.cleanup(manuallyTriggered); | ||||
|   } | ||||
|  | ||||
| @ -193,6 +193,17 @@ class WorkerMessageHandler { | ||||
|         pdfManager.ensureDoc("fingerprint"), | ||||
|         pdfManager.ensureDoc("isPureXfa"), | ||||
|       ]); | ||||
| 
 | ||||
|       if (isPureXfa) { | ||||
|         const task = new WorkerTask("Load fonts for Xfa"); | ||||
|         startWorkerTask(task); | ||||
|         await pdfManager | ||||
|           .loadXfaFonts(handler, task) | ||||
|           .catch(reason => { | ||||
|             // Ignore errors, to allow the document to load.
 | ||||
|           }) | ||||
|           .then(() => finishWorkerTask(task)); | ||||
|       } | ||||
|       return { numPages, fingerprint, isPureXfa }; | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -370,7 +370,22 @@ class FontFaceObject { | ||||
|     if (!this.data || this.disableFontFace) { | ||||
|       return null; | ||||
|     } | ||||
|     const nativeFontFace = new FontFace(this.loadedName, this.data, {}); | ||||
|     let nativeFontFace; | ||||
|     if (!this.cssFontInfo) { | ||||
|       nativeFontFace = new FontFace(this.loadedName, this.data, {}); | ||||
|     } else { | ||||
|       const css = { | ||||
|         weight: this.cssFontInfo.fontWeight, | ||||
|       }; | ||||
|       if (this.cssFontInfo.italicAngle) { | ||||
|         css.style = `oblique ${this.cssFontInfo.italicAngle}deg`; | ||||
|       } | ||||
|       nativeFontFace = new FontFace( | ||||
|         this.cssFontInfo.fontFamily, | ||||
|         this.data, | ||||
|         css | ||||
|       ); | ||||
|     } | ||||
| 
 | ||||
|     if (this.fontRegistry) { | ||||
|       this.fontRegistry.registerFont(this); | ||||
| @ -385,7 +400,16 @@ class FontFaceObject { | ||||
|     const data = bytesToString(new Uint8Array(this.data)); | ||||
|     // Add the @font-face rule to the document.
 | ||||
|     const url = `url(data:${this.mimetype};base64,${btoa(data)});`; | ||||
|     const rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`; | ||||
|     let rule; | ||||
|     if (!this.cssFontInfo) { | ||||
|       rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`; | ||||
|     } else { | ||||
|       let css = `font-weight: ${this.cssFontInfo.fontWeight};`; | ||||
|       if (this.cssFontInfo.italicAngle) { | ||||
|         css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`; | ||||
|       } | ||||
|       rule = `@font-face {font-family:"${this.cssFontInfo.fontFamily}";${css}src:${url}}`; | ||||
|     } | ||||
| 
 | ||||
|     if (this.fontRegistry) { | ||||
|       this.fontRegistry.registerFont(this, url); | ||||
|  | ||||
| @ -22,6 +22,7 @@ import { | ||||
|   log2, | ||||
|   parseXFAPath, | ||||
|   toRomanNumerals, | ||||
|   validateCSSFont, | ||||
| } from "../../src/core/core_utils.js"; | ||||
| import { XRefMock } from "./test_utils.js"; | ||||
| 
 | ||||
| @ -233,4 +234,103 @@ describe("core_utils", function () { | ||||
|       expect(encodeToXmlString(str)).toEqual(str); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe("validateCSSFont", function () { | ||||
|     it("Check font family", function () { | ||||
|       const cssFontInfo = { | ||||
|         fontFamily: `"blah blah " blah blah"`, | ||||
|         fontWeight: 0, | ||||
|         italicAngle: 0, | ||||
|       }; | ||||
| 
 | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = `"blah blah \\" blah blah"`; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(true); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = `'blah blah ' blah blah'`; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = `'blah blah \\' blah blah'`; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(true); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = `"blah blah `; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = `blah blah"`; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = `'blah blah `; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = `blah blah'`; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = "blah blah blah"; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(true); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = "blah 0blah blah"; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = "blah blah -0blah"; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = "blah blah --blah"; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = "blah blah -blah"; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(true); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = "blah fdqAJqjHJK23kl23__--Kj blah"; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(true); | ||||
| 
 | ||||
|       cssFontInfo.fontFamily = "blah fdqAJqjH$JK23kl23__--Kj blah"; | ||||
|       expect(validateCSSFont(cssFontInfo)).toEqual(false); | ||||
|     }); | ||||
| 
 | ||||
|     it("Check font weight", function () { | ||||
|       const cssFontInfo = { | ||||
|         fontFamily: "blah", | ||||
|         fontWeight: 100, | ||||
|         italicAngle: 0, | ||||
|       }; | ||||
| 
 | ||||
|       validateCSSFont(cssFontInfo); | ||||
|       expect(cssFontInfo.fontWeight).toEqual("100"); | ||||
| 
 | ||||
|       cssFontInfo.fontWeight = "700"; | ||||
|       validateCSSFont(cssFontInfo); | ||||
|       expect(cssFontInfo.fontWeight).toEqual("700"); | ||||
| 
 | ||||
|       cssFontInfo.fontWeight = "normal"; | ||||
|       validateCSSFont(cssFontInfo); | ||||
|       expect(cssFontInfo.fontWeight).toEqual("normal"); | ||||
| 
 | ||||
|       cssFontInfo.fontWeight = 314; | ||||
|       validateCSSFont(cssFontInfo); | ||||
|       expect(cssFontInfo.fontWeight).toEqual("400"); | ||||
|     }); | ||||
| 
 | ||||
|     it("Check italic angle", function () { | ||||
|       const cssFontInfo = { | ||||
|         fontFamily: "blah", | ||||
|         fontWeight: 100, | ||||
|         italicAngle: 10, | ||||
|       }; | ||||
|       validateCSSFont(cssFontInfo); | ||||
|       expect(cssFontInfo.italicAngle).toEqual("10"); | ||||
| 
 | ||||
|       cssFontInfo.italicAngle = -123; | ||||
|       validateCSSFont(cssFontInfo); | ||||
|       expect(cssFontInfo.italicAngle).toEqual("14"); | ||||
| 
 | ||||
|       cssFontInfo.italicAngle = "91"; | ||||
|       validateCSSFont(cssFontInfo); | ||||
|       expect(cssFontInfo.italicAngle).toEqual("14"); | ||||
| 
 | ||||
|       cssFontInfo.italicAngle = 2.718; | ||||
|       validateCSSFont(cssFontInfo); | ||||
|       expect(cssFontInfo.italicAngle).toEqual("2.718"); | ||||
|     }); | ||||
|   }); | ||||
| }); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user