Files
ngzz-mc/internal/assets/font.go

110 lines
3.0 KiB
Go
Raw Normal View History

package assets
import (
"encoding/json"
"fmt"
"os"
"strings"
)
// FontProvider 字体 provider(font/*.json,原版结构,设计.md §1.3)。
// 支持 type:bitmap(位图字形)与 reference(引用其他 provider 文件)。
type FontProvider struct {
Type string `json:"type"`
File string `json:"file"` // bitmap:相对 textures/ 的位图路径(无扩展名)
Chars []string `json:"chars"`
Height int `json:"height"`
Ascent int `json:"ascent"`
ID string `json:"id"` // reference:目标 provider 文件(minecraft:include/default 等)
}
// Font 字体定义(reference 已递归展开)。
type Font struct {
Providers []FontProvider
}
// Glyph 一个字符的字形定位信息。
type Glyph struct {
File string // 位图资源键(相对 textures/,如 font/ascii)
Index int // 在 chars 拼接串中的索引
Height int // 字形高度(px)
Ascent int // 基线偏移
}
// LoadFont 解析 font/<name>.json 并展开 reference provider(防环)。
func (m *Manager) LoadFont(name string) (*Font, error) {
providers, err := m.loadFontProviders(name, map[string]bool{})
if err != nil {
return nil, err
}
return &Font{Providers: providers}, nil
}
// loadFontProviders 递归加载 provider 文件;stack 用于循环引用检测。
func (m *Manager) loadFontProviders(name string, stack map[string]bool) ([]FontProvider, error) {
path, ok := m.find("font", name, ".json")
if !ok {
return nil, fmt.Errorf("assets.loadFontProviders: 字体 %q 不存在", name)
}
if stack[name] {
return nil, fmt.Errorf("assets.loadFontProviders: 字体引用循环 %q", name)
}
stack[name] = true
defer delete(stack, name)
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("assets.loadFontProviders %s: %w", name, err)
}
var def struct {
Providers []FontProvider `json:"providers"`
}
if err := json.Unmarshal(b, &def); err != nil {
return nil, fmt.Errorf("assets.loadFontProviders %s: %w", name, err)
}
var out []FontProvider
for _, p := range def.Providers {
if p.Type == "reference" {
// 展开引用:minecraft:include/default → font/include/default.json
refName := strings.TrimPrefix(p.ID, "minecraft:")
sub, err := m.loadFontProviders(refName, stack)
if err != nil {
return nil, err
}
out = append(out, sub...)
continue
}
out = append(out, p)
}
return out, nil
}
// Glyph 查找字符字形(按 providers 顺序,首个命中优先)。
func (f *Font) Glyph(ch rune) (Glyph, bool) {
for _, p := range f.Providers {
if p.Type != "bitmap" {
continue
}
idx := charIndex(p.Chars, ch)
if idx >= 0 {
return Glyph{File: p.File, Index: idx, Height: p.Height, Ascent: p.Ascent}, true
}
}
return Glyph{}, false
}
// charIndex 返回 rune 在 chars 拼接串中的索引;未找到返回 -1。
func charIndex(chars []string, ch rune) int {
offset := 0
for _, s := range chars {
for i, r := range s {
if r == ch {
return offset + i
}
}
offset += len([]rune(s))
}
return -1
}