Skip to main content

euv_chart/text/
struct.rs

1use super::*;
2
3/// A cache of rasterised glyphs, packed into a single texture.
4///
5/// The GPU backends cannot draw text directly: WebGPU and WebGL2 have no
6/// text API. The atlas solves that by rasterising each glyph once into
7/// an offscreen canvas and then drawing the glyphs as textured quads. The
8/// canvas is handed to the GPU as a texture source, so no pixel read-back
9/// is ever needed.
10///
11/// The Canvas2D backend does not use the atlas at all — it has a real
12/// text API — but it shares the measurer, so the three backends lay text
13/// out identically.
14#[derive(Clone, Data, Debug)]
15pub struct GlyphAtlas {
16    /// The canvas the glyphs are rasterised into.
17    #[get(pub(crate))]
18    #[get_mut(pub(crate))]
19    #[set(pub(crate))]
20    pub(crate) canvas: HtmlCanvasElement,
21    /// The 2D context used to rasterise glyphs.
22    #[get(pub(crate))]
23    #[get_mut(pub(crate))]
24    #[set(pub(crate))]
25    pub(crate) context: CanvasRenderingContext2d,
26    /// The rasterised glyphs, keyed by character and font.
27    #[get(pub(crate))]
28    #[get_mut(pub(crate))]
29    #[set(pub(crate))]
30    pub(crate) entries: HashMap<GlyphKey, GlyphEntry>,
31    /// The horizontal position where the next glyph is placed.
32    #[get(pub(crate), type(copy))]
33    #[get_mut(pub(crate))]
34    #[set(pub(crate))]
35    pub(crate) cursor_x: f32,
36    /// The vertical position of the current shelf.
37    #[get(pub(crate), type(copy))]
38    #[get_mut(pub(crate))]
39    #[set(pub(crate))]
40    pub(crate) cursor_y: f32,
41    /// The height of the current shelf.
42    #[get(pub(crate), type(copy))]
43    #[get_mut(pub(crate))]
44    #[set(pub(crate))]
45    pub(crate) shelf_height: f32,
46    /// The width of the atlas, in pixels.
47    #[get(pub(crate), type(copy))]
48    #[get_mut(pub(crate))]
49    #[set(pub(crate))]
50    pub(crate) width: u32,
51    /// The height of the atlas, in pixels.
52    #[get(pub(crate), type(copy))]
53    #[get_mut(pub(crate))]
54    #[set(pub(crate))]
55    pub(crate) height: u32,
56    /// Whether the atlas changed since the last upload.
57    #[get(pub(crate), type(copy))]
58    #[get_mut(pub(crate))]
59    #[set(pub(crate))]
60    pub(crate) dirty: bool,
61}
62
63/// One positioned glyph, ready to be drawn as a textured quad.
64#[derive(Clone, Copy, Debug, Default, PartialEq)]
65pub struct GlyphQuad {
66    /// The left edge of the glyph's bitmap, in canvas pixels.
67    pub x: f32,
68    /// The top edge of the glyph's bitmap, in canvas pixels.
69    pub y: f32,
70    /// The width of the glyph's bitmap, in canvas pixels.
71    pub width: f32,
72    /// The height of the glyph's bitmap, in canvas pixels.
73    pub height: f32,
74    /// The horizontal texture coordinate of the glyph's top-left corner.
75    pub u: f32,
76    /// The vertical texture coordinate of the glyph's top-left corner.
77    pub v: f32,
78    /// The width of the glyph's bitmap in texture coordinates.
79    pub u_size: f32,
80    /// The height of the glyph's bitmap in texture coordinates.
81    pub v_size: f32,
82    /// The packed colour of the glyph, as `0xRRGGBBAA`.
83    ///
84    /// A rich-text run carries its own colour; a plain run leaves the
85    /// colour at zero, which means "use the colour of the label".
86    pub color: u32,
87}
88
89/// One line of a shaped text run.
90#[derive(Clone, Debug, Default, PartialEq)]
91pub struct TextLine {
92    /// The text of the line.
93    pub text: String,
94    /// The horizontal position of the line's left edge, in canvas pixels.
95    pub left: f64,
96    /// The vertical position of the line's baseline, in canvas pixels.
97    pub baseline: f64,
98    /// The advance width of the line.
99    pub width: f64,
100}
101
102/// A shaped run of text: the glyph quads that draw it, plus the metrics
103/// of the run as a whole.
104#[derive(Clone, Debug, Default, PartialEq)]
105pub struct TextRun {
106    /// The glyph quads, in reading order.
107    pub quads: Vec<GlyphQuad>,
108    /// The lines of the run, with the position of each baseline.
109    pub lines: Vec<TextLine>,
110    /// The advance width of the whole run.
111    pub width: f64,
112    /// The height of the whole run.
113    pub height: f64,
114    /// The distance from the run's top to the first baseline.
115    pub ascent: f64,
116    /// The distance from the last baseline down to the bottom of the
117    /// lowest glyph.
118    pub descent: f64,
119    /// The distance between two consecutive baselines.
120    pub line_height: f64,
121    /// The number of lines in the run.
122    pub line_count: usize,
123}
124
125/// Combines the measurer and the atlas into a text shaper.
126///
127/// The shaper is the single place where text is turned into positioned
128/// glyphs, so the Canvas2D backend and the GPU backends lay text out
129/// identically — the same label lands on the same pixel no matter which
130/// backend is in use.
131#[derive(Clone, Data, Debug)]
132pub struct TextShaper {
133    /// The measurer used to size the text.
134    pub measurer: TextMeasurer,
135    /// The atlas the glyphs are rasterised into.
136    pub atlas: GlyphAtlas,
137}
138
139/// The measured metrics of a text run.
140///
141/// All values are in CSS pixels, for the font the run was measured with.
142#[derive(Clone, Copy, Debug, Default, PartialEq)]
143pub struct TextMetrics {
144    /// The advance width of the run.
145    pub width: f64,
146    /// The height of the run's bounding box, from the top of the ascent
147    /// to the bottom of the descent.
148    pub height: f64,
149    /// The distance from the baseline up to the top of the tallest
150    /// glyph.
151    pub ascent: f64,
152    /// The distance from the baseline down to the bottom of the lowest
153    /// glyph.
154    pub descent: f64,
155    /// The distance between two consecutive baselines.
156    pub line_height: f64,
157}
158
159/// A glyph rendered into the atlas.
160#[derive(Clone, Copy, Debug, Default, PartialEq)]
161pub struct GlyphEntry {
162    /// The horizontal texture coordinate of the glyph's left edge, from
163    /// `0.0` to `1.0`.
164    pub u: f32,
165    /// The vertical texture coordinate of the glyph's top edge, from `0.0`
166    /// to `1.0`.
167    pub v: f32,
168    /// The width of the glyph's bitmap, in atlas pixels.
169    pub width: f32,
170    /// The height of the glyph's bitmap, in atlas pixels.
171    pub height: f32,
172    /// The horizontal distance from the pen position to the left edge of
173    /// the glyph's bitmap, in pixels.
174    pub bearing_x: f32,
175    /// The vertical distance from the baseline down to the top edge of
176    /// the glyph's bitmap, in pixels.
177    pub bearing_y: f32,
178    /// The advance width of the glyph, in pixels.
179    pub advance: f32,
180}
181
182/// The key that identifies one glyph in the atlas.
183///
184/// The font string is part of the key because the same character renders
185/// differently in different fonts, and the atlas holds one rasterisation
186/// per combination.
187#[derive(Clone, Debug, Eq, Hash, PartialEq)]
188pub struct GlyphKey {
189    /// The character the glyph stands for.
190    pub character: char,
191    /// The CSS `font` shorthand the glyph was rasterised with.
192    pub font: String,
193}
194
195/// Measures text with a 2D canvas context, caching the results.
196///
197/// Every label in a chart is measured at least once per layout, and
198/// layout runs on every data change. Measuring is a cross-language call
199/// into the browser, so the results are cached by text and font: a chart
200/// with a thousand axis labels makes a thousand calls the first time and
201/// none afterwards.
202#[derive(Clone, Data)]
203pub struct TextMeasurer {
204    /// The offscreen context used for measuring.
205    ///
206    /// `None` when the host has no DOM; the measurer then falls back to
207    /// an approximation, which is what lets the layout run on a server
208    /// and in a native build.
209    #[get(pub(crate))]
210    #[get_mut(pub(crate))]
211    #[set(pub(crate))]
212    pub(crate) context: Option<CanvasRenderingContext2d>,
213    /// The cache of already-measured runs.
214    #[get(pub(crate))]
215    #[get_mut(pub(crate))]
216    #[set(pub(crate))]
217    pub(crate) cache: Rc<RefCell<HashMap<(String, String), TextMetrics>>>,
218}