107 lines
5.1 KiB
Markdown
107 lines
5.1 KiB
Markdown
# 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.
|