svg2ttf
Version:
Converts SVG font to TTF font
374 lines (318 loc) • 10.4 kB
JavaScript
'use strict'
var _ = require('lodash')
function Font () {
this.ascent = 850
this.copyright = ''
this.createdDate = new Date()
this.glyphs = []
this.ligatures = []
// Maping of code points to glyphs.
// Keys are actually numeric, thus should be `parseInt`ed.
this.codePoints = {}
this.isFixedPitch = 0
this.italicAngle = 0
this.familyClass = 0 // No Classification
this.familyName = ''
// 0x40 - REGULAR - Characters are in the standard weight/style for the font
// 0x80 - USE_TYPO_METRICS - use OS/2.sTypoAscender - OS/2.sTypoDescender + OS/2.sTypoLineGap as the default line spacing
// https://docs.microsoft.com/en-us/typography/opentype/spec/os2#fsselection
// https://github.com/fontello/svg2ttf/issues/95
this.fsSelection = 0x40 | 0x80
// Non zero value can cause issues in IE, https://github.com/fontello/svg2ttf/issues/45
this.fsType = 0
this.lowestRecPPEM = 8
this.macStyle = 0
this.modifiedDate = new Date()
this.panose = {
familyType: 2, // Latin Text
serifStyle: 0, // any
weight: 5, // book
proportion: 3, // modern
contrast: 0, // any
strokeVariation: 0, // any,
armStyle: 0, // any,
letterform: 0, // any,
midline: 0, // any,
xHeight: 0 // any,
}
this.revision = 1
this.sfntNames = []
this.underlineThickness = 0
this.unitsPerEm = 1000
this.weightClass = 400 // normal
this.width = 1000
this.widthClass = 5 // Medium (normal)
this.ySubscriptXOffset = 0
this.ySuperscriptXOffset = 0
this.int_descent = -150
this.xHeight = 0
this.capHeight = 0
// getters and setters
Object.defineProperty(this, 'descent', {
get: function () {
return this.int_descent
},
set: function (value) {
this.int_descent = parseInt(Math.round(-Math.abs(value)), 10)
}
})
this.__defineGetter__('avgCharWidth', function () {
if (this.glyphs.length === 0) {
return 0
}
var widths = _.map(this.glyphs, 'width')
return parseInt(widths.reduce(function (prev, cur) {
return prev + cur
}) / widths.length, 10)
})
Object.defineProperty(this, 'ySubscriptXSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySubscriptXSize) ? this.int_ySubscriptXSize : (this.width * 0.6347), 10)
},
set: function (value) {
this.int_ySubscriptXSize = value
}
})
Object.defineProperty(this, 'ySubscriptYSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySubscriptYSize) ? this.int_ySubscriptYSize : ((this.ascent - this.descent) * 0.7), 10)
},
set: function (value) {
this.int_ySubscriptYSize = value
}
})
Object.defineProperty(this, 'ySubscriptYOffset', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySubscriptYOffset) ? this.int_ySubscriptYOffset : ((this.ascent - this.descent) * 0.14), 10)
},
set: function (value) {
this.int_ySubscriptYOffset = value
}
})
Object.defineProperty(this, 'ySuperscriptXSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySuperscriptXSize) ? this.int_ySuperscriptXSize : (this.width * 0.6347), 10)
},
set: function (value) {
this.int_ySuperscriptXSize = value
}
})
Object.defineProperty(this, 'ySuperscriptYSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySuperscriptYSize) ? this.int_ySuperscriptYSize : ((this.ascent - this.descent) * 0.7), 10)
},
set: function (value) {
this.int_ySuperscriptYSize = value
}
})
Object.defineProperty(this, 'ySuperscriptYOffset', {
get: function () {
return parseInt(!_.isUndefined(this.int_ySuperscriptYOffset) ? this.int_ySuperscriptYOffset : ((this.ascent - this.descent) * 0.48), 10)
},
set: function (value) {
this.int_ySuperscriptYOffset = value
}
})
Object.defineProperty(this, 'yStrikeoutSize', {
get: function () {
return parseInt(!_.isUndefined(this.int_yStrikeoutSize) ? this.int_yStrikeoutSize : ((this.ascent - this.descent) * 0.049), 10)
},
set: function (value) {
this.int_yStrikeoutSize = value
}
})
Object.defineProperty(this, 'yStrikeoutPosition', {
get: function () {
return parseInt(!_.isUndefined(this.int_yStrikeoutPosition) ? this.int_yStrikeoutPosition : ((this.ascent - this.descent) * 0.258), 10)
},
set: function (value) {
this.int_yStrikeoutPosition = value
}
})
Object.defineProperty(this, 'minLsb', {
get: function () {
return parseInt(_.min(_.map(this.glyphs, 'xMin')), 10)
}
})
Object.defineProperty(this, 'minRsb', {
get: function () {
if (!this.glyphs.length) return parseInt(this.width, 10)
return parseInt(_.reduce(this.glyphs, function (minRsb, glyph) {
return Math.min(minRsb, glyph.width - glyph.xMax)
}, 0), 10)
}
})
Object.defineProperty(this, 'xMin', {
get: function () {
if (!this.glyphs.length) return this.width
return _.reduce(this.glyphs, function (xMin, glyph) {
return Math.min(xMin, glyph.xMin)
}, 0)
}
})
Object.defineProperty(this, 'yMin', {
get: function () {
if (!this.glyphs.length) return this.width
return _.reduce(this.glyphs, function (yMin, glyph) {
return Math.min(yMin, glyph.yMin)
}, 0)
}
})
Object.defineProperty(this, 'xMax', {
get: function () {
if (!this.glyphs.length) return this.width
return _.reduce(this.glyphs, function (xMax, glyph) {
return Math.max(xMax, glyph.xMax)
}, 0)
}
})
Object.defineProperty(this, 'yMax', {
get: function () {
if (!this.glyphs.length) return this.width
return _.reduce(this.glyphs, function (yMax, glyph) {
return Math.max(yMax, glyph.yMax)
}, 0)
}
})
Object.defineProperty(this, 'avgWidth', {
get: function () {
var len = this.glyphs.length
if (len === 0) {
return this.width
}
var sumWidth = _.reduce(this.glyphs, function (sumWidth, glyph) {
return sumWidth + glyph.width
}, 0)
return Math.round(sumWidth / len)
}
})
Object.defineProperty(this, 'maxWidth', {
get: function () {
if (!this.glyphs.length) return this.width
return _.reduce(this.glyphs, function (maxWidth, glyph) {
return Math.max(maxWidth, glyph.width)
}, 0)
}
})
Object.defineProperty(this, 'maxExtent', {
get: function () {
if (!this.glyphs.length) return this.width
return _.reduce(this.glyphs, function (maxExtent, glyph) {
return Math.max(maxExtent, glyph.xMax /* - glyph.xMin */)
}, 0)
}
})
// Property used for `sTypoLineGap` in OS/2 and not used for `lineGap` in HHEA, because
// non zero lineGap causes bad offset in IE, https://github.com/fontello/svg2ttf/issues/37
Object.defineProperty(this, 'lineGap', {
get: function () {
return parseInt(!_.isUndefined(this.int_lineGap) ? this.int_lineGap : ((this.ascent - this.descent) * 0.09), 10)
},
set: function (value) {
this.int_lineGap = value
}
})
Object.defineProperty(this, 'underlinePosition', {
get: function () {
return parseInt(!_.isUndefined(this.int_underlinePosition) ? this.int_underlinePosition : ((this.ascent - this.descent) * 0.01), 10)
},
set: function (value) {
this.int_underlinePosition = value
}
})
}
function Glyph () {
this.contours = []
this.d = ''
this.id = ''
this.codes = [] // needed for nice validator error output
this.height = 0
this.name = ''
this.width = 0
}
Object.defineProperty(Glyph.prototype, 'xMin', {
get: function () {
var xMin = 0
var hasPoints = false
_.forEach(this.contours, function (contour) {
_.forEach(contour.points, function (point) {
xMin = Math.min(xMin, Math.floor(point.x))
hasPoints = true
})
})
if (xMin < -32768) {
throw new Error('xMin value for glyph ' + (this.name ? ('"' + this.name + '"') : JSON.stringify(this.codes)) +
' is out of bounds (actual ' + xMin + ', expected -32768..32767, d="' + this.d + '")')
}
return hasPoints ? xMin : 0
}
})
Object.defineProperty(Glyph.prototype, 'xMax', {
get: function () {
var xMax = 0
var hasPoints = false
_.forEach(this.contours, function (contour) {
_.forEach(contour.points, function (point) {
xMax = Math.max(xMax, -Math.floor(-point.x))
hasPoints = true
})
})
if (xMax > 32767) {
throw new Error('xMax value for glyph ' + (this.name ? ('"' + this.name + '"') : JSON.stringify(this.codes)) +
' is out of bounds (actual ' + xMax + ', expected -32768..32767, d="' + this.d + '")')
}
return hasPoints ? xMax : this.width
}
})
Object.defineProperty(Glyph.prototype, 'yMin', {
get: function () {
var yMin = 0
var hasPoints = false
_.forEach(this.contours, function (contour) {
_.forEach(contour.points, function (point) {
yMin = Math.min(yMin, Math.floor(point.y))
hasPoints = true
})
})
if (yMin < -32768) {
throw new Error('yMin value for glyph ' + (this.name ? ('"' + this.name + '"') : JSON.stringify(this.codes)) +
' is out of bounds (actual ' + yMin + ', expected -32768..32767, d="' + this.d + '")')
}
return hasPoints ? yMin : 0
}
})
Object.defineProperty(Glyph.prototype, 'yMax', {
get: function () {
var yMax = 0
var hasPoints = false
_.forEach(this.contours, function (contour) {
_.forEach(contour.points, function (point) {
yMax = Math.max(yMax, -Math.floor(-point.y))
hasPoints = true
})
})
if (yMax > 32767) {
throw new Error('yMax value for glyph ' + (this.name ? ('"' + this.name + '"') : JSON.stringify(this.codes)) +
' is out of bounds (actual ' + yMax + ', expected -32768..32767, d="' + this.d + '")')
}
return hasPoints ? yMax : 0
}
})
function Contour () {
this.points = []
}
function Point () {
this.onCurve = true
this.x = 0
this.y = 0
}
function SfntName () {
this.id = 0
this.value = ''
}
module.exports.Font = Font
module.exports.Glyph = Glyph
module.exports.Contour = Contour
module.exports.Point = Point
module.exports.SfntName = SfntName
module.exports.toTTF = require('./ttf')