@@ -3,6 +3,7 @@ package textsdf
33import (
44 "errors"
55 "fmt"
6+ "unicode"
67
78 "github.com/golang/freetype/truetype"
89 "github.com/soypat/glgl/math/ms2"
@@ -20,56 +21,83 @@ type Font struct {
2021 ttf truetype.Font
2122 gb truetype.GlyphBuf
2223 // basicGlyphs optimized array access for common ASCII glyphs.
23- basicGlyphs [lastBasic - firstBasic ]glyph
24+ basicGlyphs [lastBasic - firstBasic + 1 ]glyph
2425 // Other kinds of glyphs.
2526 otherGlyphs map [rune ]glyph
2627 bld gsdf.Builder
2728}
2829
30+ // LoadTTFBytes loads a TTF file blob into f. After calling Load the Font is ready to generate text SDFs.
2931func (f * Font ) LoadTTFBytes (ttf []byte ) error {
3032 font , err := truetype .Parse (ttf )
3133 if err != nil {
3234 return err
3335 }
34- f .Reset ()
36+ f .reset ()
3537 f .ttf = * font
3638 return nil
3739}
3840
39- func (f * Font ) Reset () {
41+ // reset resets most internal state of Font without removing underlying assigned font.
42+ func (f * Font ) reset () {
4043 for i := range f .basicGlyphs {
4144 f .basicGlyphs [i ] = glyph {}
4245 }
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+ }
4552 }
4653}
4754
4855type glyph struct {
4956 sdf glbuild.Shader2D
5057}
5158
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.
5262func (f * Font ) TextLine (s string ) (glbuild.Shader2D , error ) {
53- if len (s ) == 0 {
54- return nil , errors .New ("no text provided" )
55- }
5663 var shapes []glbuild.Shader2D
5764 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+ }
6081 charshape , err := f .Glyph (c )
6182 if err != nil {
6283 return nil , fmt .Errorf ("char %q: %w" , c , err )
6384 }
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 )
6791 }
92+ charshape = f .bld .Translate2D (charshape , xOfs , 0 )
6893 shapes = append (shapes , charshape )
69- prevChar = c
94+ xOfs += float32 ( hm . AdvanceWidth )
7095 }
7196 if len (shapes ) == 1 {
7297 return shapes [0 ], nil
98+ } else if len (shapes ) == 0 {
99+ // Only whitespace.
100+ return nil , errors .New ("no text provided" )
73101 }
74102 return f .bld .Union2D (shapes ... ), nil
75103}
@@ -79,7 +107,12 @@ func (f *Font) Kern(c0, c1 rune) float32 {
79107 return float32 (f .ttf .Kern (f .scale (), truetype .Index (c0 ), truetype .Index (c1 )))
80108}
81109
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.
83116func (f * Font ) Glyph (c rune ) (_ glbuild.Shader2D , err error ) {
84117 var g glyph
85118 if c >= firstBasic && c <= lastBasic {
@@ -113,9 +146,11 @@ func (f *Font) scale() fixed.Int26_6 {
113146
114147func (f * Font ) makeGlyph (char rune ) (glyph , error ) {
115148 g := & f .gb
149+ bld := & f .bld
150+
116151 idx := f .ttf .Index (char )
117152 scale := f .scale ()
118- bld := & f . bld
153+ // hm := f.ttf.HMetric(scale, idx)
119154 err := g .Load (& f .ttf , scale , idx , font .HintingNone )
120155 if err != nil {
121156 return glyph {}, err
@@ -126,7 +161,8 @@ func (f *Font) makeGlyph(char rune) (glyph, error) {
126161 if err != nil {
127162 return glyph {}, err
128163 } 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")
130166 }
131167 start := g .Ends [0 ]
132168 g .Ends = g .Ends [1 :]
@@ -142,7 +178,6 @@ func (f *Font) makeGlyph(char rune) (glyph, error) {
142178 shape = bld .Difference2D (shape , sdf )
143179 }
144180 }
145-
146181 return glyph {sdf : shape }, nil
147182}
148183
@@ -151,56 +186,46 @@ func glyphCurve(bld *gsdf.Builder, points []truetype.Point, start, end int) (glb
151186 sampler = ms2.Spline3Sampler {Spline : quadBezier , Tolerance : 0.1 }
152187 sum float32
153188 )
154-
155- n := end - start
156- i := start
189+ points = points [ start : end ]
190+ n := len ( points )
191+ i := 0
157192 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 )
164197 v0 , v1 , v2 := p2v (p0 ), p2v (p1 ), p2v (p2 )
165198 implicit0 := ms2 .Scale (0.5 , ms2 .Add (v0 , v1 ))
166199 implicit1 := ms2 .Scale (0.5 , ms2 .Add (v1 , v2 ))
167200 switch onBits {
168201 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.
174206 poly = append (poly , v0 )
207+ i += 1
208+ sum += (v0 .X - vPrev .X ) * (v0 .Y + vPrev .Y )
209+ vPrev = v0
175210 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+
183212 case 0b000 :
184213 // implicit-off-implicit.
185214 sampler .SetSplinePoints (implicit0 , v1 , implicit1 , ms2.Vec {})
186215 v0 = implicit0
187216 i += 1
217+
188218 case 0b001 :
189219 // on-off-implicit.
190220 sampler .SetSplinePoints (v0 , v1 , implicit1 , ms2.Vec {})
191221 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+
199223 case 0b100 :
200224 // implicit-off-on.
201225 sampler .SetSplinePoints (implicit0 , v1 , v2 , ms2.Vec {})
202226 v0 = implicit0
203227 i += 2
228+
204229 case 0b101 :
205230 // On-off-on.
206231 sampler .SetSplinePoints (v0 , v1 , v2 , ms2.Vec {})
@@ -227,3 +252,11 @@ var quadBezier = ms2.NewSpline3([]float32{
227252 1 , - 2 , 1 , 0 ,
228253 0 , 0 , 0 , 0 ,
229254})
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