Files
Asuro/toolchain/gen_file_icons.py
T
Aaron 5628c1ddbf feat: file dispatch system, file-type icons, multi-instance notepad
- Add extension-based dispatch to driver.storage.filedispatch with typed
  union (variant record THandlerKind = hkMagic/hkExtension)
- Extract file-type icon constants and mapping to core.gfx.fileicons
- Add core.search utility (getFileExtension, fmtFileSize, sortStringArray)
- Generate PUA bitmap icon fonts (32/64/128px) from Font Awesome via
  gen_file_icons.py + compile_icons.sh toolchain
- Integrate file dispatch into file browser (double-click triggers dispatch)
- Register notepad as handler for text file extensions
- Fix init order in app.mgr (filedispatch.init before app inits)
- Refactor notepad from singleton to multi-instance (up to 8 windows)
  using instance array, findStateByWid, and LVGL user_data on msgboxes
2026-03-11 19:41:49 +00:00

550 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
gen_file_icons.py — Generate LVGL-compatible icon font C files from Font Awesome.
Renders FA glyphs at multiple sizes (32, 64, 128) with per-file-type label
overlays. Outputs asuro_icons_{size}.c files that compile into liblvgl.a.
Uses PIL (python3-pil) to rasterize Font Awesome TTF glyphs and composite
type-specific text labels. Output is in LVGL lv_font_fmt_txt format with
4bpp anti-aliased bitmaps in the Unicode Private Use Area (U+E000–E0FF).
Usage:
python3 gen_file_icons.py <output_dir> [--font-dir <font_cache_dir>]
Dependencies: python3-pil (already in Dockerfile)
"""
import os
import sys
import struct
import math
import urllib.request
from PIL import Image, ImageDraw, ImageFont
# =============================================================================
# Configuration
# =============================================================================
FA_VERSION = "6.7.2"
FA_TTF_URL = (
f"https://github.com/FortAwesome/Font-Awesome/releases/download/"
f"{FA_VERSION}/fontawesome-free-{FA_VERSION}-desktop.zip"
)
# We'll download the individual TTF directly from the release assets
FA_SOLID_URL = (
f"https://raw.githubusercontent.com/FortAwesome/Font-Awesome/"
f"{FA_VERSION}/webfonts/fa-solid-900.ttf"
)
SIZES = [32, 64, 128]
BPP = 4 # 4 bits per pixel (16 levels of anti-aliasing)
# Font Awesome codepoints for base shapes
FA_FILE = 0xF15B # file (blank)
FA_FILE_CODE = 0xF1C9 # file-code (angle brackets)
FA_FILE_LINES = 0xF15C # file-lines (text lines on file)
FA_FILE_IMAGE = 0xF1C5 # file-image
FA_FILE_AUDIO = 0xF1C7 # file-audio (music note on file)
FA_FILE_VIDEO = 0xF1C8 # file-video (play on file)
FA_FILE_ZIP = 0xF1C6 # file-zipper
FA_FILE_PDF = 0xF1C1 # file-pdf
FA_FOLDER = 0xF07B # folder
FA_FOLDER_OPEN = 0xF07C # folder-open
FA_HDD = 0xF0A0 # hard-drive
FA_LINK = 0xF0C1 # link
FA_GEAR = 0xF013 # gear
FA_DATABASE = 0xF1C0 # database (stacked disks)
FA_KEY = 0xF084 # key
FA_TERMINAL = 0xF120 # terminal
FA_FONT = 0xF031 # font (big A)
FA_CODE = 0xF121 # code (angle brackets)
FA_CUBE = 0xF1B2 # cube
# Category colors (R, G, B) — used for label background bands
CLR_GENERIC = (120, 130, 150) # Slate grey
CLR_CODE = (60, 120, 210) # Blue
CLR_DOC = (140, 150, 165) # Grey
CLR_DATA = (40, 170, 160) # Teal
CLR_IMAGE = (50, 180, 80) # Green
CLR_AUDIO = (140, 80, 200) # Purple
CLR_VIDEO = (190, 60, 150) # Magenta
CLR_ARCHIVE = (220, 140, 30) # Orange
CLR_SYSTEM = (200, 60, 60) # Red
CLR_FONT = (200, 160, 40) # Amber
CLR_MISC = (100, 110, 130) # Slate
# Icon definitions: (pua_codepoint, fa_base_glyph, label_text, category_color)
# label_text=None means no overlay (just the base glyph)
ICONS = [
# === Generic (E000–E005) ===
(0xE000, FA_FILE, None, CLR_GENERIC), # Generic file
(0xE001, FA_FOLDER, None, CLR_GENERIC), # Folder closed
(0xE002, FA_FOLDER_OPEN, None, CLR_GENERIC), # Folder open
(0xE003, FA_HDD, None, CLR_GENERIC), # Drive
(0xE004, FA_CUBE, None, CLR_GENERIC), # Mount point
(0xE005, FA_LINK, None, CLR_GENERIC), # Symlink
# === Code (E010–E023) ===
(0xE010, FA_FILE_CODE, None, CLR_CODE), # Generic code
(0xE011, FA_FILE_CODE, "PAS", CLR_CODE), # Pascal
(0xE012, FA_FILE_CODE, "PY", CLR_CODE), # Python
(0xE013, FA_FILE_CODE, "JS", CLR_CODE), # JavaScript
(0xE014, FA_FILE_CODE, "TS", CLR_CODE), # TypeScript
(0xE015, FA_FILE_CODE, "C", CLR_CODE), # C source
(0xE016, FA_FILE_CODE, "H", CLR_CODE), # C header
(0xE017, FA_FILE_CODE, "C++", CLR_CODE), # C++
(0xE018, FA_FILE_CODE, "RS", CLR_CODE), # Rust
(0xE019, FA_FILE_CODE, "GO", CLR_CODE), # Go
(0xE01A, FA_FILE_CODE, "RB", CLR_CODE), # Ruby
(0xE01B, FA_TERMINAL, "SH", CLR_CODE), # Shell script
(0xE01C, FA_FILE_CODE, "ASM", CLR_CODE), # Assembly
(0xE01D, FA_FILE_CODE, "LUA", CLR_CODE), # Lua
(0xE01E, FA_FILE_CODE, "SQL", CLR_CODE), # SQL
(0xE01F, FA_FILE_CODE, "CSS", CLR_CODE), # CSS
(0xE020, FA_FILE_CODE, "HTM", CLR_CODE), # HTML
(0xE021, FA_FILE_CODE, "PHP", CLR_CODE), # PHP
(0xE022, FA_FILE_CODE, "JAV", CLR_CODE), # Java
(0xE023, FA_FILE_CODE, "C#", CLR_CODE), # C#
# === Documents (E028–E02F) ===
(0xE028, FA_FILE_LINES, "TXT", CLR_DOC), # Plain text
(0xE029, FA_FILE_LINES, "MD", CLR_DOC), # Markdown
(0xE02A, FA_FILE_PDF, None, CLR_DOC), # PDF (has its own icon)
(0xE02B, FA_FILE_LINES, "DOC", CLR_DOC), # Word
(0xE02C, FA_FILE_LINES, "RTF", CLR_DOC), # Rich text
(0xE02D, FA_FILE_LINES, "LOG", CLR_DOC), # Log
(0xE02E, FA_FILE_LINES, "CSV", CLR_DOC), # CSV
(0xE02F, FA_FILE_LINES, "TEX", CLR_DOC), # LaTeX
# === Data/Config (E030–E036) ===
(0xE030, FA_FILE_CODE, "JSN", CLR_DATA), # JSON
(0xE031, FA_FILE_CODE, "XML", CLR_DATA), # XML
(0xE032, FA_FILE_CODE, "YML", CLR_DATA), # YAML
(0xE033, FA_FILE, "INI", CLR_DATA), # INI
(0xE034, FA_FILE, "CFG", CLR_DATA), # Config
(0xE035, FA_FILE, "ENV", CLR_DATA), # Env
(0xE036, FA_FILE_CODE, "TML", CLR_DATA), # TOML
# === Images (E038–E041) ===
(0xE038, FA_FILE_IMAGE, None, CLR_IMAGE), # Generic image
(0xE039, FA_FILE_IMAGE, "PNG", CLR_IMAGE), # PNG
(0xE03A, FA_FILE_IMAGE, "JPG", CLR_IMAGE), # JPEG
(0xE03B, FA_FILE_IMAGE, "BMP", CLR_IMAGE), # Bitmap
(0xE03C, FA_FILE_IMAGE, "TGA", CLR_IMAGE), # Targa
(0xE03D, FA_FILE_IMAGE, "GIF", CLR_IMAGE), # GIF
(0xE03E, FA_FILE_IMAGE, "SVG", CLR_IMAGE), # SVG
(0xE03F, FA_FILE_IMAGE, "ICO", CLR_IMAGE), # Icon
(0xE040, FA_FILE_IMAGE, "PSD", CLR_IMAGE), # Photoshop
(0xE041, FA_FILE_IMAGE, "WBP", CLR_IMAGE), # WebP
# === Audio (E044–E049) ===
(0xE044, FA_FILE_AUDIO, None, CLR_AUDIO), # Generic audio
(0xE045, FA_FILE_AUDIO, "MP3", CLR_AUDIO), # MP3
(0xE046, FA_FILE_AUDIO, "WAV", CLR_AUDIO), # WAV
(0xE047, FA_FILE_AUDIO, "OGG", CLR_AUDIO), # OGG
(0xE048, FA_FILE_AUDIO, "FLC", CLR_AUDIO), # FLAC
(0xE049, FA_FILE_AUDIO, "MID", CLR_AUDIO), # MIDI
# === Video (E04C–E051) ===
(0xE04C, FA_FILE_VIDEO, None, CLR_VIDEO), # Generic video
(0xE04D, FA_FILE_VIDEO, "MP4", CLR_VIDEO), # MP4
(0xE04E, FA_FILE_VIDEO, "AVI", CLR_VIDEO), # AVI
(0xE04F, FA_FILE_VIDEO, "MKV", CLR_VIDEO), # MKV
(0xE050, FA_FILE_VIDEO, "WBM", CLR_VIDEO), # WebM
(0xE051, FA_FILE_VIDEO, "MOV", CLR_VIDEO), # MOV
# === Archives (E054–E05C) ===
(0xE054, FA_FILE_ZIP, None, CLR_ARCHIVE), # Generic archive
(0xE055, FA_FILE_ZIP, "ZIP", CLR_ARCHIVE), # ZIP
(0xE056, FA_FILE_ZIP, "TAR", CLR_ARCHIVE), # TAR
(0xE057, FA_FILE_ZIP, "GZ", CLR_ARCHIVE), # Gzip
(0xE058, FA_FILE_ZIP, "7Z", CLR_ARCHIVE), # 7-Zip
(0xE059, FA_FILE_ZIP, "RAR", CLR_ARCHIVE), # RAR
(0xE05A, FA_FILE_ZIP, "XZ", CLR_ARCHIVE), # XZ
(0xE05B, FA_FILE_ZIP, "BZ2", CLR_ARCHIVE), # BZip2
(0xE05C, FA_FILE_ZIP, "ISO", CLR_ARCHIVE), # ISO image
# === System/Binary (E060–E069) ===
(0xE060, FA_GEAR, None, CLR_SYSTEM), # Generic system
(0xE061, FA_FILE, "BIN", CLR_SYSTEM), # Binary
(0xE062, FA_FILE, "EXE", CLR_SYSTEM), # Executable
(0xE063, FA_FILE, "DLL", CLR_SYSTEM), # Library
(0xE064, FA_FILE, "SO", CLR_SYSTEM), # Shared object
(0xE065, FA_FILE, "PPU", CLR_SYSTEM), # FPC unit
(0xE066, FA_FILE, "OBJ", CLR_SYSTEM), # Object file
(0xE067, FA_FILE, "WSM", CLR_SYSTEM), # WebAssembly
(0xE068, FA_FILE, "ELF", CLR_SYSTEM), # ELF binary
(0xE069, FA_GEAR, "KRN", CLR_SYSTEM), # Kernel/driver
# === Font/Media (E06C–E06F) ===
(0xE06C, FA_FONT, None, CLR_FONT), # Generic font
(0xE06D, FA_FONT, "TTF", CLR_FONT), # TrueType
(0xE06E, FA_FONT, "OTF", CLR_FONT), # OpenType
(0xE06F, FA_FONT, "WOF", CLR_FONT), # WOFF
# === Misc (E070–E078) ===
(0xE070, FA_LINK, "LNK", CLR_MISC), # Shortcut
(0xE071, FA_FILE, "BAK", CLR_MISC), # Backup
(0xE072, FA_FILE, "TMP", CLR_MISC), # Temporary
(0xE073, FA_FILE, "LCK", CLR_MISC), # Lock file
(0xE074, FA_KEY, None, CLR_MISC), # Key/cert
(0xE075, FA_DATABASE, None, CLR_MISC), # Database
(0xE076, FA_CODE, "GIT", CLR_MISC), # Git file
(0xE077, FA_FILE, "MK", CLR_MISC), # Makefile
(0xE078, FA_FILE, "DKR", CLR_MISC), # Dockerfile
]
# Build a contiguous range covering all icons (with empty slots for gaps)
PUA_START = 0xE000
PUA_END = max(cp for cp, _, _, _ in ICONS)
# =============================================================================
# Font Awesome TTF download/cache
# =============================================================================
def ensure_fa_ttf(font_dir):
"""Download Font Awesome Solid 900 TTF if not cached."""
os.makedirs(font_dir, exist_ok=True)
ttf_path = os.path.join(font_dir, "fa-solid-900.ttf")
if os.path.exists(ttf_path):
return ttf_path
print(f" Downloading Font Awesome {FA_VERSION} TTF...")
urllib.request.urlretrieve(FA_SOLID_URL, ttf_path)
print(f" Saved to {ttf_path}")
return ttf_path
# =============================================================================
# Glyph rendering
# =============================================================================
def render_glyph(fa_font, label_font, size, fa_codepoint, label_text, color):
"""
Render one icon glyph as a greyscale PIL Image.
1. Render the FA base glyph at full size, centered in the cell
2. If label_text is set, overlay it directly on the lower portion of
the glyph: punch a dark strip into the existing pixels, then draw
bright white text on top for maximum contrast
3. Return the image as greyscale (L mode)
"""
img = Image.new("L", (size, size), 0)
draw = ImageDraw.Draw(img)
# Render FA glyph — full size, centered
fa_char = chr(fa_codepoint)
fa_size = int(size * 0.82)
try:
fa_pil = ImageFont.truetype(fa_font, fa_size)
except Exception:
fa_pil = ImageFont.truetype(fa_font, int(size * 0.7))
bbox = fa_pil.getbbox(fa_char)
if bbox is None:
margin = size // 8
draw.rectangle([margin, margin, size - margin, size - margin],
fill=180, outline=255)
return img
gw = bbox[2] - bbox[0]
gh = bbox[3] - bbox[1]
gx = (size - gw) // 2 - bbox[0]
gy = (size - gh) // 2 - bbox[1]
draw.text((gx, gy), fa_char, fill=255, font=fa_pil)
# Overlay label text on the lower portion of the glyph
if label_text:
# Size the label font: ~30% of icon height, minimum 8px
lbl_size = max(8, int(size * 0.30))
try:
lbl_font = ImageFont.truetype(label_font, lbl_size)
# Shrink if text is wider than the icon
lbbox = lbl_font.getbbox(label_text)
if lbbox:
lw = lbbox[2] - lbbox[0]
while lw > size - 2 and lbl_size > 7:
lbl_size -= 1
lbl_font = ImageFont.truetype(label_font, lbl_size)
lbbox = lbl_font.getbbox(label_text)
if lbbox:
lw = lbbox[2] - lbbox[0]
except Exception:
lbl_font = ImageFont.load_default()
lbbox = lbl_font.getbbox(label_text)
if lbbox:
lw = lbbox[2] - lbbox[0]
lh = lbbox[3] - lbbox[1]
# Position: centered horizontally, bottom-aligned with small margin
pad = max(1, size // 16)
lx = (size - lw) // 2 - lbbox[0]
ly = size - lh - pad - lbbox[1]
# Punch a dark rectangle behind the text for contrast
# 2px padding around the text
tp = max(1, size // 16)
rx0 = max(0, lx + lbbox[0] - tp)
ry0 = max(0, ly + lbbox[1] - tp)
rx1 = min(size - 1, lx + lbbox[0] + lw + tp)
ry1 = min(size - 1, ly + lbbox[1] + lh + tp)
# Darken: set the region to a low value so the glyph fades there
draw.rectangle([rx0, ry0, rx1, ry1], fill=40)
# Draw the text in full white — maximum 4bpp contrast vs fill=40
# fill=40 -> quantises to 2, fill=255 -> quantises to 15 = 13 steps
draw.text((lx, ly), label_text, fill=255, font=lbl_font)
return img
# =============================================================================
# LVGL C font file generation
# =============================================================================
def quantize_4bpp(img):
"""Convert greyscale image to 4bpp packed bytes (high nibble first)."""
w, h = img.size
pixels = list(img.getdata())
result = []
row_bytes = (w + 1) // 2 # ceil(w/2)
for row in range(h):
for col_pair in range(row_bytes):
c0 = col_pair * 2
c1 = c0 + 1
idx0 = row * w + c0
idx1 = row * w + c1
# Quantize 0-255 to 0-15
p0 = min(15, pixels[idx0] >> 4) if c0 < w else 0
p1 = min(15, pixels[idx1] >> 4) if c1 < w else 0
result.append((p0 << 4) | p1)
return bytes(result)
def generate_font_c(icons_data, size, font_name):
"""
Generate LVGL-compatible C source for a font.
icons_data: list of (codepoint, bitmap_bytes, box_w, box_h)
sorted by codepoint, contiguous from PUA_START.
"""
lines = []
# Header
lines.append("/*******************************************************************************")
lines.append(f" * Asuro file-type icons — {size}px")
lines.append(f" * Bpp: {BPP}")
lines.append(f" * Auto-generated by gen_file_icons.py — DO NOT EDIT")
lines.append(" ******************************************************************************/")
lines.append("")
lines.append("#ifdef LV_LVGL_H_INCLUDE_SIMPLE")
lines.append(' #include "lvgl.h"')
lines.append("#else")
lines.append(' #include "lvgl/lvgl.h"')
lines.append("#endif")
lines.append("")
lines.append(f"#ifndef {font_name.upper()}")
lines.append(f"#define {font_name.upper()} 1")
lines.append("#endif")
lines.append("")
lines.append(f"#if {font_name.upper()}")
lines.append("")
# Build contiguous glyph table from PUA_START to PUA_END
# Map codepoints to their data
cp_map = {cp: (bm, bw, bh) for cp, bm, bw, bh in icons_data}
# Contiguous range PUA_START..PUA_END
glyph_list = []
for cp in range(PUA_START, PUA_END + 1):
if cp in cp_map:
glyph_list.append((cp, cp_map[cp][0], cp_map[cp][1], cp_map[cp][2]))
else:
# Empty placeholder glyph (zero-size)
glyph_list.append((cp, b"", 0, 0))
# === glyph_bitmap[] ===
lines.append("/*-----------------------")
lines.append(" * Glyph bitmap data")
lines.append(" *-----------------------*/")
lines.append("")
lines.append("static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {")
bitmap_offset = 0
glyph_offsets = [] # (bitmap_index, box_w, box_h)
for cp, bm, bw, bh in glyph_list:
glyph_offsets.append((bitmap_offset, bw, bh))
if len(bm) > 0:
lines.append(f" /* U+{cp:04X} */")
# Format bitmap bytes, 16 per line
for i in range(0, len(bm), 16):
chunk = bm[i:i+16]
hex_str = ", ".join(f"0x{b:02x}" for b in chunk)
comma = "," if i + 16 < len(bm) else ","
lines.append(f" {hex_str}{comma}")
bitmap_offset += len(bm)
lines.append("")
lines.append("};")
lines.append("")
# === glyph_dsc[] ===
# adv_w is box_w * 16 (8.4 fixed point) + small padding
lines.append("/*-----------------------")
lines.append(" * Glyph descriptors")
lines.append(" *-----------------------*/")
lines.append("")
lines.append("static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {")
lines.append(" {.bitmap_index = 0, .adv_w = 0, .box_w = 0, "
".box_h = 0, .ofs_x = 0, .ofs_y = 0}, /* sentinel */")
for i, (bm_idx, bw, bh) in enumerate(glyph_offsets):
cp = PUA_START + i
if bw == 0 and bh == 0:
# Empty slot
adv_w = size * 16 # still advance by full cell
lines.append(f" {{.bitmap_index = 0, .adv_w = {adv_w}, "
f".box_w = 0, .box_h = 0, "
f".ofs_x = 0, .ofs_y = 0}}, /* U+{cp:04X} empty */")
else:
adv_w = (bw + 1) * 16 # advance = box width + 1px padding
ofs_y = 0 # glyph top aligns with line top (base_line=0)
lines.append(f" {{.bitmap_index = {bm_idx}, .adv_w = {adv_w}, "
f".box_w = {bw}, .box_h = {bh}, "
f".ofs_x = 0, .ofs_y = {ofs_y}}}, /* U+{cp:04X} */")
lines.append("};")
lines.append("")
# === cmap — FORMAT0_TINY (contiguous range) ===
num_glyphs = PUA_END - PUA_START + 1
lines.append("/*-----------------------")
lines.append(" * Character map")
lines.append(" *-----------------------*/")
lines.append("")
lines.append("static const lv_font_fmt_txt_cmap_t cmaps[] = {")
lines.append(" {")
lines.append(f" .range_start = {PUA_START}, .range_length = {num_glyphs}, "
f".glyph_id_start = 1,")
lines.append(" .unicode_list = NULL, .glyph_id_ofs_list = NULL, "
".list_length = 0,")
lines.append(" .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY")
lines.append(" }")
lines.append("};")
lines.append("")
# === font_dsc ===
lines.append("/*-----------------------")
lines.append(" * Font descriptor")
lines.append(" *-----------------------*/")
lines.append("")
lines.append("static const lv_font_fmt_txt_dsc_t font_dsc = {")
lines.append(" .glyph_bitmap = glyph_bitmap,")
lines.append(" .glyph_dsc = glyph_dsc,")
lines.append(" .cmaps = cmaps,")
lines.append(" .kern_dsc = NULL,")
lines.append(" .kern_scale = 0,")
lines.append(" .cmap_num = 1,")
lines.append(f" .bpp = {BPP},")
lines.append(" .kern_classes = 0,")
lines.append(" .bitmap_format = 0")
lines.append("};")
lines.append("")
# === lv_font_t ===
line_height = size
base_line = 0
lines.append("/*-----------------------")
lines.append(" * Public font")
lines.append(" *-----------------------*/")
lines.append("")
lines.append(f"const lv_font_t {font_name} = {{")
lines.append(" .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt,")
lines.append(" .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt,")
lines.append(f" .line_height = {line_height},")
lines.append(f" .base_line = {base_line},")
lines.append(" .subpx = LV_FONT_SUBPX_NONE,")
lines.append(" .underline_position = 0,")
lines.append(" .underline_thickness = 0,")
lines.append(" .dsc = &font_dsc,")
lines.append(" .fallback = NULL,")
lines.append(" .user_data = NULL")
lines.append("};")
lines.append("")
lines.append(f"#endif /* {font_name.upper()} */")
lines.append("")
return "\n".join(lines)
# =============================================================================
# Main
# =============================================================================
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <output_dir> [--font-dir <dir>]")
sys.exit(1)
output_dir = sys.argv[1]
font_dir = "/tmp/icon_fonts"
if "--font-dir" in sys.argv:
idx = sys.argv.index("--font-dir")
if idx + 1 < len(sys.argv):
font_dir = sys.argv[idx + 1]
# Ensure FA TTF is available
fa_ttf = ensure_fa_ttf(font_dir)
# For label text, try to use a system monospace font, fall back to default
label_font = fa_ttf # FA itself won't have latin glyphs, need alternative
# Try common paths for a monospace font
mono_candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationMono-Regular.ttf",
"/usr/share/fonts/truetype/ubuntu/Ubuntu-R.ttf",
]
label_ttf = None
for candidate in mono_candidates:
if os.path.exists(candidate):
label_ttf = candidate
break
if label_ttf is None:
print(" WARNING: No system monospace TTF found, labels will use default font")
label_ttf = None # Will use PIL default
for sz in SIZES:
font_name = f"asuro_icons_{sz}"
print(f" Generating {font_name} ({sz}px, {len(ICONS)} icons)...")
icons_data = []
for pua_cp, fa_cp, label, color in ICONS:
img = render_glyph(fa_ttf, label_ttf, sz, fa_cp, label, color)
bm = quantize_4bpp(img)
icons_data.append((pua_cp, bm, sz, sz))
c_source = generate_font_c(icons_data, sz, font_name)
out_path = os.path.join(output_dir, f"{font_name}.c")
with open(out_path, "w") as f:
f.write(c_source)
line_count = c_source.count("\n")
print(f" Wrote {out_path} ({line_count} lines)")
print(" Icon font generation complete.")
if __name__ == "__main__":
main()