Fix automatically resizing non-English text

This commit is contained in:
Trevor Slocum 2024-11-16 11:36:17 -08:00
parent baf88f4b7b
commit b9cc08d3af
10 changed files with 202 additions and 187 deletions

View file

@ -16,7 +16,6 @@ type Button struct {
btnBackground color.RGBA
textFont *text.GoTextFaceSource
textSize int
textAutoSize int
borderSize int
borderTop color.RGBA
borderRight color.RGBA
@ -37,7 +36,7 @@ func NewButton(label string, onSelected func() error) *Button {
f.SetForegroundColor(textColor)
f.SetHorizontal(messeji.AlignCenter)
f.SetVertical(messeji.AlignCenter)
f.SetScrollBarVisible(false)
f.SetAutoResize(true)
b := &Button{
Box: NewBox(),
@ -53,7 +52,6 @@ func NewButton(label string, onSelected func() error) *Button {
borderLeft: Style.ButtonBorderLeft,
}
b.SetBackground(Style.ButtonBgColor)
b.resizeFont()
return b
}
@ -62,7 +60,6 @@ func (b *Button) SetRect(r image.Rectangle) {
b.Box.rect = r
b.field.SetRect(r)
b.resizeFont()
for _, w := range b.children {
w.SetRect(r)
@ -119,7 +116,6 @@ func (b *Button) SetText(text string) {
defer b.Unlock()
b.field.SetText(text)
b.resizeFont()
}
// SetFont sets the font and text size of button label. Scaling is not applied.
@ -128,36 +124,7 @@ func (b *Button) SetFont(fnt *text.GoTextFaceSource, size int) {
defer b.Unlock()
b.textFont, b.textSize = fnt, size
b.resizeFont()
}
func (b *Button) resizeFont() {
w, h := b.rect.Dx()-b.field.Padding()*2, b.rect.Dy()-b.field.Padding()*2
if w == 0 || h == 0 {
if b.textAutoSize == b.textSize {
return
}
b.textAutoSize = b.textSize
ff := FontFace(b.textFont, b.textSize)
b.field.SetFont(ff, fontMutex)
return
}
var autoSize int
var ff *text.GoTextFace
for autoSize = b.textSize; autoSize > 0; autoSize-- {
ff = FontFace(b.textFont, autoSize)
bounds := BoundString(ff, b.field.Text())
if bounds.Dx() <= w && bounds.Dy() <= h {
break
}
}
if b.textAutoSize == autoSize {
return
}
b.field.SetFont(ff, fontMutex)
b.textAutoSize = autoSize
b.field.SetFont(b.textFont, b.textSize, fontMutex)
}
// SetHorizontal sets the horizontal alignment of the button label.

View file

@ -463,7 +463,7 @@ func draw(w Widget, screen *ebiten.Image) error {
}
func newText() *messeji.TextField {
f := messeji.NewTextField(FontFace(Style.TextFont, Scale(Style.TextSize)), fontMutex)
f := messeji.NewTextField(Style.TextFont, Scale(Style.TextSize), fontMutex)
f.SetForegroundColor(Style.TextColorLight)
f.SetBackgroundColor(transparent)
f.SetScrollBarColors(Style.ScrollAreaColor, Style.ScrollHandleColor)

4
go.mod
View file

@ -18,8 +18,8 @@ require (
github.com/go-text/typesetting v0.2.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/jezek/xgb v1.1.1 // indirect
golang.org/x/exp/shiny v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c // indirect
golang.org/x/exp/shiny v0.0.0-20241108190413-2d47ceb2692f // indirect
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f // indirect
golang.org/x/sync v0.9.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect

8
go.sum
View file

@ -24,12 +24,12 @@ github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e h1:ZAvbj5hI/G/EbAYAcj4y
github.com/llgcode/ps v0.0.0-20210114104736-f4b0c5d1e02e/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
golang.org/x/exp/shiny v0.0.0-20241009180824-f66d83c29e7c h1:jTMrjjZRcSH/BDxWhXCP6OWsfVgmnwI7J+F4/nyVXaU=
golang.org/x/exp/shiny v0.0.0-20241009180824-f66d83c29e7c/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
golang.org/x/exp/shiny v0.0.0-20241108190413-2d47ceb2692f h1:vic/+xALPgqKNmH4tJ2tsJh9M51ULiNg5hoQd84DVM4=
golang.org/x/exp/shiny v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:3F+MieQB7dRYLTmnncoFbb1crS5lfQoTfDgQy6K4N0o=
golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g=
golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4=
golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c h1:zuNS/LWsEpPTLfrmBkis6Xofw3nieAqB4hYLn8+uswk=
golang.org/x/mobile v0.0.0-20241016134751-7ff83004ec2c/go.mod h1:snk1Mn2ZpdKCt90JPEsDh4sL3ReK520U2t0d7RHBnSU=
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f h1:23H/YlmTHfmmvpZ+ajKZL0qLz0+IwFOIqQA0mQbmLeM=
golang.org/x/mobile v0.0.0-20241108191957-fa514ef75a0f/go.mod h1:UbSUP4uu/C9hw9R2CkojhXlAxvayHjBdU9aRvE+c1To=
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=

View file

@ -23,7 +23,7 @@ type Input struct {
// NewInput returns a new Input widget.
func NewInput(text string, onSelected func(text string) (handled bool)) *Input {
f := messeji.NewInputField(FontFace(Style.TextFont, Scale(Style.TextSize)), fontMutex)
f := messeji.NewInputField(Style.TextFont, Scale(Style.TextSize), fontMutex)
f.SetForegroundColor(Style.TextColorLight)
f.SetBackgroundColor(transparent)
f.SetScrollBarColors(Style.ScrollAreaColor, Style.ScrollHandleColor)
@ -191,7 +191,7 @@ func (t *Input) SetFont(fnt *text.GoTextFaceSource, size int) {
t.Lock()
defer t.Unlock()
t.field.SetFont(FontFace(fnt, size), fontMutex)
t.field.SetFont(fnt, size, fontMutex)
}
// Padding returns the amount of padding around the text within the field.

View file

@ -28,20 +28,17 @@ Below is an InputField, which accepts keyboard input.
`
var (
mplusNormalFont *text.GoTextFace
fontMutex *sync.Mutex
fontSource *text.GoTextFaceSource
fontSize = 24
fontMutex *sync.Mutex
)
func init() {
source, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
var err error
fontSource, err = text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
if err != nil {
panic(err)
}
mplusNormalFont = &text.GoTextFace{
Source: source,
Size: 24,
}
}
type game struct {
@ -63,8 +60,8 @@ type game struct {
// NewDemoGame returns a new messeji demo game.
func NewDemoGame() *game {
g := &game{
buffer: messeji.NewTextField(mplusNormalFont, fontMutex),
input: messeji.NewInputField(mplusNormalFont, fontMutex),
buffer: messeji.NewTextField(fontSource, fontSize, fontMutex),
input: messeji.NewInputField(fontSource, fontSize, fontMutex),
op: &ebiten.DrawImageOptions{
Filter: ebiten.FilterNearest,
},

View file

@ -40,9 +40,9 @@ type InputField struct {
}
// NewInputField returns a new InputField. See type documentation for more info.
func NewInputField(face *text.GoTextFace, faceMutex *sync.Mutex) *InputField {
func NewInputField(fontSource *text.GoTextFaceSource, fontSize int, fontMutex *sync.Mutex) *InputField {
f := &InputField{
TextField: NewTextField(face, faceMutex),
TextField: NewTextField(fontSource, fontSize, fontMutex),
}
f.TextField.suffix = "_"
return f

View file

@ -94,11 +94,22 @@ type TextField struct {
// vertical is the vertical alignment of the text within field.
vertical Alignment
// face is the font face of the text within the field.
face *text.GoTextFace
autoResize bool
// faceMutex is the lock which is held whenever utilizing the font face.
faceMutex *sync.Mutex
// fontSource is the font face source of the text within the field.
fontSource *text.GoTextFaceSource
// fontFace is the font face of the text within the field.
fontFace *text.GoTextFace
// fontSize is the maximum font size of the text within the field.
fontSize int
// overrideFontSize is the actual font size of the text within the field.
overrideFontSize int
// fontMutex is the lock which is held whenever utilizing the font.
fontMutex *sync.Mutex
// lineHeight is the height of a single line of text.
lineHeight int
@ -188,14 +199,15 @@ type TextField struct {
}
// NewTextField returns a new TextField. See type documentation for more info.
func NewTextField(face *text.GoTextFace, faceMutex *sync.Mutex) *TextField {
if faceMutex == nil {
faceMutex = &sync.Mutex{}
func NewTextField(fontSource *text.GoTextFaceSource, fontSize int, fontMutex *sync.Mutex) *TextField {
if fontMutex == nil {
fontMutex = &sync.Mutex{}
}
f := &TextField{
face: face,
faceMutex: faceMutex,
fontSource: fontSource,
fontSize: fontSize,
fontMutex: fontMutex,
textColor: initialForeground,
backgroundColor: initialBackground,
padding: initialPadding,
@ -210,10 +222,10 @@ func NewTextField(face *text.GoTextFace, faceMutex *sync.Mutex) *TextField {
redraw: true,
}
f.faceMutex.Lock()
defer f.faceMutex.Unlock()
f.fontMutex.Lock()
defer f.fontMutex.Unlock()
f.fontUpdated()
f.resizeFont()
return f
}
@ -243,6 +255,12 @@ func (f *TextField) SetRect(r image.Rectangle) {
}
f.r = r
f.resizeFont()
}
func (f *TextField) text() string {
f.processIncoming()
return string(bytes.Join(f.buffer, []byte("\n")))
}
// Text returns the text in the field.
@ -250,9 +268,7 @@ func (f *TextField) Text() string {
f.Lock()
defer f.Unlock()
f.processIncoming()
return string(bytes.Join(f.buffer, []byte("\n")))
return f.text()
}
// SetText sets the text in the field.
@ -268,6 +284,7 @@ func (f *TextField) SetText(text string) {
f.incoming = append(f.incoming[:0], []byte(text)...)
f.modified = true
f.redraw = true
f.resizeFont()
}
// SetLast sets the text of the last line of the field. Newline characters are
@ -292,6 +309,7 @@ func (f *TextField) SetLast(text string) {
f.modified = true
f.redraw = true
f.resizeFont()
}
// SetPrefix sets the text shown before the content of the field.
@ -303,6 +321,7 @@ func (f *TextField) SetPrefix(text string) {
f.needWrap = 0
f.wrapStart = 0
f.modified = true
f.resizeFont()
}
// SetSuffix sets the text shown after the content of the field.
@ -314,6 +333,7 @@ func (f *TextField) SetSuffix(text string) {
f.needWrap = 0
f.wrapStart = 0
f.modified = true
f.resizeFont()
}
// SetFollow sets whether the field should automatically scroll to the end when
@ -339,6 +359,7 @@ func (f *TextField) SetSingleLine(single bool) {
f.needWrap = 0
f.wrapStart = 0
f.modified = true
f.resizeFont()
}
// SetHorizontal sets the horizontal alignment of the text within the field.
@ -392,6 +413,7 @@ func (f *TextField) SetLineHeight(height int) {
f.needWrap = 0
f.wrapStart = 0
f.modified = true
f.resizeFont()
}
// ForegroundColor returns the color of the text within the field.
@ -421,7 +443,7 @@ func (f *TextField) SetBackgroundColor(c color.RGBA) {
}
// SetFont sets the font face of the text within the field.
func (f *TextField) SetFont(face *text.GoTextFace, mutex *sync.Mutex) {
func (f *TextField) SetFont(faceSource *text.GoTextFaceSource, size int, mutex *sync.Mutex) {
if mutex == nil {
mutex = &sync.Mutex{}
}
@ -432,13 +454,25 @@ func (f *TextField) SetFont(face *text.GoTextFace, mutex *sync.Mutex) {
mutex.Lock()
defer mutex.Unlock()
f.face = face
f.faceMutex = mutex
f.fontUpdated()
f.fontSource = faceSource
f.fontSize = size
f.fontMutex = mutex
f.overrideFontSize = 0
f.needWrap = 0
f.wrapStart = 0
f.modified = true
f.resizeFont()
}
// SetAutoResize sets whether the font is automatically scaled down when it is
// too large to fit the entire text buffer on one line.
func (f *TextField) SetAutoResize(resize bool) {
f.Lock()
defer f.Unlock()
f.autoResize = resize
f.resizeFont()
}
// Padding returns the amount of padding around the text within the field.
@ -458,6 +492,7 @@ func (f *TextField) SetPadding(padding int) {
f.needWrap = 0
f.wrapStart = 0
f.modified = true
f.resizeFont()
}
// Visible returns whether the field is currently visible on the screen.
@ -493,6 +528,7 @@ func (f *TextField) SetScrollBarWidth(width int) {
f.needWrap = 0
f.wrapStart = 0
f.modified = true
f.resizeFont()
}
// SetScrollBarColors sets the color of the scroll bar area and handle.
@ -580,6 +616,53 @@ func (f *TextField) SetWordWrap(wrap bool) {
f.modified = true
}
func (f *TextField) resizeFont() {
if !f.autoResize {
if f.overrideFontSize == f.fontSize {
return
}
f.overrideFontSize = f.fontSize
f.fontFace = fontFace(f.fontSource, f.overrideFontSize)
f.fontUpdated()
f.bufferModified()
return
}
w, h := f.r.Dx()-f.padding*2, f.r.Dy()-f.padding*2
if w <= 0 || h <= 0 {
if f.overrideFontSize == f.fontSize {
return
}
f.overrideFontSize = f.fontSize
f.fontFace = fontFace(f.fontSource, f.overrideFontSize)
f.fontUpdated()
f.bufferModified()
return
}
buffer := f.text()
var size int
var ff *text.GoTextFace
var m text.Metrics
for size = f.fontSize; size > 1; size-- {
ff = fontFace(f.fontSource, size)
m = ff.Metrics()
bw, bh := text.Measure(buffer, ff, m.HAscent+m.HDescent)
if int(bw) <= w && int(bh) <= h {
break
}
}
if f.overrideFontSize == size {
return
}
f.overrideFontSize = size
f.fontFace = ff
f.fontUpdated()
f.bufferModified()
}
// SetHandleKeyboard sets a flag controlling whether keyboard input should be handled
// by the field. This can be used to facilitate focus changes between multiple inputs.
func (f *TextField) SetHandleKeyboard(handle bool) {
@ -600,6 +683,7 @@ func (f *TextField) SetMask(r rune) {
f.maskRune = r
f.modified = true
f.resizeFont()
}
// Write writes to the field's buffer.
@ -763,12 +847,12 @@ func (f *TextField) Draw(screen *ebiten.Image) {
defer f.Unlock()
if f.modified {
f.faceMutex.Lock()
f.fontMutex.Lock()
f.bufferModified()
f.modified = false
f.faceMutex.Unlock()
f.fontMutex.Unlock()
}
if !f.visible || rectIsZero(f.r) {
@ -776,12 +860,12 @@ func (f *TextField) Draw(screen *ebiten.Image) {
}
if f.redraw {
f.faceMutex.Lock()
f.fontMutex.Lock()
f.drawImage()
f.redraw = false
f.faceMutex.Unlock()
f.fontMutex.Unlock()
}
op := &ebiten.DrawImageOptions{}
@ -790,7 +874,7 @@ func (f *TextField) Draw(screen *ebiten.Image) {
}
func (f *TextField) fontUpdated() {
m := f.face.Metrics()
m := f.fontFace.Metrics()
f.lineHeight = int(m.HAscent + m.HDescent)
f.lineOffset = int(m.HLineGap)
if f.lineOffset < 0 {
@ -807,9 +891,9 @@ func (f *TextField) wrapContent(withScrollBar bool) {
}
f.wrapScrollBar = withScrollBar
if f.singleLine || len(f.buffer) == 0 {
if len(f.buffer) == 0 || (f.singleLine && !f.autoResize) {
buffer := f.prefix + string(bytes.Join(f.buffer, nil)) + f.suffix
w, _ := text.Measure(buffer, f.face, float64(f.lineHeight))
w, _ := text.Measure(buffer, f.fontFace, float64(f.lineHeight))
f.bufferWrapped = []string{buffer}
f.wrapStart = 0
@ -836,7 +920,7 @@ func (f *TextField) wrapContent(withScrollBar bool) {
line += f.suffix
}
l := len(line)
availableWidth := w - (f.padding * 2) - 5
availableWidth := w - (f.padding * 2) - 15
f.wrapStart = j
@ -864,29 +948,31 @@ func (f *TextField) wrapContent(withScrollBar bool) {
WRAPTEXT:
for start < l {
lastSpace = -1
for e, r := range line[start:] {
if unicode.IsSpace(r) {
lastSpace = start + e + 1
var e int
for _, r := range line[start:] {
if e > l-start {
e = l - start
}
w, _ := text.Measure(line[start:start+e]+string(r), f.face, float64(f.lineHeight))
runeLength := len(string(r))
if unicode.IsSpace(r) {
lastSpace = start + e
}
w, _ := text.Measure(line[start:start+e], f.fontFace, float64(f.lineHeight))
boundsWidth = int(w)
if boundsWidth > availableWidth {
original := e
if e != l-start-1 && e > 0 {
e--
if e > 0 {
e -= runeLength
}
if f.wordWrap && lastSpace != -1 && start+e+1 < l {
e = lastSpace - start - 1
}
if e != original {
w, _ := text.Measure(line[start:start+e+1], f.face, float64(f.lineHeight))
boundsWidth = int(w)
if f.wordWrap && lastSpace != -1 && start+e+runeLength < l {
e = lastSpace - start
}
w, _ := text.Measure(line[start:start+e], f.fontFace, float64(f.lineHeight))
boundsWidth = int(w)
if len(f.bufferWrapped) <= j {
f.bufferWrapped = append(f.bufferWrapped, line[start:start+e+1])
f.bufferWrapped = append(f.bufferWrapped, line[start:start+e])
} else {
f.bufferWrapped[j] = line[start : start+e+1]
f.bufferWrapped[j] = line[start : start+e]
}
if len(f.lineWidths) <= j {
f.lineWidths = append(f.lineWidths, boundsWidth)
@ -895,11 +981,14 @@ func (f *TextField) wrapContent(withScrollBar bool) {
}
j++
start = start + e + 1
start += e + runeLength
continue WRAPTEXT
}
e += runeLength
}
w, _ := text.Measure(line[start:], f.fontFace, float64(f.lineHeight))
boundsWidth = int(w)
if len(f.bufferWrapped) <= j {
f.bufferWrapped = append(f.bufferWrapped, line[start:])
} else {
@ -954,7 +1043,7 @@ func (f *TextField) drawContent() (overflow bool) {
numVisible := lastVisible - firstVisible
// Calculate buffer size (width for single-line fields or height for multi-line fields).
if f.singleLine {
w, _ := text.Measure(f.bufferWrapped[firstVisible], f.face, float64(f.lineHeight))
w, _ := text.Measure(f.bufferWrapped[firstVisible], f.fontFace, float64(f.lineHeight))
f.bufferSize = int(w)
if f.bufferSize > fieldWidth-f.padding*2 {
overflow = true
@ -1003,9 +1092,9 @@ func (f *TextField) drawContent() (overflow bool) {
// Align vertically.
totalHeight := f.lineOffset + lineHeight*(lines)
if f.vertical == AlignCenter && totalHeight <= h {
if f.vertical == AlignCenter && (f.autoResize || totalHeight <= h) {
lineY = fieldHeight/2 - totalHeight/2 + f.lineOffset + (lineHeight * (i)) - 2
} else if f.vertical == AlignEnd && totalHeight <= h {
} else if f.vertical == AlignEnd && (f.autoResize || totalHeight <= h) {
lineY = fieldHeight - lineHeight*(numVisible+1-i) - f.padding
}
@ -1013,7 +1102,7 @@ func (f *TextField) drawContent() (overflow bool) {
op := &text.DrawOptions{}
op.GeoM.Translate(float64(lineX), float64(lineY))
op.ColorScale.ScaleWithColor(f.textColor)
text.Draw(f.img, line, f.face, op)
text.Draw(f.img, line, f.fontFace, op)
}
return overflow
@ -1040,10 +1129,24 @@ func (f *TextField) clampOffset() {
}
func (f *TextField) showScrollBar() bool {
return !f.singleLine && f.scrollVisible && (f.overflow || !f.scrollAutoHide)
return !f.autoResize && !f.singleLine && f.scrollVisible && (f.overflow || !f.scrollAutoHide)
}
func (f *TextField) wrap() {
w, h := f.r.Dx(), f.r.Dy()
var newImage bool
if f.img == nil {
newImage = true
} else {
imgRect := f.img.Bounds()
imgW, imgH := imgRect.Dx(), imgRect.Dy()
newImage = imgW != w || imgH != h
}
if newImage {
f.img = ebiten.NewImage(w, h)
}
showScrollBar := f.showScrollBar()
f.wrapContent(showScrollBar)
f.overflow = f.drawContent()
@ -1060,24 +1163,12 @@ func (f *TextField) drawImage() {
return
}
w, h := f.r.Dx(), f.r.Dy()
var newImage bool
if f.img == nil {
newImage = true
} else {
imgRect := f.img.Bounds()
imgW, imgH := imgRect.Dx(), imgRect.Dy()
newImage = imgW != w || imgH != h
}
if newImage {
f.img = ebiten.NewImage(w, h)
}
f.wrap()
// Draw scrollbar.
if f.showScrollBar() {
w, h := f.r.Dx(), f.r.Dy()
scrollAreaX, scrollAreaY := w-f.scrollWidth, 0
f.scrollRect = image.Rect(scrollAreaX, scrollAreaY, scrollAreaX+f.scrollWidth, h)
@ -1152,3 +1243,10 @@ func (f *TextField) bufferModified() {
func rectIsZero(r image.Rectangle) bool {
return r.Dx() == 0 || r.Dy() == 0
}
func fontFace(source *text.GoTextFaceSource, size int) *text.GoTextFace {
return &text.GoTextFace{
Source: source,
Size: float64(size),
}
}

View file

@ -31,10 +31,8 @@ func testFace() (*text.GoTextFace, error) {
}
func TestWrapContent(t *testing.T) {
face, err := testFace()
if err != nil {
t.Error(err)
}
const fontSize = 24
fontSource := defaultFont()
testCases := []struct {
long bool // Test data type.
@ -69,7 +67,7 @@ func TestWrapContent(t *testing.T) {
}
t.Run(name, func(t *testing.T) {
textField := NewTextField(face, &sync.Mutex{})
textField := NewTextField(fontSource, fontSize, &sync.Mutex{})
testTextField = textField
textField.SetRect(testRect)
textField.SetWordWrap(c.wordWrap)
@ -80,10 +78,8 @@ func TestWrapContent(t *testing.T) {
}
func BenchmarkWrapContent(b *testing.B) {
face, err := testFace()
if err != nil {
b.Error(err)
}
const fontSize = 24
fontSource := defaultFont()
testCases := []struct {
long bool // Test data type.
@ -117,7 +113,7 @@ func BenchmarkWrapContent(b *testing.B) {
name += "/wrapWord"
}
textField := NewTextField(face, &sync.Mutex{})
textField := NewTextField(fontSource, fontSize, &sync.Mutex{})
testTextField = textField
textField.SetRect(testRect)
textField.SetWordWrap(c.wordWrap)
@ -131,3 +127,11 @@ func BenchmarkWrapContent(b *testing.B) {
})
}
}
func defaultFont() *text.GoTextFaceSource {
source, err := text.NewGoTextFaceSource(bytes.NewReader(fonts.MPlus1pRegular_ttf))
if err != nil {
panic(err)
}
return source
}

67
text.go
View file

@ -15,8 +15,6 @@ type Text struct {
field *messeji.TextField
textFont *text.GoTextFaceSource
textSize int
textResize bool
textAutoSize int
scrollVisible bool
children []Widget
}
@ -35,7 +33,6 @@ func NewText(text string) *Text {
textSize: Scale(Style.TextSize),
scrollVisible: true,
}
t.resizeFont()
return t
}
@ -46,7 +43,6 @@ func (t *Text) SetRect(r image.Rectangle) {
t.rect = r
t.field.SetRect(r)
t.resizeFont()
}
// Foreground return the color of the text within the field.
@ -139,7 +135,6 @@ func (t *Text) Write(p []byte) (n int, err error) {
if err != nil {
return n, err
}
t.resizeFont()
return n, err
}
@ -157,7 +152,6 @@ func (t *Text) SetText(text string) {
defer t.Unlock()
t.field.SetText(text)
t.resizeFont()
}
// SetLast sets the text of the last line of the field.
@ -166,52 +160,18 @@ func (t *Text) SetLast(text string) {
defer t.Unlock()
t.field.SetLast(text)
t.resizeFont()
}
func (t *Text) resizeFont() {
if !t.textResize {
if t.textAutoSize == t.textSize {
return
}
t.textAutoSize = t.textSize
ff := FontFace(t.textFont, t.textSize)
t.field.SetFont(ff, fontMutex)
return
}
// SetAutoResize sets whether the font is automatically scaled down when it is
// too large to fit the entire text buffer on one line.
func (t *Text) SetAutoResize(resize bool) {
t.Lock()
defer t.Unlock()
w, h := t.rect.Dx()-t.field.Padding()*2, t.rect.Dy()-t.field.Padding()*2
if w == 0 || h == 0 {
if t.textAutoSize == t.textSize {
return
}
t.textAutoSize = t.textSize
ff := FontFace(t.textFont, t.textSize)
t.field.SetFont(ff, fontMutex)
return
}
var autoSize int
var ff *text.GoTextFace
for autoSize = t.textSize; autoSize > 0; autoSize-- {
ff = FontFace(t.textFont, autoSize)
bounds := BoundString(ff, t.field.Text())
if bounds.Dx() <= w && bounds.Dy() <= h {
break
}
}
if t.textAutoSize == autoSize {
return
}
t.field.SetFont(ff, fontMutex)
t.textAutoSize = autoSize
t.field.SetAutoResize(resize)
}
func (t *Text) scrollBarVisible() bool {
if t.textResize {
return false
}
return t.scrollVisible
}
@ -238,7 +198,7 @@ func (t *Text) FontSize() int {
t.Lock()
defer t.Unlock()
return t.textAutoSize
return t.textSize
}
// SetFont sets the font and text size of the field. Scaling is not applied.
@ -247,18 +207,7 @@ func (t *Text) SetFont(fnt *text.GoTextFaceSource, size int) {
defer t.Unlock()
t.textFont, t.textSize = fnt, size
t.resizeFont()
}
// SetAutoResize sets whether the font is automatically scaled down when it is
// too large to fit the entire text buffer on one line.
func (t *Text) SetAutoResize(resize bool) {
t.Lock()
defer t.Unlock()
t.textResize = resize
t.resizeFont()
t.field.SetScrollBarVisible(t.scrollBarVisible())
t.field.SetFont(t.textFont, t.textSize, fontMutex)
}
// Padding returns the amount of padding around the text within the field.