Source code for noisemaker.glyphs

from __future__ import annotations

"""Font loader for Noisemaker. Creates glyph atlases from TrueType fonts."""

import os

from PIL import Image, ImageDraw, ImageFont

import noisemaker.rng as random
from noisemaker.util import get_noisemaker_dir


[docs] def load_fonts() -> list[str]: """Find all TrueType fonts in the ~/.noisemaker/fonts directory. Returns: List of absolute paths to .ttf font files, or empty list if fonts directory doesn't exist. """ fonts_dir = os.path.join(get_noisemaker_dir(), "fonts") if not os.path.exists(fonts_dir): return [] return [os.path.join(fonts_dir, f) for f in os.listdir(fonts_dir) if f.endswith(".ttf")]
[docs] def load_glyphs(shape: list[int]) -> list[list[list[list[float]]]]: """Generate a list of ASCII character glyphs sorted from darkest to brightest. Renders printable ASCII characters (32-126) using a randomly selected font, then sorts them by brightness for use in value-based text rendering. RNG: One call to :func:`random.randint` to select font. Args: shape: Glyph dimensions as [height, width]. Returns: List of glyphs, where each glyph is [y][x][channel] with normalized [0.0, 1.0] values. Sorted from darkest (space) to brightest (dense characters). """ fonts = load_fonts() if not fonts: return [] font_name = fonts[random.randint(0, len(fonts) - 1)] font = ImageFont.truetype(font_name, int(max(shape[0], shape[1]) * 0.9)) glyphs = [] totals = [] for i in range(32, 127): total = 0.0 glyph: list[list[list[float]]] = [] glyphs.append(glyph) image = Image.new("RGB", (shape[1], shape[0])) ImageDraw.Draw(image).text((0, 0), chr(i), font=font) for y in range(shape[0]): row: list[list[float]] = [] glyph.append(row) for x in range(shape[1]): pixel = image.getpixel((x, y)) if not isinstance(pixel, tuple): raise ValueError(f"Expected tuple pixel value, got {type(pixel)}") value = float(pixel[0]) / 255.0 row.append([value]) total += value totals.append(total) return [g for total, g in sorted(zip(totals, glyphs))]