Merge pull request #164 from vingtetun/master

Let Chrome (and others?) handle Type1 Fonts (TrueType not working yet)
This commit is contained in:
Andreas Gal 2011-07-03 14:25:51 -07:00
commit 637708cf1a

234
fonts.js
View File

@ -466,6 +466,29 @@ var Font = (function () {
return array; return array;
}; };
function int16(bytes) {
return (bytes[0] << 8) + (bytes[1] & 0xff);
};
function int32(bytes) {
return (bytes[0] << 24) + (bytes[1] << 16) +
(bytes[2] << 8) + (bytes[3] & 0xff);
};
function getMaxPower2(number) {
var maxPower = 0;
var value = number;
while (value >= 2) {
value /= 2;
maxPower++;
}
value = 2;
for (var i = 1; i < maxPower; i++)
value *= 2;
return value;
};
function string16(value) { function string16(value) {
return String.fromCharCode((value >> 8) & 0xff) + return String.fromCharCode((value >> 8) & 0xff) +
String.fromCharCode(value & 0xff); String.fromCharCode(value & 0xff);
@ -486,7 +509,7 @@ var Font = (function () {
header += string16(numTables); header += string16(numTables);
// searchRange (2 bytes) // searchRange (2 bytes)
var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables); var tablesMaxPower2 = getMaxPower2(numTables);
var searchRange = tablesMaxPower2 * 16; var searchRange = tablesMaxPower2 * 16;
header += string16(searchRange); header += string16(searchRange);
@ -518,7 +541,7 @@ var Font = (function () {
// checksum // checksum
var checksum = 0; var checksum = 0;
for (var i = 0; i < length; i+=4) for (var i = 0; i < length; i+=4)
checksum += FontsUtils.bytesToInteger([data[i], data[i+1], data[i+2], data[i+3]]); checksum += int16([data[i], data[i+1], data[i+2], data[i+3]]);
var tableEntry = tag + string32(checksum) + string32(offset) + string32(length); var tableEntry = tag + string32(checksum) + string32(offset) + string32(length);
tableEntry = stringToArray(tableEntry); tableEntry = stringToArray(tableEntry);
@ -565,7 +588,7 @@ var Font = (function () {
var headerSize = (12 * 2 + (ranges.length * 5 * 2)); var headerSize = (12 * 2 + (ranges.length * 5 * 2));
var segCount = ranges.length + 1; var segCount = ranges.length + 1;
var segCount2 = segCount * 2; var segCount2 = segCount * 2;
var searchRange = FontsUtils.getMaxPower2(segCount) * 2; var searchRange = getMaxPower2(segCount) * 2;
var searchEntry = Math.log(segCount) / Math.log(2); var searchEntry = Math.log(segCount) / Math.log(2);
var rangeShift = 2 * segCount - searchRange; var rangeShift = 2 * segCount - searchRange;
@ -712,9 +735,9 @@ var Font = (function () {
String.fromCharCode(tag[2]) + String.fromCharCode(tag[2]) +
String.fromCharCode(tag[3]); String.fromCharCode(tag[3]);
var checksum = FontsUtils.bytesToInteger(file.getBytes(4)); var checksum = int32(file.getBytes(4));
var offset = FontsUtils.bytesToInteger(file.getBytes(4)); var offset = int32(file.getBytes(4));
var length = FontsUtils.bytesToInteger(file.getBytes(4)); var length = int32(file.getBytes(4));
// Read the table associated data // Read the table associated data
var previousPosition = file.pos; var previousPosition = file.pos;
@ -735,26 +758,26 @@ var Font = (function () {
function readOpenTypeHeader(ttf) { function readOpenTypeHeader(ttf) {
return { return {
version: ttf.getBytes(4), version: ttf.getBytes(4),
numTables: FontsUtils.bytesToInteger(ttf.getBytes(2)), numTables: int16(ttf.getBytes(2)),
searchRange: FontsUtils.bytesToInteger(ttf.getBytes(2)), searchRange: int16(ttf.getBytes(2)),
entrySelector: FontsUtils.bytesToInteger(ttf.getBytes(2)), entrySelector: int16(ttf.getBytes(2)),
rangeShift: FontsUtils.bytesToInteger(ttf.getBytes(2)) rangeShift: int16(ttf.getBytes(2))
} }
}; };
function replaceCMapTable(cmap, font, properties) { function replaceCMapTable(cmap, font, properties) {
font.pos = (font.start ? font.start : 0) + cmap.length; font.pos = (font.start ? font.start : 0) + cmap.length;
var version = FontsUtils.bytesToInteger(font.getBytes(2)); var version = int16(font.getBytes(2));
var numTables = FontsUtils.bytesToInteger(font.getBytes(2)); var numTables = int16(font.getBytes(2));
for (var i = 0; i < numTables; i++) { for (var i = 0; i < numTables; i++) {
var platformID = FontsUtils.bytesToInteger(font.getBytes(2)); var platformID = int16(font.getBytes(2));
var encodingID = FontsUtils.bytesToInteger(font.getBytes(2)); var encodingID = int16(font.getBytes(2));
var offset = FontsUtils.bytesToInteger(font.getBytes(4)); var offset = int32(font.getBytes(4));
var format = FontsUtils.bytesToInteger(font.getBytes(2)); var format = int16(font.getBytes(2));
var length = FontsUtils.bytesToInteger(font.getBytes(2)); var length = int16(font.getBytes(2));
var language = FontsUtils.bytesToInteger(font.getBytes(2)); var language = int16(font.getBytes(2));
if ((format == 0 && numTables == 1) || if ((format == 0 && numTables == 1) ||
(format == 6 && numTables == 1 && !properties.encoding.empty)) { (format == 6 && numTables == 1 && !properties.encoding.empty)) {
@ -776,13 +799,13 @@ var Font = (function () {
// table. (This looks weird, so I can have missed something), this // table. (This looks weird, so I can have missed something), this
// works on Linux but seems to fails on Mac so let's rewrite the // works on Linux but seems to fails on Mac so let's rewrite the
// cmap table to a 3-1-4 style // cmap table to a 3-1-4 style
var firstCode = FontsUtils.bytesToInteger(font.getBytes(2)); var firstCode = int16(font.getBytes(2));
var entryCount = FontsUtils.bytesToInteger(font.getBytes(2)); var entryCount = int16(font.getBytes(2));
var glyphs = []; var glyphs = [];
var min = 0xffff, max = 0; var min = 0xffff, max = 0;
for (var j = 0; j < entryCount; j++) { for (var j = 0; j < entryCount; j++) {
var charcode = FontsUtils.bytesToInteger(font.getBytes(2)); var charcode = int16(font.getBytes(2));
glyphs.push(charcode); glyphs.push(charcode);
if (charcode < min) if (charcode < min)
@ -882,7 +905,7 @@ var Font = (function () {
// Tables needs to be written by ascendant alphabetic order // Tables needs to be written by ascendant alphabetic order
tables.sort(function tables_sort(a, b) { tables.sort(function tables_sort(a, b) {
return a.tag > b.tag; return (a.tag > b.tag) - (a.tag < b.tag);
}); });
// rewrite the tables but tweak offsets // rewrite the tables but tweak offsets
@ -921,8 +944,6 @@ var Font = (function () {
}, },
convert: function font_convert(fontName, font, properties) { convert: function font_convert(fontName, font, properties) {
var otf = new Uint8Array(kMaxFontFileSize);
function createNameTable(name) { function createNameTable(name) {
// All the strings of the name table should be an odd number of bytes // All the strings of the name table should be an odd number of bytes
if (name.length % 2) if (name.length % 2)
@ -994,46 +1015,34 @@ var Font = (function () {
return true; return true;
}; };
// Required Tables
var CFF =
font.data, // PostScript Font Program
OS2, // OS/2 and Windows Specific metrics
cmap, // Character to glyphs mapping
head, // Font header
hhea, // Horizontal header
hmtx, // Horizontal metrics
maxp, // Maximum profile
name, // Naming tables
post; // PostScript informations
var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post];
// The offsets object holds at the same time a representation of where // The offsets object holds at the same time a representation of where
// to write the table entry information about a table and another offset // to write the table entry information about a table and another offset
// representing the offset where to draw the actual data of a particular // representing the offset where to draw the actual data of a particular
// table // table
var kRequiredTablesCount = 9;
var offsets = { var offsets = {
currentOffset: 0, currentOffset: 0,
virtualOffset: tables.length * (4 * 4) virtualOffset: 9 * (4 * 4)
}; };
// It there is only one font, offset table is the first bytes of the file var otf = new Uint8Array(kMaxFontFileSize);
createOpenTypeHeader("\x4F\x54\x54\x4F", otf, offsets, tables.length); createOpenTypeHeader("\x4F\x54\x54\x4F", otf, offsets, 9);
/** CFF */
createTableEntry(otf, offsets, "CFF ", CFF);
/** OS/2 */
var charstrings = font.charstrings; var charstrings = font.charstrings;
properties.fixedPitch = isFixedPitch(charstrings); properties.fixedPitch = isFixedPitch(charstrings);
OS2 = stringToArray(createOS2Table(properties)); var fields = {
createTableEntry(otf, offsets, "OS/2", OS2); // PostScript Font Program
"CFF ": font.data,
/** CMAP */ // OS/2 and Windows Specific metrics
cmap = createCMapTable(charstrings.slice()); "OS/2": stringToArray(createOS2Table(properties)),
createTableEntry(otf, offsets, "cmap", cmap);
/** HEAD */ // Character to glyphs mapping
head = stringToArray( "cmap": createCMapTable(charstrings.slice()),
// Font header
"head": (function() {
return stringToArray(
"\x00\x01\x00\x00" + // Version number "\x00\x01\x00\x00" + // Version number
"\x00\x00\x10\x00" + // fontRevision "\x00\x00\x10\x00" + // fontRevision
"\x00\x00\x00\x00" + // checksumAdjustement "\x00\x00\x00\x00" + // checksumAdjustement
@ -1050,12 +1059,12 @@ var Font = (function () {
"\x00\x11" + // lowestRecPPEM "\x00\x11" + // lowestRecPPEM
"\x00\x00" + // fontDirectionHint "\x00\x00" + // fontDirectionHint
"\x00\x00" + // indexToLocFormat "\x00\x00" + // indexToLocFormat
"\x00\x00" // glyphDataFormat "\x00\x00"); // glyphDataFormat
); })(),
createTableEntry(otf, offsets, "head", head);
/** HHEA */ // Horizontal header
hhea = stringToArray( "hhea": (function() {
return stringToArray(
"\x00\x01\x00\x00" + // Version number "\x00\x01\x00\x00" + // Version number
string16(properties.ascent) + // Typographic Ascent string16(properties.ascent) + // Typographic Ascent
string16(properties.descent) + // Typographic Descent string16(properties.descent) + // Typographic Descent
@ -1072,41 +1081,37 @@ var Font = (function () {
"\x00\x00" + // -reserved- "\x00\x00" + // -reserved-
"\x00\x00" + // -reserved- "\x00\x00" + // -reserved-
"\x00\x00" + // metricDataFormat "\x00\x00" + // metricDataFormat
string16(charstrings.length + 1) // Number of HMetrics string16(charstrings.length + 1)); // Number of HMetrics
); })(),
createTableEntry(otf, offsets, "hhea", hhea);
/** HMTX */ // Horizontal metrics
/* For some reasons, probably related to how the backend handle fonts, "hmtx": (function() {
* Linux seems to ignore this file and prefer the data from the CFF itself var hmtx = "\x00\x00\x00\x00"; // Fake .notdef
* while Windows use this data. So be careful if you hack on Linux and
* have to touch the 'hmtx' table
*/
hmtx = "\x00\x00\x00\x00"; // Fake .notdef
for (var i = 0; i < charstrings.length; i++) { for (var i = 0; i < charstrings.length; i++) {
hmtx += string16(charstrings[i].width) + string16(0); hmtx += string16(charstrings[i].width) + string16(0);
} }
hmtx = stringToArray(hmtx); return stringToArray(hmtx);
createTableEntry(otf, offsets, "hmtx", hmtx); })(),
/** MAXP */ // Maximum profile
maxp = "\x00\x00\x50\x00" + // Version number "maxp": (function() {
string16(charstrings.length + 1); // Num of glyphs return stringToArray(
maxp = stringToArray(maxp); "\x00\x00\x50\x00" + // Version number
createTableEntry(otf, offsets, "maxp", maxp); string16(charstrings.length + 1)); // Num of glyphs
})(),
/** NAME */ // Naming tables
name = stringToArray(createNameTable(fontName)); "name": stringToArray(createNameTable(fontName)),
createTableEntry(otf, offsets, "name", name);
/** POST */ // PostScript informations
post = stringToArray(createPostTable(properties)); "post": stringToArray(createPostTable(properties))
createTableEntry(otf, offsets, "post", post); };
// Once all the table entries header are written, dump the data! for (var field in fields)
var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post]; createTableEntry(otf, offsets, field, fields[field]);
for (var i = 0; i < tables.length; i++) {
var table = tables[i]; for (var field in fields) {
var table = fields[field];
otf.set(table, offsets.currentOffset); otf.set(table, offsets.currentOffset);
offsets.currentOffset += table.length; offsets.currentOffset += table.length;
} }
@ -1144,54 +1149,6 @@ var Font = (function () {
return constructor; return constructor;
})(); })();
/**
* FontsUtils is a static class dedicated to hold codes that are not related
* to fonts in particular and needs to be share between them.
*/
var FontsUtils = {
_bytesArray: new Uint8Array(4),
integerToBytes: function fu_integerToBytes(value, bytesCount) {
var bytes = this._bytesArray;
if (bytesCount == 1) {
bytes.set([value]);
return bytes[0];
} else if (bytesCount == 2) {
bytes.set([value >> 8, value & 0xff]);
return [bytes[0], bytes[1]];
} else if (bytesCount == 4) {
bytes.set([value >> 24, value >> 16, value >> 8, value]);
return [bytes[0], bytes[1], bytes[2], bytes[3]];
}
error("This number of bytes " + bytesCount + " is not supported");
return null;
},
bytesToInteger: function fu_bytesToInteger(bytesArray) {
var value = 0;
for (var i = 0; i < bytesArray.length; i++)
value = (value << 8) + bytesArray[i];
return value;
},
getMaxPower2: function fu_getMaxPower2(number) {
var maxPower = 0;
var value = number;
while (value >= 2) {
value /= 2;
maxPower++;
}
value = 2;
for (var i = 1; i < maxPower; i++)
value *= 2;
return value;
}
};
/** /**
* Type1Parser encapsulate the needed code for parsing a Type1 font * Type1Parser encapsulate the needed code for parsing a Type1 font
* program. * program.
@ -1638,10 +1595,7 @@ CFF.prototype = {
if (count == 0) if (count == 0)
return "\x00\x00\x00"; return "\x00\x00\x00";
var data = ""; var data = String.fromCharCode(count >> 8, count & 0xff);
var bytes = FontsUtils.integerToBytes(count, 2);
for (var i = 0; i < bytes.length; i++)
data += String.fromCharCode(bytes[i]);
// Next byte contains the offset size use to reference object in the file // 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 // Actually we're using 0x04 to be sure to be able to store everything
@ -1651,9 +1605,8 @@ CFF.prototype = {
// Add another offset after this one because we need a new offset // Add another offset after this one because we need a new offset
var relativeOffset = 1; var relativeOffset = 1;
for (var i = 0; i < count + 1; i++) { for (var i = 0; i < count + 1; i++) {
var bytes = FontsUtils.integerToBytes(relativeOffset, 4); data += String.fromCharCode(relativeOffset >> 24, relativeOffset >> 16,
for (var j = 0; j < bytes.length; j++) relativeOffset >> 8, relativeOffset & 0xff);
data += String.fromCharCode(bytes[j]);
if (objects[i]) if (objects[i])
relativeOffset += objects[i].length; relativeOffset += objects[i].length;
@ -1703,7 +1656,7 @@ CFF.prototype = {
}; };
charstrings.sort(function charstrings_sort(a, b) { charstrings.sort(function charstrings_sort(a, b) {
return a.unicode > b.unicode; return a.unicode - b.unicode;
}); });
return charstrings; return charstrings;
}, },
@ -1848,8 +1801,7 @@ CFF.prototype = {
if (index == -1) if (index == -1)
index = 0; index = 0;
var bytes = FontsUtils.integerToBytes(index, 2); charset += String.fromCharCode(index >> 8, index & 0xff);
charset += String.fromCharCode(bytes[0]) + String.fromCharCode(bytes[1]);
} }
return charset; return charset;
})(this), })(this),