Fix the Issue Where Dependencies for PDF Generation are Installed but not Recogn…
…ized by the Program (Perhaps?)
Showing
5 changed files
with
268 additions
and
20 deletions
| @@ -396,10 +396,14 @@ uv venv --python 3.11 # Create Python 3.11 environment | @@ -396,10 +396,14 @@ uv venv --python 3.11 # Create Python 3.11 environment | ||
| 396 | brew install pango gdk-pixbuf libffi | 396 | brew install pango gdk-pixbuf libffi |
| 397 | 397 | ||
| 398 | # 2. Set environment variable (required) | 398 | # 2. Set environment variable (required) |
| 399 | +# Apple Silicon | ||
| 399 | export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH | 400 | export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH |
| 401 | +# Intel Mac | ||
| 402 | +export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH | ||
| 400 | 403 | ||
| 401 | # Or permanently add to ~/.zshrc | 404 | # Or permanently add to ~/.zshrc |
| 402 | echo 'export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH' >> ~/.zshrc | 405 | echo 'export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH' >> ~/.zshrc |
| 406 | +# Intel users: echo 'export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH' >> ~/.zshrc | ||
| 403 | source ~/.zshrc | 407 | source ~/.zshrc |
| 404 | ``` | 408 | ``` |
| 405 | 409 | ||
| @@ -439,7 +443,18 @@ sudo yum install -y pango gdk-pixbuf2 libffi-devel cairo | @@ -439,7 +443,18 @@ sudo yum install -y pango gdk-pixbuf2 libffi-devel cairo | ||
| 439 | # Visit: https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases | 443 | # Visit: https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases |
| 440 | # Download the latest .exe file and install | 444 | # Download the latest .exe file and install |
| 441 | 445 | ||
| 442 | -# 2. Restart command line or IDE | 446 | +# 2. Add the GTK installation bin directory to PATH (open a new terminal afterwards) |
| 447 | +# Default path example (replace with your custom install path if different) | ||
| 448 | +set PATH=C:\Program Files\GTK3-Runtime Win64\bin;%PATH% | ||
| 449 | + | ||
| 450 | +# Optional: persist the setting | ||
| 451 | +setx PATH "C:\Program Files\GTK3-Runtime Win64\bin;%PATH%" | ||
| 452 | + | ||
| 453 | +# If installed to a custom path, replace with your actual path, or set GTK_BIN_PATH=<your-bin-path>, then reopen the terminal | ||
| 454 | + | ||
| 455 | +# 3. Verify in a new terminal | ||
| 456 | +python -m ReportEngine.utils.dependency_check | ||
| 457 | +# You should see “✓ Pango dependency check passed” | ||
| 443 | ``` | 458 | ``` |
| 444 | 459 | ||
| 445 | </details> | 460 | </details> |
| @@ -389,7 +389,7 @@ conda activate your_conda_name | @@ -389,7 +389,7 @@ conda activate your_conda_name | ||
| 389 | uv venv --python 3.11 # 创建3.11环境 | 389 | uv venv --python 3.11 # 创建3.11环境 |
| 390 | ``` | 390 | ``` |
| 391 | 391 | ||
| 392 | -### 3. 安装 PDF 导出所需系统依赖(可选) | 392 | +### 2. 安装 PDF 导出所需系统依赖(可选) |
| 393 | 393 | ||
| 394 | > ⚠️ **注意**:如果您需要使用 PDF 导出功能,请按照以下步骤安装系统依赖。如果不需要 PDF 导出功能,可以跳过此步骤,系统其他功能不受影响。 | 394 | > ⚠️ **注意**:如果您需要使用 PDF 导出功能,请按照以下步骤安装系统依赖。如果不需要 PDF 导出功能,可以跳过此步骤,系统其他功能不受影响。 |
| 395 | 395 | ||
| @@ -404,10 +404,15 @@ brew install pango gdk-pixbuf libffi | @@ -404,10 +404,15 @@ brew install pango gdk-pixbuf libffi | ||
| 404 | 404 | ||
| 405 | # 步骤 2: 设置环境变量(⚠️ 必须执行!) | 405 | # 步骤 2: 设置环境变量(⚠️ 必须执行!) |
| 406 | # 方法一:临时设置(仅当前终端会话有效) | 406 | # 方法一:临时设置(仅当前终端会话有效) |
| 407 | +# Apple Silicon | ||
| 407 | export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH | 408 | export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH |
| 409 | +# Intel Mac | ||
| 410 | +export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH | ||
| 408 | 411 | ||
| 409 | # 方法二:永久设置(推荐) | 412 | # 方法二:永久设置(推荐) |
| 410 | echo 'export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH' >> ~/.zshrc | 413 | echo 'export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH' >> ~/.zshrc |
| 414 | +# Intel 用户请改为: | ||
| 415 | +# echo 'export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH' >> ~/.zshrc | ||
| 411 | source ~/.zshrc | 416 | source ~/.zshrc |
| 412 | ``` | 417 | ``` |
| 413 | 418 | ||
| @@ -462,7 +467,18 @@ sudo yum install -y pango gdk-pixbuf2 libffi-devel cairo | @@ -462,7 +467,18 @@ sudo yum install -y pango gdk-pixbuf2 libffi-devel cairo | ||
| 462 | # 访问:https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases | 467 | # 访问:https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases |
| 463 | # 下载最新版本的 .exe 文件并安装 | 468 | # 下载最新版本的 .exe 文件并安装 |
| 464 | 469 | ||
| 465 | -# 2. 重启命令行或 IDE | 470 | +# 2. 将 GTK 安装目录下的 bin 添加到 PATH(安装后请重新打开终端) |
| 471 | +# 默认路径示例(如果安装在其他目录,请替换成你的实际路径) | ||
| 472 | +set PATH=C:\Program Files\GTK3-Runtime Win64\bin;%PATH% | ||
| 473 | + | ||
| 474 | +# 可选:永久添加到 PATH | ||
| 475 | +setx PATH "C:\Program Files\GTK3-Runtime Win64\bin;%PATH%" | ||
| 476 | + | ||
| 477 | +# 如果安装在自定义目录,请替换为实际路径,或设置环境变量 GTK_BIN_PATH=你的bin路径,再重新打开终端 | ||
| 478 | + | ||
| 479 | +# 3. 验证(新终端执行) | ||
| 480 | +python -m ReportEngine.utils.dependency_check | ||
| 481 | +# 输出包含 “✓ Pango 依赖检测通过” 表示配置正确 | ||
| 466 | ``` | 482 | ``` |
| 467 | 483 | ||
| 468 | </details> | 484 | </details> |
| @@ -1191,8 +1191,8 @@ def export_pdf(task_id: str): | @@ -1191,8 +1191,8 @@ def export_pdf(task_id: str): | ||
| 1191 | return jsonify({ | 1191 | return jsonify({ |
| 1192 | 'success': False, | 1192 | 'success': False, |
| 1193 | 'error': 'PDF 导出功能不可用:缺少系统依赖', | 1193 | 'error': 'PDF 导出功能不可用:缺少系统依赖', |
| 1194 | - 'details': '请查看根目录 README.md 第393行「PDF 导出依赖」部分了解如何安装依赖', | ||
| 1195 | - 'help_url': 'https://github.com/666ghj/BettaFish#3-安装-pdf-导出所需系统依赖可选', | 1194 | + 'details': '请查看根目录 README.md “源码启动”的第二步(PDF 导出依赖)了解安装方法', |
| 1195 | + 'help_url': 'https://github.com/666ghj/BettaFish#2-安装-pdf-导出所需系统依赖可选', | ||
| 1196 | 'system_message': pango_message | 1196 | 'system_message': pango_message |
| 1197 | }), 503 | 1197 | }), 503 |
| 1198 | 1198 | ||
| @@ -1280,8 +1280,8 @@ def export_pdf_from_ir(): | @@ -1280,8 +1280,8 @@ def export_pdf_from_ir(): | ||
| 1280 | return jsonify({ | 1280 | return jsonify({ |
| 1281 | 'success': False, | 1281 | 'success': False, |
| 1282 | 'error': 'PDF 导出功能不可用:缺少系统依赖', | 1282 | 'error': 'PDF 导出功能不可用:缺少系统依赖', |
| 1283 | - 'details': '请查看根目录 README.md 第393行「PDF 导出依赖」部分了解如何安装依赖', | ||
| 1284 | - 'help_url': 'https://github.com/666ghj/BettaFish#3-安装-pdf-导出所需系统依赖可选', | 1283 | + 'details': '请查看根目录 README.md “源码启动”的第二步(PDF 导出依赖)了解安装方法', |
| 1284 | + 'help_url': 'https://github.com/666ghj/BettaFish#2-安装-pdf-导出所需系统依赖可选', | ||
| 1285 | 'system_message': pango_message | 1285 | 'system_message': pango_message |
| 1286 | }), 503 | 1286 | }), 503 |
| 1287 | 1287 |
| @@ -13,33 +13,57 @@ from pathlib import Path | @@ -13,33 +13,57 @@ from pathlib import Path | ||
| 13 | from typing import Any, Dict | 13 | from typing import Any, Dict |
| 14 | from datetime import datetime | 14 | from datetime import datetime |
| 15 | from loguru import logger | 15 | from loguru import logger |
| 16 | +from ReportEngine.utils.dependency_check import ( | ||
| 17 | + prepare_pango_environment, | ||
| 18 | + check_pango_available, | ||
| 19 | +) | ||
| 16 | 20 | ||
| 17 | # 在导入WeasyPrint之前,尝试补充常见的macOS Homebrew动态库路径, | 21 | # 在导入WeasyPrint之前,尝试补充常见的macOS Homebrew动态库路径, |
| 18 | # 避免因未设置DYLD_LIBRARY_PATH而找不到pango/cairo等依赖。 | 22 | # 避免因未设置DYLD_LIBRARY_PATH而找不到pango/cairo等依赖。 |
| 19 | if sys.platform == 'darwin': | 23 | if sys.platform == 'darwin': |
| 20 | - brew_lib = Path('/opt/homebrew/lib') | ||
| 21 | - if brew_lib.exists(): | 24 | + mac_libs = [Path('/opt/homebrew/lib'), Path('/usr/local/lib')] |
| 22 | current = os.environ.get('DYLD_LIBRARY_PATH', '') | 25 | current = os.environ.get('DYLD_LIBRARY_PATH', '') |
| 23 | - if str(brew_lib) not in current.split(':'): | ||
| 24 | - os.environ['DYLD_LIBRARY_PATH'] = f"{brew_lib}{':' + current if current else ''}" | 26 | + inserts = [] |
| 27 | + for lib in mac_libs: | ||
| 28 | + if lib.exists() and str(lib) not in current.split(':'): | ||
| 29 | + inserts.append(str(lib)) | ||
| 30 | + if inserts: | ||
| 31 | + os.environ['DYLD_LIBRARY_PATH'] = ":".join(inserts + ([current] if current else [])) | ||
| 32 | + | ||
| 33 | +# Windows: 自动补充常见 GTK/Pango 运行时路径,避免 DLL 加载失败 | ||
| 34 | +if sys.platform.startswith('win'): | ||
| 35 | + added = prepare_pango_environment() | ||
| 36 | + if added: | ||
| 37 | + logger.debug(f"已自动添加 GTK 运行时路径: {added}") | ||
| 25 | 38 | ||
| 26 | try: | 39 | try: |
| 27 | from weasyprint import HTML, CSS | 40 | from weasyprint import HTML, CSS |
| 28 | from weasyprint.text.fonts import FontConfiguration | 41 | from weasyprint.text.fonts import FontConfiguration |
| 29 | WEASYPRINT_AVAILABLE = True | 42 | WEASYPRINT_AVAILABLE = True |
| 43 | + PDF_DEP_STATUS = "OK" | ||
| 30 | except (ImportError, OSError) as e: | 44 | except (ImportError, OSError) as e: |
| 31 | WEASYPRINT_AVAILABLE = False | 45 | WEASYPRINT_AVAILABLE = False |
| 32 | - # 判断错误类型以提供更友好的提示 | 46 | + # 判断错误类型以提供更友好的提示,并尝试输出缺失依赖的详细信息 |
| 47 | + try: | ||
| 48 | + _, dep_message = check_pango_available() | ||
| 49 | + except Exception: | ||
| 50 | + dep_message = None | ||
| 51 | + | ||
| 33 | if isinstance(e, OSError): | 52 | if isinstance(e, OSError): |
| 34 | - logger.warning( | 53 | + msg = dep_message or ( |
| 35 | "PDF 导出依赖缺失(系统库未安装或环境变量未设置)," | 54 | "PDF 导出依赖缺失(系统库未安装或环境变量未设置)," |
| 36 | "PDF 导出功能将不可用。其他功能不受影响。" | 55 | "PDF 导出功能将不可用。其他功能不受影响。" |
| 37 | ) | 56 | ) |
| 57 | + logger.warning(msg) | ||
| 58 | + PDF_DEP_STATUS = msg | ||
| 38 | else: | 59 | else: |
| 39 | - logger.warning("WeasyPrint未安装,PDF导出功能将不可用") | 60 | + msg = dep_message or "WeasyPrint未安装,PDF导出功能将不可用" |
| 61 | + logger.warning(msg) | ||
| 62 | + PDF_DEP_STATUS = msg | ||
| 40 | except Exception as e: | 63 | except Exception as e: |
| 41 | WEASYPRINT_AVAILABLE = False | 64 | WEASYPRINT_AVAILABLE = False |
| 42 | - logger.warning(f"WeasyPrint 加载失败: {e},PDF导出功能将不可用") | 65 | + PDF_DEP_STATUS = f"WeasyPrint 加载失败: {e},PDF导出功能将不可用" |
| 66 | + logger.warning(PDF_DEP_STATUS) | ||
| 43 | 67 | ||
| 44 | from .html_renderer import HTMLRenderer | 68 | from .html_renderer import HTMLRenderer |
| 45 | from .pdf_layout_optimizer import PDFLayoutOptimizer, PDFLayoutConfig | 69 | from .pdf_layout_optimizer import PDFLayoutOptimizer, PDFLayoutConfig |
| @@ -73,7 +97,11 @@ class PDFRenderer: | @@ -73,7 +97,11 @@ class PDFRenderer: | ||
| 73 | self.layout_optimizer = layout_optimizer or PDFLayoutOptimizer() | 97 | self.layout_optimizer = layout_optimizer or PDFLayoutOptimizer() |
| 74 | 98 | ||
| 75 | if not WEASYPRINT_AVAILABLE: | 99 | if not WEASYPRINT_AVAILABLE: |
| 76 | - raise RuntimeError("WeasyPrint未安装,请运行: pip install weasyprint") | 100 | + raise RuntimeError( |
| 101 | + PDF_DEP_STATUS | ||
| 102 | + if 'PDF_DEP_STATUS' in globals() else | ||
| 103 | + "WeasyPrint未安装,请运行: pip install weasyprint" | ||
| 104 | + ) | ||
| 77 | 105 | ||
| 78 | # 初始化图表转换器 | 106 | # 初始化图表转换器 |
| 79 | try: | 107 | try: |
| @@ -2,9 +2,12 @@ | @@ -2,9 +2,12 @@ | ||
| 2 | 检测系统依赖工具 | 2 | 检测系统依赖工具 |
| 3 | 用于检测 PDF 生成所需的系统依赖 | 3 | 用于检测 PDF 生成所需的系统依赖 |
| 4 | """ | 4 | """ |
| 5 | +import os | ||
| 5 | import sys | 6 | import sys |
| 6 | import platform | 7 | import platform |
| 8 | +from pathlib import Path | ||
| 7 | from loguru import logger | 9 | from loguru import logger |
| 10 | +from ctypes import util as ctypes_util | ||
| 8 | 11 | ||
| 9 | 12 | ||
| 10 | def _get_platform_specific_instructions(): | 13 | def _get_platform_specific_instructions(): |
| @@ -24,10 +27,12 @@ def _get_platform_specific_instructions(): | @@ -24,10 +27,12 @@ def _get_platform_specific_instructions(): | ||
| 24 | "║ brew install pango gdk-pixbuf libffi ║\n" | 27 | "║ brew install pango gdk-pixbuf libffi ║\n" |
| 25 | "║ ║\n" | 28 | "║ ║\n" |
| 26 | "║ 2. 设置环境变量(重要!): ║\n" | 29 | "║ 2. 设置环境变量(重要!): ║\n" |
| 27 | - "║ export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH ║\n" | 30 | + "║ Apple Silicon: export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH ║\n" |
| 31 | + "║ Intel Mac: export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH ║\n" | ||
| 28 | "║ ║\n" | 32 | "║ ║\n" |
| 29 | "║ 3. 永久生效(推荐): ║\n" | 33 | "║ 3. 永久生效(推荐): ║\n" |
| 30 | "║ echo 'export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH' >> ~/.zshrc ║\n" | 34 | "║ echo 'export DYLD_LIBRARY_PATH=/opt/homebrew/lib:$DYLD_LIBRARY_PATH' >> ~/.zshrc ║\n" |
| 35 | + "║ 或 echo 'export DYLD_LIBRARY_PATH=/usr/local/lib:$DYLD_LIBRARY_PATH' >> ~/.zshrc ║\n" | ||
| 31 | "║ source ~/.zshrc ║\n" | 36 | "║ source ~/.zshrc ║\n" |
| 32 | ) | 37 | ) |
| 33 | elif system == "Linux": | 38 | elif system == "Linux": |
| @@ -40,13 +45,24 @@ def _get_platform_specific_instructions(): | @@ -40,13 +45,24 @@ def _get_platform_specific_instructions(): | ||
| 40 | "║ ║\n" | 45 | "║ ║\n" |
| 41 | "║ CentOS/RHEL: ║\n" | 46 | "║ CentOS/RHEL: ║\n" |
| 42 | "║ sudo yum install pango gdk-pixbuf2 libffi-devel cairo ║\n" | 47 | "║ sudo yum install pango gdk-pixbuf2 libffi-devel cairo ║\n" |
| 48 | + "║ ║\n" | ||
| 49 | + "║ 若仍提示缺库:export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH ║\n" | ||
| 50 | + "║ sudo ldconfig ║\n" | ||
| 43 | ) | 51 | ) |
| 44 | elif system == "Windows": | 52 | elif system == "Windows": |
| 45 | return ( | 53 | return ( |
| 46 | "║ 🪟 Windows 系统解决方案: ║\n" | 54 | "║ 🪟 Windows 系统解决方案: ║\n" |
| 47 | "║ ║\n" | 55 | "║ ║\n" |
| 48 | - "║ 下载并安装 GTK3 Runtime: ║\n" | 56 | + "║ 1. 安装 GTK3 Runtime: ║\n" |
| 49 | "║ https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases ║\n" | 57 | "║ https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases ║\n" |
| 58 | + "║ ║\n" | ||
| 59 | + "║ 2. 将 GTK 安装目录下的 bin 加入 PATH(需新开终端): ║\n" | ||
| 60 | + "║ set PATH=C:\\Program Files\\GTK3-Runtime Win64\\bin;%PATH% ║\n" | ||
| 61 | + "║ (若自定义路径,请替换为实际安装路径) ║\n" | ||
| 62 | + "║ ║\n" | ||
| 63 | + "║ 3. 验证:在新终端运行 ║\n" | ||
| 64 | + "║ python -m ReportEngine.utils.dependency_check ║\n" | ||
| 65 | + "║ 看到 ✓ 提示即表示 PDF 导出可用 ║\n" | ||
| 50 | ) | 66 | ) |
| 51 | else: | 67 | else: |
| 52 | return ( | 68 | return ( |
| @@ -54,6 +70,158 @@ def _get_platform_specific_instructions(): | @@ -54,6 +70,158 @@ def _get_platform_specific_instructions(): | ||
| 54 | ) | 70 | ) |
| 55 | 71 | ||
| 56 | 72 | ||
| 73 | +def _ensure_windows_gtk_paths(): | ||
| 74 | + """ | ||
| 75 | + 为 Windows 自动补充 GTK/Pango 运行时搜索路径,解决 DLL 未找到问题。 | ||
| 76 | + | ||
| 77 | + Returns: | ||
| 78 | + str | None: 成功添加的路径(没有命中则为 None) | ||
| 79 | + """ | ||
| 80 | + if platform.system() != "Windows": | ||
| 81 | + return None | ||
| 82 | + | ||
| 83 | + candidates = [] | ||
| 84 | + seen = set() | ||
| 85 | + | ||
| 86 | + def _add_candidate(path_like): | ||
| 87 | + if not path_like: | ||
| 88 | + return | ||
| 89 | + p = Path(path_like) | ||
| 90 | + # 如果传入的是安装根目录,尝试拼接 bin | ||
| 91 | + if p.is_dir() and p.name.lower() == "bin": | ||
| 92 | + key = str(p.resolve()).lower() | ||
| 93 | + if key not in seen: | ||
| 94 | + seen.add(key) | ||
| 95 | + candidates.append(p) | ||
| 96 | + else: | ||
| 97 | + for maybe in (p, p / "bin"): | ||
| 98 | + key = str(maybe.resolve()).lower() | ||
| 99 | + if maybe.exists() and key not in seen: | ||
| 100 | + seen.add(key) | ||
| 101 | + candidates.append(maybe) | ||
| 102 | + | ||
| 103 | + # 用户自定义提示优先 | ||
| 104 | + for env_var in ("GTK3_RUNTIME_PATH", "GTK_RUNTIME_PATH", "GTK_BIN_PATH", "GTK_BIN_DIR", "GTK_PATH"): | ||
| 105 | + _add_candidate(os.environ.get(env_var)) | ||
| 106 | + | ||
| 107 | + program_files = os.environ.get("ProgramFiles", r"C:\\Program Files") | ||
| 108 | + program_files_x86 = os.environ.get("ProgramFiles(x86)", r"C:\\Program Files (x86)") | ||
| 109 | + default_dirs = [ | ||
| 110 | + Path(program_files) / "GTK3-Runtime Win64", | ||
| 111 | + Path(program_files_x86) / "GTK3-Runtime Win64", | ||
| 112 | + Path(program_files) / "GTK3-Runtime Win32", | ||
| 113 | + Path(program_files_x86) / "GTK3-Runtime Win32", | ||
| 114 | + Path(program_files) / "GTK3-Runtime", | ||
| 115 | + Path(program_files_x86) / "GTK3-Runtime", | ||
| 116 | + ] | ||
| 117 | + | ||
| 118 | + # 常见自定义安装位置(其他盘符 / DevelopSoftware 目录) | ||
| 119 | + common_drives = ["C", "D", "E", "F"] | ||
| 120 | + common_names = ["GTK3-Runtime Win64", "GTK3-Runtime Win32", "GTK3-Runtime"] | ||
| 121 | + for drive in common_drives: | ||
| 122 | + root = Path(f"{drive}:/") | ||
| 123 | + for name in common_names: | ||
| 124 | + default_dirs.append(root / name) | ||
| 125 | + default_dirs.append(root / "DevelopSoftware" / name) | ||
| 126 | + | ||
| 127 | + # 扫描 Program Files 下所有以 GTK 开头的目录,适配自定义安装目录名 | ||
| 128 | + for root in (program_files, program_files_x86): | ||
| 129 | + root_path = Path(root) | ||
| 130 | + if root_path.exists(): | ||
| 131 | + for child in root_path.glob("GTK*"): | ||
| 132 | + default_dirs.append(child) | ||
| 133 | + | ||
| 134 | + for d in default_dirs: | ||
| 135 | + _add_candidate(d) | ||
| 136 | + | ||
| 137 | + # 如果用户已把自定义路径加入 PATH,也尝试识别 | ||
| 138 | + path_entries = os.environ.get("PATH", "").split(os.pathsep) | ||
| 139 | + for entry in path_entries: | ||
| 140 | + if not entry: | ||
| 141 | + continue | ||
| 142 | + # 粗筛包含 gtk 或 pango 的目录 | ||
| 143 | + if "gtk" in entry.lower() or "pango" in entry.lower(): | ||
| 144 | + _add_candidate(entry) | ||
| 145 | + | ||
| 146 | + for path in candidates: | ||
| 147 | + if not path or not path.exists(): | ||
| 148 | + continue | ||
| 149 | + if not any(path.glob("pango*-1.0-*.dll")) and not (path / "pango-1.0-0.dll").exists(): | ||
| 150 | + continue | ||
| 151 | + | ||
| 152 | + try: | ||
| 153 | + if hasattr(os, "add_dll_directory"): | ||
| 154 | + os.add_dll_directory(str(path)) | ||
| 155 | + except Exception: | ||
| 156 | + # 如果添加失败,继续尝试 PATH 方式 | ||
| 157 | + pass | ||
| 158 | + | ||
| 159 | + current_path = os.environ.get("PATH", "") | ||
| 160 | + if str(path) not in current_path.split(";"): | ||
| 161 | + os.environ["PATH"] = f"{path};{current_path}" | ||
| 162 | + | ||
| 163 | + return str(path) | ||
| 164 | + | ||
| 165 | + return None | ||
| 166 | + | ||
| 167 | + | ||
| 168 | +def prepare_pango_environment(): | ||
| 169 | + """ | ||
| 170 | + 初始化运行所需的本地依赖搜索路径(当前主要针对 Windows 和 macOS)。 | ||
| 171 | + | ||
| 172 | + Returns: | ||
| 173 | + str | None: 成功添加的路径(没有命中则为 None) | ||
| 174 | + """ | ||
| 175 | + system = platform.system() | ||
| 176 | + if system == "Windows": | ||
| 177 | + return _ensure_windows_gtk_paths() | ||
| 178 | + if system == "Darwin": | ||
| 179 | + # 自动补全 DYLD_LIBRARY_PATH,兼容 Apple Silicon 与 Intel | ||
| 180 | + candidates = [Path("/opt/homebrew/lib"), Path("/usr/local/lib")] | ||
| 181 | + current = os.environ.get("DYLD_LIBRARY_PATH", "") | ||
| 182 | + added = [] | ||
| 183 | + for c in candidates: | ||
| 184 | + if c.exists() and str(c) not in current.split(":"): | ||
| 185 | + added.append(str(c)) | ||
| 186 | + if added: | ||
| 187 | + os.environ["DYLD_LIBRARY_PATH"] = ":".join(added + ([current] if current else [])) | ||
| 188 | + return os.environ["DYLD_LIBRARY_PATH"] | ||
| 189 | + return None | ||
| 190 | + | ||
| 191 | + | ||
| 192 | +def _probe_native_libs(): | ||
| 193 | + """ | ||
| 194 | + 使用 ctypes 查找关键原生库,帮助定位缺失组件。 | ||
| 195 | + | ||
| 196 | + Returns: | ||
| 197 | + list[str]: 未找到的库标识 | ||
| 198 | + """ | ||
| 199 | + system = platform.system() | ||
| 200 | + targets = [] | ||
| 201 | + | ||
| 202 | + if system == "Windows": | ||
| 203 | + targets = [ | ||
| 204 | + ("pango", ["pango-1.0-0"]), | ||
| 205 | + ("gobject", ["gobject-2.0-0"]), | ||
| 206 | + ("gdk-pixbuf", ["gdk_pixbuf-2.0-0"]), | ||
| 207 | + ("cairo", ["cairo-2"]), | ||
| 208 | + ] | ||
| 209 | + else: | ||
| 210 | + targets = [ | ||
| 211 | + ("pango", ["pango-1.0"]), | ||
| 212 | + ("gobject", ["gobject-2.0"]), | ||
| 213 | + ("gdk-pixbuf", ["gdk_pixbuf-2.0"]), | ||
| 214 | + ("cairo", ["cairo", "cairo-2"]), | ||
| 215 | + ] | ||
| 216 | + | ||
| 217 | + missing = [] | ||
| 218 | + for key, variants in targets: | ||
| 219 | + found = any(ctypes_util.find_library(v) for v in variants) | ||
| 220 | + if not found: | ||
| 221 | + missing.append(key) | ||
| 222 | + return missing | ||
| 223 | + | ||
| 224 | + | ||
| 57 | def check_pango_available(): | 225 | def check_pango_available(): |
| 58 | """ | 226 | """ |
| 59 | 检测 Pango 库是否可用 | 227 | 检测 Pango 库是否可用 |
| @@ -61,6 +229,9 @@ def check_pango_available(): | @@ -61,6 +229,9 @@ def check_pango_available(): | ||
| 61 | Returns: | 229 | Returns: |
| 62 | tuple: (is_available: bool, message: str) | 230 | tuple: (is_available: bool, message: str) |
| 63 | """ | 231 | """ |
| 232 | + added_path = prepare_pango_environment() | ||
| 233 | + missing_native = _probe_native_libs() | ||
| 234 | + | ||
| 64 | try: | 235 | try: |
| 65 | # 尝试导入 weasyprint 并初始化 Pango | 236 | # 尝试导入 weasyprint 并初始化 Pango |
| 66 | from weasyprint import HTML | 237 | from weasyprint import HTML |
| @@ -74,6 +245,21 @@ def check_pango_available(): | @@ -74,6 +245,21 @@ def check_pango_available(): | ||
| 74 | # Pango 库未安装或无法加载 | 245 | # Pango 库未安装或无法加载 |
| 75 | error_msg = str(e) | 246 | error_msg = str(e) |
| 76 | platform_instructions = _get_platform_specific_instructions() | 247 | platform_instructions = _get_platform_specific_instructions() |
| 248 | + windows_hint = "" | ||
| 249 | + if platform.system() == "Windows": | ||
| 250 | + path_display = added_path or "未找到默认路径" | ||
| 251 | + # 控制长度,避免破坏提示框宽度 | ||
| 252 | + if len(path_display) > 38: | ||
| 253 | + path_display = path_display[:35] + "..." | ||
| 254 | + windows_hint = f"║ 已尝试自动添加 GTK 路径: {path_display:<38}║\n" | ||
| 255 | + arch_note = "║ 🔍 若已安装仍报错:确认 Python/GTK 位数一致,重开终端 ║\n" | ||
| 256 | + else: | ||
| 257 | + arch_note = "" | ||
| 258 | + | ||
| 259 | + missing_note = "" | ||
| 260 | + if missing_native: | ||
| 261 | + missing_str = ", ".join(missing_native) | ||
| 262 | + missing_note = f"║ 未识别到的依赖: {missing_str:<46}║\n" | ||
| 77 | 263 | ||
| 78 | if 'gobject' in error_msg.lower() or 'pango' in error_msg.lower() or 'gdk' in error_msg.lower(): | 264 | if 'gobject' in error_msg.lower() or 'pango' in error_msg.lower() or 'gdk' in error_msg.lower(): |
| 79 | return False, ( | 265 | return False, ( |
| @@ -82,12 +268,15 @@ def check_pango_available(): | @@ -82,12 +268,15 @@ def check_pango_available(): | ||
| 82 | "║ ║\n" | 268 | "║ ║\n" |
| 83 | "║ 📄 PDF 导出功能将不可用(其他功能不受影响) ║\n" | 269 | "║ 📄 PDF 导出功能将不可用(其他功能不受影响) ║\n" |
| 84 | "║ ║\n" | 270 | "║ ║\n" |
| 271 | + f"{windows_hint}" | ||
| 272 | + f"{arch_note}" | ||
| 273 | + f"{missing_note}" | ||
| 85 | f"{platform_instructions}" | 274 | f"{platform_instructions}" |
| 86 | "║ ║\n" | 275 | "║ ║\n" |
| 87 | - "║ 📖 完整文档:根目录 README.md 第393行「PDF 导出依赖」 ║\n" | 276 | + "║ 📖 完整文档:根目录 README.md ‘源码启动’的第二步 ║\n" |
| 88 | "╚════════════════════════════════════════════════════════════════╝" | 277 | "╚════════════════════════════════════════════════════════════════╝" |
| 89 | ) | 278 | ) |
| 90 | - return False, f"⚠ PDF 依赖加载失败: {error_msg}" | 279 | + return False, f"⚠ PDF 依赖加载失败: {error_msg};缺失/未识别: {', '.join(missing_native) if missing_native else '未知'}" |
| 91 | except ImportError as e: | 280 | except ImportError as e: |
| 92 | # weasyprint 未安装 | 281 | # weasyprint 未安装 |
| 93 | return False, ( | 282 | return False, ( |
-
Please register or login to post a comment