Evaluate type 1 charstrings for conversion to type 2.

This commit is contained in:
Brendan Dahl 2013-01-09 17:33:59 -08:00
parent 2ccad4ca45
commit b5278c5e27
3 changed files with 358 additions and 422 deletions

View File

@ -26,7 +26,8 @@ var SYMBOLIC_FONT_GLYPH_OFFSET = 0xF000;
// except for Type 3 fonts // except for Type 3 fonts
var PDF_GLYPH_SPACE_UNITS = 1000; var PDF_GLYPH_SPACE_UNITS = 1000;
// Until hinting is fully supported this constant can be used // Hinting is currently disabled due to unknown problems on windows
// in tracemonkey and various other pdfs with type1 fonts.
var HINTING_ENABLED = false; var HINTING_ENABLED = false;
var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0]; var FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
@ -4351,43 +4352,7 @@ var ErrorFont = (function ErrorFontClosure() {
return ErrorFont; return ErrorFont;
})(); })();
var CallothersubrCmd = (function CallothersubrCmdClosure() {
function CallothersubrCmd(index) {
this.index = index;
}
return CallothersubrCmd;
})();
/* /*
* Type1Parser encapsulate the needed code for parsing a Type1 font
* program. Some of its logic depends on the Type2 charstrings
* structure.
*/
var Type1Parser = function type1Parser() {
/*
* 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 decrypt(stream, key, discardNumber) {
var r = key, c1 = 52845, c2 = 22719;
var decryptedString = [];
var value = '';
var count = stream.length;
for (var i = 0; i < count; i++) {
value = stream[i];
decryptedString[i] = value ^ (r >> 8);
r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
}
return decryptedString.slice(discardNumber);
}
/*
* CharStrings are encoded following the the CharString Encoding sequence * CharStrings are encoded following the the CharString Encoding sequence
* describe in Chapter 6 of the "Adobe Type1 Font Format" specification. * describe in Chapter 6 of the "Adobe Type1 Font Format" specification.
* The value in a byte indicates a command, a number, or subsequent bytes * The value in a byte indicates a command, a number, or subsequent bytes
@ -4425,298 +4390,314 @@ var Type1Parser = function type1Parser() {
* to be encoded and this encoding technique helps to minimize the length of * to be encoded and this encoding technique helps to minimize the length of
* the charStrings. * the charStrings.
*/ */
var charStringDictionary = { var Type1CharString = (function Type1CharStringClosure() {
'1': 'hstem', var COMMAND_MAP = {
'3': 'vstem', 'hstem': [1],
'4': 'vmoveto', 'vstem': [3],
'5': 'rlineto', 'vmoveto': [4],
'6': 'hlineto', 'rlineto': [5],
'7': 'vlineto', 'hlineto': [6],
'8': 'rrcurveto', 'vlineto': [7],
'rrcurveto': [8],
// closepath is a Type1 command that do not take argument and is useless 'callsubr': [10],
// in Type2 and it can simply be ignored. 'flex': [12, 35],
'9': null, // closepath 'drop' : [12, 18],
'endchar': [14],
'10': 'callsubr', 'rmoveto': [21],
'hmoveto': [22],
// return is normally used inside sub-routines to tells to the execution 'vhcurveto': [30],
// flow that it can be back to normal. 'hvcurveto': [31]
// During the translation process Type1 charstrings will be flattened and
// sub-routines will be embedded directly into the charstring directly, so
// this can be ignored safely.
'11': 'return',
'12': {
// dotsection is a Type1 command to specify some hinting feature for dots
// that do not take a parameter and it can safely be ignored for Type2.
'0': null, // dotsection
// [vh]stem3 are Type1 only and Type2 supports [vh]stem with multiple
// parameters, so instead of returning [vh]stem3 take a shortcut and
// return [vhstem] instead.
'1': 'vstem',
'2': 'hstem',
'6': 'endchar', // seac
// Type1 only command with command not (yet) built-in ,throw an error
'7': -1, // sbw
'10': 'add',
'11': 'sub',
'12': 'div',
// callothersubr is a mechanism to make calls on the postscript
// interpreter, this is not supported by Type2 charstring but hopefully
// most of the default commands can be ignored safely.
'16': 'callothersubr',
'17': 'pop',
// setcurrentpoint sets the current point to x, y without performing a
// moveto (this is a one shot positionning command). This is used only
// with the return of an OtherSubrs call.
// TODO Implement the OtherSubrs charstring embedding and replace this
// call by a no-op, like 2 'pop' commands for example.
'33': null // setcurrentpoint
},
'13': 'hsbw',
'14': 'endchar',
'21': 'rmoveto',
'22': 'hmoveto',
'30': 'vhcurveto',
'31': 'hvcurveto'
}; };
var ESCAPE_CMD = 12; function Type1CharString() {
this.width = 0;
// Breaks up the stack by arguments and also calculates the value. this.lsb = 0;
function breakUpArgs(stack, numArgs) { this.flexing = false;
var args = []; this.output = [];
var index = stack.length - 1; this.stack = [];
for (var i = 0; i < numArgs; i++) {
if (index < 0) {
args.unshift({ arg: [0],
value: 0,
offset: 0 });
warn('Malformed charstring stack: not enough values on stack.');
continue;
}
var token = stack[index];
if (token === 'div') {
var a = stack[index - 2];
var b = stack[index - 1];
if (!isInt(a) || !isInt(b)) {
warn('Malformed charsting stack: expected ints on stack for div.');
a = 0;
b = 1;
}
args.unshift({ arg: [a, b, 'div'],
value: a / b,
offset: index - 2 });
index -= 3;
} else if (isInt(token)) {
args.unshift({ arg: stack.slice(index, index + 1),
value: token,
offset: index });
index--;
} else {
warn('Malformed charsting stack: found bad token ' + token + '.');
}
}
return args;
} }
function decodeCharString(array) { Type1CharString.prototype = {
var charstring = []; convert: function Type1CharString_convert(encoded, subrs) {
var lsb = 0; var count = encoded.length;
var width = 0; var error = false;
var flexing = false;
var value = '';
var count = array.length;
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
value = array[i]; var value = encoded[i];
if (value < 32) { if (value < 32) {
var command = null; if (value === 12) {
if (value == ESCAPE_CMD) { value = (value << 8) + encoded[++i];
var escape = array[++i];
// TODO Clean this code
if (escape == 16) {
var index = charstring.pop();
var argc = charstring.pop();
for (var j = 0; j < argc; j++)
charstring.push('drop');
// If the flex mechanism is not used in a font program, Adobe
// states that entries 0, 1 and 2 can simply be replaced by
// {}, which means that we can simply ignore them.
if (index < 3) {
continue;
} }
switch (value) {
// This is the same things about hint replacement, if it is not used case 1: // hstem
// entry 3 can be replaced by {3} if (!HINTING_ENABLED) {
// TODO support hint replacment this.stack = [];
if (index == 3) { break;
charstring.push(3);
i++;
continue;
} }
error = this.executeCommand(2, COMMAND_MAP.hstem);
assert(argc == 0, 'callothersubr with arguments is not supported'); break;
charstring.push(new CallothersubrCmd(index)); case 3: // vstem
continue; if (!HINTING_ENABLED) {
} else if (escape == 7) { // sbw this.stack = [];
var args = breakUpArgs(charstring, 4); break;
var arg0 = args[0]; }
var arg1 = args[1]; error = this.executeCommand(2, COMMAND_MAP.vstem);
var arg2 = args[2]; break;
lsb = arg0.value; case 4: // vmoveto
width = arg2.value; if (this.flexing) {
// To convert to type2 we have to move the width value to the first if (this.stack.length < 1) {
// part of the charstring and then use rmoveto with (dx, dy). error = true;
// The height argument will not be used for vmtx and vhea tables break;
// reconstruction -- ignoring it. }
charstring = arg2.arg; // Add the dx for flex and but also swap the values so they are
charstring = charstring.concat(arg0.arg, arg1.arg); // the right order.
charstring.push('rmoveto'); var dy = this.stack.pop();
continue; this.stack.push(0, dy);
} else if (escape == 17 || escape == 33) { break;
// pop or setcurrentpoint commands can be ignored }
// since we are not doing callothersubr error = this.executeCommand(1, COMMAND_MAP.vmoveto);
continue; break;
} else if (escape == 6) { case 5: // rlineto
error = this.executeCommand(2, COMMAND_MAP.rlineto);
break;
case 6: // hlineto
error = this.executeCommand(1, COMMAND_MAP.hlineto);
break;
case 7: // vlineto
error = this.executeCommand(1, COMMAND_MAP.vlineto);
break;
case 8: // rrcurveto
error = this.executeCommand(6, COMMAND_MAP.rrcurveto);
break;
case 9: // closepath
// closepath is a Type1 command that does not take argument and is
// useless in Type2 and it can simply be ignored.
this.stack = [];
break;
case 10: // callsubr
if (this.stack.length < 1) {
error = true;
break;
}
var subrNumber = this.stack.pop();
error = this.convert(subrs[subrNumber], subrs);
break;
case 11: // return
return error;
break;
case 13: // hsbw
if (this.stack.length < 2) {
error = true;
break;
}
// To convert to type2 we have to move the width value to the
// first part of the charstring and then use hmoveto with lsb.
var wx = this.stack.pop();
var sbx = this.stack.pop();
this.lsb = sbx;
this.width = wx;
this.stack.push(sbx);
error = this.executeCommand(1, COMMAND_MAP.hmoveto);
break;
case 14: // endchar
this.output.push(COMMAND_MAP.endchar[0]);
break;
case 21: // rmoveto
if (this.flexing) {
break;
}
error = this.executeCommand(2, COMMAND_MAP.rmoveto);
break;
case 22: // hmoveto
if (this.flexing) {
// Add the dy for flex.
this.stack.push(0);
break;
}
error = this.executeCommand(1, COMMAND_MAP.hmoveto);
break;
case 30: // vhcurveto
error = this.executeCommand(4, COMMAND_MAP.vhcurveto);
break;
case 31: // hvcurveto
error = this.executeCommand(4, COMMAND_MAP.hvcurveto);
break;
case (12 << 8) + 0: // dotsection
// dotsection is a Type1 command to specify some hinting feature
// for dots that do not take a parameter and it can safely be
// ignored for Type2.
this.stack = [];
break;
case (12 << 8) + 1: // vstem3
if (!HINTING_ENABLED) {
this.stack = [];
break;
}
// [vh]stem3 are Type1 only and Type2 supports [vh]stem with
// multiple parameters, so instead of returning [vh]stem3 take a
// shortcut and return [vhstem] instead.
error = this.executeCommand(2, COMMAND_MAP.vstem);
break;
case (12 << 8) + 2: // hstem3
if (!HINTING_ENABLED) {
this.stack = [];
break;
}
// See vstem3.
error = this.executeCommand(2, COMMAND_MAP.hstem);
break;
case (12 << 8) + 6: // seac
// seac is like type 2's special endchar but it doesn't use the // seac is like type 2's special endchar but it doesn't use the
// first argument asb, so remove it. // first argument asb, so remove it.
var args = breakUpArgs(charstring, 5); error = this.executeCommand(4, COMMAND_MAP.endchar);
var arg0 = args[0];
charstring.splice(arg0.offset, arg0.arg.length);
} else if (!HINTING_ENABLED && (escape == 1 || escape == 2)) {
charstring.push('drop', 'drop', 'drop', 'drop', 'drop', 'drop');
continue;
}
command = charStringDictionary['12'][escape];
} else {
if (value == 13) { // hsbw
var args = breakUpArgs(charstring, 2);
var arg0 = args[0];
var arg1 = args[1];
lsb = arg0.value;
width = arg1.value;
// To convert to type2 we have to move the width value to the first
// part of the charstring and then use hmoveto with lsb.
charstring = arg1.arg;
charstring = charstring.concat(arg0.arg);
charstring.push('hmoveto');
continue;
} else if (value == 10) { // callsubr
if (charstring[charstring.length - 1] < 3) { // subr #0..2
// XXX: According to the spec if flex or hinting is not used then
// subroutines 0-3 can actually be anything defined by the font,
// so we really shouldn't be doing flex here but when
// callothersubr 0-2 is used. There hasn't been a real world
// example of this yet so we'll keep doing it here.
var subrNumber = charstring.pop();
switch (subrNumber) {
case 1:
flexing = true; // prepare for flex coordinates
break; break;
case 0: case (12 << 8) + 7: // sbw
var flexArgs = breakUpArgs(charstring, 17); if (this.stack.length < 4) {
error = true;
// removing all flex arguments from the stack break;
charstring.splice(flexArgs[0].offset, }
charstring.length - flexArgs[0].offset); // To convert to type2 we have to move the width value to the
// first part of the charstring and then use rmoveto with
charstring = charstring.concat( // (dx, dy). The height argument will not be used for vmtx and
flexArgs[0].arg, // bcp1x + // vhea tables reconstruction -- ignoring it.
flexArgs[2].arg, // rpx var wy = this.stack.pop();
['add'], var wx = this.stack.pop();
flexArgs[1].arg, // bcp1y + var sby = this.stack.pop();
flexArgs[3].arg, // rpy var sbx = this.stack.pop();
['add'], this.lsb = sbx;
flexArgs[4].arg, // bcp2x this.width = wx;
flexArgs[5].arg, // bcp2y this.stack.push(sbx, sby);
flexArgs[6].arg, // p2x error = this.executeCommand(2, COMMAND_MAP.rmoveto);
flexArgs[7].arg, // p2y break;
flexArgs[8].arg, // bcp3x case (12 << 8) + 12: // div
flexArgs[9].arg, // bcp3y if (this.stack.length < 2) {
flexArgs[10].arg, // bcp4x error = true;
flexArgs[11].arg, // bcp4y break;
flexArgs[12].arg, // p3x }
flexArgs[13].arg, // p3y var num2 = this.stack.pop();
flexArgs[14].arg, // flexDepth var num1 = this.stack.pop();
this.stack.push(num1 / num2);
break;
case (12 << 8) + 16: // callothersubr
if (this.stack.length < 2) {
error = true;
break;
}
var subrNumber = this.stack.pop();
var numArgs = this.stack.pop();
if (subrNumber === 0 && numArgs === 3) {
var flexArgs = this.stack.splice(this.stack.length - 17, 17);
this.stack.push(
flexArgs[2] + flexArgs[0], // bcp1x + rpx
flexArgs[3] + flexArgs[1], // bcp1y + rpy
flexArgs[4], // bcp2x
flexArgs[5], // bcp2y
flexArgs[6], // p2x
flexArgs[7], // p2y
flexArgs[8], // bcp3x
flexArgs[9], // bcp3y
flexArgs[10], // bcp4x
flexArgs[11], // bcp4y
flexArgs[12], // p3x
flexArgs[13], // p3y
flexArgs[14] // flexDepth
// 15 = finalx unused by flex // 15 = finalx unused by flex
// 16 = finaly unused by flex // 16 = finaly unused by flex
['flex']
); );
error = this.executeCommand(13, COMMAND_MAP.flex, true);
flexing = false; 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; break;
} }
continue; continue;
}
} else if (value == 21 && flexing) { // rmoveto
continue; // ignoring rmoveto
} else if (value == 22 && flexing) { // hmoveto
// Add the dy for flex.
charstring.push(0);
continue; // ignoring hmoveto
} else if (value == 4 && flexing) { // vmoveto
// Insert the dx for flex before dy.
var flexArgs = breakUpArgs(charstring, 1);
charstring.splice(flexArgs[0].offset, 0, 0);
continue; // ignoring vmoveto
} else if (!HINTING_ENABLED && (value == 1 || value == 3)) {
charstring.push('drop', 'drop');
continue;
}
command = charStringDictionary[value];
}
// Some charstring commands are meaningless in Type2 and will return
// a null, let's just ignored them
if (!command && i < count) {
continue;
} else if (!command) {
break;
} else if (command == -1) {
warn('Support for Type1 command ' + value +
' (' + escape + ') is not implemented in charstring: ' +
charstring);
if (value == 12) {
// we know how to ignore only some the Type1 commands
switch (escape) {
case 7:
charstring.push('drop', 'drop', 'drop', 'drop');
continue;
case 8:
charstring.push('drop');
continue;
}
}
}
value = command;
} else if (value <= 246) { } else if (value <= 246) {
value = value - 139; value = value - 139;
} else if (value <= 250) { } else if (value <= 250) {
value = ((value - 247) * 256) + array[++i] + 108; value = ((value - 247) * 256) + encoded[++i] + 108;
} else if (value <= 254) { } else if (value <= 254) {
value = -((value - 251) * 256) - array[++i] - 108; value = -((value - 251) * 256) - encoded[++i] - 108;
} else { } else {
value = (array[++i] & 0xff) << 24 | (array[++i] & 0xff) << 16 | value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 |
(array[++i] & 0xff) << 8 | (array[++i] & 0xff) << 0; (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0;
} }
this.stack.push(value);
charstring.push(value);
} }
return error;
},
return { charstring: charstring, width: width, lsb: lsb }; executeCommand: function(howManyArgs, command, keepStack) {
var stackLength = this.stack.length;
if (howManyArgs > stackLength) {
return true;
}
var start = stackLength - howManyArgs;
for (var i = start; i < stackLength; i++) {
var value = this.stack[i];
if (value === (value | 0)) { // int
this.output.push(28, (value >> 8) & 0xff, value & 0xff);
} else { // fixed point
value = (65536 * value) | 0;
this.output.push(255,
(value >> 24) & 0xFF,
(value >> 16) & 0xFF,
(value >> 8) & 0xFF,
value & 0xFF);
}
}
this.output.push.apply(this.output, command);
if (keepStack) {
this.stack.splice(start, howManyArgs);
} else {
this.stack = [];
}
return false;
}
};
return Type1CharString;
})();
/*
* Type1Parser encapsulate the needed code for parsing a Type1 font
* program. Some of its logic depends on the Type2 charstrings
* structure.
*/
var Type1Parser = function type1Parser() {
/*
* 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 decrypt(stream, key, discardNumber) {
var r = key, c1 = 52845, c2 = 22719;
var decryptedString = [];
var value = '';
var count = stream.length;
for (var i = 0; i < count; i++) {
value = stream[i];
decryptedString[i] = value ^ (r >> 8);
r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
}
return decryptedString.slice(discardNumber);
} }
/* /*
@ -4786,6 +4767,7 @@ var Type1Parser = function type1Parser() {
eexecStr += String.fromCharCode(eexec[i]); eexecStr += String.fromCharCode(eexec[i]);
var glyphsSection = false, subrsSection = false; var glyphsSection = false, subrsSection = false;
var subrs = [], charstrings = [];
var program = { var program = {
subrs: [], subrs: [],
charstrings: [], charstrings: [],
@ -4821,17 +4803,14 @@ var Type1Parser = function type1Parser() {
var data = eexec.slice(i, i + length); var data = eexec.slice(i, i + length);
var lenIV = program.properties.privateData['lenIV']; var lenIV = program.properties.privateData['lenIV'];
var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV); var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV);
var str = decodeCharString(encoded);
if (glyphsSection) { if (glyphsSection) {
program.charstrings.push({ charstrings.push({
glyph: glyph, glyph: glyph,
data: str.charstring, encoded: encoded
lsb: str.lsb,
width: str.width
}); });
} else { } else {
program.subrs.push(str.charstring); subrs.push(encoded);
} }
i += length; i += length;
token = ''; token = '';
@ -4863,12 +4842,11 @@ var Type1Parser = function type1Parser() {
var data = eexec.slice(i + 1, i + 1 + length); var data = eexec.slice(i + 1, i + 1 + length);
var lenIV = program.properties.privateData['lenIV']; var lenIV = program.properties.privateData['lenIV'];
var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV); var encoded = decrypt(data, CHAR_STRS_ENCRYPT_KEY, lenIV);
var str = decodeCharString(encoded);
i = i + 1 + length; i = i + 1 + length;
t = getToken(); // read in 'NP' t = getToken(); // read in 'NP'
if (t == 'noaccess') if (t == 'noaccess')
getToken(); // read in 'put' getToken(); // read in 'put'
program.subrs[index] = str.charstring; subrs[index] = encoded;
} }
break; break;
case '/BlueValues': case '/BlueValues':
@ -4915,6 +4893,26 @@ var Type1Parser = function type1Parser() {
} }
} }
for (var i = 0; i < charstrings.length; i++) {
var glyph = charstrings[i].glyph;
var encoded = charstrings[i].encoded;
var charString = new Type1CharString();
var error = charString.convert(encoded, subrs);
var output = charString.output;
if (error) {
// It seems when FreeType encounters an error while evaluating a glyph
// that it completely ignores the glyph so we'll mimic that behaviour
// here and put an endchar to make the validator happy.
output = [14];
}
program.charstrings.push({
glyph: glyph,
data: output,
lsb: charString.lsb,
width: charString.width
});
}
return program; return program;
}; };
@ -5106,14 +5104,11 @@ Type1Font.prototype = {
}, },
getType2Charstrings: function Type1Font_getType2Charstrings( getType2Charstrings: function Type1Font_getType2Charstrings(
type1Subrs) { type1Charstrings) {
var type2Charstrings = []; var type2Charstrings = [];
var count = type1Subrs.length; for (var i = 0, ii = type1Charstrings.length; i < ii; i++) {
var type1Charstrings = []; type2Charstrings.push(type1Charstrings[i].charstring);
for (var i = 0; i < count; i++) }
type1Charstrings.push(type1Subrs[i].charstring.slice());
for (var i = 0; i < count; i++)
type2Charstrings.push(this.flattenCharstring(type1Charstrings, i));
return type2Charstrings; return type2Charstrings;
}, },
@ -5133,81 +5128,12 @@ Type1Font.prototype = {
type2Subrs.push([0x0B]); type2Subrs.push([0x0B]);
for (var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
type2Subrs.push(this.flattenCharstring(type1Subrs, i)); type2Subrs.push(type1Subrs[i]);
} }
return type2Subrs; return type2Subrs;
}, },
/*
* Flatten the commands by interpreting the postscript code and replacing
* every 'callsubr', 'callothersubr' by the real commands.
*/
commandsMap: {
'hstem': 1,
'vstem': 3,
'vmoveto': 4,
'rlineto': 5,
'hlineto': 6,
'vlineto': 7,
'rrcurveto': 8,
'callsubr': 10,
'return': 11,
'add': [12, 10],
'sub': [12, 11],
'div': [12, 12],
'exch': [12, 28],
'flex': [12, 35],
'drop' : [12, 18],
'endchar': 14,
'rmoveto': 21,
'hmoveto': 22,
'vhcurveto': 30,
'hvcurveto': 31
},
flattenCharstring: function Type1Font_flattenCharstring(charstrings, index) {
var charstring = charstrings[index];
if (!charstring)
return [0x0B];
var map = this.commandsMap;
// charstring changes size - can't cache .length in loop
for (var i = 0; i < charstring.length; i++) {
var command = charstring[i];
if (typeof command === 'string') {
var cmd = map[command];
assert(cmd, 'Unknow command: ' + command);
if (isArray(cmd))
charstring.splice(i++, 1, cmd[0], cmd[1]);
else
charstring[i] = cmd;
} else if (command instanceof CallothersubrCmd) {
var otherSubrCharstring = charstrings[command.index];
if (otherSubrCharstring) {
var lastCommand = otherSubrCharstring.indexOf('return');
if (lastCommand >= 0)
otherSubrCharstring = otherSubrCharstring.slice(0, lastCommand);
charstring.splice.apply(charstring,
[i, 1].concat(otherSubrCharstring));
} else
charstring.splice(i, 1); // ignoring empty subr call
i--;
} else {
// Type1 charstring use a division for number above 32000
if (command > 32000) {
var divisor = charstring[i + 1];
command /= divisor;
charstring.splice(i, 3, 28, (command >> 8) & 0xff, command & 0xff);
} else {
charstring.splice(i, 1, 28, (command >> 8) & 0xff, command & 0xff);
}
i += 2;
}
}
return charstring;
},
wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) { wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) {
var cff = new CFF(); var cff = new CFF();
cff.header = new CFFHeader(1, 0, 4, 4); cff.header = new CFFHeader(1, 0, 4, 4);

View File

@ -0,0 +1,2 @@
http://sci2s.ugr.es/keel/pdf/specific/articulo/alp99.pdf

View File

@ -776,6 +776,14 @@
"link": true, "link": true,
"type": "eq" "type": "eq"
}, },
{ "id": "issue1936",
"file": "pdfs/issue1936.pdf",
"md5": "7302eb9b6a626308e2a933aaed9e1756",
"rounds": 1,
"pageLimit": 1,
"link": true,
"type": "eq"
},
{ "id": "issue2337", { "id": "issue2337",
"file": "pdfs/issue2337.pdf", "file": "pdfs/issue2337.pdf",
"md5": "ea10f4131202b9b8f2a6cb7770d3f185", "md5": "ea10f4131202b9b8f2a6cb7770d3f185",