You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tetris/internal/font/font.go

195 lines
5.7 KiB

package font
import (
"bytes"
"fmt"
"image/color"
"io/fs"
"log"
"os"
"path/filepath"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/text/v2"
"golang.org/x/image/font/gofont/goregular"
)
// FontRenderer handles text rendering with assets/fonts directory support
type FontRenderer struct {
face *text.GoTextFace
}
// NewFontRenderer creates a new font renderer using only assets/fonts directory
func NewFontRenderer() *FontRenderer {
var fontData []byte
var fontSource string
// 优先加载 assets/fonts 目录中的字体
if data, source := loadFromAssetsDirectory(); data != nil {
fontData = data
fontSource = source
log.Printf("✅ 使用项目字体: %s", source)
} else {
// 如果没有找到项目字体,使用 Go 默认字体
fontData = goregular.TTF
fontSource = "Go默认字体 (建议添加中文字体到 assets/fonts 目录)"
log.Printf("⚠ 未找到项目字体,使用默认字体。建议将中文字体放入 assets/fonts/ 目录")
}
// 创建字体源
source, err := createFontSource(fontData, fontSource)
if err != nil {
// 最终回退 - 使用 Go 默认字体
log.Printf("❌ 字体加载失败,回退到默认字体: %v", err)
source, err = text.NewGoTextFaceSource(bytes.NewReader(goregular.TTF))
if err != nil {
panic(fmt.Sprintf("无法加载默认字体: %v", err))
}
fontSource = "Go默认字体 (回退)"
}
face := &text.GoTextFace{
Source: source,
Size: 16,
}
log.Printf("🎨 字体系统初始化完成,使用: %s", fontSource)
return &FontRenderer{
face: face,
}
}
// loadFromAssetsDirectory 专门扫描 assets/fonts 目录并加载字体文件
func loadFromAssetsDirectory() ([]byte, string) {
assetsPath := "assets/fonts"
// 检查目录是否存在
if _, err := os.Stat(assetsPath); os.IsNotExist(err) {
log.Printf("📁 assets/fonts 目录不存在,请创建并放入字体文件")
return nil, ""
}
// 扫描目录下的所有字体文件
var fontFiles []string
err := filepath.WalkDir(assetsPath, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() {
ext := strings.ToLower(filepath.Ext(path))
if ext == ".ttf" || ext == ".ttc" || ext == ".otf" {
fontFiles = append(fontFiles, path)
}
}
return nil
})
if err != nil {
log.Printf("❌ 扫描 assets/fonts 目录失败: %v", err)
return nil, ""
}
if len(fontFiles) == 0 {
log.Printf("📂 assets/fonts 目录为空,请添加字体文件 (.ttf, .ttc, .otf)")
return nil, ""
}
log.Printf("🔍 在 assets/fonts 目录找到 %d 个字体文件", len(fontFiles))
// 字体优先级:微软雅黑 > 其他中文字体 > 任意字体
fontPriorities := [][]string{
{"msyh", "yahei", "microsoft"}, // 微软雅黑系列
{"noto", "source", "思源", "源"}, // 思源/Noto系列
{"pingfang", "hiragino", "苹方"}, // 苹果字体系列
{"simhei", "simkai", "simsun", "宋", "黑"}, // 系统中文字体
}
// 按优先级查找字体
for _, priorityGroup := range fontPriorities {
for _, keyword := range priorityGroup {
for _, fontFile := range fontFiles {
fileName := strings.ToLower(filepath.Base(fontFile))
if strings.Contains(fileName, keyword) {
if data, err := tryLoadFont(fontFile); err == nil {
log.Printf("🎯 找到优先字体 (%s): %s", keyword, fontFile)
return data, fontFile
} else {
log.Printf("⚠ 字体文件损坏或不支持: %s, 错误: %v", fontFile, err)
}
}
}
}
}
// 如果没有找到优先字体,使用第一个可用的字体
for _, fontFile := range fontFiles {
if data, err := tryLoadFont(fontFile); err == nil {
log.Printf("📝 使用可用字体: %s", fontFile)
return data, fontFile
} else {
log.Printf("⚠ 跳过不可用字体: %s, 错误: %v", fontFile, err)
}
}
log.Printf("❌ assets/fonts 目录中没有可用的字体文件")
return nil, ""
}
// tryLoadFont 尝试加载字体文件并验证格式
func tryLoadFont(fontPath string) ([]byte, error) {
data, err := os.ReadFile(fontPath)
if err != nil {
return nil, fmt.Errorf("读取字体文件失败: %w", err)
}
// 验证字体数据是否可以被解析
_, err = createFontSource(data, fontPath)
if err != nil {
return nil, fmt.Errorf("字体格式验证失败: %w", err)
}
return data, nil
}
// createFontSource 创建字体源,处理可能的collection错误
func createFontSource(fontData []byte, fontSource string) (*text.GoTextFaceSource, error) {
// 尝试直接加载
source, err := text.NewGoTextFaceSource(bytes.NewReader(fontData))
if err != nil {
// 如果是 collection 错误,提供更详细的信息
errStr := err.Error()
if strings.Contains(errStr, "collection") {
return nil, fmt.Errorf("TTC集合字体文件不支持,请使用TTF格式: %s", filepath.Base(fontSource))
}
return nil, fmt.Errorf("字体文件格式错误: %w", err)
}
return source, nil
}
// DrawText draws text at the specified position with proper Chinese support
func (fr *FontRenderer) DrawText(screen *ebiten.Image, str string, x, y float64, clr color.Color) {
op := &text.DrawOptions{}
op.GeoM.Translate(x, y)
op.ColorScale.ScaleWithColor(clr)
text.Draw(screen, str, fr.face, op)
}
// SetFontSize sets the font size
func (fr *FontRenderer) SetFontSize(size float64) {
fr.face.Size = size
}
// MeasureText returns text dimensions with better accuracy
func (fr *FontRenderer) MeasureText(str string) (float64, float64) {
width, _ := text.Measure(str, fr.face, 0)
return width, fr.face.Size
}
// GetFontInfo returns information about the currently loaded font
func (fr *FontRenderer) GetFontInfo() string {
return fmt.Sprintf("字体大小: %.1f", fr.face.Size)
}