in src/core/evaluator.js [4301:4594]
async translateFont({
descriptor,
dict,
baseDict,
composite,
type,
firstChar,
lastChar,
toUnicode,
cssFontInfo,
}) {
const isType3Font = type === "Type3";
if (!descriptor) {
if (isType3Font) {
const bbox = lookupNormalRect(dict.getArray("FontBBox"), [0, 0, 0, 0]);
// FontDescriptor is only required for Type3 fonts when the document
// is a tagged pdf. Create a barbebones one to get by.
descriptor = new Dict(null);
descriptor.set("FontName", Name.get(type));
descriptor.set("FontBBox", bbox);
} else {
// Before PDF 1.5 if the font was one of the base 14 fonts, having a
// FontDescriptor was not required.
// This case is here for compatibility.
let baseFontName = dict.get("BaseFont");
if (!(baseFontName instanceof Name)) {
throw new FormatError("Base font is not specified");
}
// Using base font name as a font name.
baseFontName = baseFontName.name.replaceAll(/[,_]/g, "-");
const metrics = this.getBaseFontMetrics(baseFontName);
// Simulating descriptor flags attribute
const fontNameWoStyle = baseFontName.split("-", 1)[0];
const flags =
(this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) |
(metrics.monospace ? FontFlags.FixedPitch : 0) |
(getSymbolsFonts()[fontNameWoStyle]
? FontFlags.Symbolic
: FontFlags.Nonsymbolic);
const properties = {
type,
name: baseFontName,
loadedName: baseDict.loadedName,
systemFontInfo: null,
widths: metrics.widths,
defaultWidth: metrics.defaultWidth,
isSimulatedFlags: true,
flags,
firstChar,
lastChar,
toUnicode,
xHeight: 0,
capHeight: 0,
italicAngle: 0,
isType3Font,
};
const widths = dict.get("Widths");
const standardFontName = getStandardFontName(baseFontName);
let file = null;
if (standardFontName) {
file = await this.fetchStandardFontData(standardFontName);
properties.isInternalFont = !!file;
}
if (!properties.isInternalFont && this.options.useSystemFonts) {
properties.systemFontInfo = getFontSubstitution(
this.systemFontCache,
this.idFactory,
this.options.standardFontDataUrl,
baseFontName,
standardFontName,
type
);
}
const newProperties = await this.extractDataStructures(
dict,
properties
);
if (Array.isArray(widths)) {
const glyphWidths = [];
let j = firstChar;
for (const w of widths) {
const width = this.xref.fetchIfRef(w);
if (typeof width === "number") {
glyphWidths[j] = width;
}
j++;
}
newProperties.widths = glyphWidths;
} else {
newProperties.widths = this.buildCharCodeToWidth(
metrics.widths,
newProperties
);
}
return new Font(baseFontName, file, newProperties, this.options);
}
}
// According to the spec if 'FontDescriptor' is declared, 'FirstChar',
// 'LastChar' and 'Widths' should exist too, but some PDF encoders seem
// to ignore this rule when a variant of a standard font is used.
// TODO Fill the width array depending on which of the base font this is
// a variant.
let fontName = descriptor.get("FontName");
let baseFont = dict.get("BaseFont");
// Some bad PDFs have a string as the font name.
if (typeof fontName === "string") {
fontName = Name.get(fontName);
}
if (typeof baseFont === "string") {
baseFont = Name.get(baseFont);
}
const fontNameStr = fontName?.name;
const baseFontStr = baseFont?.name;
if (!isType3Font && fontNameStr !== baseFontStr) {
info(
`The FontDescriptor's FontName is "${fontNameStr}" but ` +
`should be the same as the Font's BaseFont "${baseFontStr}".`
);
// - Workaround for cases where e.g. fontNameStr = 'Arial' and
// baseFontStr = 'Arial,Bold' (needed when no font file is embedded).
//
// - Workaround for cases where e.g. fontNameStr = 'wg09np' and
// baseFontStr = 'Wingdings-Regular' (fixes issue7454.pdf).
if (
fontNameStr &&
baseFontStr &&
(baseFontStr.startsWith(fontNameStr) ||
(!isKnownFontName(fontNameStr) && isKnownFontName(baseFontStr)))
) {
fontName = null;
}
}
fontName ||= baseFont;
if (!(fontName instanceof Name)) {
throw new FormatError("invalid font name");
}
let fontFile, subtype, length1, length2, length3;
try {
fontFile = descriptor.get("FontFile", "FontFile2", "FontFile3");
if (fontFile) {
if (!(fontFile instanceof BaseStream)) {
throw new FormatError("FontFile should be a stream");
} else if (fontFile.isEmpty) {
throw new FormatError("FontFile is empty");
}
}
} catch (ex) {
if (!this.options.ignoreErrors) {
throw ex;
}
warn(`translateFont - fetching "${fontName.name}" font file: "${ex}".`);
fontFile = null;
}
let isInternalFont = false;
let glyphScaleFactors = null;
let systemFontInfo = null;
if (fontFile) {
if (fontFile.dict) {
const subtypeEntry = fontFile.dict.get("Subtype");
if (subtypeEntry instanceof Name) {
subtype = subtypeEntry.name;
}
length1 = fontFile.dict.get("Length1");
length2 = fontFile.dict.get("Length2");
length3 = fontFile.dict.get("Length3");
}
} else if (cssFontInfo) {
// We've a missing XFA font.
const standardFontName = getXfaFontName(fontName.name);
if (standardFontName) {
cssFontInfo.fontFamily = `${cssFontInfo.fontFamily}-PdfJS-XFA`;
cssFontInfo.metrics = standardFontName.metrics || null;
glyphScaleFactors = standardFontName.factors || null;
fontFile = await this.fetchStandardFontData(standardFontName.name);
isInternalFont = !!fontFile;
// We're using a substitution font but for example widths (if any)
// are related to the glyph positions in the font.
// So we overwrite everything here to be sure that widths are
// correct.
baseDict = dict = getXfaFontDict(fontName.name);
composite = true;
}
} else if (!isType3Font) {
const standardFontName = getStandardFontName(fontName.name);
if (standardFontName) {
fontFile = await this.fetchStandardFontData(standardFontName);
isInternalFont = !!fontFile;
}
if (!isInternalFont && this.options.useSystemFonts) {
systemFontInfo = getFontSubstitution(
this.systemFontCache,
this.idFactory,
this.options.standardFontDataUrl,
fontName.name,
standardFontName,
type
);
}
}
const fontMatrix = lookupMatrix(
dict.getArray("FontMatrix"),
FONT_IDENTITY_MATRIX
);
const bbox = lookupNormalRect(
descriptor.getArray("FontBBox") || dict.getArray("FontBBox"),
undefined
);
let ascent = descriptor.get("Ascent");
if (typeof ascent !== "number") {
ascent = undefined;
}
let descent = descriptor.get("Descent");
if (typeof descent !== "number") {
descent = undefined;
}
let xHeight = descriptor.get("XHeight");
if (typeof xHeight !== "number") {
xHeight = 0;
}
let capHeight = descriptor.get("CapHeight");
if (typeof capHeight !== "number") {
capHeight = 0;
}
let flags = descriptor.get("Flags");
if (!Number.isInteger(flags)) {
flags = 0;
}
let italicAngle = descriptor.get("ItalicAngle");
if (typeof italicAngle !== "number") {
italicAngle = 0;
}
const properties = {
type,
name: fontName.name,
subtype,
file: fontFile,
length1,
length2,
length3,
isInternalFont,
loadedName: baseDict.loadedName,
composite,
fixedPitch: false,
fontMatrix,
firstChar,
lastChar,
toUnicode,
bbox,
ascent,
descent,
xHeight,
capHeight,
flags,
italicAngle,
isType3Font,
cssFontInfo,
scaleFactors: glyphScaleFactors,
systemFontInfo,
};
if (composite) {
const cidEncoding = baseDict.get("Encoding");
if (cidEncoding instanceof Name) {
properties.cidEncoding = cidEncoding.name;
}
const cMap = await CMapFactory.create({
encoding: cidEncoding,
fetchBuiltInCMap: this._fetchBuiltInCMapBound,
useCMap: null,
});
properties.cMap = cMap;
properties.vertical = properties.cMap.vertical;
}
const newProperties = await this.extractDataStructures(dict, properties);
this.extractWidths(dict, descriptor, newProperties);
return new Font(fontName.name, fontFile, newProperties, this.options);
}