From 44705c79a71f0fa17e79aab29ee1aaf39c303743 Mon Sep 17 00:00:00 2001 From: MarkLo Date: Thu, 27 Nov 2025 03:58:07 +0800 Subject: [PATCH] --- backend/app/services/pdf_generator.py | 127 +++++++++++++------------- safe_emoji_mapping.py | 93 +++++++++++++++++++ test_ascii_pdf.py | 38 ++++++++ test_symbol_rendering.py | 56 ++++++++++++ 4 files changed, 250 insertions(+), 64 deletions(-) create mode 100644 safe_emoji_mapping.py create mode 100644 test_ascii_pdf.py create mode 100644 test_symbol_rendering.py diff --git a/backend/app/services/pdf_generator.py b/backend/app/services/pdf_generator.py index c9a62b03..376e0322 100644 --- a/backend/app/services/pdf_generator.py +++ b/backend/app/services/pdf_generator.py @@ -21,83 +21,82 @@ import markdown class PDFGenerator: """Generate PDF reports from markdown content""" - # Emoji to Unicode symbol mapping for PDF compatibility - # Emojis don't render well in PDFs, so we replace them with Unicode text symbols - # NOTE: Use ASCII brackets [] not full-width [] for better font compatibility - # NOTE: Avoid [文字] formats - use pure symbols only + # Emoji to safe ASCII character mapping for PDF compatibility + # STSong-Light font has issues with certain Unicode symbols + # Using ONLY ASCII characters to ensure perfect rendering EMOJI_TO_UNICODE = { - # Status & Indicators - '✅': '✓', - '❌': '✗', - '⚠️': '⚠', - '⚡': '⚡', - '🔔': '◉', + # Status & Indicators - ASCII only + '✅': '[OK]', + '❌': '[X]', + '⚠️': '[!]', + '⚡': '*', + '🔔': 'o', - # Rating & Quality - '⭐': '★', - '🌟': '☆', - '💎': '◆', - '🏆': '◈', + # Rating & Quality - ASCII only + '⭐': '*', + '🌟': '*', + '💎': '+', + '🏆': '#', - # Charts & Analytics - pure symbols only - '📊': '▓', - '📈': '↑', - '📉': '↓', - '📋': '▪', - '📌': '◆', + # Charts & Analytics - ASCII or empty + '📊': '', + '📈': '^', + '📉': 'v', + '📋': '-', + '📌': '*', - # Money & Business - symbols only + # Money & Business - ASCII currency letters '💰': '$', '💵': '$', - '💴': '¥', - '💶': '€', - '💷': '£', + '💴': 'Y', # 日元 + '💶': 'E', # 歐元 + '💷': 'P', # 英鎊 '💸': '$', - '💹': '↑', + '💹': '^', - # Direction & Movement - '🚀': '↑↑', - '⬆️': '↑', - '⬇️': '↓', - '➡️': '→', - '⬅️': '←', - '🔼': '▲', - '🔽': '▼', + # Direction & Movement - ASCII arrows + '🚀': '^^', + '⬆️': '^', + '⬇️': 'v', + '➡️': '>', + '⬅️': '<', + '🔼': '^', + '🔽': 'v', - # Symbols - '🎯': '◎', - '🔥': '※', - '💡': '◐', - '⚙️': '⚙', - '🔧': '►', - '🔨': '►', + # Symbols - ASCII only + '🎯': 'o', + '🔥': '*', + '💡': '*', + '⚙️': '*', + '🔧': '>', + '🔨': '>', - # AI & Tech - symbols only - '🤖': '▣', - '💻': '▣', - '📱': '▣', - '🖥️': '▣', + # AI & Tech - remove or simple ASCII + '🤖': '', + '💻': '', + '📱': '', + '🖥️': '', - # People & Roles - symbols only - '👤': '◇', - '👥': '◇◇', - '🔬': '◈', - '📚': '▪', + # People & Roles - remove + '👤': '', + '👥': '', + '🔬': '', + '📚': '', - # Time - symbols only - '⏰': '◷', - '📅': '▪', - '⏱️': '◷', + # Time - simple ASCII + '⏰': 'o', + '📅': '-', + '⏱️': 'o', - # Other common emojis - symbols only - '✨': '‧', - '🎨': '◈', - '📝': '▪', - '📄': '▪', - '🗂️': '▪', - '🌐': '◎', - '🔗': '∞', - '💼': '▣', + # Other common emojis - ASCII or remove + '✨': '*', + '🎨': '', + '📝': '-', + '📄': '-', + '🗂️': '=', + '🌐': 'o', + '🔗': '~', + '💼': '', } """Generate PDF reports from markdown content""" diff --git a/safe_emoji_mapping.py b/safe_emoji_mapping.py new file mode 100644 index 00000000..ca1a4e0b --- /dev/null +++ b/safe_emoji_mapping.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +最簡單的解決方案:完全移除可能導致問題的 Unicode 符號 +改用最安全的 ASCII 和基本中文字符 +""" + +# 問題分析: +# STSong-Light 對某些 Unicode 符號的支持不完整 +# 可能將某些符號(如 ★ ※ ◈ 等)錯誤渲染為「煉」 + +# 解決方案: +# 1. 完全移除這些 Unicode 符號 +# 2. 改用 ASCII 字符或簡單的中文文字 +# 3. 如果必須使用符號,只使用最基本的 ASCII 符號 + +SAFE_EMOJI_MAPPING = { + # Status - 使用 ASCII + '✅': '[OK]', + '❌': '[X]', + '⚠️': '[!]', + '⚡': '*', + '🔔': 'o', + + # Rating - 使用 ASCII + '⭐': '*', + '🌟': '*', + '💎': '+', + '🏆': '#', + + # Charts - 使用簡單文字 + '📊': '', # 完全移除 + '📈': '^', # 向上 + '📉': 'v', # 向下 + '📋': '-', + '📌': '*', + + # Money - 保留貨幣符號(這些是安全的) + '💰': '$', + '💵': '$', + '💴': 'Y', # 改用 ASCII Y 代替 ¥ + '💶': 'E', # 改用 ASCII E 代替 € + '💷': 'P', # 改用 ASCII P 代替 £ + '💸': '$', + '💹': '^', + + # Direction - 使用 ASCII + '🚀': '^^', + '⬆️': '^', + '⬇️': 'v', + '➡️': '>', + '⬅️': '<', + '🔼': '^', + '🔽': 'v', + + # Symbols - 純 ASCII + '🎯': 'o', + '🔥': '*', + '💡': '*', + '⚙️': '*', + '🔧': '>', + '🔨': '>', + + # Tech - 全部移除或改 ASCII + '🤖': '', + '💻': '', + '📱': '', + '🖥️': '', + + # People - 全部移除 + '👤': '', + '👥': '', + '🔬': '', + '📚': '', + + # Time - 純 ASCII + '⏰': 'o', + '📅': '-', + '⏱️': 'o', + + # Other - 純 ASCII 或移除 + '✨': '*', + '🎨': '', + '📝': '-', + '📄': '-', + '🗂️': '=', + '🌐': 'o', + '🔗': '~', + '💼': '', +} + +print("安全的 Emoji 映射(只使用 ASCII 和基本字符):") +print(SAFE_EMOJI_MAPPING) diff --git a/test_ascii_pdf.py b/test_ascii_pdf.py new file mode 100644 index 00000000..63b8f5a8 --- /dev/null +++ b/test_ascii_pdf.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import sys +sys.path.insert(0, '/Users/yaolo/Desktop/TradingAgentsX') + +from backend.app.services.pdf_generator import PDFGenerator + +# 測試包含各種符號的內容 +test_content = '''# 測試報告 + +## 市場分析 +- 上漲趨勢 ^ +- 技術指標 * +- 支撐位 o + +## 風險評估 +- 警告標記 [!] +- 確認標記 [OK] +- 否定標記 [X] + +## 結論 +純文字和 ASCII 符號測試,不應該出現「煉」字 +''' + +pdf_gen = PDFGenerator() +pdf_bytes = pdf_gen.generate_analyst_report_pdf( + analyst_name='ASCII 符號測試', + ticker='TEST', + analysis_date='2025-11-27', + report_content=test_content +) + +with open('/tmp/test_ascii_only.pdf', 'wb') as f: + f.write(pdf_bytes) + +print('✓ 已生成測試 PDF: /tmp/test_ascii_only.pdf') +print('請檢查 PDF 中是否還有「煉」字出現') +print('如果沒有「煉」,說明 ASCII-only 方案成功解決問題') diff --git a/test_symbol_rendering.py b/test_symbol_rendering.py new file mode 100644 index 00000000..e4e356c0 --- /dev/null +++ b/test_symbol_rendering.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +測試找出哪個字符在 STSong-Light 中被渲染為「煉」 +""" +import sys +sys.path.insert(0, '/Users/yaolo/Desktop/TradingAgentsX') + +from backend.app.services.pdf_generator import PDFGenerator +import io + +# 測試內容 - 包含我們使用的所有符號 +test_symbols = { + '星號': '★', + '菱形': '◆', + '方塊': '▓', + '小方': '▪', + '雙菱': '◈', + '空菱': '◇', + '雙圓': '◎', + '米字': '※', + '時鐘': '◷', + '點': '‧', + '無限': '∞', + '粗方': '▣', + '對號': '✓', + '叉號': '✗', + '警告': '⚠', + '閃電': '⚡', +} + +print("測試每個符號在 PDF 中的渲染...\n") + +pdf_gen = PDFGenerator() + +for name, symbol in test_symbols.items(): + test_content = f"測試{name}符號: {symbol}" + + try: + pdf_bytes = pdf_gen.generate_analyst_report_pdf( + analyst_name=f"測試: {symbol}", + ticker="TEST", + analysis_date="2025-11-27", + report_content=test_content + ) + + # 保存測試 PDF + filename = f"/tmp/test_symbol_{name}.pdf" + with open(filename, 'wb') as f: + f.write(pdf_bytes) + + print(f"✓ {name} ({symbol}) - U+{ord(symbol):04X} - 已生成: {filename}") + except Exception as e: + print(f"✗ {name} ({symbol}) - 錯誤: {e}") + +print("\n請手動檢查 /tmp/test_symbol_*.pdf 文件,看看哪個符號顯示為「煉」")