You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

245 lines
6.7 KiB

'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;
var doc = (new DOMParser()).parseFromString(str, '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;