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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246 | 1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
41
55
55
55
55
859
55
4
1
1088
191
191
191
191
1088
168
168
168
168
51
51
188
1
22
22
22
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
3
1
1
1
1
1
2
2
16
16
311
311
45
45
45
45
296
296
7
7
7
7
296
45
23
23
1
2
2
282
279
220
59
59
282
680
279
1
278
1
277
680
6
3
3
1
1
1
1
2
1
1
4
4
4
40
40
80
40
4
4
4
3
4
4
8
7
1
4
4
4
4
4
1
| r = require 'restructure'
Directory = require './tables/directory'
tables = require './tables'
CmapProcessor = require './CmapProcessor'
LayoutEngine = require './layout/LayoutEngine'
TTFGlyph = require './glyph/TTFGlyph'
CFFGlyph = require './glyph/CFFGlyph'
SBIXGlyph = require './glyph/SBIXGlyph'
COLRGlyph = require './glyph/COLRGlyph'
GlyphVariationProcessor = require './glyph/GlyphVariationProcessor'
TTFSubset = require './subset/TTFSubset'
CFFSubset = require './subset/CFFSubset'
BBox = require './glyph/BBox'
class TTFFont
get = require('./get')(this)
@probe: (buffer) ->
return buffer.toString('ascii', 0, 4) in ['true', 'OTTO', String.fromCharCode(0, 1, 0, 0)]
constructor: (@stream, variationCoords = null) ->
@_tables = {}
@_glyphs = {}
@_decodeDirectory()
# define properties for each table to lazily parse
for tag, table of @directory.tables when tables[tag]
Object.defineProperty this, tag,
get: getTable.bind(this, table)
if variationCoords
@_variationProcessor = new GlyphVariationProcessor this, variationCoords
getTable = (table) ->
unless table.tag of @_tables
pos = @stream.pos
@stream.pos = table.offset
@_tables[table.tag] = @_decodeTable table
@stream.pos = pos
return @_tables[table.tag]
_getTableStream: (tag) ->
table = @directory.tables[tag]
Eif table
@stream.pos = table.offset
return @stream
return null
_decodeDirectory: ->
@_directoryPos = @stream.pos
@directory = Directory.decode(@stream, _startOffset: 0)
_decodeTable: (table) ->
return tables[table.tag].decode(@stream, this, table.length)
get 'postscriptName', ->
name = @name.records.postscriptName
lang = Object.keys(name)[0]
return name[lang]
get 'fullName', ->
@name.records.fullName?.English
get 'familyName', ->
@name.records.fontFamily?.English
get 'subfamilyName', ->
@name.records.fontSubfamily?.English
get 'copyright', ->
@name.records.copyright?.English
get 'version', ->
@name.records.version?.English
get 'ascent', ->
@hhea.ascent
get 'descent', ->
@hhea.descent
get 'lineGap', ->
@hhea.lineGap
get 'underlinePosition', ->
@post.underlinePosition
get 'underlineThickness', ->
@post.underlineThickness
get 'italicAngle', ->
@post.italicAngle
get 'capHeight', ->
this['OS/2']?.capHeight or @ascent
get 'xHeight', ->
this['OS/2']?.xHeight or 0
get 'numGlyphs', ->
@maxp.numGlyphs
get 'unitsPerEm', ->
@head.unitsPerEm
get 'bbox', ->
@_bbox ?= Object.freeze new BBox @head.xMin, @head.yMin, @head.xMax, @head.yMax
get 'characterSet', ->
@_cmapProcessor ?= new CmapProcessor @cmap
return @_cmapProcessor.getCharacterSet()
hasGlyphForCodePoint: (codePoint) ->
@_cmapProcessor ?= new CmapProcessor @cmap
return !!@_cmapProcessor.lookup codePoint
glyphForCodePoint: (codePoint) ->
@_cmapProcessor ?= new CmapProcessor @cmap
return @getGlyph @_cmapProcessor.lookup(codePoint), [codePoint]
glyphsForString: (string) ->
# Map character codes to glyph ids
glyphs = []
len = string.length
idx = 0
while idx < len
code = string.charCodeAt idx++
if 0xd800 <= code <= 0xdbff and idx < len
next = string.charCodeAt idx
Eif 0xdc00 <= next <= 0xdfff
idx++
code = ((code & 0x3FF) << 10) + (next & 0x3FF) + 0x10000
glyphs.push @glyphForCodePoint code
return glyphs
layout: (string, userFeatures, script, language) ->
@_layoutEngine ?= new LayoutEngine this
return @_layoutEngine.layout string, userFeatures, script, language
get 'availableFeatures', ->
@_layoutEngine ?= new LayoutEngine this
return @_layoutEngine.getAvailableFeatures()
widthOfString: (string, features, script, language) ->
@_layoutEngine ?= new LayoutEngine this
return @_layoutEngine.layout(string, features, script, language).advanceWidth
_getBaseGlyph: (glyph, characters = []) ->
unless @_glyphs[glyph]
if @directory.tables.glyf?
@_glyphs[glyph] = new TTFGlyph glyph, characters, this
else Eif @directory.tables['CFF ']?
@_glyphs[glyph] = new CFFGlyph glyph, characters, this
return @_glyphs[glyph] or null
getGlyph: (glyph, characters = []) ->
unless @_glyphs[glyph]
if @directory.tables.sbix?
@_glyphs[glyph] = new SBIXGlyph glyph, characters, this
else if @directory.tables.COLR? and @directory.tables.CPAL?
@_glyphs[glyph] = new COLRGlyph glyph, characters, this
else
@_getBaseGlyph glyph, characters
return @_glyphs[glyph] or null
createSubset: ->
if @directory.tables['CFF ']?
return new CFFSubset this
return new TTFSubset this
# Returns an object describing the available variation axes
# that this font supports. Keys are setting tags, and values
# contain the axis name, range, and default value.
get 'variationAxes', ->
res = {}
Ireturn res unless @fvar
for axis in @fvar.axis
res[axis.axisTag] =
name: axis.name
min: axis.minValue
default: axis.defaultValue
max: axis.maxValue
return res
# Returns an object describing the named variation instances
# that the font designer has specified. Keys are variation names
# and values are the variation settings for this instance.
get 'namedVariations', ->
res = {}
Ireturn res unless @fvar
for instance in @fvar.instance
settings = {}
for axis, i in @fvar.axis
settings[axis.axisTag] = instance.coord[i]
res[instance.name] = settings
return res
# Returns a new font with the given variation settings applied.
# Settings can either be an instance name, or an object containing
# variation tags as specified by the `variationAxes` property.
getVariation: (settings) ->
Iunless @directory.tables.fvar and @directory.tables.gvar and @directory.tables.glyf
throw new Error 'Variations require a font with the fvar, gvar, and glyf tables.'
if typeof settings is 'string'
settings = @namedVariations[settings]
Iif typeof settings isnt 'object'
throw new Error 'Variation settings must be either a variation name or settings object.'
# normalize the coordinates
coords = for axis, i in @fvar.axis
if axis.axisTag of settings
Math.max axis.minValue, Math.min axis.maxValue, settings[axis.axisTag]
else
axis.defaultValue
stream = new r.DecodeStream @stream.buffer
stream.pos = @_directoryPos
font = new TTFFont stream, coords
font._tables = @_tables
return font
# Standardized format plugin API
getFont: (name) ->
return @getVariation name
module.exports = TTFFont
|