refactor(render): TextBackend owns its atlas, and staged glyphs carry a rotation basis

This commit is contained in:
Martino Ferrari committed 2026-09-28 09:57:28 +02:00
1 parent 90e17a0a11
commit e074f57ddf
23 files changed
+1073 -227

No files matched your search

+61 -19
View File
@@ -1,6 +1,23 @@
#include <plot/render/primitives.hpp>
#include <cmath>
namespace plot::render {
namespace {
/// `axis` as a unit vector: the cell's local +x, which is what the instance and
/// the shader both take (see `TextInstance`). A zero, infinite or NaN direction
/// means "unrotated" -- a caller's arithmetic slip must not turn a label into NaN
/// geometry, and a basis the shader trusts to be unit must be one.
Vec2 unit_axis(Vec2 axis) noexcept {
const f32 len = axis.length();
if (std::isfinite(len) && len > 0.0f) {
return axis / len;
}
return Vec2{1.0f, 0.0f};
}
} // namespace
// ----------------------------------------------------------------- LineBatch
@@ -42,31 +59,56 @@ void LineBatch::add_polyline_xy(const f32* xy, u32 n) {
// ----------------------------------------------------------------- TextBatch
void TextBatch::begin() noexcept { v_.clear(); }
void TextBatch::clear() noexcept { v_.clear(); }
void TextBatch::add_glyph(Vec2 pos_px, u32 slot) {
const Vec2 origin = BitmapFont::atlas_origin(slot);
v_.push_back(pos_px.x);
v_.push_back(pos_px.y);
v_.push_back(origin.x);
v_.push_back(origin.y);
void TextBatch::begin() noexcept {
v_.clear();
cell_uv_ = Vec2{};
}
Vec2 TextBatch::add_text(Vec2 pen_px, const char* utf8, const BitmapFont& font, f32 px_per_cell) {
void TextBatch::clear() noexcept {
v_.clear();
cell_uv_ = Vec2{};
}
void TextBatch::add_cell(Vec2 pen_px, const Glyph& glyph, f32 px, Vec2 unit_ux) {
// The cell's own axes: the run direction and its perpendicular, which is the
// cell's +v axis -- the one kTextVS builds the quad from. The bearing is in
// font pixels, so it scales with the run and follows the same axes: a glyph
// keeps its place inside a rotated run instead of drifting along screen +y.
const Vec2 uy{-unit_ux.y, unit_ux.x};
const Vec2 corner = pen_px + unit_ux * (glyph.xoff * px) + uy * (glyph.yoff * px);
v_.push_back(corner.x);
v_.push_back(corner.y);
v_.push_back(glyph.u0);
v_.push_back(glyph.v0);
v_.push_back(unit_ux.x);
v_.push_back(unit_ux.y);
// One cell for the whole batch: every glyph a batch stages comes from the same
// atlas, so the rect the shader reads is the one staged here (see
// glyph_cell_uv()).
cell_uv_ = Vec2{glyph.u1 - glyph.u0, glyph.v1 - glyph.v0};
}
void TextBatch::add_glyph(Vec2 pen_px, const Glyph& glyph, f32 px, Vec2 axis) {
add_cell(pen_px, glyph, px, unit_axis(axis));
}
Vec2 TextBatch::add_text(const char* utf8, Vec2 pen_px, f32 px, const TextBackend& font,
Vec2 axis) {
if (utf8 == nullptr) {
return pen_px;
}
const f32 scale = px_per_cell / static_cast<f32>(BitmapFont::kGlyphPx);
const Vec2 ux = unit_axis(axis);
for (const char* p = utf8; *p != '\0';) {
// Same codepoints `measure` sums and the same advances, scaled by the
// on-screen cell size: a run staged here is exactly what the plot layer
// measured, times the scale it draws at, so a label never overruns the
// space that was reserved for it.
const u32 codepoint = BitmapFont::next_codepoint(p);
add_glyph(pen_px, BitmapFont::glyph_index(codepoint));
pen_px.x += font.advance_codepoint(codepoint) * scale;
// The backend's own codepoints and the backend's own advances -- the same
// numbers `measure()` sums -- scaled by `px` and measured along the basis,
// so a run staged here is exactly as long as the plot layer measured it and
// ends where the space reserved for the label ends.
const Glyph* glyph = font.glyph(next_codepoint(p));
if (glyph == nullptr) {
continue; // no artwork for this codepoint: nothing to draw, nothing to advance
}
add_cell(pen_px, *glyph, px, ux);
pen_px = pen_px + ux * (glyph->advance * px);
}
return pen_px;
}