mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
Add FontManager to better handle fonts. Remove the go-findfont dependency. Add Go examples.
This commit is contained in:
parent
9fa596780b
commit
5154f0b60f
13 changed files with 335 additions and 29 deletions
|
|
@ -127,10 +127,10 @@ function eventTarget(event) {
|
|||
document.addEventListener("DOMContentLoaded", () => {
|
||||
});
|
||||
window.addEventListener("contextmenu", contextMenuHandler);
|
||||
const call$1 = newRuntimeCaller(objectNames.ContextMenu);
|
||||
const call$2 = newRuntimeCaller(objectNames.ContextMenu);
|
||||
const ContextMenuOpen = 0;
|
||||
function openContextMenu(id, x, y, data) {
|
||||
void call$1(ContextMenuOpen, { id, x, y, data });
|
||||
void call$2(ContextMenuOpen, { id, x, y, data });
|
||||
}
|
||||
function contextMenuHandler(event) {
|
||||
const target = eventTarget(event);
|
||||
|
|
@ -1138,7 +1138,7 @@ if (promiseWithResolvers && typeof promiseWithResolvers === "function") {
|
|||
window._wails = window._wails || {};
|
||||
window._wails.callResultHandler = resultHandler;
|
||||
window._wails.callErrorHandler = errorHandler;
|
||||
const call = newRuntimeCaller(objectNames.Call);
|
||||
const call$1 = newRuntimeCaller(objectNames.Call);
|
||||
const cancelCall = newRuntimeCaller(objectNames.CancelCall);
|
||||
const callResponses = /* @__PURE__ */ new Map();
|
||||
const CallBinding = 0;
|
||||
|
|
@ -1224,7 +1224,7 @@ function Call(options) {
|
|||
const id = generateID();
|
||||
const result = CancellablePromise.withResolvers();
|
||||
callResponses.set(id, { resolve: result.resolve, reject: result.reject });
|
||||
const request = call(CallBinding, Object.assign({ "call-id": id }, options));
|
||||
const request = call$1(CallBinding, Object.assign({ "call-id": id }, options));
|
||||
let running = false;
|
||||
request.then(() => {
|
||||
running = true;
|
||||
|
|
@ -1283,7 +1283,8 @@ function listenerOff(listener) {
|
|||
}
|
||||
window._wails = window._wails || {};
|
||||
window._wails.dispatchWailsEvent = dispatchWailsEvent;
|
||||
newRuntimeCaller(objectNames.Events);
|
||||
const call = newRuntimeCaller(objectNames.Events);
|
||||
const EmitMethod = 0;
|
||||
class WailsEvent {
|
||||
constructor(name, data = null) {
|
||||
this.name = name;
|
||||
|
|
@ -1316,6 +1317,9 @@ function OnMultiple(eventName, callback, maxCallbacks) {
|
|||
function On(eventName, callback) {
|
||||
return OnMultiple(eventName, callback, -1);
|
||||
}
|
||||
function Emit(event) {
|
||||
return call(EmitMethod, event);
|
||||
}
|
||||
window._wails = window._wails || {};
|
||||
window._wails.invoke = invoke;
|
||||
invoke("wails:runtime:ready");
|
||||
|
|
@ -1329,6 +1333,8 @@ function SetBadge(label) {
|
|||
}
|
||||
const setButton = document.getElementById("set");
|
||||
const removeButton = document.getElementById("remove");
|
||||
const setButtonUsingGo = document.getElementById("set-go");
|
||||
const removeButtonUsingGo = document.getElementById("remove-go");
|
||||
const labelElement = document.getElementById("label");
|
||||
const timeElement = document.getElementById("time");
|
||||
setButton.addEventListener("click", () => {
|
||||
|
|
@ -1338,6 +1344,16 @@ setButton.addEventListener("click", () => {
|
|||
removeButton.addEventListener("click", () => {
|
||||
RemoveBadge();
|
||||
});
|
||||
setButtonUsingGo.addEventListener("click", () => {
|
||||
let label = labelElement.value;
|
||||
void Emit({
|
||||
name: "set:badge",
|
||||
data: label
|
||||
});
|
||||
});
|
||||
removeButtonUsingGo.addEventListener("click", () => {
|
||||
void Emit({ name: "remove:badge", data: null });
|
||||
});
|
||||
On("time", (time) => {
|
||||
timeElement.innerText = time.data;
|
||||
});
|
||||
4
v3/examples/badge/frontend/dist/index.html
vendored
4
v3/examples/badge/frontend/dist/index.html
vendored
|
|
@ -6,7 +6,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="stylesheet" href="/style.css"/>
|
||||
<title>Wails App</title>
|
||||
<script type="module" crossorigin src="/assets/index-BguIgRNQ.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-sXwpgKSV.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
|
@ -25,6 +25,8 @@
|
|||
<input class="input" id="label" type="text" autocomplete="off"/>
|
||||
<button class="btn" id="set">Set</button>
|
||||
<button class="btn" id="remove">Remove</button>
|
||||
<button class="btn" id="set-go">Set using Go</button>
|
||||
<button class="btn" id="remove-go">Remove using Go</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
|
|
|
|||
2
v3/examples/badge/frontend/dist/style.css
vendored
2
v3/examples/badge/frontend/dist/style.css
vendored
|
|
@ -43,8 +43,6 @@ a:hover {
|
|||
}
|
||||
|
||||
button {
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@
|
|||
<input class="input" id="label" type="text" autocomplete="off"/>
|
||||
<button class="btn" id="set">Set</button>
|
||||
<button class="btn" id="remove">Remove</button>
|
||||
<button class="btn" id="set-go">Set using Go</button>
|
||||
<button class="btn" id="remove-go">Remove using Go</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
|
|
|
|||
|
|
@ -43,8 +43,6 @@ a:hover {
|
|||
}
|
||||
|
||||
button {
|
||||
width: 60px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import {Events} from "@wailsio/runtime";
|
||||
import {SetBadge, RemoveBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service";
|
||||
import {SetBadge, RemoveBadge} from "../bindings/github.com/wailsapp/wails/v3/pkg/services/badge/service";
|
||||
|
||||
const setButton = document.getElementById('set')! as HTMLButtonElement;
|
||||
const removeButton = document.getElementById('remove')! as HTMLButtonElement;
|
||||
const setButtonUsingGo = document.getElementById('set-go')! as HTMLButtonElement;
|
||||
const removeButtonUsingGo = document.getElementById('remove-go')! as HTMLButtonElement;
|
||||
const labelElement : HTMLInputElement = document.getElementById('label')! as HTMLInputElement;
|
||||
const timeElement = document.getElementById('time')! as HTMLDivElement;
|
||||
|
||||
|
|
@ -15,6 +17,19 @@ removeButton.addEventListener('click', () => {
|
|||
RemoveBadge();
|
||||
});
|
||||
|
||||
setButtonUsingGo.addEventListener('click', () => {
|
||||
let label = (labelElement as HTMLInputElement).value
|
||||
void Events.Emit({
|
||||
name: "set:badge",
|
||||
data: label,
|
||||
})
|
||||
})
|
||||
|
||||
removeButtonUsingGo.addEventListener('click', () => {
|
||||
void Events.Emit({name:"remove:badge", data: null})
|
||||
})
|
||||
|
||||
Events.On('time', (time: {data: any}) => {
|
||||
timeElement.innerText = time.data;
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -27,11 +27,14 @@ func main() {
|
|||
// 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files.
|
||||
// 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances.
|
||||
// 'Mac' options tailor the application when running an macOS.
|
||||
|
||||
badgeService := badge.New()
|
||||
|
||||
app := application.New(application.Options{
|
||||
Name: "badge",
|
||||
Description: "A demo of using raw HTML & CSS",
|
||||
Services: []application.Service{
|
||||
application.NewService(badge.New()),
|
||||
application.NewService(badgeService),
|
||||
},
|
||||
Assets: application.AssetOptions{
|
||||
Handler: application.AssetFileServerFS(assets),
|
||||
|
|
@ -57,6 +60,21 @@ func main() {
|
|||
URL: "/",
|
||||
})
|
||||
|
||||
app.OnEvent("remove:badge", func(event *application.CustomEvent) {
|
||||
err := badgeService.RemoveBadge()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
app.OnEvent("set:badge", func(event *application.CustomEvent) {
|
||||
text := event.Data.(string)
|
||||
err := badgeService.SetBadge(text)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
// Create a goroutine that emits an event containing the current time every second.
|
||||
// The frontend can listen to this event and update the UI accordingly.
|
||||
go func() {
|
||||
|
|
|
|||
|
|
@ -35,13 +35,13 @@ require (
|
|||
github.com/samber/lo v1.49.1 // indirect
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||
github.com/stretchr/testify v1.10.0 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.19 // indirect
|
||||
github.com/wailsapp/go-webview2 v1.0.21 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.33.0 // indirect
|
||||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/net v0.35.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -138,6 +138,7 @@ github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf
|
|||
github.com/wailsapp/go-webview2 v1.0.9 h1:lrU+q0cf1wgLdR69rN+ZnRtMJNaJRrcQ4ELxoO7/xjs=
|
||||
github.com/wailsapp/go-webview2 v1.0.9/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
|
||||
github.com/wailsapp/go-webview2 v1.0.19/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
||||
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
|
||||
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
|
||||
github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI=
|
||||
|
|
@ -153,6 +154,7 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
|||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
|
||||
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
|
|
@ -167,6 +169,7 @@ golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
|||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
@ -191,6 +194,7 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ require (
|
|||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/flopp/go-findfont v0.1.0 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
)
|
||||
|
||||
|
|
@ -137,7 +136,7 @@ require (
|
|||
golang.org/x/crypto v0.36.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect
|
||||
golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac
|
||||
golang.org/x/image v0.24.0 // indirect
|
||||
golang.org/x/image v0.24.0
|
||||
golang.org/x/mod v0.24.0 // indirect
|
||||
golang.org/x/net v0.37.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
|
|
|
|||
|
|
@ -117,8 +117,6 @@ github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc
|
|||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/flopp/go-findfont v0.1.0 h1:lPn0BymDUtJo+ZkV01VS3661HL6F4qFlkhcJN55u6mU=
|
||||
github.com/flopp/go-findfont v0.1.0/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
|
|
|
|||
|
|
@ -5,15 +5,17 @@ package badge
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/flopp/go-findfont"
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||
"golang.org/x/image/font"
|
||||
|
|
@ -108,9 +110,10 @@ func (t *ITaskbarList3) SetOverlayIcon(hwnd syscall.Handle, hIcon syscall.Handle
|
|||
}
|
||||
|
||||
type windowsBadge struct {
|
||||
taskbar *ITaskbarList3
|
||||
badgeImg *image.RGBA
|
||||
badgeSize int
|
||||
taskbar *ITaskbarList3
|
||||
badgeImg *image.RGBA
|
||||
badgeSize int
|
||||
fontManager *FontManager
|
||||
}
|
||||
|
||||
func New() *Service {
|
||||
|
|
@ -125,6 +128,7 @@ func (w *windowsBadge) Startup(ctx context.Context, options application.ServiceO
|
|||
return err
|
||||
}
|
||||
w.taskbar = taskbar
|
||||
w.fontManager = NewFontManager()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -227,12 +231,16 @@ func (w *windowsBadge) createBadgeIcon() (w32.HICON, error) {
|
|||
}
|
||||
|
||||
func (w *windowsBadge) createBadgeIconWithText(label string) (w32.HICON, error) {
|
||||
var err error
|
||||
fontPath := ""
|
||||
for _, path := range findfont.List() {
|
||||
if strings.Contains(strings.ToLower(path), "segoeuib.ttf") || // Segoe UI Bold
|
||||
strings.Contains(strings.ToLower(path), "arialbd.ttf") {
|
||||
fontPath = path
|
||||
break
|
||||
fontPath, err = w.fontManager.FindFont("segoeuib.ttf")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if fontPath == "" {
|
||||
fontPath, err = w.fontManager.FindFont("arialbd.ttf")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if fontPath == "" {
|
||||
|
|
@ -304,3 +312,118 @@ func (w *windowsBadge) createBadge() {
|
|||
|
||||
w.badgeImg = img
|
||||
}
|
||||
|
||||
// GetInstalledFonts returns a slice of strings containing paths to all installed fonts on Windows
|
||||
func GetInstalledFonts() ([]string, error) {
|
||||
var fontPaths []string
|
||||
var fontDirs = []string{
|
||||
filepath.Join(os.Getenv("windir"), "Fonts"),
|
||||
filepath.Join(os.Getenv("localappdata"), "Microsoft", "Windows", "Fonts"),
|
||||
}
|
||||
|
||||
// Check system fonts from registry
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error opening system font registry key: %v", err)
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
// Add system fonts
|
||||
valueNames, err := k.ReadValueNames(0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading system font registry values: %v", err)
|
||||
}
|
||||
|
||||
systemFontDir := fontDirs[0]
|
||||
for _, name := range valueNames {
|
||||
value, _, err := k.GetStringValue(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// If value doesn't contain path separator, assume it's in the Windows font directory
|
||||
if !strings.Contains(value, "\\") {
|
||||
value = filepath.Join(systemFontDir, value)
|
||||
}
|
||||
|
||||
if fileExists(value) {
|
||||
fontPaths = append(fontPaths, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Check user fonts from registry
|
||||
userK, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`, registry.QUERY_VALUE)
|
||||
if err == nil { // It's okay if this fails, some users might not have custom fonts
|
||||
defer userK.Close()
|
||||
|
||||
userValueNames, err := userK.ReadValueNames(0)
|
||||
if err == nil {
|
||||
userFontDir := fontDirs[1]
|
||||
for _, name := range userValueNames {
|
||||
value, _, err := userK.GetStringValue(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// If value doesn't contain path separator, assume it's in the user font directory
|
||||
if !strings.Contains(value, "\\") {
|
||||
value = filepath.Join(userFontDir, value)
|
||||
}
|
||||
|
||||
if fileExists(value) {
|
||||
fontPaths = append(fontPaths, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also check the font directories directly for any fonts not in the registry
|
||||
for _, dir := range fontDirs {
|
||||
if !dirExists(dir) {
|
||||
continue
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(file.Name()))
|
||||
// Common font extensions
|
||||
if ext == ".ttf" || ext == ".otf" || ext == ".ttc" || ext == ".fon" || ext == ".fnt" {
|
||||
fontPath := filepath.Join(dir, file.Name())
|
||||
// Check if this path is already in our list
|
||||
if !contains(fontPaths, fontPath) {
|
||||
fontPaths = append(fontPaths, fontPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fontPaths, nil
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
func fileExists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && !info.IsDir()
|
||||
}
|
||||
|
||||
func dirExists(path string) bool {
|
||||
info, err := os.Stat(path)
|
||||
return err == nil && info.IsDir()
|
||||
}
|
||||
|
||||
func contains(slice []string, item string) bool {
|
||||
for _, s := range slice {
|
||||
if strings.EqualFold(s, item) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
133
v3/pkg/services/badge/font.go
Normal file
133
v3/pkg/services/badge/font.go
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package badge
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// FontManager handles font discovery on Windows with minimal caching
|
||||
type FontManager struct {
|
||||
fontCache map[string]string // Maps only requested font filenames to paths
|
||||
fontDirs []string // Directories to search for fonts
|
||||
mu sync.RWMutex // Mutex for thread-safe access to the cache
|
||||
registryPaths []string // Registry paths to search for fonts
|
||||
}
|
||||
|
||||
// NewFontManager creates a new FontManager instance
|
||||
func NewFontManager() *FontManager {
|
||||
return &FontManager{
|
||||
fontCache: make(map[string]string),
|
||||
fontDirs: []string{
|
||||
filepath.Join(os.Getenv("windir"), "Fonts"),
|
||||
filepath.Join(os.Getenv("localappdata"), "Microsoft", "Windows", "Fonts"),
|
||||
},
|
||||
registryPaths: []string{
|
||||
`SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// FindFont searches for a font by filename and returns its full path
|
||||
// Only caches fonts that are found
|
||||
func (fm *FontManager) FindFont(fontFilename string) (string, error) {
|
||||
fontKey := strings.ToLower(fontFilename)
|
||||
|
||||
// Check if already in cache
|
||||
fm.mu.RLock()
|
||||
if path, exists := fm.fontCache[fontKey]; exists {
|
||||
fm.mu.RUnlock()
|
||||
return path, nil
|
||||
}
|
||||
fm.mu.RUnlock()
|
||||
|
||||
// If not in cache, search for the font
|
||||
fontPath, err := fm.searchForFont(fontFilename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Add to cache only if found
|
||||
fm.mu.Lock()
|
||||
fm.fontCache[fontKey] = fontPath
|
||||
fm.mu.Unlock()
|
||||
|
||||
return fontPath, nil
|
||||
}
|
||||
|
||||
// searchForFont looks for a font in all known locations
|
||||
func (fm *FontManager) searchForFont(fontFilename string) (string, error) {
|
||||
fontFileLower := strings.ToLower(fontFilename)
|
||||
|
||||
// 1. Direct file check in font directories (fastest approach)
|
||||
for _, dir := range fm.fontDirs {
|
||||
fontPath := filepath.Join(dir, fontFilename)
|
||||
if fileExists(fontPath) {
|
||||
return fontPath, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search in registry (can find fonts with different paths)
|
||||
// System fonts (HKEY_LOCAL_MACHINE)
|
||||
for _, regPath := range fm.registryPaths {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, regPath, registry.QUERY_VALUE)
|
||||
if err == nil {
|
||||
defer k.Close()
|
||||
|
||||
// Look for the specific font in registry values
|
||||
fontPath, found := fm.findFontInRegistry(k, fontFileLower, fm.fontDirs[0])
|
||||
if found {
|
||||
return fontPath, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. User fonts (HKEY_CURRENT_USER)
|
||||
for _, regPath := range fm.registryPaths {
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, regPath, registry.QUERY_VALUE)
|
||||
if err == nil {
|
||||
defer k.Close()
|
||||
|
||||
// Look for the specific font in registry values
|
||||
fontPath, found := fm.findFontInRegistry(k, fontFileLower, fm.fontDirs[1])
|
||||
if found {
|
||||
return fontPath, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("font not found: " + fontFilename)
|
||||
}
|
||||
|
||||
// findFontInRegistry searches for a specific font in a registry key
|
||||
func (fm *FontManager) findFontInRegistry(k registry.Key, fontFileLower string, defaultDir string) (string, bool) {
|
||||
valueNames, err := k.ReadValueNames(0)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
for _, name := range valueNames {
|
||||
value, _, err := k.GetStringValue(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if this registry entry corresponds to our font
|
||||
valueLower := strings.ToLower(value)
|
||||
if strings.HasSuffix(valueLower, fontFileLower) {
|
||||
// If it's a relative path, assume it's in the default font directory
|
||||
if !strings.Contains(value, "\\") {
|
||||
value = filepath.Join(defaultDir, value)
|
||||
}
|
||||
|
||||
if fileExists(value) {
|
||||
return value, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue