Merge pull request #3386 from yurydelendik/ttf-refactor
Refactoring of TrueType code and implementation of OpenType font repairing
This commit is contained in:
commit
b0f78609b3
250
src/fonts.js
250
src/fonts.js
@ -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);
|
||||
|
||||
|
1
test/pdfs/issue2442.pdf.link
Normal file
1
test/pdfs/issue2442.pdf.link
Normal file
@ -0,0 +1 @@
|
||||
https://401k.fidelity.com/static/dcl/shared/documents/WPS_PublicSite/Documents/403b_DistributionForm_441802.pdf
|
1
test/pdfs/issue3062.pdf.link
Normal file
1
test/pdfs/issue3062.pdf.link
Normal file
@ -0,0 +1 @@
|
||||
http://www3.weforum.org/docs/TTCR/2013/TTCR_DataTables12_2013.pdf
|
@ -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",
|
||||
|
Loading…
x
Reference in New Issue
Block a user