改为本地 fonts 字体渲染方案

- 在 message_summary 插件中新增本地字体 CSS 构建逻辑,动态注入模板\n- 使用 fonts/simhei.ttf 与 fonts/simsun.ttf 生成 @font-face,避免外网字体依赖\n- Gemini 总结模板移除 Google Fonts,改用本地字体变量与系统回退栈\n- 补充详细中文注释,说明离线字体加载与容错策略
This commit is contained in:
liuwei
2026-04-23 09:57:25 +08:00
parent 31e488e759
commit 6cf63bc494
2 changed files with 62 additions and 4 deletions

View File

@@ -521,15 +521,71 @@ class MessageSummaryPlugin(MessagePluginInterface):
# 约束:模板只负责展示,正文仍然由模型生成并在此做安全转义后注入。
renderer = HtmlTemplateRenderer()
summary_html = self._summary_markdown_to_html(summary_text)
# 说明:
# 1. 这里注入“本地字体 CSS”到模板避免依赖 Google Fonts 等外网资源;
# 2. 字体文件统一从仓库根目录 fonts/ 下读取,便于部署时统一管理;
# 3. 若字体文件缺失,自动回退系统字体,不影响功能可用性。
local_font_css = self._build_local_font_css()
return renderer.render(
self._summary_image_template_path,
{
"title": f"{group_name} 群聊总结",
"generated_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"summary_html": Markup(summary_html),
"local_font_css": Markup(local_font_css),
},
)
def _build_local_font_css(self) -> str:
"""构建模板可注入的本地字体 CSS。"""
# 说明:
# 1. 使用绝对 file:/// URI确保 Playwright set_content 场景下也能正确定位字体文件;
# 2. 按存在性逐个注册字体,避免硬编码导致不存在文件时报错;
# 3. 仅返回 CSS 字符串,不在此处抛异常,保证模板渲染链路稳定。
try:
project_root = Path(__file__).resolve().parents[2]
font_dir = project_root / "fonts"
simhei_path = font_dir / "simhei.ttf"
simsun_path = font_dir / "simsun.ttf"
css_parts: List[str] = []
if simhei_path.exists():
css_parts.append(
"@font-face {"
"font-family: 'ABotSimHei'; "
f"src: url('{simhei_path.resolve().as_uri()}') format('truetype'); "
"font-weight: 400 900; "
"font-style: normal; "
"font-display: swap; "
"}"
)
if simsun_path.exists():
css_parts.append(
"@font-face {"
"font-family: 'ABotSimSun'; "
f"src: url('{simsun_path.resolve().as_uri()}') format('truetype'); "
"font-weight: 400 700; "
"font-style: normal; "
"font-display: swap; "
"}"
)
# 说明:
# 1. 通过 CSS 变量统一字体栈,模板端只需引用变量即可;
# 2. 先使用本地字体,再回退系统字体,兼顾一致性和容错性。
css_parts.append(
":root { "
"--abot-font-sans: 'ABotSimHei', 'ABotSimSun', 'PingFang SC', "
"'Microsoft YaHei', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; "
"--abot-font-code: 'Cascadia Mono', 'JetBrains Mono', 'Consolas', 'SFMono-Regular', Menlo, monospace; "
"}"
)
return "\n".join(css_parts)
except Exception as e:
self.LOG.warning(f"构建本地字体 CSS 失败,回退系统字体: {e}")
return ""
async def _render_summary_image(
self,
answer: str,

View File

@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<style>
/* 说明:优先尝试加载与 Gemini 示例一致的 Inter / JetBrains Mono失败时回退到系统字体避免服务端无字体时样式崩坏。 */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800;900&family=JetBrains+Mono:wght@500;700&display=swap');
/* 说明:由后端动态注入本地字体 @font-facefonts 目录),避免外网字体依赖。 */
{{ local_font_css }}
:root {
--bg: #f8fafc;
@@ -33,7 +33,8 @@
padding: 10px 0 12px;
background: var(--bg);
color: var(--text);
font-family: "Inter", "PingFang SC", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
/* 说明:优先使用本地字体变量,未命中时回退系统字体。 */
font-family: var(--abot-font-sans, "PingFang SC", "Microsoft YaHei", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);
-webkit-font-smoothing: antialiased;
}
.report-container {
@@ -177,7 +178,8 @@
font-style: italic;
}
.markdown-body code {
font-family: "JetBrains Mono", "SFMono-Regular", Menlo, Consolas, monospace;
/* 说明:代码字体优先使用本地/系统等宽字体栈,保证服务端离线场景可读。 */
font-family: var(--abot-font-code, "Cascadia Mono", "Consolas", "SFMono-Regular", Menlo, monospace);
font-size: 12px;
background: #eff6ff;
color: #1e40af;