Use CFF compiler for building Type1 font.
This commit is contained in:
parent
4b4601d1fb
commit
a235ec1441
283
src/fonts.js
283
src/fonts.js
@ -5050,61 +5050,6 @@ var Type1Font = function Type1Font(name, file, properties) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Type1Font.prototype = {
|
Type1Font.prototype = {
|
||||||
createCFFIndexHeader: function Type1Font_createCFFIndexHeader(objects,
|
|
||||||
isByte) {
|
|
||||||
// First 2 bytes contains the number of objects contained into this index
|
|
||||||
var count = objects.length;
|
|
||||||
|
|
||||||
// If there is no object, just create an array saying that with another
|
|
||||||
// offset byte.
|
|
||||||
if (count == 0)
|
|
||||||
return '\x00\x00\x00';
|
|
||||||
|
|
||||||
var data = String.fromCharCode((count >> 8) & 0xFF, count & 0xff);
|
|
||||||
|
|
||||||
// Next byte contains the offset size use to reference object in the file
|
|
||||||
// Actually we're using 0x04 to be sure to be able to store everything
|
|
||||||
// without thinking of it while coding.
|
|
||||||
data += '\x04';
|
|
||||||
|
|
||||||
// Add another offset after this one because we need a new offset
|
|
||||||
var relativeOffset = 1;
|
|
||||||
for (var i = 0; i < count + 1; i++) {
|
|
||||||
data += String.fromCharCode((relativeOffset >>> 24) & 0xFF,
|
|
||||||
(relativeOffset >> 16) & 0xFF,
|
|
||||||
(relativeOffset >> 8) & 0xFF,
|
|
||||||
relativeOffset & 0xFF);
|
|
||||||
|
|
||||||
if (objects[i])
|
|
||||||
relativeOffset += objects[i].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < count; i++) {
|
|
||||||
for (var j = 0, jj = objects[i].length; j < jj; j++)
|
|
||||||
data += isByte ? String.fromCharCode(objects[i][j] & 0xFF) :
|
|
||||||
objects[i][j];
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
|
|
||||||
encodeNumber: function Type1Font_encodeNumber(value) {
|
|
||||||
// some of the fonts has ouf-of-range values
|
|
||||||
// they are just arithmetic overflows
|
|
||||||
// make sanitizer happy
|
|
||||||
value |= 0;
|
|
||||||
if (value >= -32768 && value <= 32767) {
|
|
||||||
return '\x1c' +
|
|
||||||
String.fromCharCode((value >> 8) & 0xFF) +
|
|
||||||
String.fromCharCode(value & 0xFF);
|
|
||||||
} else {
|
|
||||||
return '\x1d' +
|
|
||||||
String.fromCharCode((value >> 24) & 0xFF) +
|
|
||||||
String.fromCharCode((value >> 16) & 0xFF) +
|
|
||||||
String.fromCharCode((value >> 8) & 0xFF) +
|
|
||||||
String.fromCharCode(value & 0xFF);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getOrderedCharStrings: function Type1Font_getOrderedCharStrings(glyphs,
|
getOrderedCharStrings: function Type1Font_getOrderedCharStrings(glyphs,
|
||||||
properties) {
|
properties) {
|
||||||
var charstrings = [];
|
var charstrings = [];
|
||||||
@ -5223,9 +5168,9 @@ Type1Font.prototype = {
|
|||||||
if (command > 32000) {
|
if (command > 32000) {
|
||||||
var divisor = charstring[i + 1];
|
var divisor = charstring[i + 1];
|
||||||
command /= divisor;
|
command /= divisor;
|
||||||
charstring.splice(i, 3, 28, command >> 8, command & 0xff);
|
charstring.splice(i, 3, 28, (command >> 8) & 0xff, command & 0xff);
|
||||||
} else {
|
} else {
|
||||||
charstring.splice(i, 1, 28, command >> 8, command & 0xff);
|
charstring.splice(i, 1, 28, (command >> 8) & 0xff, command & 0xff);
|
||||||
}
|
}
|
||||||
i += 2;
|
i += 2;
|
||||||
}
|
}
|
||||||
@ -5234,150 +5179,88 @@ Type1Font.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) {
|
wrap: function Type1Font_wrap(name, glyphs, charstrings, subrs, properties) {
|
||||||
var comp = new CFFCompiler();
|
var cff = new CFF();
|
||||||
// TODO: remove this function after refactoring wrap to use the CFFCompiler.
|
cff.header = new CFFHeader(1, 0, 4, 4);
|
||||||
function encodeNumber(num) {
|
|
||||||
var val = comp.encodeNumber(num);
|
cff.names = [name];
|
||||||
var ret = '';
|
|
||||||
for (var i = 0; i < val.length; i++) {
|
var topDict = new CFFTopDict();
|
||||||
ret += String.fromCharCode(val[i]);
|
topDict.setByName('version', 0);
|
||||||
}
|
topDict.setByName('Notice', 1);
|
||||||
return ret;
|
topDict.setByName('FullName', 2);
|
||||||
|
topDict.setByName('FamilyName', 3);
|
||||||
|
topDict.setByName('Weight', 4);
|
||||||
|
topDict.setByName('Encoding', null); // placeholder
|
||||||
|
topDict.setByName('FontBBox', properties.bbox);
|
||||||
|
topDict.setByName('charset', null); // placeholder
|
||||||
|
topDict.setByName('CharStrings', null); // placeholder
|
||||||
|
topDict.setByName('Private', null); // placeholder
|
||||||
|
cff.topDict = topDict;
|
||||||
|
|
||||||
|
var strings = new CFFStrings();
|
||||||
|
strings.add('Version 0.11'); // Version
|
||||||
|
strings.add('See original notice'); // Notice
|
||||||
|
strings.add(name); // FullName
|
||||||
|
strings.add(name); // FamilyName
|
||||||
|
strings.add('Medium'); // Weight
|
||||||
|
cff.strings = strings;
|
||||||
|
|
||||||
|
cff.globalSubrIndex = new CFFIndex();
|
||||||
|
|
||||||
|
var count = glyphs.length;
|
||||||
|
var charsetArray = [0];
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
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
|
||||||
|
// problems
|
||||||
|
if (index == -1)
|
||||||
|
index = 0;
|
||||||
|
|
||||||
|
charsetArray.push((index >> 8) & 0xff, index & 0xff);
|
||||||
}
|
}
|
||||||
|
cff.charset = new CFFCharset(false, 0, [], charsetArray);
|
||||||
|
|
||||||
var fields = {
|
var charStringsIndex = new CFFIndex();
|
||||||
// major version, minor version, header size, offset size
|
charStringsIndex.add([0x8B, 0x0E]); // .notdef
|
||||||
'header': '\x01\x00\x04\x04',
|
for (var i = 0; i < count; i++) {
|
||||||
|
charStringsIndex.add(glyphs[i]);
|
||||||
'names': this.createCFFIndexHeader([name]),
|
|
||||||
|
|
||||||
'topDict': (function topDict(self) {
|
|
||||||
return function cffWrapTopDict() {
|
|
||||||
var header = '\x00\x01\x01\x01';
|
|
||||||
var dict =
|
|
||||||
'\xf8\x1b\x00' + // version
|
|
||||||
'\xf8\x1c\x01' + // Notice
|
|
||||||
'\xf8\x1d\x02' + // FullName
|
|
||||||
'\xf8\x1e\x03' + // FamilyName
|
|
||||||
'\xf8\x1f\x04' + // Weight
|
|
||||||
'\x1c\x00\x00\x10'; // Encoding
|
|
||||||
|
|
||||||
var boundingBox = properties.bbox;
|
|
||||||
for (var i = 0, ii = boundingBox.length; i < ii; i++)
|
|
||||||
dict += encodeNumber(boundingBox[i]);
|
|
||||||
dict += '\x05'; // FontBBox;
|
|
||||||
|
|
||||||
var offset = fields.header.length +
|
|
||||||
fields.names.length +
|
|
||||||
(header.length + 1) +
|
|
||||||
(dict.length + (4 + 4)) +
|
|
||||||
fields.strings.length +
|
|
||||||
fields.globalSubrs.length;
|
|
||||||
|
|
||||||
// If the offset if over 32767, encodeNumber is going to return
|
|
||||||
// 5 bytes to encode the position instead of 3.
|
|
||||||
if ((offset + fields.charstrings.length) > 32767) {
|
|
||||||
offset += 9;
|
|
||||||
} else {
|
|
||||||
offset += 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
dict += self.encodeNumber(offset) + '\x0f'; // Charset
|
|
||||||
|
|
||||||
offset = offset + (glyphs.length * 2) + 1;
|
|
||||||
dict += self.encodeNumber(offset) + '\x11'; // Charstrings
|
|
||||||
|
|
||||||
offset = offset + fields.charstrings.length;
|
|
||||||
dict += self.encodeNumber(fields.privateData.length);
|
|
||||||
dict += self.encodeNumber(offset) + '\x12'; // Private
|
|
||||||
|
|
||||||
return header + String.fromCharCode(dict.length + 1) + dict;
|
|
||||||
};
|
|
||||||
})(this),
|
|
||||||
|
|
||||||
'strings': (function strings(self) {
|
|
||||||
var strings = [
|
|
||||||
'Version 0.11', // Version
|
|
||||||
'See original notice', // Notice
|
|
||||||
name, // FullName
|
|
||||||
name, // FamilyName
|
|
||||||
'Medium' // Weight
|
|
||||||
];
|
|
||||||
return self.createCFFIndexHeader(strings);
|
|
||||||
})(this),
|
|
||||||
|
|
||||||
'globalSubrs': this.createCFFIndexHeader([]),
|
|
||||||
|
|
||||||
'charset': (function charset(self) {
|
|
||||||
var charsetString = '\x00'; // Encoding
|
|
||||||
|
|
||||||
var count = glyphs.length;
|
|
||||||
for (var i = 0; i < count; i++) {
|
|
||||||
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
|
|
||||||
// problems
|
|
||||||
if (index == -1)
|
|
||||||
index = 0;
|
|
||||||
|
|
||||||
charsetString += String.fromCharCode(index >> 8, index & 0xff);
|
|
||||||
}
|
|
||||||
return charsetString;
|
|
||||||
})(this),
|
|
||||||
|
|
||||||
'charstrings': this.createCFFIndexHeader([[0x8B, 0x0E]].concat(glyphs),
|
|
||||||
true),
|
|
||||||
|
|
||||||
'privateData': (function cffWrapPrivate(self) {
|
|
||||||
var data =
|
|
||||||
'\x8b\x14' + // defaultWidth
|
|
||||||
'\x8b\x15'; // nominalWidth
|
|
||||||
var fieldMap = {
|
|
||||||
BlueValues: '\x06',
|
|
||||||
OtherBlues: '\x07',
|
|
||||||
FamilyBlues: '\x08',
|
|
||||||
FamilyOtherBlues: '\x09',
|
|
||||||
StemSnapH: '\x0c\x0c',
|
|
||||||
StemSnapV: '\x0c\x0d',
|
|
||||||
BlueShift: '\x0c\x0a',
|
|
||||||
BlueFuzz: '\x0c\x0b',
|
|
||||||
BlueScale: '\x0c\x09',
|
|
||||||
LanguageGroup: '\x0c\x11',
|
|
||||||
ExpansionFactor: '\x0c\x12'
|
|
||||||
};
|
|
||||||
for (var field in fieldMap) {
|
|
||||||
if (!properties.privateData.hasOwnProperty(field))
|
|
||||||
continue;
|
|
||||||
var value = properties.privateData[field];
|
|
||||||
|
|
||||||
if (isArray(value)) {
|
|
||||||
for (var i = 0, ii = value.length; i < ii; i++)
|
|
||||||
data += encodeNumber(value[i]);
|
|
||||||
} else {
|
|
||||||
data += encodeNumber(value);
|
|
||||||
}
|
|
||||||
data += fieldMap[field];
|
|
||||||
}
|
|
||||||
|
|
||||||
data += self.encodeNumber(data.length + 4) + '\x13'; // Subrs offset
|
|
||||||
|
|
||||||
return data;
|
|
||||||
})(this),
|
|
||||||
|
|
||||||
'localSubrs': this.createCFFIndexHeader(subrs, true)
|
|
||||||
};
|
|
||||||
fields.topDict = fields.topDict();
|
|
||||||
|
|
||||||
|
|
||||||
var cff = [];
|
|
||||||
for (var index in fields) {
|
|
||||||
var field = fields[index];
|
|
||||||
for (var i = 0, ii = field.length; i < ii; i++)
|
|
||||||
cff.push(field.charCodeAt(i));
|
|
||||||
}
|
}
|
||||||
|
cff.charStrings = charStringsIndex;
|
||||||
|
|
||||||
return cff;
|
var privateDict = new CFFPrivateDict();
|
||||||
|
privateDict.setByName('Subrs', null); // placeholder
|
||||||
|
var fields = [
|
||||||
|
// TODO: missing StdHW, StdVW, ForceBold
|
||||||
|
'BlueValues',
|
||||||
|
'OtherBlues',
|
||||||
|
'FamilyBlues',
|
||||||
|
'FamilyOtherBlues',
|
||||||
|
'StemSnapH',
|
||||||
|
'StemSnapV',
|
||||||
|
'BlueShift',
|
||||||
|
'BlueFuzz',
|
||||||
|
'BlueScale',
|
||||||
|
'LanguageGroup',
|
||||||
|
'ExpansionFactor'
|
||||||
|
];
|
||||||
|
for (var i = 0, ii = fields.length; i < ii; i++) {
|
||||||
|
var field = fields[i];
|
||||||
|
if (!properties.privateData.hasOwnProperty(field))
|
||||||
|
continue;
|
||||||
|
privateDict.setByName(field, properties.privateData[field]);
|
||||||
|
}
|
||||||
|
cff.topDict.privateDict = privateDict;
|
||||||
|
|
||||||
|
var subrIndex = new CFFIndex();
|
||||||
|
for (var i = 0, ii = subrs.length; i < ii; i++) {
|
||||||
|
subrIndex.add(subrs[i]);
|
||||||
|
}
|
||||||
|
privateDict.subrsIndex = subrIndex;
|
||||||
|
|
||||||
|
var compiler = new CFFCompiler(cff);
|
||||||
|
return compiler.compile();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -6187,6 +6070,12 @@ var CFFDict = (function CFFDictClosure() {
|
|||||||
this.values[key] = value;
|
this.values[key] = value;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
setByName: function CFFDict_setByName(name, value) {
|
||||||
|
if (!(name in this.nameToKeyMap)) {
|
||||||
|
error('Invalid dictionary name "' + name + '"');
|
||||||
|
}
|
||||||
|
this.values[this.nameToKeyMap[name]] = value;
|
||||||
|
},
|
||||||
hasName: function CFFDict_hasName(name) {
|
hasName: function CFFDict_hasName(name) {
|
||||||
return this.nameToKeyMap[name] in this.values;
|
return this.nameToKeyMap[name] in this.values;
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user