UNPKG

fonteditor-core

Version:

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

878 lines (750 loc) 23.4 kB
/** * @file ttf相关处理对象 * @author mengke01(kekee000@gmail.com) */ import {overwrite} from '../common/lang'; import string from './util/string'; import pathAdjust from '../graphics/pathAdjust'; import pathCeil from '../graphics/pathCeil'; import {computePath, computePathBox} from '../graphics/computeBoundingBox'; import compound2simpleglyf from './util/compound2simpleglyf'; import glyfAdjust from './util/glyfAdjust'; import optimizettf from './util/optimizettf'; import config from './data/default'; /** * 缩放到EM框 * * @param {Array} glyfList glyf列表 * @param {number} ascent 上升 * @param {number} descent 下降 * @param {number} adjustToEmPadding 顶部和底部留白 * @return {Array} glyfList */ function adjustToEmBox(glyfList, ascent, descent, adjustToEmPadding) { glyfList.forEach((g) => { if (g.contours && g.contours.length) { const rightSideBearing = g.advanceWidth - g.xMax; const bound = computePath(...g.contours); const scale = (ascent - descent - adjustToEmPadding) / bound.height; const center = (ascent + descent) / 2; const yOffset = center - (bound.y + bound.height / 2) * scale; g.contours.forEach((contour) => { if (scale !== 1) { pathAdjust(contour, scale, scale); } pathAdjust(contour, 1, 1, 0, yOffset); pathCeil(contour); }); const box = computePathBox(...g.contours); g.xMin = box.x; g.xMax = box.x + box.width; g.yMin = box.y; g.yMax = box.y + box.height; g.leftSideBearing = g.xMin; g.advanceWidth = g.xMax + rightSideBearing; } }); return glyfList; } /** * 调整字形位置 * * @param {Array} glyfList 字形列表 * @param {number=} leftSideBearing 左边距 * @param {number=} rightSideBearing 右边距 * @param {number=} verticalAlign 垂直对齐 * * @return {Array} 改变的列表 */ function adjustPos(glyfList, leftSideBearing, rightSideBearing, verticalAlign) { let changed = false; // 左边轴 if (null != leftSideBearing) { changed = true; glyfList.forEach((g) => { if (g.leftSideBearing !== leftSideBearing) { glyfAdjust(g, 1, 1, leftSideBearing - g.leftSideBearing); } }); } // 右边轴 if (null != rightSideBearing) { changed = true; glyfList.forEach((g) => { g.advanceWidth = g.xMax + rightSideBearing; }); } // 基线高度 if (null != verticalAlign) { changed = true; glyfList.forEach(g => { if (g.contours && g.contours.length) { const bound = computePath(...g.contours); const offset = verticalAlign - bound.y; glyfAdjust(g, 1, 1, 0, offset); } }); } return changed ? glyfList : []; } /** * 合并两个ttfObject,此处仅合并简单字形 * * @param {Object} ttf ttfObject * @param {Object} imported ttfObject * @param {Object} options 参数选项 * @param {boolean} options.scale 是否自动缩放,默认true * @param {boolean} options.adjustGlyf 是否调整字形以适应边界 * (与 options.scale 互斥) * * @return {Object} 合并后的ttfObject */ function merge(ttf, imported, options = {scale: true}) { const list = imported.glyf.filter((g) => // 简单轮廓 g.contours && g.contours.length // 非预定义字形 && g.name !== '.notdef' && g.name !== '.null' && g.name !== 'nonmarkingreturn' ); // 调整字形以适应边界 if (options.adjustGlyf) { const ascent = ttf.hhea.ascent; const descent = ttf.hhea.descent; const adjustToEmPadding = 16; adjustPos(list, 16, 16); adjustToEmBox(list, ascent, descent, adjustToEmPadding); list.forEach((g) => { ttf.glyf.push(g); }); } // 根据unitsPerEm 进行缩放 else if (options.scale) { let scale = 1; // 调整glyf对导入的轮廓进行缩放处理 if (imported.head.unitsPerEm && imported.head.unitsPerEm !== ttf.head.unitsPerEm) { scale = ttf.head.unitsPerEm / imported.head.unitsPerEm; } list.forEach((g) => { glyfAdjust(g, scale, scale); ttf.glyf.push(g); }); } return list; } export default class TTF { /** * ttf读取函数 * * @constructor * @param {Object} ttf ttf文件结构 */ constructor(ttf) { this.ttf = ttf; } /** * 获取所有的字符信息 * * @return {Object} 字符信息 */ codes() { return Object.keys(this.ttf.cmap); } /** * 根据编码获取字形索引 * * @param {string} c 字符或者字符编码 * * @return {?number} 返回glyf索引号 */ getGlyfIndexByCode(c) { const charCode = typeof c === 'number' ? c : c.codePointAt(0); const glyfIndex = this.ttf.cmap[charCode] || -1; return glyfIndex; } /** * 根据索引获取字形 * * @param {number} glyfIndex glyf的索引 * * @return {?Object} 返回glyf对象 */ getGlyfByIndex(glyfIndex) { const glyfList = this.ttf.glyf; const glyf = glyfList[glyfIndex]; return glyf; } /** * 根据编码获取字形 * * @param {string} c 字符或者字符编码 * * @return {?Object} 返回glyf对象 */ getGlyfByCode(c) { const glyfIndex = this.getGlyfIndexByCode(c); return this.getGlyfByIndex(glyfIndex); } /** * 设置ttf对象 * * @param {Object} ttf ttf对象 * @return {this} */ set(ttf) { this.ttf = ttf; return this; } /** * 获取ttf对象 * * @return {ttfObject} ttf ttf对象 */ get() { return this.ttf; } /** * 添加glyf * * @param {Object} glyf glyf对象 * * @return {number} 添加的glyf */ addGlyf(glyf) { return this.insertGlyf(glyf); } /** * 插入glyf * * @param {Object} glyf glyf对象 * @param {Object} insertIndex 插入的索引 * @return {number} 添加的glyf */ insertGlyf(glyf, insertIndex) { if (insertIndex >= 0 && insertIndex < this.ttf.glyf.length) { this.ttf.glyf.splice(insertIndex, 0, glyf); } else { this.ttf.glyf.push(glyf); } return [glyf]; } /** * 合并两个ttfObject,此处仅合并简单字形 * * @param {Object} imported ttfObject * @param {Object} options 参数选项 * @param {boolean} options.scale 是否自动缩放 * @param {boolean} options.adjustGlyf 是否调整字形以适应边界 * (和 options.scale 参数互斥) * * @return {Array} 添加的glyf */ mergeGlyf(imported, options) { const list = merge(this.ttf, imported, options); return list; } /** * 删除指定字形 * * @param {Array} indexList 索引列表 * @return {Array} 删除的glyf */ removeGlyf(indexList) { const glyf = this.ttf.glyf; const removed = []; for (let i = glyf.length - 1; i >= 0; i--) { if (indexList.indexOf(i) >= 0) { removed.push(glyf[i]); glyf.splice(i, 1); } } return removed; } /** * 设置unicode代码 * * @param {string} unicode unicode代码 $E021, $22 * @param {Array=} indexList 索引列表 * @param {boolean} isGenerateName 是否生成name * @return {Array} 改变的glyf */ setUnicode(unicode, indexList, isGenerateName) { const glyf = this.ttf.glyf; let list = []; if (indexList && indexList.length) { const first = indexList.indexOf(0); if (first >= 0) { indexList.splice(first, 1); } list = indexList.map((item) => glyf[item]); } else { list = glyf.slice(1); } // 需要选出 unicode >32 的glyf if (list.length > 1) { const less32 = function (u) { return u < 33; }; list = list.filter((g) => !g.unicode || !g.unicode.some(less32)); } if (list.length) { unicode = Number('0x' + unicode.slice(1)); list.forEach((g) => { // 空格有可能会放入 nonmarkingreturn 因此不做编码 if (unicode === 0xA0 || unicode === 0x3000) { unicode++; } g.unicode = [unicode]; if (isGenerateName) { g.name = string.getUnicodeName(unicode); } unicode++; }); } return list; } /** * 生成字形名称 * * @param {Array=} indexList 索引列表 * @return {Array} 改变的glyf */ genGlyfName(indexList) { const glyf = this.ttf.glyf; let list = []; if (indexList && indexList.length) { list = indexList.map((item) => glyf[item]); } else { list = glyf; } if (list.length) { const first = this.ttf.glyf[0]; list.forEach((g) => { if (g === first) { g.name = '.notdef'; } else if (g.unicode && g.unicode.length) { g.name = string.getUnicodeName(g.unicode[0]); } else { g.name = '.notdef'; } }); } return list; } /** * 清除字形名称 * * @param {Array=} indexList 索引列表 * @return {Array} 改变的glyf */ clearGlyfName(indexList) { const glyf = this.ttf.glyf; let list = []; if (indexList && indexList.length) { list = indexList.map((item) => glyf[item]); } else { list = glyf; } if (list.length) { list.forEach((g) => { delete g.name; }); } return list; } /** * 添加并体替换指定的glyf * * @param {Array} glyfList 添加的列表 * @param {Array=} indexList 需要替换的索引列表 * @return {Array} 改变的glyf */ appendGlyf(glyfList, indexList) { const glyf = this.ttf.glyf; const result = glyfList.slice(0); if (indexList && indexList.length) { const l = Math.min(glyfList.length, indexList.length); for (let i = 0; i < l; i++) { glyf[indexList[i]] = glyfList[i]; } glyfList = glyfList.slice(l); } if (glyfList.length) { Array.prototype.splice.apply(glyf, [glyf.length, 0, ...glyfList]); } return result; } /** * 调整glyf位置 * * @param {Array=} indexList 索引列表 * @param {Object} setting 选项 * @param {number=} setting.leftSideBearing 左边距 * @param {number=} setting.rightSideBearing 右边距 * @param {number=} setting.verticalAlign 垂直对齐 * @return {Array} 改变的glyf */ adjustGlyfPos(indexList, setting) { const glyfList = this.getGlyf(indexList); return adjustPos( glyfList, setting.leftSideBearing, setting.rightSideBearing, setting.verticalAlign ); } /** * 调整glyf * * @param {Array=} indexList 索引列表 * @param {Object} setting 选项 * @param {boolean=} setting.reverse 字形反转操作 * @param {boolean=} setting.mirror 字形镜像操作 * @param {number=} setting.scale 字形缩放 * @param {boolean=} setting.adjustToEmBox 是否调整字形到 em 框 * @param {number=} setting.adjustToEmPadding 调整到 em 框的留白 * @return {boolean} */ adjustGlyf(indexList, setting) { const glyfList = this.getGlyf(indexList); let changed = false; setting.adjustToEmBox = setting.ajdustToEmBox || setting.adjustToEmBox; setting.adjustToEmPadding = setting.ajdustToEmPadding || setting.adjustToEmPadding; if (setting.reverse || setting.mirror) { changed = true; glyfList.forEach((g) => { if (g.contours && g.contours.length) { const offsetX = g.xMax + g.xMin; const offsetY = g.yMax + g.yMin; g.contours.forEach((contour) => { pathAdjust(contour, setting.mirror ? -1 : 1, setting.reverse ? -1 : 1); pathAdjust(contour, 1, 1, setting.mirror ? offsetX : 0, setting.reverse ? offsetY : 0); }); } }); } if (setting.scale && setting.scale !== 1) { changed = true; const scale = setting.scale; glyfList.forEach((g) => { if (g.contours && g.contours.length) { glyfAdjust(g, scale, scale); } }); } // 缩放到embox else if (setting.adjustToEmBox) { changed = true; const ascent = this.ttf.hhea.ascent; const descent = this.ttf.hhea.descent; const adjustToEmPadding = 2 * (setting.adjustToEmPadding || 0); adjustToEmBox(glyfList, ascent, descent, adjustToEmPadding); } return changed ? glyfList : []; } /** * 获取glyf列表 * * @param {Array=} indexList 索引列表 * @return {Array} glyflist */ getGlyf(indexList) { const glyf = this.ttf.glyf; if (indexList && indexList.length) { return indexList.map((item) => glyf[item]); } return glyf; } /** * 查找相关字形 * * @param {Object} condition 查询条件 * @param {Array|number} condition.unicode unicode编码列表或者单个unicode编码 * @param {string} condition.name glyf名字,例如`uniE001`, `uniE` * @param {Function} condition.filter 自定义过滤器 * @example * condition.filter = function (glyf) { * return glyf.name === 'logo'; * } * @return {Array} glyf字形索引列表 */ findGlyf(condition) { if (!condition) { return []; } const filters = []; // 按unicode数组查找 if (condition.unicode) { const unicodeList = Array.isArray(condition.unicode) ? condition.unicode : [condition.unicode]; const unicodeHash = {}; unicodeList.forEach((unicode) => { if (typeof unicode === 'string') { unicode = Number('0x' + unicode.slice(1)); } unicodeHash[unicode] = true; }); filters.push((glyf) => { if (!glyf.unicode || !glyf.unicode.length) { return false; } for (let i = 0, l = glyf.unicode.length; i < l; i++) { if (unicodeHash[glyf.unicode[i]]) { return true; } } }); } // 按名字查找 if (condition.name) { const name = condition.name; filters.push((glyf) => glyf.name && glyf.name.indexOf(name) === 0); } // 按筛选函数查找 if (typeof condition.filter === 'function') { filters.push(condition.filter); } const indexList = []; this.ttf.glyf.forEach((glyf, index) => { for (let filterIndex = 0, filter; (filter = filters[filterIndex++]);) { if (true === filter(glyf)) { indexList.push(index); break; } } }); return indexList; } /** * 更新指定的glyf * * @param {Object} glyf glyfobject * @param {string} index 需要替换的索引列表 * @return {Array} 改变的glyf */ replaceGlyf(glyf, index) { if (index >= 0 && index < this.ttf.glyf.length) { this.ttf.glyf[index] = glyf; return [glyf]; } return []; } /** * 设置glyf * * @param {Array} glyfList glyf列表 * @return {Array} 设置的glyf列表 */ setGlyf(glyfList) { delete this.glyf; this.ttf.glyf = glyfList || []; return this.ttf.glyf; } /** * 对字形按照unicode编码排序,此处不对复合字形进行排序,如果存在复合字形, 不进行排序 * * @param {Array} glyfList glyf列表 * @return {Array} 设置的glyf列表 */ sortGlyf() { const glyf = this.ttf.glyf; if (glyf.length > 1) { // 如果存在复合字形则退出 if (glyf.some((a) => a.compound)) { return -2; } const notdef = glyf.shift(); // 按代码点排序, 首先将空字形排到最后,然后按照unicode第一个编码进行排序 glyf.sort((a, b) => { if ((!a.unicode || !a.unicode.length) && (!b.unicode || !b.unicode.length)) { return 0; } else if ((!a.unicode || !a.unicode.length) && b.unicode) { return 1; } else if (a.unicode && (!b.unicode || !b.unicode.length)) { return -1; } return Math.min.apply(null, a.unicode) - Math.min.apply(null, b.unicode); }); glyf.unshift(notdef); return glyf; } return -1; } /** * 设置名字 * * @param {string} name 名字字段 * @return {Object} 名字对象 */ setName(name) { if (name) { this.ttf.name.fontFamily = this.ttf.name.fullName = name.fontFamily || config.name.fontFamily; this.ttf.name.fontSubFamily = name.fontSubFamily || config.name.fontSubFamily; this.ttf.name.uniqueSubFamily = name.uniqueSubFamily || ''; this.ttf.name.postScriptName = name.postScriptName || ''; } return this.ttf.name; } /** * 设置head信息 * * @param {Object} head 头部信息 * @return {Object} 头对象 */ setHead(head) { if (head) { // unitsperem if (head.unitsPerEm && head.unitsPerEm >= 64 && head.unitsPerEm <= 16384) { this.ttf.head.unitsPerEm = head.unitsPerEm; } // lowestrecppem if (head.lowestRecPPEM && head.lowestRecPPEM >= 8 && head.lowestRecPPEM <= 16384) { this.ttf.head.lowestRecPPEM = head.lowestRecPPEM; } // created if (head.created) { this.ttf.head.created = head.created; } if (head.modified) { this.ttf.head.modified = head.modified; } } return this.ttf.head; } /** * 设置hhea信息 * * @param {Object} fields 字段值 * @return {Object} 头对象 */ setHhea(fields) { overwrite(this.ttf.hhea, fields, ['ascent', 'descent', 'lineGap']); return this.ttf.hhea; } /** * 设置OS2信息 * * @param {Object} fields 字段值 * @return {Object} 头对象 */ setOS2(fields) { overwrite( this.ttf['OS/2'], fields, [ 'usWinAscent', 'usWinDescent', 'sTypoAscender', 'sTypoDescender', 'sTypoLineGap', 'sxHeight', 'bXHeight', 'usWeightClass', 'usWidthClass', 'yStrikeoutPosition', 'yStrikeoutSize', 'achVendID', // panose 'bFamilyType', 'bSerifStyle', 'bWeight', 'bProportion', 'bContrast', 'bStrokeVariation', 'bArmStyle', 'bLetterform', 'bMidline', 'bXHeight' ] ); return this.ttf['OS/2']; } /** * 设置post信息 * * @param {Object} fields 字段值 * @return {Object} 头对象 */ setPost(fields) { overwrite( this.ttf.post, fields, [ 'underlinePosition', 'underlineThickness' ] ); return this.ttf.post; } /** * 计算度量信息 * * @return {Object} 度量信息 */ calcMetrics() { let ascent = -16384; let descent = 16384; const uX = 0x78; const uH = 0x48; let sxHeight; let sCapHeight; this.ttf.glyf.forEach((g) => { if (g.yMax > ascent) { ascent = g.yMax; } if (g.yMin < descent) { descent = g.yMin; } if (g.unicode) { if (g.unicode.indexOf(uX) >= 0) { sxHeight = g.yMax; } if (g.unicode.indexOf(uH) >= 0) { sCapHeight = g.yMax; } } }); ascent = Math.round(ascent); descent = Math.round(descent); return { // 此处非必须自动设置 ascent, descent, sTypoAscender: ascent, sTypoDescender: descent, // 自动设置项目 usWinAscent: ascent, usWinDescent: -descent, sxHeight: sxHeight || 0, sCapHeight: sCapHeight || 0 }; } /** * 优化ttf字形信息 * * @return {Array} 改变的glyf */ optimize() { return optimizettf(this.ttf); } /** * 复合字形转简单字形 * * @param {Array=} indexList 索引列表 * @return {Array} 改变的glyf */ compound2simple(indexList) { const ttf = this.ttf; if (ttf.maxp && !ttf.maxp.maxComponentElements) { return []; } let i; let l; // 全部的compound glyf if (!indexList || !indexList.length) { indexList = []; for (i = 0, l = ttf.glyf.length; i < l; ++i) { if (ttf.glyf[i].compound) { indexList.push(i); } } } const list = []; for (i = 0, l = indexList.length; i < l; ++i) { const glyfIndex = indexList[i]; if (ttf.glyf[glyfIndex] && ttf.glyf[glyfIndex].compound) { compound2simpleglyf(glyfIndex, ttf, true); list.push(ttf.glyf[glyfIndex]); } } return list; } }