Files
moli/scripts/generate-layout-test-font.py
T
ldm0 3a0f6ca178 fix(fonts): match queries and honor owner selection order
Select width, style and weight capabilities before filtering FontFaceSet
queries by text and unicodeRange. Share the matching path for check/load,
including CSS calculations and descriptor ranges, so unrelated unloaded
or broken sources do not affect a query.

Publish each Document's CSS/manual FontFaceSet order independently from
stable resource slots. Preserve manual overrides, re-addition priority
and different orders for shared faces across Documents.

Cover the matching and actual DOM geometry regressions with generated
narrow/wide fonts and a shared Chromium/Moli CDP oracle.
2026-10-23 04:07:15 +08:00

158 lines
5.1 KiB
Python

# /// script
# requires-python = ">=3.11"
# dependencies = [
# "brotli==1.1.0",
# "fonttools==4.59.0",
# ]
# ///
"""Generate deterministic fonts for layout and system font matching tests."""
import hashlib
import os
from pathlib import Path
from fontTools.fontBuilder import FontBuilder
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.subset import Options, Subsetter
from fontTools.ttLib import TTFont
ROOT = Path(__file__).resolve().parents[1]
FIXTURES = ROOT / "moli-layout" / "tests" / "fixtures"
TTF_PATH = FIXTURES / "moli-ahem.ttf"
SYSTEM_FONT_TTF_PATH = ROOT / "moli-system-fonts" / "tests" / "fixtures" / "moli-ahem.ttf"
HEBREW_EMOJI_PATH = FIXTURES / "moli-hebrew-emoji.ttf"
CJK_PATH = FIXTURES / "moli-cjk.ttf"
DEJAVU_SOURCE = Path(
os.environ.get(
"MOLI_DEJAVU_FONT_SOURCE",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
)
)
DROID_CJK_SOURCE = Path(
os.environ.get(
"MOLI_DROID_CJK_FONT_SOURCE",
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf",
)
)
DEJAVU_SOURCE_SHA256 = "57f73e11f51999432bf7ab22ce55b6f945d5eca1bf824404cfa9ec2e3718c84e"
DROID_CJK_SOURCE_SHA256 = "acb6440a713d880a13a21b468ba7cd43f5a2b2934972e51be791c880730777b8"
# OpenType stores seconds since 1904-01-01. This value is Unix epoch and avoids
# FontTools treating a deliberately deterministic zero as a Unix timestamp.
OPEN_TYPE_UNIX_EPOCH = 2_082_844_800
def empty_glyph():
pen = TTGlyphPen(None)
return pen.glyph()
def box_glyph():
pen = TTGlyphPen(None)
pen.moveTo((0, 0))
pen.lineTo((600, 0))
pen.lineTo((600, 800))
pen.lineTo((0, 800))
pen.closePath()
return pen.glyph()
def build_ttf(output: Path = TTF_PATH, advance: int = 600) -> None:
builder = FontBuilder(1000, isTTF=True)
codepoints = list(range(0x21, 0x7F))
codepoints += [0x00A0, 0x00DF]
glyph_names = {
codepoint: (
f"uni{codepoint:04X}" if codepoint <= 0xFFFF else f"u{codepoint:05X}"
)
for codepoint in codepoints
}
glyph_order = [".notdef", "space", *glyph_names.values()]
builder.setupGlyphOrder(glyph_order)
# Keep glyph identities distinct so source/glyph assertions can distinguish
# transformed characters without depending on a platform font.
cmap = dict(glyph_names)
cmap[0x20] = "space"
builder.setupCharacterMap(cmap)
glyphs = {name: box_glyph() for name in glyph_names.values()}
glyphs.update({".notdef": box_glyph(), "space": empty_glyph()})
builder.setupGlyf(glyphs)
builder.setupHorizontalMetrics({glyph: (advance, 0) for glyph in glyph_order})
builder.setupHorizontalHeader(ascent=800, descent=-200, lineGap=0)
builder.setupNameTable(
{
"familyName": "Moli Ahem",
"styleName": "Regular",
"uniqueFontIdentifier": "Moli Ahem Regular 1.0",
"fullName": "Moli Ahem Regular",
"psName": "MoliAhem-Regular",
"version": "Version 1.000",
}
)
builder.setupOS2(
sTypoAscender=800,
sTypoDescender=-200,
sTypoLineGap=0,
usWinAscent=800,
usWinDescent=200,
sxHeight=800,
sCapHeight=800,
fsSelection=1 << 6,
)
builder.setupPost()
builder.setupMaxp()
builder.setupHead(created=OPEN_TYPE_UNIX_EPOCH, modified=OPEN_TYPE_UNIX_EPOCH)
builder.save(output)
def verify_source(source: Path, expected_sha256: str) -> None:
if not source.is_file():
raise SystemExit(f"required fixed-font source does not exist: {source}")
actual_sha256 = hashlib.sha256(source.read_bytes()).hexdigest()
if actual_sha256 != expected_sha256:
raise SystemExit(
f"fixed-font source hash mismatch for {source}: "
f"expected {expected_sha256}, got {actual_sha256}"
)
def subset_font(source: Path, output: Path, codepoints: list[int]) -> None:
font = TTFont(source, recalcTimestamp=False)
options = Options()
options.recalc_timestamp = False
options.retain_gids = False
options.name_IDs = [0, 1, 2, 3, 4, 5, 6, 13, 14]
subsetter = Subsetter(options=options)
subsetter.populate(unicodes=codepoints)
subsetter.subset(font)
font["head"].created = OPEN_TYPE_UNIX_EPOCH
font["head"].modified = OPEN_TYPE_UNIX_EPOCH
font.save(output, reorderTables=False)
def convert(flavor: str) -> None:
font = TTFont(TTF_PATH, recalcTimestamp=False)
font.flavor = flavor
font.save(FIXTURES / f"moli-ahem.{flavor}", reorderTables=False)
def main() -> None:
FIXTURES.mkdir(parents=True, exist_ok=True)
verify_source(DEJAVU_SOURCE, DEJAVU_SOURCE_SHA256)
verify_source(DROID_CJK_SOURCE, DROID_CJK_SOURCE_SHA256)
build_ttf()
build_ttf(FIXTURES / "moli-ahem-narrow.ttf", 500)
build_ttf(FIXTURES / "moli-ahem-wide.ttf", 1000)
SYSTEM_FONT_TTF_PATH.parent.mkdir(parents=True, exist_ok=True)
SYSTEM_FONT_TTF_PATH.write_bytes(TTF_PATH.read_bytes())
convert("woff")
convert("woff2")
subset_font(DEJAVU_SOURCE, HEBREW_EMOJI_PATH, [0x20, 0x05D0, 0x05D1, 0x1F600])
subset_font(DROID_CJK_SOURCE, CJK_PATH, [0x20, 0x4E2D, 0x6587])
if __name__ == "__main__":
main()