|
|
|
@ -2483,6 +2483,7 @@ var Font = (function FontClosure() {
@@ -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() {
@@ -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() {
@@ -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() {
@@ -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); |
|
|
|
|
if (VALID_TABLES.indexOf(table.tag) < 0) { |
|
|
|
|
continue; // skipping table if it's not a required or optional table
|
|
|
|
|
} |
|
|
|
|
tables[table.tag] = table; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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 (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 (!tables.glyf || !tables.loca) { |
|
|
|
|
error('Required "glyf" or "loca" tables are not found'); |
|
|
|
|
} |
|
|
|
|
tables.push(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; |
|
|
|
|
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() {
@@ -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() {
@@ -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() {
@@ -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() {
@@ -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)) |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 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 +4250,8 @@ var Font = (function FontClosure() {
@@ -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); |
|
|
|
|
|
|
|
|
|