svg2ttf
Version:
Converts SVG font to TTF font
210 lines (169 loc) • 6.85 kB
JavaScript
/*
* Copyright: Vitaly Puzrin
* Author: Sergey Batishchev <snb2003@rambler.ru>
*
* Written for fontello.com project.
*/
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