: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if (code > 0 && code <= 0x10ffff && Number.isInteger(code)) {
if (baseEncodingName && code === +charcode) {
const baseEncoding = getEncoding(baseEncodingName);
if (baseEncoding && (glyphName = baseEncoding[charcode])) {
toUnicode[charcode] = String.fromCharCode(glyphsUnicodeMap[glyphName]);
toUnicode[charcode] = String.fromCodePoint(code);
async buildToUnicode(properties) {
properties.hasIncludedToUnicodeMap = properties.toUnicode?.length > 0;
if (properties.hasIncludedToUnicodeMap) {
if (!properties.composite && properties.hasEncoding) {
properties.fallbackToUnicode = this._simpleFontToUnicode(properties);
return properties.toUnicode;
if (!properties.composite) {
return new ToUnicodeMap(this._simpleFontToUnicode(properties));
if (properties.composite && (properties.cMap.builtInCMap && !(properties.cMap instanceof IdentityCMap) || properties.cidSystemInfo?.registry === "Adobe" && (properties.cidSystemInfo.ordering === "GB1" || properties.cidSystemInfo.ordering === "CNS1" || properties.cidSystemInfo.ordering === "Japan1" || properties.cidSystemInfo.ordering === "Korea1"))) {
} = properties.cidSystemInfo;
const ucs2CMapName = Name.get(`${registry}-${ordering}-UCS2`);
const ucs2CMap = await CMapFactory.create({
fetchBuiltInCMap: this._fetchBuiltInCMapBound,
properties.cMap.forEach(function (charcode, cid) {
throw new FormatError("Max size of CID is 65,535");
const ucs2 = ucs2CMap.lookup(cid);
for (let i = 0, ii = ucs2.length; i < ii; i += 2) {
buf.push((ucs2.charCodeAt(i) << 8) + ucs2.charCodeAt(i + 1));
toUnicode[charcode] = String.fromCharCode(...buf);
return new ToUnicodeMap(toUnicode);
return new IdentityToUnicodeMap(properties.firstChar, properties.lastChar);
async readToUnicode(cmapObj) {
if (cmapObj instanceof Name) {
const cmap = await CMapFactory.create({
fetchBuiltInCMap: this._fetchBuiltInCMapBound,
if (cmap instanceof IdentityCMap) {
return new IdentityToUnicodeMap(0, 0xffff);
return new ToUnicodeMap(cmap.getMap());
if (cmapObj instanceof BaseStream) {
const cmap = await CMapFactory.create({
fetchBuiltInCMap: this._fetchBuiltInCMapBound,
if (cmap instanceof IdentityCMap) {
return new IdentityToUnicodeMap(0, 0xffff);
const map = new Array(cmap.length);
cmap.forEach(function (charCode, token) {
if (typeof token === "number") {
map[charCode] = String.fromCodePoint(token);
for (let k = 0; k < token.length; k += 2) {
const w1 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
if ((w1 & 0xf800) !== 0xd800) {
const w2 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
str.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000);
map[charCode] = String.fromCodePoint(...str);
return new ToUnicodeMap(map);
if (reason instanceof AbortException) {
if (this.options.ignoreErrors) {
warn(`readToUnicode - ignoring ToUnicode data: "${reason}".`);
readCidToGidMap(glyphsData, toUnicode) {
for (let j = 0, jj = glyphsData.length; j < jj; j++) {
const glyphID = glyphsData[j++] << 8 | glyphsData[j];
if (glyphID === 0 && !toUnicode.has(code)) {
extractWidths(dict, descriptor, properties) {
const glyphsVMetrics = [];
if (properties.composite) {
const dw = dict.get("DW");
defaultWidth = Number.isInteger(dw) ? dw : 1000;
const widths = dict.get("W");
if (Array.isArray(widths)) {
for (let i = 0, ii = widths.length; i < ii; i++) {
let start = xref.fetchIfRef(widths[i++]);
if (!Number.isInteger(start)) {
const code = xref.fetchIfRef(widths[i]);
if (Array.isArray(code)) {
const width = xref.fetchIfRef(c);
if (typeof width === "number") {
glyphsWidths[start] = width;
} else if (Number.isInteger(code)) {
const width = xref.fetchIfRef(widths[++i]);
if (typeof width !== "number") {
for (let j = start; j <= code; j++) {
if (properties.vertical) {
const dw2 = dict.getArray("DW2");
let vmetrics = isNumberArray(dw2, 2) ? dw2 : [880, -1000];
defaultVMetrics = [vmetrics[1], defaultWidth * 0.5, vmetrics[0]];
vmetrics = dict.get("W2");
if (Array.isArray(vmetrics)) {
for (let i = 0, ii = vmetrics.length; i < ii; i++) {
let start = xref.fetchIfRef(vmetrics[i++]);
if (!Number.isInteger(start)) {
const code = xref.fetchIfRef(vmetrics[i]);
if (Array.isArray(code)) {
for (let j = 0, jj = code.length; j < jj; j++) {
const vmetric = [xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j])];
if (isNumberArray(vmetric, null)) {
glyphsVMetrics[start] = vmetric;
} else if (Number.isInteger(code)) {
const vmetric = [xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i])];
if (!isNumberArray(vmetric, null)) {
for (let j = start; j <= code; j++) {
glyphsVMetrics[j] = vmetric;
const widths = dict.get("Widths");
if (Array.isArray(widths)) {
let j = properties.firstChar;
for (const w of widths) {
const width = xref.fetchIfRef(w);
if (typeof width === "number") {
const missingWidth = descriptor.get("MissingWidth");
defaultWidth = typeof missingWidth === "number" ? missingWidth : 0;
const baseFontName = dict.get("BaseFont");
if (baseFontName instanceof Name) {
const metrics = this.getBaseFontMetrics(baseFontName.name);
glyphsWidths = this.buildCharCodeToWidth(metrics.widths, properties);
defaultWidth = metrics.defaultWidth;
let firstWidth = defaultWidth;
for (const glyph in glyphsWidths) {
const glyphWidth = glyphsWidths[glyph];
if (firstWidth !== glyphWidth) {
properties.flags |= FontFlags.FixedPitch;
properties.flags &= ~FontFlags.FixedPitch;
properties.defaultWidth = defaultWidth;
properties.widths = glyphsWidths;
properties.defaultVMetrics = defaultVMetrics;
properties.vmetrics = glyphsVMetrics;
isSerifFont(baseFontName) {
const fontNameWoStyle = baseFontName.split("-", 1)[0];
return fontNameWoStyle in getSerifFonts() || /serif/gi.test(fontNameWoStyle);
getBaseFontMetrics(name) {
let widths = Object.create(null);
const stdFontMap = getStdFontMap();
let lookupName = stdFontMap[name] || name;
const Metrics = getMetrics();
if (!(lookupName in Metrics)) {
lookupName = this.isSerifFont(name) ? "Times-Roman" : "Helvetica";
const glyphWidths = Metrics[lookupName];
if (typeof glyphWidths === "number") {
defaultWidth = glyphWidths;
buildCharCodeToWidth(widthsByGlyphName, properties) {
const widths = Object.create(null);
const differences = properties.differences;
const encoding = properties.defaultEncoding;
for (let charCode = 0; charCode < 256; charCode++) {
if (charCode in differences && widthsByGlyphName[differences[charCode]]) {
widths[charCode] = widthsByGlyphName[differences[charCode]];
if (charCode in encoding && widthsByGlyphName[encoding[charCode]]) {
widths[charCode] = widthsByGlyphName[encoding[charCode]];
let type = dict.get("Subtype");
if (!(type instanceof Name)) {
throw new FormatError("invalid font Subtype");
if (type.name === "Type0") {
const df = dict.get("DescendantFonts");
throw new FormatError("Descendant fonts are not specified");
dict = Array.isArray(df) ? this.xref.fetchIfRef(df[0]) : df;
if (!(dict instanceof Dict)) {
throw new FormatError("Descendant font is not a dictionary.");
type = dict.get("Subtype");
if (!(type instanceof Name)) {
throw new FormatError("invalid font Subtype");
let firstChar = dict.get("FirstChar");
if (!Number.isInteger(firstChar)) {
let lastChar = dict.get("LastChar");
if (!Number.isInteger(lastChar)) {
lastChar = composite ? 0xffff : 0xff;
const descriptor = dict.get("FontDescriptor");
const toUnicode = dict.get("ToUnicode") || baseDict.get("ToUnicode");
hash = new MurmurHash3_64();
const encoding = baseDict.getRaw("Encoding");
if (encoding instanceof Name) {
hash.update(encoding.name);
} else if (encoding instanceof Ref) {
hash.update(encoding.toString());
} else if (encoding instanceof Dict) {
for (const entry of encoding.getRawValues()) {
if (entry instanceof Name) {
} else if (entry instanceof Ref) {
hash.update(entry.toString());
} else if (Array.isArray(entry)) {
const diffLength = entry.length,
diffBuf = new Array(diffLength);
for (let j = 0; j < diffLength; j++) {
const diffEntry = entry[j];
if (diffEntry instanceof Name) {
diffBuf[j] = diffEntry.name;
} else if (typeof diffEntry === "number" || diffEntry instanceof Ref) {
diffBuf[j] = diffEntry.toString();
hash.update(diffBuf.join());
hash.update(`${firstChar}-${lastChar}`);
if (toUnicode instanceof BaseStream) {
const stream = toUnicode.str || toUnicode;
const uint8array = stream.buffer ? new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) : new Uint8Array(stream.bytes.buffer, stream.start, stream.end - stream.start);
} else if (toUnicode instanceof Name) {
hash.update(toUnicode.name);
const widths = dict.get("Widths") || baseDict.get("Widths");
if (Array.isArray(widths)) {
for (const entry of widths) {
if (typeof entry === "number" || entry instanceof Ref) {
widthsBuf.push(entry.toString());
hash.update(widthsBuf.join());
hash.update("compositeFont");
const compositeWidths = dict.get("W") || baseDict.get("W");
if (Array.isArray(compositeWidths)) {
for (const entry of compositeWidths) {
if (typeof entry === "number" || entry instanceof Ref) {
widthsBuf.push(entry.toString());
} else if (Array.isArray(entry)) {
for (const element of entry) {
if (typeof element === "number" || element instanceof Ref) {
subWidthsBuf.push(element.toString());
widthsBuf.push(`[${subWidthsBuf.join()}]`);
hash.update(widthsBuf.join());
const cidToGidMap = dict.getRaw("CIDToGIDMap") || baseDict.getRaw("CIDToGIDMap");
if (cidToGidMap instanceof Name) {
hash.update(cidToGidMap.name);
} else if (cidToGidMap instanceof Ref) {
hash.update(cidToGidMap.toString());
} else if (cidToGidMap instanceof BaseStream) {
hash.update(cidToGidMap.peekBytes());
hash: hash ? hash.hexdigest() : ""
const isType3Font = type === "Type3";
const bbox = lookupNormalRect(dict.getArray("FontBBox"), [0, 0, 0, 0]);
descriptor = new Dict(null);
descriptor.set("FontName", Name.get(type));
descriptor.set("FontBBox", bbox);
let baseFontName = dict.get("BaseFont");
if (!(baseFontName instanceof Name)) {
throw new FormatError("Base font is not specified");
baseFontName = baseFontName.name.replaceAll(/[,_]/g, "-");
const metrics = this.getBaseFontMetrics(baseFontName);
const fontNameWoStyle = baseFontName.split("-", 1)[0];
const flags = (this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) | (metrics.monospace ? FontFlags.FixedPitch : 0) | (getSymbolsFonts()[fontNameWoStyle] ? FontFlags.Symbolic : FontFlags.Nonsymbolic);
loadedName: baseDict.loadedName,
defaultWidth: metrics.defaultWidth,
const widths = dict.get("Widths");
const standardFontName = getStandardFontName(baseFontName);
file = await this.fetchStandardFontData(standardFontName);
properties.isInternalFont = !!file;
if (!properties.isInternalFont && this.options.useSystemFonts) {
properties.systemFontInfo = getFontSubstitution(this.systemFontCache, this.idFactory, this.options.standardFontDataUrl, baseFontName, standardFontName, type);
const newProperties = await this.extractDataStructures(dict, properties);
if (Array.isArray(widths)) {
for (const w of widths) {
const width = this.xref.fetchIfRef(w);
if (typeof width === "number") {
newProperties.widths = glyphWidths;
newProperties.widths = this.buildCharCodeToWidth(metrics.widths, newProperties);
return new Font(baseFontName, file, newProperties);
let fontName = descriptor.get("FontName");
let baseFont = dict.get("BaseFont");
if (typeof fontName === "string") {
fontName = Name.get(fontName);
if (typeof baseFont === "string") {
baseFont = Name.get(baseFont);
const fontNameStr = fontName?.name;
const baseFontStr = baseFont?.name;
if (!isType3Font && fontNameStr !== baseFontStr) {
info(`The FontDescriptor's FontName is "${fontNameStr}" but ` + `should be the same as the Font's BaseFont "${baseFontStr}".`);
if (fontNameStr && baseFontStr && (baseFontStr.startsWith(fontNameStr) || !isKnownFontName(fontNameStr) && isKnownFontName(baseFontStr))) {