// 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 }