-
Notifications
You must be signed in to change notification settings - Fork 202
/
font.go
261 lines (222 loc) · 7.29 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
// Copyright ©2015 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.
// Some of this code (namely the code for computing the
// width of a string in a given font) was copied from
// github.com/golang/freetype/ which includes
// the following copyright notice:
// Copyright 2010 The Freetype-Go Authors. All rights reserved.
package vg
import (
"errors"
"io/ioutil"
"path/filepath"
"sync"
"gonum.org/v1/plot/vg/fonts"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
"github.com/golang/freetype/truetype"
)
var (
// FontMap maps Postscript/PDF font names to compatible
// free fonts (TrueType converted ghostscript fonts).
// Fonts that are not keys of this map are not supported.
FontMap = map[string]string{
// We use fonts from RedHat's Liberation project:
// https://fedorahosted.org/liberation-fonts/
"Courier": "LiberationMono-Regular",
"Courier-Bold": "LiberationMono-Bold",
"Courier-Oblique": "LiberationMono-Italic",
"Courier-BoldOblique": "LiberationMono-BoldItalic",
"Helvetica": "LiberationSans-Regular",
"Helvetica-Bold": "LiberationSans-Bold",
"Helvetica-Oblique": "LiberationSans-Italic",
"Helvetica-BoldOblique": "LiberationSans-BoldItalic",
"Times-Roman": "LiberationSerif-Regular",
"Times-Bold": "LiberationSerif-Bold",
"Times-Italic": "LiberationSerif-Italic",
"Times-BoldItalic": "LiberationSerif-BoldItalic",
}
// loadedFonts is indexed by a font name and it
// caches the associated *truetype.Font.
loadedFonts = make(map[string]*truetype.Font)
// FontLock protects access to the loadedFonts map.
fontLock sync.RWMutex
)
// A Font represents one of the supported font
// faces.
type Font struct {
// Size is the size of the font. The font size can
// be used as a reasonable value for the vertical
// distance between two successive lines of text.
Size Length
// name is the name of this font.
name string
// This is a little bit of a hack, but the truetype
// font is currently only needed to determine the
// dimensions of strings drawn in this font.
// The actual drawing of the strings is handled
// separately by different back-ends:
// Both Postscript and PDF are capable of drawing
// their own fonts and draw2d loads its own copy of
// the truetype fonts for its own output.
//
// This isn't a necessity--some future backend is
// free to use this field--however it is a consequence
// of the fact that the current backends were
// developed independently of this package.
// font is the truetype font pointer for this
// font.
font *truetype.Font
}
// MakeFont returns a font object. The name of the font must
// be a key of the FontMap. The font file is located by searching
// the FontDirs slice for a directory containing the relevant font
// file. The font file name is name mapped by FontMap with the
// .ttf extension. For example, the font file for the font name
// Courier is LiberationMono-Regular.ttf.
func MakeFont(name string, size Length) (font Font, err error) {
font.Size = size
font.name = name
font.font, err = getFont(name)
return
}
// Name returns the name of the font.
func (f *Font) Name() string {
return f.name
}
// Font returns the corresponding truetype.Font.
func (f *Font) Font() *truetype.Font {
return f.font
}
func (f *Font) FontFace(dpi float64) font.Face {
return truetype.NewFace(f.font, &truetype.Options{
Size: f.Size.Points(),
DPI: dpi,
})
}
// SetName sets the name of the font, effectively
// changing the font. If an error is returned then
// the font is left unchanged.
func (f *Font) SetName(name string) error {
font, err := getFont(name)
if err != nil {
return err
}
f.name = name
f.font = font
return nil
}
// FontExtents contains font metric information.
type FontExtents 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 negative value.
Descent Length
// Height is the distance from the lowest
// descending point to the highest ascending
// point.
Height Length
}
// Extents returns the FontExtents for a font.
func (f *Font) Extents() FontExtents {
bounds := f.font.Bounds(fixed.Int26_6(f.Font().FUnitsPerEm()))
scale := f.Size / Points(float64(f.Font().FUnitsPerEm()))
return FontExtents{
Ascent: Points(float64(bounds.Max.Y)) * scale,
Descent: Points(float64(bounds.Min.Y)) * scale,
Height: Points(float64(bounds.Max.Y-bounds.Min.Y)) * scale,
}
}
// Width returns width of a string when drawn using the font.
func (f *Font) Width(s string) Length {
// scale converts truetype.FUnit to float64
scale := f.Size / Points(float64(f.font.FUnitsPerEm()))
width := 0
prev, hasPrev := truetype.Index(0), false
for _, rune := range s {
index := f.font.Index(rune)
if hasPrev {
width += int(f.font.Kern(fixed.Int26_6(f.font.FUnitsPerEm()), prev, index))
}
width += int(f.font.HMetric(fixed.Int26_6(f.font.FUnitsPerEm()), index).AdvanceWidth)
prev, hasPrev = index, true
}
return Points(float64(width)) * scale
}
// AddFont associates a truetype.Font with the given name.
func AddFont(name string, font *truetype.Font) {
fontLock.Lock()
loadedFonts[name] = font
fontLock.Unlock()
}
// getFont returns the truetype.Font for the given font name or an error.
func getFont(name string) (*truetype.Font, error) {
fontLock.RLock()
f, ok := loadedFonts[name]
fontLock.RUnlock()
if ok {
return f, nil
}
bytes, err := fontData(name)
if err != nil {
return nil, err
}
font, err := truetype.Parse(bytes)
if err == nil {
fontLock.Lock()
loadedFonts[name] = font
fontLock.Unlock()
} else {
err = errors.New("Failed to parse font file: " + err.Error())
}
return font, err
}
// fontData returns the []byte data for a font name or an error if it is not found.
func fontData(name string) ([]byte, error) {
fname, err := fontFile(name)
if err != nil {
return nil, err
}
for _, d := range FontDirs {
p := filepath.Join(d, fname)
data, err := ioutil.ReadFile(p)
if err != nil {
continue
}
return data, nil
}
data, err := fonts.Asset(fname)
if err == nil {
return data, nil
}
return nil, errors.New("vg: failed to locate a font file " + fname + " for font name " + name)
}
// FontDirs is a slice of directories searched for font data files.
// If the first font file found is unreadable or cannot be parsed, then
// subsequent directories are not tried, and the font will fail to load.
//
// The default slice contains, in the following order, the values of the
// environment variable VGFONTPATH if it is defined, then the vg
// source fonts directory if it is found (i.e., if vg was installed by
// go get). If the resulting FontDirs slice is empty then the current
// directory is added to it. This slice may be changed to load fonts
// from different locations.
var FontDirs []string
// FontFile returns the font file name for a font name or an error
// if it is an unknown font (i.e., not in the FontMap).
func fontFile(name string) (string, error) {
var err error
n, ok := FontMap[name]
if !ok {
errStr := "Unknown font: " + name + ". Available fonts are:"
for n := range FontMap {
errStr += " " + n
}
err = errors.New(errStr)
}
return n + ".ttf", err
}