115 lines
5.3 KiB
C++
115 lines
5.3 KiB
C++
#pragma once
|
|
#include <plot/core/types.hpp>
|
|
#include <plot/math/vec2.hpp>
|
|
#include <plot/render/text.hpp>
|
|
|
|
namespace plot::render {
|
|
|
|
/// The renderer's built-in text backend: an anti-aliased greyscale atlas of
|
|
/// Adwaita Mono, baked at authoring time and committed as data.
|
|
///
|
|
/// There is no font file, FreeType or third-party rasteriser behind this: the
|
|
/// coverage bytes and the glyph table are generated by `tools/fonts/gen_atlas.py`
|
|
/// into `src/render/embedded_mono_font.cpp`, so `plot_core` stays dependency-free
|
|
/// and nothing rasterises at build or run time. That file carries the provenance
|
|
/// -- source face, its version, the em size, the supersample factor and the
|
|
/// coverage set -- and `gen_atlas.py --check` fails if it has drifted from its
|
|
/// input. Adwaita Mono is SIL OFL 1.1 (a copy is committed at
|
|
/// `licences/OFL-1.1.txt`); the class is named neutrally because OFL reserves
|
|
/// the font name for modified redistributions.
|
|
///
|
|
/// **One cell, every glyph.** The atlas is a fixed-cell grid -- `cell_uv()` is
|
|
/// the contract, and the staging path asserts it per glyph. Each cell is the
|
|
/// face's maximum advance wide and its ascent-to-descent extent tall, and every
|
|
/// glyph is drawn into its cell with its bearing preserved, never trimmed to its
|
|
/// ink. Because the face is monospaced the pen advances by the same amount for
|
|
/// every glyph, which is the face's own `hmtx` advance and not an assumption.
|
|
///
|
|
/// A codepoint outside the coverage set draws the baked fallback box, so a label
|
|
/// the atlas cannot spell is visible rather than silently blank.
|
|
///
|
|
/// This is the only backend the core library ships. The one that bakes a font
|
|
/// file at run time belongs to the optional target -- `plot/text/true_type_font.hpp`,
|
|
/// deliberately not part of `include/plot/plot.hpp` -- and no public header
|
|
/// outside `plot/text/` names what rasterises it, which is the isolation that
|
|
/// target exists to keep.
|
|
class EmbeddedMonoFont final : public TextBackend {
|
|
public:
|
|
/// The size the atlas was baked at, in font pixels, and the coverage samples
|
|
/// per texel. Repeated here so a caller can reason about the artwork without
|
|
/// opening the generated table; the generated translation unit is the
|
|
/// authority and pins the same values.
|
|
static constexpr u32 kEmPx = 13;
|
|
static constexpr u32 kSupersample = 4;
|
|
|
|
/// The codepoint value `codepoint_of_slot()` reports for the fallback slot. It
|
|
/// is deliberately not a Unicode scalar, so it can never collide with a
|
|
/// codepoint the atlas really holds.
|
|
static constexpr u32 kFallbackCodepoint = 0xFFFFFFFFu;
|
|
|
|
EmbeddedMonoFont() noexcept = default;
|
|
|
|
/// Width of `utf8` in font pixels: the sum of the glyphs' advances, zero for
|
|
/// the empty string and for null. Allocates nothing.
|
|
f32 measure(const char* utf8) const override;
|
|
|
|
/// Cell height in font pixels: one line of text occupies this many pixels.
|
|
f32 height() const override;
|
|
/// Top of the cell to the baseline, in font pixels.
|
|
f32 ascent() const override;
|
|
/// `kEmPx`: the size the face was baked at. The renderer scales a run by
|
|
/// `Style::device(Style::axis_font_px) / em_px()` -- the style's logical em in
|
|
/// device pixels over this em -- so the same backend draws the size the style
|
|
/// asked for, on any DPI, whatever it was baked at.
|
|
f32 em_px() const override;
|
|
|
|
/// The glyph for `codepoint`: its atlas cell as a normalised rect, no extra
|
|
/// bearing -- the bearing is inside the cell's ink -- and its advance. Never
|
|
/// null: anything outside the coverage set maps to the fallback box.
|
|
const Glyph* glyph(u32 codepoint) const override;
|
|
|
|
/// `(kCellW / atlas_width(), kCellH / atlas_height())`: the cell every glyph's
|
|
/// rect is.
|
|
Vec2 cell_uv() const override;
|
|
|
|
/// The packed atlas, GL_R8-ready, rows of `atlas_width()` bytes, top row
|
|
/// first, one coverage byte per texel. Never null.
|
|
const u8* atlas_pixels() const override;
|
|
u32 atlas_width() const override;
|
|
u32 atlas_height() const override;
|
|
|
|
/// Always zero: the atlas is a compile-time constant, so the renderer uploads
|
|
/// it once and never re-uploads.
|
|
u32 revision() const override;
|
|
|
|
/// Advance of a decoded codepoint, in font pixels. A codepoint outside the
|
|
/// coverage set reports the fallback box's advance.
|
|
static f32 advance_codepoint(u32 codepoint) noexcept;
|
|
|
|
/// Slot a codepoint maps to: printable ASCII is a direct index, the bakes SI
|
|
/// and dash codepoints are a short switch, and everything else is the fallback
|
|
/// box -- so a missing glyph is always visible, never blank.
|
|
static u32 glyph_index(u32 codepoint) noexcept;
|
|
|
|
/// Number of cells in the atlas, the fallback included.
|
|
static u32 glyph_count() noexcept;
|
|
|
|
/// The codepoint a slot was baked for, or `kFallbackCodepoint` for the
|
|
/// fallback slot. Out-of-range slots report the fallback too.
|
|
static u32 codepoint_of_slot(u32 slot) noexcept;
|
|
|
|
/// The fixed cell size, in atlas texels. Every glyph's rect is exactly this.
|
|
static u32 cell_width() noexcept;
|
|
static u32 cell_height() noexcept;
|
|
|
|
/// Texel origin of a slot's cell in the atlas; `glyph()` gives the same
|
|
/// rectangle normalised, which is what the text pipeline stages.
|
|
static Vec2 atlas_origin(u32 slot) noexcept;
|
|
|
|
/// The packed atlas image: the bytes `atlas_pixels()` returns, as a
|
|
/// compile-time table. Never null.
|
|
static const u8* atlas_image() noexcept;
|
|
};
|
|
|
|
} // namespace plot::render
|