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

110 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}