268 lines
8.1 KiB
Python
268 lines
8.1 KiB
Python
"""
|
|
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
|