mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-10-17 18:37:14 +00:00
embed the standard scaling rules
This commit is contained in:
+26
-10
@@ -12,6 +12,7 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[2]
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
MACH_MAGIC = {bytes.fromhex(h) for h in ("feedface", "cefaedfe", "feedfacf", "cffaedfe", "cafebabe", "bebafeca", "cafebabf", "bfbafeca")}
|
MACH_MAGIC = {bytes.fromhex(h) for h in ("feedface", "cefaedfe", "feedfacf", "cffaedfe", "cafebabe", "bebafeca", "cafebabf", "bfbafeca")}
|
||||||
@@ -123,6 +124,29 @@ def copy_tree(source: Path, target: Path) -> None:
|
|||||||
shutil.copytree(source, target, dirs_exist_ok=True, symlinks=False)
|
shutil.copytree(source, target, dirs_exist_ok=True, symlinks=False)
|
||||||
|
|
||||||
|
|
||||||
|
def write_fontconfig(resources: Path, fontconfig_prefix: Path) -> None:
|
||||||
|
# Noto Color Emoji has fixed-size color bitmaps. Fontconfig supplies the
|
||||||
|
# requested-size/strike-size matrix; without it glyphs render at 109px.
|
||||||
|
rule = fontconfig_prefix / "share/fontconfig/conf.avail/10-scale-bitmap-fonts.conf"
|
||||||
|
if not rule.is_file():
|
||||||
|
raise RuntimeError(f"Required Fontconfig bitmap scaling rules not found: {rule}")
|
||||||
|
resources.mkdir(parents=True, exist_ok=True)
|
||||||
|
config = ET.fromstring('''<fontconfig>
|
||||||
|
<dir>/System/Library/Fonts</dir><dir>/Library/Fonts</dir><dir>~/Library/Fonts</dir>
|
||||||
|
<cachedir prefix="xdg">fontconfig</cachedir>
|
||||||
|
<alias><family>sans-serif</family><prefer><family>Helvetica</family></prefer></alias>
|
||||||
|
<alias><family>monospace</family><prefer><family>Menlo</family></prefer></alias>
|
||||||
|
</fontconfig>
|
||||||
|
''')
|
||||||
|
# Embed the standard rules so they work after relocation without depending
|
||||||
|
# on Homebrew or Fontconfig's host configuration search paths.
|
||||||
|
for element in reversed(list(ET.parse(rule).getroot())):
|
||||||
|
config.insert(0, element)
|
||||||
|
(resources / "fonts.conf").write_text(
|
||||||
|
'<?xml version="1.0"?>\n<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">\n' +
|
||||||
|
ET.tostring(config, encoding="unicode") + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def audit(app: Path) -> dict[str, object]:
|
def audit(app: Path) -> dict[str, object]:
|
||||||
binary = app / "Contents/MacOS/zoitechat"
|
binary = app / "Contents/MacOS/zoitechat"
|
||||||
architectures = set(run("lipo", "-archs", binary).split())
|
architectures = set(run("lipo", "-archs", binary).split())
|
||||||
@@ -180,7 +204,7 @@ def package(stage: Path, app: Path, build_number: str) -> None:
|
|||||||
seeds.append((source, resources / "lib/zoitechat/plugins" / source.name))
|
seeds.append((source, resources / "lib/zoitechat/plugins" / source.name))
|
||||||
|
|
||||||
prefixes = {formula: Path(run("brew", "--prefix", formula)) for formula in (
|
prefixes = {formula: Path(run("brew", "--prefix", formula)) for formula in (
|
||||||
"gtk+3", "gdk-pixbuf", "glib", "glib-networking", "librsvg", "enchant", "adwaita-icon-theme", "hicolor-icon-theme", "gsettings-desktop-schemas", "ca-certificates", "openssl@3", "lua", "libarchive", "gtk-mac-integration")}
|
"gtk+3", "gdk-pixbuf", "glib", "glib-networking", "librsvg", "enchant", "adwaita-icon-theme", "hicolor-icon-theme", "gsettings-desktop-schemas", "ca-certificates", "openssl@3", "lua", "libarchive", "gtk-mac-integration", "fontconfig")}
|
||||||
# Runtime-loaded code is not visible in the main executable's otool output.
|
# Runtime-loaded code is not visible in the main executable's otool output.
|
||||||
module_groups = [
|
module_groups = [
|
||||||
(prefixes["gdk-pixbuf"] / "lib/gdk-pixbuf-2.0/2.10.0/loaders", "lib/gdk-pixbuf-2.0/2.10.0/loaders"),
|
(prefixes["gdk-pixbuf"] / "lib/gdk-pixbuf-2.0/2.10.0/loaders", "lib/gdk-pixbuf-2.0/2.10.0/loaders"),
|
||||||
@@ -241,15 +265,7 @@ def package(stage: Path, app: Path, build_number: str) -> None:
|
|||||||
data = data.replace(str(source), "@ZOITECHAT_RESOURCES@/" + str(target.relative_to(resources)))
|
data = data.replace(str(source), "@ZOITECHAT_RESOURCES@/" + str(target.relative_to(resources)))
|
||||||
(resources / cache).write_text(data + "\n")
|
(resources / cache).write_text(data + "\n")
|
||||||
# The private font map loads the bundled Noto font; ordinary GTK fonts stay local.
|
# The private font map loads the bundled Noto font; ordinary GTK fonts stay local.
|
||||||
(resources / "fonts.conf").write_text('''<?xml version="1.0"?>
|
write_fontconfig(resources, prefixes["fontconfig"])
|
||||||
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
|
|
||||||
<fontconfig>
|
|
||||||
<dir>/System/Library/Fonts</dir><dir>/Library/Fonts</dir><dir>~/Library/Fonts</dir>
|
|
||||||
<cachedir prefix="xdg">fontconfig</cachedir>
|
|
||||||
<alias><family>sans-serif</family><prefer><family>Helvetica</family></prefer></alias>
|
|
||||||
<alias><family>monospace</family><prefer><family>Menlo</family></prefer></alias>
|
|
||||||
</fontconfig>
|
|
||||||
''')
|
|
||||||
(resources / "zoitechat-bundle").write_text("1\n")
|
(resources / "zoitechat-bundle").write_text("1\n")
|
||||||
licenses = resources / "licenses"
|
licenses = resources / "licenses"
|
||||||
licenses.mkdir()
|
licenses.mkdir()
|
||||||
|
|||||||
@@ -1,16 +1,63 @@
|
|||||||
"""Dependency parsing, relocation guards and release audit tests without a Mac SDK."""
|
"""Dependency parsing, relocation guards and release audit tests without a Mac SDK."""
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from xml.sax.saxutils import escape
|
||||||
|
|
||||||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||||||
import bundle
|
import bundle
|
||||||
|
|
||||||
|
|
||||||
class BundleTests(unittest.TestCase):
|
class BundleTests(unittest.TestCase):
|
||||||
|
def test_missing_bitmap_scaling_rules_fail_packaging(self):
|
||||||
|
with tempfile.TemporaryDirectory() as temporary:
|
||||||
|
directory = Path(temporary)
|
||||||
|
with self.assertRaisesRegex(RuntimeError, "bitmap scaling rules not found"):
|
||||||
|
bundle.write_fontconfig(directory / "Resources", directory / "missing-prefix")
|
||||||
|
|
||||||
|
@unittest.skipUnless(shutil.which("fc-match"), "requires Fontconfig's matching tool")
|
||||||
|
def test_bundled_bitmap_scaling_survives_relocation(self):
|
||||||
|
prefix = Path(bundle.run("brew", "--prefix", "fontconfig")) if sys.platform == "darwin" else Path("/usr")
|
||||||
|
rule = prefix / "share/fontconfig/conf.avail/10-scale-bitmap-fonts.conf"
|
||||||
|
if not rule.is_file():
|
||||||
|
self.skipTest("Fontconfig bitmap scaling configuration unavailable")
|
||||||
|
font = bundle.ROOT / "data/fonts/NotoColorEmoji.ttf"
|
||||||
|
with tempfile.TemporaryDirectory(prefix="zoitechat font scaling ") as temporary:
|
||||||
|
directory = Path(temporary)
|
||||||
|
source = directory / "build-prefix/share/fontconfig/conf.avail" / rule.name
|
||||||
|
source.parent.mkdir(parents=True)
|
||||||
|
shutil.copy2(rule, source)
|
||||||
|
resources = directory / "Resources"
|
||||||
|
bundle.write_fontconfig(resources, directory / "build-prefix")
|
||||||
|
relocated = directory / "Relocated Resources"
|
||||||
|
shutil.move(resources, relocated)
|
||||||
|
source.unlink()
|
||||||
|
# Exercise the shipped configuration without consulting host font
|
||||||
|
# rules. Only the bundled font is visible after reset-dirs.
|
||||||
|
wrapper = directory / "probe.conf"
|
||||||
|
wrapper.write_text(
|
||||||
|
'<fontconfig><include>' + escape(str(relocated / "fonts.conf")) +
|
||||||
|
'</include><reset-dirs/><dir>' + escape(str(font.parent)) + '</dir></fontconfig>')
|
||||||
|
env = os.environ.copy()
|
||||||
|
env["FONTCONFIG_FILE"] = str(wrapper)
|
||||||
|
env["XDG_CACHE_HOME"] = str(directory / "cache")
|
||||||
|
for requested in (12, 16, 24):
|
||||||
|
output = subprocess.check_output(
|
||||||
|
["fc-match", "-f", "%{pixelsize}\\n%{matrix}\\n%{file}\\n",
|
||||||
|
f"Noto Color Emoji:pixelsize={requested}"], env=env, cwd=directory, text=True)
|
||||||
|
pixels, matrix, matched = output.splitlines()
|
||||||
|
self.assertEqual(Path(matched).resolve(), font.resolve())
|
||||||
|
values = [float(value) for value in matrix.split()]
|
||||||
|
self.assertEqual(len(values), 4, "bitmap scaling matrix missing")
|
||||||
|
self.assertAlmostEqual(float(pixels) * values[0], requested, places=3)
|
||||||
|
self.assertAlmostEqual(float(pixels) * values[3], requested, places=3)
|
||||||
|
self.assertEqual(values[1:3], [0.0, 0.0])
|
||||||
|
|
||||||
@unittest.skipUnless(sys.platform == "darwin", "requires Apple's Mach-O tools")
|
@unittest.skipUnless(sys.platform == "darwin", "requires Apple's Mach-O tools")
|
||||||
def test_signed_binary_relocation_and_resigning(self):
|
def test_signed_binary_relocation_and_resigning(self):
|
||||||
with tempfile.TemporaryDirectory(prefix="zoitechat relocation ") as temporary:
|
with tempfile.TemporaryDirectory(prefix="zoitechat relocation ") as temporary:
|
||||||
|
|||||||
Reference in New Issue
Block a user