Switch UI font to Iosevka (regular/bold/italic/bold-italic) with WOFF2 web fonts

This commit is contained in:
Laan Tungir
2026-09-17 11:17:21 -04:00
parent 5e261023b2
commit e6f9597c67
8 changed files with 152 additions and 12 deletions
+111
View File
@@ -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())
+2 -2
View File
@@ -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);
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+36 -7
View File
@@ -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 <style>.
@@ -87,12 +88,38 @@
@import url('./sidenav-sections.css');
/* Iosevka web fonts (subset to Latin/Greek/Cyrillic + common symbols,
converted to WOFF2 by build_iosevka_fonts.py). */
@font-face {
font-family: pageMono;
src: url(./NotoMono-Regular.ttf);
/* src: url(./courier-new.ttf); */
/* src: url(./Calcula.ttf); */
/* src: url(./IBMPlexMono-Thin.ttf); */
font-family: "Iosevka";
src: url(./Iosevka-Regular.woff2) format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Iosevka";
src: url(./Iosevka-Bold.woff2) format("woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Iosevka";
src: url(./Iosevka-Italic.woff2) format("woff2");
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Iosevka";
src: url(./Iosevka-BoldItalic.woff2) format("woff2");
font-weight: 700;
font-style: italic;
font-display: swap;
}
@font-face {
@@ -103,7 +130,9 @@
:root {
/* Core Variables */
--font-family: "pageMono", Courier, monospace;
--font-family: "Iosevka", "pageMono", Courier, monospace;
--font-mono: "Iosevka", "pageMono", Courier, monospace;
--font-family-mono: var(--font-mono);
--primary-color: #000000;
--secondary-color: #ffffff;
--accent-color: #ff0000;
+3 -3
View File
@@ -1,5 +1,5 @@
{
"VERSION": "v0.7.105",
"VERSION_NUMBER": "0.7.105",
"BUILD_DATE": "2026-08-17T10:28:11.271Z"
"VERSION": "v0.7.106",
"VERSION_NUMBER": "0.7.106",
"BUILD_DATE": "2026-09-17T15:17:21.161Z"
}