: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
dest.set(glyf, destStart);
glyphProfile.length = glyf.length;
for (i = 0; i < contoursCount; i++) {
const endPoint = glyf[j] << 8 | glyf[j + 1];
flagsCount = endPoint + 1;
const instructionsStart = j;
const instructionsLength = glyf[j] << 8 | glyf[j + 1];
glyphProfile.sizeOfInstructions = instructionsLength;
j += 2 + instructionsLength;
const instructionsEnd = j;
let coordinatesLength = 0;
for (i = 0; i < flagsCount; i++) {
glyf[j - 1] = flag & 0x3f;
const xyLength = xLength + yLength;
coordinatesLength += xyLength;
const repeat = glyf[j++];
coordinatesLength += repeat * xyLength;
if (coordinatesLength === 0) {
let glyphDataLength = j + coordinatesLength;
if (glyphDataLength > glyf.length) {
if (!hintsValid && instructionsLength > 0) {
dest.set(glyf.subarray(0, instructionsStart), destStart);
dest.set([0, 0], destStart + instructionsStart);
dest.set(glyf.subarray(instructionsEnd, glyphDataLength), destStart + instructionsStart + 2);
glyphDataLength -= instructionsLength;
if (glyf.length - glyphDataLength > 3) {
glyphDataLength = glyphDataLength + 3 & ~3;
glyphProfile.length = glyphDataLength;
if (glyf.length - glyphDataLength > 3) {
glyphDataLength = glyphDataLength + 3 & ~3;
dest.set(glyf.subarray(0, glyphDataLength), destStart);
glyphProfile.length = glyphDataLength;
dest.set(glyf, destStart);
glyphProfile.length = glyf.length;
function sanitizeHead(head, numGlyphs, locaLength) {
const version = int32(data[0], data[1], data[2], data[3]);
if (version >> 16 !== 1) {
info("Attempting to fix invalid version in head table: " + version);
const indexToLocFormat = int16(data[50], data[51]);
if (indexToLocFormat < 0 || indexToLocFormat > 1) {
info("Attempting to fix invalid indexToLocFormat in head table: " + indexToLocFormat);
const numGlyphsPlusOne = numGlyphs + 1;
if (locaLength === numGlyphsPlusOne << 1) {
} else if (locaLength === numGlyphsPlusOne << 2) {
throw new FormatError("Could not fix indexToLocFormat: " + indexToLocFormat);
function sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry, maxSizeOfInstructions) {
let itemSize, itemDecode, itemEncode;
if (isGlyphLocationsLong) {
itemDecode = function fontItemDecodeLong(data, offset) {
return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
itemEncode = function fontItemEncodeLong(data, offset, value) {
data[offset] = value >>> 24 & 0xff;
data[offset + 1] = value >> 16 & 0xff;
data[offset + 2] = value >> 8 & 0xff;
data[offset + 3] = value & 0xff;
itemDecode = function fontItemDecode(data, offset) {
return data[offset] << 9 | data[offset + 1] << 1;
itemEncode = function fontItemEncode(data, offset, value) {
data[offset] = value >> 9 & 0xff;
data[offset + 1] = value >> 1 & 0xff;
const numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs;
const locaDataSize = itemSize * (1 + numGlyphsOut);
const locaData = new Uint8Array(locaDataSize);
locaData.set(loca.data.subarray(0, locaDataSize));
const oldGlyfData = glyf.data;
const oldGlyfDataLength = oldGlyfData.length;
const newGlyfData = new Uint8Array(oldGlyfDataLength);
for (i = 0, j = 0; i < numGlyphs + 1; i++, j += itemSize) {
let offset = itemDecode(locaData, j);
if (offset > oldGlyfDataLength) {
offset = oldGlyfDataLength;
locaEntries.sort((a, b) => a.offset - b.offset);
for (i = 0; i < numGlyphs; i++) {
locaEntries[i].endOffset = locaEntries[i + 1].offset;
locaEntries.sort((a, b) => a.index - b.index);
for (i = 0; i < numGlyphs; i++) {
if (offset !== 0 || endOffset !== 0) {
const nextOffset = locaEntries[i + 1].offset;
locaEntries[i].endOffset = nextOffset;
const last = locaEntries.at(-2);
if (last.offset !== 0 && last.endOffset === 0) {
last.endOffset = oldGlyfDataLength;
const missingGlyphs = Object.create(null);
itemEncode(locaData, 0, writeOffset);
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
const glyphProfile = sanitizeGlyph(oldGlyfData, locaEntries[i].offset, locaEntries[i].endOffset, newGlyfData, writeOffset, hintsValid);
const newLength = glyphProfile.length;
if (glyphProfile.sizeOfInstructions > maxSizeOfInstructions) {
maxSizeOfInstructions = glyphProfile.sizeOfInstructions;
writeOffset += newLength;
itemEncode(locaData, j, writeOffset);
const simpleGlyph = new Uint8Array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
for (i = 0, j = itemSize; i < numGlyphsOut; i++, j += itemSize) {
itemEncode(locaData, j, simpleGlyph.length);
} else if (dupFirstEntry) {
const firstEntryLength = itemDecode(locaData, itemSize);
if (newGlyfData.length > firstEntryLength + writeOffset) {
glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset);
glyf.data = new Uint8Array(firstEntryLength + writeOffset);
glyf.data.set(newGlyfData.subarray(0, writeOffset));
glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset);
itemEncode(loca.data, locaData.length - itemSize, writeOffset + firstEntryLength);
glyf.data = newGlyfData.subarray(0, writeOffset);
function readPostScriptTable(post, propertiesObj, maxpNumGlyphs) {
const start = (font.start || 0) + post.offset;
const length = post.length,
const version = font.getInt32();
glyphNames = MacStandardGlyphOrdering;
const numGlyphs = font.getUint16();
if (numGlyphs !== maxpNumGlyphs) {
const glyphNameIndexes = [];
for (i = 0; i < numGlyphs; ++i) {
const index = font.getUint16();
glyphNameIndexes.push(index);
const stringLength = font.getByte();
strBuf.length = stringLength;
for (i = 0; i < stringLength; ++i) {
strBuf[i] = String.fromCharCode(font.getByte());
customNames.push(strBuf.join(""));
for (i = 0; i < numGlyphs; ++i) {
const j = glyphNameIndexes[i];
glyphNames.push(MacStandardGlyphOrdering[j]);
glyphNames.push(customNames[j - 258]);
warn("Unknown/unsupported post table version " + version);
if (propertiesObj.defaultEncoding) {
glyphNames = propertiesObj.defaultEncoding;
propertiesObj.glyphNames = glyphNames;
function readNameTable(nameTable) {
const start = (font.start || 0) + nameTable.offset;
const length = nameTable.length,
const format = font.getUint16();
const FORMAT_0_HEADER_LENGTH = 6;
if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) {
const numRecords = font.getUint16();
const stringsStart = font.getUint16();
const NAME_RECORD_LENGTH = 12;
for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) {
platform: font.getUint16(),
encoding: font.getUint16(),
language: font.getUint16(),
length: font.getUint16(),
if (isMacNameRecord(r) || isWinNameRecord(r)) {
for (i = 0, ii = records.length; i < ii; i++) {
const record = records[i];
if (record.length <= 0) {
const pos = start + stringsStart + record.offset;
if (pos + record.length > end) {
const nameIndex = record.name;
for (let j = 0, jj = record.length; j < jj; j += 2) {
str += String.fromCharCode(font.getUint16());
names[1][nameIndex] = str;
names[0][nameIndex] = font.getString(record.length);
const TTOpsStackDeltas = [0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1, 1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1, 0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2, 0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1, -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
function sanitizeTTProgram(table, ttContext) {
const functionsCalled = [];
let tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions;
for (let ii = data.length; i < ii;) {
for (j = 0; j < n; j++) {
} else if (op === 0x41) {
for (j = 0; j < n; j++) {
stack.push(b << 8 | data[i++]);
} else if ((op & 0xf8) === 0xb0) {
for (j = 0; j < n; j++) {
} else if ((op & 0xf8) === 0xb8) {
for (j = 0; j < n; j++) {
stack.push(b << 8 | data[i++]);
} else if (op === 0x2b && !tooComplexToFollowFunctions) {
if (!inFDEF && !inELSE) {
info("TT: CALL empty stack (or invalid entry).");
ttContext.functionsUsed[funcId] = true;
if (funcId in ttContext.functionsStackDeltas) {
const newStackLength = stack.length + ttContext.functionsStackDeltas[funcId];
if (newStackLength < 0) {
warn("TT: CALL invalid functions stack delta.");
ttContext.hintsValid = false;
stack.length = newStackLength;
} else if (funcId in ttContext.functionsDefined && !functionsCalled.includes(funcId)) {
stackTop: stack.length - 1
functionsCalled.push(funcId);
pc = ttContext.functionsDefined[funcId];
warn("TT: CALL non-existent function");
ttContext.hintsValid = false;
} else if (op === 0x2c && !tooComplexToFollowFunctions) {
warn("TT: nested FDEFs not allowed");
tooComplexToFollowFunctions = true;
ttContext.functionsDefined[funcId] = {
} else if (op === 0x2d) {
warn("TT: ENDF bad stack");
ttContext.hintsValid = false;
funcId = functionsCalled.pop();
ttContext.functionsStackDeltas[funcId] = stack.length - pc.stackTop;
} else if (op === 0x89) {
warn("TT: nested IDEFs not allowed");
tooComplexToFollowFunctions = true;
} else if (op === 0x58) {
} else if (op === 0x1b) {
} else if (op === 0x59) {
if (inELSE === ifLevel) {
} else if (op === 0x1c) {
if (!inFDEF && !inELSE) {
const offset = stack.at(-1);
if (!inFDEF && !inELSE) {
stackDelta = TTOpsStackDeltas[op];
} else if (op >= 0xc0 && op <= 0xdf) {
if (op >= 0x71 && op <= 0x75) {
while (stackDelta < 0 && stack.length > 0) {
ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
content.push(new Uint8Array(i - data.length));
if (lastDeff > lastEndf) {
warn("TT: complementing a missing function tail");
content.push(new Uint8Array([0x22, 0x2d]));