feat(render): anti-aliased embedded Adwaita Mono atlas, generated and committed

This commit is contained in:
Martino Ferrari committed 2026-09-28 10:40:23 +02:00
1 parent 3ff7b9c978
commit 5ca3587325
17 files changed
+3422 -627

No files matched your search

+24 -16
View File
@@ -88,23 +88,25 @@ f32 walk_text_clipped(Vec2 pen, const char* utf8, f32 budget_px, const render::T
void warn_replaced_text_backend(const render::TextBackend* previous) noexcept {
std::fprintf(stderr,
"plot::Renderer::draw: replacing the Plot's TextBackend (%p) with the renderer's "
"own bitmap font; a plot drawn by a Renderer measures the labels it draws.\n",
"own embedded font; a plot drawn by a Renderer measures the labels it draws.\n",
static_cast<const void*>(previous));
}
#endif
} // namespace
void Renderer::AxisFont::reset(const render::BitmapFont& font, f32 requested_px) noexcept {
void Renderer::AxisFont::reset(const render::TextBackend& font, f32 requested_px) noexcept {
font_ = &font;
const f32 native = static_cast<f32>(render::BitmapFont::kGlyphPx);
// Whole multiples of the native cell only: a 1-bit glyph drawn at 1.5x lands
// its strokes on uneven pixel counts, and the layout has to agree with what the
// pipeline draws, so the requested size picks the nearest multiple.
f32 steps = std::round(requested_px / native);
if (!(steps >= 1.0f)) steps = 1.0f; // also catches NaN
cell_px_ = steps * native;
scale_ = cell_px_ / native;
// The requested size is an em size, exactly as `em_px()` reports the size the
// atlas was baked at, so the ratio between them is all the scaling there is:
// an anti-aliased face is legible at any ratio, and a style asking for 12 px
// from a 13 px bake gets 12/13 rather than a snapped whole multiple. A backend
// that cannot say (`em_px()` at or below zero, or NaN) keeps a 1:1 scale rather
// than dividing by it.
const f32 baked = font.em_px();
const f32 ratio = requested_px / baked;
scale_ = (baked > 0.0f && ratio > 0.0f) ? ratio : 1.0f;
cell_px_ = font.height() * scale_;
}
f32 Renderer::AxisFont::measure(const char* utf8) const {
@@ -119,6 +121,12 @@ f32 Renderer::AxisFont::ascent() const {
return font_ != nullptr ? font_->ascent() * scale_ : 0.0f;
}
f32 Renderer::AxisFont::em_px() const {
// The em is the one metric that does not scale: it is the face's own unit, and
// it is what the scale above was computed from.
return font_ != nullptr ? font_->em_px() : 0.0f;
}
const render::Glyph* Renderer::AxisFont::glyph(u32 codepoint) const {
if (font_ == nullptr) {
return nullptr; // nothing to draw with, so nothing to describe
@@ -169,7 +177,7 @@ bool Renderer::init(Status* st) noexcept {
const render::UploadMode mode =
persistent ? render::UploadMode::PersistentMapped : render::UploadMode::Orphan;
font_backend_.reset(font_, style_.axis_font_px);
font_backend_.reset(default_font_, style_.axis_font_px);
// The atlas is the backend's: `sync()` uploads whatever it exposes, at whatever
// size it says, and restores the host's unpack alignment around the upload
@@ -263,7 +271,7 @@ void Renderer::draw(Plot& plot) {
// The plot reserves its label strips from TextBackend metrics, so it must
// measure with the size the text pipeline draws with, and re-read the style
// each frame so a changed axis_font_px takes effect on the next one.
font_backend_.reset(font_, style_.axis_font_px);
font_backend_.reset(default_font_, style_.axis_font_px);
// The cell every glyph staged this frame is drawn in, declared from the backend
// rather than left to whichever glyph is staged first: the instances carry rect
// origins, and the draw carries this size (see TextBackend::cell_uv).
@@ -280,7 +288,7 @@ void Renderer::draw(Plot& plot) {
}
#ifndef NDEBUG
// The renderer owns the text of the frames it draws: every label goes through
// the text pipeline in `font_`, so the plot is handed this font's metrics
// the text pipeline in `default_font_`, so the plot is handed this backend's metrics
// whatever a host installed. A host backend is therefore replaced on the first
// draw -- said out loud in a debug build, since its disappearance is otherwise
// silent -- and a Plot nobody draws keeps it (see Plot::set_text_backend()).
@@ -456,17 +464,17 @@ void Renderer::stage_xy(const f32* xy, u32 n, bool log_x, bool log_y) {
Vec2 Renderer::stage_text(Vec2 pos_px, const char* utf8) {
// Text is staged in viewport pixels: the text program has no offset uniform.
return glyphs_.add_text(utf8, to_viewport(pos_px), font_backend_.scale(), font_);
return glyphs_.add_text(utf8, to_viewport(pos_px), font_backend_.scale(), default_font_);
}
f32 Renderer::clipped_text_width(const char* utf8, f32 budget_px) const {
return walk_text_clipped(Vec2{}, utf8, budget_px, font_, font_backend_.scale(),
return walk_text_clipped(Vec2{}, utf8, budget_px, default_font_, font_backend_.scale(),
[](Vec2, const render::Glyph&) {});
}
f32 Renderer::stage_text_clipped(Vec2 pos_px, const char* utf8, f32 budget_px) {
const f32 scale = font_backend_.scale();
return walk_text_clipped(to_viewport(pos_px), utf8, budget_px, font_, scale,
return walk_text_clipped(to_viewport(pos_px), utf8, budget_px, default_font_, scale,
[this, scale](Vec2 pen, const render::Glyph& glyph) {
glyphs_.add_glyph(pen, glyph, scale);
});