# AGENTS.md Context for future agents working on this repo. ## Project This project generates an experimental OpenType font that renders bracketed text as QR Code symbols while keeping surrounding text readable. The generated font is a Modified Version of Liberation Sans Regular and is licensed under the SIL Open Font License 1.1. The bounded QR targets are: - **QR Code Version 1-L** (up to 17 characters per block) - **QR Code Version 2-L** (up to 32 characters per block) - **QR Code Version 3-L** (up to 53 characters per block) - byte mode - fixed mask pattern 0 - `[` and `]` delimiters Example: ```text Hello [QR coded] world! Download this font: [http://qr.jim.sh/] ``` ## Build Use `uv` for Python commands and dependency management. ```sh make ``` `make` runs the full Reed-Solomon parity build and compiles the fonts and `index.html` demo into the `dist/` directory. Use `make deploy` to synchronize the built `dist/` assets directly to `psy:/www/qr/` (which cleans up stale/experimental remote files via `--delete`). Do not use the placeholder parity path for normal verification. It exists only as a legacy layout-debug target: ```sh make fast-placeholder ``` The generator defaults to: ```text /usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf ``` Use `--base-font` to test another compatible TrueType base font. ## Important Implementation Details - `tools/build_font.py` is the source of truth. It emits glyph outlines, OpenType feature code, HTML demo, and SVG reference output. - Printable ASCII glyphs are copied from Liberation Sans and scaled down with `LATIN_SCALE`, so normal text can surround QR blocks in the same font. - Latin advances are snapped to 100 font-unit increments. This helps QR blocks start on the QR module grid after ordinary text. - QR modules are drawn as slightly overpainted rectangles. Current `MODULE_OVERPAINT` is intentional: it hides faint gray seams between QR helper glyph layers. - QR helper glyphs carry a tiny no-op TrueType program (`PUSHB[1] 0`, `POP[]`). This was added after Chrome/Firefox showed one-pixel offsets between adjacent rectangles. The no-op program plus smoothing-only `gasp` table worked much better than grid-fitting. - The current `gasp` table intentionally avoids grid-fit flags and uses grayscale/symmetric smoothing only. ## Verification Lightweight checks: ```sh uv run tools/shape_debug.py 'Hello [QR coded] world!' 'Download this font: [http://qr.jim.sh/]' ``` For QR matrix correctness, compare `matrix_for_text()` against an independent QR encoder in byte mode, version 1, level L, mask 0. Previous checks used: ```sh uv run --with qrcode[pil] python ... ``` Browser rendering is useful but time-consuming. The user asked to skip routine Firefox rendering for now. If visual alignment regresses, inspect recent screenshots in `~/Downloads`. We build three separate font families (`1-L`, `2-L`, `3-L`), each fixed to its respective QR version. Supporting multiple QR versions dynamically in a single font file is possible, but would require branching by payload length at close-delimiter time and emitting version-specific base patterns, coordinate maps, RS parity circuits, and advances. The current strategy compiles separate fonts for each version. ## Browser Layout, Alignment, and Line-Breaking Details ### Firefox Alignment Issue - **Symptom:** Horizontal shifting/slicing between the top/bottom (parity) and middle (data) sections of the QR code when resizing the font in Firefox. - **Cause:** Parity/state glyphs were classified as GDEF Marks and had zero-advance in `hmtx`. Firefox applies subpixel snapping/rounding to zero-advance GDEF Marks differently from standard spacing Base glyphs, causing horizontal coordinate drift. - **Solution:** We omit the GDEF table and configure all intermediate QR-related glyphs (`header_bits`, `byte_XX`, `pXX`, `sXX`) to have native `0` advance in the `hmtx` table. The closing base glyph (`qr_base_NN` or `qr_base_p55_NN`) is the only glyph with a positive `ADVANCE` width. Because there are no GPOS positioning adjustments, the browser treats them all as zero-advance Base glyphs, eliminating horizontal misalignment entirely. ### Line-Breaking Limitations - **Symptom:** QR codes containing spaces, dots, or slashes split across lines. In Chrome, the second half of the split QR code renders as plain text (e.g., `coded]`). In Firefox, it splits the shaped QR code. - **Cause:** Web browsers run line-breaking algorithms (Unicode UAX #14) on the raw Unicode text BEFORE they run the font shaper (HarfBuzz). Because line-breaking is decided on Unicode characters before GSUB/GPOS run, font-level ligatures designed to ligate breaking characters to their neighbors cannot prevent browser wrapping. - **Chrome Behavior:** Chrome splits the string into two independent lines and shapes them separately. Since the second line lacks `[`, it remains plain text. - **Firefox Behavior:** Firefox shapes first and then splits the shaped glyph run. - **Rule of Thumb:** Always wrap QR-coded elements in a CSS container with `white-space: nowrap` or `display: inline-block` to avoid breaks, as the font itself cannot override Unicode line-breaking properties.