实现所有平台安装字体的方法

This commit is contained in:
zyj
2026-03-06 11:59:00 +08:00
parent 3a35385ab8
commit f24f81e674
3 changed files with 263 additions and 19 deletions

View File

@@ -2,11 +2,13 @@ package platform
import (
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"github.com/user/devpack/internal/logging"
"golang.org/x/sys/windows/registry"
)
@@ -86,45 +88,107 @@ func (p *WindowsPlatform) DefaultShell() string {
return "powershell" // Windows PowerShell 5.1
}
// InstallFont 安装字体
// InstallFont 安装字体到用户级字体目录并注册到注册表
// 支持 .ttf (TrueType) 和 .otf (OpenType) 格式
func (p *WindowsPlatform) InstallFont(ctx context.Context, fontPath string) error {
// 目标路径为 C:\Users\<User>\AppData\Local\Microsoft\Windows\Fonts
// 也可以使用 C:\Users\<User>\AppData\Roaming\Microsoft\Windows\Fonts但 Local 更合适
log := logging.G()
// 验证源字体文件是否存在且可访问
info, err := os.Stat(fontPath)
if err != nil {
log.Errorf("无法访问字体文件: %v", err)
return fmt.Errorf("无法访问字体文件: %w", err)
}
if info.IsDir() {
return fmt.Errorf("字体路径是目录而非文件: %s", fontPath)
}
// 检查字体格式是否受支持
fontName := filepath.Base(fontPath)
ext := filepath.Ext(fontName)
var regSuffix string
switch ext {
case ".ttf":
regSuffix = " (TrueType)"
case ".otf":
regSuffix = " (OpenType)"
default:
log.Errorf("不支持的字体格式: %s", ext)
return fmt.Errorf("不支持的字体格式: %s仅支持 .ttf 和 .otf", ext)
}
// 目标路径为用户级字体目录
// C:\Users\<User>\AppData\Local\Microsoft\Windows\Fonts
destDir := filepath.Join(os.Getenv("LOCALAPPDATA"), "Microsoft", "Windows", "Fonts")
log.Debugf("正在安装字体: %s 到 %s", fontPath, destDir)
// 确保目标目录存在
os.MkdirAll(destDir, 0755)
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("创建字体目录失败: %w", err)
}
// 获取字体文件名
fontName := filepath.Base(fontPath)
// 目标路径
// 检查字体是否已安装
destPath := filepath.Join(destDir, fontName)
if _, err := os.Stat(destPath); err == nil {
log.Infof("字体已存在,跳过安装: %s", destPath)
return nil
}
// 复制字体文件到目标路径
input, err := os.Open(fontPath)
if err := copyFile(fontPath, destPath); err != nil {
return fmt.Errorf("复制字体文件失败: %w", err)
}
// 写注册表以注册字体(用户级,无需管理员权限)
key, _, err := registry.CreateKey(
registry.CURRENT_USER,
`Software\Microsoft\Windows NT\CurrentVersion\Fonts`,
registry.ALL_ACCESS,
)
if err != nil {
// 注册表写入失败时清理已复制的字体文件
os.Remove(destPath)
return fmt.Errorf("打开字体注册表失败: %w", err)
}
defer key.Close()
// 注册表键名:去掉扩展名的字体名 + 类型后缀
baseName := fontName[:len(fontName)-len(ext)]
regName := baseName + regSuffix
if err := key.SetStringValue(regName, destPath); err != nil {
os.Remove(destPath)
return fmt.Errorf("注册字体失败: %w", err)
}
log.Infof("字体安装成功: %s", fontName)
return nil
}
// copyFile 复制文件,失败时自动清理目标文件
func copyFile(src, dst string) error {
input, err := os.Open(src)
if err != nil {
return err
}
defer input.Close()
output, err := os.Create(destPath)
output, err := os.Create(dst)
if err != nil {
return err
}
defer output.Close()
_, err = io.Copy(output, input)
// 先关闭文件确保数据写入磁盘
closeErr := output.Close()
if err != nil {
os.Remove(dst) // 复制失败,清理残留文件
return err
}
// 写注册表以注册字体
key, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows NT\CurrentVersion\Fonts`, registry.ALL_ACCESS)
if err != nil {
return err
if closeErr != nil {
os.Remove(dst)
return closeErr
}
defer key.Close()
regName := fontName + " (TrueType)"
return key.SetStringValue(regName, destPath)
return nil
}