""" 将项目 fonts/ 目录下的中文字体安装到 Docker QGIS 容器。 QGIS 官方 Docker 镜像不包含中文字体,模板中的 SimHei/SimSun/YaHei 字体会显示为方块。 本脚本将 fonts/ 目录下的字体文件复制到容器内并刷新字体缓存。 用法: python app/script/install_fonts_to_container.py [--container qgis-server] [--dry-run] 前置条件: - Docker 容器已启动(docker start qgis-server) - 项目根目录下 fonts/ 目录包含所需的 .ttf/.ttc 字体文件 """ import argparse import subprocess import sys import time from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent FONTS_DIR = PROJECT_ROOT / "fonts" # 容器内字体目录 CONTAINER_FONT_DIR = "/usr/share/fonts/truetype/winfonts" def _run(cmd, timeout=10, **kwargs): """subprocess.run 封装,统一 UTF-8 编码""" return subprocess.run( cmd, capture_output=True, timeout=timeout, encoding="utf-8", errors="replace", **kwargs, ) def _load_config(): """从 settings.toml 读取配置""" try: from config import settings return { "container": getattr(settings, "QGIS_DOCKER_CONTAINER", "qgis-server"), } except ImportError: pass cfg = {"container": "qgis-server"} toml_path = PROJECT_ROOT / "settings.toml" if toml_path.exists(): for line in toml_path.read_text(encoding="utf-8").splitlines(): line = line.strip() if line.startswith("QGIS_DOCKER_CONTAINER") and "=" in line: cfg["container"] = line.split("=", 1)[1].strip().strip('"').strip("'") return cfg def _check_container(container: str): """检查容器是否运行""" result = _run(["docker", "inspect", "--format={{.State.Running}}", container], timeout=5) if (result.stdout or "").strip() != "true": raise RuntimeError(f"容器 {container} 未运行,请先 docker start {container}") def install_fonts(container: str = None, dry_run: bool = False): """将 fonts/ 目录下的字体安装到容器""" cfg = _load_config() if container is None: container = cfg["container"] _check_container(container) # 扫描字体文件 if not FONTS_DIR.is_dir(): print(f"字体目录不存在: {FONTS_DIR}") print(f"请先在项目根目录下创建 fonts/ 目录,并放入 .ttf/.ttc 字体文件。") print(f"Windows 字体路径: C:\\Windows\\Fonts\\") print(f" simhei.ttf — 黑体(模板默认字体)") print(f" simsun.ttc — 宋体") print(f" msyh.ttc — 微软雅黑") print(f" msyhbd.ttc — 微软雅黑粗体") sys.exit(1) font_files = [f for f in FONTS_DIR.iterdir() if f.is_file() and f.suffix.lower() in (".ttf", ".ttc", ".otf")] if not font_files: print(f"字体目录为空或无字体文件: {FONTS_DIR}") print(f"请放入 .ttf/.ttc/.otf 字体文件后重试。") sys.exit(1) print(f"=== 安装中文字体到 Docker 容器 {container} ===\n") print(f" 字体目录: {FONTS_DIR}") print(f" 字体文件: {len(font_files)} 个") for f in sorted(font_files): size_kb = f.stat().st_size / 1024 print(f" - {f.name} ({size_kb:.0f} KB)") print(f" 容器目标: {container}:{CONTAINER_FONT_DIR}") print() if dry_run: print(" [dry-run] 跳过安装") return # 1. 在容器内创建字体目录 t0 = time.time() _run(["docker", "exec", container, "mkdir", "-p", CONTAINER_FONT_DIR]) # 2. 逐个复制字体文件到容器 print(" [1/3] 复制字体文件...") for f in sorted(font_files): result = _run( ["docker", "cp", str(f), f"{container}:{CONTAINER_FONT_DIR}/{f.name}"], timeout=30, ) if result.returncode != 0: print(f" FAIL {f.name}: {result.stderr.strip()}") else: print(f" OK {f.name}") # 3. 刷新字体缓存 print("\n [2/3] 刷新字体缓存...") result = _run(["docker", "exec", container, "fc-cache", "-fv"], timeout=30) if result.returncode != 0: print(f" WARN fc-cache 输出: {result.stderr.strip()}") else: print(" OK") # 4. 验证字体 print("\n [3/3] 验证字体...") verify_script = ( "from PyQt5.QtGui import QFontDatabase; " "db = QFontDatabase(); " "zh = [f for f in db.families() if any(k in f for k in " "['SimHei','YaHei','SimSun','WenQuanYi','Noto Sans CJK'])]; " "print('中文字体:', zh if zh else '未安装!')" ) result = _run( ["docker", "exec", container, "python3", "-c", verify_script], timeout=10, ) print(f" {(result.stdout or '').strip()}") elapsed = time.time() - t0 print(f"\n=== 完成,耗时 {elapsed:.1f}s ===") def main(): parser = argparse.ArgumentParser(description="将中文字体安装到 Docker QGIS 容器") parser.add_argument("--container", default=None, help="Docker 容器名称") parser.add_argument("--dry-run", action="store_true", help="仅显示信息,不实际安装") args = parser.parse_args() try: install_fonts(container=args.container, dry_run=args.dry_run) except Exception as e: print(f"错误: {e}", file=sys.stderr) sys.exit(1) if __name__ == "__main__": main()