From 6a67368bddd35ccbd8ff957883abe203a228f4b5 Mon Sep 17 00:00:00 2001 From: Gabe Cook Date: Sat, 17 Aug 2024 22:53:38 -0500 Subject: [PATCH] feat(dynamicicon): Automatically determine the max font size --- internal/config/config.go | 8 +-- internal/config/default.go | 6 +- internal/dynamicicon/dynamicicon.go | 98 +++++++++++++++------------- internal/tray/systray.go | 2 +- test.png | Bin 0 -> 624 bytes 5 files changed, 59 insertions(+), 55 deletions(-) create mode 100644 test.png diff --git a/internal/config/config.go b/internal/config/config.go index 7f6b35e..405938e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,10 +25,10 @@ type Config struct { } type DynamicIcon struct { - Enabled bool `toml:"enabled"` - FontColor HexColor `toml:"font_color" comment:"Hex code used to render text."` - FontFile string `toml:"font_file" comment:"If left blank, an embedded font will be used."` - FontSize float64 `toml:"font_size" comment:"Font size in points."` + Enabled bool `toml:"enabled"` + FontColor HexColor `toml:"font_color" comment:"Hex code used to render text."` + FontFile string `toml:"font_file" comment:"If left blank, an embedded font will be used."` + MaxFontSize float64 `toml:"max_font_size" comment:"Maximum font size in points."` } type Arrows struct { diff --git a/internal/config/default.go b/internal/config/default.go index 4e012f6..18f65bd 100644 --- a/internal/config/default.go +++ b/internal/config/default.go @@ -16,9 +16,9 @@ func New() *Config { Title: "Nightscout", Units: UnitsMgdl, DynamicIcon: DynamicIcon{ - Enabled: true, - FontColor: White(), - FontSize: 28, + Enabled: true, + FontColor: White(), + MaxFontSize: 40, }, Arrows: Arrows{ DoubleUp: "⇈", diff --git a/internal/dynamicicon/dynamicicon.go b/internal/dynamicicon/dynamicicon.go index 7185b4a..e14b213 100644 --- a/internal/dynamicicon/dynamicicon.go +++ b/internal/dynamicicon/dynamicicon.go @@ -3,13 +3,12 @@ package dynamicicon import ( "bytes" _ "embed" + "errors" "image" "image/draw" "os" "sync" - "fyne.io/systray" - "github.com/gabe565/nightscout-menu-bar/internal/assets" "github.com/gabe565/nightscout-menu-bar/internal/config" "github.com/gabe565/nightscout-menu-bar/internal/nightscout" "github.com/golang/freetype/truetype" @@ -17,52 +16,37 @@ import ( "golang.org/x/image/math/fixed" ) +const ( + width, height = 32, 32 + widthF, heightF = fixed.Int26_6(width << 6), fixed.Int26_6(height << 6) +) + //go:embed Inconsolata_Condensed-Black.ttf var defaultFont []byte type DynamicIcon struct { - config *config.Config - mu sync.Mutex - callbackOffset int + config *config.Config + mu sync.Mutex - face font.Face - drawer *font.Drawer - img *image.RGBA + font *truetype.Font + img *image.RGBA } func New(conf *config.Config) *DynamicIcon { - d := &DynamicIcon{config: conf} - d.callbackOffset = conf.AddCallback(d.reloadConfig) - return d -} - -func (d *DynamicIcon) Close() error { - d.mu.Lock() - defer d.mu.Unlock() - var err error - if d.face != nil { - err = d.face.Close() + d := &DynamicIcon{ + config: conf, + img: image.NewRGBA(image.Rectangle{Max: image.Point{X: width, Y: height}}), } - d.config.RemoveCallback(d.callbackOffset) - systray.SetTemplateIcon(assets.Nightscout, assets.Nightscout) - return err + return d } -func (d *DynamicIcon) reloadConfig() { - _ = d.Close() - d.config.AddCallback(d.reloadConfig) -} +var ErrFontSize = errors.New("unable to determine the correct font size") func (d *DynamicIcon) Generate(p *nightscout.Properties) ([]byte, error) { d.mu.Lock() defer d.mu.Unlock() - const ( - width, height = 32, 32 - widthF, heightF = fixed.Int26_6(width << 6), fixed.Int26_6(height << 6) - ) - - if d.face == nil { + if d.font == nil { var b []byte if d.config.DynamicIcon.FontFile == "" { b = defaultFont @@ -78,27 +62,47 @@ func (d *DynamicIcon) Generate(p *nightscout.Properties) ([]byte, error) { return nil, err } - d.face = truetype.NewFace(f, &truetype.Options{ - Size: d.config.DynamicIcon.FontSize, + d.font = f + } + + bgnow := p.Bgnow.DisplayBg(d.config.Units) + + var face font.Face + defer func() { + if face != nil { + _ = face.Close() + } + }() + + drawer := &font.Drawer{ + Dst: d.img, + Src: image.NewUniform(d.config.DynamicIcon.FontColor.RGBA()), + } + + fontSize := d.config.DynamicIcon.MaxFontSize + for { + face = truetype.NewFace(d.font, &truetype.Options{ + Size: fontSize, }) + drawer.Face = face - d.img = image.NewRGBA(image.Rectangle{Max: image.Point{X: width, Y: height}}) + if textWidth := drawer.MeasureString(bgnow); textWidth <= widthF { + break + } - m := d.face.Metrics() - src := image.NewUniform(d.config.DynamicIcon.FontColor.RGBA()) - d.drawer = &font.Drawer{ - Dst: d.img, - Src: src, - Face: d.face, - Dot: fixed.Point26_6{Y: (heightF + m.Ascent - m.Descent) / 2}, + _ = face.Close() + if fontSize <= 1 { + return nil, ErrFontSize } - } else { - draw.Draw(d.img, d.img.Bounds(), image.Transparent, image.Point{}, draw.Src) + fontSize -= 0.5 } - bgnow := p.Bgnow.DisplayBg(d.config.Units) - d.drawer.Dot.X = (widthF - d.drawer.MeasureString(bgnow)) / 2 - d.drawer.DrawString(bgnow) + metrics := face.Metrics() + + draw.Draw(d.img, d.img.Bounds(), image.Transparent, image.Point{}, draw.Src) + drawer.Dot.X = (widthF - drawer.MeasureString(bgnow)) / 2 + drawer.Dot.Y = (heightF + metrics.Ascent - metrics.Descent) / 2 + drawer.DrawString(bgnow) var buf bytes.Buffer if err := encode(&buf, d.img); err != nil { diff --git a/internal/tray/systray.go b/internal/tray/systray.go index ff92a2a..a948215 100644 --- a/internal/tray/systray.go +++ b/internal/tray/systray.go @@ -133,8 +133,8 @@ func (t *Tray) onReady() { //nolint:gocyclo t.dynamicIcon = dynamicicon.New(t.config) } else { if t.dynamicIcon != nil { - _ = t.dynamicIcon.Close() t.dynamicIcon = nil + systray.SetTemplateIcon(assets.Nightscout, assets.Nightscout) } t.dynamicIcon = nil } diff --git a/test.png b/test.png new file mode 100644 index 0000000000000000000000000000000000000000..eb713e81fcb692a645c67f31adb6e9a9d9f7a6fc GIT binary patch literal 624 zcmV-$0+0QPP)PThdCvQ`sl`$gs0q{rT25d=Y5=xK z8no?|5W@RROpvr!(l#q%RMOQD!c0{%V7;UvNuNUq=Tc`kaEsriKbIW1O;JXHLP73a z8}P(pCsXGR;6ny9N8nHk&yWBn`xTl2#{YPDolR>64`RJni1u>>TKJdVKdj zN#`Z?O1fcq%U$&=B^|Ksh@|f+V6y}|oE|J`hmq(pyKlnytuK-^C22SZ_%D+%g0GQu zP0}4_pRE#DD`}6}hC>LG^Fd(R)(4WhBt4UK*s4rpF4!$aSq7YTleZH%3k()A{NXE@ zn-@L6J?nvJ-{8(%H`{c{Z1Hlm*&7(QHC8qdLU?TZUKjJU8NkxnT_R()o|N>`*|$3V zp{?5!{DqS4o8GYg7%ya~dIIy0IsXW72AHw^bNi*t;dYiz!X^o