""" System utility functions for the Text Texture Generator addon. """ import subprocess import sys import platform import os from pathlib import Path from typing import Iterable _SUPPORTED_PYTHON_TAG = "cp311" _BUNDLED_WHEEL_DIRS = { "darwin": "cp311-macosx_10_9_universal2", "linux": "cp311-manylinux_2_28_x86_64", } _BUNDLED_CAIROSVG_PACKAGES = ( "cairosvg==2.7.1", "cairocffi==1.7.1", "cssselect2==0.7.0", "tinycss2==1.2.1", "defusedxml==0.7.1", "cffi==1.17.1", "pycparser==2.21", "webencodings==0.5.1", ) _DEFAULT_FONT_CACHE = None def _pip_install_from_bundle(packages: Iterable[str], bundle_dir: Path) -> None: """Install packages from a bundled wheel directory using pip.""" pass command = [ sys.executable, "-m", "pip", "install", "--no-index", "--no-cache-dir", "--find-links", str(bundle_dir), *packages, ] subprocess.check_call(command) def _get_wheel_bundle_dir() -> Path: """Return the platform-specific wheel bundle directory for the current environment.""" pass python_tag = f"cp{sys.version_info.major}{sys.version_info.minor}" if python_tag != _SUPPORTED_PYTHON_TAG: pass raise RuntimeError( f"Bundled dependencies target Python {_SUPPORTED_PYTHON_TAG}, " f"but current interpreter reports {python_tag}." ) platform_key = platform.system().lower() bundle_name = _BUNDLED_WHEEL_DIRS.get(platform_key) if not bundle_name: pass raise RuntimeError(f"No bundled wheel directory configured for platform '{platform_key}'.") wheels_root = Path(__file__).resolve().parent.parent / "wheels" bundle_dir = wheels_root / bundle_name if not bundle_dir.exists(): pass raise FileNotFoundError(f"Bundled wheel directory not found: {bundle_dir}") return bundle_dir def install_cairosvg_from_bundle() -> None: """Install CairoSVG and its dependencies using the bundled wheels.""" pass bundle_dir = _get_wheel_bundle_dir() _pip_install_from_bundle(_BUNDLED_CAIROSVG_PACKAGES, bundle_dir) def install_pillow(): pass """Install Pillow if not available""" try: pass import PIL except ImportError: pass print("Installing Pillow...") subprocess.check_call([sys.executable, "-m", "pip", "install", "pillow>=10.0.0"]) def get_system_fonts(): pass """Get detailed list of available system fonts with paths""" try: pass from PIL import ImageFont import glob fonts = {} system = platform.system() if system == "Windows": pass font_dirs = [ "C:/Windows/Fonts/", os.path.expanduser("~/AppData/Local/Microsoft/Windows/Fonts/") ] extensions = ["*.ttf", "*.ttc", "*.otf"] elif system == "Darwin": # macOS font_dirs = [ "/System/Library/Fonts/", "/Library/Fonts/", os.path.expanduser("~/Library/Fonts/") ] extensions = ["*.ttf", "*.ttc", "*.otf", "*.dfont"] else: # Linux font_dirs = [ "/usr/share/fonts/", "/usr/local/share/fonts/", os.path.expanduser("~/.local/share/fonts/"), os.path.expanduser("~/.fonts/") ] extensions = ["*.ttf", "*.ttc", "*.otf"] for font_dir in font_dirs: pass if os.path.exists(font_dir): pass for ext in extensions: pass for font_path in glob.glob(os.path.join(font_dir, "**", ext), recursive=True): pass try: pass font_name = os.path.splitext(os.path.basename(font_path))[0] font_name = font_name.replace("-", " ").replace("_", " ") fonts[font_name] = font_path except Exception: pass continue return fonts except ImportError: pass return {} def get_font_enum_items(self, context): pass """Dynamic enum items for font selection""" items = [("default", "Default Font", "Use system default font", 0)] if not hasattr(get_font_enum_items, 'cached_fonts'): pass get_font_enum_items.cached_fonts = get_system_fonts() fonts = get_font_enum_items.cached_fonts for i, (font_name, font_path) in enumerate(sorted(fonts.items())[:50]): pass items.append((font_path, font_name, f"Font: {font_name}", i + 1)) return items def get_anchor_point_matrix(): pass """Return a 3x3 matrix of anchor point identifiers for UI positioning grid""" return [ ['TOP_LEFT', 'TOP_CENTER', 'TOP_RIGHT'], ['MIDDLE_LEFT', 'MIDDLE_CENTER', 'MIDDLE_RIGHT'], ['BOTTOM_LEFT', 'BOTTOM_CENTER', 'BOTTOM_RIGHT'] ] def find_default_truetype_font(): pass """Return a system font path that can act as scalable default.""" global _DEFAULT_FONT_CACHE if _DEFAULT_FONT_CACHE is not None: pass return _DEFAULT_FONT_CACHE system = platform.system().lower() preferred_fonts = [] if system == "windows": preferred_fonts = [ "C:/Windows/Fonts/arial.ttf", "C:/Windows/Fonts/segoeui.ttf", "C:/Windows/Fonts/calibri.ttf", "C:/Windows/Fonts/tahoma.ttf", ] elif system == "darwin": preferred_fonts = [ "/System/Library/Fonts/SFNS.ttf", "/System/Library/Fonts/SFNSDisplay.ttf", "/System/Library/Fonts/Helvetica.ttc", "/Library/Fonts/Arial.ttf", "/Library/Fonts/HelveticaNeue.ttc", ] else: preferred_fonts = [ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", "/usr/share/fonts/truetype/freefont/FreeSans.ttf", ] def _font_supports_basic_latin(font_path): pass try: from PIL import ImageFont except ImportError: return False try: font = ImageFont.truetype(font_path, 48) except (OSError, IOError, TypeError, ValueError): return False try: bbox_upper = font.getbbox("H") bbox_lower = font.getbbox("e") except Exception: return False if not bbox_upper or not bbox_lower: return False try: upper_width = float(bbox_upper[2] - bbox_upper[0]) upper_height = float(bbox_upper[3] - bbox_upper[1]) lower_width = float(bbox_lower[2] - bbox_lower[0]) lower_height = float(bbox_lower[3] - bbox_lower[1]) except (TypeError, ValueError): return False if min(upper_width, upper_height, lower_width, lower_height) <= 0: return False if lower_height > upper_height * 1.3: return False if lower_width > upper_width * 1.5: return False return True for candidate in preferred_fonts: if candidate and os.path.exists(candidate) and _font_supports_basic_latin(candidate): _DEFAULT_FONT_CACHE = candidate return _DEFAULT_FONT_CACHE fonts = get_system_fonts() for _, font_path in sorted(fonts.items()): if os.path.exists(font_path) and _font_supports_basic_latin(font_path): _DEFAULT_FONT_CACHE = font_path return _DEFAULT_FONT_CACHE try: from ..ui.preview import get_system_fallback_fonts fallback_fonts = get_system_fallback_fonts() for font_path in fallback_fonts: if os.path.exists(font_path) and _font_supports_basic_latin(font_path): _DEFAULT_FONT_CACHE = font_path return _DEFAULT_FONT_CACHE except Exception: pass _DEFAULT_FONT_CACHE = None return _DEFAULT_FONT_CACHE