Versió inicial: CLI i GUI Tk per a fonts bitmap
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## What this is
|
||||
|
||||
Standalone tool that produces bitmap fonts (`.gif` + `.fnt`) for the "Projecte 2026" game engine.
|
||||
|
||||
- [`font_gen.py`](font_gen.py) — pure rasterising core + CLI. The two public functions are [`build_font_bitmap`](font_gen.py) (TTF → in-memory `FontBitmapResult`, no disk I/O) and [`save_font_files`](font_gen.py) (writes `.gif` + `.fnt`). The CLI wrappers `render_font` (TTF) and `render_gif_font` (GIF) layer logging on top.
|
||||
- [`font_gen_gui.py`](font_gen_gui.py) — Tkinter GUI that imports the core, runs `build_font_bitmap` on every input change (debounced 200 ms) for live preview, and calls `save_font_files` on GENERAR.
|
||||
|
||||
The two files are independent: copy them anywhere and run. There is no install step, no test suite, no linter config.
|
||||
|
||||
## Running
|
||||
|
||||
```sh
|
||||
pip install Pillow # only mandatory dependency
|
||||
python3 font_gen.py # GUI (no args)
|
||||
python3 font_gen.py --ttf foo.ttf --size 8 --output foo # CLI from TTF
|
||||
python3 font_gen.py --gif foo.gif --output foo --columns 16 \
|
||||
--box-width 10 --box-height 7 --cell-spacing 1 # CLI from existing GIF
|
||||
```
|
||||
|
||||
`magick` (ImageMagick) is invoked optionally inside `save_font_files` to clamp the GIF palette to 2 colours; if missing it is silently skipped.
|
||||
|
||||
Default output directory is **next to the script** (`os.path.dirname(__file__)`). The CLI accepts `--dir` to override; the GUI always writes alongside the script.
|
||||
|
||||
## Output contract (don't break these)
|
||||
|
||||
These properties are consumed by the engine and must hold for any change to the rasteriser:
|
||||
|
||||
- **Indexed GIF, palette index 0 = background, index 1 = glyph.** Compatible with `SurfaceSprite::render(1, color)` in the engine. The post-process `magick … -colors 2 GIF87:` enforces a 2-colour palette when available.
|
||||
- **Character set order is fixed.** [`ALL_CHARS`](font_gen.py) = ASCII 32–126 followed by the ES/CA/VA extension list, in that exact sequence. `.fnt` rows and GIF cells are addressed by this order; reordering breaks every existing `.fnt` consumer.
|
||||
- **`.fnt` format**: header keys (`box_width`, `box_height`, `columns`, optional `cell_spacing`, optional `row_spacing` only when it differs from `cell_spacing`), blank line, then `<codepoint_decimal> <visual_width> # <name>` per supported char. See [`_write_fnt`](font_gen.py).
|
||||
- **Visual width is measured from pixels**, not from the font's typographic advance — last column containing an opaque pixel + 1. `getlength()` is only used to size the canvas.
|
||||
|
||||
## TTF rendering: the non-obvious bits
|
||||
|
||||
- **`.notdef` detection**: some TTFs return a substitute glyph (with positive advance and a valid bbox) for missing codepoints, which would falsely pass the `getlength(ch) >= 1` check. The tool renders `U+FFFE` as a reference and rejects any char whose pixels match it. See `_is_notdef` inside `build_font_bitmap`.
|
||||
- **Fallback table** (`CHAR_FALLBACKS`) maps accented ES/CA/VA chars to ASCII equivalents (`À→A`, `ñ→n`, …). When a TTF lacks the accented glyph, the fallback is drawn into the cell **but the original codepoint is recorded in the `.fnt`**, so localised text still resolves at runtime.
|
||||
- **Vertical placement**: `draw.text((-bbox[0], y_offset), …)` where `y_offset` is computed from the average top of capital letters. Don't "fix" this by removing the offset — it keeps descenders inside the cell and aligns punctuation to the baseline.
|
||||
- **`box_width` auto-mode** uses `max(getlength)` across rendered chars; override with `--box-width` for pixel-exact bitmap strikes.
|
||||
|
||||
## GIF mode quirks (CLI-only)
|
||||
|
||||
- `cell_spacing` is both the inter-column gap **and** the top/left border. Origin of cell `(col, row)` is `(col*(bw+cs)+cs, row*(bh+rs)+cs)` — see `render_gif_font`.
|
||||
- Empty cells (all index 0) are marked unsupported and dropped from the `.fnt`, except whitespace chars which get a default width of `box_width // 2`.
|
||||
- The source GIF is `shutil.copy2`'d unchanged to the output dir; only the `.fnt` is regenerated.
|
||||
- This mode is intentionally **not exposed in the GUI** — it's a maintenance path for re-measuring an existing bitmap font, not a primary workflow.
|
||||
|
||||
## GUI specifics
|
||||
|
||||
- **Live preview**: every change to TTF / size / advanced options schedules `_update_preview` via `self.after(200, …)` (debounced). Render runs on the main thread — for typical pixel-font sizes it is sub-second.
|
||||
- **`ImageTk.PhotoImage` lifetime**: stored on `self._preview_img_tk` to prevent Tk from garbage-collecting the image and showing a blank label. If you refactor preview rendering, keep this reference alive.
|
||||
- **Preview palette is for display only**: index 0 is recoloured white and index 1 black so the user can read the glyphs on screen. The image saved to disk uses the original palette (index 1 = white) — `_render_preview` operates on a copy.
|
||||
- **Preview scale**: nearest-neighbor upscale by `min(8, PREVIEW_MAX_W // w, PREVIEW_MAX_H // h)`. Pixel-perfect, never blurred.
|
||||
|
||||
## Entry point dispatch
|
||||
|
||||
In `font_gen.py` `__main__`: `len(sys.argv) == 1` launches the GUI directly (no argparse). Anything else goes through `main()`, which honours `--gui` to launch the GUI explicitly. The GUI is imported lazily inside `_launch_gui()` so CLI-only invocations don't require tkinter.
|
||||
Reference in New Issue
Block a user