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));
|
let fontSize = Math.max(8, Math.floor(conwayRows * 0.38));
|
||||||
while (fontSize > 6) {
|
while (fontSize > 6) {
|
||||||
offCtx.font = `700 ${fontSize}px pageMono, monospace`;
|
offCtx.font = `700 ${fontSize}px Iosevka, monospace`;
|
||||||
const width = offCtx.measureText(text).width;
|
const width = offCtx.measureText(text).width;
|
||||||
if (width <= conwayCols * 0.9) {
|
if (width <= conwayCols * 0.9) {
|
||||||
break;
|
break;
|
||||||
@@ -577,7 +577,7 @@
|
|||||||
fontSize -= 1;
|
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));
|
offCtx.fillText(text, Math.floor(conwayCols / 2), Math.floor(conwayRows / 2));
|
||||||
|
|
||||||
const { data, width, height } = offCtx.getImageData(0, 0, conwayCols, conwayRows);
|
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.
|
* for colors so they automatically adapt to dark/light mode.
|
||||||
*
|
*
|
||||||
* 7. Font family should always be var(--font-family) unless a specific
|
* 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.
|
* 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>.
|
* 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');
|
@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-face {
|
||||||
font-family: pageMono;
|
font-family: "Iosevka";
|
||||||
src: url(./NotoMono-Regular.ttf);
|
src: url(./Iosevka-Regular.woff2) format("woff2");
|
||||||
/* src: url(./courier-new.ttf); */
|
font-weight: 400;
|
||||||
/* src: url(./Calcula.ttf); */
|
font-style: normal;
|
||||||
/* src: url(./IBMPlexMono-Thin.ttf); */
|
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 {
|
@font-face {
|
||||||
@@ -103,7 +130,9 @@
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* Core Variables */
|
/* 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;
|
--primary-color: #000000;
|
||||||
--secondary-color: #ffffff;
|
--secondary-color: #ffffff;
|
||||||
--accent-color: #ff0000;
|
--accent-color: #ff0000;
|
||||||
|
|||||||
+3
-3
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"VERSION": "v0.7.105",
|
"VERSION": "v0.7.106",
|
||||||
"VERSION_NUMBER": "0.7.105",
|
"VERSION_NUMBER": "0.7.106",
|
||||||
"BUILD_DATE": "2026-08-17T10:28:11.271Z"
|
"BUILD_DATE": "2026-09-17T15:17:21.161Z"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user