fix(ppt): matplotlib CJK fallback covers container Noto CJK JP variant
All checks were successful
CD Pipeline / deploy (push) Successful in 2m19s

實戰驗證在 188 容器內發現:matplotlib font_manager 從 fonts-noto-cjk
ttc 檔只載入 'Noto Sans CJK JP' / 'Noto Serif CJK JP' 兩個變體(TC/SC/HK/KR
名稱未列入 ttflist),導致原本 fallback 清單比對失敗 → font.family 退到
'sans-serif'(DejaVu Sans)→ 中文 glyph 全部缺失印出 UserWarning。

修正:
- _mpl_setup() fallback 清單加入 'Noto Sans CJK JP' / 'Noto Serif CJK JP'
- 最終 fallback 加 substring match(涵蓋未列入但名稱含 CJK/PingFang/
  JhengHei/YaHei/Source Han/WenQuanYi/Hiragino 的字型)
- _mpl_horiz_bar_png 內重複的 cjk_candidates 邏輯移除,改呼叫 _mpl_setup
  避免兩處清單漂移

註:Noto Sans CJK JP ttc 檔本身含完整 CJK Unified Ideographs,可正常
渲染中文(漢字共用),只是字型名稱叫 JP 而已。

bump 模板版本(舊圖表中文方塊版本快取應失效重生):
- daily   v3.0   → v3.0.1
- weekly  v3.0   → v3.0.1
- monthly v3.1   → v3.1.1

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
OoO
2026-05-02 23:38:51 +08:00
parent 1c81866541
commit b5a2b09445

View File

@@ -41,9 +41,9 @@ REPORTS_DIR.mkdir(parents=True, exist_ok=True)
# 路由層會把版本號併入快取 key舊快取自然 miss → 重新生成。
# Bump 規則major 設計改版 +0.1;微調文案不需 bump。
TEMPLATE_VERSIONS = {
'daily': 'v3.0', # 2026-05-02 暖紙封面 + matplotlib 折線 + 4 卡指標 + 附錄頁
'weekly': 'v3.0', # 2026-05-02 暖紙封面 + matplotlib 折線 + 區間結論帶 + 附錄頁
'monthly': 'v3.1', # 2026-05-02 v3.1 升級:封面 elevator + KPI △% + 趨勢折線 + 帕雷托 + 商品 △/🆕 + 附錄頁
'daily': 'v3.0.1', # 2026-05-02 (CJK fix) matplotlib fallback 涵蓋容器 Noto CJK JP
'weekly': 'v3.0.1', # 2026-05-02 (CJK fix) 同上
'monthly': 'v3.1.1', # 2026-05-02 (CJK fix) v3.1 + 容器 CJK 字型 fallback 修正
'strategy': 'v3.0', # 2026-05-02 AI 頁去黑改暖紙 + 附錄頁
'competitor': 'v3.0', # 2026-05-02 AI 頁去黑改暖紙 + 附錄頁
'promo': 'v3.0', # 2026-05-02 AI 頁去黑改暖紙 + 附錄頁
@@ -484,18 +484,8 @@ def _mpl_horiz_bar_png(categories, values, total_width_cm=18.5, total_height_cm=
if not categories or not values:
return None
# 嘗試找一個能顯示中文的字型macOS / Linux 容器都適用
cjk_candidates = [
"PingFang TC", "PingFang SC", "Heiti TC", "Microsoft JhengHei",
"Noto Sans CJK TC", "Noto Sans TC", "Noto Sans CJK",
"Source Han Sans TC", "Source Han Sans", "WenQuanYi Zen Hei",
"Hiragino Sans GB", "Arial Unicode MS",
]
available = {f.name for f in fm.fontManager.ttflist}
chosen_cjk = next((f for f in cjk_candidates if f in available), None)
if chosen_cjk:
plt.rcParams["font.family"] = [chosen_cjk, "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False
# 共用 _mpl_setup 的 fallback 邏輯(含容器 Noto CJK JP 變體
_mpl_setup()
fig = None
try:
@@ -581,7 +571,13 @@ def _add_image_from_buf(slide, buf, l, t, w, h):
def _mpl_setup():
"""共用:設定 CJK 字型並回傳 (matplotlib, plt)"""
"""共用:設定 CJK 字型並回傳 (matplotlib, plt)
fallback 策略先試精確名稱macOS/Windows 環境),再試容器常見的 Noto CJK
JP 變體fonts-noto-cjk 套件 ttc 檔matplotlib ttflist 只載入 JP 命名,但
字型本身含完整 CJK Unified Ideographs可正常顯示中文
最終都找不到時用 substring match覆蓋未列入但名稱含 CJK/Hei 的字型)。
"""
try:
import matplotlib
matplotlib.use("Agg")
@@ -589,14 +585,30 @@ def _mpl_setup():
import matplotlib.font_manager as fm
except ImportError:
return None, None
cjk_candidates = [
"PingFang TC", "PingFang SC", "Heiti TC", "Microsoft JhengHei",
"Noto Sans CJK TC", "Noto Sans TC", "Noto Sans CJK",
"Source Han Sans TC", "Source Han Sans", "WenQuanYi Zen Hei",
"Hiragino Sans GB", "Arial Unicode MS",
# macOS
"PingFang TC", "PingFang SC", "Heiti TC", "Hiragino Sans GB",
# Windows
"Microsoft JhengHei", "Microsoft YaHei",
# Linux/Dockernoto-cjk 套件 ttc 檔matplotlib ttflist 只認 JP 變體
# 但實際渲染中文 OK因為 ttc 內共用同一漢字字型表)
"Noto Sans CJK JP", "Noto Serif CJK JP",
"Noto Sans CJK TC", "Noto Sans CJK SC", "Noto Sans CJK",
"Noto Sans TC", "Noto Sans SC",
"Source Han Sans TC", "Source Han Sans SC", "Source Han Sans",
"WenQuanYi Zen Hei", "Arial Unicode MS",
]
available = {f.name for f in fm.fontManager.ttflist}
chosen_cjk = next((f for f in cjk_candidates if f in available), None)
if not chosen_cjk:
# 最後手段substring match
for name in available:
if any(k in name for k in ("CJK", "PingFang", "JhengHei", "YaHei",
"Source Han", "WenQuanYi", "Hiragino")):
chosen_cjk = name
break
if chosen_cjk:
plt.rcParams["font.family"] = [chosen_cjk, "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False