From df352b375bbb43e6deca1b167125f82e047a7bab Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Mon, 24 Jun 2013 15:33:50 -0500 Subject: [PATCH] Refactoring TTF repair logic --- src/fonts.js | 210 +++++++++++++++++++-------------------------------- 1 file changed, 77 insertions(+), 133 deletions(-) diff --git a/src/fonts.js b/src/fonts.js index 289a2dd9c..5b73b9ea7 100644 --- a/src/fonts.js +++ b/src/fonts.js @@ -3528,30 +3528,6 @@ var Font = (function FontClosure() { glyf.data = newGlyfData.subarray(0, writeOffset); } - function findEmptyGlyphs(locaTable, isGlyphLocationsLong, emptyGlyphIds) { - var itemSize, itemDecode; - if (isGlyphLocationsLong) { - itemSize = 4; - itemDecode = function fontItemDecodeLong(data, offset) { - return (data[offset] << 24) | (data[offset + 1] << 16) | - (data[offset + 2] << 8) | data[offset + 3]; - }; - } else { - itemSize = 2; - itemDecode = function fontItemDecode(data, offset) { - return (data[offset] << 9) | (data[offset + 1] << 1); - }; - } - var data = locaTable.data, length = data.length; - var lastOffset = itemDecode(data, 0); - for (var i = itemSize, j = 0; i < length; i += itemSize, j++) { - var offset = itemDecode(data, i); - if (offset == lastOffset) - emptyGlyphIds[j] = true; - lastOffset = offset; - } - } - function readPostScriptTable(post, properties, maxpNumGlyphs) { var start = (font.start ? font.start : 0) + post.offset; font.pos = start; @@ -3886,75 +3862,59 @@ var Font = (function FontClosure() { // The following steps modify the original font data, making copy font = new Stream(new Uint8Array(font.getBytes())); - // Check that required tables are present - var requiredTables = ['OS/2', 'cmap', 'head', 'hhea', - 'hmtx', 'maxp', 'name', 'post']; + var VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp', + 'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF ']; var header = readOpenTypeHeader(font); var numTables = header.numTables; - var cmap, post, maxp, hhea, hmtx, head, os2; - var glyf, fpgm, loca, prep, cvt; - var tables = []; + var tables = { 'OS/2': null, cmap: null, head: null, hhea: null, + hmtx: null, maxp: null, name: null, post: null}; for (var i = 0; i < numTables; i++) { var table = readTableEntry(font); - var index = requiredTables.indexOf(table.tag); - if (index != -1) { - if (table.tag == 'cmap') - cmap = table; - else if (table.tag == 'post') - post = table; - else if (table.tag == 'maxp') - maxp = table; - else if (table.tag == 'hhea') - hhea = table; - else if (table.tag == 'hmtx') - hmtx = table; - else if (table.tag == 'head') - head = table; - else if (table.tag == 'OS/2') - os2 = table; - - requiredTables.splice(index, 1); - } else { - if (table.tag == 'loca') - loca = table; - else if (table.tag == 'glyf') - glyf = table; - else if (table.tag == 'fpgm') - fpgm = table; - else if (table.tag == 'prep') - prep = table; - else if (table.tag == 'cvt ') - cvt = table; - else if (table.tag == 'CFF ') - return null; // XXX: OpenType font is found, stopping - else // skipping table if it's not a required or optional table - continue; + if (VALID_TABLES.indexOf(table.tag) < 0) { + continue; // skipping table if it's not a required or optional table } - tables.push(table); + tables[table.tag] = table; } - // Ensure the hmtx table contains the advance width and - // sidebearings information for numGlyphs in the maxp table - font.pos = (font.start || 0) + maxp.offset; + var isTrueType = !tables['CFF ']; + if (!isTrueType) { + // OpenType font + delete tables.glyf; + delete tables.loca; + delete tables.fpgm; + delete tables.prep; + delete tables['cvt ']; + + return; // TODO: implement OpenType support + } else { + if (!tables.glyf || !tables.loca) { + error('Required "glyf" or "loca" tables are not found'); + } + } + + font.pos = (font.start || 0) + tables.maxp.offset; var version = int32(font.getBytes(4)); var numGlyphs = int16(font.getBytes(2)); var maxFunctionDefs = 0; - if (version >= 0x00010000 && maxp.length >= 22) { + if (version >= 0x00010000 && tables.maxp.length >= 22) { font.pos += 14; var maxFunctionDefs = int16(font.getBytes(2)); } - var hintsValid = sanitizeTTPrograms(fpgm, prep, maxFunctionDefs); + var hintsValid = sanitizeTTPrograms(tables.fpgm, tables.prep, + maxFunctionDefs); if (!hintsValid) { - tables.splice(tables.indexOf(fpgm), 1); - fpgm = null; - tables.splice(tables.indexOf(prep), 1); - prep = null; + delete tables.fpgm; + delete tables.prep; } - var numTables = tables.length + requiredTables.length; + // Tables needs to be written by ascendant alphabetic order + var tablesNames = Object.keys(tables); + tablesNames.sort(); + + numTables = tablesNames.length; // header and new offsets. Table entry information is appended to the // end of file. The virtualOffset represents where to put the actual @@ -3968,35 +3928,36 @@ var Font = (function FontClosure() { // of missing tables createOpenTypeHeader(header.version, ttf, numTables); - sanitizeMetrics(font, hhea, hmtx, numGlyphs); + // Ensure the hmtx table contains the advance width and + // sidebearings information for numGlyphs in the maxp table + sanitizeMetrics(font, tables.hhea, tables.hmtx, numGlyphs); - if (head) { - sanitizeHead(head, numGlyphs, loca.length); + if (!tables.head) { + error('Required "head" table is not found'); } - var isGlyphLocationsLong = int16([head.data[50], head.data[51]]); - if (head && loca && glyf) { - sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong, - hintsValid); - } + sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0); - var emptyGlyphIds = []; - if (glyf) - findEmptyGlyphs(loca, isGlyphLocationsLong, emptyGlyphIds); + if (isTrueType) { + var isGlyphLocationsLong = int16([tables.head.data[50], + tables.head.data[51]]); + + sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs, + isGlyphLocationsLong, hintsValid); + } // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth // Sometimes it's 0. That needs to be fixed - if (hhea.data[10] === 0 && hhea.data[11] === 0) { - hhea.data[10] = 0xFF; - hhea.data[11] = 0xFF; + if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) { + tables.hhea.data[10] = 0xFF; + tables.hhea.data[11] = 0xFF; } // The 'post' table has glyphs names. - if (post) { - var valid = readPostScriptTable(post, properties, numGlyphs); + if (tables.post) { + var valid = readPostScriptTable(tables.post, properties, numGlyphs); if (!valid) { - tables.splice(tables.indexOf(post), 1); - post = null; + tables.post = null; } } @@ -4010,12 +3971,11 @@ var Font = (function FontClosure() { // U+00AD (soft hyphen) is not drawn. // So, offset all the glyphs by 0xFF to avoid these cases and use // the encoding to map incoming characters to the new glyph positions - if (!cmap) { - cmap = { + if (!tables.cmap) { + tables.cmap = { tag: 'cmap', data: null }; - tables.push(cmap); } var cidToGidMap = properties.cidToGidMap || []; @@ -4083,7 +4043,7 @@ var Font = (function FontClosure() { // but this.hasEncoding is currently true for any encodings on the // Encodings object (e.g. MacExpertEncoding). So should consider using // better check for this. - var cmapTable = readCmapTable(cmap, font, this.hasEncoding, + var cmapTable = readCmapTable(tables.cmap, font, this.hasEncoding, this.isSymbolicFont); // TODO(mack): If the (3, 0) cmap table used, then the font is @@ -4210,69 +4170,53 @@ var Font = (function FontClosure() { } // Converting glyphs and ids into font's cmap table - cmap.data = createCmapTable(glyphs, ids); + tables.cmap.data = createCmapTable(glyphs, ids); var unicodeIsEnabled = []; for (var i = 0, ii = glyphs.length; i < ii; i++) { unicodeIsEnabled[glyphs[i].unicode] = true; } this.unicodeIsEnabled = unicodeIsEnabled; - if (os2 && !validateOS2Table(os2)) { - tables.splice(tables.indexOf(os2), 1); - os2 = null; - } - - if (!os2) { + if (!tables['OS/2'] || !validateOS2Table(tables['OS/2'])) { // extract some more font properties from the OpenType head and // hhea tables; yMin and descent value are always negative var override = { - unitsPerEm: int16([head.data[18], head.data[19]]), - yMax: int16([head.data[42], head.data[43]]), - yMin: int16([head.data[38], head.data[39]]) - 0x10000, - ascent: int16([hhea.data[4], hhea.data[5]]), - descent: int16([hhea.data[6], hhea.data[7]]) - 0x10000 + unitsPerEm: int16([tables.head.data[18], tables.head.data[19]]), + yMax: int16([tables.head.data[42], tables.head.data[43]]), + yMin: int16([tables.head.data[38], tables.head.data[39]]) - 0x10000, + ascent: int16([tables.hhea.data[4], tables.hhea.data[5]]), + descent: int16([tables.hhea.data[6], tables.hhea.data[7]]) - 0x10000 }; - tables.push({ + tables['OS/2'] = { tag: 'OS/2', data: stringToArray(createOS2Table(properties, glyphs, override)) - }); + }; } // Rewrite the 'post' table if needed - if (!post) { - tables.push({ + if (!tables.post) { + tables.post = { tag: 'post', data: stringToArray(createPostTable(properties)) - }); + }; } // Re-creating 'name' table - if (requiredTables.indexOf('name') != -1) { - tables.push({ + if (!tables.name) { + tables.name = { tag: 'name', data: stringToArray(createNameTable(this.name)) - }); + }; } else { // ... using existing 'name' table as prototype - for (var i = 0, ii = tables.length; i < ii; i++) { - var table = tables[i]; - if (table.tag === 'name') { - var namePrototype = readNameTable(table); - table.data = stringToArray(createNameTable(name, namePrototype)); - break; - } - } + var namePrototype = readNameTable(tables.name); + tables.name.data = stringToArray(createNameTable(name, namePrototype)); } - // Tables needs to be written by ascendant alphabetic order - tables.sort(function tables_sort(a, b) { - return (a.tag > b.tag) - (a.tag < b.tag); - }); - // rewrite the tables but tweak offsets - for (var i = 0, ii = tables.length; i < ii; i++) { - var table = tables[i]; + for (var i = 0; i < numTables; i++) { + var table = tables[tablesNames[i]]; var data = []; var tableData = table.data; @@ -4282,8 +4226,8 @@ var Font = (function FontClosure() { } // Add the table datas - for (var i = 0, ii = tables.length; i < ii; i++) { - var table = tables[i]; + for (var i = 0; i < numTables; i++) { + var table = tables[tablesNames[i]]; var tableData = table.data; ttf.file += arrayToString(tableData);