93 lines
2.5 KiB
Go
93 lines
2.5 KiB
Go
|
|
package runner
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"io"
|
||
|
|
"os"
|
||
|
|
"os/exec"
|
||
|
|
"path/filepath"
|
||
|
|
"runtime"
|
||
|
|
"strings"
|
||
|
|
)
|
||
|
|
|
||
|
|
// InstallFont downloads a font file and installs it into the OS so After Effects
|
||
|
|
// can use it. Returns nil on success. Best-effort per platform.
|
||
|
|
func InstallFont(ctx context.Context, fileURL, name string) error {
|
||
|
|
ext := filepath.Ext(strings.Split(fileURL, "?")[0])
|
||
|
|
if ext == "" {
|
||
|
|
ext = ".ttf"
|
||
|
|
}
|
||
|
|
fileName := sanitizeFontName(name) + ext
|
||
|
|
|
||
|
|
tmpDir, err := os.MkdirTemp("", "fr-font-")
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("tmp dir: %w", err)
|
||
|
|
}
|
||
|
|
defer os.RemoveAll(tmpDir)
|
||
|
|
tmp := filepath.Join(tmpDir, fileName)
|
||
|
|
if _, err := DownloadFile(ctx, fileURL, tmp); err != nil {
|
||
|
|
return fmt.Errorf("download: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
switch runtime.GOOS {
|
||
|
|
case "windows":
|
||
|
|
return installFontWindows(ctx, tmp, fileName)
|
||
|
|
case "darwin":
|
||
|
|
return copyFile(tmp, filepath.Join(os.Getenv("HOME"), "Library", "Fonts", fileName))
|
||
|
|
default: // linux
|
||
|
|
dir := filepath.Join(os.Getenv("HOME"), ".local", "share", "fonts")
|
||
|
|
_ = os.MkdirAll(dir, 0o755)
|
||
|
|
if err := copyFile(tmp, filepath.Join(dir, fileName)); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
_ = exec.CommandContext(ctx, "fc-cache", "-f").Run()
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func installFontWindows(ctx context.Context, src, fileName string) error {
|
||
|
|
// Prefer the machine-wide Fonts dir; fall back to the per-user dir.
|
||
|
|
dst := filepath.Join(os.Getenv("WINDIR"), "Fonts", fileName)
|
||
|
|
if err := copyFile(src, dst); err != nil {
|
||
|
|
userFonts := filepath.Join(os.Getenv("LOCALAPPDATA"), "Microsoft", "Windows", "Fonts")
|
||
|
|
_ = os.MkdirAll(userFonts, 0o755)
|
||
|
|
dst = filepath.Join(userFonts, fileName)
|
||
|
|
if err := copyFile(src, dst); err != nil {
|
||
|
|
return fmt.Errorf("copy font: %w", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Register so Windows/AE pick it up without a reboot (best-effort; needs admin).
|
||
|
|
_ = exec.CommandContext(ctx, "reg", "add",
|
||
|
|
`HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts`,
|
||
|
|
"/v", fileName, "/t", "REG_SZ", "/d", dst, "/f").Run()
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func copyFile(src, dst string) error {
|
||
|
|
in, err := os.Open(src)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer in.Close()
|
||
|
|
out, err := os.Create(dst)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer out.Close()
|
||
|
|
if _, err := io.Copy(out, in); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return out.Sync()
|
||
|
|
}
|
||
|
|
|
||
|
|
func sanitizeFontName(s string) string {
|
||
|
|
s = strings.TrimSpace(s)
|
||
|
|
repl := strings.NewReplacer("/", "_", "\\", "_", ":", "_", "*", "_", "?", "_", "\"", "_", "<", "_", ">", "_", "|", "_", " ", "_")
|
||
|
|
s = repl.Replace(s)
|
||
|
|
if s == "" {
|
||
|
|
return "font"
|
||
|
|
}
|
||
|
|
return s
|
||
|
|
}
|