diff --git a/build_iosevka_fonts.py b/build_iosevka_fonts.py new file mode 100644 index 0000000..e0534e6 --- /dev/null +++ b/build_iosevka_fonts.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +Build web-ready Iosevka fonts for the client. + +The system Iosevka TTFs are ~10MB each (43k+ glyphs, full Unicode coverage). +For web delivery we subset them to Latin + common punctuation/symbols and +convert to WOFF2, which is far smaller and supported by all modern browsers. + +Usage: + python3 build_iosevka_fonts.py + +Outputs (into www/css/): + Iosevka-Regular.woff2 + Iosevka-Bold.woff2 + Iosevka-Italic.woff2 + Iosevka-BoldItalic.woff2 +""" + +import os +import sys + +from fontTools import subset +from fontTools.ttLib import TTFont + +# Source TTF directory (system-installed Iosevka). +SRC_DIR = "/usr/share/fonts/truetype/iosevka" + +# Destination directory for the generated web fonts. +OUT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "www", "css") + +# Variants we ship: (source filename, output filename) +VARIANTS = [ + ("Iosevka-Regular.ttf", "Iosevka-Regular.woff2"), + ("Iosevka-Bold.ttf", "Iosevka-Bold.woff2"), + ("Iosevka-Italic.ttf", "Iosevka-Italic.woff2"), + ("Iosevka-BoldItalic.ttf", "Iosevka-BoldItalic.woff2"), +] + +# Unicode ranges to keep. Latin + Latin-1 Supplement + Latin Extended-A, +# Greek, Cyrillic, general punctuation, currency, arrows, math operators, +# box drawing, geometric shapes, misc symbols, dingbats, and the +# replacement char. This keeps the web font around ~300 KB per variant +# while covering everything the UI realistically renders. +UNICODE_RANGES = [ + "U+0000-00FF", # Basic Latin + Latin-1 Supplement + "U+0100-017F", # Latin Extended-A + "U+0370-03FF", # Greek and Coptic + "U+0400-04FF", # Cyrillic + "U+2000-206F", # General Punctuation + "U+20A0-20CF", # Currency Symbols + "U+2190-21FF", # Arrows + "U+2200-22FF", # Mathematical Operators + "U+2500-257F", # Box Drawing + "U+25A0-25FF", # Geometric Shapes + "U+2600-26FF", # Miscellaneous Symbols + "U+2700-27BF", # Dingbats + "U+FFFD", # Replacement character +] + + +def build_variant(src_name: str, out_name: str) -> None: + src_path = os.path.join(SRC_DIR, src_name) + out_path = os.path.join(OUT_DIR, out_name) + + if not os.path.exists(src_path): + print(f" ! Missing source font: {src_path}", file=sys.stderr) + return + + print(f" - {src_name} -> {out_name}") + + font = TTFont(src_path) + + options = subset.Options() + options.flavor = "woff2" + options.desubroutinize = True + options.layout_features = ["*"] # keep all OpenType features (ligatures, etc.) + options.name_IDs = ["*"] + options.name_legacy = True + options.notdef_outline = True + options.recalc_bounds = True + options.drop_tables = ["DSIG"] + + subsetter = subset.Subsetter(options=options) + subsetter.populate(unicodes=subset.parse_unicodes(",".join(UNICODE_RANGES))) + subsetter.subset(font) + + font.flavor = "woff2" + font.save(out_path) + font.close() + + size_kb = os.path.getsize(out_path) / 1024 + print(f" wrote {out_path} ({size_kb:.1f} KB)") + + +def main() -> int: + if not os.path.isdir(SRC_DIR): + print(f"Source font directory not found: {SRC_DIR}", file=sys.stderr) + return 1 + + os.makedirs(OUT_DIR, exist_ok=True) + print(f"Building Iosevka web fonts into {OUT_DIR}") + + for src_name, out_name in VARIANTS: + build_variant(src_name, out_name) + + print("Done.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/www/conway.html b/www/conway.html index 7e0ec8c..9510dc2 100644 --- a/www/conway.html +++ b/www/conway.html @@ -569,7 +569,7 @@ let fontSize = Math.max(8, Math.floor(conwayRows * 0.38)); while (fontSize > 6) { - offCtx.font = `700 ${fontSize}px pageMono, monospace`; + offCtx.font = `700 ${fontSize}px Iosevka, monospace`; const width = offCtx.measureText(text).width; if (width <= conwayCols * 0.9) { break; @@ -577,7 +577,7 @@ fontSize -= 1; } - offCtx.font = `700 ${fontSize}px pageMono, monospace`; + offCtx.font = `700 ${fontSize}px Iosevka, monospace`; offCtx.fillText(text, Math.floor(conwayCols / 2), Math.floor(conwayRows / 2)); const { data, width, height } = offCtx.getImageData(0, 0, conwayCols, conwayRows); diff --git a/www/css/Iosevka-Bold.woff2 b/www/css/Iosevka-Bold.woff2 new file mode 100644 index 0000000..a8c2e21 Binary files /dev/null and b/www/css/Iosevka-Bold.woff2 differ diff --git a/www/css/Iosevka-BoldItalic.woff2 b/www/css/Iosevka-BoldItalic.woff2 new file mode 100644 index 0000000..35ff90d Binary files /dev/null and b/www/css/Iosevka-BoldItalic.woff2 differ diff --git a/www/css/Iosevka-Italic.woff2 b/www/css/Iosevka-Italic.woff2 new file mode 100644 index 0000000..c665fbc Binary files /dev/null and b/www/css/Iosevka-Italic.woff2 differ diff --git a/www/css/Iosevka-Regular.woff2 b/www/css/Iosevka-Regular.woff2 new file mode 100644 index 0000000..aa57159 Binary files /dev/null and b/www/css/Iosevka-Regular.woff2 differ diff --git a/www/css/client.css b/www/css/client.css index c46bf0e..434e273 100644 --- a/www/css/client.css +++ b/www/css/client.css @@ -69,7 +69,8 @@ * for colors so they automatically adapt to dark/light mode. * * 7. Font family should always be var(--font-family) unless a specific - * element needs var(--font-mono) or pageKanji. + * element needs var(--font-mono) or pageKanji. The primary UI font is + * Iosevka (see @font-face declarations below). * * 8. If a style is used on 2+ pages, move it to client.css. * If a style is used on only 1 page, keep it in that page's