1
0
streamdeck-lets-go/fontawesome.go
Maksim Totmin f65ff75a43 Initial commit: Stream Deck daemon with web UI
- Web-based key editor (Alpine.js SPA)
- Actions: shell commands, scripts, built-in media/volume/brightness, page switching
- Keyboard shortcut action (wtype/xdotool)
- On-device display with periodic command output
- Auto page switching (Hyprland, Sway, Niri, GNOME, KDE, X11)
- Screensaver with idle detection
- Config hot-reload
- Multi-device support
2026-06-16 22:35:14 +07:00

139 lines
2.8 KiB
Go

package main
import (
"fmt"
"image"
"image/color"
"strings"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/math/fixed"
)
func parseFAIcon(ref string) (faStyle, string, error) {
if !strings.HasPrefix(ref, "fa") {
return 0, "", fmt.Errorf("not a font awesome ref")
}
var style faStyle
var name string
switch {
case strings.HasPrefix(ref, "fab:"):
style = faBrands
name = strings.TrimPrefix(ref, "fab:")
case strings.HasPrefix(ref, "far:"):
style = faRegular
name = strings.TrimPrefix(ref, "far:")
case strings.HasPrefix(ref, "fa:"):
style = faSolid
name = strings.TrimPrefix(ref, "fa:")
default:
return 0, "", fmt.Errorf("invalid font awesome ref: %s", ref)
}
if name == "" {
return 0, "", fmt.Errorf("empty icon name")
}
return style, name, nil
}
func faCodepoint(style faStyle, name string) (rune, error) {
m, ok := faCodepoints[style]
if !ok {
return 0, fmt.Errorf("unknown style")
}
if cp, ok := m[name]; ok {
return cp, nil
}
if cp, ok := m[normalizeFAName(name)]; ok {
return cp, nil
}
return 0, fmt.Errorf("icon %q not found", name)
}
func normalizeFAName(name string) string {
if len(name) == 0 {
return name
}
parts := strings.Split(name, "-")
for i, p := range parts {
if len(p) > 0 {
parts[i] = strings.ToUpper(p[:1]) + p[1:]
}
}
camel := strings.Join(parts, "")
return strings.ToLower(camel[:1]) + camel[1:]
}
func faFontBytes(style faStyle) ([]byte, error) {
return faFonts.ReadFile(style.otfPath())
}
func loadFAFace(style faStyle, pointSize float64) (font.Face, error) {
data, err := faFontBytes(style)
if err != nil {
return nil, fmt.Errorf("read font: %w", err)
}
fnt, err := opentype.Parse(data)
if err != nil {
return nil, fmt.Errorf("parse font: %w", err)
}
face, err := opentype.NewFace(fnt, &opentype.FaceOptions{
Size: pointSize,
DPI: 72,
Hinting: font.HintingFull,
})
if err != nil {
return nil, fmt.Errorf("new face: %w", err)
}
return face, nil
}
func renderFAGlyph(style faStyle, name string, size int, scale float64) (image.Image, error) {
cp, err := faCodepoint(style, name)
if err != nil {
return nil, err
}
if scale <= 0 {
scale = 0.55
}
fontSize := float64(size) * scale
face, err := loadFAFace(style, fontSize)
if err != nil {
return nil, err
}
defer face.Close()
rgba := image.NewRGBA(image.Rect(0, 0, size, size))
offX := (size - int(fontSize)) / 2
if offX < 0 {
offX = size / 8
}
baselineY := size * 7 / 10
d := font.Drawer{
Dst: rgba,
Src: image.NewUniform(color.White),
Face: face,
Dot: fixed.P(offX, baselineY),
}
d.DrawString(string(cp))
return rgba, nil
}
func isFAIconRef(path string) bool {
return strings.HasPrefix(path, "fa:") || strings.HasPrefix(path, "far:") || strings.HasPrefix(path, "fab:")
}