Merge pull request #3386 from yurydelendik/ttf-refactor

Refactoring of TrueType code and implementation of OpenType font repairing
This commit is contained in:
Brendan Dahl 2013-07-08 22:11:31 -07:00
commit b0f78609b3
4 changed files with 127 additions and 141 deletions

View File

@ -2483,6 +2483,7 @@ var Font = (function FontClosure() {
data = this.convert(name, cff, properties);
break;
case 'OpenType':
case 'TrueType':
case 'CIDFontType2':
this.mimetype = 'font/opentype';
@ -2490,14 +2491,10 @@ var Font = (function FontClosure() {
// Repair the TrueType file. It is can be damaged in the point of
// view of the sanitizer
data = this.checkAndRepair(name, file, properties);
if (!data) {
// TrueType data is not found, e.g. when the font is an OpenType font
warn('Font is not a TrueType font');
}
break;
default:
warn('Font ' + type + ' is not supported');
error('Font ' + type + ' is not supported');
break;
}
@ -3528,30 +3525,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 +3859,69 @@ 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
if (!tables.head || !tables.hhea || !tables.maxp || !tables.post) {
// no major tables: throwing everything at CFFFont
var cffFile = new Stream(tables['CFF '].data);
var cff = new CFFFont(cffFile, properties);
return this.convert(name, cff, properties);
}
delete tables.glyf;
delete tables.loca;
delete tables.fpgm;
delete tables.prep;
delete tables['cvt '];
} else {
if (!tables.glyf || !tables.loca) {
error('Required "glyf" or "loca" tables are not found');
}
}
if (!tables.maxp) {
error('Required "maxp" table is 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));
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 +3935,40 @@ 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);
if (isTrueType) {
var isGlyphLocationsLong = int16([tables.head.data[50],
tables.head.data[51]]);
sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs,
isGlyphLocationsLong, hintsValid);
}
var emptyGlyphIds = [];
if (glyf)
findEmptyGlyphs(loca, isGlyphLocationsLong, emptyGlyphIds);
if (!tables.hhea) {
error('Required "hhea" table is not found');
}
// 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 +3982,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 +4054,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 +4181,66 @@ 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({
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;
}
if (!isTrueType) {
try {
// Trying to repair CFF file
var cffFile = new Stream(tables['CFF '].data);
var parser = new CFFParser(cffFile, properties);
var cff = parser.parse();
var compiler = new CFFCompiler(cff);
tables['CFF '].data = compiler.compile();
} catch (e) {
warn('Failed to compile font ' + properties.loadedName);
}
}
// 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);
});
// Re-creating 'name' table
if (!tables.name) {
tables.name = {
tag: 'name',
data: stringToArray(createNameTable(this.name))
};
} else {
// ... using existing 'name' table as prototype
var namePrototype = readNameTable(tables.name);
tables.name.data = stringToArray(createNameTable(name, namePrototype));
}
// 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 +4250,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);

View File

@ -0,0 +1 @@
https://401k.fidelity.com/static/dcl/shared/documents/WPS_PublicSite/Documents/403b_DistributionForm_441802.pdf

View File

@ -0,0 +1 @@
http://www3.weforum.org/docs/TTCR/2013/TTCR_DataTables12_2013.pdf

View File

@ -988,6 +988,22 @@
"link": true,
"type": "eq"
},
{ "id": "issue3062",
"file": "pdfs/issue3062.pdf",
"md5": "206715f1258f0e117df4180d98dd4d68",
"rounds": 1,
"lastPage": 1,
"link": true,
"type": "eq"
},
{ "id": "issue2442",
"file": "pdfs/issue2442.pdf",
"md5": "4656e36c44f44c71a499f02ce6c781d3",
"rounds": 1,
"lastPage": 1,
"link": true,
"type": "eq"
},
{ "id": "issue2337",
"file": "pdfs/issue2337.pdf",
"md5": "ea10f4131202b9b8f2a6cb7770d3f185",