Refactoring TTF repair logic
This commit is contained in:
parent
cce3e9cd1b
commit
df352b375b
210
src/fonts.js
210
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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user