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

120 lines
3.2 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 font 字体图集:解析素材包字体 provider,构建字形像素区域表(UI还原.md §4)。
//
// 原版字体为 8×8 位图(如 font/ascii.png,128×128 = 16×16 格),
// 本包负责把「provider 位图 + chars 索引」映射为每个 rune 的像素矩形与度量。
package font
import (
"fmt"
"image"
"image/png"
"os"
"strings"
"mc/internal/assets"
)
// Glyph 一个字形的像素区域与度量。
type Glyph struct {
X, Y int // 位图内像素位置(左上角)
W, H int // 像素尺寸(8×8,含下伸行高 9)
Width int // 渲染步进宽度(8)
Height int // 行高(provider.height,缺省 8)
Ascent int // 基线偏移(provider.ascent,缺省 8)
}
// Atlas 字体图集:位图图像 + 字形表。
type Atlas struct {
Image image.Image
Glyphs map[rune]Glyph
}
// Missing 缺失字形(用于未覆盖字符的回退显示)。
func (a *Atlas) Missing() Glyph { return Glyph{X: 0, Y: 0, W: 8, H: 8, Width: 8, Height: 8, Ascent: 8} }
// Build 从资源管理器加载字体(name 如 "default"),reference provider 已由 assets 展开。
// 每个 bitmap provider 对应一张 8px 网格位图:列数 = 图宽 / 8。
// 主位图(Atlas.Image)确定性选择:优先含 "ascii" 的位图,否则取首个(供 UI 直接采样)。
func Build(m *assets.Manager, name string) (*Atlas, error) {
f, err := m.LoadFont(name)
if err != nil {
return nil, fmt.Errorf("font.Build: %w", err)
}
at := &Atlas{Glyphs: make(map[rune]Glyph)}
imgCache := make(map[string]image.Image)
var primaryName string
for _, p := range f.Providers {
if p.Type != "bitmap" || p.File == "" {
continue
}
img, ok := imgCache[p.File]
if !ok {
// 规范化纹理键:minecraft:font/ascii.png → font/ascii(素材包纹理键格式)
key := strings.TrimPrefix(p.File, "minecraft:")
key = strings.TrimSuffix(key, ".png")
path, found := m.ResolveTexture(key)
if !found {
continue // 该包没有此位图:跳过
}
img, err = loadPNG(path)
if err != nil {
return nil, fmt.Errorf("font.Build %s: %w", p.File, err)
}
imgCache[p.File] = img
if primaryName == "" {
primaryName = p.File // 首个位图作为回退主位图
}
if strings.Contains(p.File, "ascii") {
primaryName = p.File // ascii 优先(UI 主字体)
}
}
cols := img.Bounds().Dx() / 8
if cols <= 0 {
cols = 16
}
height, ascent := p.Height, p.Ascent
if height <= 0 {
height = 8
}
if ascent <= 0 {
ascent = height
}
idx := 0
for _, chars := range p.Chars {
for _, r := range chars {
g := Glyph{
X: (idx % cols) * 8,
Y: (idx / cols) * 8,
W: 8,
H: height,
Width: 8,
Height: height,
Ascent: ascent,
}
if _, dup := at.Glyphs[r]; !dup {
at.Glyphs[r] = g
}
idx++
}
}
}
if primaryName != "" {
at.Image = imgCache[primaryName] // 确定性主位图
}
return at, nil
}
// loadPNG 解码 PNG。
func loadPNG(path string) (image.Image, error) {
fp, err := os.Open(path)
if err != nil {
return nil, err
}
defer fp.Close()
img, err := png.Decode(fp)
if err != nil {
return nil, err
}
return img, nil
}