Skip to main content

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}