text/v2, exp/textinput: bug fix: RuneLen could return -1 for errors

This commit is contained in:
Hajime Hoshi
2025-06-09 01:16:29 +09:00
parent a034565163
commit 61426ac72d
4 changed files with 45 additions and 5 deletions

View File

@@ -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
}
}

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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 {