Files
Scoreboard/vendor/fonts/tools/gen-manifest.py
T
AskaEth bc8b1ee6c0 feat: M3-M7 全模块落地 + 集成接线
- M3 认证/权限/审计/限流 + 全部 REST 接口
- M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制)
- M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配)
- M6 WebSocket 实时同步中心
- M7 xlsx 导出 + 赛事包 zip + 备份
- 规则包 YAML 导入导出(差异预览)
- 字体 vendor 子集化(4 套 x2 字重)
- 集成:realtime/export/rules 接入 main+httpapi;backup 子命令
- 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
2026-09-09 19:02:24 +08:00

133 lines
4.6 KiB
Python

#!/usr/bin/env python3
"""生成 vendor/fonts/manifest.json。
数据源:vendor/fonts/tools/fonts.conf(字体元数据)+ vendor/fonts/*.woff2(实际产物)。
每次跑完 subset-fonts.sh 会自动调用;也可单独运行:
python3 vendor/fonts/tools/gen-manifest.py
"""
from __future__ import annotations
import hashlib
import json
import subprocess
import sys
from datetime import date
from pathlib import Path
HERE = Path(__file__).resolve().parent
OUT_DIR = HERE.parent
ROOT = OUT_DIR.parent.parent # 仓库根,用于生成 vendor/fonts/... 形式的相对路径
CONF = HERE / "fonts.conf"
WEIGHTS = (("regular", 400), ("bold", 700))
SUBSET_DESC = "ASCII + GB2312 一级/二级汉字 + 中文标点 + 数字符号"
def charset_count() -> int:
"""复用 subset-fonts.sh 的字符集生成逻辑,保证清单与产物一致。"""
code = (
"chars=set()\n"
"for c in range(0x20,0x7F): chars.add(chr(c))\n"
"for hi in range(0xA1,0xFF):\n"
" for lo in range(0xA1,0xFF):\n"
" try: chars.add(bytes((hi,lo)).decode('gb2312'))\n"
" except UnicodeDecodeError: pass\n"
"chars.update(' ·—…‘’“”※→←↑↓√×÷±≈≠≤≥∞°℃℉¥€£©®™№§¶†‡•‰′″〇')\n"
"chars.update('「」『』【】〔〕〖〗《》〈〉')\n"
"chars.update('0123456789')\n"
"chars.update('!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~')\n"
"print(len(chars))\n"
)
return int(subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, check=True).stdout)
def human(n: int) -> str:
return f"{n / 1024 / 1024:.2f} MB" if n >= 1024 * 1024 else f"{n / 1024:.0f} KB"
def read_conf() -> list[dict[str, str]]:
rows = []
for line in CONF.read_text(encoding="utf-8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
fid, name, family, category, license_, source, reg, bold = (p.strip() for p in line.split("|"))
rows.append(
{
"id": fid,
"displayName": name,
"fontFamily": family,
"category": category,
"license": license_,
"source": source,
"regular": reg,
"bold": bold,
}
)
return rows
def main() -> int:
fonts = []
grand_total = 0
for row in read_conf():
weights = []
font_total = 0
for weight_name, weight_class in WEIGHTS:
path = OUT_DIR / f"{row['id']}-{weight_name}.woff2"
if not path.is_file():
print(f"缺少产物: {path},请先运行 subset-fonts.sh", file=sys.stderr)
return 1
data = path.read_bytes()
size = len(data)
font_total += size
weights.append(
{
"weight": weight_class,
"style": "normal",
"file": path.name,
"path": str(path.relative_to(ROOT)),
"bytes": size,
"size": human(size),
"sha256": hashlib.sha256(data).hexdigest(),
}
)
grand_total += font_total
fonts.append(
{
"id": row["id"],
"displayName": row["displayName"],
"fontFamily": row["fontFamily"],
"cssVar": f"--font-{row['id']}",
"category": row["category"],
"license": row["license"],
"source": row["source"],
"totalBytes": font_total,
"totalSize": human(font_total),
"weights": weights,
}
)
manifest = {
"version": 1,
"generatedAt": date.today().isoformat(),
"generator": "vendor/fonts/tools/subset-fonts.sh",
"subset": {
"charset": SUBSET_DESC,
"charCount": charset_count(),
"format": "woff2",
"layoutFeatures": "kern,liga,clig,calt,ccmp,locl,mark,mkmk",
},
"totalBytes": grand_total,
"totalSize": human(grand_total),
"fonts": fonts,
}
out = OUT_DIR / "manifest.json"
out.write_text(json.dumps(manifest, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"manifest.json: {len(fonts)} 套字体,{len(fonts) * 2} 个文件,合计 {human(grand_total)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())