From 9472cbae53ba0bba489ad1c15a7063e6fa723267 Mon Sep 17 00:00:00 2001 From: soypat Date: Fri, 22 Nov 2024 01:01:06 -0300 Subject: [PATCH] keep working on font beziers --- forge/textsdf/glyph_test.go | 188 ++++++++++++++++++++++++++++++------ forge/textsdf/iso-3098.ttf | Bin 0 -> 25444 bytes 2 files changed, 157 insertions(+), 31 deletions(-) create mode 100644 forge/textsdf/iso-3098.ttf diff --git a/forge/textsdf/glyph_test.go b/forge/textsdf/glyph_test.go index 3f395e0..e67f7e0 100644 --- a/forge/textsdf/glyph_test.go +++ b/forge/textsdf/glyph_test.go @@ -3,48 +3,31 @@ package textsdf import ( "testing" + _ "embed" + + "github.com/golang/freetype/truetype" "github.com/soypat/glgl/math/ms2" "github.com/soypat/gsdf" + "github.com/soypat/gsdf/glbuild" "github.com/soypat/gsdf/gleval" "github.com/soypat/gsdf/gsdfaux" + "golang.org/x/image/font" + "golang.org/x/image/math/fixed" ) -func TestABC(t *testing.T) { - spline := []ms2.Vec{ - {0, 0}, - {1, 0}, - {1, 0}, - {1, 1}, - } - // var points []ms2.Vec - var bld gsdf.Builder - var poly ms2.PolygonBuilder - circle := bld.NewCircle(0.1) - shape := bld.Translate2D(circle, spline[0].X, spline[1].Y) - sampler := ms2.Spline3Sampler{ - Spline: ms2.SplineBezier(), - Tolerance: 0.01, - } +//go:embed iso-3098.ttf +var _isonormTTF []byte - for i := 0; i < len(spline); i += 4 { - v0, v1, v2, v3 := spline[4*i], spline[4*i+1], spline[4*i+2], spline[4*i+3] - - shape = bld.Union2D(shape, bld.Translate2D(circle, v1.X, v1.Y)) - shape = bld.Union2D(shape, bld.Translate2D(circle, v2.X, v2.Y)) - - sampler.SetSplinePoints(v0, v1, v2, v3) - points := sampler.SampleBisectWithExtremes(nil, 2) - for _, pt := range points { - poly.Add(pt) - } +func TestABC(t *testing.T) { + ttf, err := truetype.Parse(_isonormTTF) + if err != nil { + t.Fatal(err) } - v, err := poly.AppendVecs(nil) + var glyph truetype.GlyphBuf + shape, err := sdf(ttf, &glyph, 'A') if err != nil { t.Fatal(err) } - - shape = bld.Union2D(shape, bld.NewPolygon(v)) - _ = shape sdfcpu, err := gleval.NewCPUSDF2(shape) if err != nil { t.Fatal(err) @@ -54,3 +37,146 @@ func TestABC(t *testing.T) { t.Fatal(err) } } + +func sdf(ttf *truetype.Font, glyph *truetype.GlyphBuf, char rune) (glbuild.Shader2D, error) { + idx := ttf.Index(char) + scale := fixed.Int26_6(ttf.FUnitsPerEm()) + // hm := ttf.HMetric(scale, idx) + + // kern := ttf.Kern(scale, iprev, idx) + // xOfs := float32(kern) + err := glyph.Load(ttf, scale, idx, font.HintingNone) + if err != nil { + return nil, err + } + return glyphSDF(glyph) + +} + +func glyphSDF(g *truetype.GlyphBuf) (glbuild.Shader2D, error) { + var spline []ms2.Vec + var bld gsdf.Builder + shape := bld.NewCircle(0.1) + for n := range g.Ends { + spline = spline[:0] + start := 0 + if n != 0 { + start = g.Ends[n-1] + } + end := g.Ends[n] - 1 + if n != 1 { + continue + } + sdf, cw, err := glyphCurve(g.Points, start, end) + if err != nil { + return shape, err + } + cw = true + if cw { + shape = bld.Union2D(shape, sdf) + } else { + shape = bld.Difference2D(shape, sdf) + } + + } + return shape, nil +} + +// func glyphCurve2(points []truetype.Point, start, end int) (glbuild.Shader2D, bool, error) { +// var ( +// bld gsdf.Builder +// sampler = ms2.Spline3Sampler{Spline: quadraticBezier(), Tolerance: 2} +// sum float32 +// ) +// n := end - start + 1 +// i := 0 +// vprev := p2v(points[end]) +// for i < n { +// p0, p1, p2 := points[i], points[start+(i+1)%n], points[start+(i+2)%n] +// onBits := p0.Flags&1 | +// (p1.Flags&1)<<1 | +// (p2.Flags&1)<<2 +// switch bits { +// case 0b001: +// sampler.SetSplinePoints(vprev,) +// } +// off := points[i].Flags&1 == 0 // Point on or off curve. + +// } +// } + +func glyphCurve(points []truetype.Point, start, end int) (glbuild.Shader2D, bool, error) { + var bld gsdf.Builder + var spline []ms2.Vec + sampler := ms2.Spline3Sampler{ + Spline: quadraticBezier(), + Tolerance: 2, + } + var sum float32 + offPrev := points[end].Flags&1 == 0 + vPrev := p2v(points[end]) + for i := start; i <= end; i++ { + p := points[i] + v := p2v(p) + off := p.Flags&1 == 0 // Point on or off curve. + if off && offPrev { + // Implicit point on curve as midpoint of 2 off-points. + spline = append(spline, ms2.Scale(0.5, ms2.Add(v, vPrev))) + } else if !off && !offPrev { + // Add inneffective bezier off point. + spline = append(spline, ms2.Scale(0.5, ms2.Add(v, vPrev))) + } + spline = append(spline, v) + sum += (v.X - vPrev.X) * (v.Y + vPrev.Y) + vPrev = v + offPrev = off + } + cw := sum > 0 + var poly []ms2.Vec + const c = 10 + circle := bld.NewCircle(c / 2) + CIRCLE := bld.NewCircle(c) + shape := bld.NewCircle(0.1) + for i := 0; i+2 < len(spline); i += 2 { + p0, cp, p1 := spline[i], spline[i+1], spline[i+2] + sampler.SetSplinePoints(p0, cp, p1, ms2.Vec{}) + shape = bld.Union2D(shape, bld.Translate2D(CIRCLE, p0.X, p0.Y), bld.Translate2D(circle, cp.X, cp.Y)) + poly = append(poly, + sampler.Evaluate(0), + sampler.Evaluate(0.25), + sampler.Evaluate(0.5), + sampler.Evaluate(0.75), + ) + _ = p1 + _ = cp + } + return shape, cw, nil + sdf := bld.NewPolygon(poly) + return sdf, cw, nil +} + +func p2v(p truetype.Point) ms2.Vec { + return ms2.Vec{ + X: float32(p.X), + Y: float32(p.Y), + } +} + +func quadraticBezier() (s ms2.Spline3) { + matrix := []float32{ + 1, 0, 0, 0, + -2, 2, 0, 0, + 1, -2, 1, 0, + 0, 0, 0, 0, + } + // for i := range matrix { + // matrix[i] *= 20 + // } + return ms2.NewSpline3(matrix) + // return ms2.NewSpline3([]float32{ + // 1, -2, 1, 0, + // 0, 2, -2, 0, + // 0, 0, 1, 0, + // 0, 0, 0, 0, + // }) +} diff --git a/forge/textsdf/iso-3098.ttf b/forge/textsdf/iso-3098.ttf new file mode 100644 index 0000000000000000000000000000000000000000..87a0b4260c42ffd86f03c5ff2612cdc5ef95a9c8 GIT binary patch literal 25444 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 N=O5yUY5$Lj{{zD3{2%}T literal 0 HcmV?d00001