From e5e756c0b449a3f2de46027c1d283bb104d2140a Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 13 Aug 2022 18:19:26 +0200 Subject: [PATCH] Remove the remaining closures in the `src/core/cff_parser.js` file Given that the code is written with JavaScript module-syntax, none of this functionality will "leak" outside of this file with these changes. For e.g. the `gulp mozcentral` command the *built* `pdf.worker.js` file-size decreases `~2 kB` with this patch, and most of the improvement comes from having less overall indentation in the code. --- src/core/cff_parser.js | 1817 ++++++++++++++++++++-------------------- 1 file changed, 901 insertions(+), 916 deletions(-) diff --git a/src/core/cff_parser.js b/src/core/cff_parser.js index fd1a12042..cef4b3ed7 100644 --- a/src/core/cff_parser.js +++ b/src/core/cff_parser.js @@ -107,884 +107,877 @@ const CFFStandardStrings = [ const NUM_STANDARD_CFF_STRINGS = 391; -const CFFParser = (function CFFParserClosure() { - const CharstringValidationData = [ - null, - { id: "hstem", min: 2, stackClearing: true, stem: true }, - null, - { id: "vstem", min: 2, stackClearing: true, stem: true }, - { id: "vmoveto", min: 1, stackClearing: true }, - { id: "rlineto", min: 2, resetStack: true }, - { id: "hlineto", min: 1, resetStack: true }, - { id: "vlineto", min: 1, resetStack: true }, - { id: "rrcurveto", min: 6, resetStack: true }, - null, - { id: "callsubr", min: 1, undefStack: true }, - { id: "return", min: 0, undefStack: true }, - null, // 12 - null, - { id: "endchar", min: 0, stackClearing: true }, - null, - null, - null, - { id: "hstemhm", min: 2, stackClearing: true, stem: true }, - { id: "hintmask", min: 0, stackClearing: true }, - { id: "cntrmask", min: 0, stackClearing: true }, - { id: "rmoveto", min: 2, stackClearing: true }, - { id: "hmoveto", min: 1, stackClearing: true }, - { id: "vstemhm", min: 2, stackClearing: true, stem: true }, - { id: "rcurveline", min: 8, resetStack: true }, - { id: "rlinecurve", min: 8, resetStack: true }, - { id: "vvcurveto", min: 4, resetStack: true }, - { id: "hhcurveto", min: 4, resetStack: true }, - null, // shortint - { id: "callgsubr", min: 1, undefStack: true }, - { id: "vhcurveto", min: 4, resetStack: true }, - { id: "hvcurveto", min: 4, resetStack: true }, - ]; - const CharstringValidationData12 = [ - null, - null, - null, - { id: "and", min: 2, stackDelta: -1 }, - { id: "or", min: 2, stackDelta: -1 }, - { id: "not", min: 1, stackDelta: 0 }, - null, - null, - null, - { id: "abs", min: 1, stackDelta: 0 }, - { - id: "add", - min: 2, - stackDelta: -1, - stackFn: function stack_div(stack, index) { - stack[index - 2] = stack[index - 2] + stack[index - 1]; - }, - }, - { - id: "sub", - min: 2, - stackDelta: -1, - stackFn: function stack_div(stack, index) { - stack[index - 2] = stack[index - 2] - stack[index - 1]; - }, - }, - { - id: "div", - min: 2, - stackDelta: -1, - stackFn: function stack_div(stack, index) { - stack[index - 2] = stack[index - 2] / stack[index - 1]; - }, - }, - null, - { - id: "neg", - min: 1, - stackDelta: 0, - stackFn: function stack_div(stack, index) { - stack[index - 1] = -stack[index - 1]; - }, - }, - { id: "eq", min: 2, stackDelta: -1 }, - null, - null, - { id: "drop", min: 1, stackDelta: -1 }, - null, - { id: "put", min: 2, stackDelta: -2 }, - { id: "get", min: 1, stackDelta: 0 }, - { id: "ifelse", min: 4, stackDelta: -3 }, - { id: "random", min: 0, stackDelta: 1 }, - { - id: "mul", - min: 2, - stackDelta: -1, - stackFn: function stack_div(stack, index) { - stack[index - 2] = stack[index - 2] * stack[index - 1]; - }, - }, - null, - { id: "sqrt", min: 1, stackDelta: 0 }, - { id: "dup", min: 1, stackDelta: 1 }, - { id: "exch", min: 2, stackDelta: 0 }, - { id: "index", min: 2, stackDelta: 0 }, - { id: "roll", min: 3, stackDelta: -2 }, - null, - null, - null, - { id: "hflex", min: 7, resetStack: true }, - { id: "flex", min: 13, resetStack: true }, - { id: "hflex1", min: 9, resetStack: true }, - { id: "flex1", min: 11, resetStack: true }, - ]; +const CharstringValidationData = [ + null, + { id: "hstem", min: 2, stackClearing: true, stem: true }, + null, + { id: "vstem", min: 2, stackClearing: true, stem: true }, + { id: "vmoveto", min: 1, stackClearing: true }, + { id: "rlineto", min: 2, resetStack: true }, + { id: "hlineto", min: 1, resetStack: true }, + { id: "vlineto", min: 1, resetStack: true }, + { id: "rrcurveto", min: 6, resetStack: true }, + null, + { id: "callsubr", min: 1, undefStack: true }, + { id: "return", min: 0, undefStack: true }, + null, // 12 + null, + { id: "endchar", min: 0, stackClearing: true }, + null, + null, + null, + { id: "hstemhm", min: 2, stackClearing: true, stem: true }, + { id: "hintmask", min: 0, stackClearing: true }, + { id: "cntrmask", min: 0, stackClearing: true }, + { id: "rmoveto", min: 2, stackClearing: true }, + { id: "hmoveto", min: 1, stackClearing: true }, + { id: "vstemhm", min: 2, stackClearing: true, stem: true }, + { id: "rcurveline", min: 8, resetStack: true }, + { id: "rlinecurve", min: 8, resetStack: true }, + { id: "vvcurveto", min: 4, resetStack: true }, + { id: "hhcurveto", min: 4, resetStack: true }, + null, // shortint + { id: "callgsubr", min: 1, undefStack: true }, + { id: "vhcurveto", min: 4, resetStack: true }, + { id: "hvcurveto", min: 4, resetStack: true }, +]; - // eslint-disable-next-line no-shadow - class CFFParser { - constructor(file, properties, seacAnalysisEnabled) { - this.bytes = file.getBytes(); - this.properties = properties; - this.seacAnalysisEnabled = !!seacAnalysisEnabled; +const CharstringValidationData12 = [ + null, + null, + null, + { id: "and", min: 2, stackDelta: -1 }, + { id: "or", min: 2, stackDelta: -1 }, + { id: "not", min: 1, stackDelta: 0 }, + null, + null, + null, + { id: "abs", min: 1, stackDelta: 0 }, + { + id: "add", + min: 2, + stackDelta: -1, + stackFn(stack, index) { + stack[index - 2] = stack[index - 2] + stack[index - 1]; + }, + }, + { + id: "sub", + min: 2, + stackDelta: -1, + stackFn(stack, index) { + stack[index - 2] = stack[index - 2] - stack[index - 1]; + }, + }, + { + id: "div", + min: 2, + stackDelta: -1, + stackFn(stack, index) { + stack[index - 2] = stack[index - 2] / stack[index - 1]; + }, + }, + null, + { + id: "neg", + min: 1, + stackDelta: 0, + stackFn(stack, index) { + stack[index - 1] = -stack[index - 1]; + }, + }, + { id: "eq", min: 2, stackDelta: -1 }, + null, + null, + { id: "drop", min: 1, stackDelta: -1 }, + null, + { id: "put", min: 2, stackDelta: -2 }, + { id: "get", min: 1, stackDelta: 0 }, + { id: "ifelse", min: 4, stackDelta: -3 }, + { id: "random", min: 0, stackDelta: 1 }, + { + id: "mul", + min: 2, + stackDelta: -1, + stackFn(stack, index) { + stack[index - 2] = stack[index - 2] * stack[index - 1]; + }, + }, + null, + { id: "sqrt", min: 1, stackDelta: 0 }, + { id: "dup", min: 1, stackDelta: 1 }, + { id: "exch", min: 2, stackDelta: 0 }, + { id: "index", min: 2, stackDelta: 0 }, + { id: "roll", min: 3, stackDelta: -2 }, + null, + null, + null, + { id: "hflex", min: 7, resetStack: true }, + { id: "flex", min: 13, resetStack: true }, + { id: "hflex1", min: 9, resetStack: true }, + { id: "flex1", min: 11, resetStack: true }, +]; + +class CFFParser { + constructor(file, properties, seacAnalysisEnabled) { + this.bytes = file.getBytes(); + this.properties = properties; + this.seacAnalysisEnabled = !!seacAnalysisEnabled; + } + + parse() { + const properties = this.properties; + const cff = new CFF(); + this.cff = cff; + + // The first five sections must be in order, all the others are reached + // via offsets contained in one of the below. + const header = this.parseHeader(); + const nameIndex = this.parseIndex(header.endPos); + const topDictIndex = this.parseIndex(nameIndex.endPos); + const stringIndex = this.parseIndex(topDictIndex.endPos); + const globalSubrIndex = this.parseIndex(stringIndex.endPos); + + const topDictParsed = this.parseDict(topDictIndex.obj.get(0)); + const topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings); + + cff.header = header.obj; + cff.names = this.parseNameIndex(nameIndex.obj); + cff.strings = this.parseStringIndex(stringIndex.obj); + cff.topDict = topDict; + cff.globalSubrIndex = globalSubrIndex.obj; + + this.parsePrivateDict(cff.topDict); + + cff.isCIDFont = topDict.hasName("ROS"); + + const charStringOffset = topDict.getByName("CharStrings"); + const charStringIndex = this.parseIndex(charStringOffset).obj; + + const fontMatrix = topDict.getByName("FontMatrix"); + if (fontMatrix) { + properties.fontMatrix = fontMatrix; } - parse() { - const properties = this.properties; - const cff = new CFF(); - this.cff = cff; + const fontBBox = topDict.getByName("FontBBox"); + if (fontBBox) { + // adjusting ascent/descent + properties.ascent = Math.max(fontBBox[3], fontBBox[1]); + properties.descent = Math.min(fontBBox[1], fontBBox[3]); + properties.ascentScaled = true; + } - // The first five sections must be in order, all the others are reached - // via offsets contained in one of the below. - const header = this.parseHeader(); - const nameIndex = this.parseIndex(header.endPos); - const topDictIndex = this.parseIndex(nameIndex.endPos); - const stringIndex = this.parseIndex(topDictIndex.endPos); - const globalSubrIndex = this.parseIndex(stringIndex.endPos); - - const topDictParsed = this.parseDict(topDictIndex.obj.get(0)); - const topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings); - - cff.header = header.obj; - cff.names = this.parseNameIndex(nameIndex.obj); - cff.strings = this.parseStringIndex(stringIndex.obj); - cff.topDict = topDict; - cff.globalSubrIndex = globalSubrIndex.obj; - - this.parsePrivateDict(cff.topDict); - - cff.isCIDFont = topDict.hasName("ROS"); - - const charStringOffset = topDict.getByName("CharStrings"); - const charStringIndex = this.parseIndex(charStringOffset).obj; - - const fontMatrix = topDict.getByName("FontMatrix"); - if (fontMatrix) { - properties.fontMatrix = fontMatrix; + let charset, encoding; + if (cff.isCIDFont) { + const fdArrayIndex = this.parseIndex(topDict.getByName("FDArray")).obj; + for (let i = 0, ii = fdArrayIndex.count; i < ii; ++i) { + const dictRaw = fdArrayIndex.get(i); + const fontDict = this.createDict( + CFFTopDict, + this.parseDict(dictRaw), + cff.strings + ); + this.parsePrivateDict(fontDict); + cff.fdArray.push(fontDict); } + // cid fonts don't have an encoding + encoding = null; + charset = this.parseCharsets( + topDict.getByName("charset"), + charStringIndex.count, + cff.strings, + true + ); + cff.fdSelect = this.parseFDSelect( + topDict.getByName("FDSelect"), + charStringIndex.count + ); + } else { + charset = this.parseCharsets( + topDict.getByName("charset"), + charStringIndex.count, + cff.strings, + false + ); + encoding = this.parseEncoding( + topDict.getByName("Encoding"), + properties, + cff.strings, + charset.charset + ); + } - const fontBBox = topDict.getByName("FontBBox"); - if (fontBBox) { - // adjusting ascent/descent - properties.ascent = Math.max(fontBBox[3], fontBBox[1]); - properties.descent = Math.min(fontBBox[1], fontBBox[3]); - properties.ascentScaled = true; + cff.charset = charset; + cff.encoding = encoding; + + const charStringsAndSeacs = this.parseCharStrings({ + charStrings: charStringIndex, + localSubrIndex: topDict.privateDict.subrsIndex, + globalSubrIndex: globalSubrIndex.obj, + fdSelect: cff.fdSelect, + fdArray: cff.fdArray, + privateDict: topDict.privateDict, + }); + cff.charStrings = charStringsAndSeacs.charStrings; + cff.seacs = charStringsAndSeacs.seacs; + cff.widths = charStringsAndSeacs.widths; + + return cff; + } + + parseHeader() { + let bytes = this.bytes; + const bytesLength = bytes.length; + let offset = 0; + + // Prevent an infinite loop, by checking that the offset is within the + // bounds of the bytes array. Necessary in empty, or invalid, font files. + while (offset < bytesLength && bytes[offset] !== 1) { + ++offset; + } + if (offset >= bytesLength) { + throw new FormatError("Invalid CFF header"); + } + if (offset !== 0) { + info("cff data is shifted"); + bytes = bytes.subarray(offset); + this.bytes = bytes; + } + const major = bytes[0]; + const minor = bytes[1]; + const hdrSize = bytes[2]; + const offSize = bytes[3]; + const header = new CFFHeader(major, minor, hdrSize, offSize); + return { obj: header, endPos: hdrSize }; + } + + parseDict(dict) { + let pos = 0; + + function parseOperand() { + let value = dict[pos++]; + if (value === 30) { + return parseFloatOperand(); + } else if (value === 28) { + value = dict[pos++]; + value = ((value << 24) | (dict[pos++] << 16)) >> 16; + return value; + } else if (value === 29) { + value = dict[pos++]; + value = (value << 8) | dict[pos++]; + value = (value << 8) | dict[pos++]; + value = (value << 8) | dict[pos++]; + return value; + } else if (value >= 32 && value <= 246) { + return value - 139; + } else if (value >= 247 && value <= 250) { + return (value - 247) * 256 + dict[pos++] + 108; + } else if (value >= 251 && value <= 254) { + return -((value - 251) * 256) - dict[pos++] - 108; } + warn('CFFParser_parseDict: "' + value + '" is a reserved command.'); + return NaN; + } - let charset, encoding; - if (cff.isCIDFont) { - const fdArrayIndex = this.parseIndex(topDict.getByName("FDArray")).obj; - for (let i = 0, ii = fdArrayIndex.count; i < ii; ++i) { - const dictRaw = fdArrayIndex.get(i); - const fontDict = this.createDict( - CFFTopDict, - this.parseDict(dictRaw), - cff.strings - ); - this.parsePrivateDict(fontDict); - cff.fdArray.push(fontDict); + function parseFloatOperand() { + let str = ""; + const eof = 15; + // prettier-ignore + const lookup = ["0", "1", "2", "3", "4", "5", "6", "7", "8", + "9", ".", "E", "E-", null, "-"]; + const length = dict.length; + while (pos < length) { + const b = dict[pos++]; + const b1 = b >> 4; + const b2 = b & 15; + + if (b1 === eof) { + break; } - // cid fonts don't have an encoding - encoding = null; - charset = this.parseCharsets( - topDict.getByName("charset"), - charStringIndex.count, - cff.strings, - true - ); - cff.fdSelect = this.parseFDSelect( - topDict.getByName("FDSelect"), - charStringIndex.count - ); + str += lookup[b1]; + + if (b2 === eof) { + break; + } + str += lookup[b2]; + } + return parseFloat(str); + } + + let operands = []; + const entries = []; + + pos = 0; + const end = dict.length; + while (pos < end) { + let b = dict[pos]; + if (b <= 21) { + if (b === 12) { + b = (b << 8) | dict[++pos]; + } + entries.push([b, operands]); + operands = []; + ++pos; } else { - charset = this.parseCharsets( - topDict.getByName("charset"), - charStringIndex.count, - cff.strings, - false - ); - encoding = this.parseEncoding( - topDict.getByName("Encoding"), - properties, - cff.strings, - charset.charset - ); + operands.push(parseOperand()); } - - cff.charset = charset; - cff.encoding = encoding; - - const charStringsAndSeacs = this.parseCharStrings({ - charStrings: charStringIndex, - localSubrIndex: topDict.privateDict.subrsIndex, - globalSubrIndex: globalSubrIndex.obj, - fdSelect: cff.fdSelect, - fdArray: cff.fdArray, - privateDict: topDict.privateDict, - }); - cff.charStrings = charStringsAndSeacs.charStrings; - cff.seacs = charStringsAndSeacs.seacs; - cff.widths = charStringsAndSeacs.widths; - - return cff; } + return entries; + } - parseHeader() { - let bytes = this.bytes; - const bytesLength = bytes.length; - let offset = 0; + parseIndex(pos) { + const cffIndex = new CFFIndex(); + const bytes = this.bytes; + const count = (bytes[pos++] << 8) | bytes[pos++]; + const offsets = []; + let end = pos; + let i, ii; - // Prevent an infinite loop, by checking that the offset is within the - // bounds of the bytes array. Necessary in empty, or invalid, font files. - while (offset < bytesLength && bytes[offset] !== 1) { - ++offset; + if (count !== 0) { + const offsetSize = bytes[pos++]; + // add 1 for offset to determine size of last object + const startPos = pos + (count + 1) * offsetSize - 1; + + for (i = 0, ii = count + 1; i < ii; ++i) { + let offset = 0; + for (let j = 0; j < offsetSize; ++j) { + offset <<= 8; + offset += bytes[pos++]; + } + offsets.push(startPos + offset); } - if (offset >= bytesLength) { - throw new FormatError("Invalid CFF header"); - } - if (offset !== 0) { - info("cff data is shifted"); - bytes = bytes.subarray(offset); - this.bytes = bytes; - } - const major = bytes[0]; - const minor = bytes[1]; - const hdrSize = bytes[2]; - const offSize = bytes[3]; - const header = new CFFHeader(major, minor, hdrSize, offSize); - return { obj: header, endPos: hdrSize }; + end = offsets[count]; } + for (i = 0, ii = offsets.length - 1; i < ii; ++i) { + const offsetStart = offsets[i]; + const offsetEnd = offsets[i + 1]; + cffIndex.add(bytes.subarray(offsetStart, offsetEnd)); + } + return { obj: cffIndex, endPos: end }; + } - parseDict(dict) { - let pos = 0; + parseNameIndex(index) { + const names = []; + for (let i = 0, ii = index.count; i < ii; ++i) { + const name = index.get(i); + names.push(bytesToString(name)); + } + return names; + } - function parseOperand() { - let value = dict[pos++]; - if (value === 30) { - return parseFloatOperand(); - } else if (value === 28) { - value = dict[pos++]; - value = ((value << 24) | (dict[pos++] << 16)) >> 16; - return value; - } else if (value === 29) { - value = dict[pos++]; - value = (value << 8) | dict[pos++]; - value = (value << 8) | dict[pos++]; - value = (value << 8) | dict[pos++]; - return value; - } else if (value >= 32 && value <= 246) { - return value - 139; - } else if (value >= 247 && value <= 250) { - return (value - 247) * 256 + dict[pos++] + 108; - } else if (value >= 251 && value <= 254) { - return -((value - 251) * 256) - dict[pos++] - 108; - } - warn('CFFParser_parseDict: "' + value + '" is a reserved command.'); - return NaN; - } + parseStringIndex(index) { + const strings = new CFFStrings(); + for (let i = 0, ii = index.count; i < ii; ++i) { + const data = index.get(i); + strings.add(bytesToString(data)); + } + return strings; + } - function parseFloatOperand() { - let str = ""; - const eof = 15; - // prettier-ignore - const lookup = ["0", "1", "2", "3", "4", "5", "6", "7", "8", - "9", ".", "E", "E-", null, "-"]; - const length = dict.length; - while (pos < length) { - const b = dict[pos++]; - const b1 = b >> 4; - const b2 = b & 15; + createDict(Type, dict, strings) { + const cffDict = new Type(strings); + for (let i = 0, ii = dict.length; i < ii; ++i) { + const pair = dict[i]; + const key = pair[0]; + const value = pair[1]; + cffDict.setByKey(key, value); + } + return cffDict; + } - if (b1 === eof) { - break; - } - str += lookup[b1]; + parseCharString(state, data, localSubrIndex, globalSubrIndex) { + if (!data || state.callDepth > MAX_SUBR_NESTING) { + return false; + } + let stackSize = state.stackSize; + const stack = state.stack; - if (b2 === eof) { - break; - } - str += lookup[b2]; - } - return parseFloat(str); - } + const length = data.length; - let operands = []; - const entries = []; - - pos = 0; - const end = dict.length; - while (pos < end) { - let b = dict[pos]; - if (b <= 21) { - if (b === 12) { - b = (b << 8) | dict[++pos]; - } - entries.push([b, operands]); - operands = []; - ++pos; + for (let j = 0; j < length; ) { + const value = data[j++]; + let validationCommand = null; + if (value === 12) { + const q = data[j++]; + if (q === 0) { + // The CFF specification state that the 'dotsection' command + // (12, 0) is deprecated and treated as a no-op, but all Type2 + // charstrings processors should support them. Unfortunately + // the font sanitizer don't. As a workaround the sequence (12, 0) + // is replaced by a useless (0, hmoveto). + data[j - 2] = 139; + data[j - 1] = 22; + stackSize = 0; } else { - operands.push(parseOperand()); + validationCommand = CharstringValidationData12[q]; } - } - return entries; - } - - parseIndex(pos) { - const cffIndex = new CFFIndex(); - const bytes = this.bytes; - const count = (bytes[pos++] << 8) | bytes[pos++]; - const offsets = []; - let end = pos; - let i, ii; - - if (count !== 0) { - const offsetSize = bytes[pos++]; - // add 1 for offset to determine size of last object - const startPos = pos + (count + 1) * offsetSize - 1; - - for (i = 0, ii = count + 1; i < ii; ++i) { - let offset = 0; - for (let j = 0; j < offsetSize; ++j) { - offset <<= 8; - offset += bytes[pos++]; + } else if (value === 28) { + // number (16 bit) + stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16)) >> 16; + j += 2; + stackSize++; + } else if (value === 14) { + if (stackSize >= 4) { + stackSize -= 4; + if (this.seacAnalysisEnabled) { + state.seac = stack.slice(stackSize, stackSize + 4); + return false; } - offsets.push(startPos + offset); } - end = offsets[count]; - } - for (i = 0, ii = offsets.length - 1; i < ii; ++i) { - const offsetStart = offsets[i]; - const offsetEnd = offsets[i + 1]; - cffIndex.add(bytes.subarray(offsetStart, offsetEnd)); - } - return { obj: cffIndex, endPos: end }; - } - - parseNameIndex(index) { - const names = []; - for (let i = 0, ii = index.count; i < ii; ++i) { - const name = index.get(i); - names.push(bytesToString(name)); - } - return names; - } - - parseStringIndex(index) { - const strings = new CFFStrings(); - for (let i = 0, ii = index.count; i < ii; ++i) { - const data = index.get(i); - strings.add(bytesToString(data)); - } - return strings; - } - - createDict(Type, dict, strings) { - const cffDict = new Type(strings); - for (let i = 0, ii = dict.length; i < ii; ++i) { - const pair = dict[i]; - const key = pair[0]; - const value = pair[1]; - cffDict.setByKey(key, value); - } - return cffDict; - } - - parseCharString(state, data, localSubrIndex, globalSubrIndex) { - if (!data || state.callDepth > MAX_SUBR_NESTING) { - return false; - } - let stackSize = state.stackSize; - const stack = state.stack; - - const length = data.length; - - for (let j = 0; j < length; ) { - const value = data[j++]; - let validationCommand = null; - if (value === 12) { - const q = data[j++]; - if (q === 0) { - // The CFF specification state that the 'dotsection' command - // (12, 0) is deprecated and treated as a no-op, but all Type2 - // charstrings processors should support them. Unfortunately - // the font sanitizer don't. As a workaround the sequence (12, 0) - // is replaced by a useless (0, hmoveto). - data[j - 2] = 139; - data[j - 1] = 22; - stackSize = 0; - } else { - validationCommand = CharstringValidationData12[q]; - } - } else if (value === 28) { - // number (16 bit) - stack[stackSize] = ((data[j] << 24) | (data[j + 1] << 16)) >> 16; - j += 2; - stackSize++; - } else if (value === 14) { - if (stackSize >= 4) { - stackSize -= 4; - if (this.seacAnalysisEnabled) { - state.seac = stack.slice(stackSize, stackSize + 4); - return false; - } - } + validationCommand = CharstringValidationData[value]; + } else if (value >= 32 && value <= 246) { + // number + stack[stackSize] = value - 139; + stackSize++; + } else if (value >= 247 && value <= 254) { + // number (+1 bytes) + stack[stackSize] = + value < 251 + ? ((value - 247) << 8) + data[j] + 108 + : -((value - 251) << 8) - data[j] - 108; + j++; + stackSize++; + } else if (value === 255) { + // number (32 bit) + stack[stackSize] = + ((data[j] << 24) | + (data[j + 1] << 16) | + (data[j + 2] << 8) | + data[j + 3]) / + 65536; + j += 4; + stackSize++; + } else if (value === 19 || value === 20) { + state.hints += stackSize >> 1; + // skipping right amount of hints flag data + j += (state.hints + 7) >> 3; + stackSize %= 2; + validationCommand = CharstringValidationData[value]; + } else if (value === 10 || value === 29) { + let subrsIndex; + if (value === 10) { + subrsIndex = localSubrIndex; + } else { + subrsIndex = globalSubrIndex; + } + if (!subrsIndex) { validationCommand = CharstringValidationData[value]; - } else if (value >= 32 && value <= 246) { - // number - stack[stackSize] = value - 139; - stackSize++; - } else if (value >= 247 && value <= 254) { - // number (+1 bytes) - stack[stackSize] = - value < 251 - ? ((value - 247) << 8) + data[j] + 108 - : -((value - 251) << 8) - data[j] - 108; - j++; - stackSize++; - } else if (value === 255) { - // number (32 bit) - stack[stackSize] = - ((data[j] << 24) | - (data[j + 1] << 16) | - (data[j + 2] << 8) | - data[j + 3]) / - 65536; - j += 4; - stackSize++; - } else if (value === 19 || value === 20) { + warn("Missing subrsIndex for " + validationCommand.id); + return false; + } + let bias = 32768; + if (subrsIndex.count < 1240) { + bias = 107; + } else if (subrsIndex.count < 33900) { + bias = 1131; + } + const subrNumber = stack[--stackSize] + bias; + if ( + subrNumber < 0 || + subrNumber >= subrsIndex.count || + isNaN(subrNumber) + ) { + validationCommand = CharstringValidationData[value]; + warn("Out of bounds subrIndex for " + validationCommand.id); + return false; + } + state.stackSize = stackSize; + state.callDepth++; + const valid = this.parseCharString( + state, + subrsIndex.get(subrNumber), + localSubrIndex, + globalSubrIndex + ); + if (!valid) { + return false; + } + state.callDepth--; + stackSize = state.stackSize; + continue; + } else if (value === 11) { + state.stackSize = stackSize; + return true; + } else if (value === 0 && j === data.length) { + // Operator 0 is not used according to the current spec and + // it's the last char and consequently it's likely a terminator. + // So just replace it by endchar command to make OTS happy. + data[j - 1] = 14; + validationCommand = CharstringValidationData[14]; + } else { + validationCommand = CharstringValidationData[value]; + } + if (validationCommand) { + if (validationCommand.stem) { state.hints += stackSize >> 1; - // skipping right amount of hints flag data - j += (state.hints + 7) >> 3; - stackSize %= 2; - validationCommand = CharstringValidationData[value]; - } else if (value === 10 || value === 29) { - let subrsIndex; - if (value === 10) { - subrsIndex = localSubrIndex; - } else { - subrsIndex = globalSubrIndex; + if (value === 3 || value === 23) { + // vstem or vstemhm. + state.hasVStems = true; + } else if (state.hasVStems && (value === 1 || value === 18)) { + // Some browsers don't draw glyphs that specify vstems before + // hstems. As a workaround, replace hstem (1) and hstemhm (18) + // with a pointless vstem (3) or vstemhm (23). + warn("CFF stem hints are in wrong order"); + data[j - 1] = value === 1 ? 3 : 23; } - if (!subrsIndex) { - validationCommand = CharstringValidationData[value]; - warn("Missing subrsIndex for " + validationCommand.id); - return false; - } - let bias = 32768; - if (subrsIndex.count < 1240) { - bias = 107; - } else if (subrsIndex.count < 33900) { - bias = 1131; - } - const subrNumber = stack[--stackSize] + bias; - if ( - subrNumber < 0 || - subrNumber >= subrsIndex.count || - isNaN(subrNumber) - ) { - validationCommand = CharstringValidationData[value]; - warn("Out of bounds subrIndex for " + validationCommand.id); - return false; - } - state.stackSize = stackSize; - state.callDepth++; - const valid = this.parseCharString( - state, - subrsIndex.get(subrNumber), - localSubrIndex, - globalSubrIndex - ); - if (!valid) { - return false; - } - state.callDepth--; - stackSize = state.stackSize; - continue; - } else if (value === 11) { - state.stackSize = stackSize; - return true; - } else if (value === 0 && j === data.length) { - // Operator 0 is not used according to the current spec and - // it's the last char and consequently it's likely a terminator. - // So just replace it by endchar command to make OTS happy. - data[j - 1] = 14; - validationCommand = CharstringValidationData[14]; - } else { - validationCommand = CharstringValidationData[value]; } - if (validationCommand) { - if (validationCommand.stem) { - state.hints += stackSize >> 1; - if (value === 3 || value === 23) { - // vstem or vstemhm. - state.hasVStems = true; - } else if (state.hasVStems && (value === 1 || value === 18)) { - // Some browsers don't draw glyphs that specify vstems before - // hstems. As a workaround, replace hstem (1) and hstemhm (18) - // with a pointless vstem (3) or vstemhm (23). - warn("CFF stem hints are in wrong order"); - data[j - 1] = value === 1 ? 3 : 23; - } - } - if ("min" in validationCommand) { - if (!state.undefStack && stackSize < validationCommand.min) { - warn( - "Not enough parameters for " + - validationCommand.id + - "; actual: " + - stackSize + - ", expected: " + - validationCommand.min - ); + if ("min" in validationCommand) { + if (!state.undefStack && stackSize < validationCommand.min) { + warn( + "Not enough parameters for " + + validationCommand.id + + "; actual: " + + stackSize + + ", expected: " + + validationCommand.min + ); - if (stackSize === 0) { - // Just "fix" the outline in replacing command by a endchar: - // it could lead to wrong rendering of some glyphs or not. - // For example, the pdf in #6132 is well-rendered. - data[j - 1] = 14; - return true; - } - return false; + if (stackSize === 0) { + // Just "fix" the outline in replacing command by a endchar: + // it could lead to wrong rendering of some glyphs or not. + // For example, the pdf in #6132 is well-rendered. + data[j - 1] = 14; + return true; } + return false; } - if (state.firstStackClearing && validationCommand.stackClearing) { - state.firstStackClearing = false; - // the optional character width can be found before the first - // stack-clearing command arguments - stackSize -= validationCommand.min; - if (stackSize >= 2 && validationCommand.stem) { - // there are even amount of arguments for stem commands - stackSize %= 2; - } else if (stackSize > 1) { - warn("Found too many parameters for stack-clearing command"); - } - if (stackSize > 0) { - // Width can be any number since its the difference - // from nominalWidthX. - state.width = stack[stackSize - 1]; - } + } + if (state.firstStackClearing && validationCommand.stackClearing) { + state.firstStackClearing = false; + // the optional character width can be found before the first + // stack-clearing command arguments + stackSize -= validationCommand.min; + if (stackSize >= 2 && validationCommand.stem) { + // there are even amount of arguments for stem commands + stackSize %= 2; + } else if (stackSize > 1) { + warn("Found too many parameters for stack-clearing command"); } - if ("stackDelta" in validationCommand) { - if ("stackFn" in validationCommand) { - validationCommand.stackFn(stack, stackSize); - } - stackSize += validationCommand.stackDelta; - } else if (validationCommand.stackClearing) { - stackSize = 0; - } else if (validationCommand.resetStack) { - stackSize = 0; - state.undefStack = false; - } else if (validationCommand.undefStack) { - stackSize = 0; - state.undefStack = true; - state.firstStackClearing = false; + if (stackSize > 0) { + // Width can be any number since its the difference + // from nominalWidthX. + state.width = stack[stackSize - 1]; } } + if ("stackDelta" in validationCommand) { + if ("stackFn" in validationCommand) { + validationCommand.stackFn(stack, stackSize); + } + stackSize += validationCommand.stackDelta; + } else if (validationCommand.stackClearing) { + stackSize = 0; + } else if (validationCommand.resetStack) { + stackSize = 0; + state.undefStack = false; + } else if (validationCommand.undefStack) { + stackSize = 0; + state.undefStack = true; + state.firstStackClearing = false; + } } - state.stackSize = stackSize; - return true; } + state.stackSize = stackSize; + return true; + } - parseCharStrings({ - charStrings, - localSubrIndex, - globalSubrIndex, - fdSelect, - fdArray, - privateDict, - }) { - const seacs = []; - const widths = []; - const count = charStrings.count; - for (let i = 0; i < count; i++) { - const charstring = charStrings.get(i); - const state = { - callDepth: 0, - stackSize: 0, - stack: [], - undefStack: true, - hints: 0, - firstStackClearing: true, - seac: null, - width: null, - hasVStems: false, - }; - let valid = true; - let localSubrToUse = null; - let privateDictToUse = privateDict; - if (fdSelect && fdArray.length) { - const fdIndex = fdSelect.getFDIndex(i); - if (fdIndex === -1) { - warn("Glyph index is not in fd select."); - valid = false; - } - if (fdIndex >= fdArray.length) { - warn("Invalid fd index for glyph index."); - valid = false; - } - if (valid) { - privateDictToUse = fdArray[fdIndex].privateDict; - localSubrToUse = privateDictToUse.subrsIndex; - } - } else if (localSubrIndex) { - localSubrToUse = localSubrIndex; + parseCharStrings({ + charStrings, + localSubrIndex, + globalSubrIndex, + fdSelect, + fdArray, + privateDict, + }) { + const seacs = []; + const widths = []; + const count = charStrings.count; + for (let i = 0; i < count; i++) { + const charstring = charStrings.get(i); + const state = { + callDepth: 0, + stackSize: 0, + stack: [], + undefStack: true, + hints: 0, + firstStackClearing: true, + seac: null, + width: null, + hasVStems: false, + }; + let valid = true; + let localSubrToUse = null; + let privateDictToUse = privateDict; + if (fdSelect && fdArray.length) { + const fdIndex = fdSelect.getFDIndex(i); + if (fdIndex === -1) { + warn("Glyph index is not in fd select."); + valid = false; + } + if (fdIndex >= fdArray.length) { + warn("Invalid fd index for glyph index."); + valid = false; } if (valid) { - valid = this.parseCharString( - state, - charstring, - localSubrToUse, - globalSubrIndex - ); - } - if (state.width !== null) { - const nominalWidth = privateDictToUse.getByName("nominalWidthX"); - widths[i] = nominalWidth + state.width; - } else { - const defaultWidth = privateDictToUse.getByName("defaultWidthX"); - widths[i] = defaultWidth; - } - if (state.seac !== null) { - seacs[i] = state.seac; - } - if (!valid) { - // resetting invalid charstring to single 'endchar' - charStrings.set(i, new Uint8Array([14])); + privateDictToUse = fdArray[fdIndex].privateDict; + localSubrToUse = privateDictToUse.subrsIndex; } + } else if (localSubrIndex) { + localSubrToUse = localSubrIndex; } - return { charStrings, seacs, widths }; - } - - emptyPrivateDictionary(parentDict) { - const privateDict = this.createDict( - CFFPrivateDict, - [], - parentDict.strings - ); - parentDict.setByKey(18, [0, 0]); - parentDict.privateDict = privateDict; - } - - parsePrivateDict(parentDict) { - // no private dict, do nothing - if (!parentDict.hasName("Private")) { - this.emptyPrivateDictionary(parentDict); - return; - } - const privateOffset = parentDict.getByName("Private"); - // make sure the params are formatted correctly - if (!Array.isArray(privateOffset) || privateOffset.length !== 2) { - parentDict.removeByName("Private"); - return; - } - const size = privateOffset[0]; - const offset = privateOffset[1]; - // remove empty dicts or ones that refer to invalid location - if (size === 0 || offset >= this.bytes.length) { - this.emptyPrivateDictionary(parentDict); - return; - } - - const privateDictEnd = offset + size; - const dictData = this.bytes.subarray(offset, privateDictEnd); - const dict = this.parseDict(dictData); - const privateDict = this.createDict( - CFFPrivateDict, - dict, - parentDict.strings - ); - parentDict.privateDict = privateDict; - - // Parse the Subrs index also since it's relative to the private dict. - if (!privateDict.getByName("Subrs")) { - return; - } - const subrsOffset = privateDict.getByName("Subrs"); - const relativeOffset = offset + subrsOffset; - // Validate the offset. - if (subrsOffset === 0 || relativeOffset >= this.bytes.length) { - this.emptyPrivateDictionary(parentDict); - return; - } - const subrsIndex = this.parseIndex(relativeOffset); - privateDict.subrsIndex = subrsIndex.obj; - } - - parseCharsets(pos, length, strings, cid) { - if (pos === 0) { - return new CFFCharset( - true, - CFFCharsetPredefinedTypes.ISO_ADOBE, - ISOAdobeCharset - ); - } else if (pos === 1) { - return new CFFCharset( - true, - CFFCharsetPredefinedTypes.EXPERT, - ExpertCharset - ); - } else if (pos === 2) { - return new CFFCharset( - true, - CFFCharsetPredefinedTypes.EXPERT_SUBSET, - ExpertSubsetCharset + if (valid) { + valid = this.parseCharString( + state, + charstring, + localSubrToUse, + globalSubrIndex ); } - - const bytes = this.bytes; - const start = pos; - const format = bytes[pos++]; - const charset = [cid ? 0 : ".notdef"]; - let id, count, i; - - // subtract 1 for the .notdef glyph - length -= 1; - - switch (format) { - case 0: - for (i = 0; i < length; i++) { - id = (bytes[pos++] << 8) | bytes[pos++]; - charset.push(cid ? id : strings.get(id)); - } - break; - case 1: - while (charset.length <= length) { - id = (bytes[pos++] << 8) | bytes[pos++]; - count = bytes[pos++]; - for (i = 0; i <= count; i++) { - charset.push(cid ? id++ : strings.get(id++)); - } - } - break; - case 2: - while (charset.length <= length) { - id = (bytes[pos++] << 8) | bytes[pos++]; - count = (bytes[pos++] << 8) | bytes[pos++]; - for (i = 0; i <= count; i++) { - charset.push(cid ? id++ : strings.get(id++)); - } - } - break; - default: - throw new FormatError("Unknown charset format"); - } - // Raw won't be needed if we actually compile the charset. - const end = pos; - const raw = bytes.subarray(start, end); - - return new CFFCharset(false, format, charset, raw); - } - - parseEncoding(pos, properties, strings, charset) { - const encoding = Object.create(null); - const bytes = this.bytes; - let predefined = false; - let format, i, ii; - let raw = null; - - function readSupplement() { - const supplementsCount = bytes[pos++]; - for (i = 0; i < supplementsCount; i++) { - const code = bytes[pos++]; - const sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff); - encoding[code] = charset.indexOf(strings.get(sid)); - } - } - - if (pos === 0 || pos === 1) { - predefined = true; - format = pos; - const baseEncoding = pos ? ExpertEncoding : StandardEncoding; - for (i = 0, ii = charset.length; i < ii; i++) { - const index = baseEncoding.indexOf(charset[i]); - if (index !== -1) { - encoding[index] = i; - } - } + if (state.width !== null) { + const nominalWidth = privateDictToUse.getByName("nominalWidthX"); + widths[i] = nominalWidth + state.width; } else { - const dataStart = pos; - format = bytes[pos++]; - switch (format & 0x7f) { - case 0: - const glyphsCount = bytes[pos++]; - for (i = 1; i <= glyphsCount; i++) { - encoding[bytes[pos++]] = i; - } - break; - - case 1: - const rangesCount = bytes[pos++]; - let gid = 1; - for (i = 0; i < rangesCount; i++) { - const start = bytes[pos++]; - const left = bytes[pos++]; - for (let j = start; j <= start + left; j++) { - encoding[j] = gid++; - } - } - break; - - default: - throw new FormatError(`Unknown encoding format: ${format} in CFF`); - } - const dataEnd = pos; - if (format & 0x80) { - // hasSupplement - // The font sanitizer does not support CFF encoding with a - // supplement, since the encoding is not really used to map - // between gid to glyph, let's overwrite what is declared in - // the top dictionary to let the sanitizer think the font use - // StandardEncoding, that's a lie but that's ok. - bytes[dataStart] &= 0x7f; - readSupplement(); - } - raw = bytes.subarray(dataStart, dataEnd); + const defaultWidth = privateDictToUse.getByName("defaultWidthX"); + widths[i] = defaultWidth; + } + if (state.seac !== null) { + seacs[i] = state.seac; + } + if (!valid) { + // resetting invalid charstring to single 'endchar' + charStrings.set(i, new Uint8Array([14])); } - format &= 0x7f; - return new CFFEncoding(predefined, format, encoding, raw); - } - - parseFDSelect(pos, length) { - const bytes = this.bytes; - const format = bytes[pos++]; - const fdSelect = []; - let i; - - switch (format) { - case 0: - for (i = 0; i < length; ++i) { - const id = bytes[pos++]; - fdSelect.push(id); - } - break; - case 3: - const rangesCount = (bytes[pos++] << 8) | bytes[pos++]; - for (i = 0; i < rangesCount; ++i) { - let first = (bytes[pos++] << 8) | bytes[pos++]; - if (i === 0 && first !== 0) { - warn( - "parseFDSelect: The first range must have a first GID of 0" + - " -- trying to recover." - ); - first = 0; - } - const fdIndex = bytes[pos++]; - const next = (bytes[pos] << 8) | bytes[pos + 1]; - for (let j = first; j < next; ++j) { - fdSelect.push(fdIndex); - } - } - // Advance past the sentinel(next). - pos += 2; - break; - default: - throw new FormatError(`parseFDSelect: Unknown format "${format}".`); - } - if (fdSelect.length !== length) { - throw new FormatError("parseFDSelect: Invalid font data."); - } - - return new CFFFDSelect(format, fdSelect); } + return { charStrings, seacs, widths }; } - return CFFParser; -})(); + + emptyPrivateDictionary(parentDict) { + const privateDict = this.createDict(CFFPrivateDict, [], parentDict.strings); + parentDict.setByKey(18, [0, 0]); + parentDict.privateDict = privateDict; + } + + parsePrivateDict(parentDict) { + // no private dict, do nothing + if (!parentDict.hasName("Private")) { + this.emptyPrivateDictionary(parentDict); + return; + } + const privateOffset = parentDict.getByName("Private"); + // make sure the params are formatted correctly + if (!Array.isArray(privateOffset) || privateOffset.length !== 2) { + parentDict.removeByName("Private"); + return; + } + const size = privateOffset[0]; + const offset = privateOffset[1]; + // remove empty dicts or ones that refer to invalid location + if (size === 0 || offset >= this.bytes.length) { + this.emptyPrivateDictionary(parentDict); + return; + } + + const privateDictEnd = offset + size; + const dictData = this.bytes.subarray(offset, privateDictEnd); + const dict = this.parseDict(dictData); + const privateDict = this.createDict( + CFFPrivateDict, + dict, + parentDict.strings + ); + parentDict.privateDict = privateDict; + + // Parse the Subrs index also since it's relative to the private dict. + if (!privateDict.getByName("Subrs")) { + return; + } + const subrsOffset = privateDict.getByName("Subrs"); + const relativeOffset = offset + subrsOffset; + // Validate the offset. + if (subrsOffset === 0 || relativeOffset >= this.bytes.length) { + this.emptyPrivateDictionary(parentDict); + return; + } + const subrsIndex = this.parseIndex(relativeOffset); + privateDict.subrsIndex = subrsIndex.obj; + } + + parseCharsets(pos, length, strings, cid) { + if (pos === 0) { + return new CFFCharset( + true, + CFFCharsetPredefinedTypes.ISO_ADOBE, + ISOAdobeCharset + ); + } else if (pos === 1) { + return new CFFCharset( + true, + CFFCharsetPredefinedTypes.EXPERT, + ExpertCharset + ); + } else if (pos === 2) { + return new CFFCharset( + true, + CFFCharsetPredefinedTypes.EXPERT_SUBSET, + ExpertSubsetCharset + ); + } + + const bytes = this.bytes; + const start = pos; + const format = bytes[pos++]; + const charset = [cid ? 0 : ".notdef"]; + let id, count, i; + + // subtract 1 for the .notdef glyph + length -= 1; + + switch (format) { + case 0: + for (i = 0; i < length; i++) { + id = (bytes[pos++] << 8) | bytes[pos++]; + charset.push(cid ? id : strings.get(id)); + } + break; + case 1: + while (charset.length <= length) { + id = (bytes[pos++] << 8) | bytes[pos++]; + count = bytes[pos++]; + for (i = 0; i <= count; i++) { + charset.push(cid ? id++ : strings.get(id++)); + } + } + break; + case 2: + while (charset.length <= length) { + id = (bytes[pos++] << 8) | bytes[pos++]; + count = (bytes[pos++] << 8) | bytes[pos++]; + for (i = 0; i <= count; i++) { + charset.push(cid ? id++ : strings.get(id++)); + } + } + break; + default: + throw new FormatError("Unknown charset format"); + } + // Raw won't be needed if we actually compile the charset. + const end = pos; + const raw = bytes.subarray(start, end); + + return new CFFCharset(false, format, charset, raw); + } + + parseEncoding(pos, properties, strings, charset) { + const encoding = Object.create(null); + const bytes = this.bytes; + let predefined = false; + let format, i, ii; + let raw = null; + + function readSupplement() { + const supplementsCount = bytes[pos++]; + for (i = 0; i < supplementsCount; i++) { + const code = bytes[pos++]; + const sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff); + encoding[code] = charset.indexOf(strings.get(sid)); + } + } + + if (pos === 0 || pos === 1) { + predefined = true; + format = pos; + const baseEncoding = pos ? ExpertEncoding : StandardEncoding; + for (i = 0, ii = charset.length; i < ii; i++) { + const index = baseEncoding.indexOf(charset[i]); + if (index !== -1) { + encoding[index] = i; + } + } + } else { + const dataStart = pos; + format = bytes[pos++]; + switch (format & 0x7f) { + case 0: + const glyphsCount = bytes[pos++]; + for (i = 1; i <= glyphsCount; i++) { + encoding[bytes[pos++]] = i; + } + break; + + case 1: + const rangesCount = bytes[pos++]; + let gid = 1; + for (i = 0; i < rangesCount; i++) { + const start = bytes[pos++]; + const left = bytes[pos++]; + for (let j = start; j <= start + left; j++) { + encoding[j] = gid++; + } + } + break; + + default: + throw new FormatError(`Unknown encoding format: ${format} in CFF`); + } + const dataEnd = pos; + if (format & 0x80) { + // hasSupplement + // The font sanitizer does not support CFF encoding with a + // supplement, since the encoding is not really used to map + // between gid to glyph, let's overwrite what is declared in + // the top dictionary to let the sanitizer think the font use + // StandardEncoding, that's a lie but that's ok. + bytes[dataStart] &= 0x7f; + readSupplement(); + } + raw = bytes.subarray(dataStart, dataEnd); + } + format &= 0x7f; + return new CFFEncoding(predefined, format, encoding, raw); + } + + parseFDSelect(pos, length) { + const bytes = this.bytes; + const format = bytes[pos++]; + const fdSelect = []; + let i; + + switch (format) { + case 0: + for (i = 0; i < length; ++i) { + const id = bytes[pos++]; + fdSelect.push(id); + } + break; + case 3: + const rangesCount = (bytes[pos++] << 8) | bytes[pos++]; + for (i = 0; i < rangesCount; ++i) { + let first = (bytes[pos++] << 8) | bytes[pos++]; + if (i === 0 && first !== 0) { + warn( + "parseFDSelect: The first range must have a first GID of 0" + + " -- trying to recover." + ); + first = 0; + } + const fdIndex = bytes[pos++]; + const next = (bytes[pos] << 8) | bytes[pos + 1]; + for (let j = first; j < next; ++j) { + fdSelect.push(fdIndex); + } + } + // Advance past the sentinel(next). + pos += 2; + break; + default: + throw new FormatError(`parseFDSelect: Unknown format "${format}".`); + } + if (fdSelect.length !== length) { + throw new FormatError("parseFDSelect: Invalid font data."); + } + + return new CFFFDSelect(format, fdSelect); + } +} // Compact Font Format class CFF { @@ -1189,97 +1182,89 @@ class CFFDict { } } -const CFFTopDict = (function CFFTopDictClosure() { - const layout = [ - [[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], - // prettier-ignore - [[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], - // XXX: CID Fonts on DirectWrite 6.1 only seem to work if FDSelect comes - // before FDArray. - [[12, 37], "FDSelect", "offset", null], - [[12, 36], "FDArray", "offset", null], - [[12, 38], "FontName", "sid", null], - ]; - let tables = null; +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], + // prettier-ignore + [[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], + // XXX: CID Fonts on DirectWrite 6.1 only seem to work if FDSelect comes + // before FDArray. + [[12, 37], "FDSelect", "offset", null], + [[12, 36], "FDArray", "offset", null], + [[12, 38], "FontName", "sid", null], +]; - // eslint-disable-next-line no-shadow - class CFFTopDict extends CFFDict { - constructor(strings) { - if (tables === null) { - tables = CFFDict.createTables(layout); - } - super(tables, strings); - this.privateDict = null; - } +class CFFTopDict extends CFFDict { + static get tables() { + return shadow(this, "tables", this.createTables(CFFTopDictLayout)); } - return CFFTopDict; -})(); -const CFFPrivateDict = (function CFFPrivateDictClosure() { - const layout = [ - [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], - ]; - let tables = null; - - // eslint-disable-next-line no-shadow - class CFFPrivateDict extends CFFDict { - constructor(strings) { - if (tables === null) { - tables = CFFDict.createTables(layout); - } - super(tables, strings); - this.subrsIndex = null; - } + constructor(strings) { + super(CFFTopDict.tables, strings); + this.privateDict = null; } - return CFFPrivateDict; -})(); +} + +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 { + static get tables() { + return shadow(this, "tables", this.createTables(CFFPrivateDictLayout)); + } + + constructor(strings) { + super(CFFPrivateDict.tables, strings); + this.subrsIndex = null; + } +} const CFFCharsetPredefinedTypes = { ISO_ADOBE: 0,