Aetheria/tools/emoji/fetch_emoji_font.py
Meta Vens 851476bc23 Emoji font script: pin Unicode emoji-test.txt, refuse outside the pinned container
The corpus now derives from emoji-test.txt (sha256 pinned), keeps single code points of any digit
count, and lists the emoji Twemoji lacks. The script runs only on linux, Python 3.13, image
python:3.13-slim and records those in the lock; identical pins with a different font exit 1 and
leave the committed font and lock untouched.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-08 22:05:09 +02:00

176 lines
9.2 KiB
Python

"""Build Assets/Fonts/Emoji/Twemoji.ttf from the pinned Twemoji release.
The font reproduces ONLY inside the pinned Linux container (PINNED_IMAGE, Python PINNED_PYTHON); on any
other platform the script refuses to run and writes nothing. It does not run under `py -3` on Windows.
Run it on Yggdrasil (or any docker host) from the repository root:
docker run --rm -e EMOJI_FONT_IMAGE=python:3.13-slim -v "$PWD":/src -w /src python:3.13-slim sh -c \\
"pip install -r tools/emoji/requirements.txt && python tools/emoji/fetch_emoji_font.py [--jobs N]"
Downloads the jdecked/twemoji release source archive and Unicode's emoji-test.txt (both sha256 pinned
below), runs nanoemoji (glyf_colr_0) over the SVGs, and writes the font, the licence text, the smoke's
corpus (emoji-singles.txt, from emoji-test.txt), the list of emoji Twemoji lacks (emoji-unsupported.txt)
and emoji-font.lock. The font is the only artifact Unity consumes; nothing here is hand-made art.
A run whose font-determining pins match the lock must reproduce the font's sha256, or the script exits 1
and leaves the committed font and lock untouched; the lock never follows the machine.
"""
import argparse, hashlib, os, platform, re, shutil, subprocess, sys, tarfile, tempfile, urllib.request
from importlib import metadata
from pathlib import Path
TWEMOJI_VERSION = "v17.0.3"
TWEMOJI_URL = "https://github.com/jdecked/twemoji/archive/refs/tags/v17.0.3.tar.gz"
TWEMOJI_SHA256 = "a0855654b633045ae2337537e77f1bb4361162f7fcd910e613eaab1d6d9c5fca"
# 2026-06-01T22:21:37Z, the release's publication time: fixes head.created/modified for reproducibility.
SOURCE_DATE_EPOCH = "1780352497"
FAMILY = "Twemoji"
# Unicode's emoji-test.txt for the Emoji version Twemoji v17.0.3 targets: the smoke's corpus.
EMOJI_TEST_VERSION = "17.0"
EMOJI_TEST_URL = "https://unicode.org/Public/17.0.0/emoji/emoji-test.txt"
EMOJI_TEST_SHA256 = "1d8a944f88d7952f7ef7c5167fef3c67995bcae24543949710231b03a201acda"
# The one environment the font reproduces in. The docker command passes the image tag in; a container
# cannot read its own image name.
PINNED_PLATFORM = "linux"
PINNED_PYTHON = "3.13"
PINNED_IMAGE = "python:3.13-slim"
ROOT = Path(__file__).resolve().parents[2]
FONT_DIR = ROOT / "Assets" / "Fonts" / "Emoji"
SINGLES = ROOT / "tools" / "emoji" / "emoji-singles.txt"
UNSUPPORTED = ROOT / "tools" / "emoji" / "emoji-unsupported.txt"
def sha256(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for block in iter(lambda: f.read(1 << 20), b""):
h.update(block)
return h.hexdigest()
def download(cache, url, name, pinned):
path = cache / name
if not path.exists():
cache.mkdir(parents=True, exist_ok=True)
urllib.request.urlretrieve(url, path)
got = sha256(path)
if got != pinned:
path.unlink()
sys.exit(f"{name} sha256 {got} != pinned {pinned}")
return path
def single_code_points(emoji_test):
"""Every emoji of the Emoji version that is one code point, bare or with its FE0F presentation
selector (so (c) and (r) are in), of any qualification status including components."""
found = set()
for line in emoji_test.read_text(encoding="utf-8").splitlines():
if not line or line.startswith("#"):
continue
points = [int(p, 16) for p in line.split(";", 1)[0].split() if int(p, 16) != 0xFE0F]
if len(points) == 1:
found.add(points[0])
return sorted(found)
def refuse_unless_pinned_environment():
here = (sys.platform, f"{sys.version_info.major}.{sys.version_info.minor}", os.environ.get("EMOJI_FONT_IMAGE"))
if here != (PINNED_PLATFORM, PINNED_PYTHON, PINNED_IMAGE):
sys.exit(f"refusing to run: the font reproduces only on {PINNED_PLATFORM}, Python {PINNED_PYTHON}, image "
f"{PINNED_IMAGE} (EMOJI_FONT_IMAGE); this is {here}. Nothing was written.")
def licence_text(root):
readme = (root / "README.md").read_text(encoding="utf-8")
attribution = re.search(r"## Attribution Requirements\n\n(.*?)\n\n## ", readme, re.S).group(1)
graphics = re.search(r"Graphics licensed under CC-BY 4.0: \S+", readme).group(0)
copyright_lines = [l for l in (root / "LICENSE").read_text(encoding="utf-8").splitlines() if l.startswith("Copyright")]
return (
f"Twemoji {TWEMOJI_VERSION}: {TWEMOJI_URL}\n"
+ "\n".join(copyright_lines) + "\n" + graphics + "\n\n"
"Change notice (CC BY 4.0 section 3(a)(1)(B)): the SVG graphics are converted to a COLRv0 colour\n"
"font (Twemoji.ttf) by tools/emoji/fetch_emoji_font.py using nanoemoji. No artwork is edited.\n\n"
"Attribution Requirements (upstream README)\n\n" + attribution + "\n\n"
"LICENSE-GRAPHICS (upstream, verbatim)\n"
"=======================================================================\n\n"
+ (root / "LICENSE-GRAPHICS").read_text(encoding="utf-8")
)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--cache", type=Path, default=Path(tempfile.gettempdir()) / "aetheria-emoji-cache")
ap.add_argument("--jobs", type=int, default=max(1, min(4, (os.cpu_count() or 2) // 2)))
args = ap.parse_args()
refuse_unless_pinned_environment()
archive = download(args.cache, TWEMOJI_URL, f"twemoji-{TWEMOJI_VERSION}.tar.gz", TWEMOJI_SHA256)
emoji_test = download(args.cache, EMOJI_TEST_URL, f"emoji-test-{EMOJI_TEST_VERSION}.txt", EMOJI_TEST_SHA256)
work = args.cache / f"build-{TWEMOJI_VERSION}"
if work.exists():
shutil.rmtree(work)
work.mkdir(parents=True)
with tarfile.open(archive) as tar:
tar.extractall(work / "src", filter="data")
src = next((work / "src").iterdir())
svgs = sorted((src / "assets" / "svg").glob("*.svg"))
env = dict(os.environ, SOURCE_DATE_EPOCH=SOURCE_DATE_EPOCH)
# nanoemoji shells out to ninja and picosvg by name; they live beside this interpreter.
env["PATH"] = str(Path(sys.executable).parent) + os.pathsep + env["PATH"]
build = work / "build"
# 4000 paths overflow the Windows command line, so the inputs travel in an absl flagfile.
flagfile = work / "inputs.flags"
flagfile.write_text("".join(f"{p.as_posix()}\n" for p in svgs), encoding="utf-8")
run = [sys.executable, "-c", "from nanoemoji.nanoemoji import main; main()", "--color_format", "glyf_colr_0",
"--family", FAMILY, "--output_file", f"{FAMILY}.ttf", "--build_dir", str(build), "--noexec_ninja",
f"--flagfile={flagfile}"]
subprocess.run(run, check=True, env=env, cwd=work)
subprocess.run(["ninja", "-j", str(args.jobs), "-C", str(build)], check=True, env=env)
built = build / f"{FAMILY}.ttf"
font_sha = sha256(built)
tools = {n: metadata.version(n) for n in ("nanoemoji", "fonttools", "picosvg", "ninja")}
font_pins = {
"source_sha256": TWEMOJI_SHA256, **{f"tool_{n}": v for n, v in tools.items()},
"source_date_epoch": SOURCE_DATE_EPOCH, "color_format": "glyf_colr_0",
"python_version": platform.python_version(), "platform": PINNED_PLATFORM, "container_image": PINNED_IMAGE,
}
lock_path = FONT_DIR / "emoji-font.lock"
old = dict(l.split("=", 1) for l in lock_path.read_text().splitlines() if "=" in l) if lock_path.exists() else {}
if old and all(old.get(k) == v for k, v in font_pins.items()):
if old.get("font_sha256") != font_sha:
sys.exit(f"NOT REPRODUCED: font_sha256 {font_sha} != lock {old.get('font_sha256')} with identical pins. "
"Nothing was written.")
print("reproduced: font_sha256 equals the lock")
# The smoke's corpus is Unicode's list of single-code-point emoji, not the supplier's file names;
# what Twemoji does not draw is reported, not hidden.
drawn = {int(p.stem, 16) for p in svgs if re.fullmatch(r"[0-9a-f]+", p.stem)}
singles = single_code_points(emoji_test)
unsupported = [cp for cp in singles if cp not in drawn]
FONT_DIR.mkdir(parents=True, exist_ok=True)
out = FONT_DIR / f"{FAMILY}.ttf"
shutil.copyfile(built, out)
(FONT_DIR / "LICENSE.txt").write_text(licence_text(src), encoding="utf-8", newline="\n")
SINGLES.write_text("".join(f"{cp:X}\n" for cp in singles), encoding="utf-8", newline="\n")
UNSUPPORTED.write_text("".join(f"{cp:X}\n" for cp in unsupported), encoding="utf-8", newline="\n")
print(f"Twemoji lacks {len(unsupported)} of {len(singles)} single-code-point emoji: "
+ " ".join(f"U+{cp:04X}" for cp in unsupported))
lock = [
f"source_url={TWEMOJI_URL}", f"source_version={TWEMOJI_VERSION}", f"source_sha256={TWEMOJI_SHA256}",
f"emoji_test_version={EMOJI_TEST_VERSION}", f"emoji_test_url={EMOJI_TEST_URL}", f"emoji_test_sha256={EMOJI_TEST_SHA256}",
f"svg_count={len(svgs)}", f"single_codepoint_count={len(singles)}", f"unsupported_count={len(unsupported)}",
*(f"tool_{n}={v}" for n, v in tools.items()),
f"source_date_epoch={SOURCE_DATE_EPOCH}", "color_format=glyf_colr_0",
f"python_version={font_pins['python_version']}", f"platform={PINNED_PLATFORM}", f"container_image={PINNED_IMAGE}",
f"font_file={out.name}", f"font_sha256={font_sha}",
f"singles_sha256={sha256(SINGLES)}", f"unsupported_sha256={sha256(UNSUPPORTED)}",
]
lock_path.write_text("\n".join(lock) + "\n", encoding="utf-8", newline="\n")
print("\n".join(lock))
if __name__ == "__main__":
main()