fontToSfntTable function
dynamic
fontToSfntTable(
- dynamic font
Implementation
function fontToSfntTable(font) {
const xMins = [];
const yMins = [];
const xMaxs = [];
const yMaxs = [];
const advanceWidths = [];
const leftSideBearings = [];
const rightSideBearings = [];
let firstCharIndex;
let lastCharIndex = 0;
let ulUnicodeRange1 = 0;
let ulUnicodeRange2 = 0;
let ulUnicodeRange3 = 0;
let ulUnicodeRange4 = 0;
for (let i = 0; i < font.glyphs.length; i += 1) {
const glyph = font.glyphs.get(i);
const unicode = glyph.unicode | 0;
if (isNaN(glyph.advanceWidth)) {
throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.');
}
if (firstCharIndex > unicode || firstCharIndex === undefined) {
// ignore .notdef char
if (unicode > 0) {
firstCharIndex = unicode;
}
}
if (lastCharIndex < unicode) {
lastCharIndex = unicode;
}
const position = os2.getUnicodeRange(unicode);
if (position < 32) {
ulUnicodeRange1 |= 1 << position;
} else if (position < 64) {
ulUnicodeRange2 |= 1 << position - 32;
} else if (position < 96) {
ulUnicodeRange3 |= 1 << position - 64;
} else if (position < 123) {
ulUnicodeRange4 |= 1 << position - 96;
} else {
throw new Error('Unicode ranges bits > 123 are reserved for internal usage');
}
// Skip non-important characters.
if (glyph.name === '.notdef') continue;
const metrics = glyph.getMetrics();
xMins.push(metrics.xMin);
yMins.push(metrics.yMin);
xMaxs.push(metrics.xMax);
yMaxs.push(metrics.yMax);
leftSideBearings.push(metrics.leftSideBearing);
rightSideBearings.push(metrics.rightSideBearing);
advanceWidths.push(glyph.advanceWidth);
}
const globals = {
xMin: Math.min.apply(null, xMins),
yMin: Math.min.apply(null, yMins),
xMax: Math.max.apply(null, xMaxs),
yMax: Math.max.apply(null, yMaxs),
advanceWidthMax: Math.max.apply(null, advanceWidths),
advanceWidthAvg: average(advanceWidths),
minLeftSideBearing: Math.min.apply(null, leftSideBearings),
maxLeftSideBearing: Math.max.apply(null, leftSideBearings),
minRightSideBearing: Math.min.apply(null, rightSideBearings)
};
globals.ascender = font.ascender;
globals.descender = font.descender;
const headTable = head.make({
flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0)
unitsPerEm: font.unitsPerEm,
xMin: globals.xMin,
yMin: globals.yMin,
xMax: globals.xMax,
yMax: globals.yMax,
lowestRecPPEM: 3,
createdTimestamp: font.createdTimestamp
});
const hheaTable = hhea.make({
ascender: globals.ascender,
descender: globals.descender,
advanceWidthMax: globals.advanceWidthMax,
minLeftSideBearing: globals.minLeftSideBearing,
minRightSideBearing: globals.minRightSideBearing,
xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin),
numberOfHMetrics: font.glyphs.length
});
const maxpTable = maxp.make(font.glyphs.length);
const os2Table = os2.make(Object.assign({
xAvgCharWidth: Math.round(globals.advanceWidthAvg),
usFirstCharIndex: firstCharIndex,
usLastCharIndex: lastCharIndex,
ulUnicodeRange1: ulUnicodeRange1,
ulUnicodeRange2: ulUnicodeRange2,
ulUnicodeRange3: ulUnicodeRange3,
ulUnicodeRange4: ulUnicodeRange4,
// See http://typophile.com/node/13081 for more info on vertical metrics.
// We get metrics for typical characters (such as "x" for xHeight).
// We provide some fallback characters if characters are unavailable: their
// ordering was chosen experimentally.
sTypoAscender: globals.ascender,
sTypoDescender: globals.descender,
sTypoLineGap: 0,
usWinAscent: globals.yMax,
usWinDescent: Math.abs(globals.yMin),
ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now
sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax,
sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax,
usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available.
usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available.
}, font.tables.os2));
const hmtxTable = hmtx.make(font.glyphs);
const cmapTable = cmap.make(font.glyphs);
const englishFamilyName = font.getEnglishName('fontFamily');
const englishStyleName = font.getEnglishName('fontSubfamily');
const englishFullName = englishFamilyName + ' ' + englishStyleName;
let postScriptName = font.getEnglishName('postScriptName');
if (!postScriptName) {
postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName;
}
const names = {};
for (let n in font.names) {
names[n] = font.names[n];
}
if (!names.uniqueID) {
names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName};
}
if (!names.postScriptName) {
names.postScriptName = {en: postScriptName};
}
if (!names.preferredFamily) {
names.preferredFamily = font.names.fontFamily;
}
if (!names.preferredSubfamily) {
names.preferredSubfamily = font.names.fontSubfamily;
}
const languageTags = [];
const nameTable = _name.make(names, languageTags);
const ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined);
const postTable = post.make();
const cffTable = cff.make(font.glyphs, {
version: font.getEnglishName('version'),
fullName: englishFullName,
familyName: englishFamilyName,
weightName: englishStyleName,
postScriptName: postScriptName,
unitsPerEm: font.unitsPerEm,
fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax]
});
const metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined;
// The order does not matter because makeSfntTable() will sort them.
const tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable];
if (ltagTable) {
tables.push(ltagTable);
}
// Optional tables
if (font.tables.gsub) {
tables.push(gsub.make(font.tables.gsub));
}
if (metaTable) {
tables.push(metaTable);
}
const sfntTable = makeSfntTable(tables);
// Compute the font's checkSum and store it in head.checkSumAdjustment.
const bytes = sfntTable.encode();
const checkSum = computeCheckSum(bytes);
const tableFields = sfntTable.fields;
let checkSumAdjusted = false;
for (let i = 0; i < tableFields.length; i += 1) {
if (tableFields[i].name === 'head table') {
tableFields[i].value.checkSumAdjustment = 0xB1B0AFBA - checkSum;
checkSumAdjusted = true;
break;
}
}
if (!checkSumAdjusted) {
throw new Error('Could not find head table with checkSum to adjust.');
}
return sfntTable;
}