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

View File

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

View File

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