#pragma once #include namespace plot::render { /// One glyph's atlas rectangle and metrics, in font pixels. /// /// **The rectangle is normalised, v grows downward.** `(u0, v0)` is the glyph's /// top-left atlas texel and `(u1, v1)` its bottom-right, each divided by the /// atlas' size -- so the atlas is stored with its top row first, exactly the row /// order `atlas_pixels()` hands over, and a larger `v` is a row further down. /// Text is laid out with the same handedness: a glyph's own +y runs from `v0` /// toward `v1`, which on screen is downward when the glyph is unrotated. A /// backend that rasterised its atlas bottom row first would draw every glyph /// mirrored unless it divided `v` the other way, so the convention is stated /// once, here, and every consumer -- the text shader's UVs, the vertex shader's /// second cell axis, `TextBatch` -- uses it and nothing else. /// /// `(xoff, yoff)` is the bearing from the pen to the glyph's top-left corner /// (top-left origin, font pixels) and `advance` is how far the pen moves along /// the baseline. Both are in font pixels, so a caller scaling a run multiplies /// them by the same factor it draws the atlas with. struct Glyph { f32 u0 = 0.0f; f32 v0 = 0.0f; f32 u1 = 0.0f; f32 v1 = 0.0f; f32 xoff = 0.0f; f32 yoff = 0.0f; f32 advance = 0.0f; }; /// A byte sequence that is not valid UTF-8, as reported by `next_codepoint`. It /// is deliberately not a Unicode scalar value, so it can only map to whatever a /// backend draws for "no glyph" rather than colliding with a real codepoint. inline constexpr u32 kInvalidCodepoint = 0xFFFFFFFFu; /// Decodes one UTF-8 sequence from `p` and advances `p` past it -- one byte when /// the sequence is malformed, so a damaged label still terminates. /// /// It belongs to this interface rather than to a font because it is what keeps a /// backend's `measure()` and the glyphs the pipeline stages for the same string /// in step: both walk the string with this decoder, so neither can disagree with /// the other about where a codepoint starts. u32 next_codepoint(const char*& p) noexcept; /// The font a `Plot` measures with and the renderer draws with: metrics, plus the /// coverage atlas the text pipeline samples. /// /// Text is passed as UTF-8, not as a byte-per-character string: `plot::format_si` /// emits U+00B5, U+00B0 and U+03A9 for micro, degree and ohm, and an /// implementation that ignores that prints mojibake. /// /// The atlas is the backend's own: it owns the pixels, the glyph table that /// describes them and the `revision()` that says when they changed. The renderer /// uploads whatever the backend exposes and re-uploads when the revision moves, /// so a backend can be swapped -- or re-baked at another size -- without the /// renderer knowing anything about fonts. `measure()`/`height()`/`ascent()` are /// in the atlas' font pixels: the pipeline scales them, never re-rasterises. /// /// **One atlas, one cell size.** A staged glyph carries the *origin* of its rect /// and the draw carries the rect's size, once, for every instance in it (see /// `TextProgram::set_glyph_uv`) -- which is what keeps a glyph at six floats while /// still letting it be rotated. So every glyph a backend publishes must occupy a /// rect of the same size: its cell, with the ink inside it at `(xoff, yoff)`. A /// proportional face therefore bakes each glyph into a cell wide enough for the /// widest advance and spends the atlas area rather than the correctness; a /// backend whose rects differ in size would have its glyphs read through /// whichever rect was staged first. class TextBackend { public: TextBackend() noexcept = default; TextBackend(const TextBackend&) = default; TextBackend& operator=(const TextBackend&) = default; virtual ~TextBackend() = default; /// Width of `utf8` in the same units as `height()` (font pixels for a font /// atlas). Zero for the empty string. virtual f32 measure(const char* utf8) const = 0; /// Line height: the cell a single line of text occupies. virtual f32 height() const = 0; /// Distance from the top of the cell to the baseline, so a caller can align /// text bottoms instead of cell bottoms. virtual f32 ascent() const = 0; /// The glyph for `codepoint`, or null when this backend has none -- the caller /// then draws nothing for it (a backend that wants a visible missing-glyph box /// publishes one as the glyph for the codepoints it cannot render). The /// returned pointer stays valid for the backend's lifetime, so callers may hold /// it across a frame. virtual const Glyph* glyph(u32 codepoint) const = 0; /// 8-bit single-channel coverage, row-major, `atlas_width() * atlas_height()` /// bytes, top row first. Uploaded as GL_R8. Null is legal and means "no text". virtual const u8* atlas_pixels() const = 0; /// Atlas size in texels. Zero width or height means "no text", like a null /// `atlas_pixels()`. virtual u32 atlas_width() const = 0; virtual u32 atlas_height() const = 0; /// Bumped whenever `atlas_pixels()` content changes, so the renderer knows to /// re-upload instead of trusting its cache. Must not change otherwise -- a /// revision that moves on its own would re-upload the same bytes every frame -- /// and any change counts, so a backend may re-bake at a new size and keep /// counting up. virtual u32 revision() const = 0; }; } // namespace plot::render