diff --git a/src/fonts.js b/src/fonts.js index 3f8928cc4..422806798 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -5212,6 +5212,81 @@ var CFFFont = (function CFFFontClosure() { })(); var CFFParser = (function CFFParserClosure() { + var CharstringValidationData = [ + null, + { id: 'hstem', min: 2, resetStack: true }, + null, + { id: 'vstem', min: 2, resetStack: true }, + { id: 'vmoveto', min: 1, resetStack: 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, resetStack: true }, + null, // 12 + null, + null, // endchar + null, + null, + null, + { id: 'hstemhm', min: 2, resetStack: true }, + null, // hintmask + null, // cntrmask + { id: 'rmoveto', min: 2, resetStack: true }, + { id: 'hmoveto', min: 1, resetStack: true }, + { id: 'vstemhm', min: 2, resetStack: 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 } + ]; + var CharstringValidationData12 = [ + null, + null, + null, + { id: 'and', min: 2, stackDelta: -1 }, + { id: 'or', min: 2, stackDelta: -1 }, + { id: 'not', min: 2, stackDelta: -1 }, + null, + null, + null, + { id: 'abs', min: 1, stackDelta: 0 }, + { id: 'add', min: 2, stackDelta: -1 }, + { id: 'sub', min: 2, stackDelta: -1 }, + { id: 'div', min: 2, stackDelta: -1 }, + null, + { id: 'neg', min: 1, stackDelta: 0 }, + { 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 }, + 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 } + ]; + function CFFParser(file, properties) { this.bytes = file.getBytes(); this.properties = properties; @@ -5472,29 +5547,83 @@ var CFFParser = (function CFFParserClosure() { }, parseCharStrings: function CFFParser_parseCharStrings(charStringOffset) { var charStrings = this.parseIndex(charStringOffset).obj; - // 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). var count = charStrings.count; for (var i = 0; i < count; i++) { var charstring = charStrings.get(i); + var stackSize = 0; + var undefStack = true; + var hints = 0; + var valid = true; var data = charstring; var length = data.length; - for (var j = 0; j <= length;) { + for (var j = 0; j < length;) { var value = data[j++]; - if (value == 12 && data[j++] == 0) { + var validationCommand = null; + if (value == 12) { + var 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; - } else if (value === 28) { + stackSize = 0; + } else { + validationCommand = CharstringValidationData12[q]; + } + } else if (value === 28) { // number (16 bit) j += 2; - } else if (value >= 247 && value <= 254) { + stackSize++; + } else if (value == 14) { + if (stackSize >= 4) { + // TODO fix deprecated endchar construct for Windows + stackSize -= 4; + } + } else if (value >= 32 && value <= 246) { // number + stackSize++; + } else if (value >= 247 && value <= 254) { // number (+1 bytes) j++; - } else if (value == 255) { + stackSize++; + } else if (value == 255) { // number (32 bit) j += 4; + stackSize++; + } else if (value == 18 || value == 23) { + hints += stackSize >> 1; + validationCommand = CharstringValidationData[value]; + } else if (value == 19 || value == 20) { + hints += stackSize >> 1; + j += (hints + 7) >> 3; // skipping right amount of hints flag data + stackSize = 0; + } else { + validationCommand = CharstringValidationData[value]; } + if (validationCommand) { + if ('min' in validationCommand) { + if (!undefStack && stackSize < validationCommand.min) { + warn('Not enough parameters for ' + validationCommand.id + + '; actual: ' + stackSize + + ', expected: ' + validationCommand.min); + valid = false; + break; + } + } + if ('stackDelta' in validationCommand) { + stackSize += validationCommand.stackDelta; + } else if (validationCommand.resetStack) { + stackSize = 0; + undefStack = false; + } else if (validationCommand.undefStack) { + stackSize = 0; + undefStack = true; + } + } + } + if (!valid) { + // resetting invalid charstring to single 'endchar' + charStrings.set(i, new Uint8Array([14])); } } return charStrings; @@ -5760,6 +5889,10 @@ var CFFIndex = (function CFFIndexClosure() { this.length += data.length; this.objects.push(data); }, + set: function CFFIndex_set(index, data) { + this.length += data.length - this.objects[index].length; + this.objects[index] = data; + }, get: function CFFIndex_get(index) { return this.objects[index]; },