diff --git a/fonts.js b/fonts.js index c0b83a9c9..12d7ce82e 100644 --- a/fonts.js +++ b/fonts.js @@ -466,6 +466,29 @@ var Font = (function () { 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) { return String.fromCharCode((value >> 8) & 0xff) + String.fromCharCode(value & 0xff); @@ -486,7 +509,7 @@ var Font = (function () { header += string16(numTables); // searchRange (2 bytes) - var tablesMaxPower2 = FontsUtils.getMaxPower2(numTables); + var tablesMaxPower2 = getMaxPower2(numTables); var searchRange = tablesMaxPower2 * 16; header += string16(searchRange); @@ -507,7 +530,7 @@ var Font = (function () { // length var length = data.length; - + // Per spec tables must be 4-bytes align so add padding as needed while (data.length & 3) data.push(0x00); @@ -518,7 +541,7 @@ var Font = (function () { // checksum var checksum = 0; 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); tableEntry = stringToArray(tableEntry); @@ -565,7 +588,7 @@ var Font = (function () { var headerSize = (12 * 2 + (ranges.length * 5 * 2)); var segCount = ranges.length + 1; var segCount2 = segCount * 2; - var searchRange = FontsUtils.getMaxPower2(segCount) * 2; + var searchRange = getMaxPower2(segCount) * 2; var searchEntry = Math.log(segCount) / Math.log(2); var rangeShift = 2 * segCount - searchRange; @@ -712,9 +735,9 @@ var Font = (function () { String.fromCharCode(tag[2]) + String.fromCharCode(tag[3]); - var checksum = FontsUtils.bytesToInteger(file.getBytes(4)); - var offset = FontsUtils.bytesToInteger(file.getBytes(4)); - var length = FontsUtils.bytesToInteger(file.getBytes(4)); + var checksum = int32(file.getBytes(4)); + var offset = int32(file.getBytes(4)); + var length = int32(file.getBytes(4)); // Read the table associated data var previousPosition = file.pos; @@ -735,26 +758,26 @@ var Font = (function () { function readOpenTypeHeader(ttf) { return { version: ttf.getBytes(4), - numTables: FontsUtils.bytesToInteger(ttf.getBytes(2)), - searchRange: FontsUtils.bytesToInteger(ttf.getBytes(2)), - entrySelector: FontsUtils.bytesToInteger(ttf.getBytes(2)), - rangeShift: FontsUtils.bytesToInteger(ttf.getBytes(2)) + numTables: int16(ttf.getBytes(2)), + searchRange: int16(ttf.getBytes(2)), + entrySelector: int16(ttf.getBytes(2)), + rangeShift: int16(ttf.getBytes(2)) } }; function replaceCMapTable(cmap, font, properties) { font.pos = (font.start ? font.start : 0) + cmap.length; - var version = FontsUtils.bytesToInteger(font.getBytes(2)); - var numTables = FontsUtils.bytesToInteger(font.getBytes(2)); + var version = int16(font.getBytes(2)); + var numTables = int16(font.getBytes(2)); for (var i = 0; i < numTables; i++) { - var platformID = FontsUtils.bytesToInteger(font.getBytes(2)); - var encodingID = FontsUtils.bytesToInteger(font.getBytes(2)); - var offset = FontsUtils.bytesToInteger(font.getBytes(4)); - var format = FontsUtils.bytesToInteger(font.getBytes(2)); - var length = FontsUtils.bytesToInteger(font.getBytes(2)); - var language = FontsUtils.bytesToInteger(font.getBytes(2)); + var platformID = int16(font.getBytes(2)); + var encodingID = int16(font.getBytes(2)); + var offset = int32(font.getBytes(4)); + var format = int16(font.getBytes(2)); + var length = int16(font.getBytes(2)); + var language = int16(font.getBytes(2)); if ((format == 0 && numTables == 1) || (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 // works on Linux but seems to fails on Mac so let's rewrite the // cmap table to a 3-1-4 style - var firstCode = FontsUtils.bytesToInteger(font.getBytes(2)); - var entryCount = FontsUtils.bytesToInteger(font.getBytes(2)); + var firstCode = int16(font.getBytes(2)); + var entryCount = int16(font.getBytes(2)); var glyphs = []; var min = 0xffff, max = 0; for (var j = 0; j < entryCount; j++) { - var charcode = FontsUtils.bytesToInteger(font.getBytes(2)); + var charcode = int16(font.getBytes(2)); glyphs.push(charcode); if (charcode < min) @@ -882,7 +905,7 @@ var Font = (function () { // Tables needs to be written by ascendant alphabetic order 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 @@ -921,8 +944,6 @@ var Font = (function () { }, convert: function font_convert(fontName, font, properties) { - var otf = new Uint8Array(kMaxFontFileSize); - function createNameTable(name) { // All the strings of the name table should be an odd number of bytes if (name.length % 2) @@ -985,7 +1006,7 @@ var Font = (function () { nameTable += strings.join("") + stringsUnicode.join(""); return nameTable; } - + function isFixedPitch(glyphs) { for (var i = 0; i < glyphs.length - 1; i++) { if (glyphs[i] != glyphs[i+1]) @@ -994,46 +1015,34 @@ var Font = (function () { 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 // to write the table entry information about a table and another offset // representing the offset where to draw the actual data of a particular // table + var kRequiredTablesCount = 9; var offsets = { 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 - createOpenTypeHeader("\x4F\x54\x54\x4F", otf, offsets, tables.length); + var otf = new Uint8Array(kMaxFontFileSize); + createOpenTypeHeader("\x4F\x54\x54\x4F", otf, offsets, 9); - /** CFF */ - createTableEntry(otf, offsets, "CFF ", CFF); - - /** OS/2 */ var charstrings = font.charstrings; properties.fixedPitch = isFixedPitch(charstrings); - OS2 = stringToArray(createOS2Table(properties)); - createTableEntry(otf, offsets, "OS/2", OS2); + var fields = { + // PostScript Font Program + "CFF ": font.data, - /** CMAP */ - cmap = createCMapTable(charstrings.slice()); - createTableEntry(otf, offsets, "cmap", cmap); + // OS/2 and Windows Specific metrics + "OS/2": stringToArray(createOS2Table(properties)), - /** HEAD */ - head = stringToArray( + // Character to glyphs mapping + "cmap": createCMapTable(charstrings.slice()), + + // Font header + "head": (function() { + return stringToArray( "\x00\x01\x00\x00" + // Version number "\x00\x00\x10\x00" + // fontRevision "\x00\x00\x00\x00" + // checksumAdjustement @@ -1050,63 +1059,59 @@ var Font = (function () { "\x00\x11" + // lowestRecPPEM "\x00\x00" + // fontDirectionHint "\x00\x00" + // indexToLocFormat - "\x00\x00" // glyphDataFormat - ); - createTableEntry(otf, offsets, "head", head); + "\x00\x00"); // glyphDataFormat + })(), - /** HHEA */ - hhea = stringToArray( - "\x00\x01\x00\x00" + // Version number - string16(properties.ascent) + // Typographic Ascent - string16(properties.descent) + // Typographic Descent - "\x00\x00" + // Line Gap - "\xFF\xFF" + // advanceWidthMax - "\x00\x00" + // minLeftSidebearing - "\x00\x00" + // minRightSidebearing - "\x00\x00" + // xMaxExtent - string16(properties.capHeight) + // caretSlopeRise - string16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun - "\x00\x00" + // caretOffset - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // -reserved- - "\x00\x00" + // metricDataFormat - string16(charstrings.length + 1) // Number of HMetrics - ); - createTableEntry(otf, offsets, "hhea", hhea); + // Horizontal header + "hhea": (function() { + return stringToArray( + "\x00\x01\x00\x00" + // Version number + string16(properties.ascent) + // Typographic Ascent + string16(properties.descent) + // Typographic Descent + "\x00\x00" + // Line Gap + "\xFF\xFF" + // advanceWidthMax + "\x00\x00" + // minLeftSidebearing + "\x00\x00" + // minRightSidebearing + "\x00\x00" + // xMaxExtent + string16(properties.capHeight) + // caretSlopeRise + string16(Math.tan(properties.italicAngle) * properties.xHeight) + // caretSlopeRun + "\x00\x00" + // caretOffset + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // -reserved- + "\x00\x00" + // metricDataFormat + string16(charstrings.length + 1)); // Number of HMetrics + })(), - /** HMTX */ - /* For some reasons, probably related to how the backend handle fonts, - * Linux seems to ignore this file and prefer the data from the CFF itself - * 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++) { - hmtx += string16(charstrings[i].width) + string16(0); - } - hmtx = stringToArray(hmtx); - createTableEntry(otf, offsets, "hmtx", hmtx); + // Horizontal metrics + "hmtx": (function() { + var hmtx = "\x00\x00\x00\x00"; // Fake .notdef + for (var i = 0; i < charstrings.length; i++) { + hmtx += string16(charstrings[i].width) + string16(0); + } + return stringToArray(hmtx); + })(), - /** MAXP */ - maxp = "\x00\x00\x50\x00" + // Version number - string16(charstrings.length + 1); // Num of glyphs - maxp = stringToArray(maxp); - createTableEntry(otf, offsets, "maxp", maxp); + // Maximum profile + "maxp": (function() { + return stringToArray( + "\x00\x00\x50\x00" + // Version number + string16(charstrings.length + 1)); // Num of glyphs + })(), - /** NAME */ - name = stringToArray(createNameTable(fontName)); - createTableEntry(otf, offsets, "name", name); + // Naming tables + "name": stringToArray(createNameTable(fontName)), - /** POST */ - post = stringToArray(createPostTable(properties)); - createTableEntry(otf, offsets, "post", post); + // PostScript informations + "post": stringToArray(createPostTable(properties)) + }; - // Once all the table entries header are written, dump the data! - var tables = [CFF, OS2, cmap, head, hhea, hmtx, maxp, name, post]; - for (var i = 0; i < tables.length; i++) { - var table = tables[i]; + for (var field in fields) + createTableEntry(otf, offsets, field, fields[field]); + + for (var field in fields) { + var table = fields[field]; otf.set(table, offsets.currentOffset); offsets.currentOffset += table.length; } @@ -1144,54 +1149,6 @@ var Font = (function () { 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 * program. @@ -1638,10 +1595,7 @@ CFF.prototype = { if (count == 0) return "\x00\x00\x00"; - var data = ""; - var bytes = FontsUtils.integerToBytes(count, 2); - for (var i = 0; i < bytes.length; i++) - data += String.fromCharCode(bytes[i]); + var data = String.fromCharCode(count >> 8, 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 @@ -1651,9 +1605,8 @@ CFF.prototype = { // Add another offset after this one because we need a new offset var relativeOffset = 1; for (var i = 0; i < count + 1; i++) { - var bytes = FontsUtils.integerToBytes(relativeOffset, 4); - for (var j = 0; j < bytes.length; j++) - data += String.fromCharCode(bytes[j]); + data += String.fromCharCode(relativeOffset >> 24, relativeOffset >> 16, + relativeOffset >> 8, relativeOffset & 0xff); if (objects[i]) relativeOffset += objects[i].length; @@ -1703,7 +1656,7 @@ CFF.prototype = { }; charstrings.sort(function charstrings_sort(a, b) { - return a.unicode > b.unicode; + return a.unicode - b.unicode; }); return charstrings; }, @@ -1848,8 +1801,7 @@ CFF.prototype = { if (index == -1) index = 0; - var bytes = FontsUtils.integerToBytes(index, 2); - charset += String.fromCharCode(bytes[0]) + String.fromCharCode(bytes[1]); + charset += String.fromCharCode(index >> 8, index & 0xff); } return charset; })(this),