Skip to content

Commit fe9b306

Browse files
hajimehoshiclaude
andcommitted
debugui: replace uniseg with go-text/typesetting segmenter for line breaking
Closes #49 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f9c0b31 commit fe9b306

4 files changed

Lines changed: 62 additions & 36 deletions

File tree

context.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"maps"
1010
"slices"
1111

12+
"github.com/go-text/typesetting/segmenter"
1213
"github.com/hajimehoshi/ebiten/v2"
1314
)
1415

@@ -54,9 +55,25 @@ type Context struct {
5455
screenWidth int
5556
screenHeight int
5657

58+
segStack []segmenter.Segmenter
59+
segStackIdx int
60+
5761
err error
5862
}
5963

64+
func (c *Context) pushSegmenter() *segmenter.Segmenter {
65+
if c.segStackIdx >= len(c.segStack) {
66+
c.segStack = append(c.segStack, segmenter.Segmenter{})
67+
}
68+
seg := &c.segStack[c.segStackIdx]
69+
c.segStackIdx++
70+
return seg
71+
}
72+
73+
func (c *Context) popSegmenter() {
74+
c.segStackIdx--
75+
}
76+
6077
func (c *Context) wrapEventHandlerAndError(f func() (EventHandler, error)) EventHandler {
6178
if c.err != nil {
6279
return &nullEventHandler{}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@ module github.com/ebitengine/debugui
33
go 1.24.0
44

55
require (
6+
github.com/go-text/typesetting v0.3.5-0.20260309142344-94fe510e6837
67
github.com/hajimehoshi/bitmapfont/v4 v4.1.0
78
github.com/hajimehoshi/ebiten/v2 v2.9.8
89
github.com/kisielk/errcheck v1.10.0
9-
github.com/rivo/uniseg v0.4.7
1010
golang.org/x/tools v0.42.0
1111
)
1212

1313
require (
1414
github.com/ebitengine/gomobile v0.0.0-20250923094054-ea854a63cce1 // indirect
1515
github.com/ebitengine/hideconsole v1.0.0 // indirect
1616
github.com/ebitengine/purego v0.9.0 // indirect
17-
github.com/go-text/typesetting v0.3.0 // indirect
1817
github.com/jezek/xgb v1.1.1 // indirect
1918
github.com/pierrec/lz4/v4 v4.1.22 // indirect
19+
github.com/rivo/uniseg v0.4.7 // indirect
2020
golang.org/x/image v0.31.0 // indirect
2121
golang.org/x/mod v0.33.0 // indirect
2222
golang.org/x/sync v0.19.0 // indirect

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj
44
github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A=
55
github.com/ebitengine/purego v0.9.0 h1:mh0zpKBIXDceC63hpvPuGLiJ8ZAa3DfrFTudmfi8A4k=
66
github.com/ebitengine/purego v0.9.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
7-
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
8-
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
9-
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
10-
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
7+
github.com/go-text/typesetting v0.3.5-0.20260309142344-94fe510e6837 h1:UDgW/cvxs0VfC5ATr1PCk+09SpdNzcpdq/BL6pcBCII=
8+
github.com/go-text/typesetting v0.3.5-0.20260309142344-94fe510e6837/go.mod h1:4qZCQphq4KSgGTAeI0uMEkVbROgfah8BuyF5LRYr7XY=
9+
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3 h1:drBZzMgdYPbmyXqOto4YhhJGrFIQCX94FpR4MzTCsos=
10+
github.com/go-text/typesetting-utils v0.0.0-20260223113751-2d88ac90dae3/go.mod h1:3/62I4La/HBRX9TcTpBj4eipLiwzf+vhI+7whTc9V7o=
1111
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
1212
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1313
github.com/hajimehoshi/bitmapfont/v4 v4.1.0 h1:eE3qa5Do4qhowZVIHjsrX5pYyyPN6sAFWMsO7QREm3U=

text.go

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,50 +8,59 @@ import (
88
"iter"
99
"strings"
1010
"unicode"
11-
12-
"github.com/rivo/uniseg"
1311
)
1412

1513
func removeSpaceAtLineTail(str string) string {
1614
return strings.TrimRightFunc(str, unicode.IsSpace)
1715
}
1816

19-
func lines(text string, width int) iter.Seq[string] {
17+
func sanitizeUTF8(s string) string {
18+
var b strings.Builder
19+
for _, r := range s {
20+
b.WriteRune(r)
21+
}
22+
return b.String()
23+
}
24+
25+
func (c *Context) lines(text string, width int) iter.Seq[string] {
2026
return func(yield func(string) bool) {
27+
seg := c.pushSegmenter()
28+
defer c.popSegmenter()
29+
30+
if err := seg.InitWithString(text); err != nil {
31+
text = sanitizeUTF8(text)
32+
if err := seg.InitWithString(text); err != nil {
33+
panic("debugui: segmenter.InitWithString failed even after sanitizing: " + err.Error())
34+
}
35+
}
36+
2137
var line string
22-
var word string
23-
state := -1
24-
for len(text) > 0 {
25-
cluster, nextText, boundaries, nextState := uniseg.StepString(text, state)
26-
switch m := boundaries & uniseg.MaskLine; m {
27-
default:
28-
word += cluster
29-
case uniseg.LineCanBreak, uniseg.LineMustBreak:
30-
if line == "" {
31-
line += word + cluster
32-
} else {
33-
if l := removeSpaceAtLineTail(line + word + cluster); textWidth(l) > width {
34-
if !yield(removeSpaceAtLineTail(line)) {
35-
return
36-
}
37-
line = word + cluster
38-
} else {
39-
line += word + cluster
40-
}
41-
}
42-
word = ""
43-
if m == uniseg.LineMustBreak {
38+
it := seg.LineIterator()
39+
for it.Next() {
40+
l := it.Line()
41+
segment := text[l.OffsetInBytes : l.OffsetInBytes+l.LengthInBytes]
42+
43+
if line == "" {
44+
line = segment
45+
} else {
46+
if trimmed := removeSpaceAtLineTail(line + segment); textWidth(trimmed) > width {
4447
if !yield(removeSpaceAtLineTail(line)) {
4548
return
4649
}
47-
line = ""
50+
line = segment
51+
} else {
52+
line += segment
53+
}
54+
}
55+
56+
if l.IsMandatoryBreak {
57+
if !yield(removeSpaceAtLineTail(line)) {
58+
return
4859
}
60+
line = ""
4961
}
50-
state = nextState
51-
text = nextText
5262
}
5363

54-
line += word
5564
if len(line) > 0 {
5665
if !yield(removeSpaceAtLineTail(line)) {
5766
return
@@ -63,7 +72,7 @@ func lines(text string, width int) iter.Seq[string] {
6372
// Text creates a text label.
6473
func (c *Context) Text(text string) {
6574
c.GridCell(func(bounds image.Rectangle) {
66-
for line := range lines(text, bounds.Dx()-c.style().padding) {
75+
for line := range c.lines(text, bounds.Dx()-c.style().padding) {
6776
_, _ = c.widget(widgetID{}, 0, nil, nil, func(bounds image.Rectangle) {
6877
c.drawWidgetText(line, bounds, colorText, 0)
6978
})

0 commit comments

Comments
 (0)