@@ -3,6 +3,7 @@ package textsdf
3
3
import (
4
4
"errors"
5
5
"fmt"
6
+ "unicode"
6
7
7
8
"github.com/golang/freetype/truetype"
8
9
"github.com/soypat/glgl/math/ms2"
@@ -20,56 +21,83 @@ type Font struct {
20
21
ttf truetype.Font
21
22
gb truetype.GlyphBuf
22
23
// basicGlyphs optimized array access for common ASCII glyphs.
23
- basicGlyphs [lastBasic - firstBasic ]glyph
24
+ basicGlyphs [lastBasic - firstBasic + 1 ]glyph
24
25
// Other kinds of glyphs.
25
26
otherGlyphs map [rune ]glyph
26
27
bld gsdf.Builder
27
28
}
28
29
30
+ // LoadTTFBytes loads a TTF file blob into f. After calling Load the Font is ready to generate text SDFs.
29
31
func (f * Font ) LoadTTFBytes (ttf []byte ) error {
30
32
font , err := truetype .Parse (ttf )
31
33
if err != nil {
32
34
return err
33
35
}
34
- f .Reset ()
36
+ f .reset ()
35
37
f .ttf = * font
36
38
return nil
37
39
}
38
40
39
- func (f * Font ) Reset () {
41
+ // reset resets most internal state of Font without removing underlying assigned font.
42
+ func (f * Font ) reset () {
40
43
for i := range f .basicGlyphs {
41
44
f .basicGlyphs [i ] = glyph {}
42
45
}
43
- for k := range f .otherGlyphs {
44
- delete (f .otherGlyphs , k )
46
+ if f .otherGlyphs == nil {
47
+ f .otherGlyphs = make (map [rune ]glyph )
48
+ } else {
49
+ for k := range f .otherGlyphs {
50
+ delete (f .otherGlyphs , k )
51
+ }
45
52
}
46
53
}
47
54
48
55
type glyph struct {
49
56
sdf glbuild.Shader2D
50
57
}
51
58
59
+ // TextLine returns a single line of text with the set font.
60
+ // TextLine takes kerning and advance width into account for letter spacing.
61
+ // Glyph locations are set starting at x=0 and appended in positive x direction.
52
62
func (f * Font ) TextLine (s string ) (glbuild.Shader2D , error ) {
53
- if len (s ) == 0 {
54
- return nil , errors .New ("no text provided" )
55
- }
56
63
var shapes []glbuild.Shader2D
57
64
scale := f .scale ()
58
- var prevChar rune
59
- for i , c := range s {
65
+ var idxPrev truetype.Index
66
+ var xOfs float32
67
+ for ic , c := range s {
68
+ if ! unicode .IsGraphic (c ) {
69
+ return nil , fmt .Errorf ("char %q not graphic" , c )
70
+ }
71
+
72
+ idx := truetype .Index (c )
73
+ hm := f .ttf .HMetric (scale , idx )
74
+ if unicode .IsSpace (c ) {
75
+ if c == '\t' {
76
+ hm .AdvanceWidth *= 4
77
+ }
78
+ xOfs += float32 (hm .AdvanceWidth )
79
+ continue
80
+ }
60
81
charshape , err := f .Glyph (c )
61
82
if err != nil {
62
83
return nil , fmt .Errorf ("char %q: %w" , c , err )
63
84
}
64
- if i > 0 {
65
- kern := f .ttf .Kern (scale , truetype .Index (prevChar ), truetype .Index (c ))
66
- charshape = f .bld .Translate2D (charshape , float32 (kern ), 0 )
85
+
86
+ kern := f .ttf .Kern (scale , idxPrev , idx )
87
+ xOfs += float32 (kern )
88
+ idxPrev = idx
89
+ if ic == 0 {
90
+ xOfs += float32 (hm .LeftSideBearing )
67
91
}
92
+ charshape = f .bld .Translate2D (charshape , xOfs , 0 )
68
93
shapes = append (shapes , charshape )
69
- prevChar = c
94
+ xOfs += float32 ( hm . AdvanceWidth )
70
95
}
71
96
if len (shapes ) == 1 {
72
97
return shapes [0 ], nil
98
+ } else if len (shapes ) == 0 {
99
+ // Only whitespace.
100
+ return nil , errors .New ("no text provided" )
73
101
}
74
102
return f .bld .Union2D (shapes ... ), nil
75
103
}
@@ -79,7 +107,12 @@ func (f *Font) Kern(c0, c1 rune) float32 {
79
107
return float32 (f .ttf .Kern (f .scale (), truetype .Index (c0 ), truetype .Index (c1 )))
80
108
}
81
109
82
- // Glyph returns a SDF for a character.
110
+ // Kern returns the horizontal adjustment for the given glyph pair. A positive kern means to move the glyphs further apart.
111
+ func (f * Font ) AdvanceWidth (c rune ) float32 {
112
+ return float32 (f .ttf .HMetric (f .scale (), truetype .Index (c )).AdvanceWidth )
113
+ }
114
+
115
+ // Glyph returns a SDF for a character defined by the argument rune.
83
116
func (f * Font ) Glyph (c rune ) (_ glbuild.Shader2D , err error ) {
84
117
var g glyph
85
118
if c >= firstBasic && c <= lastBasic {
@@ -113,9 +146,11 @@ func (f *Font) scale() fixed.Int26_6 {
113
146
114
147
func (f * Font ) makeGlyph (char rune ) (glyph , error ) {
115
148
g := & f .gb
149
+ bld := & f .bld
150
+
116
151
idx := f .ttf .Index (char )
117
152
scale := f .scale ()
118
- bld := & f . bld
153
+ // hm := f.ttf.HMetric(scale, idx)
119
154
err := g .Load (& f .ttf , scale , idx , font .HintingNone )
120
155
if err != nil {
121
156
return glyph {}, err
@@ -126,7 +161,8 @@ func (f *Font) makeGlyph(char rune) (glyph, error) {
126
161
if err != nil {
127
162
return glyph {}, err
128
163
} else if ! fill {
129
- return glyph {}, errors .New ("first glyph shape is negative space" )
164
+ _ = fill // This is not an error...
165
+ // return glyph{}, errors.New("first glyph shape is negative space")
130
166
}
131
167
start := g .Ends [0 ]
132
168
g .Ends = g .Ends [1 :]
@@ -142,7 +178,6 @@ func (f *Font) makeGlyph(char rune) (glyph, error) {
142
178
shape = bld .Difference2D (shape , sdf )
143
179
}
144
180
}
145
-
146
181
return glyph {sdf : shape }, nil
147
182
}
148
183
@@ -151,56 +186,46 @@ func glyphCurve(bld *gsdf.Builder, points []truetype.Point, start, end int) (glb
151
186
sampler = ms2.Spline3Sampler {Spline : quadBezier , Tolerance : 0.1 }
152
187
sum float32
153
188
)
154
-
155
- n := end - start
156
- i := start
189
+ points = points [ start : end ]
190
+ n := len ( points )
191
+ i := 0
157
192
var poly []ms2.Vec
158
- vPrev := p2v (points [end - 1 ])
159
- for i < start + n {
160
- p0 , p1 , p2 := points [i ], points [start + (i + 1 )% n ], points [start + (i + 2 )% n ]
161
- onBits := p0 .Flags & 1 |
162
- (p1 .Flags & 1 )<< 1 |
163
- (p2 .Flags & 1 )<< 2
193
+ vPrev := p2v (points [n - 1 ])
194
+ for i < n {
195
+ p0 , p1 , p2 := points [i ], points [(i + 1 )% n ], points [(i + 2 )% n ]
196
+ onBits := onbits3 (points , 0 , n , i )
164
197
v0 , v1 , v2 := p2v (p0 ), p2v (p1 ), p2v (p2 )
165
198
implicit0 := ms2 .Scale (0.5 , ms2 .Add (v0 , v1 ))
166
199
implicit1 := ms2 .Scale (0.5 , ms2 .Add (v1 , v2 ))
167
200
switch onBits {
168
201
case 0b010 , 0b110 :
169
- // sampler.SetSplinePoints(vPrev, v0, v1, ms2.Vec{})
170
- i += 1
171
- println ("prohibited" )
172
- // not valid off start. If getting this error try replacing with `i++;continue`
173
- // return nil, false, errors.New("invalid start to bezier")
202
+ // implicit off start case?
203
+ fallthrough
204
+ case 0b011 , 0b111 :
205
+ // on-on Straight line.
174
206
poly = append (poly , v0 )
207
+ i += 1
208
+ sum += (v0 .X - vPrev .X ) * (v0 .Y + vPrev .Y )
209
+ vPrev = v0
175
210
continue
176
- // // if i == start+n-1 {
177
- // // poly = append(poly, v0)
178
- // // }
179
- // vPrev = v0
180
- // i += 1
181
- // return bld.NewCircle(1), sum > 0, nil
182
- // continue
211
+
183
212
case 0b000 :
184
213
// implicit-off-implicit.
185
214
sampler .SetSplinePoints (implicit0 , v1 , implicit1 , ms2.Vec {})
186
215
v0 = implicit0
187
216
i += 1
217
+
188
218
case 0b001 :
189
219
// on-off-implicit.
190
220
sampler .SetSplinePoints (v0 , v1 , implicit1 , ms2.Vec {})
191
221
i += 1
192
- case 0b011 , 0b111 :
193
- // on-on Straight line.
194
- poly = append (poly , v0 )
195
- i += 1
196
- sum += (v0 .X - vPrev .X ) * (v0 .Y + vPrev .Y )
197
- vPrev = v0
198
- continue
222
+
199
223
case 0b100 :
200
224
// implicit-off-on.
201
225
sampler .SetSplinePoints (implicit0 , v1 , v2 , ms2.Vec {})
202
226
v0 = implicit0
203
227
i += 2
228
+
204
229
case 0b101 :
205
230
// On-off-on.
206
231
sampler .SetSplinePoints (v0 , v1 , v2 , ms2.Vec {})
@@ -227,3 +252,11 @@ var quadBezier = ms2.NewSpline3([]float32{
227
252
1 , - 2 , 1 , 0 ,
228
253
0 , 0 , 0 , 0 ,
229
254
})
255
+
256
+ func onbits3 (points []truetype.Point , start , end , i int ) uint32 {
257
+ n := end - start
258
+ p0 , p1 , p2 := points [i ], points [start + (i + 1 )% n ], points [start + (i + 2 )% n ]
259
+ return p0 .Flags & 1 |
260
+ (p1 .Flags & 1 )<< 1 |
261
+ (p2 .Flags & 1 )<< 2
262
+ }
0 commit comments