: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
toFontChar[+charCode] = unicode;
function isMacNameRecord(r) {
return r.platform === 1 && r.encoding === 0 && r.language === 0;
function isWinNameRecord(r) {
return r.platform === 3 && r.encoding === 1 && r.language === 0x409;
function convertCidString(charCode, cid, shouldThrow = false) {
return cid.charCodeAt(0);
return cid.charCodeAt(0) << 8 | cid.charCodeAt(1);
const msg = `Unsupported CID string (charCode ${charCode}): "${cid}".`;
throw new FormatError(msg);
function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId, toUnicode) {
const newMap = Object.create(null);
const toUnicodeExtraMap = new Map();
const usedGlyphIds = new Set();
let privateUseAreaIndex = 0;
const privateUseOffetStart = PRIVATE_USE_AREAS[privateUseAreaIndex][0];
let nextAvailableFontCharCode = privateUseOffetStart;
let privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];
const isInPrivateArea = code => PRIVATE_USE_AREAS[0][0] <= code && code <= PRIVATE_USE_AREAS[0][1] || PRIVATE_USE_AREAS[1][0] <= code && code <= PRIVATE_USE_AREAS[1][1];
for (const originalCharCode in charCodeToGlyphId) {
let glyphId = charCodeToGlyphId[originalCharCode];
if (!hasGlyph(glyphId)) {
if (nextAvailableFontCharCode > privateUseOffetEnd) {
if (privateUseAreaIndex >= PRIVATE_USE_AREAS.length) {
warn("Ran out of space in font private use area.");
nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0];
privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];
const fontCharCode = nextAvailableFontCharCode++;
glyphId = newGlyphZeroId;
let unicode = toUnicode.get(originalCharCode);
if (typeof unicode === "string") {
unicode = unicode.codePointAt(0);
if (unicode && !isInPrivateArea(unicode) && !usedGlyphIds.has(glyphId)) {
toUnicodeExtraMap.set(unicode, glyphId);
usedGlyphIds.add(glyphId);
newMap[fontCharCode] = glyphId;
toFontChar[originalCharCode] = fontCharCode;
charCodeToGlyphId: newMap,
nextAvailableFontCharCode
function getRanges(glyphs, toUnicodeExtraMap, numGlyphs) {
for (const charCode in glyphs) {
if (glyphs[charCode] >= numGlyphs) {
fontCharCode: charCode | 0,
glyphId: glyphs[charCode]
for (const [unicode, glyphId] of toUnicodeExtraMap) {
if (glyphId >= numGlyphs) {
if (codes.length === 0) {
codes.sort(function fontGetRangesSort(a, b) {
return a.fontCharCode - b.fontCharCode;
const length = codes.length;
for (let n = 0; n < length;) {
const start = codes[n].fontCharCode;
const codeIndices = [codes[n].glyphId];
while (n < length && end + 1 === codes[n].fontCharCode) {
codeIndices.push(codes[n].glyphId);
ranges.push([start, end, codeIndices]);
function createCmapTable(glyphs, toUnicodeExtraMap, numGlyphs) {
const ranges = getRanges(glyphs, toUnicodeExtraMap, numGlyphs);
const numTables = ranges.at(-1)[1] > 0xffff ? 2 : 1;
let cmap = "\x00\x00" + string16(numTables) + "\x00\x03" + "\x00\x01" + string32(4 + numTables * 8);
for (i = ranges.length - 1; i >= 0; --i) {
if (ranges[i][0] <= 0xffff) {
if (ranges[i][0] < 0xffff && ranges[i][1] === 0xffff) {
const trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0;
const segCount = bmpLength + trailingRangesCount;
const searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);
let range, start, end, codes;
for (i = 0, ii = bmpLength; i < ii; i++) {
startCount += string16(start);
endCount += string16(end);
for (j = 1, jj = codes.length; j < jj; ++j) {
if (codes[j] !== codes[j - 1] + 1) {
const offset = (segCount - i) * 2 + bias * 2;
idRangeOffsets += string16(offset);
for (j = 0, jj = codes.length; j < jj; ++j) {
glyphsIds += string16(codes[j]);
const startCode = codes[0];
idDeltas += string16(startCode - start & 0xffff);
idRangeOffsets += string16(0);
if (trailingRangesCount > 0) {
startCount += "\xFF\xFF";
idRangeOffsets += "\x00\x00";
const format314 = "\x00\x00" + string16(2 * segCount) + string16(searchParams.range) + string16(searchParams.entry) + string16(searchParams.rangeShift) + endCount + "\x00\x00" + startCount + idDeltas + idRangeOffsets + glyphsIds;
cmap += "\x00\x03" + "\x00\x0A" + string32(4 + numTables * 8 + 4 + format314.length);
for (i = 0, ii = ranges.length; i < ii; i++) {
for (j = 1, jj = codes.length; j < jj; ++j) {
if (codes[j] !== codes[j - 1] + 1) {
format31012 += string32(start) + string32(end) + string32(code);
format31012 += string32(start) + string32(range[1]) + string32(code);
header31012 = "\x00\x0C" + "\x00\x00" + string32(format31012.length + 16) + "\x00\x00\x00\x00" + string32(format31012.length / 12);
return cmap + "\x00\x04" + string16(format314.length + 4) + format314 + header31012 + format31012;
function validateOS2Table(os2, file) {
file.pos = (file.start || 0) + os2.offset;
const version = file.getUint16();
const selection = file.getUint16();
if (version < 4 && selection & 0x0300) {
const firstChar = file.getUint16();
const lastChar = file.getUint16();
if (firstChar > lastChar) {
const usWinAscent = file.getUint16();
os2.data[8] = os2.data[9] = 0;
function createOS2Table(properties, charstrings, override) {
let firstCharIndex = null;
for (let code in charstrings) {
if (firstCharIndex > code || !firstCharIndex) {
if (lastCharIndex < code) {
position = getUnicodeRangeFor(code, position);
ulUnicodeRange1 |= 1 << position;
} else if (position < 64) {
ulUnicodeRange2 |= 1 << position - 32;
} else if (position < 96) {
ulUnicodeRange3 |= 1 << position - 64;
} else if (position < 123) {
ulUnicodeRange4 |= 1 << position - 96;
throw new FormatError("Unicode ranges Bits > 123 are reserved for internal usage");
if (lastCharIndex > 0xffff) {
const bbox = properties.bbox || [0, 0, 0, 0];
const unitsPerEm = override.unitsPerEm || 1 / (properties.fontMatrix || FONT_IDENTITY_MATRIX)[0];
const scale = properties.ascentScaled ? 1.0 : unitsPerEm / PDF_GLYPH_SPACE_UNITS;
const typoAscent = override.ascent || Math.round(scale * (properties.ascent || bbox[3]));
let typoDescent = override.descent || Math.round(scale * (properties.descent || bbox[1]));
if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) {
typoDescent = -typoDescent;
const winAscent = override.yMax || typoAscent;
const winDescent = -override.yMin || -typoDescent;
return "\x00\x03" + "\x02\x24" + "\x01\xF4" + "\x00\x05" + "\x00\x00" + "\x02\x8A" + "\x02\xBB" + "\x00\x00" + "\x00\x8C" + "\x02\x8A" + "\x02\xBB" + "\x00\x00" + "\x01\xDF" + "\x00\x31" + "\x01\x02" + "\x00\x00" + "\x00\x00\x06" + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + "\x00\x00\x00\x00\x00\x00" + string32(ulUnicodeRange1) + string32(ulUnicodeRange2) + string32(ulUnicodeRange3) + string32(ulUnicodeRange4) + "\x2A\x32\x31\x2A" + string16(properties.italicAngle ? 1 : 0) + string16(firstCharIndex || properties.firstChar) + string16(lastCharIndex || properties.lastChar) + string16(typoAscent) + string16(typoDescent) + "\x00\x64" + string16(winAscent) + string16(winDescent) + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + string16(properties.xHeight) + string16(properties.capHeight) + string16(0) + string16(firstCharIndex || properties.firstChar) + "\x00\x03";
function createPostTable(properties) {
const angle = Math.floor(properties.italicAngle * 2 ** 16);
return "\x00\x03\x00\x00" + string32(angle) + "\x00\x00" + "\x00\x00" + string32(properties.fixedPitch ? 1 : 0) + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00";
function createPostscriptName(name) {
return name.replaceAll(/[^\x21-\x7E]|[[\](){}<>/%]/g, "").slice(0, 63);
function createNameTable(name, proto) {
const strings = [proto[0][0] || "Original licence", proto[0][1] || name, proto[0][2] || "Unknown", proto[0][3] || "uniqueID", proto[0][4] || name, proto[0][5] || "Version 0.11", proto[0][6] || createPostscriptName(name), proto[0][7] || "Unknown", proto[0][8] || "Unknown", proto[0][9] || "Unknown"];
const stringsUnicode = [];
for (i = 0, ii = strings.length; i < ii; i++) {
str = proto[1][i] || strings[i];
const strBufUnicode = [];
for (j = 0, jj = str.length; j < jj; j++) {
strBufUnicode.push(string16(str.charCodeAt(j)));
stringsUnicode.push(strBufUnicode.join(""));
const names = [strings, stringsUnicode];
const platforms = ["\x00\x01", "\x00\x03"];
const encodings = ["\x00\x00", "\x00\x01"];
const languages = ["\x00\x00", "\x04\x09"];
const namesRecordCount = strings.length * platforms.length;
let nameTable = "\x00\x00" + string16(namesRecordCount) + string16(namesRecordCount * 12 + 6);
for (i = 0, ii = platforms.length; i < ii; i++) {
for (j = 0, jj = strs.length; j < jj; j++) {
const nameRecord = platforms[i] + encodings[i] + languages[i] + string16(j) + string16(str.length) + string16(strOffset);
nameTable += strings.join("") + stringsUnicode.join("");
constructor(name, file, properties) {
this.disableFontFace = false;
this.loadedName = properties.loadedName;
this.isType3Font = properties.isType3Font;
this.missingFile = false;
this.cssFontInfo = properties.cssFontInfo;
this._charsCache = Object.create(null);
this._glyphCache = Object.create(null);
let isSerifFont = !!(properties.flags & FontFlags.Serif);
if (!isSerifFont && !properties.isSimulatedFlags) {
const baseName = name.replaceAll(/[,_]/g, "-").split("-", 1)[0],
serifFonts = getSerifFonts();
for (const namePart of baseName.split("+")) {
if (serifFonts[namePart]) {
this.isSerifFont = isSerifFont;
this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);
this.systemFontInfo = properties.systemFontInfo;
const matches = name.match(/^InvalidPDFjsFont_(.*)_\d+$/);
this.isInvalidPDFjsFont = !!matches;
if (this.isInvalidPDFjsFont) {
this.fallbackName = matches[1];
} else if (this.isMonospace) {
this.fallbackName = "monospace";
} else if (this.isSerifFont) {
this.fallbackName = "serif";
this.fallbackName = "sans-serif";
if (this.systemFontInfo?.guessFallback) {
this.systemFontInfo.guessFallback = false;
this.systemFontInfo.css += `,${this.fallbackName}`;
this.differences = properties.differences;
this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth;
this.composite = properties.composite;
this.cMap = properties.cMap;
this.capHeight = properties.capHeight / PDF_GLYPH_SPACE_UNITS;
this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
this.lineHeight = this.ascent - this.descent;
this.fontMatrix = properties.fontMatrix;
this.bbox = properties.bbox;
this.defaultEncoding = properties.defaultEncoding;
this.toUnicode = properties.toUnicode;
if (properties.type === "Type3") {
for (let charCode = 0; charCode < 256; charCode++) {
this.toFontChar[charCode] = this.differences[charCode] || properties.defaultEncoding[charCode];
this.cidEncoding = properties.cidEncoding || "";
this.vertical = !!properties.vertical;
this.vmetrics = properties.vmetrics;
this.defaultVMetrics = properties.defaultVMetrics;
if (!file || file.isEmpty) {
warn('Font file is empty in "' + name + '" (' + this.loadedName + ")");
this.fallbackToSystemFont(properties);
[type, subtype] = getFontFileType(file, properties);
if (type !== this.type || subtype !== this.subtype) {
info("Inconsistent font file Type/SubType, expected: " + `${this.type}/${this.subtype} but found: ${type}/${subtype}.`);
info("MMType1 font (" + name + "), falling back to Type1.");
this.mimetype = "font/opentype";
const cff = subtype === "Type1C" || subtype === "CIDFontType0C" ? new CFFFont(file, properties) : new Type1Font(name, file, properties);
adjustWidths(properties);
data = this.convert(name, cff, properties);
this.mimetype = "font/opentype";
data = this.checkAndRepair(name, file, properties);
adjustWidths(properties);
throw new FormatError(`Font ${type} is not supported`);
this.fallbackToSystemFont(properties);
amendFallbackToUnicode(properties);
this.fontMatrix = properties.fontMatrix;
this.widths = properties.widths;
this.defaultWidth = properties.defaultWidth;
this.toUnicode = properties.toUnicode;
this.seacMap = properties.seacMap;
const renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
return shadow(this, "renderer", renderer);
exportData(extraProperties = false) {
const exportDataProperties = extraProperties ? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES] : EXPORT_DATA_PROPERTIES;
const data = Object.create(null);
for (property of exportDataProperties) {
if (value !== undefined) {
fallbackToSystemFont(properties) {
let fontName = normalizeFontName(name);
const stdFontMap = getStdFontMap(),
nonStdFontMap = getNonStdFontMap();
const isStandardFont = !!stdFontMap[fontName];
const isMappedToStandardFont = !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
const fontBasicMetricsMap = getFontBasicMetrics();
const metrics = fontBasicMetricsMap[fontName];
if (isNaN(this.ascent)) {
this.ascent = metrics.ascent / PDF_GLYPH_SPACE_UNITS;
if (isNaN(this.descent)) {
this.descent = metrics.descent / PDF_GLYPH_SPACE_UNITS;
if (isNaN(this.capHeight)) {
this.capHeight = metrics.capHeight / PDF_GLYPH_SPACE_UNITS;
this.bold = /bold/gi.test(fontName);
this.italic = /oblique|italic/gi.test(fontName);
this.black = /Black/g.test(name);
const isNarrow = /Narrow/g.test(name);
this.remeasure = (!isStandardFont || isNarrow) && Object.keys(this.widths).length > 0;
if ((isStandardFont || isMappedToStandardFont) && type === "CIDFontType2" && this.cidEncoding.startsWith("Identity-")) {
const cidToGidMap = properties.cidToGidMap;
applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts());
if (/Arial-?Black/i.test(name)) {
applyStandardFontGlyphMap(map, getSupplementalGlyphMapForArialBlack());
} else if (/Calibri/i.test(name)) {