: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
throw new FormatError(`parseFDSelect: Unknown format "${format}".`);
if (fdSelect.length !== length) {
throw new FormatError("parseFDSelect: Invalid font data.");
return new CFFFDSelect(format, fdSelect);
this.strings = new CFFStrings();
this.globalSubrIndex = null;
if (this.charStrings.count >= 65535) {
warn("Not enough space in charstrings to duplicate first glyph.");
const glyphZero = this.charStrings.get(0);
this.charStrings.add(glyphZero);
this.fdSelect.fdSelect.push(this.fdSelect.fdSelect[0]);
if (id < 0 || id >= this.charStrings.count) {
const glyph = this.charStrings.get(id);
constructor(major, minor, hdrSize, offSize) {
if (index >= 0 && index <= NUM_STANDARD_CFF_STRINGS - 1) {
return CFFStandardStrings[index];
if (index - NUM_STANDARD_CFF_STRINGS <= this.strings.length) {
return this.strings[index - NUM_STANDARD_CFF_STRINGS];
return CFFStandardStrings[0];
let index = CFFStandardStrings.indexOf(str);
index = this.strings.indexOf(str);
return index + NUM_STANDARD_CFF_STRINGS;
this.strings.push(value);
return this.strings.length;
this.length += data.length;
this.length += data.length - this.objects[index].length;
this.objects[index] = data;
return this.objects[index];
return this.objects.length;
constructor(tables, strings) {
this.keyToNameMap = tables.keyToNameMap;
this.nameToKeyMap = tables.nameToKeyMap;
this.defaults = tables.defaults;
this.types = tables.types;
this.opcodes = tables.opcodes;
this.order = tables.order;
this.values = Object.create(null);
if (!(key in this.keyToNameMap)) {
if (value.length === 0) {
for (const val of value) {
warn(`Invalid CFFDict value: "${value}" for key "${key}".`);
const type = this.types[key];
if (type === "num" || type === "sid" || type === "offset") {
this.values[key] = value;
if (!(name in this.nameToKeyMap)) {
throw new FormatError(`Invalid dictionary name "${name}"`);
this.values[this.nameToKeyMap[name]] = value;
return this.nameToKeyMap[name] in this.values;
if (!(name in this.nameToKeyMap)) {
throw new FormatError(`Invalid dictionary name ${name}"`);
const key = this.nameToKeyMap[name];
if (!(key in this.values)) {
return this.defaults[key];
delete this.values[this.nameToKeyMap[name]];
static createTables(layout) {
for (const entry of layout) {
const key = Array.isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0];
tables.keyToNameMap[key] = entry[1];
tables.nameToKeyMap[entry[1]] = key;
tables.types[key] = entry[2];
tables.defaults[key] = entry[3];
tables.opcodes[key] = Array.isArray(entry[0]) ? entry[0] : [entry[0]];
const CFFTopDictLayout = [[[12, 30], "ROS", ["sid", "sid", "num"], null], [[12, 20], "SyntheticBase", "num", null], [0, "version", "sid", null], [1, "Notice", "sid", null], [[12, 0], "Copyright", "sid", null], [2, "FullName", "sid", null], [3, "FamilyName", "sid", null], [4, "Weight", "sid", null], [[12, 1], "isFixedPitch", "num", 0], [[12, 2], "ItalicAngle", "num", 0], [[12, 3], "UnderlinePosition", "num", -100], [[12, 4], "UnderlineThickness", "num", 50], [[12, 5], "PaintType", "num", 0], [[12, 6], "CharstringType", "num", 2], [[12, 7], "FontMatrix", ["num", "num", "num", "num", "num", "num"], [0.001, 0, 0, 0.001, 0, 0]], [13, "UniqueID", "num", null], [5, "FontBBox", ["num", "num", "num", "num"], [0, 0, 0, 0]], [[12, 8], "StrokeWidth", "num", 0], [14, "XUID", "array", null], [15, "charset", "offset", 0], [16, "Encoding", "offset", 0], [17, "CharStrings", "offset", 0], [18, "Private", ["offset", "offset"], null], [[12, 21], "PostScript", "sid", null], [[12, 22], "BaseFontName", "sid", null], [[12, 23], "BaseFontBlend", "delta", null], [[12, 31], "CIDFontVersion", "num", 0], [[12, 32], "CIDFontRevision", "num", 0], [[12, 33], "CIDFontType", "num", 0], [[12, 34], "CIDCount", "num", 8720], [[12, 35], "UIDBase", "num", null], [[12, 37], "FDSelect", "offset", null], [[12, 36], "FDArray", "offset", null], [[12, 38], "FontName", "sid", null]];
class CFFTopDict extends CFFDict {
return shadow(this, "tables", this.createTables(CFFTopDictLayout));
super(CFFTopDict.tables, strings);
const CFFPrivateDictLayout = [[6, "BlueValues", "delta", null], [7, "OtherBlues", "delta", null], [8, "FamilyBlues", "delta", null], [9, "FamilyOtherBlues", "delta", null], [[12, 9], "BlueScale", "num", 0.039625], [[12, 10], "BlueShift", "num", 7], [[12, 11], "BlueFuzz", "num", 1], [10, "StdHW", "num", null], [11, "StdVW", "num", null], [[12, 12], "StemSnapH", "delta", null], [[12, 13], "StemSnapV", "delta", null], [[12, 14], "ForceBold", "num", 0], [[12, 17], "LanguageGroup", "num", 0], [[12, 18], "ExpansionFactor", "num", 0.06], [[12, 19], "initialRandomSeed", "num", 0], [20, "defaultWidthX", "num", 0], [21, "nominalWidthX", "num", 0], [19, "Subrs", "offset", null]];
class CFFPrivateDict extends CFFDict {
return shadow(this, "tables", this.createTables(CFFPrivateDictLayout));
super(CFFPrivateDict.tables, strings);
const CFFCharsetPredefinedTypes = {
constructor(predefined, format, charset, raw) {
this.predefined = predefined;
constructor(predefined, format, encoding, raw) {
this.predefined = predefined;
this.encoding = encoding;
constructor(format, fdSelect) {
this.fdSelect = fdSelect;
if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) {
return this.fdSelect[glyphIndex];
this.offsets = Object.create(null);
return key in this.offsets;
if (key in this.offsets) {
throw new FormatError(`Already tracking location of ${key}`);
this.offsets[key] = location;
for (const key in this.offsets) {
this.offsets[key] += value;
setEntryLocation(key, values, output) {
if (!(key in this.offsets)) {
throw new FormatError(`Not tracking location of ${key}`);
const data = output.data;
const dataOffset = this.offsets[key];
for (let i = 0, ii = values.length; i < ii; ++i) {
const offset0 = i * size + dataOffset;
const offset1 = offset0 + 1;
const offset2 = offset0 + 2;
const offset3 = offset0 + 3;
const offset4 = offset0 + 4;
if (data[offset0] !== 0x1d || data[offset1] !== 0 || data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) {
throw new FormatError("writing to an offset that is not empty");
data[offset1] = value >> 24 & 0xff;
data[offset2] = value >> 16 & 0xff;
data[offset3] = value >> 8 & 0xff;
data[offset4] = value & 0xff;
this.data = this.data.concat(data);
this.length = this.data.length;
const header = this.compileHeader(cff.header);
const nameIndex = this.compileNameIndex(cff.names);
if (cff.topDict.hasName("FontMatrix")) {
const base = cff.topDict.getByName("FontMatrix");
cff.topDict.removeByName("FontMatrix");
for (const subDict of cff.fdArray) {
let matrix = base.slice(0);
if (subDict.hasName("FontMatrix")) {
matrix = Util.transform(matrix, subDict.getByName("FontMatrix"));
subDict.setByName("FontMatrix", matrix);
const xuid = cff.topDict.getByName("XUID");
cff.topDict.removeByName("XUID");
cff.topDict.setByName("charset", 0);
let compiled = this.compileTopDicts([cff.topDict], output.length, cff.isCIDFont);
output.add(compiled.output);
const topDictTracker = compiled.trackers[0];
const stringIndex = this.compileStringIndex(cff.strings.strings);
const globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
output.add(globalSubrIndex);
if (cff.encoding && cff.topDict.hasName("Encoding")) {
if (cff.encoding.predefined) {
topDictTracker.setEntryLocation("Encoding", [cff.encoding.format], output);
const encoding = this.compileEncoding(cff.encoding);
topDictTracker.setEntryLocation("Encoding", [output.length], output);
const charset = this.compileCharset(cff.charset, cff.charStrings.count, cff.strings, cff.isCIDFont);
topDictTracker.setEntryLocation("charset", [output.length], output);
const charStrings = this.compileCharStrings(cff.charStrings);
topDictTracker.setEntryLocation("CharStrings", [output.length], output);
topDictTracker.setEntryLocation("FDSelect", [output.length], output);
const fdSelect = this.compileFDSelect(cff.fdSelect);
compiled = this.compileTopDicts(cff.fdArray, output.length, true);
topDictTracker.setEntryLocation("FDArray", [output.length], output);
output.add(compiled.output);
const fontDictTrackers = compiled.trackers;
this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
this.compilePrivateDicts([cff.topDict], [topDictTracker], output);
if (Number.isInteger(value)) {
return this.encodeInteger(value);
return this.encodeFloat(value);
static get EncodeFloatRegExp() {
return shadow(this, "EncodeFloatRegExp", /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/);
let value = num.toString();
const m = CFFCompiler.EncodeFloatRegExp.exec(value);
const epsilon = parseFloat("1e" + ((m[2] ? +m[2] : 0) + m[1].length));
value = (Math.round(num * epsilon) / epsilon).toString();
for (i = 0, ii = value.length; i < ii; ++i) {
nibbles += value[++i] === "-" ? "c" : "b";
nibbles += nibbles.length & 1 ? "f" : "ff";
for (i = 0, ii = nibbles.length; i < ii; i += 2) {
out.push(parseInt(nibbles.substring(i, i + 2), 16));
if (value >= -107 && value <= 107) {
} else if (value >= 108 && value <= 1131) {
code = [(value >> 8) + 247, value & 0xff];
} else if (value >= -1131 && value <= -108) {
code = [(value >> 8) + 251, value & 0xff];
} else if (value >= -32768 && value <= 32767) {
code = [0x1c, value >> 8 & 0xff, value & 0xff];
code = [0x1d, value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff];
return [header.major, header.minor, 4, header.offSize];
compileNameIndex(names) {
const nameIndex = new CFFIndex();
for (const name of names) {
const length = Math.min(name.length, 127);
let sanitizedName = new Array(length);
for (let j = 0; j < length; j++) {
if (char < "!" || char > "~" || char === "[" || char === "]" || char === "(" || char === ")" || char === "{" || char === "}" || char === "<" || char === ">" || char === "/" || char === "%") {
sanitizedName = sanitizedName.join("");
if (sanitizedName === "") {
sanitizedName = "Bad_Font_Name";
nameIndex.add(stringToBytes(sanitizedName));
return this.compileIndex(nameIndex);
compileTopDicts(dicts, length, removeCidKeys) {
const fontDictTrackers = [];
let fdArrayIndex = new CFFIndex();
for (const fontDict of dicts) {
fontDict.removeByName("CIDFontVersion");
fontDict.removeByName("CIDFontRevision");
fontDict.removeByName("CIDFontType");
fontDict.removeByName("CIDCount");
fontDict.removeByName("UIDBase");
const fontDictTracker = new CFFOffsetTracker();
const fontDictData = this.compileDict(fontDict, fontDictTracker);
fontDictTrackers.push(fontDictTracker);
fdArrayIndex.add(fontDictData);
fontDictTracker.offset(length);
fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers);
trackers: fontDictTrackers,
compilePrivateDicts(dicts, trackers, output) {
for (let i = 0, ii = dicts.length; i < ii; ++i) {
const fontDict = dicts[i];
const privateDict = fontDict.privateDict;
if (!privateDict || !fontDict.hasName("Private")) {
throw new FormatError("There must be a private dictionary.");
const privateDictTracker = new CFFOffsetTracker();
const privateDictData = this.compileDict(privateDict, privateDictTracker);
let outputLength = output.length;
privateDictTracker.offset(outputLength);
if (!privateDictData.length) {
trackers[i].setEntryLocation("Private", [privateDictData.length, outputLength], output);
output.add(privateDictData);
if (privateDict.subrsIndex && privateDict.hasName("Subrs")) {
const subrs = this.compileIndex(privateDict.subrsIndex);
privateDictTracker.setEntryLocation("Subrs", [privateDictData.length], output);
compileDict(dict, offsetTracker) {
for (const key of dict.order) {
if (!(key in dict.values)) {
let values = dict.values[key];
let types = dict.types[key];
if (!Array.isArray(types)) {
if (!Array.isArray(values)) {
if (values.length === 0) {
for (let j = 0, jj = types.length; j < jj; ++j) {
out.push(...this.encodeNumber(value));