fix(text): carry a glyph's negative bearing and harden the bake's extents
This commit is contained in:
1 parent
b0f9fbbb35
commit
d853db62ae
5 files changed
+470
-60
No files matched your search
@@ -98,9 +98,12 @@ class Renderer {
|
||||
/// which is the same lifetime rule `Plot::set_text_backend()` states.
|
||||
void set_text_backend(const render::TextBackend* font) noexcept;
|
||||
|
||||
/// The backend the last frame was drawn with, or the embedded face when the
|
||||
/// host installed none. The renderer's own wrapper while a frame is in flight,
|
||||
/// so a caller reads this after `draw()` to see what the plot was handed.
|
||||
/// The backend this renderer *borrows*: whatever `set_text_backend()` was given,
|
||||
/// or the embedded face when nothing was installed. It is deliberately **not**
|
||||
/// the `AxisFont` wrapper the renderer builds around it -- after a `draw()`,
|
||||
/// `Plot::text_backend()` reads back that wrapper, because the wrapper is what
|
||||
/// the plot measured with -- so a caller checking or re-pointing the face itself
|
||||
/// asks here.
|
||||
const render::TextBackend* text_backend() const noexcept { return font_; }
|
||||
|
||||
/// Starts a frame: records `fb_w` x `fb_h` for the projection, flips
|
||||
|
||||
@@ -44,6 +44,18 @@ namespace plot::text {
|
||||
/// face's own, so a proportional run advances per glyph while every rect stays
|
||||
/// one size.
|
||||
///
|
||||
/// **No ink is trimmed away, in either direction.** The cell is as wide as the
|
||||
/// widest advance, the widest ink *to the right* of the pen, **and** the deepest
|
||||
/// ink to its left: an italic glyph reaches back over the pen that advances it
|
||||
/// (half the italic faces on a stock Linux box do, `U+006A` being the usual
|
||||
/// offender), so the cell sits a texel or two to the right of the pen and
|
||||
/// `Glyph::xoff` is the matching non-positive bearing -- the pipeline places the
|
||||
/// cell corner at `pen + xoff`, which is exactly what makes a negative bearing
|
||||
/// expressible. Bounding ink on one side and clipping it on the other would be an
|
||||
/// inconsistency, and a silent one: the bitmap `stb_truetype` hands over is
|
||||
/// already trimmed to its ink, so a cell that could not hold it would drop the
|
||||
/// columns it did not fit without any error to report.
|
||||
///
|
||||
/// **What is rejected, and how.** `from_file()` parses and bakes first and
|
||||
/// installs the result only on success, so a failure leaves the object exactly as
|
||||
/// it was -- a host can try a file, keep the font it already had, and read why
|
||||
@@ -53,11 +65,17 @@ namespace plot::text {
|
||||
/// is `bool` plus out-params, the same one every other fallible `plot` call uses,
|
||||
/// and no `Result<T>` is introduced for a font.
|
||||
///
|
||||
/// **Trust.** stb_truetype's own header says it does no range checking of the
|
||||
/// offsets in a font file, so a file crafted to be hostile can read memory. This
|
||||
/// class parses the host's fonts, not untrusted input: a *random* file is
|
||||
/// rejected, a *malicious* one is out of scope, and the same is true of
|
||||
/// everything else in `third_party/`.
|
||||
/// **Trust, and what this class does not check.** The file is assumed to be well
|
||||
/// formed. What *is* checked here, before the vendored parser sees a byte, is that
|
||||
/// the input is an sfnt (or a collection) whose table directory fits inside the
|
||||
/// bytes read -- because that parser's own table scan reads `numTables` records
|
||||
/// whatever the file's length is (its header says so in capitals, and warns
|
||||
/// against untrusted files). Nothing *inside* those tables is validated by us:
|
||||
/// stb_truetype does no range checking of the offsets it finds there, so a font
|
||||
/// crafted to be hostile can still read memory, and a font this class accepts is
|
||||
/// not a font that has been vetted. Untrusted fonts are out of scope: bake fonts
|
||||
/// you or the user trust, and treat a font from the network the way you would
|
||||
/// treat any other parser input from it.
|
||||
///
|
||||
/// **Where it lives.** `plot_core` does not provide this class: it is built by
|
||||
/// the optional `plot_font_ttf` target, and this header is deliberately absent
|
||||
@@ -90,10 +108,12 @@ class TrueTypeFont final : public render::TextBackend {
|
||||
/// another file.
|
||||
///
|
||||
/// False with `*st` filled in (and `*out` untouched) for a path that is not a
|
||||
/// readable regular file, a file the parser cannot accept, a non-positive or
|
||||
/// non-finite `px`, and a bake in which no requested codepoint produced ink --
|
||||
/// the last is the `tools/fonts/gen_atlas.py` rule, where a blank cell that
|
||||
/// nobody notices is exactly the failure this feature exists to remove.
|
||||
/// readable regular file, a file the parser cannot accept, a non-positive,
|
||||
/// non-finite or absurdly large `px` (an em beyond a million pixels is refused
|
||||
/// as out of range, like a coverage set whose atlas would not fit -- both before
|
||||
/// anything is allocated), and a bake in which no requested codepoint produced
|
||||
/// ink: a bake of nothing but blank cells and the fallback box is refused rather
|
||||
/// than shipped, the rule `tools/fonts/gen_atlas.py` applies at authoring time.
|
||||
/// `*out` must not be null. It allocates, so a failure to allocate is reported
|
||||
/// as `ErrorCode::OutOfMemory` rather than thrown.
|
||||
static bool from_file(const char* utf8_path, f32 px, TrueTypeFont* out, Status* st = nullptr);
|
||||
@@ -140,11 +160,15 @@ class TrueTypeFont final : public render::TextBackend {
|
||||
f32 em_px() const override;
|
||||
|
||||
/// The glyph for `codepoint`: its cell as a normalised rect (the same rect for
|
||||
/// every glyph -- the fixed-cell contract), its bearing into the cell -- zero,
|
||||
/// because the pen starts at the cell's left edge and the ink is placed inside
|
||||
/// it -- and the face's advance. Never null once a face is loaded: a codepoint
|
||||
/// outside the coverage maps to the fallback box, so a missing glyph is visible
|
||||
/// rather than blank. Null for a backend with no face.
|
||||
/// every glyph -- the fixed-cell contract), its bearing, and the face's advance.
|
||||
/// The bearing is `xoff = -shift`, zero for a face whose ink stays right of
|
||||
/// every pen, and negative when some covered glyph reaches back over it -- the
|
||||
/// cell corner goes at `pen + xoff`, which is what keeps that overhang inside
|
||||
/// the cell instead of clipped (`yoff` is always zero: the baseline is
|
||||
/// `ascent()` below the cell's top, and no glyph's ink reaches above it
|
||||
/// unaccounted for). Never null once a face is loaded: a codepoint outside the
|
||||
/// coverage maps to the fallback box, so a missing glyph is visible rather than
|
||||
/// blank. Null for a backend with no face.
|
||||
const render::Glyph* glyph(u32 codepoint) const override;
|
||||
|
||||
/// `(cell_width() / atlas_width(), 1)` -- the atlas is a single row of cells.
|
||||
|
||||
Reference in new issue
Block a user