@@ -5,11 +5,11 @@ import (
55 "fmt"
66 "unicode"
77
8- "github.com/golang/freetype/truetype"
98 "github.com/soypat/geometry/ms2"
109 "github.com/soypat/gsdf"
1110 "github.com/soypat/gsdf/glbuild"
1211 "golang.org/x/image/font"
12+ "golang.org/x/image/font/sfnt"
1313 "golang.org/x/image/math/fixed"
1414)
1515
@@ -26,8 +26,8 @@ type FontConfig struct {
2626
2727// Font implements font parsing and glyph (character) generation.
2828type Font struct {
29- ttf truetype. Font
30- gb truetype. GlyphBuf
29+ buf sfnt. Buffer
30+ sfn * sfnt. Font
3131 // basicGlyphs optimized array access for common ASCII glyphs.
3232 basicGlyphs [lastBasic - firstBasic + 1 ]glyph
3333 // Other kinds of glyphs.
@@ -50,12 +50,12 @@ func (f *Font) Configure(cfg FontConfig) error {
5050
5151// LoadTTFBytes loads a TTF file blob into f. After calling Load the Font is ready to generate text SDFs.
5252func (f * Font ) LoadTTFBytes (ttf []byte ) error {
53- font , err := truetype .Parse (ttf )
53+ font , err := sfnt .Parse (ttf )
5454 if err != nil {
5555 return err
5656 }
5757 f .reset ()
58- f .ttf = * font
58+ f .sfn = font
5959 return nil
6060}
6161
@@ -88,38 +88,48 @@ type glyph struct {
8888// Glyph locations are set starting at x=0 and appended in positive x direction.
8989func (f * Font ) TextLine (s string ) (glbuild.Shader2D , error ) {
9090 var shapes []glbuild.Shader2D
91- scale := f .scale ()
92- var idxPrev truetype.Index
93- var xOfs int64
94- scalout := f .scaleout ()
91+ ppem := f .scale ()
92+
93+ var idxPrev sfnt.GlyphIndex
94+ var xOfs fixed.Int26_6
95+ scaleout := f .scaleout ()
9596 for ic , c := range s {
9697 if ! unicode .IsGraphic (c ) {
9798 return nil , fmt .Errorf ("char %q not graphic" , c )
9899 }
99100
100- idx := truetype .Index (c )
101- hm := f .ttf .HMetric (scale , idx )
101+ idx , err := f .sfn .GlyphIndex (& f .buf , c )
102+ if err != nil {
103+ return nil , fmt .Errorf ("char %q glyph index: %w" , c , err )
104+ }
105+
106+ advance , err := f .sfn .GlyphAdvance (& f .buf , idx , ppem , font .HintingNone )
107+ if err != nil {
108+ return nil , fmt .Errorf ("char %q advance: %w" , c , err )
109+ }
110+
102111 if unicode .IsSpace (c ) {
103112 if c == '\t' {
104- hm . AdvanceWidth *= 4
113+ advance *= 4
105114 }
106- xOfs += int64 ( hm . AdvanceWidth )
115+ xOfs += advance
107116 continue
108117 }
118+
109119 charshape , err := f .Glyph (c )
110120 if err != nil {
111121 return nil , fmt .Errorf ("char %q: %w" , c , err )
112122 }
113123
114- kern := f .ttf .Kern (scale , idxPrev , idx )
115- xOfs += int64 (kern )
116- idxPrev = idx
117- if ic == 0 {
118- xOfs += int64 (hm .LeftSideBearing )
124+ if ic > 0 {
125+ kern , _ := f .sfn .Kern (& f .buf , idxPrev , idx , ppem , font .HintingNone )
126+ xOfs += kern
119127 }
120- charshape = f .bld .Translate2D (charshape , float32 (xOfs )* scalout , 0 )
128+ idxPrev = idx
129+
130+ charshape = f .bld .Translate2D (charshape , float32 (xOfs )* scaleout , 0 )
121131 shapes = append (shapes , charshape )
122- xOfs += int64 ( hm . AdvanceWidth )
132+ xOfs += advance
123133 }
124134 if len (shapes ) == 1 {
125135 return shapes [0 ], nil
@@ -132,12 +142,17 @@ func (f *Font) TextLine(s string) (glbuild.Shader2D, error) {
132142
133143// Kern returns the horizontal adjustment for the given glyph pair. A positive kern means to move the glyphs further apart.
134144func (f * Font ) Kern (c0 , c1 rune ) float32 {
135- return float32 (f .ttf .Kern (f .scale (), truetype .Index (c0 ), truetype .Index (c1 )))
145+ idx0 , _ := f .sfn .GlyphIndex (& f .buf , c0 )
146+ idx1 , _ := f .sfn .GlyphIndex (& f .buf , c1 )
147+ kern , _ := f .sfn .Kern (& f .buf , idx0 , idx1 , f .scale (), font .HintingNone )
148+ return float32 (kern ) * f .scaleout ()
136149}
137150
138- // Kern returns the horizontal adjustment for the given glyph pair. A positive kern means to move the glyphs further apart .
151+ // AdvanceWidth returns the horizontal advance width for the given glyph.
139152func (f * Font ) AdvanceWidth (c rune ) float32 {
140- return float32 (f .ttf .HMetric (f .scale (), truetype .Index (c )).AdvanceWidth )
153+ idx , _ := f .sfn .GlyphIndex (& f .buf , c )
154+ advance , _ := f .sfn .GlyphAdvance (& f .buf , idx , f .scale (), font .HintingNone )
155+ return float32 (advance ) * f .scaleout ()
141156}
142157
143158// Glyph returns a SDF for a character defined by the argument rune.
@@ -177,11 +192,12 @@ func (f *Font) glyph(c rune) (g *glyph, err error) {
177192}
178193
179194func (f * Font ) scale () fixed.Int26_6 {
180- return fixed .Int26_6 (f .ttf .FUnitsPerEm ())
195+ units := f .sfn .UnitsPerEm ()
196+ return fixed .Int26_6 (units )
181197}
182198
183199func (f * Font ) rawbounds () ms2.Box {
184- bb := f .ttf .Bounds (f . scale ())
200+ bb , _ := f .sfn .Bounds (& f . buf , f . scale (), font . HintingNone )
185201 return ms2.Box {
186202 Min : ms2.Vec {X : float32 (bb .Min .X ), Y : float32 (bb .Min .Y )},
187203 Max : ms2.Vec {X : float32 (bb .Max .X ), Y : float32 (bb .Max .Y )},
@@ -196,32 +212,38 @@ func (f *Font) scaleout() float32 {
196212}
197213
198214func (f * Font ) makeGlyph (char rune ) (glyph , error ) {
199- g := & f .gb
200215 bld := f .bld
201216
202- idx := f .ttf .Index (char )
203- scale := f .scale ()
204- // hm := f.ttf.HMetric(scale, idx)
205- err := g .Load (& f .ttf , scale , idx , font .HintingNone )
217+ idx , err := f .sfn .GlyphIndex (& f .buf , char )
206218 if err != nil {
207219 return glyph {}, err
208220 }
209- scaleout := f .scaleout ()
210221
222+ ppem := f .scale ()
223+ segments , err := f .sfn .LoadGlyph (& f .buf , idx , ppem , nil )
224+ if err != nil {
225+ return glyph {}, err
226+ }
227+
228+ scaleout := f .scaleout ()
211229 tol := f .reltol
212- // Build Glyph.
213- shape , fill , err := glyphCurve (bld , g .Points , 0 , g .Ends [0 ], tol , scaleout )
230+
231+ // Split segments into contours (each MoveTo starts a new contour).
232+ contours := splitContours (segments )
233+ if len (contours ) == 0 {
234+ return glyph {}, errors .New ("glyph has no contours" )
235+ }
236+
237+ // Build first contour.
238+ shape , fill , err := segmentsToPolygon (bld , contours [0 ], tol , scaleout )
214239 if err != nil {
215240 return glyph {}, err
216- } else if ! fill {
217- _ = fill // This is not an error...
218- // return glyph{}, errors.New("first glyph shape is negative space")
219241 }
220- start := g . Ends [ 0 ]
221- g . Ends = g . Ends [ 1 :]
222- for _ , end := range g . Ends {
223- sdf , fill , err := glyphCurve ( bld , g . Points , start , end , tol , scaleout )
224- start = end
242+ _ = fill // First contour fill direction is not necessarily an error.
243+
244+ // Process remaining contours.
245+ for _ , contour := range contours [ 1 :] {
246+ sdf , fill , err := segmentsToPolygon ( bld , contour , tol , scaleout )
225247 if err != nil {
226248 return glyph {}, err
227249 }
@@ -234,77 +256,82 @@ func (f *Font) makeGlyph(char rune) (glyph, error) {
234256 return glyph {sdf : shape }, nil
235257}
236258
237- func glyphCurve (bld * gsdf.Builder , points []truetype.Point , start , end int , tol , scale float32 ) (glbuild.Shader2D , bool , error ) {
259+ // splitContours splits segments into separate contours. Each contour starts with a MoveTo.
260+ func splitContours (segments sfnt.Segments ) []sfnt.Segments {
261+ var contours []sfnt.Segments
262+ var current sfnt.Segments
263+ for _ , seg := range segments {
264+ if seg .Op == sfnt .SegmentOpMoveTo && len (current ) > 0 {
265+ contours = append (contours , current )
266+ current = nil
267+ }
268+ current = append (current , seg )
269+ }
270+ if len (current ) > 0 {
271+ contours = append (contours , current )
272+ }
273+ return contours
274+ }
275+
276+ // segmentsToPolygon converts sfnt segments to a polygon.
277+ // Returns the polygon SDF, whether it's a fill (positive winding), and any error.
278+ func segmentsToPolygon (bld * gsdf.Builder , segments sfnt.Segments , tol , scale float32 ) (glbuild.Shader2D , bool , error ) {
238279 var (
239- sampler = ms2.Spline3Sampler {Spline : quadBezier , Tolerance : tol }
240- windingSum float32
280+ poly []ms2.Vec
281+ windingSum float32
282+ prev ms2.Vec
283+ quadsampler = ms2.Spline3Sampler {
284+ Spline : ms2 .SplineBezierQuadratic (),
285+ Tolerance : tol ,
286+ }
287+ cubicSampler = ms2.Spline3Sampler {
288+ Spline : ms2 .SplineBezierCubic (),
289+ Tolerance : tol ,
290+ }
241291 )
242- points = points [start :end ]
243- n := len (points )
244- i := 0
245- var poly []ms2.Vec
246- vPrev := p2v (points [n - 1 ], scale )
247- for i < n {
248- p0 , p1 , p2 := points [i ], points [(i + 1 )% n ], points [(i + 2 )% n ]
249- onBits := onbits3 (points , 0 , n , i )
250- v0 , v1 , v2 := p2v (p0 , scale ), p2v (p1 , scale ), p2v (p2 , scale )
251- implicit0 := ms2 .Scale (0.5 , ms2 .Add (v0 , v1 ))
252- implicit1 := ms2 .Scale (0.5 , ms2 .Add (v1 , v2 ))
253- switch onBits {
254- case 0b010 , 0b110 :
255- // implicit off start case?
256- fallthrough
257- case 0b011 , 0b111 :
258- // on-on Straight line.
259- poly = append (poly , v0 )
260- i += 1
261- windingSum += (v0 .X - vPrev .X ) * (v0 .Y + vPrev .Y )
262- vPrev = v0
263- continue
264292
265- case 0b000 :
266- // implicit-off-implicit.
267- sampler .SetSplinePoints (implicit0 , v1 , implicit1 , ms2.Vec {})
268- v0 = implicit0
269- i += 1
270-
271- case 0b001 :
272- // on-off-implicit.
273- sampler .SetSplinePoints (v0 , v1 , implicit1 , ms2.Vec {})
274- i += 1
275-
276- case 0b100 :
277- // implicit-off-on.
278- sampler .SetSplinePoints (implicit0 , v1 , v2 , ms2.Vec {})
279- v0 = implicit0
280- i += 2
281-
282- case 0b101 :
283- // On-off-on.
284- sampler .SetSplinePoints (v0 , v1 , v2 , ms2.Vec {})
285- i += 2
293+ for _ , seg := range segments {
294+ switch seg .Op {
295+ case sfnt .SegmentOpMoveTo :
296+ // Start of contour - note: sfnt Y axis increases downward, so we negate Y.
297+ prev = fixedToVec (seg .Args [0 ], scale )
298+
299+ case sfnt .SegmentOpLineTo :
300+ p := fixedToVec (seg .Args [0 ], scale )
301+ poly = append (poly , prev )
302+ windingSum += (prev .X - p .X ) * (prev .Y + p .Y )
303+ prev = p
304+
305+ case sfnt .SegmentOpQuadTo :
306+ // Quadratic bezier: prev -> Args[0] (control) -> Args[1] (end)
307+ ctrl := fixedToVec (seg .Args [0 ], scale )
308+ end := fixedToVec (seg .Args [1 ], scale )
309+ quadsampler .SetSplinePoints (prev , ctrl , end , ms2.Vec {})
310+ poly = append (poly , prev )
311+ poly = quadsampler .SampleBisect (poly , 4 )
312+ windingSum += (prev .X - end .X ) * (prev .Y + end .Y )
313+ prev = end
314+
315+ case sfnt .SegmentOpCubeTo :
316+ // Cubic bezier: prev -> Args[0] (ctrl1) -> Args[1] (ctrl2) -> Args[2] (end)
317+ ctrl1 := fixedToVec (seg .Args [0 ], scale )
318+ ctrl2 := fixedToVec (seg .Args [1 ], scale )
319+ end := fixedToVec (seg .Args [2 ], scale )
320+ cubicSampler .SetSplinePoints (prev , ctrl1 , ctrl2 , end )
321+ poly = append (poly , prev )
322+ poly = cubicSampler .SampleBisect (poly , 4 )
323+ windingSum += (prev .X - end .X ) * (prev .Y + end .Y )
324+ prev = end
286325 }
287- poly = append (poly , v0 ) // Append start point.
288- poly = sampler .SampleBisect (poly , 4 )
289- windingSum += (v0 .X - vPrev .X ) * (v0 .Y + vPrev .Y )
290- vPrev = v0
291326 }
292- return bld .NewPolygon (poly ), windingSum > 0 , bld .Err ()
327+ return bld .NewPolygon (poly ), windingSum < 0 , bld .Err ()
293328}
294329
295- func p2v (p truetype.Point , scale float32 ) ms2.Vec {
330+ // fixedToVec converts a fixed.Point26_6 to ms2.Vec with scaling.
331+ // Note: sfnt has Y increasing downward, so we negate Y to flip to standard math coordinates.
332+ func fixedToVec (p fixed.Point26_6 , scale float32 ) ms2.Vec {
296333 return ms2.Vec {
297334 X : float32 (p .X ) * scale ,
298- Y : float32 (p .Y ) * scale ,
335+ Y : - float32 (p .Y ) * scale , // Negate Y to flip coordinate system.
299336 }
300337}
301-
302- var quadBezier = ms2 .SplineBezierQuadratic ()
303-
304- func onbits3 (points []truetype.Point , start , end , i int ) uint32 {
305- n := end - start
306- p0 , p1 , p2 := points [i ], points [start + (i + 1 )% n ], points [start + (i + 2 )% n ]
307- return p0 .Flags & 1 |
308- (p1 .Flags & 1 )<< 1 |
309- (p2 .Flags & 1 )<< 2
310- }
0 commit comments