fix(font): install correctly on Windows

This commit is contained in:
Jan De Dobbeleer 2022-06-08 20:02:30 +02:00 committed by Jan De Dobbeleer
parent c301864021
commit 265790659c

View file

@ -1,34 +1,37 @@
// Derived from https://github.com/Crosse/font-install
// Copyright 2020 Seth Wright <seth@crosse.org>
package font package font
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path" "path/filepath"
"syscall"
"unsafe"
"golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/registry"
) )
// FontsDir denotes the path to the user's fonts directory on Linux. // https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-addfontresourcea
// Windows doesn't have the concept of a permanent, per-user collection var FontsDir = filepath.Join(os.Getenv("WINDIR"), "Fonts")
// of fonts, meaning that all fonts are stored in the system-level fonts
// directory, which is %WINDIR%\Fonts by default. const (
var FontsDir = path.Join(os.Getenv("WINDIR"), "Fonts") WM_FONTCHANGE = 0x001D // nolint:revive
HWND_BROADCAST = 0xFFFF // nolint:revive
)
func install(font *Font) (err error) { func install(font *Font) (err error) {
// To install a font on Windows: // To install a font on Windows:
// - Copy the file to the fonts directory // - Copy the file to the fonts directory
// - Create a registry entry for the font // - Add registry entry
fullPath := path.Join(FontsDir, font.FileName) // - Call AddFontResourceW to set the font
// - Notify other applications that the fonts have changed
err = ioutil.WriteFile(fullPath, font.Data, 0644) //nolint:gosec fullPath := filepath.Join(FontsDir, font.FileName)
err = os.WriteFile(fullPath, font.Data, 0644)
if err != nil { 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) k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`, registry.WRITE)
if err != nil { if err != nil {
// If this fails, remove the font file as well. // If this fails, remove the font file as well.
@ -40,12 +43,8 @@ func install(font *Font) (err error) {
} }
defer k.Close() defer k.Close()
// Apparently it's "ok" to mark an OpenType font as "TrueType", name := fmt.Sprintf("%v (TrueType)", font.Name)
// and since this tool only supports True- and OpenType fonts, if err = k.SetStringValue(name, font.FileName); err != nil {
// 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 {
// If this fails, remove the font file as well. // If this fails, remove the font file as well.
if nexterr := os.Remove(fullPath); nexterr != nil { if nexterr := os.Remove(fullPath); nexterr != nil {
return nexterr return nexterr
@ -54,5 +53,26 @@ func install(font *Font) (err error) {
return err 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 return nil
} }