110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
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
|
||
}
|