dbf850c434
- M5 Unit Modules library + M5 Unit Test app - Minor fixes for TodoList, TwoEleven, Snake, Brainfuck and Breakout - Fixed SerialConsole to use the uart controller as it was broken in one of the many updates - Fixed keyboard input in TwoEleven, Breakout, Magic8Ball and Snake - Bluetooth Media Keys app, supports pressing physical keys to trigger the corresponding buttonmatrix button - Epub Reader app
130 lines
5.3 KiB
Python
130 lines
5.3 KiB
Python
"""
|
||
gen_fonts.py - regenerate all LVGL binary font files for EpubReader.
|
||
|
||
Prerequisites:
|
||
node / npm must be on PATH
|
||
npm install -g lv_font_conv
|
||
|
||
Usage (from any directory):
|
||
python Apps/EpubReader/gen_fonts.py
|
||
|
||
Output: .bin files written to Apps/EpubReader/assets/
|
||
|
||
Output font filenames are font-family-agnostic: font_<size>pt_<variant>.bin
|
||
The app loader expects exactly these names - swap in any 4-weight typeface by
|
||
editing the FONTS dict and placing the .ttf files in Apps/EpubReader/fonts/.
|
||
|
||
Default: Noto Serif (4 weights) https://fonts.google.com/noto/specimen/Noto+Serif
|
||
NotoSerif-Medium.ttf → regular
|
||
NotoSerif-MediumItalic.ttf → italic
|
||
NotoSerif-Bold.ttf → bold
|
||
NotoSerif-BoldItalic.ttf → bold italic
|
||
"""
|
||
|
||
import shutil
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
SCRIPT_DIR = Path(__file__).parent
|
||
|
||
# ── Latin content fonts (4 weights) ───────────────────────────────────────────
|
||
FONTS = {
|
||
"regular": SCRIPT_DIR / "fonts" / "NotoSerif-Medium.ttf",
|
||
"italic": SCRIPT_DIR / "fonts" / "NotoSerif-MediumItalic.ttf",
|
||
"bold": SCRIPT_DIR / "fonts" / "NotoSerif-Bold.ttf",
|
||
"bold_italic": SCRIPT_DIR / "fonts" / "NotoSerif-BoldItalic.ttf",
|
||
}
|
||
|
||
# ── Unicode ranges ─────────────────────────────────────────────────────────────
|
||
|
||
# Unicode coverage - adjust if your chosen font supports different blocks:
|
||
LATIN_TEXT_RANGES = [
|
||
"-r", "0x20-0x7E", # Basic Latin
|
||
"-r", "0xA0-0x024F", # Latin-1 Supplement + Latin Extended-A/B
|
||
"-r", "0x0370-0x03FF", # Greek (π, Δ, Σ, Ω etc.)
|
||
"-r", "0x1E9E", # ẞ (capital sharp-s, German)
|
||
"-r", "0x2010-0x2026", # General Punctuation (hyphens, dashes, ellipsis)
|
||
"-r", "0x2070-0x209F", # Superscripts and Subscripts (⁰⁴⁵⁶⁷⁸⁹ ₀₁₂₃₄₅₆₇₈₉)
|
||
"-r", "0x20A0-0x20CF", # Currency Symbols (€ ₹ ₽ ₩ ₪ ₴ etc.)
|
||
"-r", "0x2100-0x214F", # Letterlike Symbols (™ ℃ ℉ etc.)
|
||
"-r", "0x2150-0x218F", # Number Forms (⅓ ⅔ ⅛ ⅜ ⅝ ⅞ etc.)
|
||
]
|
||
|
||
# ── Sizes to generate ──────────────────────────────────────────────────────────
|
||
SIZES = [16, 20, 28, 36]
|
||
|
||
# ── Rendering options ──────────────────────────────────────────────────────────
|
||
BPP = 2 # 2-bit anti-aliasing; use 4 for smoother rendering (~2× larger)
|
||
FORMAT = "bin" # "lvgl" = C struct output; "bin" = binary blob
|
||
COMPRESS = False # True = enable RLE compression (smaller binary, slightly slower)
|
||
|
||
# ── Output directory ───────────────────────────────────────────────────────────
|
||
OUT_DIR = SCRIPT_DIR / "assets"
|
||
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
||
|
||
# ── Locate lv_font_conv (handles Windows .cmd npm wrappers) ───────────────────
|
||
def _find_tool() -> str:
|
||
for name in ("lv_font_conv", "lv_font_conv.cmd"):
|
||
found = shutil.which(name)
|
||
if found:
|
||
return found
|
||
print("ERROR: lv_font_conv not found. Install with: npm install -g lv_font_conv", file=sys.stderr)
|
||
sys.exit(1)
|
||
|
||
LV_FONT_CONV = _find_tool()
|
||
USE_SHELL = sys.platform == "win32"
|
||
|
||
def _run(cmd: list) -> bool:
|
||
if USE_SHELL:
|
||
result = subprocess.run(subprocess.list2cmdline(cmd), capture_output=True, text=True, shell=True)
|
||
else:
|
||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||
if result.returncode != 0:
|
||
print(f" ERROR: {result.stderr.strip()}", file=sys.stderr)
|
||
return False
|
||
return True
|
||
|
||
|
||
def generate_font(font_path: Path, ranges: list, size: int, symbol: str) -> bool:
|
||
"""Convert a single .ttf to an LVGL binary font file."""
|
||
if not font_path.exists():
|
||
print(f" SKIP {symbol} (font not found: {font_path.name})")
|
||
return False
|
||
|
||
out = OUT_DIR / f"{symbol}.bin"
|
||
cmd = [LV_FONT_CONV,
|
||
"--font", str(font_path)] + ranges + [
|
||
"--size", str(size),
|
||
"--bpp", str(BPP),
|
||
"--format", FORMAT,
|
||
"--lv-font-name", symbol,
|
||
"-o", str(out),
|
||
]
|
||
if not COMPRESS:
|
||
cmd.append("--no-compress")
|
||
|
||
print(f" {symbol:<36} size={size} ← {font_path.name}")
|
||
return _run(cmd)
|
||
|
||
|
||
def main():
|
||
print(f"lv_font_conv → {OUT_DIR}\n")
|
||
generated = 0
|
||
skipped = 0
|
||
|
||
# ── Content font variants (Latin text + symbols) ───────────────────────────
|
||
for size in SIZES:
|
||
print(f"── {size}pt {'─' * 52}")
|
||
for font_key in FONTS.keys():
|
||
symbol = f"font_{size}pt_{font_key}"
|
||
ok = generate_font(FONTS[font_key], LATIN_TEXT_RANGES, size, symbol)
|
||
if ok: generated += 1
|
||
else: skipped += 1
|
||
|
||
print(f"\nDone. {generated} generated, {skipped} skipped.")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|