-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathvariations.go
188 lines (158 loc) · 5.6 KB
/
variations.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
package truetype
var _ FaceVariable = (*Font)(nil)
// FaceVariable is an extension interface supporting Opentype variable fonts.
// See the `Variations` method to check if a font is actually variable.
type FaceVariable interface {
// Variations returns the variations for the font,
// or an empty table for non-variable fonts.
Variations() TableFvar
// SetVarCoordinates apply the normalized coordinates values.
// Use `NormalizeVariations` to convert from design space units.
// See also `SetVariations`.
SetVarCoordinates(coords []float32)
// VarCoordinates returns the current variable coordinates,
// in normalized units.
VarCoordinates() []float32
// NormalizeVariations should normalize the given design-space coordinates. The minimum and maximum
// values for the axis are mapped to the interval [-1,1], with the default
// axis value mapped to 0.
// This should be a no-op for non-variable fonts.
NormalizeVariations(coords []float32) []float32
}
// SetVariations applies a list of font-variation settings to a font,
// defaulting to the values given in the `fvar` table.
// Note that passing an empty slice will instead remove the coordinates.
func SetVariations(face FaceVariable, variations []Variation) {
if len(variations) == 0 {
face.SetVarCoordinates(nil)
return
}
fvar := face.Variations()
if len(fvar.Axis) == 0 {
face.SetVarCoordinates(nil)
return
}
designCoords := fvar.GetDesignCoordsDefault(variations)
face.SetVarCoordinates(face.NormalizeVariations(designCoords))
}
func (font *Font) SetVarCoordinates(coords []float32) {
font.varCoords = coords
}
func (font *Font) VarCoordinates() []float32 { return font.varCoords }
// Variation defines a value for a wanted variation axis.
type Variation struct {
Tag Tag // variation-axis identifier tag
Value float32 // in design units
}
type VarInstance struct {
Coords []float32 // in design units; length: number of axis
Subfamily NameID
PSStringID NameID
}
type TableFvar struct {
Axis []VarAxis
Instances []VarInstance // contains the default instance
}
// IsDefaultInstance returns `true` is `instance` has the same
// coordinates as the default instance.
func (fvar TableFvar) IsDefaultInstance(it VarInstance) bool {
for i, c := range it.Coords {
if c != fvar.Axis[i].Default {
return false
}
}
return true
}
// add the default instance if it not already explicitely present
func (fvar *TableFvar) checkDefaultInstance(names TableName) {
for _, instance := range fvar.Instances {
if fvar.IsDefaultInstance(instance) {
return
}
}
// add the default instance
// choose the subfamily entry
subFamily := NamePreferredSubfamily
if v1, v2 := names.getEntry(subFamily); v1 == nil && v2 == nil {
subFamily = NameFontSubfamily
}
defaultInstance := VarInstance{
Coords: make([]float32, len(fvar.Axis)),
Subfamily: subFamily,
PSStringID: NamePostscript,
}
for i, axe := range fvar.Axis {
defaultInstance.Coords[i] = axe.Default
}
fvar.Instances = append(fvar.Instances, defaultInstance)
}
// GetDesignCoordsDefault returns the design coordinates corresponding to the given pairs of axis/value.
// The default value of the axis is used when not specified in the variations.
func (fvar *TableFvar) GetDesignCoordsDefault(variations []Variation) []float32 {
designCoords := make([]float32, len(fvar.Axis))
// start with default values
for i, axis := range fvar.Axis {
designCoords[i] = axis.Default
}
fvar.GetDesignCoords(variations, designCoords)
return designCoords
}
// GetDesignCoords updates the design coordinates, with the given pairs of axis/value.
// It will panic if `designCoords` has not the length expected by the table, that is the number of axis.
func (fvar *TableFvar) GetDesignCoords(variations []Variation, designCoords []float32) {
for _, variation := range variations {
// allow for multiple axis with the same tag
for index, axis := range fvar.Axis {
if axis.Tag == variation.Tag {
designCoords[index] = variation.Value
}
}
}
}
// normalize based on the [min,def,max] values for the axis to be [-1,0,1].
func (fvar *TableFvar) normalizeCoordinates(coords []float32) []float32 {
normalized := make([]float32, len(coords))
for i, a := range fvar.Axis {
coord := coords[i]
// out of range: clamping
if coord > a.Maximum {
coord = a.Maximum
} else if coord < a.Minimum {
coord = a.Minimum
}
if coord < a.Default {
normalized[i] = -(coord - a.Default) / (a.Minimum - a.Default)
} else if coord > a.Default {
normalized[i] = (coord - a.Default) / (a.Maximum - a.Default)
} else {
normalized[i] = 0
}
}
return normalized
}
func (f *Font) Variations() TableFvar { return f.fvar }
// Normalizes the given design-space coordinates. The minimum and maximum
// values for the axis are mapped to the interval [-1,1], with the default
// axis value mapped to 0.
// Any additional scaling defined in the face's `avar` table is also
// applied, as described at https://docs.microsoft.com/en-us/typography/opentype/spec/avar
func (f *Font) NormalizeVariations(coords []float32) []float32 {
// ported from freetype2
// Axis normalization is a two-stage process. First we normalize
// based on the [min,def,max] values for the axis to be [-1,0,1].
// Then, if there's an `avar' table, we renormalize this range.
normalized := f.fvar.normalizeCoordinates(coords)
// now applying 'avar'
for i, av := range f.avar {
for j := 1; j < len(av); j++ {
previous, pair := av[j-1], av[j]
if normalized[i] < pair.from {
normalized[i] =
previous.to + (normalized[i]-previous.from)*
(pair.to-previous.to)/(pair.from-previous.from)
break
}
}
}
return normalized
}