Files
tamakyi e807dea218 feat(share-card): 字体自动发现 + 启动探测,缺字体不再等到第一次请求才 500
- 系统候选补上 glyf 轮廓的 CJK 字体(文泉驿正黑/微米黑、DroidSansFallback、
  AR PL UMing),并把 Noto Sans CJK 压到最后:它是 CFF/OTTO,freetype 必定解析失败,
  排前面只会白白多读十几 MB
- static/fonts 同时按当前工作目录与可执行文件目录查找,systemd 没配
  WorkingDirectory 时也能找到(原先只按 CWD,文件在却报「没有可用字体」)
- 启动时探一次字体:命中打 Info 带路径,缺失打 WARN 并给出补救命令
  (apt install fonts-wqy-zenhei / 放 static/fonts/ / 配 font_path)
- .ttc 先提取首个字面再用(仅对 glyf 轮廓的集合有效,如文泉驿);
  失败结果缓存 1 分钟,避免公开端点被「每请求重新解析十几 MB」放大

实测:本地实例 apt install fonts-wqy-zenhei 后无需任何配置,启动即打出
font=/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc,/share-card 与单题分享图
从 500 变为 200 image/jpeg,中文与二维码渲染正常。
2026-10-27 12:37:48 +08:00

72 lines
2.7 KiB
Go
Raw Permalink 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 question
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/tamakyi/TamaBox/internal/conf"
)
// TestFontCandidatesOrder 锁住候选字体的优先级。
//
// 顺序错了不会报错,只会让线上多花几秒去解析一个注定失败的 19 MB CFF 字体
// (Noto Sans CJK 是 OTTO 轮廓,freetype 不支持),甚至在缺字体时把排障引向
// 「配了 font_path 却仍失败」的错误方向。
func TestFontCandidatesOrder(t *testing.T) {
old := conf.App.FontPath
t.Cleanup(func() { conf.App.FontPath = old })
conf.App.FontPath = "/opt/fonts/mine.ttf"
got := fontCandidates()
require.NotEmpty(t, got)
require.Equal(t, "/opt/fonts/mine.ttf", got[0], "配置项必须排在最前")
idxWQY, idxNoto := -1, -1
for i, p := range got {
if strings.Contains(p, "wqy-zenhei") {
idxWQY = i
}
if strings.Contains(p, "NotoSansCJK") && idxNoto < 0 {
idxNoto = i
}
}
require.NotEqual(t, -1, idxWQY, "系统 glyf 候选(文泉驿)应保留")
require.NotEqual(t, -1, idxNoto, "CFF 兜底候选应保留")
require.Less(t, idxWQY, idxNoto, "glyf 轮廓的系统字体必须排在 Noto(CFF) 之前")
}
// TestStaticFontDirs 校验两条不变式:返回的目录一定真实存在、且不重复。
// 存在性很重要 —— 把不存在的目录交给 Glob 不会报错,只会静默返回空,
// 于是「字体明明放在 static/fonts 却找不到」这种问题查起来毫无线索。
func TestStaticFontDirs(t *testing.T) {
seen := make(map[string]struct{})
for _, d := range staticFontDirs() {
st, err := os.Stat(d)
require.NoError(t, err, "候选目录必须存在:%s", d)
require.True(t, st.IsDir(), "候选必须是目录:%s", d)
_, dup := seen[d]
require.False(t, dup, "目录重复:%s", d)
seen[d] = struct{}{}
}
}
// TestLoadFontWithPathReportsTried 确认失败信息里列出实际尝试过的路径 ——
// 缺字体时这条文案就是唯一线索(分享卡是公开端点,没人看得到 console)。
func TestLoadFontWithPathReportsTried(t *testing.T) {
old := conf.App.FontPath
t.Cleanup(func() { conf.App.FontPath = old })
conf.App.FontPath = filepath.Join(t.TempDir(), "definitely-missing.ttf")
// 环境里可能偏偏装着可用系统字体,那就只能跳过(不断言失败)。
if _, path, err := loadFontWithPath(); err == nil {
t.Skipf("本机存在可用系统字体 %q,跳过失败路径断言", path)
} else {
require.Contains(t, err.Error(), "definitely-missing.ttf", "错误信息必须列出尝试过的路径")
require.Contains(t, err.Error(), "TrueType 轮廓", "错误信息必须点明需要 glyf 轮廓")
}
}