diff --git a/src/core/fonts.js b/src/core/fonts.js index 78df52e50..7acbd0d83 100644 --- a/src/core/fonts.js +++ b/src/core/fonts.js @@ -22,26 +22,26 @@ 'pdfjs/core/glyphlist', 'pdfjs/core/charsets', 'pdfjs/core/font_renderer', 'pdfjs/core/encodings', 'pdfjs/core/standard_fonts', 'pdfjs/core/unicode', - 'pdfjs/core/cff_parser'], factory); + 'pdfjs/core/type1_parser', 'pdfjs/core/cff_parser'], factory); } else if (typeof exports !== 'undefined') { factory(exports, require('../shared/util.js'), require('./primitives.js'), require('./stream.js'), require('./parser.js'), require('./glyphlist.js'), require('./charsets.js'), require('./font_renderer.js'), require('./encodings.js'), require('./standard_fonts'), require('./unicode.js'), - require('./cff_parser')); + require('./type1_parser'), require('./cff_parser')); } else { factory((root.pdfjsCoreFonts = {}), root.pdfjsSharedUtil, root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreParser, root.pdfjsCoreGlyphList, root.pdfjsCoreCharsets, root.pdfjsCoreFontRenderer, root.pdfjsCoreEncodings, root.pdfjsCoreStandardFonts, root.pdfjsCoreUnicode, - root.pdfjsCoreCFFParser); + root.pdfjsCoreType1Parser, root.pdfjsCoreCFFParser); } }(this, function (exports, sharedUtil, corePrimitives, coreStream, coreParser, coreGlyphList, coreCharsets, coreFontRenderer, coreEncodings, coreStandardFonts, coreUnicode, - coreCFFParser) { + coreType1Parser, coreCFFParser) { var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX; var FontType = sharedUtil.FontType; @@ -82,6 +82,7 @@ var getSupplementalGlyphMapForArialBlack = var getUnicodeRangeFor = coreUnicode.getUnicodeRangeFor; var mapSpecialUnicodeValues = coreUnicode.mapSpecialUnicodeValues; var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph; +var Type1Parser = coreType1Parser.Type1Parser; var CFFStandardStrings = coreCFFParser.CFFStandardStrings; var CFFParser = coreCFFParser.CFFParser; var CFFCompiler = coreCFFParser.CFFCompiler; @@ -102,10 +103,6 @@ var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false; // except for Type 3 fonts var PDF_GLYPH_SPACE_UNITS = 1000; -// Hinting is currently disabled due to unknown problems on windows -// in tracemonkey and various other pdfs with type1 fonts. -var HINTING_ENABLED = false; - // Accented charactars are not displayed properly on Windows, using this flag // to control analysis of seac charstrings. var SEAC_ANALYSIS_ENABLED = false; @@ -2885,681 +2882,6 @@ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) { return charCodeToGlyphId; } -/* - * 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 = []; - } - - Type1CharString.prototype = { - convert: function Type1CharString_convert(encoded, subrs) { - var count = encoded.length; - var error = false; - var wx, sbx, subrNumber; - 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; - } - subrNumber = this.stack.pop(); - error = this.convert(subrs[subrNumber], subrs); - break; - case 11: // return - return error; - 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. - wx = this.stack.pop(); - sbx = this.stack.pop(); - this.lsb = sbx; - this.width = wx; - this.stack.push(wx, sbx); - error = this.executeCommand(2, 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. - if (SEAC_ANALYSIS_ENABLED) { - this.seac = this.stack.splice(-4, 4); - error = this.executeCommand(0, COMMAND_MAP.endchar); - } else { - 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(); - wx = this.stack.pop(); - var sby = this.stack.pop(); - sbx = this.stack.pop(); - this.lsb = sbx; - this.width = wx; - this.stack.push(wx, sbx, sby); - error = this.executeCommand(3, 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; - } - 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.length = 0; - } - return false; - } - }; - - return Type1CharString; -})(); - -/* - * Type1Parser encapsulate the needed code for parsing a Type1 font - * program. Some of its logic depends on the Type2 charstrings - * structure. - * Note: this doesn't really parse the font since that would require evaluation - * of PostScript, but it is possible in most cases to extract what we need - * without a full parse. - */ -var Type1Parser = (function Type1ParserClosure() { - /* - * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence - * of Plaintext Bytes. The function took a key as a parameter which can be - * for decrypting the eexec block of for decoding charStrings. - */ - var EEXEC_ENCRYPT_KEY = 55665; - var CHAR_STRS_ENCRYPT_KEY = 4330; - - function isHexDigit(code) { - return code >= 48 && code <= 57 || // '0'-'9' - code >= 65 && code <= 70 || // 'A'-'F' - code >= 97 && code <= 102; // 'a'-'f' - } - - function decrypt(data, key, discardNumber) { - if (discardNumber >= data.length) { - return new Uint8Array(0); - } - var r = key | 0, c1 = 52845, c2 = 22719, i, j; - for (i = 0; i < discardNumber; i++) { - r = ((data[i] + r) * c1 + c2) & ((1 << 16) - 1); - } - var count = data.length - discardNumber; - var decrypted = new Uint8Array(count); - for (i = discardNumber, j = 0; j < count; i++, j++) { - var value = data[i]; - decrypted[j] = value ^ (r >> 8); - r = ((value + r) * c1 + c2) & ((1 << 16) - 1); - } - return decrypted; - } - - function decryptAscii(data, key, discardNumber) { - var r = key | 0, c1 = 52845, c2 = 22719; - var count = data.length, maybeLength = count >>> 1; - var decrypted = new Uint8Array(maybeLength); - var i, j; - for (i = 0, j = 0; i < count; i++) { - var digit1 = data[i]; - if (!isHexDigit(digit1)) { - continue; - } - i++; - var digit2; - while (i < count && !isHexDigit(digit2 = data[i])) { - i++; - } - if (i < count) { - var value = parseInt(String.fromCharCode(digit1, digit2), 16); - decrypted[j++] = value ^ (r >> 8); - r = ((value + r) * c1 + c2) & ((1 << 16) - 1); - } - } - return Array.prototype.slice.call(decrypted, discardNumber, j); - } - - function isSpecial(c) { - return c === 0x2F || // '/' - c === 0x5B || c === 0x5D || // '[', ']' - c === 0x7B || c === 0x7D || // '{', '}' - c === 0x28 || c === 0x29; // '(', ')' - } - - function Type1Parser(stream, encrypted) { - if (encrypted) { - var data = stream.getBytes(); - var isBinary = !(isHexDigit(data[0]) && isHexDigit(data[1]) && - isHexDigit(data[2]) && isHexDigit(data[3])); - stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) : - decryptAscii(data, EEXEC_ENCRYPT_KEY, 4)); - } - this.stream = stream; - this.nextChar(); - } - - Type1Parser.prototype = { - readNumberArray: function Type1Parser_readNumberArray() { - this.getToken(); // read '[' or '{' (arrays can start with either) - var array = []; - while (true) { - var token = this.getToken(); - if (token === null || token === ']' || token === '}') { - break; - } - array.push(parseFloat(token || 0)); - } - return array; - }, - - readNumber: function Type1Parser_readNumber() { - var token = this.getToken(); - return parseFloat(token || 0); - }, - - readInt: function Type1Parser_readInt() { - // Use '| 0' to prevent setting a double into length such as the double - // does not flow into the loop variable. - var token = this.getToken(); - return parseInt(token || 0, 10) | 0; - }, - - readBoolean: function Type1Parser_readBoolean() { - var token = this.getToken(); - - // Use 1 and 0 since that's what type2 charstrings use. - return token === 'true' ? 1 : 0; - }, - - nextChar : function Type1_nextChar() { - return (this.currentChar = this.stream.getByte()); - }, - - getToken: function Type1Parser_getToken() { - // Eat whitespace and comments. - var comment = false; - var ch = this.currentChar; - while (true) { - if (ch === -1) { - return null; - } - - if (comment) { - if (ch === 0x0A || ch === 0x0D) { - comment = false; - } - } else if (ch === 0x25) { // '%' - comment = true; - } else if (!Lexer.isSpace(ch)) { - break; - } - ch = this.nextChar(); - } - if (isSpecial(ch)) { - this.nextChar(); - return String.fromCharCode(ch); - } - var token = ''; - do { - token += String.fromCharCode(ch); - ch = this.nextChar(); - } while (ch >= 0 && !Lexer.isSpace(ch) && !isSpecial(ch)); - return token; - }, - - /* - * Returns an object containing a Subrs array and a CharStrings - * array extracted from and eexec encrypted block of data - */ - extractFontProgram: function Type1Parser_extractFontProgram() { - var stream = this.stream; - - var subrs = [], charstrings = []; - var privateData = Object.create(null); - privateData['lenIV'] = 4; - var program = { - subrs: [], - charstrings: [], - properties: { - 'privateData': privateData - } - }; - var token, length, data, lenIV, encoded; - while ((token = this.getToken()) !== null) { - if (token !== '/') { - continue; - } - token = this.getToken(); - switch (token) { - case 'CharStrings': - // The number immediately following CharStrings must be greater or - // equal to the number of CharStrings. - this.getToken(); - this.getToken(); // read in 'dict' - this.getToken(); // read in 'dup' - this.getToken(); // read in 'begin' - while(true) { - token = this.getToken(); - if (token === null || token === 'end') { - break; - } - - if (token !== '/') { - continue; - } - var glyph = this.getToken(); - length = this.readInt(); - this.getToken(); // read in 'RD' or '-|' - data = stream.makeSubStream(stream.pos, length); - lenIV = program.properties.privateData['lenIV']; - encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV); - // Skip past the required space and binary data. - stream.skip(length); - this.nextChar(); - token = this.getToken(); // read in 'ND' or '|-' - if (token === 'noaccess') { - this.getToken(); // read in 'def' - } - charstrings.push({ - glyph: glyph, - encoded: encoded - }); - } - break; - case 'Subrs': - var num = this.readInt(); - this.getToken(); // read in 'array' - while ((token = this.getToken()) === 'dup') { - var index = this.readInt(); - length = this.readInt(); - this.getToken(); // read in 'RD' or '-|' - data = stream.makeSubStream(stream.pos, length); - lenIV = program.properties.privateData['lenIV']; - encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV); - // Skip past the required space and binary data. - stream.skip(length); - this.nextChar(); - token = this.getToken(); // read in 'NP' or '|' - if (token === 'noaccess') { - this.getToken(); // read in 'put' - } - subrs[index] = encoded; - } - break; - case 'BlueValues': - case 'OtherBlues': - case 'FamilyBlues': - case 'FamilyOtherBlues': - var blueArray = this.readNumberArray(); - // *Blue* values may contain invalid data: disables reading of - // those values when hinting is disabled. - if (blueArray.length > 0 && (blueArray.length % 2) === 0 && - HINTING_ENABLED) { - program.properties.privateData[token] = blueArray; - } - break; - case 'StemSnapH': - case 'StemSnapV': - program.properties.privateData[token] = this.readNumberArray(); - break; - case 'StdHW': - case 'StdVW': - program.properties.privateData[token] = - this.readNumberArray()[0]; - break; - case 'BlueShift': - case 'lenIV': - case 'BlueFuzz': - case 'BlueScale': - case 'LanguageGroup': - case 'ExpansionFactor': - program.properties.privateData[token] = this.readNumber(); - break; - case 'ForceBold': - program.properties.privateData[token] = this.readBoolean(); - break; - } - } - - for (var i = 0; i < charstrings.length; i++) { - glyph = charstrings[i].glyph; - 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({ - glyphName: glyph, - charstring: output, - width: charString.width, - lsb: charString.lsb, - seac: charString.seac - }); - } - - return program; - }, - - extractFontHeader: function Type1Parser_extractFontHeader(properties) { - var token; - while ((token = this.getToken()) !== null) { - if (token !== '/') { - continue; - } - token = this.getToken(); - switch (token) { - case 'FontMatrix': - var matrix = this.readNumberArray(); - properties.fontMatrix = matrix; - break; - case 'Encoding': - var encodingArg = this.getToken(); - var encoding; - if (!/^\d+$/.test(encodingArg)) { - // encoding name is specified - encoding = getEncoding(encodingArg); - } else { - encoding = []; - var size = parseInt(encodingArg, 10) | 0; - this.getToken(); // read in 'array' - - for (var j = 0; j < size; j++) { - token = this.getToken(); - // skipping till first dup or def (e.g. ignoring for statement) - while (token !== 'dup' && token !== 'def') { - token = this.getToken(); - if (token === null) { - return; // invalid header - } - } - if (token === 'def') { - break; // read all array data - } - var index = this.readInt(); - this.getToken(); // read in '/' - var glyph = this.getToken(); - encoding[index] = glyph; - this.getToken(); // read the in 'put' - } - } - properties.builtInEncoding = encoding; - break; - case 'FontBBox': - var fontBBox = this.readNumberArray(); - // adjusting ascent/descent - properties.ascent = fontBBox[3]; - properties.descent = fontBBox[1]; - properties.ascentScaled = true; - break; - } - } - } - }; - - return Type1Parser; -})(); - // Type1Font is also a CIDFontType0. var Type1Font = (function Type1FontClosure() { function findBlock(streamBytes, signature, startIndex) { @@ -3694,7 +3016,8 @@ var Type1Font = (function Type1FontClosure() { // Get the data block containing glyphs and subrs informations var headerBlock = getHeaderBlock(file, headerBlockLength); headerBlockLength = headerBlock.length; - var headerBlockParser = new Type1Parser(headerBlock.stream); + var headerBlockParser = new Type1Parser(headerBlock.stream, false, + SEAC_ANALYSIS_ENABLED); headerBlockParser.extractFontHeader(properties); if (pfbHeaderPresent) { @@ -3706,7 +3029,8 @@ var Type1Font = (function Type1FontClosure() { // Decrypt the data blocks and retrieve it's content var eexecBlock = getEexecBlock(file, eexecBlockLength); eexecBlockLength = eexecBlock.length; - var eexecBlockParser = new Type1Parser(eexecBlock.stream, true); + var eexecBlockParser = new Type1Parser(eexecBlock.stream, true, + SEAC_ANALYSIS_ENABLED); var data = eexecBlockParser.extractFontProgram(); for (var info in data.properties) { properties[info] = data.properties[info]; @@ -3992,6 +3316,5 @@ exports.Font = Font; exports.FontFlags = FontFlags; exports.IdentityToUnicodeMap = IdentityToUnicodeMap; exports.ToUnicodeMap = ToUnicodeMap; -exports.Type1Parser = Type1Parser; exports.getFontType = getFontType; })); diff --git a/src/core/type1_parser.js b/src/core/type1_parser.js new file mode 100644 index 000000000..20cfabeca --- /dev/null +++ b/src/core/type1_parser.js @@ -0,0 +1,722 @@ +/* Copyright 2012 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define('pdfjs/core/type1_parser', ['exports', 'pdfjs/shared/util', + 'pdfjs/core/stream', 'pdfjs/core/parser', 'pdfjs/core/encodings'], + factory); + } else if (typeof exports !== 'undefined') { + factory(exports, require('../shared/util.js'), require('./stream.js'), + require('./parser.js'), require('./encodings.js')); + } else { + factory((root.pdfjsCoreType1Parser = {}), root.pdfjsSharedUtil, + root.pdfjsCoreStream, root.pdfjsCoreParser, root.pdfjsCoreEncodings); + } +}(this, function (exports, sharedUtil, coreStream, coreParser, coreEncodings) { + +var warn = sharedUtil.warn; +var Stream = coreStream.Stream; +var Lexer = coreParser.Lexer; +var getEncoding = coreEncodings.getEncoding; + +// Hinting is currently disabled due to unknown problems on windows +// in tracemonkey and various other pdfs with type1 fonts. +var HINTING_ENABLED = false; + +/* + * 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 = []; + } + + Type1CharString.prototype = { + convert: function Type1CharString_convert(encoded, subrs, + seacAnalysisEnabled) { + var count = encoded.length; + var error = false; + var wx, sbx, subrNumber; + 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; + } + subrNumber = this.stack.pop(); + error = this.convert(subrs[subrNumber], subrs, + seacAnalysisEnabled); + break; + case 11: // return + return error; + 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. + wx = this.stack.pop(); + sbx = this.stack.pop(); + this.lsb = sbx; + this.width = wx; + this.stack.push(wx, sbx); + error = this.executeCommand(2, 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. + if (seacAnalysisEnabled) { + this.seac = this.stack.splice(-4, 4); + error = this.executeCommand(0, COMMAND_MAP.endchar); + } else { + 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(); + wx = this.stack.pop(); + var sby = this.stack.pop(); + sbx = this.stack.pop(); + this.lsb = sbx; + this.width = wx; + this.stack.push(wx, sbx, sby); + error = this.executeCommand(3, 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; + } + 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.length = 0; + } + return false; + } + }; + + return Type1CharString; +})(); + +/* + * Type1Parser encapsulate the needed code for parsing a Type1 font + * program. Some of its logic depends on the Type2 charstrings + * structure. + * Note: this doesn't really parse the font since that would require evaluation + * of PostScript, but it is possible in most cases to extract what we need + * without a full parse. + */ +var Type1Parser = (function Type1ParserClosure() { + /* + * Decrypt a Sequence of Ciphertext Bytes to Produce the Original Sequence + * of Plaintext Bytes. The function took a key as a parameter which can be + * for decrypting the eexec block of for decoding charStrings. + */ + var EEXEC_ENCRYPT_KEY = 55665; + var CHAR_STRS_ENCRYPT_KEY = 4330; + + function isHexDigit(code) { + return code >= 48 && code <= 57 || // '0'-'9' + code >= 65 && code <= 70 || // 'A'-'F' + code >= 97 && code <= 102; // 'a'-'f' + } + + function decrypt(data, key, discardNumber) { + if (discardNumber >= data.length) { + return new Uint8Array(0); + } + var r = key | 0, c1 = 52845, c2 = 22719, i, j; + for (i = 0; i < discardNumber; i++) { + r = ((data[i] + r) * c1 + c2) & ((1 << 16) - 1); + } + var count = data.length - discardNumber; + var decrypted = new Uint8Array(count); + for (i = discardNumber, j = 0; j < count; i++, j++) { + var value = data[i]; + decrypted[j] = value ^ (r >> 8); + r = ((value + r) * c1 + c2) & ((1 << 16) - 1); + } + return decrypted; + } + + function decryptAscii(data, key, discardNumber) { + var r = key | 0, c1 = 52845, c2 = 22719; + var count = data.length, maybeLength = count >>> 1; + var decrypted = new Uint8Array(maybeLength); + var i, j; + for (i = 0, j = 0; i < count; i++) { + var digit1 = data[i]; + if (!isHexDigit(digit1)) { + continue; + } + i++; + var digit2; + while (i < count && !isHexDigit(digit2 = data[i])) { + i++; + } + if (i < count) { + var value = parseInt(String.fromCharCode(digit1, digit2), 16); + decrypted[j++] = value ^ (r >> 8); + r = ((value + r) * c1 + c2) & ((1 << 16) - 1); + } + } + return Array.prototype.slice.call(decrypted, discardNumber, j); + } + + function isSpecial(c) { + return c === 0x2F || // '/' + c === 0x5B || c === 0x5D || // '[', ']' + c === 0x7B || c === 0x7D || // '{', '}' + c === 0x28 || c === 0x29; // '(', ')' + } + + function Type1Parser(stream, encrypted, seacAnalysisEnabled) { + if (encrypted) { + var data = stream.getBytes(); + var isBinary = !(isHexDigit(data[0]) && isHexDigit(data[1]) && + isHexDigit(data[2]) && isHexDigit(data[3])); + stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) : + decryptAscii(data, EEXEC_ENCRYPT_KEY, 4)); + } + this.seacAnalysisEnabled = !!seacAnalysisEnabled; + + this.stream = stream; + this.nextChar(); + } + + Type1Parser.prototype = { + readNumberArray: function Type1Parser_readNumberArray() { + this.getToken(); // read '[' or '{' (arrays can start with either) + var array = []; + while (true) { + var token = this.getToken(); + if (token === null || token === ']' || token === '}') { + break; + } + array.push(parseFloat(token || 0)); + } + return array; + }, + + readNumber: function Type1Parser_readNumber() { + var token = this.getToken(); + return parseFloat(token || 0); + }, + + readInt: function Type1Parser_readInt() { + // Use '| 0' to prevent setting a double into length such as the double + // does not flow into the loop variable. + var token = this.getToken(); + return parseInt(token || 0, 10) | 0; + }, + + readBoolean: function Type1Parser_readBoolean() { + var token = this.getToken(); + + // Use 1 and 0 since that's what type2 charstrings use. + return token === 'true' ? 1 : 0; + }, + + nextChar : function Type1_nextChar() { + return (this.currentChar = this.stream.getByte()); + }, + + getToken: function Type1Parser_getToken() { + // Eat whitespace and comments. + var comment = false; + var ch = this.currentChar; + while (true) { + if (ch === -1) { + return null; + } + + if (comment) { + if (ch === 0x0A || ch === 0x0D) { + comment = false; + } + } else if (ch === 0x25) { // '%' + comment = true; + } else if (!Lexer.isSpace(ch)) { + break; + } + ch = this.nextChar(); + } + if (isSpecial(ch)) { + this.nextChar(); + return String.fromCharCode(ch); + } + var token = ''; + do { + token += String.fromCharCode(ch); + ch = this.nextChar(); + } while (ch >= 0 && !Lexer.isSpace(ch) && !isSpecial(ch)); + return token; + }, + + /* + * Returns an object containing a Subrs array and a CharStrings + * array extracted from and eexec encrypted block of data + */ + extractFontProgram: function Type1Parser_extractFontProgram() { + var stream = this.stream; + + var subrs = [], charstrings = []; + var privateData = Object.create(null); + privateData['lenIV'] = 4; + var program = { + subrs: [], + charstrings: [], + properties: { + 'privateData': privateData + } + }; + var token, length, data, lenIV, encoded; + while ((token = this.getToken()) !== null) { + if (token !== '/') { + continue; + } + token = this.getToken(); + switch (token) { + case 'CharStrings': + // The number immediately following CharStrings must be greater or + // equal to the number of CharStrings. + this.getToken(); + this.getToken(); // read in 'dict' + this.getToken(); // read in 'dup' + this.getToken(); // read in 'begin' + while(true) { + token = this.getToken(); + if (token === null || token === 'end') { + break; + } + + if (token !== '/') { + continue; + } + var glyph = this.getToken(); + length = this.readInt(); + this.getToken(); // read in 'RD' or '-|' + data = stream.makeSubStream(stream.pos, length); + lenIV = program.properties.privateData['lenIV']; + encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV); + // Skip past the required space and binary data. + stream.skip(length); + this.nextChar(); + token = this.getToken(); // read in 'ND' or '|-' + if (token === 'noaccess') { + this.getToken(); // read in 'def' + } + charstrings.push({ + glyph: glyph, + encoded: encoded + }); + } + break; + case 'Subrs': + var num = this.readInt(); + this.getToken(); // read in 'array' + while ((token = this.getToken()) === 'dup') { + var index = this.readInt(); + length = this.readInt(); + this.getToken(); // read in 'RD' or '-|' + data = stream.makeSubStream(stream.pos, length); + lenIV = program.properties.privateData['lenIV']; + encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY, lenIV); + // Skip past the required space and binary data. + stream.skip(length); + this.nextChar(); + token = this.getToken(); // read in 'NP' or '|' + if (token === 'noaccess') { + this.getToken(); // read in 'put' + } + subrs[index] = encoded; + } + break; + case 'BlueValues': + case 'OtherBlues': + case 'FamilyBlues': + case 'FamilyOtherBlues': + var blueArray = this.readNumberArray(); + // *Blue* values may contain invalid data: disables reading of + // those values when hinting is disabled. + if (blueArray.length > 0 && (blueArray.length % 2) === 0 && + HINTING_ENABLED) { + program.properties.privateData[token] = blueArray; + } + break; + case 'StemSnapH': + case 'StemSnapV': + program.properties.privateData[token] = this.readNumberArray(); + break; + case 'StdHW': + case 'StdVW': + program.properties.privateData[token] = + this.readNumberArray()[0]; + break; + case 'BlueShift': + case 'lenIV': + case 'BlueFuzz': + case 'BlueScale': + case 'LanguageGroup': + case 'ExpansionFactor': + program.properties.privateData[token] = this.readNumber(); + break; + case 'ForceBold': + program.properties.privateData[token] = this.readBoolean(); + break; + } + } + + for (var i = 0; i < charstrings.length; i++) { + glyph = charstrings[i].glyph; + encoded = charstrings[i].encoded; + var charString = new Type1CharString(); + var error = charString.convert(encoded, subrs, + this.seacAnalysisEnabled); + 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({ + glyphName: glyph, + charstring: output, + width: charString.width, + lsb: charString.lsb, + seac: charString.seac + }); + } + + return program; + }, + + extractFontHeader: function Type1Parser_extractFontHeader(properties) { + var token; + while ((token = this.getToken()) !== null) { + if (token !== '/') { + continue; + } + token = this.getToken(); + switch (token) { + case 'FontMatrix': + var matrix = this.readNumberArray(); + properties.fontMatrix = matrix; + break; + case 'Encoding': + var encodingArg = this.getToken(); + var encoding; + if (!/^\d+$/.test(encodingArg)) { + // encoding name is specified + encoding = getEncoding(encodingArg); + } else { + encoding = []; + var size = parseInt(encodingArg, 10) | 0; + this.getToken(); // read in 'array' + + for (var j = 0; j < size; j++) { + token = this.getToken(); + // skipping till first dup or def (e.g. ignoring for statement) + while (token !== 'dup' && token !== 'def') { + token = this.getToken(); + if (token === null) { + return; // invalid header + } + } + if (token === 'def') { + break; // read all array data + } + var index = this.readInt(); + this.getToken(); // read in '/' + var glyph = this.getToken(); + encoding[index] = glyph; + this.getToken(); // read the in 'put' + } + } + properties.builtInEncoding = encoding; + break; + case 'FontBBox': + var fontBBox = this.readNumberArray(); + // adjusting ascent/descent + properties.ascent = fontBBox[3]; + properties.descent = fontBBox[1]; + properties.ascentScaled = true; + break; + } + } + } + }; + + return Type1Parser; +})(); + +exports.Type1Parser = Type1Parser; +})); diff --git a/test/unit/font_spec.js b/test/unit/font_spec.js index 378e5880b..3d8e18807 100644 --- a/test/unit/font_spec.js +++ b/test/unit/font_spec.js @@ -297,7 +297,7 @@ describe('font', function() { it('splits tokens', function() { var stream = new StringStream('/BlueValues[-17 0]noaccess def'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); expect(parser.getToken()).toEqual('/'); expect(parser.getToken()).toEqual('BlueValues'); expect(parser.getToken()).toEqual('['); @@ -310,35 +310,35 @@ describe('font', function() { }); it('handles glued tokens', function() { var stream = new StringStream('dup/CharStrings'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); expect(parser.getToken()).toEqual('dup'); expect(parser.getToken()).toEqual('/'); expect(parser.getToken()).toEqual('CharStrings'); }); it('ignores whitespace', function() { var stream = new StringStream('\nab c\t'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); expect(parser.getToken()).toEqual('ab'); expect(parser.getToken()).toEqual('c'); }); it('parses numbers', function() { var stream = new StringStream('123'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); expect(parser.readNumber()).toEqual(123); }); it('parses booleans', function() { var stream = new StringStream('true false'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); expect(parser.readBoolean()).toEqual(1); expect(parser.readBoolean()).toEqual(0); }); it('parses number arrays', function() { var stream = new StringStream('[1 2]'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); expect(parser.readNumberArray()).toEqual([1, 2]); // Variation on spacing. stream = new StringStream('[ 1 2 ]'); - parser = new Type1Parser(stream); + parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); expect(parser.readNumberArray()).toEqual([1, 2]); }); it('skips comments', function() { @@ -347,7 +347,7 @@ describe('font', function() { '%%Title: CMSY10\n' + '%Version: 003.002\n' + 'FontDirectory'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); expect(parser.getToken()).toEqual('FontDirectory'); }); it('parses font program', function() { @@ -359,7 +359,7 @@ describe('font', function() { '/CharStrings 46 dict dup begin\n' + '/.notdef 1 RD x ND' + '\n' + 'end'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); var program = parser.extractFontProgram(); expect(program.charstrings.length).toEqual(1); expect(program.properties.privateData.ExpansionFactor).toEqual(99); @@ -367,7 +367,7 @@ describe('font', function() { it('parses font header font matrix', function() { var stream = new StringStream( '/FontMatrix [0.001 0 0 0.001 0 0 ]readonly def\n'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); var props = {}; parser.extractFontHeader(props); expect(props.fontMatrix).toEqual([0.001, 0, 0, 0.001, 0, 0]); @@ -378,7 +378,7 @@ describe('font', function() { '0 1 255 {1 index exch /.notdef put} for\n' + 'dup 33 /arrowright put\n' + 'readonly def\n'); - var parser = new Type1Parser(stream); + var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED); var props = { overridableEncoding: true }; parser.extractFontHeader(props); expect(props.builtInEncoding[33]).toEqual('arrowright'); diff --git a/test/unit/jasmine-boot.js b/test/unit/jasmine-boot.js index 8648c666f..8ae3bda0a 100644 --- a/test/unit/jasmine-boot.js +++ b/test/unit/jasmine-boot.js @@ -48,12 +48,14 @@ function initializePDFJS(callback) { 'pdfjs/core/annotation', 'pdfjs/core/crypto', 'pdfjs/core/stream', 'pdfjs/core/fonts', 'pdfjs/core/ps_parser', 'pdfjs/core/function', 'pdfjs/core/parser', 'pdfjs/core/evaluator', 'pdfjs/core/cmap', - 'pdfjs/core/worker', 'pdfjs/core/network', 'pdfjs/core/cff_parser', - 'pdfjs/display/api', 'pdfjs/display/metadata', 'pdfjs/display/dom_utils'], + 'pdfjs/core/worker', 'pdfjs/core/network', 'pdfjs/core/type1_parser', + 'pdfjs/core/cff_parser', 'pdfjs/display/api', 'pdfjs/display/metadata', + 'pdfjs/display/dom_utils'], function (sharedUtil, displayGlobal, corePrimitives, coreAnnotation, coreCrypto, coreStream, coreFonts, corePsParser, coreFunction, coreParser, coreEvaluator, coreCMap, coreWorker, coreNetwork, - coreCFFParser, displayAPI, displayMetadata, displayDOMUtils) { + coreType1Parser, coreCFFParser, displayAPI, displayMetadata, + displayDOMUtils) { pdfjsLibs = { sharedUtil: sharedUtil, @@ -70,6 +72,7 @@ function initializePDFJS(callback) { coreCMap: coreCMap, coreWorker: coreWorker, coreNetwork: coreNetwork, + coreType1Parser: coreType1Parser, coreCFFParser: coreCFFParser, displayAPI: displayAPI, displayMetadata: displayMetadata,