-
-
Notifications
You must be signed in to change notification settings - Fork 80
/
index.js
210 lines (169 loc) · 6.84 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
/*
* Copyright: Vitaly Puzrin
* Author: Sergey Batishchev <[email protected]>
*
* Written for fontello.com project.
*/
'use strict';
var _ = require('lodash');
var SvgPath = require('svgpath');
var ucs2 = require('./lib/ucs2');
var svg = require('./lib/svg');
var sfnt = require('./lib/sfnt');
var VERSION_RE = /^(Version )?(\d+[.]\d+)$/i;
function svg2ttf(svgString, options) {
var font = new sfnt.Font();
var svgFont = svg.load(svgString);
options = options || {};
font.id = options.id || svgFont.id;
font.familyName = options.familyname || svgFont.familyName || svgFont.id;
font.copyright = options.copyright || svgFont.metadata;
font.description = options.description || 'Generated by svg2ttf from Fontello project.';
font.url = options.url || 'http://fontello.com';
font.sfntNames.push({ id: 2, value: options.subfamilyname || svgFont.subfamilyName || 'Regular' }); // subfamily name
font.sfntNames.push({ id: 4, value: options.fullname || svgFont.id }); // full name
var versionString = options.version || 'Version 1.0';
if (typeof versionString !== 'string') {
throw new Error('svg2ttf: version option should be a string');
}
if (!VERSION_RE.test(versionString)) {
throw new Error('svg2ttf: invalid option, version - "' + options.version + '"');
}
versionString = 'Version ' + versionString.match(VERSION_RE)[2];
font.sfntNames.push({ id: 5, value: versionString }); // version ID for TTF name table
font.sfntNames.push({ id: 6, value: (options.fullname || svgFont.id).replace(/[\s\(\)\[\]<>%\/]/g, '').substr(0, 62) }); // Postscript name for the font, required for OSX Font Book
if (typeof options.ts !== 'undefined') {
font.createdDate = font.modifiedDate = new Date(parseInt(options.ts, 10) * 1000);
}
// Try to fill font metrics or guess defaults
//
font.unitsPerEm = svgFont.unitsPerEm || 1000;
font.horizOriginX = svgFont.horizOriginX || 0;
font.horizOriginY = svgFont.horizOriginY || 0;
font.vertOriginX = svgFont.vertOriginX || 0;
font.vertOriginY = svgFont.vertOriginY || 0;
font.width = svgFont.width || svgFont.unitsPerEm;
font.height = svgFont.height || svgFont.unitsPerEm;
font.descent = !isNaN(svgFont.descent) ? svgFont.descent : -font.vertOriginY;
font.ascent = svgFont.ascent || (font.unitsPerEm - font.vertOriginY);
// Values for font substitution. We're mostly working with icon fonts, so they aren't expected to be substituted.
// https://docs.microsoft.com/en-us/typography/opentype/spec/os2#sxheight
font.capHeight = svgFont.capHeight || 0; // 0 is a valid value if "H" glyph doesn't exist
font.xHeight = svgFont.xHeight || 0; // 0 is a valid value if "x" glyph doesn't exist
if (typeof svgFont.weightClass !== 'undefined') {
var wght = parseInt(svgFont.weightClass, 10);
if (!isNaN(wght)) font.weightClass = wght;
else {
// Unknown names are silently ignored
if (svgFont.weightClass === 'normal') font.weightClass = 400;
if (svgFont.weightClass === 'bold') font.weightClass = 700;
}
}
if (typeof svgFont.underlinePosition !== 'undefined') {
font.underlinePosition = svgFont.underlinePosition;
}
if (typeof svgFont.underlineThickness !== 'undefined') {
font.underlineThickness = svgFont.underlineThickness;
}
var glyphs = font.glyphs;
var codePoints = font.codePoints;
var ligatures = font.ligatures;
function addCodePoint(codePoint, glyph) {
if (codePoints[codePoint]) {
// Ignore code points already defined
return false;
}
codePoints[codePoint] = glyph;
return true;
}
// add SVG glyphs to SFNT font
_.forEach(svgFont.glyphs, function (svgGlyph) {
var glyph = new sfnt.Glyph();
glyph.name = svgGlyph.name;
glyph.codes = svgGlyph.ligatureCodes || svgGlyph.unicode; // needed for nice validator error output
glyph.d = svgGlyph.d;
glyph.height = !isNaN(svgGlyph.height) ? svgGlyph.height : font.height;
glyph.width = !isNaN(svgGlyph.width) ? svgGlyph.width : font.width;
glyphs.push(glyph);
svgGlyph.sfntGlyph = glyph;
_.forEach(svgGlyph.unicode, function (codePoint) {
addCodePoint(codePoint, glyph);
});
});
var missingGlyph;
// add missing glyph to SFNT font
// also, check missing glyph existance and single instance
if (svgFont.missingGlyph) {
missingGlyph = new sfnt.Glyph();
missingGlyph.d = svgFont.missingGlyph.d;
missingGlyph.height = !isNaN(svgFont.missingGlyph.height) ? svgFont.missingGlyph.height : font.height;
missingGlyph.width = !isNaN(svgFont.missingGlyph.width) ? svgFont.missingGlyph.width : font.width;
} else {
missingGlyph = _.find(glyphs, function (glyph) {
return glyph.name === '.notdef';
});
}
if (!missingGlyph) { // no missing glyph and .notdef glyph, we need to create missing glyph
missingGlyph = new sfnt.Glyph();
}
// Create glyphs for all characters used in ligatures
_.forEach(svgFont.ligatures, function (svgLigature) {
var ligature = {
ligature: svgLigature.ligature,
unicode: svgLigature.unicode,
glyph: svgLigature.glyph.sfntGlyph
};
_.forEach(ligature.unicode, function (charPoint) {
// We need to have a distinct glyph for each code point so we can reference it in GSUB
var glyph = new sfnt.Glyph();
var added = addCodePoint(charPoint, glyph);
if (added) {
glyph.name = ucs2.encode([ charPoint ]);
glyphs.push(glyph);
}
});
ligatures.push(ligature);
});
// Missing Glyph needs to have index 0
if (glyphs.indexOf(missingGlyph) !== -1) {
glyphs.splice(glyphs.indexOf(missingGlyph), 1);
}
glyphs.unshift(missingGlyph);
var nextID = 0;
//add IDs
_.forEach(glyphs, function (glyph) {
glyph.id = nextID;
nextID++;
});
_.forEach(glyphs, function (glyph) {
// Calculate accuracy for cubicToQuad transformation
// For glyphs with height and width smaller than 500 use relative 0.06% accuracy,
// for larger glyphs use fixed accuracy 0.3.
var glyphSize = Math.max(glyph.width, glyph.height);
var accuracy = (glyphSize > 500) ? 0.3 : glyphSize * 0.0006;
//SVG transformations
var svgPath = new SvgPath(glyph.d)
.abs()
.unshort()
.unarc()
.iterate(function (segment, index, x, y) {
return svg.cubicToQuad(segment, index, x, y, accuracy);
});
var sfntContours = svg.toSfntCoutours(svgPath);
// Add contours to SFNT font
glyph.contours = _.map(sfntContours, function (sfntContour) {
var contour = new sfnt.Contour();
contour.points = _.map(sfntContour, function (sfntPoint) {
var point = new sfnt.Point();
point.x = sfntPoint.x;
point.y = sfntPoint.y;
point.onCurve = sfntPoint.onCurve;
return point;
});
return contour;
});
});
var ttf = sfnt.toTTF(font);
return ttf;
}
module.exports = svg2ttf;