From 265790659c5bdfd8ef28be3bcb6b57e68c35b522 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Wed, 8 Jun 2022 20:02:30 +0200 Subject: [PATCH] fix(font): install correctly on Windows --- src/font/install_windows.go | 62 ++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/src/font/install_windows.go b/src/font/install_windows.go index 81efb6d9..330d2dad 100644 --- a/src/font/install_windows.go +++ b/src/font/install_windows.go @@ -1,34 +1,37 @@ -// Derived from https://github.com/Crosse/font-install -// Copyright 2020 Seth Wright package font import ( + "errors" "fmt" - "io/ioutil" "os" - "path" + "path/filepath" + "syscall" + "unsafe" "golang.org/x/sys/windows/registry" ) -// FontsDir denotes the path to the user's fonts directory on Linux. -// Windows doesn't have the concept of a permanent, per-user collection -// of fonts, meaning that all fonts are stored in the system-level fonts -// directory, which is %WINDIR%\Fonts by default. -var FontsDir = path.Join(os.Getenv("WINDIR"), "Fonts") +// https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-addfontresourcea +var FontsDir = filepath.Join(os.Getenv("WINDIR"), "Fonts") + +const ( + WM_FONTCHANGE = 0x001D // nolint:revive + HWND_BROADCAST = 0xFFFF // nolint:revive +) func install(font *Font) (err error) { // To install a font on Windows: // - Copy the file to the fonts directory - // - Create a registry entry for the font - fullPath := path.Join(FontsDir, font.FileName) - - err = ioutil.WriteFile(fullPath, font.Data, 0644) //nolint:gosec + // - Add registry entry + // - Call AddFontResourceW to set the font + // - Notify other applications that the fonts have changed + fullPath := filepath.Join(FontsDir, font.FileName) + err = os.WriteFile(fullPath, font.Data, 0644) if err != nil { - return err + return } - // Second, write metadata about the font to the registry. + // Add registry entry k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`, registry.WRITE) if err != nil { // If this fails, remove the font file as well. @@ -40,12 +43,8 @@ func install(font *Font) (err error) { } defer k.Close() - // Apparently it's "ok" to mark an OpenType font as "TrueType", - // and since this tool only supports True- and OpenType fonts, - // this should be Okay(tm). - // Besides, Windows does it, so why can't I? - valueName := fmt.Sprintf("%v (TrueType)", font.FileName) - if err = k.SetStringValue(font.Name, valueName); err != nil { + name := fmt.Sprintf("%v (TrueType)", font.Name) + if err = k.SetStringValue(name, font.FileName); err != nil { // If this fails, remove the font file as well. if nexterr := os.Remove(fullPath); nexterr != nil { return nexterr @@ -54,5 +53,26 @@ func install(font *Font) (err error) { return err } + gdi32 := syscall.NewLazyDLL("gdi32.dll") + proc := gdi32.NewProc("AddFontResourceW") + + fontPtr, err := syscall.UTF16PtrFromString(fullPath) + if err != nil { + return + } + + ret, _, _ := proc.Call(uintptr(unsafe.Pointer(fontPtr))) + if ret == 0 { + return errors.New("unable to add font resource") + } + + // Notify other applications that the fonts have changed + user32 := syscall.NewLazyDLL("user32.dll") + procSendMessageW := user32.NewProc("SendMessageW") + _, _, e1 := syscall.SyscallN(procSendMessageW.Addr(), uintptr(HWND_BROADCAST), uintptr(WM_FONTCHANGE), 0, 0) + if e1 != 0 { + return errors.New("unable to broadcast font change") + } + return nil }