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()
|