denise_text/source.rs
1//! What a font has to provide, and what it says about a glyph.
2
3use alloc::vec::Vec;
4
5use denise::Size;
6
7/// Identifies a font within one [`TextEngine`](crate::TextEngine).
8///
9/// Small and `Copy` because it ends up in every glyph cache key, and because M5's
10/// C ABI has to carry it across `extern "C"` without a pointer.
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
12pub struct FontId(pub u16);
13
14impl FontId {
15 /// The face a style that names none is drawn in.
16 ///
17 /// **Not a face itself — a redirection.** Every [`TextStyle`] this workspace
18 /// builds without naming a font carries this, and it resolves through
19 /// [`TextEngine::default_font`](crate::TextEngine::default_font) at glyph
20 /// time. Until somebody calls
21 /// [`set_default_font`](crate::TextEngine::set_default_font) it is the
22 /// built-in bitmap face, which is what an embedded board with no fonts
23 /// installed gets and why it is always registered first.
24 ///
25 /// [`TextStyle`]: crate::TextStyle
26 pub const DEFAULT: Self = Self(0);
27}
28
29/// Identifies a glyph *within one font*.
30///
31/// Not a `char`, and the distinction is the whole reason the shaping tier can
32/// exist. A source that maps characters straight to glyphs uses the code point
33/// here; a source that shapes uses the font's own glyph index, because after
34/// shaping there is no longer a one-to-one correspondence — `fi` may be one
35/// glyph, `é` may be two, and an Arabic letter is a different glyph in the middle
36/// of a word than at its end. A cache keyed by `char` cannot represent any of
37/// that, so this one is not.
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct GlyphId(pub u32);
40
41impl GlyphId {
42 /// The identity a character-mapped source uses.
43 #[inline]
44 pub const fn from_char(ch: char) -> Self {
45 Self(ch as u32)
46 }
47
48 /// The character this came from, for a source that maps them directly.
49 #[inline]
50 pub const fn as_char(self) -> Option<char> {
51 char::from_u32(self.0)
52 }
53}
54
55/// One glyph, positioned by whatever laid the line out.
56#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
57pub struct ShapedGlyph {
58 /// Which glyph.
59 pub id: GlyphId,
60 /// Pen position, relative to the start of the run.
61 pub x: i32,
62 /// Baseline offset, relative to the run's baseline. Non-zero only for a
63 /// source that positions marks vertically.
64 pub y: i32,
65}
66
67/// Vertical metrics of a font at one size, in pixels.
68///
69/// Ascent and descent are both positive distances *from* the baseline, which is
70/// the convention that stops the sign of `descent` being a coin toss.
71#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
72pub struct FontMetrics {
73 /// How far the tallest glyph rises above the baseline.
74 pub ascent: i32,
75 /// How far the deepest glyph falls below it.
76 pub descent: i32,
77 /// Extra space the designer asked for between lines.
78 pub line_gap: i32,
79}
80
81impl FontMetrics {
82 /// Baseline-to-baseline distance.
83 #[inline]
84 pub const fn line_height(&self) -> i32 {
85 self.ascent + self.descent + self.line_gap
86 }
87}
88
89/// Where one glyph sits relative to the pen, in pixels.
90///
91/// Following FreeType: `bearing_x` is rightwards from the pen to the mask's left
92/// edge and `bearing_y` is **upwards** from the baseline to its top edge. So a
93/// glyph is drawn at `(pen.x + bearing_x, baseline - bearing_y)`, and a descender
94/// is the case where `bearing_y` is smaller than the mask is tall.
95#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
96pub struct GlyphMetrics {
97 /// How far the pen moves after this glyph.
98 pub advance: i32,
99 /// Rightwards from the pen to the mask's left edge.
100 pub bearing_x: i32,
101 /// Upwards from the baseline to the mask's top edge.
102 pub bearing_y: i32,
103 /// Extent of the coverage mask. Zero for a glyph with no ink, such as a space.
104 pub size: Size,
105}
106
107impl GlyphMetrics {
108 /// Returns `true` if the glyph has no coverage to draw.
109 #[inline]
110 pub const fn is_blank(&self) -> bool {
111 self.size.width == 0 || self.size.height == 0
112 }
113}
114
115/// One rasterised glyph, borrowed from whatever scratch space the source used.
116#[derive(Clone, Copy, Debug)]
117pub struct Rasterised<'a> {
118 /// Where it sits and how far the pen moves.
119 pub metrics: GlyphMetrics,
120 /// Coverage, `0` transparent to `255` solid, row-major.
121 pub coverage: &'a [u8],
122 /// Bytes per row of `coverage`, at least `metrics.size.width`.
123 pub stride: usize,
124}
125
126/// A thing that can lay out, measure and rasterise glyphs.
127///
128/// [`shape`](GlyphSource::shape) is the layout step and has a default that
129/// accumulates per-character advances — correct for every script where a
130/// character is a glyph. A backend that can do better overrides it. Keeping
131/// shaping *on this trait* rather than beside it is what lets the cache and the
132/// draw path be written once: they deal in [`GlyphId`]s and never need to know
133/// whether a shaper produced them.
134pub trait GlyphSource {
135 /// Human-readable name, for logging which font a panel actually loaded.
136 fn name(&self) -> &str;
137
138 /// Vertical metrics at `size_px`.
139 fn metrics(&self, size_px: u16) -> FontMetrics;
140
141 /// The glyph a character maps to, or `None` if this source has none.
142 ///
143 /// Only meaningful for text that needs no shaping. Use [`GlyphSource::shape`]
144 /// for anything else, and note that a source which shapes may return `None`
145 /// here for a character it can nonetheless render in context.
146 fn glyph_id(&self, ch: char) -> Option<GlyphId> {
147 self.contains(ch).then(|| GlyphId::from_char(ch))
148 }
149
150 /// Metrics for one glyph, without rasterising it.
151 ///
152 /// Used for measurement, which happens far more often than drawing: a label
153 /// that has not changed is measured on every layout pass and drawn on none.
154 fn glyph_metrics(&mut self, glyph: GlyphId, size_px: u16) -> Option<GlyphMetrics>;
155
156 /// Rasterises one glyph.
157 ///
158 /// Returning a borrow of the source's own scratch buffer rather than filling a
159 /// caller's slice keeps this to one call, and lets a backend that already has
160 /// the bitmap hand it over without copying it twice.
161 fn rasterise(&mut self, glyph: GlyphId, size_px: u16) -> Option<Rasterised<'_>>;
162
163 /// Turns a string into positioned glyphs, appended to `out`.
164 ///
165 /// **Only called when [`can_shape`](GlyphSource::can_shape) is `true`.** A
166 /// source that maps characters to glyphs one for one does not implement this:
167 /// the engine lays those out itself, taking each advance from the glyph cache
168 /// so that measuring a label a hundred times costs one rasterisation rather
169 /// than a hundred outline computations.
170 ///
171 /// Returns the run's total advance, which is its width.
172 fn shape(&mut self, text: &str, size_px: u16, out: &mut Vec<ShapedGlyph>) -> i32 {
173 let _ = (text, size_px, out);
174 0
175 }
176
177 /// Returns `true` if this source lays out runs itself through
178 /// [`shape`](GlyphSource::shape), rather than one glyph per character.
179 ///
180 /// Worth logging at startup: a panel that needs ligatures and got a source
181 /// that cannot provide them looks subtly wrong rather than obviously broken.
182 fn can_shape(&self) -> bool {
183 false
184 }
185
186 /// The glyph to draw for a character this source does not have.
187 ///
188 /// `None` drops the character silently, which is almost never what anyone
189 /// wants — a visible box is a defect somebody will report.
190 fn fallback_id(&self, ch: char) -> Option<GlyphId> {
191 let _ = ch;
192 None
193 }
194
195 /// Returns `true` if this source has a glyph of its own for `ch`, as opposed
196 /// to a fallback box.
197 fn contains(&self, ch: char) -> bool;
198
199 /// Sizes this source can actually produce, or `None` if it is continuous.
200 ///
201 /// A bitmap font can only be scaled by whole numbers; asking it for 13 px and
202 /// silently getting 16 is the sort of thing that makes a layout wrong by three
203 /// pixels for reasons nobody can find. This makes the snapping visible.
204 fn snap_size(&self, size_px: u16) -> u16 {
205 size_px
206 }
207}