From 6adb1b0d9390adbddcd77df2fe94db86428076b2 Mon Sep 17 00:00:00 2001 From: Kevin Z Date: Sun, 10 Dec 2023 07:31:25 -0700 Subject: [PATCH] Fix #280, blur font on darwin (#298) * fix #280 Font's on retina will be blur cause Rentina have 2x DPI Now we times the DPI by 2 when on darwin, and scale the Label panel by 0.5 to keep the original size Since we scaled the panel size back, so it should not affect much on non-Rentina darwin However, if you are using non-darwin with rentina monitor, issue #280 will still happen, need a way to detect the monitor * remove a debug log * we should devide on int but not floor, or the text will be out of shape * Revert "we should devide on int but not floor, or the text will be out of shape" I was completely wrong, we should divide on the floor or the text won't keep it's original size This reverts commit 73f1b6a320f90cfec9de8878ec06fa351966beaf. * use `window.Get().GetScale()` instead of hardcoded size and os * use explicit rune casting to pass `go test` * fix format for font.go * fix for Edit --- gui/builder.go | 2 +- gui/edit.go | 10 ++++--- gui/label.go | 14 ++++++++-- text/font.go | 76 +++++++++++++++++++++++++++++++++++--------------- 4 files changed, 73 insertions(+), 29 deletions(-) diff --git a/gui/builder.go b/gui/builder.go index 1c08c0b8..98131b31 100644 --- a/gui/builder.go +++ b/gui/builder.go @@ -879,7 +879,7 @@ func AttribCheckIcons(b *Builder, am map[string]interface{}, fname string) error if err != nil { return b.err(am, fname, fmt.Sprintf("Invalid icon codepoint value/name:%v", parts[i])) } - text += string(val) + text += string(rune(val)) } am[fname] = text return nil diff --git a/gui/edit.go b/gui/edit.go index 89231d53..b16bdbb5 100644 --- a/gui/edit.go +++ b/gui/edit.go @@ -394,7 +394,7 @@ func (ed *Edit) CursorInput(s string) { // Checks if new text exceeds edit width width, _ := ed.Label.font.MeasureText(newText) - if float32(width)+editMarginX+float32(1) >= ed.Label.ContentWidth() { + if float32(width) / float32(ed.Label.font.ScaleX()) + editMarginX + float32(1) >= ed.Label.ContentWidth() { return } @@ -412,7 +412,8 @@ func (ed *Edit) CursorInput(s string) { func (ed *Edit) redraw(caret bool) { line := 0 - ed.Label.setTextCaret(ed.text, editMarginX, ed.width, caret, line, ed.col, ed.selStart, ed.selEnd) + scaleX, _ := window.Get().GetScale() + ed.Label.setTextCaret(ed.text, editMarginX, int(float64(ed.width) * scaleX), caret, line, ed.col, ed.selStart, ed.selEnd) } // onKey receives subscribed key events @@ -499,7 +500,7 @@ func (ed *Edit) handleMouse(mouseX float32, dragged bool) { for nchars = 1; nchars <= text.StrCount(ed.text); nchars++ { width, _ := ed.Label.font.MeasureText(text.StrPrefix(ed.text, nchars)) posx := mouseX - ed.pospix.X - if posx < editMarginX+float32(width) { + if posx < editMarginX + float32(float64(width) / ed.Label.font.ScaleX()) { break } } @@ -597,8 +598,9 @@ func (ed *Edit) applyStyle(s *EditStyle) { //ed.Label.SetBgAlpha(s.BgAlpha) if !ed.focus && len(ed.text) == 0 && len(ed.placeHolder) > 0 { + scaleX, _ := window.Get().GetScale() ed.Label.SetColor4(&s.HolderColor) - ed.Label.setTextCaret(ed.placeHolder, editMarginX, ed.width, false, -1, ed.col, ed.selStart, ed.selEnd) + ed.Label.setTextCaret(ed.placeHolder, editMarginX, int(float64(ed.width) * scaleX), false, -1, ed.col, ed.selStart, ed.selEnd) } else { ed.Label.SetColor4(&s.FgColor) ed.redraw(ed.focus) diff --git a/gui/label.go b/gui/label.go index 66ebb9a2..a939ae48 100644 --- a/gui/label.go +++ b/gui/label.go @@ -9,6 +9,7 @@ import ( "github.com/xackery/engine/math32" "github.com/xackery/engine/text" "github.com/xackery/engine/texture" + "github.com/xackery/engine/window" ) // Label is a panel which contains a texture with text. @@ -83,6 +84,9 @@ func (l *Label) SetText(text string) { l.font.SetAttributes(&l.style.FontAttributes) l.font.SetColor(&l.style.FgColor) + scaleX, scaleY := window.Get().GetScale() + l.font.SetScaleXY(scaleX, scaleY) + // Create an image with the text textImage := l.font.DrawText(text) @@ -98,7 +102,10 @@ func (l *Label) SetText(text string) { } // Update label panel dimensions - l.Panel.SetContentSize(float32(textImage.Rect.Dx()), float32(textImage.Rect.Dy())) + width, height := float32(textImage.Rect.Dx()), float32(textImage.Rect.Dy()) + // since we enlarged the font texture for higher quality, we have to scale it back to it's original point size + width, height = width/float32(scaleX), height/float32(scaleY) + l.Panel.SetContentSize(width, height) } // Text returns the label text. @@ -219,6 +226,9 @@ func (l *Label) setTextCaret(msg string, mx, width int, drawCaret bool, line, co l.font.SetAttributes(&l.style.FontAttributes) l.font.SetColor(&l.style.FgColor) + scaleX, scaleY := window.Get().GetScale() + l.font.SetScaleXY(scaleX, scaleY) + // Create canvas and draw text _, height := l.font.MeasureText(msg) canvas := text.NewCanvas(width, height, &l.style.BgColor) @@ -237,6 +247,6 @@ func (l *Label) setTextCaret(msg string, mx, width int, drawCaret bool, line, co l.tex.SetMinFilter(gls.NEAREST) // Updates label panel dimensions - l.Panel.SetContentSize(float32(width), float32(height)) + l.Panel.SetContentSize(float32(width)/float32(scaleX), float32(height)/float32(scaleY)) l.text = msg } diff --git a/text/font.go b/text/font.go index f0070bad..5ec84e3a 100644 --- a/text/font.go +++ b/text/font.go @@ -9,6 +9,7 @@ import ( "image/color" "image/draw" "io/ioutil" + "math" "strings" "github.com/golang/freetype/truetype" @@ -20,12 +21,13 @@ import ( // Font represents a TrueType font face. // Attributes must be set prior to drawing. type Font struct { - ttf *truetype.Font // The TrueType font - face font.Face // The font face - attrib FontAttributes // Internal attribute cache - fg *image.Uniform // Text color cache - bg *image.Uniform // Background color cache - changed bool // Whether attributes have changed and the font face needs to be recreated + ttf *truetype.Font // The TrueType font + face font.Face // The font face + attrib FontAttributes // Internal attribute cache + fg *image.Uniform // Text color cache + bg *image.Uniform // Background color cache + scaleX, scaleY float64 // Scales of actual pixel/GL point, used for fix Retina Monitor + changed bool // Whether attributes have changed and the font face needs to be recreated } // FontAttributes contains tunable attributes of a font. @@ -36,6 +38,18 @@ type FontAttributes struct { Hinting font.Hinting // Font hinting } +func (a *FontAttributes) newTTOptions(scaleX, scaleY float64) *truetype.Options { + dpi := a.DPI + if scaleX != 0 && scaleY != 0 { + dpi *= math.Sqrt(scaleX * scaleY) + } + return &truetype.Options{ + Size: a.PointSize, + DPI: dpi, + Hinting: a.Hinting, + } +} + // Font Hinting types. const ( HintingNone = font.HintingNone @@ -75,11 +89,7 @@ func NewFontFromData(fontData []byte) (*Font, error) { f.SetColor(&math32.Color4{0, 0, 0, 1}) // Create font face - f.face = truetype.NewFace(f.ttf, &truetype.Options{ - Size: f.attrib.PointSize, - DPI: f.attrib.DPI, - Hinting: f.attrib.Hinting, - }) + f.face = truetype.NewFace(f.ttf, f.attrib.newTTOptions(f.scaleX, f.scaleY)) return f, nil } @@ -124,6 +134,29 @@ func (f *Font) SetHinting(hinting font.Hinting) { f.changed = true } +func (f *Font) ScaleXY() (x, y float64) { + return f.scaleX, f.scaleY +} + +func (f *Font) ScaleX() float64 { + return f.scaleX +} + +func (f *Font) ScaleY() float64 { + return f.scaleY +} + +// SetScale sets the ratio of actual pixel/GL point. +func (f *Font) SetScaleXY(x, y float64) { + + if x == f.scaleX && y == f.scaleY { + return + } + f.scaleX = x + f.scaleY = y + f.changed = true +} + // SetFgColor sets the text color. func (f *Font) SetFgColor(color *math32.Color4) { @@ -158,11 +191,7 @@ func (f *Font) SetAttributes(fa *FontAttributes) { func (f *Font) updateFace() { if f.changed { - f.face = truetype.NewFace(f.ttf, &truetype.Options{ - Size: f.attrib.PointSize, - DPI: f.attrib.DPI, - Hinting: f.attrib.Hinting, - }) + f.face = truetype.NewFace(f.ttf, f.attrib.newTTOptions(f.scaleX, f.scaleY)) f.changed = false } } @@ -277,6 +306,7 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, drawCaret bool, li d := &font.Drawer{Dst: c.RGBA, Src: f.fg, Face: f.face} // Draw text + actualPointSize := int(f.attrib.PointSize * f.scaleY) metrics := f.face.Metrics() py := y + metrics.Ascent.Round() lineHeight := (metrics.Ascent + metrics.Descent).Ceil() @@ -291,8 +321,8 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, drawCaret bool, li // TODO This will not work when the selection spans multiple lines // Currently there is no multiline edit text // Once there is, this needs to change - caretH := int(f.attrib.PointSize) + 2 - caretY := int(d.Dot.Y>>6) - int(f.attrib.PointSize) + 2 + caretH := actualPointSize + 2 + caretY := int(d.Dot.Y>>6) - actualPointSize + 2 color := Color4RGBA(&math32.Color4{0, 0, 1, 0.5}) // Hardcoded to blue, alpha 50% for w := width; w < widthEnd; w++ { for j := caretY; j < caretY+caretH; j++ { @@ -305,11 +335,13 @@ func (c Canvas) DrawTextCaret(x, y int, text string, f *Font, drawCaret bool, li if drawCaret && l == line && col <= StrCount(s) { width, _ := f.MeasureText(StrPrefix(s, col)) // Draw caret vertical line - caretH := int(f.attrib.PointSize) + 2 - caretY := int(d.Dot.Y>>6) - int(f.attrib.PointSize) + 2 + caretH := actualPointSize + 2 + caretY := int(d.Dot.Y>>6) - actualPointSize + 2 color := Color4RGBA(&math32.Color4{0, 0, 0, 1}) // Hardcoded to black - for j := caretY; j < caretY+caretH; j++ { - c.RGBA.Set(x+width, j, color) + for i := 0; i < int(f.scaleX); i++ { + for j := caretY; j < caretY+caretH; j++ { + c.RGBA.Set(x+width+i, j, color) + } } } py += lineHeight