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}