Switch UI font to Iosevka (regular/bold/italic/bold-italic) with WOFF2 web fonts
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user