Refactoring TTF repair logic

This commit is contained in:
Yury Delendik 2013-06-24 15:33:50 -05:00
parent cce3e9cd1b
commit df352b375b

View File

@ -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);