Files
plotter/include/plot/render/embedded_mono_font.hpp
Martino Ferrari ed3a8ce645 licence: MIT LICENSE and SPDX identifiers in every first-party file
The repository had no licence at all, so every source was all-rights-reserved
by default. Add the verbatim MIT text at LICENSE (Copyright (c) 2026 Martino
Ferrari) and a two-line SPDX header to each of the 113 first-party sources --
48 include/plot, 26 src, 10 adapters, 5 examples, 21 tests, 3 tools -- so the
grant travels with each file rather than relying on the root document alone.

Scripts keep their shebang on line 1; the SPDX lines follow it. Genuinely
vendored code is untouched: third_party/stb_truetype.h carries no header of
ours. src/render/embedded_mono_font.cpp is generated, so tools/fonts/gen_atlas.py
emit() now writes the two lines into its header comment and the file was
regenerated -- gen_atlas.py --check passes and the regeneration adds only those
comment lines to the previously committed file.

README's provenance section now states MIT and links LICENSE.

No code, test or public API change.
2026-09-28 22:13:40 +02:00

118 lines
5.4 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#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