ifont
Version:
An isomorphic icon font generator with support for ligatures.
196 lines (157 loc) • 4.99 kB
JavaScript
;
import * as _ from './lodash.js';
import cubic2quad from 'cubic2quad';
import svgpath from 'svgpath';
import ucs2 from './ucs2.js';
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(icons) {
var familyName = 'iFont';
var subfamilyName = 'Regular';
var id = (familyName + '-' + subfamilyName).replace(/[\s\(\)\[\]<>%\/]/g, '').substr(0, 62);
var size = 240;
var font = {
id: id,
familyName: familyName,
subfamilyName: subfamilyName,
stretch: 'normal',
width: size,
// height: size,
ascent: size,
descent: 0,
unitsPerEm: size,
};
font.missingGlyph = {};
font.missingGlyph.d = '';
font.missingGlyph.width = 0;
var glyphs = [];
var ligatures = [];
icons.forEach ( icon => {
var glyph = {
d: icon.path,
unicode: [],
character: icon.name,
ligature: icon.name,
ligatureCodes: icon.name.split ( '' ).map ( c => c.charCodeAt ( 0 ) ),
name: '',
width: size
}
glyphs.push(glyph);
ligatures.push({
ligature: glyph.ligature,
unicode: glyph.ligatureCodes,
glyph: 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;
}
export {load};
export {cubicToQuad};
export {toSfntCoutours};