hscredit.utils.fonts 源代码

"""系统字体安装工具.

将 hscredit 包内置字体安装到当前用户的系统字体目录,避免要求管理员权限。
"""

import ctypes
import os
import platform
import shutil
import subprocess
from pathlib import Path
from typing import Optional, Tuple


FONT_NAME = "Alimama FangYuanTi VF"
EXCEL_FONT_NAME = "阿里妈妈方圆体 VF Medium"
FALLBACK_FONT_NAME = "楷体"
FONT_FILENAME = "hscredit-font.ttf"
_default_font_name = FALLBACK_FONT_NAME


[文档] def get_bundled_font_path() -> Path: """返回 hscredit 包内置字体文件路径.""" return Path(__file__).resolve().parent.parent / "resources" / "fonts" / "font.ttf"
def is_font_available(font_name: str = FONT_NAME) -> bool: """检查当前系统字体列表是否包含指定字体家族.""" from matplotlib import font_manager expected_name = font_name.casefold() return any(font.name.casefold() == expected_name for font in font_manager.fontManager.ttflist) def initialize_bundled_font() -> str: """安装内置字体并返回本次初始化选出的默认字体名称. 字体安装属于可选的环境增强,任何安装异常均不得阻断 hscredit 导入。 安装失败时若系统已经存在品牌字体则继续使用,否则回退到楷体。 """ global _default_font_name try: install_bundled_font() available = True except Exception: try: available = is_font_available(FONT_NAME) except Exception: available = False _default_font_name = FONT_NAME if available else FALLBACK_FONT_NAME return _default_font_name def get_default_font_name() -> str: """返回最近一次环境初始化选出的默认字体名称.""" return _default_font_name def get_default_excel_font_name() -> str: """返回 Excel 样式应使用的默认字体名称.""" if _default_font_name == FONT_NAME: return EXCEL_FONT_NAME return _default_font_name def _same_file_content(source: Path, destination: Path) -> bool: """判断两个字体文件内容是否一致.""" if not destination.is_file() or source.stat().st_size != destination.stat().st_size: return False with source.open("rb") as source_file, destination.open("rb") as destination_file: while True: source_chunk = source_file.read(1024 * 1024) destination_chunk = destination_file.read(1024 * 1024) if source_chunk != destination_chunk: return False if not source_chunk: return True def _copy_font(source: Path, destination: Path, force: bool) -> bool: """复制字体文件,返回本次是否实际写入.""" destination.parent.mkdir(parents=True, exist_ok=True) if not force and _same_file_content(source, destination): return False shutil.copy2(source, destination) return True def _install_windows_font(source: Path, force: bool) -> Tuple[Path, bool]: """将字体安装到 Windows 当前用户字体库.""" import winreg local_app_data = os.environ.get("LOCALAPPDATA") if not local_app_data: raise RuntimeError("无法获取 LOCALAPPDATA,不能定位 Windows 用户字体目录。") destination = Path(local_app_data) / "Microsoft" / "Windows" / "Fonts" / FONT_FILENAME changed = _copy_font(source, destination, force) registry_path = r"Software\Microsoft\Windows NT\CurrentVersion\Fonts" with winreg.CreateKeyEx(winreg.HKEY_CURRENT_USER, registry_path, 0, winreg.KEY_SET_VALUE) as key: winreg.SetValueEx(key, f"{FONT_NAME} (TrueType)", 0, winreg.REG_SZ, str(destination)) # 将字体加载到当前 Windows 会话;返回 0 表示加载失败,由上层选择安全回退字体。 loaded_fonts = ctypes.windll.gdi32.AddFontResourceW(str(destination)) if loaded_fonts == 0: raise OSError("无法将字体加载到当前 Windows 会话。") hwnd_broadcast = 0xFFFF wm_fontchange = 0x001D smto_abortifhung = 0x0002 result = ctypes.c_ulong() ctypes.windll.user32.SendMessageTimeoutW( hwnd_broadcast, wm_fontchange, 0, 0, smto_abortifhung, 1000, ctypes.byref(result), ) return destination, changed def _install_macos_font(source: Path, force: bool) -> Tuple[Path, bool]: """将字体安装到 macOS 当前用户字体库.""" destination = Path.home() / "Library" / "Fonts" / FONT_FILENAME return destination, _copy_font(source, destination, force) def _install_linux_font(source: Path, force: bool) -> Tuple[Path, bool]: """将字体安装到 Linux 当前用户字体库.""" configured_data_home = os.environ.get("XDG_DATA_HOME") data_home = Path(configured_data_home) if configured_data_home else Path.home() / ".local" / "share" destination = data_home / "fonts" / FONT_FILENAME changed = _copy_font(source, destination, force) fc_cache = shutil.which("fc-cache") if fc_cache: subprocess.run( [fc_cache, "-f", str(destination.parent)], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True, ) return destination, changed
[文档] def install_bundled_font(force: bool = False, system: Optional[str] = None) -> Tuple[Path, bool]: """安装 hscredit 内置字体到当前用户字体库. :param force: 是否强制覆盖已有字体文件 :param system: 操作系统名称,仅用于测试或显式覆盖自动识别结果 :return: ``(安装路径, 是否实际更新字体文件)`` """ source = get_bundled_font_path() if not source.is_file(): raise FileNotFoundError(f"未找到内置字体文件:{source}") system_name = (system or platform.system()).lower() if system_name == "windows": return _install_windows_font(source, force) if system_name == "darwin": return _install_macos_font(source, force) if system_name == "linux": return _install_linux_font(source, force) raise RuntimeError(f"暂不支持在 {system or platform.system()} 系统上自动安装字体。")