macOS support

This commit is contained in:
sarah committed 2026-10-14 13:18:19 +02:00
1 parent 3fcaf328a9
commit b2eedc9ec8
5 files changed
+161 -21

No files matched your search

+59 -6
View File
@@ -48,14 +48,20 @@ BundledFontFamily = "Fixedsys Excelsior"
ActiveFontFamily = BundledFontFamily # set by ApplyAppearance, read by BuildTheme
IsWindows = sys.platform == "win32"
IsMac = sys.platform == "darwin"
# Each system's own per-user folders, like SaraSuite's: ~/.config and ~/.cache
# on Linux; %APPDATA% (settings) and %LOCALAPPDATA% (caches) on Windows.
# on Linux; %APPDATA% (settings) and %LOCALAPPDATA% (caches) on Windows;
# ~/Library/Application Support and ~/Library/Caches on macOS.
if IsWindows:
ConfigHome = Path(os.environ.get("APPDATA") or Path.home() / "AppData" / "Roaming")
CacheDir = Path(os.environ.get("LOCALAPPDATA") or Path.home() / "AppData" / "Local") / "SaraFem" / "Cache"
# Where the file tree starts by default: the system drive (C:\), not "/".
DefaultFiletreeRoot = (os.environ.get("SystemDrive") or "C:") + "\\"
elif IsMac:
ConfigHome = Path.home() / "Library" / "Application Support"
CacheDir = Path.home() / "Library" / "Caches" / "SaraFem"
DefaultFiletreeRoot = "/"
else:
ConfigHome = Path(os.environ.get("XDG_CONFIG_HOME") or Path.home() / ".config")
CacheDir = Path(os.environ.get("XDG_CACHE_HOME") or Path.home() / ".cache") / "SaraFem"
@@ -593,12 +599,59 @@ def _WindowsFonts() -> set[str]:
return Families
def _MacFonts() -> set[str]:
"""Font families on macOS (no fontconfig there), read from the font files'
name tables in the system, shared and personal Fonts folders. The
typographic family (name ID 16) where there is one, else ID 1."""
import struct
Families: set[str] = set()
for Folder in (Path("/System/Library/Fonts"), Path("/Library/Fonts"), Path.home() / "Library" / "Fonts"):
if not Folder.is_dir():
continue
for FilePath in Folder.rglob("*"):
if FilePath.suffix.lower() not in (".ttf", ".otf", ".ttc"):
continue
try:
Data = FilePath.read_bytes()
except OSError:
continue
Offsets = [0]
if Data[:4] == b"ttcf": # a collection: every font in it
Count = struct.unpack_from(">I", Data, 8)[0]
Offsets = list(struct.unpack_from(f">{Count}I", Data, 12))
for Offset in Offsets:
try:
for Index in range(struct.unpack_from(">H", Data, Offset + 4)[0]):
Tag, _Sum, Start, _Length = struct.unpack_from(">4sIII", Data, Offset + 12 + Index * 16)
if Tag != b"name":
continue
_Format, Count, Strings = struct.unpack_from(">HHH", Data, Start)
Found: dict[int, str] = {}
for Record in range(Count):
PlatformId, _Encoding, Language, NameId, Length, At = struct.unpack_from(
">6H", Data, Start + 6 + Record * 12)
if NameId not in (1, 16) or NameId in Found:
continue
Raw = Data[Start + Strings + At:Start + Strings + At + Length]
if PlatformId in (0, 3):
Found[NameId] = Raw.decode("utf-16-be", "replace")
elif PlatformId == 1 and Language == 0:
Found[NameId] = Raw.decode("mac-roman", "replace")
Name = (Found.get(16) or Found.get(1) or "").strip()
if Name and not Name.startswith("."): # ".SF NS" and co. are system-internal
Families.add(Name)
except struct.error:
continue
return Families
def InstalledFontFamilies() -> list[str]:
"""Font families fontconfig (or, on Windows, the registry) knows about,
for the font picker. The bundled font is always first, since it isn't
installed system-wide."""
if IsWindows:
Families = _WindowsFonts()
"""Font families fontconfig (or, on Windows, the registry; on macOS, the
font files themselves) knows about, for the font picker. The bundled font
is always first, since it isn't installed system-wide."""
if IsWindows or IsMac:
Families = _WindowsFonts() if IsWindows else _MacFonts()
Families.discard(BundledFontFamily)
return [BundledFontFamily] + sorted(Families, key=str.lower)
try: