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

115 lines
2.9 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。
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)
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
}
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 at.Image == nil && len(imgCache) > 0 {
for _, img := range imgCache { // 取第一张作为主位图(测试/调试用)
at.Image = img
break
}
}
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
}