1
2
3
4
5 package font
6
7 import (
8 "errors"
9 "fmt"
10 "sync"
11
12 "golang.org/x/image/font"
13 "golang.org/x/image/font/opentype"
14 "golang.org/x/image/font/sfnt"
15 "golang.org/x/image/math/fixed"
16 )
17
18
19 var DefaultCache *Cache = NewCache(nil)
20
21
22 type Font struct {
23
24 Typeface Typeface
25
26
27
28
29
30 Variant Variant
31
32
33 Style font.Style
34
35
36 Weight font.Weight
37
38
39 Size Length
40 }
41
42
43 func (f *Font) Name() string {
44 v := f.Variant
45 w := weightName(f.Weight)
46 s := styleName(f.Style)
47
48 switch f.Style {
49 case font.StyleNormal:
50 s = ""
51 if f.Weight == font.WeightNormal {
52 w = "Regular"
53 }
54 default:
55 if f.Weight == font.WeightNormal {
56 w = ""
57 }
58 }
59
60 return fmt.Sprintf("%s%s-%s%s", f.Typeface, v, w, s)
61 }
62
63
64 func From(fnt Font, size Length) Font {
65 o := fnt
66 o.Size = size
67 return o
68 }
69
70
71
72 type Typeface string
73
74
75 type Variant string
76
77
78 type Extents struct {
79
80
81 Ascent Length
82
83
84
85
86 Descent Length
87
88
89
90
91 Height Length
92 }
93
94
95 type Face struct {
96 Font Font
97 Face *opentype.Font
98 }
99
100
101 func (f *Face) Name() string {
102 return f.Font.Name()
103 }
104
105
106
107 func (f *Face) FontFace(dpi float64) font.Face {
108 face, err := opentype.NewFace(f.Face, &opentype.FaceOptions{
109 Size: f.Font.Size.Points(),
110 DPI: dpi,
111 })
112 if err != nil {
113 panic(err)
114 }
115 return face
116 }
117
118
119 const defaultHinting = font.HintingNone
120
121
122 func (f *Face) Extents() Extents {
123 var (
124
125 buf sfnt.Buffer
126 ppem = fixed.Int26_6(f.Face.UnitsPerEm())
127 )
128
129 met, err := f.Face.Metrics(&buf, ppem, defaultHinting)
130 if err != nil {
131 panic(fmt.Errorf("could not extract font extents: %v", err))
132 }
133 scale := f.Font.Size / Points(float64(ppem))
134 return Extents{
135 Ascent: Points(float64(met.Ascent)) * scale,
136 Descent: Points(float64(met.Descent)) * scale,
137 Height: Points(float64(met.Height)) * scale,
138 }
139 }
140
141
142 func (f *Face) Width(s string) Length {
143 var (
144 pixelsPerEm = fixed.Int26_6(f.Face.UnitsPerEm())
145
146
147 scale = f.Font.Size / Points(float64(pixelsPerEm))
148
149 width = 0
150 hasPrev = false
151 buf sfnt.Buffer
152 prev, idx sfnt.GlyphIndex
153 hinting = defaultHinting
154 )
155 for _, rune := range s {
156 var err error
157 idx, err = f.Face.GlyphIndex(&buf, rune)
158 if err != nil {
159 panic(fmt.Errorf("could not get glyph index: %v", err))
160 }
161 if hasPrev {
162 kern, err := f.Face.Kern(&buf, prev, idx, pixelsPerEm, hinting)
163 switch {
164 case err == nil:
165 width += int(kern)
166 case errors.Is(err, sfnt.ErrNotFound):
167
168 default:
169 panic(fmt.Errorf("could not get kerning: %v", err))
170 }
171 }
172 adv, err := f.Face.GlyphAdvance(&buf, idx, pixelsPerEm, hinting)
173 if err != nil {
174 panic(fmt.Errorf("could not retrieve glyph's advance: %v", err))
175 }
176 width += int(adv)
177 prev, hasPrev = idx, true
178 }
179 return Points(float64(width)) * scale
180 }
181
182
183 type Collection []Face
184
185
186 type Cache struct {
187 mu sync.RWMutex
188 def Typeface
189 faces map[Font]*opentype.Font
190 }
191
192
193
194
195
196
197
198
199
200
201
202 func (c *Cache) GobEncode() ([]byte, error) { return nil, nil }
203 func (c *Cache) GobDecode([]byte) error {
204 if c.faces == nil {
205 c.faces = make(map[Font]*opentype.Font)
206 }
207 return nil
208 }
209
210
211
212
213 func NewCache(coll Collection) *Cache {
214 cache := &Cache{
215 faces: make(map[Font]*opentype.Font, len(coll)),
216 }
217 cache.Add(coll)
218 return cache
219 }
220
221
222
223
224 func (c *Cache) Add(coll Collection) {
225 c.mu.Lock()
226 defer c.mu.Unlock()
227
228 if c.faces == nil {
229 c.faces = make(map[Font]*opentype.Font, len(coll))
230 }
231 for i, f := range coll {
232 if i == 0 && c.def == "" {
233 c.def = f.Font.Typeface
234 }
235 fnt := f.Font
236 fnt.Size = 0
237 c.faces[fnt] = f.Face
238 }
239 }
240
241
242
243
244
245
246 func (c *Cache) Lookup(fnt Font, size Length) Face {
247 c.mu.RLock()
248 defer c.mu.RUnlock()
249
250 if len(c.faces) == 0 {
251 return Face{}
252 }
253
254 face := c.lookup(fnt)
255 if face == nil {
256 fnt.Typeface = c.def
257 face = c.lookup(fnt)
258 }
259
260 ff := Face{
261 Font: fnt,
262 Face: face,
263 }
264 ff.Font.Size = size
265 return ff
266 }
267
268
269 func (c *Cache) Has(fnt Font) bool {
270 c.mu.RLock()
271 defer c.mu.RUnlock()
272
273 face := c.lookup(fnt)
274 return face != nil
275 }
276
277 func (c *Cache) lookup(key Font) *opentype.Font {
278 key.Size = 0
279
280 tf := c.faces[key]
281 if tf == nil {
282 key := key
283 key.Weight = font.WeightNormal
284 tf = c.faces[key]
285 }
286 if tf == nil {
287 key := key
288 key.Style = font.StyleNormal
289 tf = c.faces[key]
290 }
291 if tf == nil {
292 key := key
293 key.Style = font.StyleNormal
294 key.Weight = font.WeightNormal
295 tf = c.faces[key]
296 }
297
298 return tf
299 }
300
301 func weightName(w font.Weight) string {
302 switch w {
303 case font.WeightThin:
304 return "Thin"
305 case font.WeightExtraLight:
306 return "ExtraLight"
307 case font.WeightLight:
308 return "Light"
309 case font.WeightNormal:
310 return "Regular"
311 case font.WeightMedium:
312 return "Medium"
313 case font.WeightSemiBold:
314 return "SemiBold"
315 case font.WeightBold:
316 return "Bold"
317 case font.WeightExtraBold:
318 return "ExtraBold"
319 case font.WeightBlack:
320 return "Black"
321 }
322 return fmt.Sprintf("weight(%d)", w)
323 }
324
325 func styleName(sty font.Style) string {
326 switch sty {
327 case font.StyleNormal:
328 return "Normal"
329 case font.StyleItalic:
330 return "Italic"
331 case font.StyleOblique:
332 return "Oblique"
333 }
334 return fmt.Sprintf("style(%d)", sty)
335 }
336
View as plain text