diff --git a/exp/textinput/textinput.go b/exp/textinput/textinput.go index 44f9a07d0..4d862c5df 100644 --- a/exp/textinput/textinput.go +++ b/exp/textinput/textinput.go @@ -19,6 +19,7 @@ package textinput import ( + "fmt" "image" "unicode/utf16" "unicode/utf8" @@ -65,27 +66,49 @@ func start(bounds image.Rectangle) (states <-chan textInputState, close func()) } func convertUTF16CountToByteCount(text string, c int) int { + if !utf8.ValidString(text) { + return -1 + } if c == 0 { return 0 } var utf16Len int for idx, r := range text { - utf16Len += utf16.RuneLen(r) + l16 := utf16.RuneLen(r) + if l16 < 0 { + panic(fmt.Sprintf("textinput: invalid rune: %c", r)) + } + utf16Len += l16 if utf16Len >= c { - return idx + utf8.RuneLen(r) + l8 := utf8.RuneLen(r) + if l8 < 0 { + panic(fmt.Sprintf("textinput: invalid rune: %c", r)) + } + return idx + l8 } } return -1 } func convertByteCountToUTF16Count(text string, c int) int { + if !utf8.ValidString(text) { + return -1 + } if c == 0 { return 0 } var utf16Len int for idx, r := range text { - utf16Len += utf16.RuneLen(r) - if idx+utf8.RuneLen(r) >= c { + l16 := utf16.RuneLen(r) + if l16 < 0 { + panic(fmt.Sprintf("textinput: invalid rune length for rune %c", r)) + } + utf16Len += l16 + l8 := utf8.RuneLen(r) + if l8 < 0 { + panic(fmt.Sprintf("textinput: invalid rune length for rune %c", r)) + } + if idx+l8 >= c { return utf16Len } } diff --git a/exp/textinput/textinput_test.go b/exp/textinput/textinput_test.go index 1d82957b5..d45ca2b1e 100644 --- a/exp/textinput/textinput_test.go +++ b/exp/textinput/textinput_test.go @@ -42,6 +42,10 @@ func TestConvertUTF16CountToByteCount(t *testing.T) { {"寿司🍣食べたい", 4, 10}, {"寿司🍣食べたい", 5, 13}, {"寿司🍣食べたい", 100, -1}, + {"\xff\xff\xff\xff", 0, -1}, + {"\xff\xff\xff\xff", 1, -1}, + {"\xff\xff\xff\xff", 2, -1}, + {"\xff\xff\xff\xff", 100, -1}, } for _, tc := range testCases { if got := textinput.ConvertUTF16CountToByteCount(tc.text, tc.c); got != tc.want { @@ -72,6 +76,10 @@ func TestConvertByteCountToUTF16Count(t *testing.T) { {"寿司🍣食べたい", 10, 4}, {"寿司🍣食べたい", 13, 5}, {"寿司🍣食べたい", 100, -1}, + {"\xff\xff\xff\xff", 0, -1}, + {"\xff\xff\xff\xff", 3, -1}, + {"\xff\xff\xff\xff", 6, -1}, + {"\xff\xff\xff\xff", 100, -1}, } for _, tc := range testCases { if got := textinput.ConvertByteCountToUTF16Count(tc.text, tc.c); got != tc.want { diff --git a/text/v2/gox.go b/text/v2/gox.go index 1db7a87dc..f1375ebe8 100644 --- a/text/v2/gox.go +++ b/text/v2/gox.go @@ -172,6 +172,10 @@ func (g *GoXFace) appendGlyphsForLine(glyphs []Glyph, line string, indexOffset i // Adjust the position to the integers. // The current glyph images assume that they are rendered on integer positions so far. size := utf8.RuneLen(r) + if size < 0 { + // Treat an error as 1, following DecodeRuneInString. + size = 1 + } // Append a glyph even if img is nil. // This is necessary to return index information for control characters. diff --git a/text/v2/multi.go b/text/v2/multi.go index 361d93a3d..6c8cedba6 100644 --- a/text/v2/multi.go +++ b/text/v2/multi.go @@ -168,7 +168,6 @@ func (m *MultiFace) splitText(text string) iter.Seq[textChunk] { var chunk textChunk for _, r := range text { fi := -1 - l := utf8.RuneLen(r) for i, f := range m.faces { if !f.hasGlyph(r) && i < len(m.faces)-1 { continue @@ -180,6 +179,12 @@ func (m *MultiFace) splitText(text string) iter.Seq[textChunk] { panic("text: a face was not selected correctly") } + l := utf8.RuneLen(r) + if l < 0 { + // Treat an error as 1, following DecodeRuneInString. + l = 1 + } + var s int if chunk != (textChunk{}) { if chunk.faceIndex == fi {