# visual_renderer.py (滿血防豆腐塊、雙字型分流完全體) from PIL import Image, ImageDraw, ImageFont import os def draw_glossing_card(data, output_path): """ 將四行分析畫在畫布上 包含動態寬度計算,防止長句被截斷(支援雙字型分流防亂碼) """ font_size = 32 font_zh_path = "NotoSansTC-Regular.ttf" # 💡 核心改動:定義高階拉丁符號(族語專用)的字型候補清單 # Linux 伺服器通常內建 DejaVuSans 或 LiberationSans,地端 Windows 則有 arial font_ind_candidates = [ "DejaVuSans.ttf", "LiberationSans-Regular.ttf", "arial.ttf", "Arial.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" ] # 1. 載入中文大腦 try: font_zh = ImageFont.truetype(font_zh_path, font_size) except Exception as e: print(f"⚠️ 找不到中文字型或載入失敗: {e}") font_zh = ImageFont.load_default() # 2. 載入族語拉丁大腦(尋找認得 ʉ 和 ’ 的字型) font_ind = None for f_path in font_ind_candidates: try: font_ind = ImageFont.truetype(f_path, font_size) print(f"✅ 族語圖卡成功啟用特殊符號防彈字型: {f_path}") break except: continue if font_ind is None: print("⚠️ 未找到特殊拉丁字型,自動降級使用中文字型(可能出現豆腐塊)") font_ind = font_zh # 建立一個臨時畫布來精準測量文字寬度 (相容不同版本的 Pillow) temp_img = Image.new('RGB', (1, 1)) temp_draw = ImageDraw.Draw(temp_img) def get_text_width(text, font_obj): if hasattr(temp_draw, 'textlength'): return temp_draw.textlength(text, font=font_obj) elif hasattr(temp_draw, 'textbbox'): return temp_draw.textbbox((0,0), text, font=font_obj)[2] else: return font_obj.getsize(text)[0] # 💡 3. 動態計算畫布所需的總寬度(依據各自使用的字型精準測量) x_offset = 80 for i in range(len(data['glossing']['line1'])): w1_len = get_text_width(data['glossing']['line1'][i], font_ind) # 族語用拉丁字型量 w2_len = get_text_width(data['glossing']['line2'][i], font_ind) # 構詞用拉丁字型量 w3_len = get_text_width(data['glossing']['line3'][i], font_zh) # 中文用中文字型量 x_offset += max(w1_len, w2_len, w3_len) + 50 # 每個單詞群組保留 50px 間距 # 計算第四行(翻譯)的寬度 trans_w = get_text_width(data['translation'], font_zh) # 決定最終畫布寬度 canvas_width = int(max(1200, x_offset + 80, trans_w + 160)) canvas_height = 500 # 固定高度 bg_color = (253, 248, 241) # 米白色紙張感 img = Image.new('RGB', (canvas_width, canvas_height), color=bg_color) draw = ImageDraw.Draw(img) # 💡 4. 開始分流繪製 curr_x = 80 y_start = 100 line_spacing = 80 # 四行等距 for i in range(len(data['glossing']['line1'])): w1 = data['glossing']['line1'][i] w2 = data['glossing']['line2'][i] w3 = data['glossing']['line3'][i] # 🚀 第一、二行強行注入 font_ind,徹底封殺 ʉ 與 ’ 的豆腐塊 draw.text((curr_x, y_start), w1, fill=(0,0,0), font=font_ind) draw.text((curr_x, y_start + line_spacing), w2, fill=(139,0,0), font=font_ind) # 🚀 第三行回歸 font_zh,確保中文解說完美顯示 draw.text((curr_x, y_start + line_spacing * 2), w3, fill=(85,85,85), font=font_zh) # 推進 X 座標到下一個單詞的起點 bbox_w1 = get_text_width(w1, font_ind) bbox_w2 = get_text_width(w2, font_ind) bbox_w3 = get_text_width(w3, font_zh) curr_x += max(bbox_w1, bbox_w2, bbox_w3) + 50 # 🚀 第四行回歸 font_zh 畫出整句意譯 draw.text((80, y_start + line_spacing * 3.5), data['translation'], fill=(0,0,0), font=font_zh) img.save(output_path) return output_path