Repository navigation
[M2-TEXT-01] Bitmap font atlas (stb_truetype vendored) - #85
Merged
Merged
Conversation
- Vendor stb_truetype (deps/stb, pinned commit 2c980bb, tree-hash in deps.lock; ADR 0009): the FR-2.8 bitmap P0 rasterizer. The pinned commit's API is the glyph-index + metrics surface (no stbtt_GetFontOffsetForData / codepoint convenience wrappers). - New laige::render::GlyphAtlas (public header laige/render/font.h, implementation font.cpp): create(fontBytes, Options) rasterizes the configured glyph range ONCE at scene set-up into a fixed 8-bit alpha atlas (deterministic shelf packing) + per-glyph GlyphMetrics (advance, bearingX/Y, width/height — px at 1x, the scale factor documented; lineHeight() == fontSize() by the stbtt pixel-height convention); glyph(code) is total, O(1), zero-allocation, never logs (out-of-range -> the U+0020 space slot, else the zero metric; unknown in-range -> the font's .notdef). - The untrusted-font boundary (SCALE-004): the engine-side header + table-directory guard (stbtt's directory scan is not bounds-checked in the pinned commit), the GetFontOffsetForIndex / InitFont reject, and the degenerate vertical-metrics guard (the pixel-height scale must be finite, positive, and <= 2^15 — the int domain of stbtt's box rounding and the lround'd advance). MalformedInput + one structured Error font/font_invalid. - The fixed-size capacity guard: a glyph cell wider/taller than the atlas, or a full shelf -> BudgetExhausted + one Error font/atlas_full (the atlas is fixed-size by contract); success -> one Info font/atlas_created (the lookup path logs nothing). - The vendor boundary is the laige-stb static target (one impl TU, the plain-compiler policy of the vendor impl TUs — DEP-005); the public header names no stb type (include-lint R3 owner rule). - Tests: ctest -R font (21 tests, GL-free — every local tree + CI): the validation matrix (no log), the untrusted-font boundary (empty / short / directory-cut / non-font / degenerate metrics — exactly one structured Error each), the stopped state, the capacity guard, the introspection + one-Info contract, the move semantics, the metric goldens measured against the committed Vera test font (tests/laige-render/assets/vera.ttf, OFL 1.1; the assets/ README documents provenance + license), the .notdef + out-of-range fallbacks, the atlas invariants (every cell inside the fixed atlas), the deterministic-atlas contract (same font + options -> identical bytes; the font-atlas-determinism fnv1a line — no pinned cross-platform hash: presentation-only float, ARCH-009, the scope is in-process/same-build), and the 1000-lookup zero-allocation window. - Docs: ADR 0009 (DEP-003 justification; FreeType / hand-rolled / baked-atlas alternatives rejected), docs/api/font.md (the full contract incl. the DOC-004 Performance section), deps/README.md (the stb row + the missing GLM row), the docs/README.md index, the render module README, the assets README, the roadmap (M2 22/33, total 68/194) + Change Log. - laige-api.json regenerated LAST (1419 -> 1462 symbols, 42 -> 43 headers; api-real-tree green). No budgets.json entry: the atlas is built once at set-up, not per frame — the per-frame text cost lands with M2-TEXT-02 (part of the composite 50k render-CPU budget, M2-PERF-01; the repo entry count stays 17). - Local verification: all six canonical trees warning-clean, full ctest green (build 117/117, build-release 106/106, build-shared 117/117, build-clang 117/117, build-asan 114/114, build-tsan 114/114 — prior counts + 1 for the new font entry each); tools/laige-include-lint OK (5/10 vendored deps), tools/laige-determinism-lint OK.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
M2-TEXT-01 · Font rasterization (bitmap, P0)
FR-2.8 bitmap half: the configured TTF/OTF font bytes + glyph range rasterized once at scene set-up into a fixed-size 8-bit alpha glyph atlas + per-glyph metrics (advance, bearing, line height — documented px at 1x). No runtime re-rasterization;
glyph(code)is a total O(1) zero-alloc lookup.What landed
stb_truetype.h(nothings/stb, pinned commit2c980bb, tree-hash-pinned indeps.lock; ADR 0009 with the full DEP-003 justification — FreeType / hand-rolled / baked-atlas alternatives all rejected). The pinned commit's API is the glyph-index + metrics surface (nostbtt_GetFontOffsetForData/ codepoint wrappers — documented in the ADR).laige::render::GlyphAtlas(include/laige/render/font.h,font.cpp) —create(fontBytes, Options): options validation first (first failure wins, no log), the untrusted-font boundary (the engine-side header + table-directory guard — stbtt's directory scan is not bounds-checked — plus the degenerate vertical-metrics guard: the pixel-height scale must be finite, positive, and ≤ 2^15, the int domain of stbtt's float→int conversions), the fixed-size capacity guard (BudgetExhausted+ one Errorfont/atlas_full), one Infofont/atlas_createdon success.GlyphMetrics: advance, bearingX/Y, width/height, atlasX/Y (all px at 1x;lineHeight() == fontSize()by the stbtt pixel-height convention).glyph(code): in-range → the slot (unknown codepoints → the font's.notdef), out-of-range → the U+0020 space slot (else the zero metric), stopped → nullptr — never a crash, never silent.laige-stbstatic target (one impl TU, the plain-compiler policy, DEP-005); the public header names no stb type (include-lint R3).ctest -R font(21 tests, GL-free — every local tree + CI): the validation matrix, the untrusted-font boundary (empty / short / directory-cut / non-font / degenerate metrics — exactly one structured Error each), the stopped state, the capacity guard, the move semantics, the metric goldens measured against the committed Vera test font (tests/laige-render/assets/vera.ttf, OFL 1.1), the.notdef+ out-of-range fallbacks, the atlas invariants, the deterministic-atlas contract (same font + options → identical bytes; the machine-greppablefont-atlas-determinismfnv1a line — no pinned cross-platform hash: presentation-only float, ARCH-009), and the 1000-lookup zero-allocation window.docs/api/font.md(the full contract incl. the DOC-004 Performance section),deps/README.md(the stb row + the previously-missing GLM row), the docs index, the render module README, the assets README, the roadmap (M2 22/33, total 68/194) + Change Log.laige-api.jsonregenerated LAST (1419 → 1462 symbols, 42 → 43 headers). Nobudgets.jsonentry: the atlas is built once at set-up, not per frame (the per-frame text cost lands with M2-TEXT-02, part of the composite 50k render-CPU budget; the repo entry count stays 17).asan-reports/(the ASan test entries'log_pathoutput) added to.gitignore(the ASan verification runs produce it; it was pollutinggit status).Local verification (all six canonical trees, warning-clean)
(prior counts + 1 for the new
fontentry each;ctest -R fontgreen in all six;tools/laige-include-lintOK — 5/10 vendored deps;tools/laige-determinism-lintOK.)CI scope
First MSVC compile of this C++ (font.h / font.cpp / stb_truetype_impl.cpp / font_tests.cpp) —
ci:windowslabel; the C4267/C4244 audit was done against the final code (the degenerate-scale guard keeps every float→int conversion in its int domain; explicit casts at every size_t boundary).