-
Notifications
You must be signed in to change notification settings - Fork 204
/
Copy pathfont.go
335 lines (289 loc) · 7.64 KB
/
font.go
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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
// Copyright ©2021 The Gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package font
import (
"errors"
"fmt"
"sync"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/math/fixed"
)
// DefaultCache is the global cache for fonts.
var DefaultCache *Cache = NewCache(nil)
// Font represents a font face.
type Font struct {
// Typeface identifies the Font.
Typeface Typeface
// TODO(sbinet): [email protected] has dropped font.Font.Variant
// we should probably follow suit.
// Variant is the variant of a font, such as "Mono" or "Smallcaps".
Variant Variant
// Style is the style of a font, such as Regular or Italic.
Style font.Style
// Weight is the weight of a font, such as Normal or Bold.
Weight font.Weight
// Size is the size of the font.
Size Length
}
// Name returns a fully qualified name for the given font.
func (f *Font) Name() string {
v := f.Variant
w := weightName(f.Weight)
s := styleName(f.Style)
switch f.Style {
case font.StyleNormal:
s = ""
if f.Weight == font.WeightNormal {
w = "Regular"
}
default:
if f.Weight == font.WeightNormal {
w = ""
}
}
return fmt.Sprintf("%s%s-%s%s", f.Typeface, v, w, s)
}
// From returns a copy of the provided font with its size set.
func From(fnt Font, size Length) Font {
o := fnt
o.Size = size
return o
}
// Typeface identifies a particular typeface design.
// The empty string denotes the default typeface.
type Typeface string
// Variant denotes a typeface variant, such as "Mono", "Smallcaps" or "Math".
type Variant string
// Extents contains font metric information.
type Extents struct {
// Ascent is the distance that the text
// extends above the baseline.
Ascent Length
// Descent is the distance that the text
// extends below the baseline. The descent
// is given as a positive value.
Descent Length
// Height is the distance from the lowest
// descending point to the highest ascending
// point.
Height Length
}
// Face holds a font descriptor and the associated font face.
type Face struct {
Font Font
Face *opentype.Font
}
// Name returns a fully qualified name for the given font.
func (f *Face) Name() string {
return f.Font.Name()
}
// FontFace returns the opentype font face for the requested
// dots-per-inch resolution.
func (f *Face) FontFace(dpi float64) font.Face {
face, err := opentype.NewFace(f.Face, &opentype.FaceOptions{
Size: f.Font.Size.Points(),
DPI: dpi,
})
if err != nil {
panic(err)
}
return face
}
// default hinting for OpenType fonts
const defaultHinting = font.HintingNone
// Extents returns the FontExtents for a font.
func (f *Face) Extents() Extents {
var (
// TODO(sbinet): re-use a Font-level sfnt.Buffer instead?
buf sfnt.Buffer
ppem = fixed.Int26_6(f.Face.UnitsPerEm())
)
met, err := f.Face.Metrics(&buf, ppem, defaultHinting)
if err != nil {
panic(fmt.Errorf("could not extract font extents: %v", err))
}
scale := f.Font.Size / Points(float64(ppem))
return Extents{
Ascent: Points(float64(met.Ascent)) * scale,
Descent: Points(float64(met.Descent)) * scale,
Height: Points(float64(met.Height)) * scale,
}
}
// Width returns width of a string when drawn using the font.
func (f *Face) Width(s string) Length {
var (
pixelsPerEm = fixed.Int26_6(f.Face.UnitsPerEm())
// scale converts sfnt.Unit to float64
scale = f.Font.Size / Points(float64(pixelsPerEm))
width = 0
hasPrev = false
buf sfnt.Buffer
prev, idx sfnt.GlyphIndex
hinting = defaultHinting
)
for _, rune := range s {
var err error
idx, err = f.Face.GlyphIndex(&buf, rune)
if err != nil {
panic(fmt.Errorf("could not get glyph index: %v", err))
}
if hasPrev {
kern, err := f.Face.Kern(&buf, prev, idx, pixelsPerEm, hinting)
switch {
case err == nil:
width += int(kern)
case errors.Is(err, sfnt.ErrNotFound):
// no-op
default:
panic(fmt.Errorf("could not get kerning: %v", err))
}
}
adv, err := f.Face.GlyphAdvance(&buf, idx, pixelsPerEm, hinting)
if err != nil {
panic(fmt.Errorf("could not retrieve glyph's advance: %v", err))
}
width += int(adv)
prev, hasPrev = idx, true
}
return Points(float64(width)) * scale
}
// Collection is a collection of fonts, regrouped under a common typeface.
type Collection []Face
// Cache collects font faces.
type Cache struct {
mu sync.RWMutex
def Typeface
faces map[Font]*opentype.Font
}
// We make Cache implement dummy GobDecoder and GobEncoder interfaces
// to allow plot.Plot (or any other type holding a Cache) to be (de)serialized
// with encoding/gob.
// As Cache holds opentype.Font, the reflect-based gob (de)serialization can not
// work: gob isn't happy with opentype.Font having no exported field:
//
// error: gob: type font.Cache has no exported fields
//
// FIXME(sbinet): perhaps encode/decode Cache.def typeface?
func (c *Cache) GobEncode() ([]byte, error) { return nil, nil }
func (c *Cache) GobDecode([]byte) error {
if c.faces == nil {
c.faces = make(map[Font]*opentype.Font)
}
return nil
}
// NewCache creates a new cache of fonts from the provided collection of
// font Faces.
// The first font Face in the collection is set to be the default one.
func NewCache(coll Collection) *Cache {
cache := &Cache{
faces: make(map[Font]*opentype.Font, len(coll)),
}
cache.Add(coll)
return cache
}
// Add adds a whole collection of font Faces to the font cache.
// If the cache is empty, the first font Face in the collection is set
// to be the default one.
func (c *Cache) Add(coll Collection) {
c.mu.Lock()
defer c.mu.Unlock()
if c.faces == nil {
c.faces = make(map[Font]*opentype.Font, len(coll))
}
for i, f := range coll {
if i == 0 && c.def == "" {
c.def = f.Font.Typeface
}
fnt := f.Font
fnt.Size = 0 // store all font descriptors with the same size.
c.faces[fnt] = f.Face
}
}
// Lookup returns the font Face corresponding to the provided Font descriptor,
// with the provided font size set.
//
// If no matching font Face could be found, the one corresponding to
// the default typeface is selected and returned.
func (c *Cache) Lookup(fnt Font, size Length) Face {
c.mu.RLock()
defer c.mu.RUnlock()
if len(c.faces) == 0 {
return Face{}
}
face := c.lookup(fnt)
if face == nil {
fnt.Typeface = c.def
face = c.lookup(fnt)
}
ff := Face{
Font: fnt,
Face: face,
}
ff.Font.Size = size
return ff
}
// Has returns whether the cache contains the exact font descriptor.
func (c *Cache) Has(fnt Font) bool {
c.mu.RLock()
defer c.mu.RUnlock()
face := c.lookup(fnt)
return face != nil
}
func (c *Cache) lookup(key Font) *opentype.Font {
key.Size = 0
tf := c.faces[key]
if tf == nil {
key := key
key.Weight = font.WeightNormal
tf = c.faces[key]
}
if tf == nil {
key := key
key.Style = font.StyleNormal
tf = c.faces[key]
}
if tf == nil {
key := key
key.Style = font.StyleNormal
key.Weight = font.WeightNormal
tf = c.faces[key]
}
return tf
}
func weightName(w font.Weight) string {
switch w {
case font.WeightThin:
return "Thin"
case font.WeightExtraLight:
return "ExtraLight"
case font.WeightLight:
return "Light"
case font.WeightNormal:
return "Regular"
case font.WeightMedium:
return "Medium"
case font.WeightSemiBold:
return "SemiBold"
case font.WeightBold:
return "Bold"
case font.WeightExtraBold:
return "ExtraBold"
case font.WeightBlack:
return "Black"
}
return fmt.Sprintf("weight(%d)", w)
}
func styleName(sty font.Style) string {
switch sty {
case font.StyleNormal:
return "Normal"
case font.StyleItalic:
return "Italic"
case font.StyleOblique:
return "Oblique"
}
return fmt.Sprintf("style(%d)", sty)
}