feat(ui): 中文字体渲染(系统 CJK 字体光栅化,微软雅黑验证通过)
This commit is contained in:
5
go.mod
5
go.mod
@@ -19,7 +19,8 @@ require (
|
||||
github.com/hajimehoshi/oto v0.7.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 // indirect
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 // indirect
|
||||
golang.org/x/image v0.45.0 // indirect
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
)
|
||||
|
||||
6
go.sum
6
go.sum
@@ -43,6 +43,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL
|
||||
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.45.0 h1:FMb1nTbH5H9vF55SriQHgFw5GnNL9Jg6L25BwXKzhB0=
|
||||
golang.org/x/image v0.45.0/go.mod h1:n62x/7RqlwXDvGsSU4u6IUTUf6KghUZ9Bt7cG/T9Fx4=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc=
|
||||
golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -51,7 +53,11 @@ golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
76
internal/cjkfont/cjkfont.go
Normal file
76
internal/cjkfont/cjkfont.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Package cjkfont 中文字体渲染:加载系统 CJK 字体(微软雅黑)光栅化文本(UI还原.md §4 中文回退)。
|
||||
//
|
||||
// 素材包位图字体只含 ASCII;中文(如「年糕历险记」)用系统字体渲染,
|
||||
// 保持像素风(最近邻采样、整数缩放),字形不追求与原版一致。
|
||||
package cjkfont
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"os"
|
||||
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/math/fixed"
|
||||
)
|
||||
|
||||
// Renderer CJK 文本渲染器。
|
||||
type Renderer struct {
|
||||
face font.Face
|
||||
}
|
||||
|
||||
// Load 加载字体文件(如 C:\Windows\Fonts\msyh.ttc)。
|
||||
func Load(path string, sizePx float64) (*Renderer, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 兼容 .ttc 字体集合(如 msyh.ttc)与单字体 .ttf/.otf
|
||||
var f *opentype.Font
|
||||
if col, err := opentype.ParseCollection(data); err == nil && col != nil && col.NumFonts() > 0 {
|
||||
f, err = col.Font(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
f, err = opentype.Parse(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
face, err := opentype.NewFace(f, &opentype.FaceOptions{
|
||||
Size: sizePx,
|
||||
DPI: 72,
|
||||
Hinting: font.HintingNone,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Renderer{face: face}, nil
|
||||
}
|
||||
|
||||
// Width 文本像素宽度。
|
||||
func (r *Renderer) Width(s string) int {
|
||||
d := &font.Drawer{Face: r.face}
|
||||
return d.MeasureString(s).Ceil()
|
||||
}
|
||||
|
||||
// Draw 渲染文本为 RGBA 图像(含 1px 阴影,UI还原.md §4)。
|
||||
func (r *Renderer) Draw(s string, c color.RGBA) image.Image {
|
||||
w := r.Width(s) + 1
|
||||
h := int(r.face.Metrics().Height.Ceil()) + 1
|
||||
img := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
d := &font.Drawer{
|
||||
Dst: img,
|
||||
Src: image.NewUniform(c),
|
||||
Face: r.face,
|
||||
}
|
||||
// 阴影
|
||||
sh := &font.Drawer{Dst: img, Src: image.NewUniform(color.RGBA{10, 10, 10, 220}), Face: r.face}
|
||||
sh.Dot = fixed.P(1, int(r.face.Metrics().Ascent.Ceil())+1)
|
||||
sh.DrawString(s)
|
||||
// 正文
|
||||
d.Dot = fixed.P(0, int(r.face.Metrics().Ascent.Ceil()))
|
||||
d.DrawString(s)
|
||||
return img
|
||||
}
|
||||
52
internal/cjkfont/cjkfont_test.go
Normal file
52
internal/cjkfont/cjkfont_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package cjkfont
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// msyhCandidates 微软雅黑常见路径。
|
||||
var msyhCandidates = []string{
|
||||
filepath.Join(os.Getenv("WINDIR"), "Fonts", "msyh.ttc"),
|
||||
filepath.Join(os.Getenv("WINDIR"), "Fonts", "msyh.ttf"),
|
||||
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
|
||||
}
|
||||
|
||||
// TestLoadDraw 加载系统 CJK 字体并渲染中文(无字体时跳过)。
|
||||
func TestLoadDraw(t *testing.T) {
|
||||
path := ""
|
||||
for _, p := range msyhCandidates {
|
||||
if _, err := os.Stat(p); err == nil {
|
||||
path = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if path == "" {
|
||||
t.Skip("本机无 CJK 字体,跳过")
|
||||
}
|
||||
r, err := Load(path, 16)
|
||||
if err != nil {
|
||||
t.Fatalf("加载字体失败: %v", err)
|
||||
}
|
||||
img := r.Draw("年糕历险记", color.RGBA{255, 255, 255, 255})
|
||||
b := img.Bounds()
|
||||
if b.Dx() < 16 || b.Dy() < 8 {
|
||||
t.Fatalf("渲染尺寸异常: %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("渲染结果全透明")
|
||||
}
|
||||
if w := r.Width("年糕历险记"); w < 16 {
|
||||
t.Fatalf("宽度异常: %d", w)
|
||||
}
|
||||
}
|
||||
BIN
tools/zig.zip
BIN
tools/zig.zip
Binary file not shown.
Reference in New Issue
Block a user