实现所有平台安装字体的方法
This commit is contained in:
@@ -2,8 +2,13 @@ package platform
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/user/devpack/internal/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DarwinPlatform macOS 平台实现
|
// DarwinPlatform macOS 平台实现
|
||||||
@@ -64,6 +69,94 @@ func (p *DarwinPlatform) DefaultShell() string {
|
|||||||
return "/bin/zsh"
|
return "/bin/zsh"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstallFont 安装字体到用户级字体目录
|
||||||
|
// macOS 会自动检测 ~/Library/Fonts/ 中的新字体,无需手动刷新缓存
|
||||||
|
// 支持 .ttf (TrueType) 和 .otf (OpenType) 格式
|
||||||
func (p *DarwinPlatform) InstallFont(ctx context.Context, fontPath string) error {
|
func (p *DarwinPlatform) InstallFont(ctx context.Context, fontPath string) error {
|
||||||
|
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)
|
||||||
|
if ext != ".ttf" && ext != ".otf" {
|
||||||
|
log.Errorf("不支持的字体格式: %s", ext)
|
||||||
|
return fmt.Errorf("不支持的字体格式: %s,仅支持 .ttf 和 .otf", ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户级字体目录: ~/Library/Fonts/
|
||||||
|
// macOS 自动监视此目录,放入即可使用
|
||||||
|
destDir := filepath.Join(p.homeDir, "Library", "Fonts")
|
||||||
|
log.Debugf("正在安装字体: %s 到 %s", fontPath, destDir)
|
||||||
|
|
||||||
|
// 确保目标目录存在(通常已存在)
|
||||||
|
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("创建字体目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查字体是否已安装
|
||||||
|
destPath := filepath.Join(destDir, fontName)
|
||||||
|
if _, err := os.Stat(destPath); err == nil {
|
||||||
|
log.Infof("字体已存在,跳过安装: %s", destPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制字体文件到目标路径
|
||||||
|
if err := darwinCopyFile(fontPath, destPath); err != nil {
|
||||||
|
return fmt.Errorf("复制字体文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 可选:通过 atsutil 强制刷新字体服务缓存以立即生效
|
||||||
|
// 某些应用可能需要重启才能看到新字体
|
||||||
|
if _, err := exec.LookPath("atsutil"); err == nil {
|
||||||
|
// 移除用户字体数据库缓存
|
||||||
|
cmd := exec.CommandContext(ctx, "atsutil", "databases", "-removeUser")
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
log.Debugf("清理字体缓存数据库: %s, %v", string(out), err)
|
||||||
|
}
|
||||||
|
// 重启字体服务
|
||||||
|
cmd = exec.CommandContext(ctx, "atsutil", "server", "-shutdown")
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
log.Debugf("重启字体服务: %s, %v", string(out), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("字体安装成功: %s", fontName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// darwinCopyFile 复制文件,失败时自动清理目标文件
|
||||||
|
func darwinCopyFile(src, dst string) error {
|
||||||
|
input, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer input.Close()
|
||||||
|
|
||||||
|
output, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(output, input)
|
||||||
|
closeErr := output.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(dst)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if closeErr != nil {
|
||||||
|
os.Remove(dst)
|
||||||
|
return closeErr
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,13 @@ package platform
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/user/devpack/internal/logging"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LinuxPlatform Linux 平台实现
|
// LinuxPlatform Linux 平台实现
|
||||||
@@ -81,6 +86,88 @@ func (p *LinuxPlatform) DefaultShell() string {
|
|||||||
return "/bin/bash"
|
return "/bin/bash"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InstallFont 安装字体到用户级字体目录并刷新字体缓存
|
||||||
|
// 支持 .ttf (TrueType) 和 .otf (OpenType) 格式
|
||||||
func (p *LinuxPlatform) InstallFont(ctx context.Context, fontPath string) error {
|
func (p *LinuxPlatform) InstallFont(ctx context.Context, fontPath string) error {
|
||||||
|
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)
|
||||||
|
if ext != ".ttf" && ext != ".otf" {
|
||||||
|
log.Errorf("不支持的字体格式: %s", ext)
|
||||||
|
return fmt.Errorf("不支持的字体格式: %s,仅支持 .ttf 和 .otf", ext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户级字体目录: ~/.local/share/fonts/
|
||||||
|
destDir := filepath.Join(p.homeDir, ".local", "share", "fonts")
|
||||||
|
log.Debugf("正在安装字体: %s 到 %s", fontPath, destDir)
|
||||||
|
|
||||||
|
// 确保目标目录存在
|
||||||
|
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("创建字体目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查字体是否已安装
|
||||||
|
destPath := filepath.Join(destDir, fontName)
|
||||||
|
if _, err := os.Stat(destPath); err == nil {
|
||||||
|
log.Infof("字体已存在,跳过安装: %s", destPath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制字体文件到目标路径
|
||||||
|
if err := linuxCopyFile(fontPath, destPath); err != nil {
|
||||||
|
return fmt.Errorf("复制字体文件失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新字体缓存
|
||||||
|
if _, err := exec.LookPath("fc-cache"); err == nil {
|
||||||
|
cmd := exec.CommandContext(ctx, "fc-cache", "-f", destDir)
|
||||||
|
if out, err := cmd.CombinedOutput(); err != nil {
|
||||||
|
log.Warnf("刷新字体缓存失败: %s, %v", string(out), err)
|
||||||
|
// 字体文件已复制成功,缓存刷新失败不是致命错误
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warnf("未找到 fc-cache 命令,字体缓存未刷新,可能需要重新登录后生效")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("字体安装成功: %s", fontName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// linuxCopyFile 复制文件,失败时自动清理目标文件
|
||||||
|
func linuxCopyFile(src, dst string) error {
|
||||||
|
input, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer input.Close()
|
||||||
|
|
||||||
|
output, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(output, input)
|
||||||
|
closeErr := output.Close()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(dst)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if closeErr != nil {
|
||||||
|
os.Remove(dst)
|
||||||
|
return closeErr
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ package platform
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/user/devpack/internal/logging"
|
||||||
"golang.org/x/sys/windows/registry"
|
"golang.org/x/sys/windows/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,45 +88,107 @@ func (p *WindowsPlatform) DefaultShell() string {
|
|||||||
return "powershell" // Windows PowerShell 5.1
|
return "powershell" // Windows PowerShell 5.1
|
||||||
}
|
}
|
||||||
|
|
||||||
// InstallFont 安装字体
|
// InstallFont 安装字体到用户级字体目录并注册到注册表
|
||||||
|
// 支持 .ttf (TrueType) 和 .otf (OpenType) 格式
|
||||||
func (p *WindowsPlatform) InstallFont(ctx context.Context, fontPath string) error {
|
func (p *WindowsPlatform) InstallFont(ctx context.Context, fontPath string) error {
|
||||||
// 目标路径为 C:\Users\<User>\AppData\Local\Microsoft\Windows\Fonts
|
log := logging.G()
|
||||||
// 也可以使用 C:\Users\<User>\AppData\Roaming\Microsoft\Windows\Fonts,但 Local 更合适
|
|
||||||
|
// 验证源字体文件是否存在且可访问
|
||||||
|
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")
|
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)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer input.Close()
|
defer input.Close()
|
||||||
|
|
||||||
output, err := os.Create(destPath)
|
output, err := os.Create(dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer output.Close()
|
|
||||||
_, err = io.Copy(output, input)
|
_, err = io.Copy(output, input)
|
||||||
|
// 先关闭文件确保数据写入磁盘
|
||||||
|
closeErr := output.Close()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
os.Remove(dst) // 复制失败,清理残留文件
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if closeErr != nil {
|
||||||
// 写注册表以注册字体
|
os.Remove(dst)
|
||||||
key, _, err := registry.CreateKey(registry.CURRENT_USER, `Software\Microsoft\Windows NT\CurrentVersion\Fonts`, registry.ALL_ACCESS)
|
return closeErr
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
defer key.Close()
|
return nil
|
||||||
regName := fontName + " (TrueType)"
|
|
||||||
|
|
||||||
return key.SetStringValue(regName, destPath)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user