160 lines
5.4 KiB
Python
160 lines
5.4 KiB
Python
"""
|
||
将项目 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()
|