UNPKG

fonteditor-core

Version:

fonts (ttf, woff, woff2, eot, svg, otf) parse, write, transform, glyph adjust.

244 lines (204 loc) 6.99 kB
/** * @file ttf写入器 * @author mengke01(kekee000@gmail.com) */ import Writer from './writer'; import Directory from './table/directory'; import supportTables from './table/support'; import checkSum from './util/checkSum'; import error from './error'; // 支持写的表, 注意表顺序 const SUPPORT_TABLES = [ 'OS/2', 'cmap', 'glyf', 'head', 'hhea', 'hmtx', 'loca', 'maxp', 'name', 'post' ]; export default class TTFWriter { constructor(options = {}) { this.options = { writeZeroContoursGlyfData: options.writeZeroContoursGlyfData || false, // 不写入空 glyf 数据 hinting: options.hinting || false, // 默认不保留hints信息 kerning: options.kerning || false, // 默认不保留 kernings space 信息 support: options.support // 自定义的导出表结构,可以自己修改某些表项目 }; } /** * 处理ttf结构,以便于写 * * @param {ttfObject} ttf ttf数据结构 */ resolveTTF(ttf) { // 头部信息 ttf.version = ttf.version || 0x1; ttf.numTables = ttf.writeOptions.tables.length; ttf.entrySelector = Math.floor(Math.log(ttf.numTables) / Math.LN2); ttf.searchRange = Math.pow(2, ttf.entrySelector) * 16; ttf.rangeShift = ttf.numTables * 16 - ttf.searchRange; // 重置校验码 ttf.head.checkSumAdjustment = 0; ttf.head.magickNumber = 0x5F0F3CF5; if (typeof ttf.head.created === 'string') { ttf.head.created = /^\d+$/.test(ttf.head.created) ? +ttf.head.created : Date.parse(ttf.head.created); } if (typeof ttf.head.modified === 'string') { ttf.head.modified = /^\d+$/.test(ttf.head.modified) ? +ttf.head.modified : Date.parse(ttf.head.modified); } // 重置日期 if (!ttf.head.created) { ttf.head.created = Date.now(); } if (!ttf.head.modified) { ttf.head.modified = ttf.head.created; } const checkUnicodeRepeat = {}; // 检查是否有重复代码点 // 将glyf的代码点按小到大排序 ttf.glyf.forEach((glyf, index) => { if (glyf.unicode) { glyf.unicode = glyf.unicode.sort(); glyf.unicode.forEach((u) => { if (checkUnicodeRepeat[u]) { error.raise({ number: 10200, data: index }, index); } else { checkUnicodeRepeat[u] = true; } }); } }); } /** * 写ttf文件 * * @param {ttfObject} ttf ttf数据结构 * @return {ArrayBuffer} 字节流 */ dump(ttf) { // 用来做写入缓存的对象,用完后删掉 ttf.support = Object.assign({}, this.options.support); // head + directory let ttfSize = 12 + ttf.numTables * 16; let ttfHeadOffset = 0; // 记录head的偏移 // 构造tables ttf.support.tables = []; ttf.writeOptions.tables.forEach((tableName) => { const offset = ttfSize; const TableClass = supportTables[tableName]; const tableSize = new TableClass().size(ttf); // 原始的表大小 let size = tableSize; // 对齐后的表大小 if (tableName === 'head') { ttfHeadOffset = offset; } // 4字节对齐 if (size % 4) { size += 4 - size % 4; } ttf.support.tables.push({ name: tableName, checkSum: 0, offset, length: tableSize, size }); ttfSize += size; }); const writer = new Writer(new ArrayBuffer(ttfSize)); // 写头部 writer.writeFixed(ttf.version); writer.writeUint16(ttf.numTables); writer.writeUint16(ttf.searchRange); writer.writeUint16(ttf.entrySelector); writer.writeUint16(ttf.rangeShift); // 写表偏移 new Directory().write(writer, ttf); // 写支持的表数据 ttf.support.tables.forEach((table) => { const tableStart = writer.offset; const TableClass = supportTables[table.name]; new TableClass().write(writer, ttf); if (table.length % 4) { // 对齐字节 writer.writeEmpty(4 - table.length % 4); } // 计算校验和 table.checkSum = checkSum(writer.getBuffer(), tableStart, table.size); }); // 重新写入每个表校验和 ttf.support.tables.forEach((table, index) => { const offset = 12 + index * 16 + 4; writer.writeUint32(table.checkSum, offset); }); // 写入总校验和 const ttfCheckSum = (0xB1B0AFBA - checkSum(writer.getBuffer()) + 0x100000000) % 0x100000000; writer.writeUint32(ttfCheckSum, ttfHeadOffset + 8); delete ttf.writeOptions; delete ttf.support; const buffer = writer.getBuffer(); writer.dispose(); return buffer; } /** * 对ttf的表进行评估,标记需要处理的表 * * @param {Object} ttf ttf对象 */ prepareDump(ttf) { if (!ttf.glyf || ttf.glyf.length === 0) { error.raise(10201); } if (!ttf['OS/2'] || !ttf.head || !ttf.name) { error.raise(10204); } const tables = SUPPORT_TABLES.slice(0); ttf.writeOptions = {}; // hinting tables direct copy if (this.options.hinting) { ['cvt', 'fpgm', 'prep', 'gasp', 'GPOS', 'kern', 'kerx'].forEach((table) => { if (ttf[table]) { tables.push(table); } }); } // copy kerning space table if (this.options.kerning) { ['GPOS', 'kern', 'kerx'].forEach((table) => { if (ttf[table]) { tables.push(table); } }); } ttf.writeOptions.writeZeroContoursGlyfData = !!this.options.writeZeroContoursGlyfData; ttf.writeOptions.hinting = !!this.options.hinting; ttf.writeOptions.kerning = !!this.options.kerning; ttf.writeOptions.tables = tables.sort(); } /** * 写一个ttf字体结构 * * @param {Object} ttf ttf数据结构 * @return {ArrayBuffer} 缓冲数组 */ write(ttf) { this.prepareDump(ttf); this.resolveTTF(ttf); const buffer = this.dump(ttf); return buffer; } /** * 注销 */ dispose() { delete this.options; } }