Browse Source

Extract `Type1Parser` from fonts.js

Jonas Jenwald 9 years ago
parent
commit
ef551e8266
  1. 695
      src/core/fonts.js
  2. 722
      src/core/type1_parser.js
  3. 22
      test/unit/font_spec.js
  4. 9
      test/unit/jasmine-boot.js

695
src/core/fonts.js

@ -22,26 +22,26 @@
'pdfjs/core/glyphlist', 'pdfjs/core/charsets', 'pdfjs/core/glyphlist', 'pdfjs/core/charsets',
'pdfjs/core/font_renderer', 'pdfjs/core/encodings', 'pdfjs/core/font_renderer', 'pdfjs/core/encodings',
'pdfjs/core/standard_fonts', 'pdfjs/core/unicode', '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') { } else if (typeof exports !== 'undefined') {
factory(exports, require('../shared/util.js'), require('./primitives.js'), factory(exports, require('../shared/util.js'), require('./primitives.js'),
require('./stream.js'), require('./parser.js'), require('./stream.js'), require('./parser.js'),
require('./glyphlist.js'), require('./charsets.js'), require('./glyphlist.js'), require('./charsets.js'),
require('./font_renderer.js'), require('./encodings.js'), require('./font_renderer.js'), require('./encodings.js'),
require('./standard_fonts'), require('./unicode.js'), require('./standard_fonts'), require('./unicode.js'),
require('./cff_parser')); require('./type1_parser'), require('./cff_parser'));
} else { } else {
factory((root.pdfjsCoreFonts = {}), root.pdfjsSharedUtil, factory((root.pdfjsCoreFonts = {}), root.pdfjsSharedUtil,
root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreParser, root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreParser,
root.pdfjsCoreGlyphList, root.pdfjsCoreCharsets, root.pdfjsCoreGlyphList, root.pdfjsCoreCharsets,
root.pdfjsCoreFontRenderer, root.pdfjsCoreEncodings, root.pdfjsCoreFontRenderer, root.pdfjsCoreEncodings,
root.pdfjsCoreStandardFonts, root.pdfjsCoreUnicode, root.pdfjsCoreStandardFonts, root.pdfjsCoreUnicode,
root.pdfjsCoreCFFParser); root.pdfjsCoreType1Parser, root.pdfjsCoreCFFParser);
} }
}(this, function (exports, sharedUtil, corePrimitives, coreStream, coreParser, }(this, function (exports, sharedUtil, corePrimitives, coreStream, coreParser,
coreGlyphList, coreCharsets, coreFontRenderer, coreGlyphList, coreCharsets, coreFontRenderer,
coreEncodings, coreStandardFonts, coreUnicode, coreEncodings, coreStandardFonts, coreUnicode,
coreCFFParser) { coreType1Parser, coreCFFParser) {
var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX; var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
var FontType = sharedUtil.FontType; var FontType = sharedUtil.FontType;
@ -82,6 +82,7 @@ var getSupplementalGlyphMapForArialBlack =
var getUnicodeRangeFor = coreUnicode.getUnicodeRangeFor; var getUnicodeRangeFor = coreUnicode.getUnicodeRangeFor;
var mapSpecialUnicodeValues = coreUnicode.mapSpecialUnicodeValues; var mapSpecialUnicodeValues = coreUnicode.mapSpecialUnicodeValues;
var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph; var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
var Type1Parser = coreType1Parser.Type1Parser;
var CFFStandardStrings = coreCFFParser.CFFStandardStrings; var CFFStandardStrings = coreCFFParser.CFFStandardStrings;
var CFFParser = coreCFFParser.CFFParser; var CFFParser = coreCFFParser.CFFParser;
var CFFCompiler = coreCFFParser.CFFCompiler; var CFFCompiler = coreCFFParser.CFFCompiler;
@ -102,10 +103,6 @@ var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false;
// except for Type 3 fonts // except for Type 3 fonts
var PDF_GLYPH_SPACE_UNITS = 1000; 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 // Accented charactars are not displayed properly on Windows, using this flag
// to control analysis of seac charstrings. // to control analysis of seac charstrings.
var SEAC_ANALYSIS_ENABLED = false; var SEAC_ANALYSIS_ENABLED = false;
@ -2885,681 +2882,6 @@ function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) {
return charCodeToGlyphId; 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. // Type1Font is also a CIDFontType0.
var Type1Font = (function Type1FontClosure() { var Type1Font = (function Type1FontClosure() {
function findBlock(streamBytes, signature, startIndex) { function findBlock(streamBytes, signature, startIndex) {
@ -3694,7 +3016,8 @@ var Type1Font = (function Type1FontClosure() {
// Get the data block containing glyphs and subrs informations // Get the data block containing glyphs and subrs informations
var headerBlock = getHeaderBlock(file, headerBlockLength); var headerBlock = getHeaderBlock(file, headerBlockLength);
headerBlockLength = headerBlock.length; headerBlockLength = headerBlock.length;
var headerBlockParser = new Type1Parser(headerBlock.stream); var headerBlockParser = new Type1Parser(headerBlock.stream, false,
SEAC_ANALYSIS_ENABLED);
headerBlockParser.extractFontHeader(properties); headerBlockParser.extractFontHeader(properties);
if (pfbHeaderPresent) { if (pfbHeaderPresent) {
@ -3706,7 +3029,8 @@ var Type1Font = (function Type1FontClosure() {
// Decrypt the data blocks and retrieve it's content // Decrypt the data blocks and retrieve it's content
var eexecBlock = getEexecBlock(file, eexecBlockLength); var eexecBlock = getEexecBlock(file, eexecBlockLength);
eexecBlockLength = eexecBlock.length; eexecBlockLength = eexecBlock.length;
var eexecBlockParser = new Type1Parser(eexecBlock.stream, true); var eexecBlockParser = new Type1Parser(eexecBlock.stream, true,
SEAC_ANALYSIS_ENABLED);
var data = eexecBlockParser.extractFontProgram(); var data = eexecBlockParser.extractFontProgram();
for (var info in data.properties) { for (var info in data.properties) {
properties[info] = data.properties[info]; properties[info] = data.properties[info];
@ -3992,6 +3316,5 @@ exports.Font = Font;
exports.FontFlags = FontFlags; exports.FontFlags = FontFlags;
exports.IdentityToUnicodeMap = IdentityToUnicodeMap; exports.IdentityToUnicodeMap = IdentityToUnicodeMap;
exports.ToUnicodeMap = ToUnicodeMap; exports.ToUnicodeMap = ToUnicodeMap;
exports.Type1Parser = Type1Parser;
exports.getFontType = getFontType; exports.getFontType = getFontType;
})); }));

722
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;
}));

22
test/unit/font_spec.js

@ -297,7 +297,7 @@ describe('font', function() {
it('splits tokens', function() { it('splits tokens', function() {
var stream = new StringStream('/BlueValues[-17 0]noaccess def'); 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('/');
expect(parser.getToken()).toEqual('BlueValues'); expect(parser.getToken()).toEqual('BlueValues');
expect(parser.getToken()).toEqual('['); expect(parser.getToken()).toEqual('[');
@ -310,35 +310,35 @@ describe('font', function() {
}); });
it('handles glued tokens', function() { it('handles glued tokens', function() {
var stream = new StringStream('dup/CharStrings'); 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('dup');
expect(parser.getToken()).toEqual('/'); expect(parser.getToken()).toEqual('/');
expect(parser.getToken()).toEqual('CharStrings'); expect(parser.getToken()).toEqual('CharStrings');
}); });
it('ignores whitespace', function() { it('ignores whitespace', function() {
var stream = new StringStream('\nab c\t'); 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('ab');
expect(parser.getToken()).toEqual('c'); expect(parser.getToken()).toEqual('c');
}); });
it('parses numbers', function() { it('parses numbers', function() {
var stream = new StringStream('123'); var stream = new StringStream('123');
var parser = new Type1Parser(stream); var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED);
expect(parser.readNumber()).toEqual(123); expect(parser.readNumber()).toEqual(123);
}); });
it('parses booleans', function() { it('parses booleans', function() {
var stream = new StringStream('true false'); 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(1);
expect(parser.readBoolean()).toEqual(0); expect(parser.readBoolean()).toEqual(0);
}); });
it('parses number arrays', function() { it('parses number arrays', function() {
var stream = new StringStream('[1 2]'); 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]); expect(parser.readNumberArray()).toEqual([1, 2]);
// Variation on spacing. // Variation on spacing.
stream = new StringStream('[ 1 2 ]'); stream = new StringStream('[ 1 2 ]');
parser = new Type1Parser(stream); parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED);
expect(parser.readNumberArray()).toEqual([1, 2]); expect(parser.readNumberArray()).toEqual([1, 2]);
}); });
it('skips comments', function() { it('skips comments', function() {
@ -347,7 +347,7 @@ describe('font', function() {
'%%Title: CMSY10\n' + '%%Title: CMSY10\n' +
'%Version: 003.002\n' + '%Version: 003.002\n' +
'FontDirectory'); 'FontDirectory');
var parser = new Type1Parser(stream); var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED);
expect(parser.getToken()).toEqual('FontDirectory'); expect(parser.getToken()).toEqual('FontDirectory');
}); });
it('parses font program', function() { it('parses font program', function() {
@ -359,7 +359,7 @@ describe('font', function() {
'/CharStrings 46 dict dup begin\n' + '/CharStrings 46 dict dup begin\n' +
'/.notdef 1 RD x ND' + '\n' + '/.notdef 1 RD x ND' + '\n' +
'end'); 'end');
var parser = new Type1Parser(stream); var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED);
var program = parser.extractFontProgram(); var program = parser.extractFontProgram();
expect(program.charstrings.length).toEqual(1); expect(program.charstrings.length).toEqual(1);
expect(program.properties.privateData.ExpansionFactor).toEqual(99); expect(program.properties.privateData.ExpansionFactor).toEqual(99);
@ -367,7 +367,7 @@ describe('font', function() {
it('parses font header font matrix', function() { it('parses font header font matrix', function() {
var stream = new StringStream( var stream = new StringStream(
'/FontMatrix [0.001 0 0 0.001 0 0 ]readonly def\n'); '/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 = {}; var props = {};
parser.extractFontHeader(props); parser.extractFontHeader(props);
expect(props.fontMatrix).toEqual([0.001, 0, 0, 0.001, 0, 0]); 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' + '0 1 255 {1 index exch /.notdef put} for\n' +
'dup 33 /arrowright put\n' + 'dup 33 /arrowright put\n' +
'readonly def\n'); 'readonly def\n');
var parser = new Type1Parser(stream); var parser = new Type1Parser(stream, false, SEAC_ANALYSIS_ENABLED);
var props = { overridableEncoding: true }; var props = { overridableEncoding: true };
parser.extractFontHeader(props); parser.extractFontHeader(props);
expect(props.builtInEncoding[33]).toEqual('arrowright'); expect(props.builtInEncoding[33]).toEqual('arrowright');

9
test/unit/jasmine-boot.js

@ -48,12 +48,14 @@ function initializePDFJS(callback) {
'pdfjs/core/annotation', 'pdfjs/core/crypto', 'pdfjs/core/stream', 'pdfjs/core/annotation', 'pdfjs/core/crypto', 'pdfjs/core/stream',
'pdfjs/core/fonts', 'pdfjs/core/ps_parser', 'pdfjs/core/function', 'pdfjs/core/fonts', 'pdfjs/core/ps_parser', 'pdfjs/core/function',
'pdfjs/core/parser', 'pdfjs/core/evaluator', 'pdfjs/core/cmap', 'pdfjs/core/parser', 'pdfjs/core/evaluator', 'pdfjs/core/cmap',
'pdfjs/core/worker', 'pdfjs/core/network', 'pdfjs/core/cff_parser', 'pdfjs/core/worker', 'pdfjs/core/network', 'pdfjs/core/type1_parser',
'pdfjs/display/api', 'pdfjs/display/metadata', 'pdfjs/display/dom_utils'], 'pdfjs/core/cff_parser', 'pdfjs/display/api', 'pdfjs/display/metadata',
'pdfjs/display/dom_utils'],
function (sharedUtil, displayGlobal, corePrimitives, coreAnnotation, function (sharedUtil, displayGlobal, corePrimitives, coreAnnotation,
coreCrypto, coreStream, coreFonts, corePsParser, coreFunction, coreCrypto, coreStream, coreFonts, corePsParser, coreFunction,
coreParser, coreEvaluator, coreCMap, coreWorker, coreNetwork, coreParser, coreEvaluator, coreCMap, coreWorker, coreNetwork,
coreCFFParser, displayAPI, displayMetadata, displayDOMUtils) { coreType1Parser, coreCFFParser, displayAPI, displayMetadata,
displayDOMUtils) {
pdfjsLibs = { pdfjsLibs = {
sharedUtil: sharedUtil, sharedUtil: sharedUtil,
@ -70,6 +72,7 @@ function initializePDFJS(callback) {
coreCMap: coreCMap, coreCMap: coreCMap,
coreWorker: coreWorker, coreWorker: coreWorker,
coreNetwork: coreNetwork, coreNetwork: coreNetwork,
coreType1Parser: coreType1Parser,
coreCFFParser: coreCFFParser, coreCFFParser: coreCFFParser,
displayAPI: displayAPI, displayAPI: displayAPI,
displayMetadata: displayMetadata, displayMetadata: displayMetadata,

Loading…
Cancel
Save