Browse Source

Merge pull request #7146 from Snuffleupagus/extract-CFFParser

Extract CFFParser and Type1Parser from fonts.js
Yury Delendik 9 years ago
parent
commit
34aa915441
  1. 1646
      src/core/cff_parser.js
  2. 32
      src/core/font_renderer.js
  3. 2349
      src/core/fonts.js
  4. 722
      src/core/type1_parser.js
  5. 97
      test/unit/font_spec.js
  6. 10
      test/unit/jasmine-boot.js

1646
src/core/cff_parser.js

File diff suppressed because it is too large Load Diff

32
src/core/font_renderer.js

@ -17,17 +17,19 @@
(function (root, factory) { (function (root, factory) {
if (typeof define === 'function' && define.amd) { if (typeof define === 'function' && define.amd) {
define('pdfjs/core/font_renderer', ['exports', 'pdfjs/shared/util', define('pdfjs/core/font_renderer', ['exports', 'pdfjs/shared/util',
'pdfjs/core/stream', 'pdfjs/core/glyphlist', 'pdfjs/core/encodings'], 'pdfjs/core/stream', 'pdfjs/core/glyphlist', 'pdfjs/core/encodings',
factory); 'pdfjs/core/cff_parser'], factory);
} else if (typeof exports !== 'undefined') { } else if (typeof exports !== 'undefined') {
factory(exports, require('../shared/util.js'), require('./stream.js'), factory(exports, require('../shared/util.js'), require('./stream.js'),
require('./glyphlist.js'), require('./encodings.js')); require('./glyphlist.js'), require('./encodings.js'),
require('./cff_parser.js'));
} else { } else {
factory((root.pdfjsCoreFontRenderer = {}), root.pdfjsSharedUtil, factory((root.pdfjsCoreFontRenderer = {}), root.pdfjsSharedUtil,
root.pdfjsCoreStream, root.pdfjsCoreGlyphList, root.pdfjsCoreEncodings); root.pdfjsCoreStream, root.pdfjsCoreGlyphList, root.pdfjsCoreEncodings,
root.pdfjsCoreCFFParser);
} }
}(this, function (exports, sharedUtil, coreStream, coreGlyphList, }(this, function (exports, sharedUtil, coreStream, coreGlyphList,
coreEncodings) { coreEncodings, coreCFFParser) {
var Util = sharedUtil.Util; var Util = sharedUtil.Util;
var bytesToString = sharedUtil.bytesToString; var bytesToString = sharedUtil.bytesToString;
@ -35,9 +37,7 @@ var error = sharedUtil.error;
var Stream = coreStream.Stream; var Stream = coreStream.Stream;
var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode; var getGlyphsUnicode = coreGlyphList.getGlyphsUnicode;
var StandardEncoding = coreEncodings.StandardEncoding; var StandardEncoding = coreEncodings.StandardEncoding;
var CFFParser = coreCFFParser.CFFParser;
var coreFonts; // see _setCoreFonts below
var CFFParser; // = coreFonts.CFFParser;
var FontRendererFactory = (function FontRendererFactoryClosure() { var FontRendererFactory = (function FontRendererFactoryClosure() {
function getLong(data, offset) { function getLong(data, offset) {
@ -99,10 +99,10 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
error('not supported cmap: ' + format); error('not supported cmap: ' + format);
} }
function parseCff(data, start, end) { function parseCff(data, start, end, seacAnalysisEnabled) {
var properties = {}; var properties = {};
var parser = new CFFParser(new Stream(data, start, end - start), var parser = new CFFParser(new Stream(data, start, end - start),
properties); properties, seacAnalysisEnabled);
var cff = parser.parse(); var cff = parser.parse();
return { return {
glyphs: cff.charStrings.objects, glyphs: cff.charStrings.objects,
@ -696,7 +696,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
return { return {
create: function FontRendererFactory_create(font) { create: function FontRendererFactory_create(font, seacAnalysisEnabled) {
var data = new Uint8Array(font.data); var data = new Uint8Array(font.data);
var cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm; var cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm;
var numTables = getUshort(data, 4); var numTables = getUshort(data, 4);
@ -719,7 +719,7 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
indexToLocFormat = getUshort(data, offset + 50); indexToLocFormat = getUshort(data, offset + 50);
break; break;
case 'CFF ': case 'CFF ':
cff = parseCff(data, offset, offset + length); cff = parseCff(data, offset, offset + length, seacAnalysisEnabled);
break; break;
} }
} }
@ -736,13 +736,5 @@ var FontRendererFactory = (function FontRendererFactoryClosure() {
}; };
})(); })();
// TODO refactor to remove cyclic dependency on fonts.js
function _setCoreFonts(coreFonts_) {
coreFonts = coreFonts_;
CFFParser = coreFonts_.CFFParser;
}
exports._setCoreFonts = _setCoreFonts;
exports.FontRendererFactory = FontRendererFactory; exports.FontRendererFactory = FontRendererFactory;
})); }));

2349
src/core/fonts.js

File diff suppressed because it is too large Load Diff

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

97
test/unit/font_spec.js

@ -1,6 +1,5 @@
/* globals expect, it, describe, CFFCompiler, CFFParser, CFFIndex, CFFStrings, /* globals expect, it, describe, CFFCompiler, CFFParser, CFFIndex, CFFStrings,
SEAC_ANALYSIS_ENABLED, Type1Parser, StringStream, Type1Parser, StringStream, SEAC_ANALYSIS_ENABLED */
_enableSeacAnalysis */
'use strict'; 'use strict';
@ -38,7 +37,7 @@ describe('font', function() {
} }
describe('CFFParser', function() { describe('CFFParser', function() {
var parser = new CFFParser(fontData, {}); var parser = new CFFParser(fontData, {}, SEAC_ANALYSIS_ENABLED);
var cff = parser.parse(); var cff = parser.parse();
it('parses header', function() { it('parses header', function() {
@ -117,46 +116,42 @@ describe('font', function() {
}); });
it('parses a CharString endchar with 4 args w/seac enabled', function() { it('parses a CharString endchar with 4 args w/seac enabled', function() {
var seacAnalysisState = SEAC_ANALYSIS_ENABLED; var parser = new CFFParser(fontData, {},
try { /* seacAnalysisEnabled = */ true);
_enableSeacAnalysis(true); var cff = parser.parse();
var bytes = new Uint8Array([0, 1, // count
1, // offsetSize var bytes = new Uint8Array([0, 1, // count
0, // offset[0] 1, // offsetSize
237, 247, 22, 247, 72, 204, 247, 86, 14]); 0, // offset[0]
parser.bytes = bytes; 237, 247, 22, 247, 72, 204, 247, 86, 14]);
var charStringsIndex = parser.parseIndex(0).obj; parser.bytes = bytes;
var result = parser.parseCharStrings(charStringsIndex); var charStringsIndex = parser.parseIndex(0).obj;
expect(result.charStrings.count).toEqual(1); var result = parser.parseCharStrings(charStringsIndex);
expect(result.charStrings.get(0).length).toEqual(1); expect(result.charStrings.count).toEqual(1);
expect(result.seacs.length).toEqual(1); expect(result.charStrings.get(0).length).toEqual(1);
expect(result.seacs[0].length).toEqual(4); expect(result.seacs.length).toEqual(1);
expect(result.seacs[0][0]).toEqual(130); expect(result.seacs[0].length).toEqual(4);
expect(result.seacs[0][1]).toEqual(180); expect(result.seacs[0][0]).toEqual(130);
expect(result.seacs[0][2]).toEqual(65); expect(result.seacs[0][1]).toEqual(180);
expect(result.seacs[0][3]).toEqual(194); expect(result.seacs[0][2]).toEqual(65);
} finally { expect(result.seacs[0][3]).toEqual(194);
_enableSeacAnalysis(seacAnalysisState);
}
}); });
it('parses a CharString endchar with 4 args w/seac disabled', function() { it('parses a CharString endchar with 4 args w/seac disabled', function() {
var seacAnalysisState = SEAC_ANALYSIS_ENABLED; var parser = new CFFParser(fontData, {},
try { /* seacAnalysisEnabled = */ false);
_enableSeacAnalysis(false); var cff = parser.parse();
var bytes = new Uint8Array([0, 1, // count
1, // offsetSize var bytes = new Uint8Array([0, 1, // count
0, // offset[0] 1, // offsetSize
237, 247, 22, 247, 72, 204, 247, 86, 14]); 0, // offset[0]
parser.bytes = bytes; 237, 247, 22, 247, 72, 204, 247, 86, 14]);
var charStringsIndex = parser.parseIndex(0).obj; parser.bytes = bytes;
var result = parser.parseCharStrings(charStringsIndex); var charStringsIndex = parser.parseIndex(0).obj;
expect(result.charStrings.count).toEqual(1); var result = parser.parseCharStrings(charStringsIndex);
expect(result.charStrings.get(0).length).toEqual(9); expect(result.charStrings.count).toEqual(1);
expect(result.seacs.length).toEqual(0); expect(result.charStrings.get(0).length).toEqual(9);
} finally { expect(result.seacs.length).toEqual(0);
_enableSeacAnalysis(seacAnalysisState);
}
}); });
it('parses a CharString endchar no args', function() { it('parses a CharString endchar no args', function() {
@ -302,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('[');
@ -315,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() {
@ -352,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() {
@ -364,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);
@ -372,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]);
@ -383,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');

10
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/display/api', 'pdfjs/core/worker', 'pdfjs/core/network', 'pdfjs/core/type1_parser',
'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,
displayAPI, displayMetadata, displayDOMUtils) { coreType1Parser, coreCFFParser, displayAPI, displayMetadata,
displayDOMUtils) {
pdfjsLibs = { pdfjsLibs = {
sharedUtil: sharedUtil, sharedUtil: sharedUtil,
@ -70,6 +72,8 @@ function initializePDFJS(callback) {
coreCMap: coreCMap, coreCMap: coreCMap,
coreWorker: coreWorker, coreWorker: coreWorker,
coreNetwork: coreNetwork, coreNetwork: coreNetwork,
coreType1Parser: coreType1Parser,
coreCFFParser: coreCFFParser,
displayAPI: displayAPI, displayAPI: displayAPI,
displayMetadata: displayMetadata, displayMetadata: displayMetadata,
displayDOMUtils: displayDOMUtils displayDOMUtils: displayDOMUtils

Loading…
Cancel
Save