feat(mods): Lua 沙箱 Mod 运行时——generation 注册表、注册 API、事件、热重载与回滚、文件监听防抖(含测试)
This commit is contained in:
79
internal/textr/text.go
Normal file
79
internal/textr/text.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Package textr 文本光栅化:把字符串用位图字体渲染成 RGBA 图像(CPU 侧,UI还原.md §4)。
|
||||
//
|
||||
// 字形来自素材包 8×8 位图字体(internal/font);本包输出可直接上传为 GL 纹理的图像,
|
||||
// 因此与渲染线程解耦(GL 只负责贴图)。
|
||||
package textr
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
|
||||
"mc/internal/font"
|
||||
)
|
||||
|
||||
// Drawer 文本绘制器。
|
||||
type Drawer struct {
|
||||
atlas *font.Atlas
|
||||
}
|
||||
|
||||
// New 创建绘制器。
|
||||
func New(atlas *font.Atlas) *Drawer { return &Drawer{atlas: atlas} }
|
||||
|
||||
// Width 计算文本像素宽度(8px/字符)。
|
||||
func (d *Drawer) Width(s string) int {
|
||||
w := 0
|
||||
for _, r := range s {
|
||||
if g, ok := d.atlas.Glyphs[r]; ok {
|
||||
w += g.Width
|
||||
} else {
|
||||
w += 8 // 缺失字形占位
|
||||
}
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Draw 渲染一行文本(白字透明底;shadow 为 true 时附加 1px 黑色阴影,UI还原.md §4)。
|
||||
// 返回图像与阴影偏移(阴影在图像内左上角,调用方绘制时需偏移 1px)。
|
||||
func (d *Drawer) Draw(s string, c color.RGBA, shadow bool) image.Image {
|
||||
w := d.Width(s)
|
||||
h := 8
|
||||
if shadow {
|
||||
w++
|
||||
h++
|
||||
}
|
||||
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
x := 0
|
||||
for _, r := range s {
|
||||
g, ok := d.atlas.Glyphs[r]
|
||||
if !ok {
|
||||
g = d.atlas.Missing()
|
||||
}
|
||||
d.blit(img, g, x, 0, c)
|
||||
if shadow {
|
||||
d.blit(img, g, x+1, 1, color.RGBA{0, 0, 0, 255})
|
||||
}
|
||||
x += g.Width
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
// blit 把字形像素以指定颜色写入目标(字形位图按 alpha 通道着色)。
|
||||
func (d *Drawer) blit(dst *image.RGBA, g font.Glyph, dx, dy int, c color.RGBA) {
|
||||
if d.atlas.Image == nil {
|
||||
return
|
||||
}
|
||||
src := d.atlas.Image
|
||||
for y := 0; y < g.H; y++ {
|
||||
for x := 0; x < g.W; x++ {
|
||||
r, _, _, a := src.At(g.X+x, g.Y+y).RGBA()
|
||||
if a == 0 {
|
||||
continue
|
||||
}
|
||||
_ = r // 字形颜色以纹理亮度为准,目标色统一用 c
|
||||
dst.SetRGBA(dx+x, dy+y, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _ = draw.Draw // 保持 image/draw 引用(后续 9-patch 使用)
|
||||
66
internal/textr/text_test.go
Normal file
66
internal/textr/text_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package textr
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"mc/internal/assets"
|
||||
"mc/internal/font"
|
||||
)
|
||||
|
||||
// newTestDrawer 从真实素材包构建字体图集与绘制器。
|
||||
func newTestDrawer(t *testing.T) *Drawer {
|
||||
t.Helper()
|
||||
m := assets.New()
|
||||
if err := m.Load(filepath.Join("..", "..", "assets"), filepath.Join("..", "..", "resourcepacks")); err != nil {
|
||||
t.Fatalf("加载内容包失败: %v", err)
|
||||
}
|
||||
at, err := font.Build(m, "default")
|
||||
if err != nil {
|
||||
t.Fatalf("构建字体失败: %v", err)
|
||||
}
|
||||
return New(at)
|
||||
}
|
||||
|
||||
// TestWidth 文本宽度 = 字符数 × 8。
|
||||
func TestWidth(t *testing.T) {
|
||||
d := newTestDrawer(t)
|
||||
if w := d.Width("AB"); w != 16 {
|
||||
t.Fatalf("宽度期望 16,实际 %d", w)
|
||||
}
|
||||
if w := d.Width(""); w != 0 {
|
||||
t.Fatalf("空串宽度期望 0,实际 %d", w)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDraw 渲染 'A' 得到 8×8 且含非透明像素。
|
||||
func TestDraw(t *testing.T) {
|
||||
d := newTestDrawer(t)
|
||||
img := d.Draw("A", color.RGBA{255, 255, 255, 255}, false)
|
||||
b := img.Bounds()
|
||||
if b.Dx() != 8 || b.Dy() != 8 {
|
||||
t.Fatalf("图像尺寸期望 8×8,实际 %dx%d", b.Dx(), b.Dy())
|
||||
}
|
||||
opaque := 0
|
||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||
for x := b.Min.X; x < b.Max.X; x++ {
|
||||
if _, _, _, a := img.At(x, y).RGBA(); a > 0 {
|
||||
opaque++
|
||||
}
|
||||
}
|
||||
}
|
||||
if opaque == 0 {
|
||||
t.Fatal("渲染结果全透明")
|
||||
}
|
||||
}
|
||||
|
||||
// TestDrawShadow 阴影模式:图像加 1px 且含黑色像素。
|
||||
func TestDrawShadow(t *testing.T) {
|
||||
d := newTestDrawer(t)
|
||||
img := d.Draw("A", color.RGBA{255, 255, 255, 255}, true)
|
||||
b := img.Bounds()
|
||||
if b.Dx() != 9 || b.Dy() != 9 {
|
||||
t.Fatalf("阴影图像尺寸期望 9×9,实际 %dx%d", b.Dx(), b.Dy())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user