Skip to content

Commit

Permalink
XFA -- Load fonts permanently from the pdf
Browse files Browse the repository at this point in the history
  - Different fonts can be used in xfa and some of them are embedded in the pdf.
  - Load all the fonts in window.document.
  - Disable font cache clean up in such a case to avoid rendering issues.
  • Loading branch information
calixteman committed Mar 26, 2021
1 parent 63471bc commit 3b1234b
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 24 deletions.
48 changes: 48 additions & 0 deletions src/core/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
isName,
isRef,
isStream,
Name,
Ref,
} from "./primitives.js";
import {
Expand Down Expand Up @@ -826,6 +827,53 @@ class PDFDocument {
return this.xfaFactory !== null;
}

loadXfaFonts(handler, task) {
const resources = this.catalog.acroForm.get("DR");
const fonts = resources.get("Font");
if (!(fonts instanceof Dict)) {
return Promise.resolve(null);
}

const objectLoader = new ObjectLoader(resources, ["Font"], this.xref);
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;
},
};

return objectLoader.load().then(() => {
fonts.forEach((pdfFontName, font) => {
const descriptor = font.get("FontDescriptor");
if (descriptor instanceof Dict) {
const fontFamily = descriptor.get("FontFamily");
const fontWeight = descriptor.get("FontWeight");
const italicAngle = descriptor.get("ItalicAngle");

partialEvaluator.handleSetFont(
resources,
[Name.get(pdfFontName), 1],
null,
operatorList,
task,
initialState,
/* fallbackFontDict = */ null,
/* cssFontInfo = */ { fontFamily, fontWeight, italicAngle }
);
}
});
});
}

get formInfo() {
const formInfo = { hasFields: false, hasAcroForm: false, hasXfa: false };
const acroForm = this.catalog.acroForm;
Expand Down
21 changes: 18 additions & 3 deletions src/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,8 @@ class PartialEvaluator {
operatorList,
task,
state,
fallbackFontDict = null
fallbackFontDict = null,
cssFontInfo = null
) {
// TODO(mack): Not needed?
var fontName;
Expand All @@ -801,7 +802,13 @@ class PartialEvaluator {
fontName = fontArgs[0].name;
}

return this.loadFont(fontName, fontRef, resources, fallbackFontDict)
return this.loadFont(
fontName,
fontRef,
resources,
fallbackFontDict,
cssFontInfo
)
.then(translated => {
if (!translated.font.isType3Font) {
return translated;
Expand Down Expand Up @@ -990,7 +997,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",
Expand Down Expand Up @@ -1059,6 +1072,7 @@ class PartialEvaluator {
let preEvaluatedFont;
try {
preEvaluatedFont = this.preEvaluateFont(font);
preEvaluatedFont.cssFontInfo = cssFontInfo;
} catch (reason) {
warn(`loadFont - preEvaluateFont failed: "${reason}".`);
return errorFont();
Expand Down Expand Up @@ -3494,6 +3508,7 @@ class PartialEvaluator {
flags: descriptor.get("Flags"),
italicAngle: descriptor.get("ItalicAngle"),
isType3Font: false,
cssFontInfo: preEvaluatedFont.cssFontInfo,
};

if (composite) {
Expand Down
42 changes: 26 additions & 16 deletions src/core/fonts.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const EXPORT_DATA_PROPERTIES = [
"bold",
"charProcOperatorList",
"composite",
"cssFontInfo",
"data",
"defaultVMetrics",
"defaultWidth",
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -2928,23 +2930,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) {
Expand Down
4 changes: 4 additions & 0 deletions src/core/pdf_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
8 changes: 8 additions & 0 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ 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)
.finally(() => finishWorkerTask(task));
}
return { numPages, fingerprint, isPureXfa };
}

Expand Down
10 changes: 7 additions & 3 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,9 @@ class PDFDocumentProxy {
* @returns {Promise} A promise that is resolved when clean-up has finished.
*/
cleanup() {
return this._transport.startCleanup();
// Don't cleanup the font loader with Xfa since fonts are used in HTML
// to display Xfa content.
return this._transport.startCleanup(/* keepFonts */ this.isPureXfa);
}

/**
Expand Down Expand Up @@ -2785,7 +2787,7 @@ class WorkerTransport {
return this.messageHandler.sendWithPromise("GetStats", null);
}

startCleanup() {
startCleanup(keepFonts = false) {
return this.messageHandler.sendWithPromise("Cleanup", null).then(() => {
for (let i = 0, ii = this.pageCache.length; i < ii; i++) {
const page = this.pageCache[i];
Expand All @@ -2800,7 +2802,9 @@ class WorkerTransport {
}
}
this.commonObjs.clear();
this.fontLoader.clear();
if (!keepFonts) {
this.fontLoader.clear();
}
this._hasJSActionsPromise = null;
});
}
Expand Down
28 changes: 26 additions & 2 deletions src/display/font_loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit 3b1234b

Please sign in to comment.