return;
}
+ this.loadedName = getUniqueName();
+ properties.id = this.loadedName;
var data;
switch (type) {
case 'Type1':
var subtype = properties.subtype;
var cff = (subtype == 'Type1C' || subtype == 'CIDFontType0C') ?
- new Type2CFF(file, properties) : new CFF(name, file, properties);
+ new CFF(file, properties) : new Type1Font(name, file, properties);
// Wrap the CFF data inside an OTF font file
data = this.convert(name, cff, properties);
this.widthMultiplier = !properties.fontMatrix ? 1.0 :
1.0 / properties.fontMatrix[0];
this.encoding = properties.baseEncoding;
- this.loadedName = getUniqueName();
this.loading = true;
};
* The CFF class takes a Type1 file and wrap it into a
* 'Compact Font Format' which itself embed Type2 charstrings.
*/
-var CFFStrings = [
+var CFFStandardStrings = [
'.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent',
'ampersand', 'quoteright', 'parenleft', 'parenright', 'asterisk', 'plus',
'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', 'three', 'four',
var type1Parser = new Type1Parser();
-var CFF = function cffCFF(name, file, properties) {
+// Type1Font is also a CIDFontType0.
+var Type1Font = function Type1Font(name, file, properties) {
// Get the data block containing glyphs and subrs informations
var headerBlock = file.getBytes(properties.length1);
type1Parser.extractFontHeader(headerBlock, properties);
subrs, properties);
};
-CFF.prototype = {
- createCFFIndexHeader: function cff_createCFFIndexHeader(objects, isByte) {
+Type1Font.prototype = {
+ createCFFIndexHeader: function createCFFIndexHeader(objects, isByte) {
// First 2 bytes contains the number of objects contained into this index
var count = objects.length;
return data;
},
- encodeNumber: function cff_encodeNumber(value) {
+ encodeNumber: function encodeNumber(value) {
// some of the fonts has ouf-of-range values
// they are just arithmetic overflows
// make sanitizer happy
}
},
- getOrderedCharStrings: function cff_getOrderedCharStrings(glyphs,
+ getOrderedCharStrings: function type1Font_getOrderedCharStrings(glyphs,
properties) {
var charstrings = [];
var i, length, glyphName;
return charstrings;
},
- getType2Charstrings: function cff_getType2Charstrings(type1Charstrings) {
+ getType2Charstrings: function getType2Charstrings(type1Charstrings) {
var type2Charstrings = [];
var count = type1Charstrings.length;
for (var i = 0; i < count; i++) {
return type2Charstrings;
},
- getType2Subrs: function cff_getType2Subrs(type1Subrs) {
+ getType2Subrs: function getType2Subrs(type1Subrs) {
var bias = 0;
var count = type1Subrs.length;
if (count < 1240)
var count = glyphs.length;
for (var i = 0; i < count; i++) {
- var index = CFFStrings.indexOf(charstrings[i].glyph);
+ var index = CFFStandardStrings.indexOf(charstrings[i].glyph);
// Some characters like asterikmath && circlecopyrt are
// missing from the original strings, for the moment let's
// map them to .notdef and see later if it cause any
}
};
-var Type2CFF = (function Type2CFFClosure() {
- // TODO: replace parsing code with the Type2Parser in font_utils.js
- function Type2CFF(file, properties) {
- var bytes = file.getBytes();
- this.bytes = bytes;
+var CFF = (function CFFClosure() {
+ function CFF(file, properties) {
this.properties = properties;
- this.data = this.parse();
+ var parser = new CFFParser(file, properties);
+ var cff = parser.parse();
+ var compiler = new CFFCompiler(cff);
+ this.readExtra(cff);
+ try {
+ this.data = compiler.compile();
+ } catch (e) {
+ warn('Failed to compile font ' + properties.loadedName);
+ // There may have just been an issue with the compiler, set the data
+ // anyway and hope the font loaded.
+ this.data = file;
+ }
}
- Type2CFF.prototype = {
- parse: function cff_parse() {
- var header = this.parseHeader();
- var properties = this.properties;
-
- var nameIndex = this.parseIndex(header.endPos);
- this.sanitizeName(nameIndex);
-
- var dictIndex = this.parseIndex(nameIndex.endPos);
- if (dictIndex.length != 1)
- error('CFF contains more than 1 font');
-
- var stringIndex = this.parseIndex(dictIndex.endPos);
- var gsubrIndex = this.parseIndex(stringIndex.endPos);
-
- var strings = this.getStrings(stringIndex);
-
- var baseDict = this.parseDict(dictIndex.get(0).data);
- var topDict = this.getTopDict(baseDict, strings);
-
- var bytes = this.bytes;
-
- var privateDict = {};
- var privateInfo = topDict.Private;
- if (privateInfo) {
- var privOffset = privateInfo[1], privLength = privateInfo[0];
- var privBytes = bytes.subarray(privOffset, privOffset + privLength);
- baseDict = this.parseDict(privBytes);
- privateDict = this.getPrivDict(baseDict, strings);
- } else {
- privateDict.defaultWidthX = properties.defaultWidth;
- }
-
- var charStrings = this.parseIndex(topDict.CharStrings);
-
- var charset, encoding;
- var isCIDFont = properties.subtype == 'CIDFontType0C';
- if (isCIDFont) {
- charset = ['.notdef'];
- for (var i = 1, ii = charStrings.length; i < ii; ++i)
- charset.push('glyph' + i);
-
- encoding = this.parseCidMap(topDict.charset,
- charStrings.length);
- } else {
- charset = this.parseCharsets(topDict.charset,
- charStrings.length, strings);
- encoding = this.parseEncoding(topDict.Encoding, properties,
- strings, charset);
- }
-
- // The font sanitizer does not support CFF encoding with a
- // supplement, since the encoding is not really use to map
- // between gid to glyph, let's overwrite what is declared in
- // the top dictionary to let the sanitizer think the font use
- // StandardEncoding, that's a lie but that's ok.
- if (encoding.hasSupplement)
- bytes[topDict.Encoding] &= 0x7F;
-
- // The CFF specification state that the 'dotsection' command
- // (12, 0) is deprecated and treated as a no-op, but all Type2
- // charstrings processors should support them. Unfortunately
- // the font sanitizer don't. As a workaround the sequence (12, 0)
- // is replaced by a useless (0, hmoveto).
- var count = charStrings.length;
- for (var i = 0; i < count; i++) {
- var charstring = charStrings.get(i);
-
- var start = charstring.start;
- var data = charstring.data;
- var length = data.length;
- for (var j = 0; j <= length; j) {
- var value = data[j++];
- if (value == 12 && data[j++] == 0) {
- bytes[start + j - 2] = 139;
- bytes[start + j - 1] = 22;
- } else if (value === 28) {
- j += 2;
- } else if (value >= 247 && value <= 254) {
- j++;
- } else if (value == 255) {
- j += 4;
- }
- }
- }
-
+ CFF.prototype = {
+ readExtra: function readExtra(cff) {
// charstrings contains info about glyphs (one element per glyph
// containing mappings for {unicode, width})
- var charstrings = this.getCharStrings(charset, encoding.encoding,
- privateDict, this.properties);
+ var charset = cff.charset.charset;
+ var encoding = cff.encoding ? cff.encoding.encoding : null;
+ var charstrings = this.getCharStrings(charset, encoding);
// create the mapping between charstring and glyph id
var glyphIds = [];
this.charstrings = charstrings;
this.glyphIds = glyphIds;
-
- var data = [];
- for (var i = 0, ii = bytes.length; i < ii; ++i)
- data.push(bytes[i]);
- return data;
},
-
- getCharStrings: function cff_charstrings(charsets, encoding,
- privateDict, properties) {
+ getCharStrings: function getCharStrings(charsets, encoding) {
var charstrings = [];
var unicodeUsed = [];
var unassignedUnicodeItems = [];
var inverseEncoding = [];
- for (var charcode in encoding)
- inverseEncoding[encoding[charcode]] = charcode | 0;
+ // CID fonts don't have an encoding.
+ if (encoding !== null)
+ for (var charcode in encoding)
+ inverseEncoding[encoding[charcode]] = charcode | 0;
+ else
+ inverseEncoding = charsets;
for (var i = 0, ii = charsets.length; i < ii; i++) {
var glyph = charsets[i];
if (glyph == '.notdef')
}
// sort the array by the unicode value (again)
- charstrings.sort(function type2CFFGetCharStringsSort(a, b) {
+ charstrings.sort(function getCharStringsSort(a, b) {
return a.unicode - b.unicode;
});
return charstrings;
- },
-
- parseEncoding: function cff_parseencoding(pos, properties, strings,
- charset) {
- var encoding = {};
- var bytes = this.bytes;
- var result = {
- encoding: encoding,
- hasSupplement: false
- };
-
- function readSupplement() {
- var supplementsCount = bytes[pos++];
- for (var i = 0; i < supplementsCount; i++) {
- var code = bytes[pos++];
- var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff);
- encoding[code] = properties.differences.indexOf(strings[sid]);
- }
- }
-
- if (pos == 0 || pos == 1) {
- var gid = 1;
- var baseEncoding = pos ? Encodings.ExpertEncoding :
- Encodings.StandardEncoding;
- for (var i = 0, ii = charset.length; i < ii; i++) {
- var index = baseEncoding.indexOf(charset[i]);
- if (index != -1)
- encoding[index] = gid++;
- }
- } else {
- var format = bytes[pos++];
- switch (format & 0x7f) {
- case 0:
- var glyphsCount = bytes[pos++];
- for (var i = 1; i <= glyphsCount; i++)
- encoding[bytes[pos++]] = i;
- break;
-
- case 1:
- var rangesCount = bytes[pos++];
- var gid = 1;
- for (var i = 0; i < rangesCount; i++) {
- var start = bytes[pos++];
- var left = bytes[pos++];
- for (var j = start; j <= start + left; j++)
- encoding[j] = gid++;
- }
- break;
-
- default:
- error('Unknow encoding format: ' + format + ' in CFF');
- break;
- }
- if (format & 0x80) {
- readSupplement();
- result.hasSupplement = true;
- }
- }
- return result;
- },
-
- parseCharsets: function cff_parsecharsets(pos, length, strings) {
- if (pos == 0) {
- return ISOAdobeCharset.slice();
- } else if (pos == 1) {
- return ExpertCharset.slice();
- } else if (pos == 2) {
- return ExpertSubsetCharset.slice();
- }
-
- var bytes = this.bytes;
- var format = bytes[pos++];
- var charset = ['.notdef'];
-
- // subtract 1 for the .notdef glyph
- length -= 1;
-
- switch (format) {
- case 0:
- for (var i = 0; i < length; i++) {
- var sid = (bytes[pos++] << 8) | bytes[pos++];
- charset.push(strings[sid]);
- }
- break;
- case 1:
- while (charset.length <= length) {
- var sid = (bytes[pos++] << 8) | bytes[pos++];
- var count = bytes[pos++];
- for (var i = 0; i <= count; i++)
- charset.push(strings[sid++]);
- }
- break;
- case 2:
- while (charset.length <= length) {
- var sid = (bytes[pos++] << 8) | bytes[pos++];
- var count = (bytes[pos++] << 8) | bytes[pos++];
- for (var i = 0; i <= count; i++)
- charset.push(strings[sid++]);
- }
- break;
- default:
- error('Unknown charset format');
- }
- return charset;
- },
+ }
+ };
- parseCidMap: function cff_parsecharsets(pos, length) {
- var bytes = this.bytes;
- var format = bytes[pos++];
+ return CFF;
+})();
- var encoding = {};
- var map = {encoding: encoding};
+var CFFParser = (function CFFParserClosure() {
+ function CFFParser(file, properties) {
+ this.bytes = file.getBytes();
+ this.properties = properties;
+ }
+ CFFParser.prototype = {
+ parse: function parse() {
+ var properties = this.properties;
+ var cff = new CFFTable();
+ this.cff = cff;
- encoding[0] = 0;
+ // The first five sections must be in order, all the others are reached
+ // via offsets contained in one of the below.
+ var header = this.parseHeader();
+ var nameIndex = this.parseIndex(header.endPos);
+ var topDictIndex = this.parseIndex(nameIndex.endPos);
+ var stringIndex = this.parseIndex(topDictIndex.endPos);
+ var globalSubrIndex = this.parseIndex(stringIndex.endPos);
- var gid = 1;
- switch (format) {
- case 0:
- while (gid < length) {
- var cid = (bytes[pos++] << 8) | bytes[pos++];
- encoding[cid] = gid++;
- }
- break;
- case 1:
- while (gid < length) {
- var cid = (bytes[pos++] << 8) | bytes[pos++];
- var count = bytes[pos++];
- for (var i = 0; i <= count; i++)
- encoding[cid++] = gid++;
- }
- break;
- case 2:
- while (gid < length) {
- var cid = (bytes[pos++] << 8) | bytes[pos++];
- var count = (bytes[pos++] << 8) | bytes[pos++];
- for (var i = 0; i <= count; i++)
- encoding[cid++] = gid++;
- }
- break;
- default:
- error('Unknown charset format');
- }
- return map;
- },
+ var topDictParsed = this.parseDict(topDictIndex.obj.get(0));
+ var topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings);
- getPrivDict: function cff_getprivdict(baseDict, strings) {
- var dict = {};
+ cff.header = header.obj;
+ cff.names = this.parseNameIndex(nameIndex.obj);
+ cff.strings = this.parseStringIndex(stringIndex.obj);
+ cff.topDict = topDict;
+ cff.globalSubrIndex = globalSubrIndex.obj;
- // default values
- dict['defaultWidthX'] = 0;
- dict['nominalWidthX'] = 0;
+ this.parsePrivateDict(cff.topDict);
- for (var i = 0, ii = baseDict.length; i < ii; ++i) {
- var pair = baseDict[i];
- var key = pair[0];
- var value = pair[1];
- switch (key) {
- case 20:
- dict['defaultWidthX'] = value[0];
- case 21:
- dict['nominalWidthX'] = value[0];
- default:
- TODO('interpret top dict key: ' + key);
- }
- }
- return dict;
- },
- getTopDict: function cff_gettopdict(baseDict, strings) {
- var dict = {};
+ cff.isCIDFont = topDict.hasName('ROS');
- // default values
- dict['Encoding'] = 0;
- dict['charset'] = 0;
+ var charStringOffset = topDict.getByName('CharStrings');
+ cff.charStrings = this.parseCharStrings(charStringOffset);
- for (var i = 0, ii = baseDict.length; i < ii; ++i) {
- var pair = baseDict[i];
- var key = pair[0];
- var value = pair[1];
- switch (key) {
- case 1:
- dict['Notice'] = strings[value[0]];
- break;
- case 4:
- dict['Weight'] = strings[value[0]];
- break;
- case 3094:
- dict['BaseFontName'] = strings[value[0]];
- break;
- case 5:
- dict['FontBBox'] = value;
- break;
- case 13:
- dict['UniqueID'] = value[0];
- break;
- case 15:
- dict['charset'] = value[0];
- break;
- case 16:
- dict['Encoding'] = value[0];
- break;
- case 17:
- dict['CharStrings'] = value[0];
- break;
- case 18:
- dict['Private'] = value;
- break;
- case 3102:
- case 3103:
- case 3104:
- case 3105:
- case 3106:
- case 3107:
- case 3108:
- case 3109:
- case 3110:
- dict['cidOperatorPresent'] = true;
- break;
- default:
- TODO('interpret top dict key: ' + key);
- }
- }
- return dict;
- },
- sanitizeName: function cff_sanitizeName(nameIndex) {
- // There should really only be one font, but loop to make sure.
- for (var i = 0, ii = nameIndex.length; i < ii; ++i) {
- var data = nameIndex.get(i).data;
- var length = data.length;
- if (length > 127)
- warn('Font had name longer than 127 chars, will be rejected.');
- // Only certain chars are permitted in the font name.
- for (var j = 0; j < length; ++j) {
- var c = data[j];
- if (j === 0 && c === 0)
- continue;
- if (c < 33 || c > 126) {
- data[j] = 95;
- continue;
- }
- switch (c) {
- case 91: // [
- case 93: // ]
- case 40: // (
- case 41: // )
- case 123: // {
- case 125: // }
- case 60: // <
- case 62: // >
- case 47: // /
- case 37: // %
- data[j] = 95;
- break;
- }
+ var charset, encoding;
+ if (cff.isCIDFont) {
+ var fdArrayIndex = this.parseIndex(topDict.getByName('FDArray')).obj;
+ for (var i = 0, ii = fdArrayIndex.count; i < ii; ++i) {
+ var dictRaw = fdArrayIndex.get(i);
+ var fontDict = this.createDict(CFFTopDict, this.parseDict(dictRaw),
+ cff.strings);
+ this.parsePrivateDict(fontDict);
+ cff.fdArray.push(fontDict);
}
+ // cid fonts don't have an encoding
+ encoding = null;
+ charset = this.parseCharsets(topDict.getByName('charset'),
+ cff.charStrings.count, cff.strings, true);
+ cff.fdSelect = this.parseFDSelect(topDict.getByName('FDSelect'),
+ cff.charStrings.count);
+ } else {
+ charset = this.parseCharsets(topDict.getByName('charset'),
+ cff.charStrings.count, cff.strings, false);
+ encoding = this.parseEncoding(topDict.getByName('Encoding'),
+ properties,
+ cff.strings, charset.charset);
}
- },
- getStrings: function cff_getStrings(stringIndex) {
- function bytesToString(bytesArray) {
- var str = '';
- for (var i = 0, ii = bytesArray.length; i < ii; i++)
- str += String.fromCharCode(bytesArray[i]);
- return str;
- }
-
- var stringArray = [];
- for (var i = 0, ii = CFFStrings.length; i < ii; i++)
- stringArray.push(CFFStrings[i]);
+ cff.charset = charset;
+ cff.encoding = encoding;
- for (var i = 0, ii = stringIndex.length; i < ii; i++)
- stringArray.push(bytesToString(stringIndex.get(i).data));
-
- return stringArray;
+ return cff;
},
- parseHeader: function cff_parseHeader() {
+ parseHeader: function parseHeader() {
var bytes = this.bytes;
var offset = 0;
++offset;
if (offset != 0) {
- warning('cff data is shifted');
+ warn('cff data is shifted');
bytes = bytes.subarray(offset);
this.bytes = bytes;
}
-
- return {
- endPos: bytes[2],
- offsetSize: bytes[3]
- };
+ var major = bytes[0];
+ var minor = bytes[1];
+ var hdrSize = bytes[2];
+ var offSize = bytes[3];
+ var header = new CFFHeader(major, minor, hdrSize, offSize);
+ return {obj: header, endPos: hdrSize};
},
- parseDict: function cff_parseDict(dict) {
+ parseDict: function parseDict(dict) {
var pos = 0;
function parseOperand() {
value = (value << 8) | dict[pos++];
value = (value << 8) | dict[pos++];
return value;
- } else if (value <= 246) {
+ } else if (value >= 32 && value <= 246) {
return value - 139;
- } else if (value <= 250) {
+ } else if (value >= 247 && value <= 250) {
return ((value - 247) * 256) + dict[pos++] + 108;
- } else if (value <= 254) {
+ } else if (value >= 251 && value <= 254) {
return -((value - 251) * 256) - dict[pos++] - 108;
} else {
error('255 is not a valid DICT command');
while (pos < end) {
var b = dict[pos];
if (b <= 21) {
- if (b === 12) {
- ++pos;
- var op = dict[pos];
- if ((op > 14 && op < 17) ||
- (op > 23 && op < 30) || op > 38) {
- warn('Invalid CFF dictionary key: ' + op);
- // trying to replace it with initialRandomSeed
- // to pass sanitizer
- dict[pos] = 19;
- }
- var b = (b << 8) | op;
- }
- if (!operands.length && b == 8 &&
- dict[pos + 1] == 9) {
- // no operands for FamilyBlues, removing the key
- // and next one is FamilyOtherBlues - skipping them
- // also replacing FamilyBlues to pass sanitizer
- dict[pos] = 139;
- pos += 2;
- continue;
- }
+ if (b === 12)
+ b = (b << 8) | dict[++pos];
entries.push([b, operands]);
operands = [];
++pos;
}
return entries;
},
- parseIndex: function cff_parseIndex(pos) {
+ parseIndex: function parseIndex(pos) {
+ var cffIndex = new CFFIndex();
var bytes = this.bytes;
- var count = bytes[pos++] << 8 | bytes[pos++];
+ var count = (bytes[pos++] << 8) | bytes[pos++];
var offsets = [];
+ var start = pos;
var end = pos;
if (count != 0) {
}
end = offsets[count];
}
+ for (var i = 0, ii = offsets.length - 1; i < ii; ++i) {
+ var offsetStart = offsets[i];
+ var offsetEnd = offsets[i + 1];
+ cffIndex.add(bytes.subarray(offsetStart, offsetEnd));
+ }
+ return {obj: cffIndex, endPos: end};
+ },
+ parseNameIndex: function parseNameIndex(index) {
+ var names = [];
+ for (var i = 0, ii = index.count; i < ii; ++i) {
+ var name = index.get(i);
+ // OTS doesn't allow names to be over 127 characters.
+ var length = Math.min(name.length, 127);
+ var data = new Array(length);
+ // OTS also only permits certain characters in the name.
+ for (var j = 0; j < length; ++j) {
+ var c = name[j];
+ if (j === 0 && c === 0) {
+ data[j] = c;
+ continue;
+ }
+ if ((c < 33 || c > 126) || c === 91 /* [ */ || c === 93 /* ] */ ||
+ c === 40 /* ( */ || c === 41 /* ) */ || c === 123 /* { */ ||
+ c === 125 /* } */ || c === 60 /* < */ || c === 62 /* > */ ||
+ c === 47 /* / */ || c === 37 /* % */) {
+ data[j] = 95;
+ continue;
+ }
+ data[j] = c;
+ }
+ names.push(String.fromCharCode.apply(null, data));
+ }
+ return names;
+ },
+ parseStringIndex: function parseStringIndex(index) {
+ var strings = new CFFStrings();
+ for (var i = 0, ii = index.count; i < ii; ++i) {
+ var data = index.get(i);
+ strings.add(String.fromCharCode.apply(null, data));
+ }
+ return strings;
+ },
+ createDict: function createDict(type, dict, strings) {
+ var cffDict = new type(strings);
+ var types = cffDict.types;
+
+ for (var i = 0, ii = dict.length; i < ii; ++i) {
+ var pair = dict[i];
+ var key = pair[0];
+ var value = pair[1];
+ cffDict.setByKey(key, value);
+ }
+ return cffDict;
+ },
+ parseCharStrings: function parseCharStrings(charStringOffset) {
+ var charStrings = this.parseIndex(charStringOffset).obj;
+ // The CFF specification state that the 'dotsection' command
+ // (12, 0) is deprecated and treated as a no-op, but all Type2
+ // charstrings processors should support them. Unfortunately
+ // the font sanitizer don't. As a workaround the sequence (12, 0)
+ // is replaced by a useless (0, hmoveto).
+ var count = charStrings.count;
+ for (var i = 0; i < count; i++) {
+ var charstring = charStrings.get(i);
+ var data = charstring;
+ var length = data.length;
+ for (var j = 0; j <= length; j) {
+ var value = data[j++];
+ if (value == 12 && data[j++] == 0) {
+ data[j - 2] = 139;
+ data[j - 1] = 22;
+ } else if (value === 28) {
+ j += 2;
+ } else if (value >= 247 && value <= 254) {
+ j++;
+ } else if (value == 255) {
+ j += 4;
+ }
+ }
+ }
+ return charStrings;
+ },
+ parsePrivateDict: function parsePrivateDict(parentDict) {
+ // no private dict, do nothing
+ if (!parentDict.hasName('Private'))
+ return;
+ var privateOffset = parentDict.getByName('Private');
+ // make sure the params are formatted correctly
+ if (!isArray(privateOffset) || privateOffset.length !== 2) {
+ parentDict.removeByName('Private');
+ return;
+ }
+ var size = privateOffset[0];
+ var offset = privateOffset[1];
+ // remove empty dicts or ones that refer to invalid location
+ if (size === 0 || offset >= this.bytes.length) {
+ parentDict.removeByName('Private');
+ return;
+ }
+
+ var privateDictEnd = offset + size;
+ var dictData = this.bytes.subarray(offset, privateDictEnd);
+ var dict = this.parseDict(dictData);
+ var privateDict = this.createDict(CFFPrivateDict, dict,
+ parentDict.strings);
+ parentDict.privateDict = privateDict;
+
+ // Parse the Subrs index also since it's relative to the private dict.
+ if (!privateDict.getByName('Subrs'))
+ return;
+ var subrsOffset = privateDict.getByName('Subrs');
+ var relativeOffset = offset + subrsOffset;
+ // Validate the offset.
+ if (subrsOffset === 0 || relativeOffset >= this.bytes.length) {
+ privateDict.removeByName('Subrs');
+ return;
+ }
+ var subrsIndex = this.parseIndex(relativeOffset);
+ privateDict.subrsIndex = subrsIndex.obj;
+ },
+ parseCharsets: function parsecharsets(pos, length, strings, cid) {
+ if (pos == 0) {
+ return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE,
+ ISOAdobeCharset.slice());
+ } else if (pos == 1) {
+ return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT,
+ ExpertCharset.slice());
+ } else if (pos == 2) {
+ return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET,
+ ExpertSubsetCharset.slice());
+ }
+
+ var bytes = this.bytes;
+ var start = pos;
+ var format = bytes[pos++];
+ var charset = ['.notdef'];
+
+ // subtract 1 for the .notdef glyph
+ length -= 1;
+
+ switch (format) {
+ case 0:
+ for (var i = 0; i < length; i++) {
+ var id = (bytes[pos++] << 8) | bytes[pos++];
+ charset.push(cid ? id : strings.get(id));
+ }
+ break;
+ case 1:
+ while (charset.length <= length) {
+ var id = (bytes[pos++] << 8) | bytes[pos++];
+ var count = bytes[pos++];
+ for (var i = 0; i <= count; i++)
+ charset.push(cid ? id++ : strings.get(id++));
+ }
+ break;
+ case 2:
+ while (charset.length <= length) {
+ var id = (bytes[pos++] << 8) | bytes[pos++];
+ var count = (bytes[pos++] << 8) | bytes[pos++];
+ for (var i = 0; i <= count; i++)
+ charset.push(cid ? id++ : strings.get(id++));
+ }
+ break;
+ default:
+ error('Unknown charset format');
+ }
+ // Raw won't be needed if we actually compile the charset.
+ var end = pos;
+ var raw = bytes.subarray(start, end);
+
+ return new CFFCharset(false, format, charset, raw);
+ },
+ parseEncoding: function parseEncoding(pos, properties, strings, charset) {
+ var encoding = {};
+ var bytes = this.bytes;
+ var predefined = false;
+ var hasSupplement = false;
+ var format;
+ var raw = null;
+
+ function readSupplement() {
+ var supplementsCount = bytes[pos++];
+ for (var i = 0; i < supplementsCount; i++) {
+ var code = bytes[pos++];
+ var sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff);
+ encoding[code] = properties.differences.indexOf(strings.get(sid));
+ }
+ }
+
+ if (pos == 0 || pos == 1) {
+ predefined = true;
+ format = pos;
+ var gid = 1;
+ var baseEncoding = pos ? Encodings.ExpertEncoding :
+ Encodings.StandardEncoding;
+ for (var i = 0, ii = charset.length; i < ii; i++) {
+ var index = baseEncoding.indexOf(charset[i]);
+ if (index != -1)
+ encoding[index] = gid++;
+ }
+ } else {
+ var dataStart = pos;
+ var format = bytes[pos++];
+ switch (format & 0x7f) {
+ case 0:
+ var glyphsCount = bytes[pos++];
+ for (var i = 1; i <= glyphsCount; i++)
+ encoding[bytes[pos++]] = i;
+ break;
+
+ case 1:
+ var rangesCount = bytes[pos++];
+ var gid = 1;
+ for (var i = 0; i < rangesCount; i++) {
+ var start = bytes[pos++];
+ var left = bytes[pos++];
+ for (var j = start; j <= start + left; j++)
+ encoding[j] = gid++;
+ }
+ break;
+
+ default:
+ error('Unknow encoding format: ' + format + ' in CFF');
+ break;
+ }
+ var dataEnd = pos;
+ if (format & 0x80) {
+ // The font sanitizer does not support CFF encoding with a
+ // supplement, since the encoding is not really used to map
+ // between gid to glyph, let's overwrite what is declared in
+ // the top dictionary to let the sanitizer think the font use
+ // StandardEncoding, that's a lie but that's ok.
+ bytes[dataStart] &= 0x7f;
+ readSupplement();
+ hasSupplement = true;
+ }
+ raw = bytes.subarray(dataStart, dataEnd);
+ }
+ format = format & 0x7f;
+ return new CFFEncoding(predefined, format, encoding, raw);
+ },
+ parseFDSelect: function parseFDSelect(pos, length) {
+ var start = pos;
+ var bytes = this.bytes;
+ var format = bytes[pos++];
+ var fdSelect = [];
+ switch (format) {
+ case 0:
+ for (var i = 0; i < length; ++i) {
+ var id = bytes[pos++];
+ fdSelect.push(id);
+ }
+ break;
+ case 3:
+ var rangesCount = (bytes[pos++] << 8) | bytes[pos++];
+ for (var i = 0; i < rangesCount; ++i) {
+ var first = (bytes[pos++] << 8) | bytes[pos++];
+ var fdIndex = bytes[pos++];
+ var next = (bytes[pos] << 8) | bytes[pos + 1];
+ for (var j = first; j < next; ++j)
+ fdSelect.push(fdIndex);
+ }
+ // Advance past the sentinel(next).
+ pos += 2;
+ break;
+ default:
+ error('Unknown fdselect format ' + format);
+ break;
+ }
+ var end = pos;
+ return new CFFFDSelect(fdSelect, bytes.subarray(start, end));
+ }
+ };
+ return CFFParser;
+})();
+
+// Compact Font Format
+var CFFTable = (function CFFTableClosure() {
+ function CFFTable() {
+ this.header = null;
+ this.names = [];
+ this.topDict = null;
+ this.strings = new CFFStrings();
+ this.globalSubrIndex = null;
+
+ // The following could really be per font, but since we only have one font
+ // store them here.
+ this.encoding = null;
+ this.charset = null;
+ this.charStrings = null;
+ this.fdArray = [];
+ this.fdSelect = null;
+
+ this.isCIDFont = false;
+ }
+ return CFFTable;
+})();
+
+var CFFHeader = (function CFFHeader() {
+ function CFFHeader(major, minor, hdrSize, offSize) {
+ this.major = major;
+ this.minor = minor;
+ this.hdrSize = hdrSize;
+ this.offSize = offSize;
+ }
+ return CFFHeader;
+})();
+
+var CFFStrings = (function CFFStrings() {
+ function CFFStrings() {
+ this.strings = [];
+ }
+ CFFStrings.prototype = {
+ get: function get(index) {
+ if (index >= 0 && index <= 390)
+ return CFFStandardStrings[index];
+ if (index - 391 <= this.strings.length)
+ return this.strings[index - 391];
+ return CFFStandardStrings[0];
+ },
+ add: function add(value) {
+ this.strings.push(value);
+ },
+ get count() {
+ return this.strings.length;
+ }
+ };
+ return CFFStrings;
+})();
+
+var CFFIndex = (function() {
+ function CFFIndex() {
+ this.objects = [];
+ this.length = 0;
+ }
+ CFFIndex.prototype = {
+ add: function add(data) {
+ this.length += data.length;
+ this.objects.push(data);
+ },
+ get: function get(index) {
+ return this.objects[index];
+ },
+ get count() {
+ return this.objects.length;
+ }
+ };
+ return CFFIndex;
+})();
+
+var CFFDict = (function CFFDictClosure() {
+ function CFFDict(tables, strings) {
+ this.keyToNameMap = tables.keyToNameMap;
+ this.nameToKeyMap = tables.nameToKeyMap;
+ this.defaults = tables.defaults;
+ this.types = tables.types;
+ this.opcodes = tables.opcodes;
+ this.order = tables.order;
+ this.strings = strings;
+ this.values = {};
+ }
+ CFFDict.prototype = {
+ // value should always be an array
+ setByKey: function setByKey(key, value) {
+ if (!(key in this.keyToNameMap))
+ return false;
+ // ignore empty values
+ if (value.length === 0)
+ return true;
+ var type = this.types[key];
+ // remove the array wrapping these types of values
+ if (type === 'num' || type === 'sid' || type === 'offset')
+ value = value[0];
+ this.values[key] = value;
+ return true;
+ },
+ hasName: function hasName(name) {
+ return this.nameToKeyMap[name] in this.values;
+ },
+ getByName: function getByName(name) {
+ if (!(name in this.nameToKeyMap))
+ error('Invalid dictionary name "' + name + '"');
+ var key = this.nameToKeyMap[name];
+ if (!(key in this.values))
+ return this.defaults[key];
+ return this.values[key];
+ },
+ removeByName: function removeByName(name) {
+ delete this.values[this.nameToKeyMap[name]];
+ },
+ dump: function dump(title) {
+ console.log('----' + title + ' Dictionary Dump------');
+ for (var key in this.values) {
+ if (key in this.keyToNameMap)
+ console.log(this.keyToNameMap[key] + '(' + key + '): ' +
+ this.values[key]);
+ else
+ console.log('Unknown(' + key + '): ' + this.values[key]);
+ }
+ }
+ };
+ CFFDict.createTables = function createTables(layout) {
+ var tables = {
+ keyToNameMap: {},
+ nameToKeyMap: {},
+ defaults: {},
+ types: {},
+ opcodes: {},
+ order: []
+ };
+ for (var i = 0, ii = layout.length; i < ii; ++i) {
+ var entry = layout[i];
+ var key = isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0];
+ tables.keyToNameMap[key] = entry[1];
+ tables.nameToKeyMap[entry[1]] = key;
+ tables.types[key] = entry[2];
+ tables.defaults[key] = entry[3];
+ tables.opcodes[key] = isArray(entry[0]) ? entry[0] : [entry[0]];
+ tables.order.push(key);
+ }
+ return tables;
+ };
+ return CFFDict;
+})();
+
+var CFFTopDict = (function CFFTopDictClosure() {
+ var layout = [
+ [[12, 30], 'ROS', ['sid', 'sid', 'num'], null],
+ [[12, 20], 'SyntheticBase', 'num', null],
+ [0, 'version', 'sid', null],
+ [1, 'Notice', 'sid', null],
+ [[12, 0], 'Copyright', 'sid', null],
+ [2, 'FullName', 'sid', null],
+ [3, 'FamilyName', 'sid', null],
+ [4, 'Weight', 'sid', null],
+ [[12, 1], 'isFixedPitch', 'num', 0],
+ [[12, 2], 'ItalicAngle', 'num', 0],
+ [[12, 3], 'UnderlinePosition', 'num', -100],
+ [[12, 4], 'UnderlineThickness', 'num', 50],
+ [[12, 5], 'PaintType', 'num', 0],
+ [[12, 6], 'CharstringType', 'num', 2],
+ [[12, 7], 'FontMatrix', ['num', 'num', 'num', 'num'],
+ [.001, 0, 0, .001, 0, 0]],
+ [13, 'UniqueID', 'num', null],
+ [5, 'FontBBox', ['num', 'num', 'num', 'num'], [0, 0, 0, 0]],
+ [[12, 8], 'StrokeWidth', 'num', 0],
+ [14, 'XUID', 'array', null],
+ [15, 'charset', 'offset', 0],
+ [16, 'Encoding', 'offset', 0],
+ [17, 'CharStrings', 'offset', 0],
+ [18, 'Private', ['offset', 'offset'], null],
+ [[12, 21], 'PostScript', 'sid', null],
+ [[12, 22], 'BaseFontName', 'sid', null],
+ [[12, 23], 'BaseFontBlend', 'delta', null],
+ [[12, 31], 'CIDFontVersion', 'num', 0],
+ [[12, 32], 'CIDFontRevision', 'num', 0],
+ [[12, 33], 'CIDFontType', 'num', 0],
+ [[12, 34], 'CIDCount', 'num', 8720],
+ [[12, 35], 'UIDBase', 'num', null],
+ [[12, 36], 'FDArray', 'offset', null],
+ [[12, 37], 'FDSelect', 'offset', null],
+ [[12, 38], 'FontName', 'sid', null]];
+ var tables = null;
+ function CFFTopDict(strings) {
+ if (tables === null)
+ tables = CFFDict.createTables(layout);
+ CFFDict.call(this, tables, strings);
+ this.privateDict = null;
+ }
+ CFFTopDict.prototype = Object.create(CFFDict.prototype);
+ return CFFTopDict;
+})();
+
+var CFFPrivateDict = (function CFFPrivateDictClosure() {
+ var layout = [
+ [6, 'BlueValues', 'delta', null],
+ [7, 'OtherBlues', 'delta', null],
+ [8, 'FamilyBlues', 'delta', null],
+ [9, 'FamilyOtherBlues', 'delta', null],
+ [[12, 9], 'BlueScale', 'num', 0.039625],
+ [[12, 10], 'BlueShift', 'num', 7],
+ [[12, 11], 'BlueFuzz', 'num', 1],
+ [10, 'StdHW', 'num', null],
+ [11, 'StdVW', 'num', null],
+ [[12, 12], 'StemSnapH', 'delta', null],
+ [[12, 13], 'StemSnapV', 'delta', null],
+ [[12, 14], 'ForceBold', 'num', 0],
+ [[12, 17], 'LanguageGroup', 'num', 0],
+ [[12, 18], 'ExpansionFactor', 'num', 0.06],
+ [[12, 19], 'initialRandomSeed', 'num', 0],
+ [19, 'Subrs', 'offset', null],
+ [20, 'defaultWidthX', 'num', 0],
+ [21, 'nominalWidthX', 'num', 0]
+ ];
+ var tables = null;
+ function CFFPrivateDict(strings) {
+ if (tables === null)
+ tables = CFFDict.createTables(layout);
+ CFFDict.call(this, tables, strings);
+ this.subrsIndex = null;
+ }
+ CFFPrivateDict.prototype = Object.create(CFFDict.prototype);
+ return CFFPrivateDict;
+})();
+
+var CFFCharsetPredefinedTypes = {
+ ISO_ADOBE: 0,
+ EXPERT: 1,
+ EXPERT_SUBSET: 2
+};
+var CFFCharsetEmbeddedTypes = {
+ FORMAT0: 0,
+ FORMAT1: 1,
+ FORMAT2: 2
+};
+var CFFCharset = (function CFFCharsetClosure() {
+ function CFFCharset(predefined, format, charset, raw) {
+ this.predefined = predefined;
+ this.format = format;
+ this.charset = charset;
+ this.raw = raw;
+ }
+ return CFFCharset;
+})();
+
+var CFFEncodingPredefinedTypes = {
+ STANDARD: 0,
+ EXPERT: 1
+};
+var CFFCharsetEmbeddedTypes = {
+ FORMAT0: 0,
+ FORMAT1: 1
+};
+var CFFEncoding = (function CFFEncodingClosure() {
+ function CFFEncoding(predefined, format, encoding, raw) {
+ this.predefined = predefined;
+ this.format = format;
+ this.encoding = encoding;
+ this.raw = raw;
+ }
+ return CFFEncoding;
+})();
+
+var CFFFDSelect = (function CFFFDSelectClosure() {
+ function CFFFDSelect(fdSelect, raw) {
+ this.fdSelect = fdSelect;
+ this.raw = raw;
+ }
+ return CFFFDSelect;
+})();
+
+// Helper class to keep track of where an offset is within the data and helps
+// filling in that offset once it's known.
+var CFFOffsetTracker = (function CFFOffsetTracker() {
+ function CFFOffsetTracker() {
+ this.offsets = {};
+ }
+ CFFOffsetTracker.prototype = {
+ isTracking: function isTracking(key) {
+ return key in this.offsets;
+ },
+ track: function track(key, location) {
+ if (key in this.offsets)
+ error('Already tracking location of ' + key);
+ this.offsets[key] = location;
+ },
+ offset: function offset(value) {
+ for (var key in this.offsets) {
+ this.offsets[key] += value;
+ }
+ },
+ setEntryLocation: function setEntryLocation(key, values, output) {
+ if (!(key in this.offsets))
+ error('Not tracking location of ' + key);
+ var data = output.data;
+ var dataOffset = this.offsets[key];
+ var size = 5;
+ for (var i = 0, ii = values.length; i < ii; ++i) {
+ var offset0 = i * size + dataOffset;
+ var offset1 = offset0 + 1;
+ var offset2 = offset0 + 2;
+ var offset3 = offset0 + 3;
+ var offset4 = offset0 + 4;
+ // It's easy to screw up offsets so perform this sanity check.
+ if (data[offset0] !== 0x1d || data[offset1] !== 0 ||
+ data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0)
+ error('writing to an offset that is not empty');
+ var value = values[i];
+ data[offset0] = 0x1d;
+ data[offset1] = (value >> 24) & 0xFF;
+ data[offset2] = (value >> 16) & 0xFF;
+ data[offset3] = (value >> 8) & 0xFF;
+ data[offset4] = value & 0xFF;
+ }
+ }
+ };
+ return CFFOffsetTracker;
+})();
+
+// Takes a CFF and converts it to the binary representation.
+var CFFCompiler = (function CFFCompilerClosure() {
+ function stringToArray(str) {
+ var array = [];
+ for (var i = 0, ii = str.length; i < ii; ++i)
+ array[i] = str.charCodeAt(i);
+
+ return array;
+ };
+ function CFFCompiler(cff) {
+ this.cff = cff;
+ }
+ CFFCompiler.prototype = {
+ compile: function compile() {
+ var cff = this.cff;
+ var output = {
+ data: [],
+ length: 0,
+ add: function add(data) {
+ this.data = this.data.concat(data);
+ this.length = this.data.length;
+ }
+ };
+
+ // Compile the five entries that must be in order.
+ var header = this.compileHeader(cff.header);
+ output.add(header);
+
+ var nameIndex = this.compileNameIndex(cff.names);
+ output.add(nameIndex);
+
+ var compiled = this.compileTopDicts([cff.topDict], output.length);
+ output.add(compiled.output);
+ var topDictTracker = compiled.trackers[0];
+
+ var stringIndex = this.compileStringIndex(cff.strings.strings);
+ output.add(stringIndex);
+
+ var globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
+ output.add(globalSubrIndex);
+
+ // Now start on the other entries that have no specfic order.
+ if (cff.encoding && cff.topDict.hasName('Encoding')) {
+ if (cff.encoding.predefined) {
+ topDictTracker.setEntryLocation('Encoding', [cff.encoding.format],
+ output);
+ } else {
+ var encoding = this.compileEncoding(cff.encoding);
+ topDictTracker.setEntryLocation('Encoding', [output.length], output);
+ output.add(encoding);
+ }
+ }
+
+ if (cff.charset && cff.topDict.hasName('charset')) {
+ if (cff.charset.predefined) {
+ topDictTracker.setEntryLocation('charset', [cff.charset.format],
+ output);
+ } else {
+ var charset = this.compileCharset(cff.charset);
+ topDictTracker.setEntryLocation('charset', [output.length], output);
+ output.add(charset);
+ }
+ }
+
+ var charStrings = this.compileCharStrings(cff.charStrings);
+ topDictTracker.setEntryLocation('CharStrings', [output.length], output);
+ output.add(charStrings);
+
+ if (cff.isCIDFont) {
+ var compiled = this.compileTopDicts(cff.fdArray, output.length);
+ topDictTracker.setEntryLocation('FDArray', [output.length], output);
+ output.add(compiled.output);
+ var fontDictTrackers = compiled.trackers;
+
+ this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
+
+ topDictTracker.setEntryLocation('FDSelect', [output.length], output);
+ var fdSelect = this.compileFDSelect(cff.fdSelect.raw);
+ output.add(fdSelect);
+ }
+
+ this.compilePrivateDicts([cff.topDict], [topDictTracker], output);
+
+ return output.data;
+ },
+ encodeNumber: function encodeNumber(value) {
+ if (parseFloat(value) == parseInt(value) && !isNaN(value)) // isInt
+ return this.encodeInteger(value);
+ else
+ return this.encodeFloat(value);
+ },
+ encodeFloat: function encodeFloat(value) {
+ value = value.toString();
+ // Strip off the any leading zeros.
+ if (value.substr(0, 2) === '0.')
+ value = value.substr(1);
+ else if (value.substr(0, 3) === '-0.')
+ value = '-' + value.substr(2);
+ var nibbles = [];
+ for (var i = 0, ii = value.length; i < ii; ++i) {
+ var a = value.charAt(i), b = value.charAt(i + 1);
+ var nibble;
+ if (a === 'e' && b === '-') {
+ nibble = 0xc;
+ ++i;
+ } else if (a === '.') {
+ nibble = 0xa;
+ } else if (a === 'E') {
+ nibble = 0xb;
+ } else if (a === '-') {
+ nibble = 0xe;
+ } else {
+ nibble = a;
+ }
+ nibbles.push(nibble);
+ }
+ nibbles.push(0xf);
+ if (nibbles.length % 2)
+ nibbles.push(0xf);
+ var out = [30];
+ for (var i = 0, ii = nibbles.length; i < ii; i += 2)
+ out.push(nibbles[i] << 4 | nibbles[i + 1]);
+ return out;
+ },
+ encodeInteger: function encodeInteger(value) {
+ var code;
+ if (value >= -107 && value <= 107) {
+ code = [value + 139];
+ } else if (value >= 108 && value <= 1131) {
+ value = [value - 108];
+ code = [(value >> 8) + 247, value & 0xFF];
+ } else if (value >= -1131 && value <= -108) {
+ value = -value - 108;
+ code = [(value >> 8) + 251, value & 0xFF];
+ } else if (value >= -32768 && value <= 32767) {
+ code = [0x1c, (value >> 8) & 0xFF, value & 0xFF];
+ } else {
+ code = [0x1d,
+ (value >> 24) & 0xFF,
+ (value >> 16) & 0xFF,
+ (value >> 8) & 0xFF,
+ value & 0xFF];
+ }
+ return code;
+ },
+ compileHeader: function compileHeader(header) {
+ return [
+ header.major,
+ header.minor,
+ header.hdrSize,
+ header.offSize
+ ];
+ },
+ compileNameIndex: function compileNameIndex(names) {
+ var nameIndex = new CFFIndex();
+ for (var i = 0, ii = names.length; i < ii; ++i)
+ nameIndex.add(stringToArray(names[i]));
+ return this.compileIndex(nameIndex);
+ },
+ compileTopDicts: function compileTopDicts(dicts, length) {
+ var fontDictTrackers = [];
+ var fdArrayIndex = new CFFIndex();
+ for (var i = 0, ii = dicts.length; i < ii; ++i) {
+ var fontDict = dicts[i];
+ var fontDictTracker = new CFFOffsetTracker();
+ var fontDictData = this.compileDict(fontDict, fontDictTracker);
+ fontDictTrackers.push(fontDictTracker);
+ fdArrayIndex.add(fontDictData);
+ fontDictTracker.offset(length);
+ }
+ fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers);
return {
- get: function index_get(index) {
- if (index >= count)
- return null;
-
- var start = offsets[index];
- var end = offsets[index + 1];
- return {
- start: start,
- end: end,
- data: bytes.subarray(start, end)
- };
- },
- length: count,
- endPos: end
+ trackers: fontDictTrackers,
+ output: fdArrayIndex
};
+ },
+ compilePrivateDicts: function compilePrivateDicts(dicts, trackers, output) {
+ for (var i = 0, ii = dicts.length; i < ii; ++i) {
+ var fontDict = dicts[i];
+ if (!fontDict.privateDict || !fontDict.hasName('Private'))
+ continue;
+ var privateDict = fontDict.privateDict;
+ var privateDictTracker = new CFFOffsetTracker();
+ var privateDictData = this.compileDict(privateDict, privateDictTracker);
+
+ privateDictTracker.offset(output.length);
+ trackers[i].setEntryLocation('Private',
+ [privateDictData.length, output.length],
+ output);
+ output.add(privateDictData);
+
+ if (privateDict.subrsIndex && privateDict.hasName('Subrs')) {
+ var subrs = this.compileIndex(privateDict.subrsIndex);
+ privateDictTracker.setEntryLocation('Subrs', [privateDictData.length],
+ output);
+ output.add(subrs);
+ }
+ }
+ },
+ compileDict: function compileDict(dict, offsetTracker) {
+ var out = [];
+ // The dictionary keys must be in a certain order.
+ var order = dict.order;
+ for (var i = 0; i < order.length; ++i) {
+ var key = order[i];
+ if (!(key in dict.values))
+ continue;
+ //console.log('dict order: ' + dict.keyToNameMap[key]);
+ var values = dict.values[key];
+ var types = dict.types[key];
+ if (!isArray(types)) types = [types];
+ if (!isArray(values)) values = [values];
+
+ // Remove any empty dict values.
+ if (values.length === 0)
+ continue;
+
+ for (var j = 0, jj = types.length; j < jj; ++j) {
+ var type = types[j];
+ var value = values[j];
+ switch (type) {
+ case 'num':
+ case 'sid':
+ out = out.concat(this.encodeNumber(value));
+ break;
+ case 'offset':
+ // For offsets we just insert a 32bit integer so we don't have to
+ // deal with figuring out the length of the offset when it gets
+ // replaced later on by the compiler.
+ var name = dict.keyToNameMap[key];
+ // Some offsets have the offset and the length, so just record the
+ // position of the first one.
+ if (!offsetTracker.isTracking(name))
+ offsetTracker.track(name, out.length);
+ out = out.concat([0x1d, 0, 0, 0, 0]);
+ break;
+ case 'array':
+ case 'delta':
+ out = out.concat(this.encodeNumber(value));
+ for (var k = 1, kk = values.length; k < kk; ++k)
+ out = out.concat(this.encodeNumber(values[k]));
+ break;
+ default:
+ error('Unknown data type of ' + type);
+ break;
+ }
+ }
+ out = out.concat(dict.opcodes[key]);
+ }
+ return out;
+ },
+ compileStringIndex: function compileStringIndex(strings) {
+ var stringIndex = new CFFIndex();
+ for (var i = 0, ii = strings.length; i < ii; ++i)
+ stringIndex.add(stringToArray(strings[i]));
+ return this.compileIndex(stringIndex);
+ },
+ compileGlobalSubrIndex: function compileGlobalSubrIndex() {
+ var globalSubrIndex = this.cff.globalSubrIndex;
+ this.out.writeByteArray(this.compileIndex(globalSubrIndex));
+ },
+ compileCharStrings: function compileCharStrings(charStrings) {
+ return this.compileIndex(charStrings);
+ },
+ compileCharset: function compileCharset(charset) {
+ return this.compileTypedArray(charset.raw);
+ },
+ compileEncoding: function compileEncoding(encoding) {
+ return this.compileTypedArray(encoding.raw);
+ },
+ compileFDSelect: function compileFDSelect(fdSelect) {
+ return this.compileTypedArray(fdSelect);
+ },
+ compileTypedArray: function compileTypedArray(data) {
+ var out = new Array(data.length);
+ for (var i = 0, ii = data.length; i < ii; ++i)
+ out[i] = data[i];
+ return out;
+ },
+ compileIndex: function compileIndex(index, trackers) {
+ trackers = trackers || [];
+ var objects = index.objects;
+ // First 2 bytes contains the number of objects contained into this index
+ var count = objects.length;
+
+ // If there is no object, just create an index. This technically
+ // should just be [0, 0] but OTS has an issue with that.
+ if (count == 0)
+ return [0, 0, 0];
+
+ var data = [(count >> 8) & 0xFF, count & 0xff];
+
+ var lastOffset = 1;
+ for (var i = 0; i < count; ++i)
+ lastOffset += objects[i].length;
+
+ var offsetSize;
+ if (lastOffset < 0x100)
+ offsetSize = 1;
+ else if (lastOffset < 0x10000)
+ offsetSize = 2;
+ else if (lastOffset < 0x1000000)
+ offsetSize = 3;
+ else
+ offsetSize = 4;
+
+ // Next byte contains the offset size use to reference object in the file
+ data.push(offsetSize);
+
+ // Add another offset after this one because we need a new offset
+ var relativeOffset = 1;
+ for (var i = 0; i < count + 1; i++) {
+ if (offsetSize === 1) {
+ data.push(relativeOffset & 0xFF);
+ } else if (offsetSize === 2) {
+ data.push((relativeOffset >> 8) & 0xFF,
+ relativeOffset & 0xFF);
+ } else if (offsetSize === 3) {
+ data.push((relativeOffset >> 16) & 0xFF,
+ (relativeOffset >> 8) & 0xFF,
+ relativeOffset & 0xFF);
+ } else {
+ data.push((relativeOffset >>> 24) & 0xFF,
+ (relativeOffset >> 16) & 0xFF,
+ (relativeOffset >> 8) & 0xFF,
+ relativeOffset & 0xFF);
+ }
+
+ if (objects[i])
+ relativeOffset += objects[i].length;
+ }
+ var offset = data.length;
+
+ for (var i = 0; i < count; i++) {
+ // Notify the tracker where the object will be offset in the data.
+ if (trackers[i])
+ trackers[i].offset(data.length);
+ for (var j = 0, jj = objects[i].length; j < jj; j++)
+ data.push(objects[i][j]);
+ }
+ return data;
}
};
-
- return Type2CFF;
+ return CFFCompiler;
})();