diff --git a/services/ppt_generator.py b/services/ppt_generator.py index 9b11d4a..5841b5b 100644 --- a/services/ppt_generator.py +++ b/services/ppt_generator.py @@ -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/Docker(noto-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