in src/core/fonts.js [3116:3314]
convert(fontName, font, properties) {
// TODO: Check the charstring widths to determine this.
properties.fixedPitch = false;
if (properties.builtInEncoding) {
// For Type1 fonts that do not include either `ToUnicode` or `Encoding`
// data, attempt to use the `builtInEncoding` to improve text selection.
adjustType1ToUnicode(properties, properties.builtInEncoding);
}
// Type 1 fonts have a notdef inserted at the beginning, so glyph 0
// becomes glyph 1. In a CFF font glyph 0 is appended to the end of the
// char strings.
let glyphZeroId = 1;
if (font instanceof CFFFont) {
glyphZeroId = font.numGlyphs - 1;
}
const mapping = font.getGlyphMapping(properties);
let newMapping = null;
let newCharCodeToGlyphId = mapping;
let toUnicodeExtraMap = null;
// 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 use area.
if (!properties.cssFontInfo) {
newMapping = adjustMapping(
mapping,
font.hasGlyphId.bind(font),
glyphZeroId,
this.toUnicode
);
this.toFontChar = newMapping.toFontChar;
newCharCodeToGlyphId = newMapping.charCodeToGlyphId;
toUnicodeExtraMap = newMapping.toUnicodeExtraMap;
}
const numGlyphs = font.numGlyphs;
function getCharCodes(charCodeToGlyphId, glyphId) {
let charCodes = null;
for (const charCode in charCodeToGlyphId) {
if (glyphId === charCodeToGlyphId[charCode]) {
(charCodes ||= []).push(charCode | 0);
}
}
return charCodes;
}
function createCharCode(charCodeToGlyphId, glyphId) {
for (const charCode in charCodeToGlyphId) {
if (glyphId === charCodeToGlyphId[charCode]) {
return charCode | 0;
}
}
newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] =
glyphId;
return newMapping.nextAvailableFontCharCode++;
}
const seacs = font.seacs;
if (newMapping && SEAC_ANALYSIS_ENABLED && seacs?.length) {
const matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
const charset = font.getCharset();
const seacMap = Object.create(null);
for (let glyphId in seacs) {
glyphId |= 0;
const seac = seacs[glyphId];
const baseGlyphName = StandardEncoding[seac[2]];
const accentGlyphName = StandardEncoding[seac[3]];
const baseGlyphId = charset.indexOf(baseGlyphName);
const accentGlyphId = charset.indexOf(accentGlyphName);
if (baseGlyphId < 0 || accentGlyphId < 0) {
continue;
}
const accentOffset = {
x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5],
};
const charCodes = getCharCodes(mapping, glyphId);
if (!charCodes) {
// There's no point in mapping it if the char code was never mapped
// to begin with.
continue;
}
for (const charCode of charCodes) {
// Find a fontCharCode that maps to the base and accent glyphs.
// If one doesn't exists, create it.
const charCodeToGlyphId = newMapping.charCodeToGlyphId;
const baseFontCharCode = createCharCode(
charCodeToGlyphId,
baseGlyphId
);
const accentFontCharCode = createCharCode(
charCodeToGlyphId,
accentGlyphId
);
seacMap[charCode] = {
baseFontCharCode,
accentFontCharCode,
accentOffset,
};
}
}
properties.seacMap = seacMap;
}
const unitsPerEm = properties.fontMatrix
? 1 / Math.max(...properties.fontMatrix.slice(0, 4).map(Math.abs))
: 1000;
const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F");
// PostScript Font Program
builder.addTable("CFF ", font.data);
// OS/2 and Windows Specific metrics
builder.addTable("OS/2", createOS2Table(properties, newCharCodeToGlyphId));
// Character to glyphs mapping
builder.addTable(
"cmap",
createCmapTable(newCharCodeToGlyphId, toUnicodeExtraMap, numGlyphs)
);
// Font header
builder.addTable(
"head",
"\x00\x01\x00\x00" + // Version number
"\x00\x00\x10\x00" + // fontRevision
"\x00\x00\x00\x00" + // checksumAdjustement
"\x5F\x0F\x3C\xF5" + // magicNumber
"\x00\x00" + // Flags
safeString16(unitsPerEm) + // unitsPerEM
"\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // creation date
"\x00\x00\x00\x00\x9e\x0b\x7e\x27" + // modifification date
"\x00\x00" + // xMin
safeString16(properties.descent) + // yMin
"\x0F\xFF" + // xMax
safeString16(properties.ascent) + // yMax
string16(properties.italicAngle ? 2 : 0) + // macStyle
"\x00\x11" + // lowestRecPPEM
"\x00\x00" + // fontDirectionHint
"\x00\x00" + // indexToLocFormat
"\x00\x00"
); // glyphDataFormat
// Horizontal header
builder.addTable(
"hhea",
"\x00\x01\x00\x00" + // Version number
safeString16(properties.ascent) + // Typographic Ascent
safeString16(properties.descent) + // Typographic Descent
"\x00\x00" + // Line Gap
"\xFF\xFF" + // advanceWidthMax
"\x00\x00" + // minLeftSidebearing
"\x00\x00" + // minRightSidebearing
"\x00\x00" + // xMaxExtent
safeString16(properties.capHeight) + // caretSlopeRise
safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun
"\x00\x00" + // caretOffset
"\x00\x00" + // -reserved-
"\x00\x00" + // -reserved-
"\x00\x00" + // -reserved-
"\x00\x00" + // -reserved-
"\x00\x00" + // metricDataFormat
string16(numGlyphs)
); // Number of HMetrics
// Horizontal metrics
builder.addTable(
"hmtx",
(function fontFieldsHmtx() {
const charstrings = font.charstrings;
const cffWidths = font.cff ? font.cff.widths : null;
let hmtx = "\x00\x00\x00\x00"; // Fake .notdef
for (let i = 1, ii = numGlyphs; i < ii; i++) {
let width = 0;
if (charstrings) {
const charstring = charstrings[i - 1];
width = "width" in charstring ? charstring.width : 0;
} else if (cffWidths) {
width = Math.ceil(cffWidths[i] || 0);
}
hmtx += string16(width) + string16(0);
}
return hmtx;
})()
);
// Maximum profile
builder.addTable(
"maxp",
"\x00\x00\x50\x00" + string16(numGlyphs) // Version number
); // Num of glyphs
// Naming tables
builder.addTable("name", createNameTable(fontName));
// PostScript information
builder.addTable("post", createPostTable(properties));
return builder.toArray();
}