From b5278c5e27df280e6ee9253773e2034991d4c545 Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Wed, 9 Jan 2013 17:33:59 -0800 Subject: [PATCH] Evaluate type 1 charstrings for conversion to type 2. --- src/fonts.js | 770 ++++++++++++++++------------------- test/pdfs/issue1936.pdf.link | 2 + test/test_manifest.json | 8 + 3 files changed, 358 insertions(+), 422 deletions(-) create mode 100644 test/pdfs/issue1936.pdf.link diff --git a/src/fonts.js b/src/fonts.js index 8177916cb..699dd1e93 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -26,7 +26,8 @@ var SYMBOLIC_FONT_GLYPH_OFFSET = 0xF000; // except for Type 3 fonts var PDF_GLYPH_SPACE_UNITS = 1000; -// Until hinting is fully supported this constant can be used +// Hinting is currently disabled due to unknown problems on windows +// in tracemonkey and various other pdfs with type1 fonts. var HINTING_ENABLED = false; var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; @@ -4351,12 +4352,324 @@ var ErrorFont = (function ErrorFontClosure() { return ErrorFont; })(); -var CallothersubrCmd = (function CallothersubrCmdClosure() { - function CallothersubrCmd(index) { - this.index = index; +/* + * CharStrings are encoded following the the CharString Encoding sequence + * describe in Chapter 6 of the "Adobe Type1 Font Format" specification. + * The value in a byte indicates a command, a number, or subsequent bytes + * that are to be interpreted in a special way. + * + * CharString Number Encoding: + * A CharString byte containing the values from 32 through 255 inclusive + * indicate an integer. These values are decoded in four ranges. + * + * 1. A CharString byte containing a value, v, between 32 and 246 inclusive, + * indicate the integer v - 139. Thus, the integer values from -107 through + * 107 inclusive may be encoded in single byte. + * + * 2. A CharString byte containing a value, v, between 247 and 250 inclusive, + * indicates an integer involving the next byte, w, according to the formula: + * [(v - 247) x 256] + w + 108 + * + * 3. A CharString byte containing a value, v, between 251 and 254 inclusive, + * indicates an integer involving the next byte, w, according to the formula: + * -[(v - 251) * 256] - w - 108 + * + * 4. A CharString containing the value 255 indicates that the next 4 bytes + * are a two complement signed integer. The first of these bytes contains the + * highest order bits, the second byte contains the next higher order bits + * and the fourth byte contain the lowest order bits. + * + * + * CharString Command Encoding: + * CharStrings commands are encoded in 1 or 2 bytes. + * + * Single byte commands are encoded in 1 byte that contains a value between + * 0 and 31 inclusive. + * If a command byte contains the value 12, then the value in the next byte + * indicates a command. This "escape" mechanism allows many extra commands + * to be encoded and this encoding technique helps to minimize the length of + * the charStrings. + */ +var Type1CharString = (function Type1CharStringClosure() { + var COMMAND_MAP = { + 'hstem': [1], + 'vstem': [3], + 'vmoveto': [4], + 'rlineto': [5], + 'hlineto': [6], + 'vlineto': [7], + 'rrcurveto': [8], + 'callsubr': [10], + 'flex': [12, 35], + 'drop' : [12, 18], + 'endchar': [14], + 'rmoveto': [21], + 'hmoveto': [22], + 'vhcurveto': [30], + 'hvcurveto': [31] + }; + + function Type1CharString() { + this.width = 0; + this.lsb = 0; + this.flexing = false; + this.output = []; + this.stack = []; } - return CallothersubrCmd; + Type1CharString.prototype = { + convert: function Type1CharString_convert(encoded, subrs) { + var count = encoded.length; + var error = false; + for (var i = 0; i < count; i++) { + var value = encoded[i]; + if (value < 32) { + if (value === 12) { + value = (value << 8) + encoded[++i]; + } + switch (value) { + case 1: // hstem + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + error = this.executeCommand(2, COMMAND_MAP.hstem); + break; + case 3: // vstem + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + error = this.executeCommand(2, COMMAND_MAP.vstem); + break; + case 4: // vmoveto + if (this.flexing) { + if (this.stack.length < 1) { + error = true; + break; + } + // Add the dx for flex and but also swap the values so they are + // the right order. + var dy = this.stack.pop(); + this.stack.push(0, dy); + break; + } + error = this.executeCommand(1, COMMAND_MAP.vmoveto); + break; + case 5: // rlineto + error = this.executeCommand(2, COMMAND_MAP.rlineto); + break; + case 6: // hlineto + error = this.executeCommand(1, COMMAND_MAP.hlineto); + break; + case 7: // vlineto + error = this.executeCommand(1, COMMAND_MAP.vlineto); + break; + case 8: // rrcurveto + error = this.executeCommand(6, COMMAND_MAP.rrcurveto); + break; + case 9: // closepath + // closepath is a Type1 command that does not take argument and is + // useless in Type2 and it can simply be ignored. + this.stack = []; + break; + case 10: // callsubr + if (this.stack.length < 1) { + error = true; + break; + } + var subrNumber = this.stack.pop(); + error = this.convert(subrs[subrNumber], subrs); + break; + case 11: // return + return error; + break; + case 13: // hsbw + if (this.stack.length < 2) { + error = true; + break; + } + // To convert to type2 we have to move the width value to the + // first part of the charstring and then use hmoveto with lsb. + var wx = this.stack.pop(); + var sbx = this.stack.pop(); + this.lsb = sbx; + this.width = wx; + this.stack.push(sbx); + error = this.executeCommand(1, COMMAND_MAP.hmoveto); + break; + case 14: // endchar + this.output.push(COMMAND_MAP.endchar[0]); + break; + case 21: // rmoveto + if (this.flexing) { + break; + } + error = this.executeCommand(2, COMMAND_MAP.rmoveto); + break; + case 22: // hmoveto + if (this.flexing) { + // Add the dy for flex. + this.stack.push(0); + break; + } + error = this.executeCommand(1, COMMAND_MAP.hmoveto); + break; + case 30: // vhcurveto + error = this.executeCommand(4, COMMAND_MAP.vhcurveto); + break; + case 31: // hvcurveto + error = this.executeCommand(4, COMMAND_MAP.hvcurveto); + break; + case (12 << 8) + 0: // dotsection + // dotsection is a Type1 command to specify some hinting feature + // for dots that do not take a parameter and it can safely be + // ignored for Type2. + this.stack = []; + break; + case (12 << 8) + 1: // vstem3 + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + // [vh]stem3 are Type1 only and Type2 supports [vh]stem with + // multiple parameters, so instead of returning [vh]stem3 take a + // shortcut and return [vhstem] instead. + error = this.executeCommand(2, COMMAND_MAP.vstem); + break; + case (12 << 8) + 2: // hstem3 + if (!HINTING_ENABLED) { + this.stack = []; + break; + } + // See vstem3. + error = this.executeCommand(2, COMMAND_MAP.hstem); + break; + case (12 << 8) + 6: // seac + // seac is like type 2's special endchar but it doesn't use the + // first argument asb, so remove it. + error = this.executeCommand(4, COMMAND_MAP.endchar); + break; + case (12 << 8) + 7: // sbw + if (this.stack.length < 4) { + error = true; + break; + } + // To convert to type2 we have to move the width value to the + // first part of the charstring and then use rmoveto with + // (dx, dy). The height argument will not be used for vmtx and + // vhea tables reconstruction -- ignoring it. + var wy = this.stack.pop(); + var wx = this.stack.pop(); + var sby = this.stack.pop(); + var sbx = this.stack.pop(); + this.lsb = sbx; + this.width = wx; + this.stack.push(sbx, sby); + error = this.executeCommand(2, COMMAND_MAP.rmoveto); + break; + case (12 << 8) + 12: // div + if (this.stack.length < 2) { + error = true; + break; + } + var num2 = this.stack.pop(); + var num1 = this.stack.pop(); + this.stack.push(num1 / num2); + break; + case (12 << 8) + 16: // callothersubr + if (this.stack.length < 2) { + error = true; + break; + } + var subrNumber = this.stack.pop(); + var numArgs = this.stack.pop(); + if (subrNumber === 0 && numArgs === 3) { + var flexArgs = this.stack.splice(this.stack.length - 17, 17); + this.stack.push( + flexArgs[2] + flexArgs[0], // bcp1x + rpx + flexArgs[3] + flexArgs[1], // bcp1y + rpy + flexArgs[4], // bcp2x + flexArgs[5], // bcp2y + flexArgs[6], // p2x + flexArgs[7], // p2y + flexArgs[8], // bcp3x + flexArgs[9], // bcp3y + flexArgs[10], // bcp4x + flexArgs[11], // bcp4y + flexArgs[12], // p3x + flexArgs[13], // p3y + flexArgs[14] // flexDepth + // 15 = finalx unused by flex + // 16 = finaly unused by flex + ); + error = this.executeCommand(13, COMMAND_MAP.flex, true); + this.flexing = false; + this.stack.push(flexArgs[15], flexArgs[16]); + } else if (subrNumber === 1 && numArgs === 0) { + this.flexing = true; + } + break; + case (12 << 8) + 17: // pop + // Ignore this since it is only used with othersubr. + break; + case (12 << 8) + 33: // setcurrentpoint + // Ignore for now. + this.stack = []; + break; + default: + warn('Unknown type 1 charstring command of "' + value + '"'); + break; + } + if (error) { + break; + } + continue; + } else if (value <= 246) { + value = value - 139; + } else if (value <= 250) { + value = ((value - 247) * 256) + encoded[++i] + 108; + } else if (value <= 254) { + value = -((value - 251) * 256) - encoded[++i] - 108; + } else { + value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 | + (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0; + } + this.stack.push(value); + } + return error; + }, + + executeCommand: function(howManyArgs, command, keepStack) { + var stackLength = this.stack.length; + if (howManyArgs > stackLength) { + return true; + } + var start = stackLength - howManyArgs; + for (var i = start; i < stackLength; i++) { + var value = this.stack[i]; + if (value === (value | 0)) { // int + this.output.push(28, (value >> 8) & 0xff, value & 0xff); + } else { // fixed point + value = (65536 * value) | 0; + this.output.push(255, + (value >> 24) & 0xFF, + (value >> 16) & 0xFF, + (value >> 8) & 0xFF, + value & 0xFF); + } + } + this.output.push.apply(this.output, command); + if (keepStack) { + this.stack.splice(start, howManyArgs); + } else { + this.stack = []; + } + return false; + } + }; + + return Type1CharString; })(); /* @@ -4387,338 +4700,6 @@ var Type1Parser = function type1Parser() { return decryptedString.slice(discardNumber); } - /* - * CharStrings are encoded following the the CharString Encoding sequence - * describe in Chapter 6 of the "Adobe Type1 Font Format" specification. - * The value in a byte indicates a command, a number, or subsequent bytes - * that are to be interpreted in a special way. - * - * CharString Number Encoding: - * A CharString byte containing the values from 32 through 255 inclusive - * indicate an integer. These values are decoded in four ranges. - * - * 1. A CharString byte containing a value, v, between 32 and 246 inclusive, - * indicate the integer v - 139. Thus, the integer values from -107 through - * 107 inclusive may be encoded in single byte. - * - * 2. A CharString byte containing a value, v, between 247 and 250 inclusive, - * indicates an integer involving the next byte, w, according to the formula: - * [(v - 247) x 256] + w + 108 - * - * 3. A CharString byte containing a value, v, between 251 and 254 inclusive, - * indicates an integer involving the next byte, w, according to the formula: - * -[(v - 251) * 256] - w - 108 - * - * 4. A CharString containing the value 255 indicates that the next 4 bytes - * are a two complement signed integer. The first of these bytes contains the - * highest order bits, the second byte contains the next higher order bits - * and the fourth byte contain the lowest order bits. - * - * - * CharString Command Encoding: - * CharStrings commands are encoded in 1 or 2 bytes. - * - * Single byte commands are encoded in 1 byte that contains a value between - * 0 and 31 inclusive. - * If a command byte contains the value 12, then the value in the next byte - * indicates a command. This "escape" mechanism allows many extra commands - * to be encoded and this encoding technique helps to minimize the length of - * the charStrings. - */ - var charStringDictionary = { - '1': 'hstem', - '3': 'vstem', - '4': 'vmoveto', - '5': 'rlineto', - '6': 'hlineto', - '7': 'vlineto', - '8': 'rrcurveto', - - // closepath is a Type1 command that do not take argument and is useless - // in Type2 and it can simply be ignored. - '9': null, // closepath - - '10': 'callsubr', - - // return is normally used inside sub-routines to tells to the execution - // flow that it can be back to normal. - // During the translation process Type1 charstrings will be flattened and - // sub-routines will be embedded directly into the charstring directly, so - // this can be ignored safely. - '11': 'return', - - '12': { - // dotsection is a Type1 command to specify some hinting feature for dots - // that do not take a parameter and it can safely be ignored for Type2. - '0': null, // dotsection - - // [vh]stem3 are Type1 only and Type2 supports [vh]stem with multiple - // parameters, so instead of returning [vh]stem3 take a shortcut and - // return [vhstem] instead. - '1': 'vstem', - '2': 'hstem', - - '6': 'endchar', // seac - // Type1 only command with command not (yet) built-in ,throw an error - '7': -1, // sbw - - '10': 'add', - '11': 'sub', - '12': 'div', - - // callothersubr is a mechanism to make calls on the postscript - // interpreter, this is not supported by Type2 charstring but hopefully - // most of the default commands can be ignored safely. - '16': 'callothersubr', - - '17': 'pop', - - // setcurrentpoint sets the current point to x, y without performing a - // moveto (this is a one shot positionning command). This is used only - // with the return of an OtherSubrs call. - // TODO Implement the OtherSubrs charstring embedding and replace this - // call by a no-op, like 2 'pop' commands for example. - '33': null // setcurrentpoint - }, - '13': 'hsbw', - '14': 'endchar', - '21': 'rmoveto', - '22': 'hmoveto', - '30': 'vhcurveto', - '31': 'hvcurveto' - }; - - var ESCAPE_CMD = 12; - - // Breaks up the stack by arguments and also calculates the value. - function breakUpArgs(stack, numArgs) { - var args = []; - var index = stack.length - 1; - for (var i = 0; i < numArgs; i++) { - if (index < 0) { - args.unshift({ arg: [0], - value: 0, - offset: 0 }); - warn('Malformed charstring stack: not enough values on stack.'); - continue; - } - var token = stack[index]; - if (token === 'div') { - var a = stack[index - 2]; - var b = stack[index - 1]; - if (!isInt(a) || !isInt(b)) { - warn('Malformed charsting stack: expected ints on stack for div.'); - a = 0; - b = 1; - } - args.unshift({ arg: [a, b, 'div'], - value: a / b, - offset: index - 2 }); - index -= 3; - } else if (isInt(token)) { - args.unshift({ arg: stack.slice(index, index + 1), - value: token, - offset: index }); - index--; - } else { - warn('Malformed charsting stack: found bad token ' + token + '.'); - } - } - return args; - } - - function decodeCharString(array) { - var charstring = []; - var lsb = 0; - var width = 0; - var flexing = false; - - var value = ''; - var count = array.length; - for (var i = 0; i < count; i++) { - value = array[i]; - - if (value < 32) { - var command = null; - if (value == ESCAPE_CMD) { - var escape = array[++i]; - - // TODO Clean this code - if (escape == 16) { - var index = charstring.pop(); - var argc = charstring.pop(); - for (var j = 0; j < argc; j++) - charstring.push('drop'); - - // If the flex mechanism is not used in a font program, Adobe - // states that entries 0, 1 and 2 can simply be replaced by - // {}, which means that we can simply ignore them. - if (index < 3) { - continue; - } - - // This is the same things about hint replacement, if it is not used - // entry 3 can be replaced by {3} - // TODO support hint replacment - if (index == 3) { - charstring.push(3); - i++; - continue; - } - - assert(argc == 0, 'callothersubr with arguments is not supported'); - charstring.push(new CallothersubrCmd(index)); - continue; - } else if (escape == 7) { // sbw - var args = breakUpArgs(charstring, 4); - var arg0 = args[0]; - var arg1 = args[1]; - var arg2 = args[2]; - lsb = arg0.value; - width = arg2.value; - // To convert to type2 we have to move the width value to the first - // part of the charstring and then use rmoveto with (dx, dy). - // The height argument will not be used for vmtx and vhea tables - // reconstruction -- ignoring it. - charstring = arg2.arg; - charstring = charstring.concat(arg0.arg, arg1.arg); - charstring.push('rmoveto'); - continue; - } else if (escape == 17 || escape == 33) { - // pop or setcurrentpoint commands can be ignored - // since we are not doing callothersubr - continue; - } else if (escape == 6) { - // seac is like type 2's special endchar but it doesn't use the - // first argument asb, so remove it. - var args = breakUpArgs(charstring, 5); - var arg0 = args[0]; - charstring.splice(arg0.offset, arg0.arg.length); - } else if (!HINTING_ENABLED && (escape == 1 || escape == 2)) { - charstring.push('drop', 'drop', 'drop', 'drop', 'drop', 'drop'); - continue; - } - - command = charStringDictionary['12'][escape]; - } else { - if (value == 13) { // hsbw - var args = breakUpArgs(charstring, 2); - var arg0 = args[0]; - var arg1 = args[1]; - lsb = arg0.value; - width = arg1.value; - // To convert to type2 we have to move the width value to the first - // part of the charstring and then use hmoveto with lsb. - charstring = arg1.arg; - charstring = charstring.concat(arg0.arg); - charstring.push('hmoveto'); - continue; - } else if (value == 10) { // callsubr - if (charstring[charstring.length - 1] < 3) { // subr #0..2 - // XXX: According to the spec if flex or hinting is not used then - // subroutines 0-3 can actually be anything defined by the font, - // so we really shouldn't be doing flex here but when - // callothersubr 0-2 is used. There hasn't been a real world - // example of this yet so we'll keep doing it here. - var subrNumber = charstring.pop(); - switch (subrNumber) { - case 1: - flexing = true; // prepare for flex coordinates - break; - case 0: - var flexArgs = breakUpArgs(charstring, 17); - - // removing all flex arguments from the stack - charstring.splice(flexArgs[0].offset, - charstring.length - flexArgs[0].offset); - - charstring = charstring.concat( - flexArgs[0].arg, // bcp1x + - flexArgs[2].arg, // rpx - ['add'], - flexArgs[1].arg, // bcp1y + - flexArgs[3].arg, // rpy - ['add'], - flexArgs[4].arg, // bcp2x - flexArgs[5].arg, // bcp2y - flexArgs[6].arg, // p2x - flexArgs[7].arg, // p2y - flexArgs[8].arg, // bcp3x - flexArgs[9].arg, // bcp3y - flexArgs[10].arg, // bcp4x - flexArgs[11].arg, // bcp4y - flexArgs[12].arg, // p3x - flexArgs[13].arg, // p3y - flexArgs[14].arg, // flexDepth - // 15 = finalx unused by flex - // 16 = finaly unused by flex - ['flex'] - ); - - flexing = false; - break; - } - continue; - } - } else if (value == 21 && flexing) { // rmoveto - continue; // ignoring rmoveto - } else if (value == 22 && flexing) { // hmoveto - // Add the dy for flex. - charstring.push(0); - continue; // ignoring hmoveto - } else if (value == 4 && flexing) { // vmoveto - // Insert the dx for flex before dy. - var flexArgs = breakUpArgs(charstring, 1); - charstring.splice(flexArgs[0].offset, 0, 0); - continue; // ignoring vmoveto - } else if (!HINTING_ENABLED && (value == 1 || value == 3)) { - charstring.push('drop', 'drop'); - continue; - } - command = charStringDictionary[value]; - } - - // Some charstring commands are meaningless in Type2 and will return - // a null, let's just ignored them - if (!command && i < count) { - continue; - } else if (!command) { - break; - } else if (command == -1) { - warn('Support for Type1 command ' + value + - ' (' + escape + ') is not implemented in charstring: ' + - charstring); - if (value == 12) { - // we know how to ignore only some the Type1 commands - switch (escape) { - case 7: - charstring.push('drop', 'drop', 'drop', 'drop'); - continue; - case 8: - charstring.push('drop'); - continue; - } - } - } - - value = command; - } else if (value <= 246) { - value = value - 139; - } else if (value <= 250) { - value = ((value - 247) * 256) + array[++i] + 108; - } else if (value <= 254) { - value = -((value - 251) * 256) - array[++i] - 108; - } else { - value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 | - (array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0; - } - - charstring.push(value); - } - - return { charstring: charstring, width: width, lsb: lsb }; - } - /* * Returns an object containing a Subrs array and a CharStrings * array extracted from and eexec encrypted block of data @@ -4786,6 +4767,7 @@ var Type1Parser = function type1Parser() { eexecStr += String.fromCharCode(eexec[i]); var glyphsSection = false, subrsSection = false; + var subrs = [], charstrings = []; var program = { subrs: [], charstrings: [], @@ -4821,17 +4803,14 @@ var Type1Parser = function type1Parser() { var data = eexec.slice(i, i + length); var lenIV = program.properties.privateData['lenIV']; var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV); - var str = decodeCharString(encoded); if (glyphsSection) { - program.charstrings.push({ + charstrings.push({ glyph: glyph, - data: str.charstring, - lsb: str.lsb, - width: str.width + encoded: encoded }); } else { - program.subrs.push(str.charstring); + subrs.push(encoded); } i += length; token = ''; @@ -4863,12 +4842,11 @@ var Type1Parser = function type1Parser() { var data = eexec.slice(i + 1, i + 1 + length); var lenIV = program.properties.privateData['lenIV']; var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV); - var str = decodeCharString(encoded); i = i + 1 + length; t = getToken(); // read in 'NP' if (t == 'noaccess') getToken(); // read in 'put' - program.subrs[index] = str.charstring; + subrs[index] = encoded; } break; case '/BlueValues': @@ -4915,6 +4893,26 @@ var Type1Parser = function type1Parser() { } } + for (var i = 0; i < charstrings.length; i++) { + var glyph = charstrings[i].glyph; + var encoded = charstrings[i].encoded; + var charString = new Type1CharString(); + var error = charString.convert(encoded, subrs); + var output = charString.output; + if (error) { + // It seems when FreeType encounters an error while evaluating a glyph + // that it completely ignores the glyph so we'll mimic that behaviour + // here and put an endchar to make the validator happy. + output = [14]; + } + program.charstrings.push({ + glyph: glyph, + data: output, + lsb: charString.lsb, + width: charString.width + }); + } + return program; }; @@ -5106,14 +5104,11 @@ Type1Font.prototype = { }, getType2Charstrings: function Type1Font_getType2Charstrings( - type1Subrs) { + type1Charstrings) { var type2Charstrings = []; - var count = type1Subrs.length; - var type1Charstrings = []; - for (var i = 0; i < count; i++) - type1Charstrings.push(type1Subrs[i].charstring.slice()); - for (var i = 0; i < count; i++) - type2Charstrings.push(this.flattenCharstring(type1Charstrings, i)); + for (var i = 0, ii = type1Charstrings.length; i < ii; i++) { + type2Charstrings.push(type1Charstrings[i].charstring); + } return type2Charstrings; }, @@ -5133,81 +5128,12 @@ Type1Font.prototype = { type2Subrs.push([0x0B]); for (var i = 0; i < count; i++) { - type2Subrs.push(this.flattenCharstring(type1Subrs, i)); + type2Subrs.push(type1Subrs[i]); } return type2Subrs; }, - /* - * Flatten the commands by interpreting the postscript code and replacing - * every 'callsubr', 'callothersubr' by the real commands. - */ - commandsMap: { - 'hstem': 1, - 'vstem': 3, - 'vmoveto': 4, - 'rlineto': 5, - 'hlineto': 6, - 'vlineto': 7, - 'rrcurveto': 8, - 'callsubr': 10, - 'return': 11, - 'add': [12, 10], - 'sub': [12, 11], - 'div': [12, 12], - 'exch': [12, 28], - 'flex': [12, 35], - 'drop' : [12, 18], - 'endchar': 14, - 'rmoveto': 21, - 'hmoveto': 22, - 'vhcurveto': 30, - 'hvcurveto': 31 - }, - - flattenCharstring: function Type1Font_flattenCharstring(charstrings, index) { - var charstring = charstrings[index]; - if (!charstring) - return [0x0B]; - var map = this.commandsMap; - // charstring changes size - can't cache .length in loop - for (var i = 0; i < charstring.length; i++) { - var command = charstring[i]; - if (typeof command === 'string') { - var cmd = map[command]; - assert(cmd, 'Unknow command: ' + command); - - if (isArray(cmd)) - charstring.splice(i++, 1, cmd[0], cmd[1]); - else - charstring[i] = cmd; - } else if (command instanceof CallothersubrCmd) { - var otherSubrCharstring = charstrings[command.index]; - if (otherSubrCharstring) { - var lastCommand = otherSubrCharstring.indexOf('return'); - if (lastCommand >= 0) - otherSubrCharstring = otherSubrCharstring.slice(0, lastCommand); - charstring.splice.apply(charstring, - [i, 1].concat(otherSubrCharstring)); - } else - charstring.splice(i, 1); // ignoring empty subr call - i--; - } else { - // Type1 charstring use a division for number above 32000 - if (command > 32000) { - var divisor = charstring[i + 1]; - command /= divisor; - charstring.splice(i, 3, 28, (command >> 8) & 0xff, command & 0xff); - } else { - charstring.splice(i, 1, 28, (command >> 8) & 0xff, command & 0xff); - } - i += 2; - } - } - return charstring; - }, - wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) { var cff = new CFF(); cff.header = new CFFHeader(1, 0, 4, 4); diff --git a/test/pdfs/issue1936.pdf.link b/test/pdfs/issue1936.pdf.link new file mode 100644 index 000000000..198c7a0a4 --- /dev/null +++ b/test/pdfs/issue1936.pdf.link @@ -0,0 +1,2 @@ +http://sci2s.ugr.es/keel/pdf/specific/articulo/alp99.pdf + diff --git a/test/test_manifest.json b/test/test_manifest.json index 62e5e2c01..1edd33fdd 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -776,6 +776,14 @@ "link": true, "type": "eq" }, + { "id": "issue1936", + "file": "pdfs/issue1936.pdf", + "md5": "7302eb9b6a626308e2a933aaed9e1756", + "rounds": 1, + "pageLimit": 1, + "link": true, + "type": "eq" + }, { "id": "issue2337", "file": "pdfs/issue2337.pdf", "md5": "ea10f4131202b9b8f2a6cb7770d3f185",