feat(text): optional vendored stb_truetype backend for runtime external fonts
This commit is contained in:
1 parent
daff60e3f0
commit
14d664a246
12 files changed
+6511
-19
No files matched your search
+30
-10
@@ -113,7 +113,8 @@ 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 embedded font; a plot drawn by a Renderer measures the labels it draws.\n",
|
||||
"own font; a plot drawn by a Renderer measures the labels it draws -- install the "
|
||||
"face with Renderer::set_text_backend() instead.\n",
|
||||
static_cast<const void*>(previous));
|
||||
}
|
||||
#endif
|
||||
@@ -185,6 +186,23 @@ u32 Renderer::AxisFont::revision() const { return font_ != nullptr ? font_->revi
|
||||
|
||||
// ------------------------------------------------------------------ init
|
||||
|
||||
void Renderer::set_text_backend(const render::TextBackend* font) noexcept {
|
||||
const render::TextBackend* next = font != nullptr ? font : &default_font_;
|
||||
if (next == font_) {
|
||||
return; // the same face: no upload, and nothing changed
|
||||
}
|
||||
// A replacement is a different *source* of atlas bytes, which `revision()`
|
||||
// cannot express on its own: two faces may legitimately number their first bake
|
||||
// the same. So the cache is dropped rather than trusted. Assigning a fresh
|
||||
// atlas releases the texture the last frame sampled and forgets the revision
|
||||
// that was uploaded, which makes the next `begin_frame_text()` upload whatever
|
||||
// the new backend exposes, at whatever size it says. That is one upload on the
|
||||
// frame a host deliberately swapped the font -- and it is the direction that
|
||||
// cannot draw the old face's glyphs under the new face's rects.
|
||||
atlas_ = render::TextAtlas{};
|
||||
font_ = next;
|
||||
}
|
||||
|
||||
bool Renderer::init(Status* st) noexcept {
|
||||
if (!gl::available()) {
|
||||
return fail(st, ErrorCode::Unsupported,
|
||||
@@ -202,7 +220,7 @@ bool Renderer::init(Status* st) noexcept {
|
||||
const render::UploadMode mode =
|
||||
persistent ? render::UploadMode::PersistentMapped : render::UploadMode::Orphan;
|
||||
|
||||
font_backend_.reset(default_font_, style_.device(style_.axis_font_px));
|
||||
font_backend_.reset(*font_, style_.device(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
|
||||
@@ -292,7 +310,7 @@ bool Renderer::begin_frame_text() noexcept {
|
||||
// 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(default_font_, style_.device(style_.axis_font_px));
|
||||
font_backend_.reset(*font_, style_.device(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).
|
||||
@@ -310,10 +328,12 @@ bool Renderer::begin_frame_text() noexcept {
|
||||
void Renderer::prepare_plot(Plot& plot) {
|
||||
#ifndef NDEBUG
|
||||
// The renderer owns the text of the frames it draws: every label goes through
|
||||
// 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()).
|
||||
// the text pipeline in `font_` -- the embedded face, or whatever a host put
|
||||
// there with Renderer::set_text_backend() -- so the plot is handed this
|
||||
// backend's metrics whatever it was given. A backend installed on the *Plot* 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()).
|
||||
if (const render::TextBackend* previous = plot.text_backend();
|
||||
previous != nullptr && previous != &font_backend_) {
|
||||
warn_replaced_text_backend(previous);
|
||||
@@ -573,17 +593,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(), default_font_);
|
||||
return glyphs_.add_text(utf8, to_viewport(pos_px), font_backend_.scale(), *font_);
|
||||
}
|
||||
|
||||
f32 Renderer::clipped_text_width(const char* utf8, f32 budget_px, Vec2 axis) const {
|
||||
return walk_text_clipped(Vec2{}, utf8, budget_px, default_font_, font_backend_.scale(), axis,
|
||||
return walk_text_clipped(Vec2{}, utf8, budget_px, *font_, font_backend_.scale(), axis,
|
||||
[](Vec2, const render::Glyph&) {});
|
||||
}
|
||||
|
||||
f32 Renderer::stage_text_clipped(Vec2 pos_px, const char* utf8, f32 budget_px, Vec2 axis) {
|
||||
const f32 scale = font_backend_.scale();
|
||||
return walk_text_clipped(to_viewport(pos_px), utf8, budget_px, default_font_, scale, axis,
|
||||
return walk_text_clipped(to_viewport(pos_px), utf8, budget_px, *font_, scale, axis,
|
||||
[this, scale, axis](Vec2 pen, const render::Glyph& glyph) {
|
||||
glyphs_.add_glyph(pen, glyph, scale, axis);
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user