UNPKG

fonteditor-core

Version:

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

393 lines (372 loc) 12.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = svg2ttfObject; var _string = _interopRequireDefault(require("../common/string")); var _DOMParser = _interopRequireDefault(require("../common/DOMParser")); var _path2contours = _interopRequireDefault(require("./svg/path2contours")); var _svgnode2contours = _interopRequireDefault(require("./svg/svgnode2contours")); var _computeBoundingBox = require("../graphics/computeBoundingBox"); var _pathsUtil = _interopRequireDefault(require("../graphics/pathsUtil")); var _glyfAdjust = _interopRequireDefault(require("./util/glyfAdjust")); var _error = _interopRequireDefault(require("./error")); var _getEmptyttfObject = _interopRequireDefault(require("./getEmptyttfObject")); var _reduceGlyf = _interopRequireDefault(require("./util/reduceGlyf")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } /** * @file svg格式转ttfObject格式 * @author mengke01(kekee000@gmail.com) */ /** * 加载xml字符串 * * @param {string} xml xml字符串 * @return {Document} */ function loadXML(xml) { if (_DOMParser.default) { try { var domParser = new _DOMParser.default(); var xmlDoc = domParser.parseFromString(xml, 'text/xml'); return xmlDoc; } catch (exp) { _error.default.raise(10103); } } _error.default.raise(10004); } /** * 对xml文本进行处理 * * @param {string} svg svg文本 * @return {string} 处理后文本 */ function resolveSVG(svg) { // 去除xmlns,防止xmlns导致svg解析错误 svg = svg.replace(/\s+xmlns(?::[\w-]+)?=("|')[^"']*\1/g, ' ').replace(/<defs[>\s][\s\S]+?\/defs>/g, function (text) { if (text.indexOf('</font>') >= 0) { return text; } return ''; }).replace(/<use[>\s][\s\S]+?\/use>/g, ''); return svg; } /** * 获取空的ttf格式对象 * * @return {Object} ttfObject对象 */ function getEmptyTTF() { var ttf = (0, _getEmptyttfObject.default)(); ttf.head.unitsPerEm = 0; // 去除unitsPerEm以便于重新计算 ttf.from = 'svgfont'; return ttf; } /** * 获取空的对象,用来作为ttf的容器 * * @return {Object} ttfObject对象 */ function getEmptyObject() { return { 'from': 'svg', 'OS/2': {}, 'name': {}, 'hhea': {}, 'head': {}, 'post': {}, 'glyf': [] }; } /** * 根据边界获取unitsPerEm * * @param {number} xMin x最小值 * @param {number} xMax x最大值 * @param {number} yMin y最小值 * @param {number} yMax y最大值 * @return {number} */ function getUnitsPerEm(xMin, xMax, yMin, yMax) { var seed = Math.ceil(Math.min(yMax - yMin, xMax - xMin)); if (!seed) { return 1024; } if (seed <= 128) { return seed; } // 获取合适的unitsPerEm var unitsPerEm = 128; while (unitsPerEm < 16384) { if (seed <= 1.2 * unitsPerEm) { return unitsPerEm; } unitsPerEm <<= 1; } return 1024; } /** * 对ttfObject进行处理,去除小数 * * @param {Object} ttf ttfObject * @return {Object} ttfObject */ function resolve(ttf) { // 如果是svg格式字体,则去小数 // 由于svg格式导入时候会出现字形重复问题,这里进行优化 if (ttf.from === 'svgfont' && ttf.head.unitsPerEm > 128) { ttf.glyf.forEach(function (g) { if (g.contours) { (0, _glyfAdjust.default)(g); (0, _reduceGlyf.default)(g); } }); } // 否则重新计算字形大小,缩放到1024的em else { var xMin = 16384; var xMax = -16384; var yMin = 16384; var yMax = -16384; ttf.glyf.forEach(function (g) { if (g.contours) { var bound = _computeBoundingBox.computePathBox.apply(void 0, _toConsumableArray(g.contours)); if (bound) { xMin = Math.min(xMin, bound.x); xMax = Math.max(xMax, bound.x + bound.width); yMin = Math.min(yMin, bound.y); yMax = Math.max(yMax, bound.y + bound.height); } } }); var unitsPerEm = getUnitsPerEm(xMin, xMax, yMin, yMax); var scale = 1024 / unitsPerEm; ttf.glyf.forEach(function (g) { (0, _glyfAdjust.default)(g, scale, scale); (0, _reduceGlyf.default)(g); }); ttf.head.unitsPerEm = 1024; } return ttf; } /** * 解析字体信息相关节点 * * @param {Document} xmlDoc XML文档对象 * @param {Object} ttf ttf对象 * @return {Object} ttf对象 */ function parseFont(xmlDoc, ttf) { var metaNode = xmlDoc.getElementsByTagName('metadata')[0]; var fontNode = xmlDoc.getElementsByTagName('font')[0]; var fontFaceNode = xmlDoc.getElementsByTagName('font-face')[0]; if (metaNode && metaNode.textContent) { ttf.metadata = _string.default.decodeHTML(metaNode.textContent.trim()); } // 解析font,如果有font节点说明是svg格式字体文件 if (fontNode) { ttf.id = fontNode.getAttribute('id') || ''; ttf.hhea.advanceWidthMax = +(fontNode.getAttribute('horiz-adv-x') || 0); ttf.from = 'svgfont'; } if (fontFaceNode) { var OS2 = ttf['OS/2']; ttf.name.fontFamily = fontFaceNode.getAttribute('font-family') || ''; OS2.usWeightClass = +(fontFaceNode.getAttribute('font-weight') || 0); ttf.head.unitsPerEm = +(fontFaceNode.getAttribute('units-per-em') || 0); // 解析panose, eg: 2 0 6 3 0 0 0 0 0 0 var panose = (fontFaceNode.getAttribute('panose-1') || '').split(' '); ['bFamilyType', 'bSerifStyle', 'bWeight', 'bProportion', 'bContrast', 'bStrokeVariation', 'bArmStyle', 'bLetterform', 'bMidline', 'bXHeight'].forEach(function (name, i) { OS2[name] = +(panose[i] || 0); }); ttf.hhea.ascent = +(fontFaceNode.getAttribute('ascent') || 0); ttf.hhea.descent = +(fontFaceNode.getAttribute('descent') || 0); OS2.bXHeight = +(fontFaceNode.getAttribute('x-height') || 0); // 解析bounding var box = (fontFaceNode.getAttribute('bbox') || '').split(' '); ['xMin', 'yMin', 'xMax', 'yMax'].forEach(function (name, i) { ttf.head[name] = +(box[i] || ''); }); ttf.post.underlineThickness = +(fontFaceNode.getAttribute('underline-thickness') || 0); ttf.post.underlinePosition = +(fontFaceNode.getAttribute('underline-position') || 0); // unicode range var unicodeRange = fontFaceNode.getAttribute('unicode-range'); if (unicodeRange) { unicodeRange.replace(/u\+([0-9A-Z]+)(-[0-9A-Z]+)?/i, function ($0, a, b) { OS2.usFirstCharIndex = Number('0x' + a); OS2.usLastCharIndex = b ? Number('0x' + b.slice(1)) : 0xFFFFFFFF; }); } } return ttf; } /** * 解析字体信息相关节点 * * @param {Document} xmlDoc XML文档对象 * @param {Object} ttf ttf对象 * @return {Object} ttf对象 */ function parseGlyf(xmlDoc, ttf) { var missingNode = xmlDoc.getElementsByTagName('missing-glyph')[0]; // 解析glyf var d; var unicode; if (missingNode) { var missing = { name: '.notdef' }; if (missingNode.getAttribute('horiz-adv-x')) { missing.advanceWidth = +missingNode.getAttribute('horiz-adv-x'); } if (d = missingNode.getAttribute('d')) { missing.contours = (0, _path2contours.default)(d); } // 去除默认的空字形 if (ttf.glyf[0] && ttf.glyf[0].name === '.notdef') { ttf.glyf.splice(0, 1); } ttf.glyf.unshift(missing); } var glyfNodes = xmlDoc.getElementsByTagName('glyph'); if (glyfNodes.length) { for (var i = 0, l = glyfNodes.length; i < l; i++) { var node = glyfNodes[i]; var glyf = { name: node.getAttribute('glyph-name') || node.getAttribute('name') || '' }; if (node.getAttribute('horiz-adv-x')) { glyf.advanceWidth = +node.getAttribute('horiz-adv-x'); } if (unicode = node.getAttribute('unicode')) { var nextUnicode = []; var totalCodePoints = 0; for (var ui = 0; ui < unicode.length; ui++) { var ucp = unicode.codePointAt(ui); nextUnicode.push(ucp); ui = ucp > 0xffff ? ui + 1 : ui; totalCodePoints += 1; } if (totalCodePoints === 1) { // TTF can't handle ligatures glyf.unicode = nextUnicode; if (d = node.getAttribute('d')) { glyf.contours = (0, _path2contours.default)(d); } ttf.glyf.push(glyf); } } } } return ttf; } /** * 解析字体信息相关节点 * * @param {Document} xmlDoc XML文档对象 * @param {Object} ttf ttf对象 */ function parsePath(xmlDoc, ttf) { // 单个path组成一个glfy字形 var contours; var glyf; var node; var pathNodes = xmlDoc.getElementsByTagName('path'); if (pathNodes.length) { for (var i = 0, l = pathNodes.length; i < l; i++) { node = pathNodes[i]; glyf = { name: node.getAttribute('name') || '' }; contours = (0, _svgnode2contours.default)([node]); glyf.contours = contours; ttf.glyf.push(glyf); } } // 其他svg指令组成一个glyf字形 contours = (0, _svgnode2contours.default)(Array.prototype.slice.call(xmlDoc.getElementsByTagName('*')).filter(function (node) { return node.tagName !== 'path'; })); if (contours) { glyf = { name: '' }; glyf.contours = contours; ttf.glyf.push(glyf); } } /** * 解析xml文档 * * @param {Document} xmlDoc XML文档对象 * @param {Object} options 导入选项 * * @return {Object} 解析后对象 */ function parseXML(xmlDoc, options) { if (!xmlDoc.getElementsByTagName('svg').length) { _error.default.raise(10106); } var ttf; // 如果是svg字体格式,则解析glyf,否则解析path if (xmlDoc.getElementsByTagName('font')[0]) { ttf = getEmptyTTF(); parseFont(xmlDoc, ttf); parseGlyf(xmlDoc, ttf); } else { ttf = getEmptyObject(); parsePath(xmlDoc, ttf); } if (!ttf.glyf.length) { _error.default.raise(10201); } if (ttf.from === 'svg') { var glyf = ttf.glyf; var i; var l; // 合并导入的字形为单个字形 if (options.combinePath) { var combined = []; for (i = 0, l = glyf.length; i < l; i++) { var contours = glyf[i].contours; for (var index = 0, length = contours.length; index < length; index++) { combined.push(contours[index]); } } glyf[0].contours = combined; glyf.splice(1); } // 对字形进行反转 for (i = 0, l = glyf.length; i < l; i++) { // 这里为了使ai等工具里面的字形方便导入,对svg做了反向处理 glyf[i].contours = _pathsUtil.default.flip(glyf[i].contours); } } return ttf; } /** * svg格式转ttfObject格式 * * @param {string|Document} svg svg格式 * @param {Object=} options 导入选项 * @param {boolean} options.combinePath 是否合并成单个字形,仅限于普通svg导入 * @return {Object} ttfObject */ function svg2ttfObject(svg) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { combinePath: false }; var xmlDoc = svg; if (typeof svg === 'string') { svg = resolveSVG(svg); xmlDoc = loadXML(svg); } var ttf = parseXML(xmlDoc, options); return resolve(ttf); }