Skip to content

Commit

Permalink
newpaint: actually spaces _are_ represented in output! use advance on…
Browse files Browse the repository at this point in the history
…ly for bounds - works great in practice. still a minor test glitch -- off by 1. could investigate further later.
  • Loading branch information
rcoreilly committed Feb 8, 2025
1 parent 70331a9 commit 4f33cd1
Show file tree
Hide file tree
Showing 4 changed files with 33 additions and 25 deletions.
8 changes: 6 additions & 2 deletions paint/renderers/rasterx/text.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,17 @@ func (rs *Renderer) TextRun(run *shaped.Run, ln *shaped.Line, lns *shaped.Lines,
// todo: render strikethrough
}

func (rs *Renderer) GlyphOutline(run *shaped.Run, g *shaping.Glyph, bitmap font.GlyphOutline, fill, stroke image.Image, bb math32.Box2, pos math32.Vector2) {
func (rs *Renderer) GlyphOutline(run *shaped.Run, g *shaping.Glyph, outline font.GlyphOutline, fill, stroke image.Image, bb math32.Box2, pos math32.Vector2) {
scale := math32.FromFixed(run.Size) / float32(run.Face.Upem())
x := pos.X
y := pos.Y

if len(outline.Segments) == 0 {
// fmt.Println("nil path:", g.GlyphID)
return
}
rs.Path.Clear()
for _, s := range bitmap.Segments {
for _, s := range outline.Segments {
switch s.Op {
case opentype.SegmentOpMoveTo:
rs.Path.Start(fixed.Point26_6{X: math32.ToFixed(s.Args[0].X*scale + x), Y: math32.ToFixed(-s.Args[0].Y*scale + y)})
Expand Down
8 changes: 8 additions & 0 deletions text/shaped/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# shaped

This is the package that interfaces directly with go-text to turn rich text into _shaped_ text that is suitable for rendering. The lowest-level process is what harfbuzz does (https://harfbuzz.github.io/), shaping runes into combinations of glyphs. In more complex scripts, this can be a very complex process. In Latin languages like English, it is relatively straightforward. In any case, the result of shaping is a slice of `shaping.Output` representations, where each `Output` represents a `Run` of glyphs. Thus wrap the Output in a `Run` type, which adds more functions but uses the same underlying data.

The `Shaper` takes the rich text input and produces these elemental Run outputs. Then, the `WrapLines` function turns these runs into a sequence of `Line`s that sequence the runs into a full line.

One important feature of this shaping process is that _spaces_ are explicitly represented in the output.

20 changes: 6 additions & 14 deletions text/shaped/regions.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (ls *Lines) RuneAtPoint(pt math32.Vector2, start math32.Vector2) int {
lbb := ln.Bounds.Translate(off)
if !lbb.ContainsPoint(pt) {
if pt.Y >= lbb.Min.Y && pt.Y < lbb.Max.Y { // this is our line
if pt.X <= lbb.Min.X+2 {
if pt.X <= lbb.Min.X {
return ln.SourceRange.Start
}
return ln.SourceRange.End
Expand Down Expand Up @@ -185,7 +185,7 @@ func (rn *Run) Runes() textpos.Range {
}

// GlyphsAt returns the indexs of the glyph(s) at given original source rune index.
// Only works for non-space rendering runes. Empty if none found.
// Empty if none found.
func (rn *Run) GlyphsAt(i int) []int {
var gis []int
for gi := range rn.Glyphs {
Expand Down Expand Up @@ -233,24 +233,16 @@ func (rn *Run) RuneAtPoint(src rich.Text, pt, off math32.Vector2) int {
// todo: vertical case!
adv := off.X
rr := rn.Runes()
pri := rr.Start
for gi := range rn.Glyphs {
g := &rn.Glyphs[gi]
cri := g.ClusterIndex
gb := rn.GlyphBoundsBox(g)
gadv := math32.FromFixed(g.XAdvance)
cx := adv + gb.Min.X
dx := pt.X - cx
if dx >= -2 && pt.X < adv+gb.Max.X+2 {
mx := adv + gadv
// fmt.Println(gi, cri, adv, mx, pt.X)
if pt.X >= adv && pt.X < mx {
// fmt.Println("fits!")
return cri
}
if pt.X < cx { // it is before us, in space
nri := cri - pri
ri := pri + int(math32.Round(float32(nri)*((adv-pt.X)/(cx-adv)))) // linear interpolation
// fmt.Println("before:", gi, ri, pri, cri, adv, cx, adv-pt.X, cx-adv)
return ri
}
pri = cri
adv += gadv
}
return rr.End
Expand Down
22 changes: 13 additions & 9 deletions text/shaped/shaped_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
package shaped_test

import (
"fmt"
"os"
"testing"
"unicode"

"cogentcore.org/core/base/iox/imagex"
"cogentcore.org/core/base/runes"
Expand Down Expand Up @@ -80,13 +78,10 @@ func TestBasic(t *testing.T) {

assert.Equal(t, len(src), lns.RuneFromLinePos(textpos.Pos{3, 30}))

for ri, r := range src {
for ri, _ := range src {
lp := lns.RuneToLinePos(ri)
assert.Equal(t, ri, lns.RuneFromLinePos(lp))

if unicode.IsSpace(r) { // todo: deal with spaces!
continue
}
// fmt.Println("\n####", ri, string(r))
gb := lns.RuneBounds(ri)
assert.NotEqual(t, gb, (math32.Box2{}))
Expand All @@ -97,6 +92,9 @@ func TestBasic(t *testing.T) {
cp := gb.Center()
si := lns.RuneAtPoint(cp, pos)
// fmt.Println(cp, si)
// if ri != si {
// fmt.Println(ri, si, gb, cp, lns.RuneBounds(si))
// }
assert.Equal(t, ri, si)
}
})
Expand Down Expand Up @@ -195,10 +193,16 @@ func TestSpacePos(t *testing.T) {
sty := rich.NewStyle()
tx := rich.NewText(sty, []rune(src))
lns := sh.WrapLines(tx, sty, tsty, rts, math32.Vec2(250, 250))
pc.TextLines(lns, math32.Vec2(10, 10))
pos := math32.Vec2(10, 10)
pc.TextLines(lns, pos)
pc.RenderDone()

sb := lns.RuneBounds(4)
fmt.Println("sb:", sb)
sb := lns.RuneBounds(3)
// fmt.Println("sb:", sb)

cp := sb.Center().Add(pos)
si := lns.RuneAtPoint(cp, pos)
// fmt.Println(si)
assert.Equal(t, 3, si)
})
}

0 comments on commit 4f33cd1

Please sign in to comment.