: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
foldTTTable(table, content);
function checkInvalidFunctions(ttContext, maxFunctionDefs) {
if (ttContext.tooComplexToFollowFunctions) {
if (ttContext.functionsDefined.length > maxFunctionDefs) {
warn("TT: more functions defined than expected");
ttContext.hintsValid = false;
for (let j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
if (j > maxFunctionDefs) {
warn("TT: invalid function id: " + j);
ttContext.hintsValid = false;
if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {
warn("TT: undefined function: " + j);
ttContext.hintsValid = false;
function foldTTTable(table, content) {
if (content.length > 1) {
for (j = 0, jj = content.length; j < jj; j++) {
newLength += content[j].length;
newLength = newLength + 3 & ~3;
const result = new Uint8Array(newLength);
for (j = 0, jj = content.length; j < jj; j++) {
result.set(content[j], pos);
pos += content[j].length;
table.length = newLength;
function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) {
functionsStackDeltas: [],
tooComplexToFollowFunctions: false,
sanitizeTTProgram(fpgm, ttContext);
sanitizeTTProgram(prep, ttContext);
checkInvalidFunctions(ttContext, maxFunctionDefs);
if (cvt && cvt.length & 1) {
const cvtData = new Uint8Array(cvt.length + 1);
return ttContext.hintsValid;
font = new Stream(new Uint8Array(font.getBytes()));
if (isTrueTypeCollectionFile(font)) {
const ttcData = readTrueTypeCollectionData(font, this.name);
header = readOpenTypeHeader(font);
tables = readTables(font, header.numTables);
const isTrueType = !tables["CFF "];
const isComposite = properties.composite && (properties.cidToGidMap?.length > 0 || !(properties.cMap instanceof IdentityCMap));
if (header.version === "OTTO" && !isComposite || !tables.head || !tables.hhea || !tables.maxp || !tables.post) {
cffFile = new Stream(tables["CFF "].data);
cff = new CFFFont(cffFile, properties);
adjustWidths(properties);
return this.convert(name, cff, properties);
throw new FormatError('Required "loca" table is not found');
warn('Required "glyf" table is not found -- trying to recover.');
throw new FormatError('Required "maxp" table is not found');
font.pos = (font.start || 0) + tables.maxp.offset;
let version = font.getInt32();
const numGlyphs = font.getUint16();
if (version !== 0x00010000 && version !== 0x00005000) {
if (tables.maxp.length === 6) {
} else if (tables.maxp.length >= 32) {
throw new FormatError(`"maxp" table has a wrong version number`);
writeUint32(tables.maxp.data, 0, version);
if (properties.scaleFactors?.length === numGlyphs && isTrueType) {
const isGlyphLocationsLong = int16(tables.head.data[50], tables.head.data[51]);
const glyphs = new GlyfTable({
glyfTable: tables.glyf.data,
locaTable: tables.loca.data,
glyphs.scale(scaleFactors);
if (isLocationLong !== !!isGlyphLocationsLong) {
tables.head.data[50] = 0;
tables.head.data[51] = isLocationLong ? 1 : 0;
const metrics = tables.hmtx.data;
for (let i = 0; i < numGlyphs; i++) {
const advanceWidth = Math.round(scaleFactors[i] * int16(metrics[j], metrics[j + 1]));
metrics[j] = advanceWidth >> 8 & 0xff;
metrics[j + 1] = advanceWidth & 0xff;
const lsb = Math.round(scaleFactors[i] * signedInt16(metrics[j + 2], metrics[j + 3]));
writeSignedInt16(metrics, j + 2, lsb);
let numGlyphsOut = numGlyphs + 1;
let dupFirstEntry = true;
if (numGlyphsOut > 0xffff) {
numGlyphsOut = numGlyphs;
warn("Not enough space in glyfs to duplicate first glyph.");
let maxSizeOfInstructions = 0;
if (version >= 0x00010000 && tables.maxp.length >= 32) {
const maxZones = font.getUint16();
tables.maxp.data[14] = 0;
tables.maxp.data[15] = 2;
maxFunctionDefs = font.getUint16();
maxSizeOfInstructions = font.getUint16();
tables.maxp.data[4] = numGlyphsOut >> 8;
tables.maxp.data[5] = numGlyphsOut & 255;
const hintsValid = sanitizeTTPrograms(tables.fpgm, tables.prep, tables["cvt "], maxFunctionDefs);
sanitizeMetrics(font, tables.hhea, tables.hmtx, tables.head, numGlyphsOut, dupFirstEntry);
throw new FormatError('Required "head" table is not found');
sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0);
let missingGlyphs = Object.create(null);
const isGlyphLocationsLong = int16(tables.head.data[50], tables.head.data[51]);
const glyphsInfo = sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry, maxSizeOfInstructions);
missingGlyphs = glyphsInfo.missingGlyphs;
if (version >= 0x00010000 && tables.maxp.length >= 32) {
tables.maxp.data[26] = glyphsInfo.maxSizeOfInstructions >> 8;
tables.maxp.data[27] = glyphsInfo.maxSizeOfInstructions & 255;
throw new FormatError('Required "hhea" table is not found');
if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) {
tables.hhea.data[10] = 0xff;
tables.hhea.data[11] = 0xff;
const metricsOverride = {
unitsPerEm: int16(tables.head.data[18], tables.head.data[19]),
yMax: signedInt16(tables.head.data[42], tables.head.data[43]),
yMin: signedInt16(tables.head.data[38], tables.head.data[39]),
ascent: signedInt16(tables.hhea.data[4], tables.hhea.data[5]),
descent: signedInt16(tables.hhea.data[6], tables.hhea.data[7]),
lineGap: signedInt16(tables.hhea.data[8], tables.hhea.data[9])
this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;
this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;
this.lineGap = metricsOverride.lineGap / metricsOverride.unitsPerEm;
if (this.cssFontInfo?.lineHeight) {
this.lineHeight = this.cssFontInfo.metrics.lineHeight;
this.lineGap = this.cssFontInfo.metrics.lineGap;
this.lineHeight = this.ascent - this.descent + this.lineGap;
readPostScriptTable(tables.post, properties, numGlyphs);
data: createPostTable(properties)
const charCodeToGlyphId = Object.create(null);
function hasGlyph(glyphId) {
return !missingGlyphs[glyphId];
if (properties.composite) {
const cidToGidMap = properties.cidToGidMap || [];
const isCidToGidMapEmpty = cidToGidMap.length === 0;
properties.cMap.forEach(function (charCode, cid) {
if (typeof cid === "string") {
cid = convertCidString(charCode, cid, true);
throw new FormatError("Max size of CID is 65,535");
if (isCidToGidMapEmpty) {
} else if (cidToGidMap[cid] !== undefined) {
glyphId = cidToGidMap[cid];
if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId)) {
charCodeToGlyphId[charCode] = glyphId;
const cmapTable = readCmapTable(tables.cmap, font, this.isSymbolicFont, properties.hasEncoding);
const cmapPlatformId = cmapTable.platformId;
const cmapEncodingId = cmapTable.encodingId;
const cmapMappings = cmapTable.mappings;
if (properties.hasEncoding && (properties.baseEncodingName === "MacRomanEncoding" || properties.baseEncodingName === "WinAnsiEncoding")) {
baseEncoding = getEncoding(properties.baseEncodingName);
if (properties.hasEncoding && !this.isSymbolicFont && (cmapPlatformId === 3 && cmapEncodingId === 1 || cmapPlatformId === 1 && cmapEncodingId === 0)) {
const glyphsUnicodeMap = getGlyphsUnicode();
for (let charCode = 0; charCode < 256; charCode++) {
if (this.differences[charCode] !== undefined) {
glyphName = this.differences[charCode];
} else if (baseEncoding.length && baseEncoding[charCode] !== "") {
glyphName = baseEncoding[charCode];
glyphName = StandardEncoding[charCode];
const standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
if (cmapPlatformId === 3 && cmapEncodingId === 1) {
unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
} else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
if (unicodeOrCharCode === undefined) {
if (!properties.glyphNames && properties.hasIncludedToUnicodeMap && !(this.toUnicode instanceof IdentityToUnicodeMap)) {
const unicode = this.toUnicode.get(charCode);
unicodeOrCharCode = unicode.codePointAt(0);
if (unicodeOrCharCode === undefined) {
for (const mapping of cmapMappings) {
if (mapping.charCode !== unicodeOrCharCode) {
charCodeToGlyphId[charCode] = mapping.glyphId;
} else if (cmapPlatformId === 0) {
for (const mapping of cmapMappings) {
charCodeToGlyphId[mapping.charCode] = mapping.glyphId;
} else if (cmapPlatformId === 3 && cmapEncodingId === 0) {
for (const mapping of cmapMappings) {
let charCode = mapping.charCode;
if (charCode >= 0xf000 && charCode <= 0xf0ff) {
charCodeToGlyphId[charCode] = mapping.glyphId;
for (const mapping of cmapMappings) {
charCodeToGlyphId[mapping.charCode] = mapping.glyphId;
if (properties.glyphNames && (baseEncoding.length || this.differences.length)) {
for (let i = 0; i < 256; ++i) {
if (!forcePostTable && charCodeToGlyphId[i] !== undefined) {
const glyphName = this.differences[i] || baseEncoding[i];
const glyphId = properties.glyphNames.indexOf(glyphName);
if (glyphId > 0 && hasGlyph(glyphId)) {
charCodeToGlyphId[i] = glyphId;
if (charCodeToGlyphId.length === 0) {
charCodeToGlyphId[0] = 0;
let glyphZeroId = numGlyphsOut - 1;
if (!properties.cssFontInfo) {
const newMapping = adjustMapping(charCodeToGlyphId, hasGlyph, glyphZeroId, this.toUnicode);
this.toFontChar = newMapping.toFontChar;
data: createCmapTable(newMapping.charCodeToGlyphId, newMapping.toUnicodeExtraMap, numGlyphsOut)
if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) {
data: createOS2Table(properties, newMapping.charCodeToGlyphId, metricsOverride)
cffFile = new Stream(tables["CFF "].data);
const parser = new CFFParser(cffFile, properties, SEAC_ANALYSIS_ENABLED);
cff.duplicateFirstGlyph();
const compiler = new CFFCompiler(cff);
tables["CFF "].data = compiler.compile();
warn("Failed to compile font " + properties.loadedName);
data: createNameTable(this.name)
const [namePrototype, nameRecords] = readNameTable(tables.name);
tables.name.data = createNameTable(name, namePrototype);
this.psName = namePrototype[0][6] || null;
if (!properties.composite) {
adjustTrueTypeToUnicode(properties, this.isSymbolicFont, nameRecords);
const builder = new OpenTypeFileBuilder(header.version);
for (const tableTag in tables) {
builder.addTable(tableTag, tables[tableTag].data);
return builder.toArray();
convert(fontName, font, properties) {
properties.fixedPitch = false;
if (properties.builtInEncoding) {
adjustType1ToUnicode(properties, properties.builtInEncoding);
if (font instanceof CFFFont) {
glyphZeroId = font.numGlyphs - 1;
const mapping = font.getGlyphMapping(properties);
let newCharCodeToGlyphId = mapping;
let toUnicodeExtraMap = null;
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) {
for (const charCode in charCodeToGlyphId) {
if (glyphId === charCodeToGlyphId[charCode]) {
(charCodes ||= []).push(charCode | 0);
function createCharCode(charCodeToGlyphId, glyphId) {
for (const charCode in charCodeToGlyphId) {
if (glyphId === charCodeToGlyphId[charCode]) {
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) {
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) {
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);
for (const charCode of charCodes) {
const charCodeToGlyphId = newMapping.charCodeToGlyphId;
const baseFontCharCode = createCharCode(charCodeToGlyphId, baseGlyphId);
const accentFontCharCode = createCharCode(charCodeToGlyphId, accentGlyphId);
properties.seacMap = seacMap;
const unitsPerEm = 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];
const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F");
builder.addTable("CFF ", font.data);
builder.addTable("OS/2", createOS2Table(properties, newCharCodeToGlyphId));
builder.addTable("cmap", createCmapTable(newCharCodeToGlyphId, toUnicodeExtraMap, numGlyphs));
builder.addTable("head", "\x00\x01\x00\x00" + "\x00\x00\x10\x00" + "\x00\x00\x00\x00" + "\x5F\x0F\x3C\xF5" + "\x00\x00" + safeString16(unitsPerEm) + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + "\x00\x00" + safeString16(properties.descent) + "\x0F\xFF" + safeString16(properties.ascent) + string16(properties.italicAngle ? 2 : 0) + "\x00\x11" + "\x00\x00" + "\x00\x00" + "\x00\x00");
builder.addTable("hhea", "\x00\x01\x00\x00" + safeString16(properties.ascent) + safeString16(properties.descent) + "\x00\x00" + "\xFF\xFF" + "\x00\x00" + "\x00\x00" + "\x00\x00" + safeString16(properties.capHeight) + safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + string16(numGlyphs));
builder.addTable("hmtx", function fontFieldsHmtx() {
const charstrings = font.charstrings;
const cffWidths = font.cff ? font.cff.widths : null;
let hmtx = "\x00\x00\x00\x00";
for (let i = 1, ii = numGlyphs; i < ii; i++) {
const charstring = charstrings[i - 1];
width = "width" in charstring ? charstring.width : 0;
width = Math.ceil(cffWidths[i] || 0);
hmtx += string16(width) + string16(0);
builder.addTable("maxp", "\x00\x00\x50\x00" + string16(numGlyphs));
builder.addTable("name", createNameTable(fontName));
builder.addTable("post", createPostTable(properties));
return builder.toArray();
_charToGlyph(charcode, isSpace = false) {
let glyph = this._glyphCache[charcode];
if (glyph?.isSpace === isSpace) {
let fontCharCode, width, operatorListId;
let widthCode = charcode;
if (this.cMap?.contains(charcode)) {