61 lines
2.7 KiB
Python
Executable file
61 lines
2.7 KiB
Python
Executable file
#!/usr/bin/env -S uv run -q --script
|
|
# /// script
|
|
# dependencies = ["fonttools==4.68.0", "brotli==1.2.1", "lxml==6.1.3"]
|
|
# ///
|
|
# ./scripts/fonts.py fonts/fonts.txt (family weight style file) → werc/pub/style/fonts/*.woff2 + fonts.css
|
|
import pathlib, shutil
|
|
from fontTools import subset
|
|
from fontTools.ttLib import TTFont
|
|
|
|
root = pathlib.Path(__file__).resolve().parent.parent
|
|
src, out, css = root / 'fonts', root / 'werc/pub/style/fonts', root / 'werc/pub/style/fonts.css'
|
|
CORE = [(0x20, 0x7e), (0xa0, 0x17f), (0x2000, 0x206f), (0x20ac, 0x20ac), (0x2122, 0x2122),
|
|
(0x2190, 0x21ff), (0x2212, 0x2212), (0x2500, 0x25ff)]
|
|
SPLIT = 64 # more extra glyphs than this → separate .ext file, fetched only by pages that use them
|
|
|
|
def in_core(c): return any(a <= c <= b for a, b in CORE)
|
|
def rng(a, b): return f'U+{a:X}' if a == b else f'U+{a:X}-{b:X}'
|
|
core_range = ', '.join(rng(a, b) for a, b in CORE)
|
|
ext_range, prev = [], -1
|
|
for a, b in CORE + [(0x110000, 0x110000)]:
|
|
if a > prev + 1: ext_range.append(rng(prev + 1, a - 1))
|
|
prev = b
|
|
ext_range = ', '.join(ext_range)
|
|
|
|
opts = subset.Options()
|
|
opts.flavor, opts.layout_features, opts.name_IDs, opts.notdef_outline = 'woff2', ['*'], ['*'], True
|
|
opts.drop_tables += ['FFTM']
|
|
|
|
def build(path, name, cps):
|
|
font = subset.load_font(str(path), opts)
|
|
s = subset.Subsetter(opts)
|
|
s.populate(unicodes=cps)
|
|
s.subset(font)
|
|
subset.save_font(font, str(out / name), opts)
|
|
|
|
shutil.rmtree(out, ignore_errors=True)
|
|
out.mkdir()
|
|
files, faces = {}, []
|
|
for line in (src / 'fonts.txt').read_text().splitlines():
|
|
if not line.strip(): continue
|
|
*family, weight, style, file = line.split()
|
|
if file not in files:
|
|
slug = pathlib.Path(file).stem.lower().replace('_', '-')
|
|
cps = set(TTFont(src / file, lazy=True).getBestCmap())
|
|
core, ext = {c for c in cps if in_core(c)}, {c for c in cps if not in_core(c)}
|
|
if len(ext) > SPLIT:
|
|
build(src / file, f'{slug}.woff2', core)
|
|
build(src / file, f'{slug}.ext.woff2', ext)
|
|
files[file] = [(f'{slug}.woff2', core_range), (f'{slug}.ext.woff2', ext_range)]
|
|
else:
|
|
build(src / file, f'{slug}.woff2', cps)
|
|
files[file] = [(f'{slug}.woff2', None)]
|
|
for name, ur in files[file]:
|
|
faces.append(f"@font-face {{ font-family: '{' '.join(family)}'; src: url('fonts/{name}') format('woff2'); "
|
|
f"font-weight: {weight}; font-style: {style}; font-display: swap;"
|
|
+ (f' unicode-range: {ur};' if ur else '') + ' }')
|
|
|
|
css.write_text('\n'.join(faces) + '\n')
|
|
before = sum((src / f).stat().st_size for f in files)
|
|
after = sum(p.stat().st_size for p in out.iterdir())
|
|
print(f'{len(faces)} faces, {len(files)} sources {before // 1024} KB → {after // 1024} KB')
|