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.

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>
  • Loading branch information
calixteman and Snuffleupagus committed Apr 15, 2021
1 parent 6cf3070 commit 7509ac0
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 21 deletions.
61 changes: 61 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 @@ -854,6 +855,66 @@ 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 promise = partialEvaluator.handleSetFont(
resources,
[Name.get(fontName), 1],
/* fontRef = */ null,
operatorList,
task,
initialState,
/* fallbackFontDict = */ null,
/* cssFontInfo = */ { fontFamily, fontWeight, italicAngle }
);
promises.push(promise.catch(() => {}));
}
}
await Promise.all(promises);
}

get formInfo() {
const formInfo = {
hasFields: false,
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,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;
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -3529,6 +3543,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 @@ -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) {
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
11 changes: 11 additions & 0 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
}

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 7509ac0

Please sign in to comment.