UNPKG

svg2ttf

Version:

Converts SVG font to TTF font

245 lines (195 loc) 7.03 kB
'use strict' var _ = require('lodash') var cubic2quad = require('cubic2quad') var svgpath = require('svgpath') var DOMParser = require('@xmldom/xmldom').DOMParser var ucs2 = require('./ucs2') function getGlyph (glyphElem, fontInfo) { var glyph = {} if (glyphElem.hasAttribute('d')) { glyph.d = glyphElem.getAttribute('d').trim() } else { // try nested <path> var pathElem = glyphElem.getElementsByTagName('path')[0] if (pathElem.hasAttribute('d')) { // <path> has reversed Y axis glyph.d = svgpath(pathElem.getAttribute('d')) .scale(1, -1) .translate(0, fontInfo.ascent) .toString() } else { throw new Error("Can't find 'd' attribute of <glyph> tag.") } } glyph.unicode = [] if (glyphElem.getAttribute('unicode')) { glyph.character = glyphElem.getAttribute('unicode') var unicode = ucs2.decode(glyph.character) // If more than one code point is involved, the glyph is a ligature glyph if (unicode.length > 1) { glyph.ligature = glyph.character glyph.ligatureCodes = unicode } else { glyph.unicode.push(unicode[0]) } } glyph.name = glyphElem.getAttribute('glyph-name') if (glyphElem.getAttribute('horiz-adv-x')) { glyph.width = parseInt(glyphElem.getAttribute('horiz-adv-x'), 10) } return glyph } function deduplicateGlyps (glyphs, ligatures) { // Result (the list of unique glyphs) var result = [] _.forEach(glyphs, function (glyph) { // Search for glyphs with the same properties (width and d) var canonical = _.find(result, { width: glyph.width, d: glyph.d }) if (canonical) { // Add the code points to the unicode array. // The fields “name” and “character” are not that important so we leave them how we first enounter them and throw the rest away canonical.unicode = canonical.unicode.concat(glyph.unicode) glyph.canonical = canonical } else { result.push(glyph) } }) // Update ligatures to point to the canonical version _.forEach(ligatures, function (ligature) { while (_.has(ligature.glyph, 'canonical')) { ligature.glyph = ligature.glyph.canonical } }) return result } function load (str) { var attrs // xmldom@0.9.x rejects XML declarations that are not at the very start. // Trim surrounding whitespace to keep compatibility with historical svg2ttf inputs. // TODO: remove in next major version. var doc = (new DOMParser()).parseFromString(str.trim(), 'application/xml') var metadata, fontElem, fontFaceElem metadata = doc.getElementsByTagName('metadata')[0] fontElem = doc.getElementsByTagName('font')[0] if (!fontElem) { throw new Error("Can't find <font> tag. Make sure you SVG file is font, not image.") } fontFaceElem = fontElem.getElementsByTagName('font-face')[0] var familyName = fontFaceElem.getAttribute('font-family') || 'fontello' var subfamilyName = fontFaceElem.getAttribute('font-style') || 'Regular' var id = fontElem.getAttribute('id') || (familyName + '-' + subfamilyName).replace(/[\s\(\)\[\]<>%\/]/g, '').substr(0, 62) var font = { id: id, familyName: familyName, subfamilyName: subfamilyName, stretch: fontFaceElem.getAttribute('font-stretch') || 'normal' } // Doesn't work with complex content like <strong>Copyright:></strong><em>Fontello</em> if (metadata && metadata.textContent) { font.metadata = metadata.textContent } // Get <font> numeric attributes attrs = { width: 'horiz-adv-x', // height: 'vert-adv-y', horizOriginX: 'horiz-origin-x', horizOriginY: 'horiz-origin-y', vertOriginX: 'vert-origin-x', vertOriginY: 'vert-origin-y' } _.forEach(attrs, function (val, key) { if (fontElem.hasAttribute(val)) { font[key] = parseInt(fontElem.getAttribute(val), 10) } }) // Get <font-face> numeric attributes attrs = { ascent: 'ascent', descent: 'descent', unitsPerEm: 'units-per-em', capHeight: 'cap-height', xHeight: 'x-height', underlineThickness: 'underline-thickness', underlinePosition: 'underline-position' } _.forEach(attrs, function (val, key) { if (fontFaceElem.hasAttribute(val)) { font[key] = parseInt(fontFaceElem.getAttribute(val), 10) } }) if (fontFaceElem.hasAttribute('font-weight')) { font.weightClass = fontFaceElem.getAttribute('font-weight') } var missingGlyphElem = fontElem.getElementsByTagName('missing-glyph')[0] if (missingGlyphElem) { font.missingGlyph = {} font.missingGlyph.d = missingGlyphElem.getAttribute('d') || '' if (missingGlyphElem.getAttribute('horiz-adv-x')) { font.missingGlyph.width = parseInt(missingGlyphElem.getAttribute('horiz-adv-x'), 10) } } var glyphs = [] var ligatures = [] _.forEach(fontElem.getElementsByTagName('glyph'), function (glyphElem) { var glyph = getGlyph(glyphElem, font) if (_.has(glyph, 'ligature')) { ligatures.push({ ligature: glyph.ligature, unicode: glyph.ligatureCodes, glyph: glyph }) } glyphs.push(glyph) }) glyphs = deduplicateGlyps(glyphs, ligatures) font.glyphs = glyphs font.ligatures = ligatures return font } function cubicToQuad (segment, index, x, y, accuracy) { if (segment[0] === 'C') { var quadCurves = cubic2quad( x, y, segment[1], segment[2], segment[3], segment[4], segment[5], segment[6], accuracy ) var res = [] for (var i = 2; i < quadCurves.length; i += 4) { res.push(['Q', quadCurves[i], quadCurves[i + 1], quadCurves[i + 2], quadCurves[i + 3]]) } return res } } // Converts svg points to contours. All points must be converted // to relative ones, smooth curves must be converted to generic ones // before this conversion. // function toSfntCoutours (svgPath) { var resContours = [] var resContour = [] svgPath.iterate(function (segment, index, x, y) { // start new contour if (index === 0 || segment[0] === 'M') { resContour = [] resContours.push(resContour) } var name = segment[0] if (name === 'Q') { // add control point of quad spline, it is not on curve resContour.push({ x: segment[1], y: segment[2], onCurve: false }) } // add on-curve point if (name === 'H') { // vertical line has Y coordinate only, X remains the same resContour.push({ x: segment[1], y: y, onCurve: true }) } else if (name === 'V') { // horizontal line has X coordinate only, Y remains the same resContour.push({ x: x, y: segment[1], onCurve: true }) } else if (name !== 'Z') { // for all commands (except H and V) X and Y are placed in the end of the segment resContour.push({ x: segment[segment.length - 2], y: segment[segment.length - 1], onCurve: true }) } }) return resContours } module.exports.load = load module.exports.cubicToQuad = cubicToQuad module.exports.toSfntCoutours = toSfntCoutours