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 -17
View File
@@ -8,7 +8,7 @@
#include <plot/core/types.hpp>
#include <plot/math/vec2.hpp>
#include <plot/plot/plot.hpp>
#include <plot/render/bitmap_font.hpp>
#include <plot/render/embedded_mono_font.hpp>
#include <plot/render/buffer.hpp>
#include <plot/render/primitives.hpp>
#include <plot/render/program.hpp>
@@ -155,9 +155,11 @@ class Renderer {
bool using_persistent_upload() const noexcept {
return line_vao_.persistent() && text_vao_.persistent();
}
/// The axis text size the renderer actually draws with: `style().axis_font_px`
/// rounded to the nearest whole multiple of `render::BitmapFont::kGlyphPx`,
/// because a 1-bit glyph only scales cleanly by whole factors.
/// The axis text size the renderer actually draws with: the on-screen cell
/// height of the backend it draws with. `Style::axis_font_px` is an em size, so
/// with an ascent-to-descent cell it is this that a host reads back as the line
/// height it got -- and it is the backend's own `height()` scaled, so it always
/// agrees with the metrics the plot laid the labels out with.
f32 axis_font_px() const noexcept { return font_backend_.cell_px(); }
/// Writes the framebuffer's viewport region as a binary PPM (P6, 8 bits per
@@ -174,23 +176,25 @@ class Renderer {
bool dump_frame_ppm(const char* path, Status* st = nullptr) const;
private:
/// The bitmap font as the plot layer sees it, at the size the text pipeline
/// draws it: `measure`/`height`/`ascent` scale with the glyph cell, so a label
/// can never be laid out for one size and drawn at another. The 8x8 font scales
/// by whole multiples of its cell only -- anything else lands a 1-bit glyph's
/// strokes unevenly -- so `reset()` picks the multiple nearest the requested
/// size.
/// The backend the plot layer sees, at the size the text pipeline draws it:
/// `measure`/`height`/`ascent` scale with the run, so a label can never be laid
/// out for one size and drawn at another.
///
/// It is also the backend the renderer uploads the atlas from, so it forwards
/// the whole interface: the metrics and the glyph bearings and advances are
/// scaled, while the artwork itself -- the atlas bytes, its size and its
/// revision -- is the font's own and passes through untouched.
/// It wraps a `TextBackend` and nothing more: the scale is
/// `axis_font_px / em_px()`, a plain ratio, so an anti-aliased face is drawn at
/// the size the style asked for rather than at a whole multiple of its cell,
/// and any backend -- the embedded font, a host's TrueType face -- is drawn the
/// same way. It forwards the whole interface: the metrics and the glyph
/// bearings and advances are scaled, while the artwork itself -- the atlas
/// bytes, its size and its revision -- is the backend's own and passes through
/// untouched.
class AxisFont final : public render::TextBackend {
public:
void reset(const render::BitmapFont& font, f32 requested_px) noexcept;
void reset(const render::TextBackend& font, f32 requested_px) noexcept;
f32 measure(const char* utf8) const override;
f32 height() const override;
f32 ascent() const override;
f32 em_px() const override;
/// The font's glyph at this wrapper's scale: the atlas rect is unchanged (it
/// is scale-free) and the bearing and advance are multiplied by it, so one
/// `Glyph` describes the glyph this backend draws. The result lives in the
@@ -213,7 +217,7 @@ class Renderer {
f32 scale() const noexcept { return scale_; }
private:
const render::BitmapFont* font_ = nullptr;
const render::TextBackend* font_ = nullptr;
f32 cell_px_ = 1.0f;
f32 scale_ = 1.0f;
/// `glyph()`'s scaled result, returned by pointer because the interface is
@@ -280,7 +284,10 @@ class Renderer {
void draw_measures(const Plot& plot, const PlotLayout& layout);
void draw_annotations(const Plot& plot);
render::BitmapFont font_;
/// The built-in backend the renderer owns. Every use of it goes through the
/// `TextBackend` interface -- `self`-typed nowhere -- so swapping it for
/// another face is a change to this member alone.
render::EmbeddedMonoFont default_font_;
AxisFont font_backend_;
render::Style style_;