Skip to main content

pfx_input/glyph/
mod.rs

1pub mod font;
2pub mod keycap;
3pub mod shape;
4
5use std::collections::{BTreeMap, BTreeSet};
6
7use pfx_text::{Atlas, Baseline, Error, Representation, Style, TextEngine};
8use sha2::{Digest, Sha256};
9
10use crate::device::{Button, Control, Key, MouseButton, Stick, Wheel};
11use crate::family::{Family, GlyphStyle};
12use shape::{Contour, Dir, Shape};
13
14pub const ATLAS_DIGEST: &str = "8a1f27a3b01ad6cb8ef376d2b373d528411f03db37f86c72a165e44cc21e587c";
15pub const BOX: f32 = 48.0;
16pub const PPEM: f32 = 64.0;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub enum Mark {
20    Text(&'static str),
21    Cross,
22    Circle,
23    Square,
24    Triangle,
25    Spot(Dir),
26    Bars,
27    Windows,
28    Rays,
29    Plus,
30    Minus,
31    Forward,
32    Back,
33    Home,
34    Share,
35    Capture,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub enum MouseMark {
40    Button(MouseButton),
41    Wheel(Wheel),
42    Move,
43}
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
46pub enum Glyph {
47    Disc(Mark),
48    Pill(Mark),
49    Stick(&'static str),
50    Bumper(&'static str),
51    Trigger(&'static str),
52    Grip(&'static str),
53    DPad(Option<Dir>),
54    Touchpad,
55    Keycap(u16),
56    Arrow(Dir),
57    Mouse(MouseMark),
58}
59
60pub fn glyph(family: Family, control: Control) -> Glyph {
61    match control {
62        Control::Key(key) => key_glyph(key),
63        Control::Mouse(button) => Glyph::Mouse(MouseMark::Button(button)),
64        Control::Wheel(wheel) => Glyph::Mouse(MouseMark::Wheel(wheel)),
65        Control::MouseMove => Glyph::Mouse(MouseMark::Move),
66        Control::Stick(stick) => Glyph::Stick(match stick {
67            Stick::Left => "L",
68            Stick::Right => "R",
69        }),
70        Control::DPad => Glyph::DPad(None),
71        Control::Button(button) => button_glyph(family, button),
72    }
73}
74
75pub fn glyph_for(style: GlyphStyle, control: Control) -> Glyph {
76    glyph(style.family(), control)
77}
78
79fn key_glyph(key: Key) -> Glyph {
80    match key {
81        Key::Up => Glyph::Arrow(Dir::Up),
82        Key::Down => Glyph::Arrow(Dir::Down),
83        Key::Left => Glyph::Arrow(Dir::Left),
84        Key::Right => Glyph::Arrow(Dir::Right),
85        _ => keycap::keycap_for(keycap::estimate(key.label())),
86    }
87}
88
89#[derive(Clone, Copy, PartialEq, Eq)]
90enum Look {
91    Letters,
92    Shapes,
93    Nintendo,
94    Positions,
95}
96
97fn look(family: Family) -> Look {
98    match family {
99        Family::DualSense | Family::DualShock4 => Look::Shapes,
100        Family::SwitchPro => Look::Nintendo,
101        Family::Generic => Look::Positions,
102        Family::Xbox | Family::SteamController | Family::SteamDeck => Look::Letters,
103    }
104}
105
106fn numbered(family: Family) -> bool {
107    matches!(
108        family,
109        Family::DualSense | Family::DualShock4 | Family::SteamDeck
110    )
111}
112
113fn button_glyph(family: Family, button: Button) -> Glyph {
114    let look = look(family);
115    let face = |letters: &'static str, shape: Mark, nintendo: &'static str, spot: Dir| match look {
116        Look::Letters => Glyph::Disc(Mark::Text(letters)),
117        Look::Shapes => Glyph::Disc(shape),
118        Look::Nintendo => Glyph::Disc(Mark::Text(nintendo)),
119        Look::Positions => Glyph::Disc(Mark::Spot(spot)),
120    };
121    let numbered = numbered(family);
122    match button {
123        Button::South => face("A", Mark::Cross, "B", Dir::Down),
124        Button::East => face("B", Mark::Circle, "A", Dir::Right),
125        Button::West => face("X", Mark::Square, "Y", Dir::Left),
126        Button::North => face("Y", Mark::Triangle, "X", Dir::Up),
127        Button::LeftBumper => Glyph::Bumper(match family {
128            Family::SwitchPro => "L",
129            _ if numbered => "L1",
130            _ => "LB",
131        }),
132        Button::RightBumper => Glyph::Bumper(match family {
133            Family::SwitchPro => "R",
134            _ if numbered => "R1",
135            _ => "RB",
136        }),
137        Button::LeftTrigger => Glyph::Trigger(match family {
138            Family::SwitchPro => "ZL",
139            _ if numbered => "L2",
140            _ => "LT",
141        }),
142        Button::RightTrigger => Glyph::Trigger(match family {
143            Family::SwitchPro => "ZR",
144            _ if numbered => "R2",
145            _ => "RT",
146        }),
147        Button::LeftStick => Glyph::Disc(Mark::Text(if numbered { "L3" } else { "LS" })),
148        Button::RightStick => Glyph::Disc(Mark::Text(if numbered { "R3" } else { "RS" })),
149        Button::DPadUp => Glyph::DPad(Some(Dir::Up)),
150        Button::DPadDown => Glyph::DPad(Some(Dir::Down)),
151        Button::DPadLeft => Glyph::DPad(Some(Dir::Left)),
152        Button::DPadRight => Glyph::DPad(Some(Dir::Right)),
153        Button::Menu => match family {
154            Family::DualSense | Family::DualShock4 => Glyph::Pill(Mark::Bars),
155            Family::SwitchPro => Glyph::Disc(Mark::Plus),
156            Family::SteamController => Glyph::Disc(Mark::Forward),
157            Family::Xbox | Family::SteamDeck | Family::Generic => Glyph::Disc(Mark::Bars),
158        },
159        Button::View => match family {
160            Family::DualSense | Family::DualShock4 => Glyph::Pill(Mark::Rays),
161            Family::SwitchPro => Glyph::Disc(Mark::Minus),
162            Family::SteamController => Glyph::Disc(Mark::Back),
163            Family::Xbox | Family::SteamDeck | Family::Generic => Glyph::Disc(Mark::Windows),
164        },
165        Button::Guide => Glyph::Disc(Mark::Home),
166        Button::Touchpad => Glyph::Touchpad,
167        Button::LeftGrip => Glyph::Grip(grip(family, 0)),
168        Button::RightGrip => Glyph::Grip(grip(family, 1)),
169        Button::LeftGripLower => Glyph::Grip(grip(family, 2)),
170        Button::RightGripLower => Glyph::Grip(grip(family, 3)),
171        Button::Misc => match family {
172            Family::Xbox => Glyph::Disc(Mark::Share),
173            Family::SwitchPro => Glyph::Disc(Mark::Capture),
174            _ => Glyph::Disc(Mark::Text("M")),
175        },
176    }
177}
178
179fn grip(family: Family, index: usize) -> &'static str {
180    match family {
181        Family::SteamController => ["LG", "RG", "LG", "RG"][index],
182        Family::Xbox => ["P3", "P1", "P4", "P2"][index],
183        _ => ["L4", "R4", "L5", "R5"][index],
184    }
185}
186
187pub fn catalogue() -> Vec<Glyph> {
188    let mut set = BTreeSet::new();
189    for family in Family::ALL {
190        for button in Button::ALL {
191            set.insert(glyph(family, Control::Button(button)));
192        }
193        for stick in [Stick::Left, Stick::Right] {
194            set.insert(glyph(family, Control::Stick(stick)));
195        }
196        set.insert(glyph(family, Control::DPad));
197    }
198    for key in Key::ALL {
199        set.insert(glyph(Family::Generic, Control::Key(key)));
200    }
201    for width in keycap::KEYCAP_WIDTHS {
202        set.insert(Glyph::Keycap(width));
203    }
204    for button in [
205        MouseButton::Left,
206        MouseButton::Right,
207        MouseButton::Middle,
208        MouseButton::Back,
209        MouseButton::Forward,
210    ] {
211        set.insert(glyph(Family::Generic, Control::Mouse(button)));
212    }
213    for wheel in [Wheel::Up, Wheel::Down, Wheel::Left, Wheel::Right] {
214        set.insert(glyph(Family::Generic, Control::Wheel(wheel)));
215    }
216    set.insert(glyph(Family::Generic, Control::MouseMove));
217    set.into_iter().collect()
218}
219
220const R: f32 = 22.0;
221const C: f32 = BOX / 2.0;
222
223fn mark(mark: Mark, cx: f32, cy: f32, size: f32) -> Vec<Contour> {
224    let s = size;
225    match mark {
226        Mark::Text(text) => {
227            let cells = shape::label_width(text, 1.0);
228            let max_w = if cells > 5.0 { s * 1.5 } else { s * 1.1 };
229            shape::label(text, cx, cy, max_w, s * 1.15, s / 4.5)
230        }
231        Mark::Cross => vec![shape::cross(cx, cy, s * 0.62, s * 0.11)],
232        Mark::Circle => vec![
233            shape::circle(cx, cy, s * 0.5),
234            shape::circle(cx, cy, s * 0.5 - s * 0.16),
235        ],
236        Mark::Square => vec![
237            shape::rect(cx - s * 0.45, cy - s * 0.45, s * 0.9, s * 0.9, 0.0),
238            shape::rect(
239                cx - s * 0.45 + s * 0.15,
240                cy - s * 0.45 + s * 0.15,
241                s * 0.9 - s * 0.3,
242                s * 0.9 - s * 0.3,
243                0.0,
244            ),
245        ],
246        Mark::Triangle => vec![
247            shape::triangle(cx, cy + s * 0.08, s * 0.6, Dir::Up),
248            shape::triangle(cx, cy + s * 0.08, s * 0.6 - s * 0.3, Dir::Up),
249        ],
250        Mark::Spot(dir) => {
251            let d = s * 0.42;
252            let r = s * 0.17;
253            let mut contours = Vec::new();
254            for (spot, dx, dy) in [
255                (Dir::Up, 0.0, -d),
256                (Dir::Down, 0.0, d),
257                (Dir::Left, -d, 0.0),
258                (Dir::Right, d, 0.0),
259            ] {
260                contours.push(shape::circle(cx + dx, cy + dy, r));
261                if spot != dir {
262                    contours.push(shape::circle(cx + dx, cy + dy, r * 0.45));
263                }
264            }
265            contours
266        }
267        Mark::Bars => (0..3)
268            .map(|i| {
269                let y = cy - s * 0.32 + i as f32 * s * 0.32;
270                shape::rect(cx - s * 0.42, y - s * 0.07, s * 0.84, s * 0.14, s * 0.07)
271            })
272            .collect(),
273        Mark::Windows => {
274            let w = s * 0.6;
275            let t = s * 0.13;
276            let back_x = cx - s * 0.5;
277            let back_y = cy - s * 0.5;
278            let front_x = cx - s * 0.1;
279            let front_y = cy - s * 0.1;
280            vec![
281                shape::polygon(&[
282                    [back_x, back_y],
283                    [back_x + w, back_y],
284                    [back_x + w, front_y - t * 0.8],
285                    [back_x + w - t, front_y - t * 0.8],
286                    [back_x + w - t, back_y + t],
287                    [back_x + t, back_y + t],
288                    [back_x + t, back_y + w - t],
289                    [front_x - t * 0.8, back_y + w - t],
290                    [front_x - t * 0.8, back_y + w],
291                    [back_x, back_y + w],
292                ]),
293                shape::rect(front_x, front_y, w, w, 0.0),
294                shape::rect(front_x + t, front_y + t, w - 2.0 * t, w - 2.0 * t, 0.0),
295            ]
296        }
297        Mark::Rays => (0..3)
298            .map(|i| {
299                let x = cx - s * 0.3 + i as f32 * s * 0.3;
300                let tall = if i == 1 { s * 0.7 } else { s * 0.5 };
301                shape::rect(x - s * 0.07, cy - tall / 2.0, s * 0.14, tall, s * 0.07)
302            })
303            .collect(),
304        Mark::Plus => vec![shape::plus(cx, cy, s * 0.5, s * 0.1)],
305        Mark::Minus => vec![shape::rect(cx - s * 0.5, cy - s * 0.1, s, s * 0.2, 0.0)],
306        Mark::Forward => vec![shape::triangle(cx + s * 0.08, cy, s * 0.48, Dir::Right)],
307        Mark::Back => vec![shape::triangle(cx - s * 0.08, cy, s * 0.48, Dir::Left)],
308        Mark::Home => vec![shape::polygon(&[
309            [cx, cy - s * 0.5],
310            [cx + s * 0.5, cy - s * 0.05],
311            [cx + s * 0.34, cy - s * 0.05],
312            [cx + s * 0.34, cy + s * 0.45],
313            [cx - s * 0.34, cy + s * 0.45],
314            [cx - s * 0.34, cy - s * 0.05],
315            [cx - s * 0.5, cy - s * 0.05],
316        ])],
317        Mark::Share => vec![shape::polygon(&[
318            [cx, cy - s * 0.5],
319            [cx + s * 0.4, cy - s * 0.1],
320            [cx + s * 0.12, cy - s * 0.1],
321            [cx + s * 0.12, cy + s * 0.45],
322            [cx - s * 0.12, cy + s * 0.45],
323            [cx - s * 0.12, cy - s * 0.1],
324            [cx - s * 0.4, cy - s * 0.1],
325        ])],
326        Mark::Capture => vec![
327            shape::rect(cx - s * 0.45, cy - s * 0.45, s * 0.9, s * 0.9, s * 0.12),
328            shape::circle(cx, cy, s * 0.25),
329        ],
330    }
331}
332
333fn arrow(cx: f32, cy: f32, size: f32, dir: Dir) -> Contour {
334    let (w, h) = (size * 0.5, size * 0.42);
335    let points: [[f32; 2]; 7] = [
336        [0.0, -size * 0.5],
337        [w, -size * 0.5 + h],
338        [w * 0.38, -size * 0.5 + h],
339        [w * 0.38, size * 0.5],
340        [-w * 0.38, size * 0.5],
341        [-w * 0.38, -size * 0.5 + h],
342        [-w, -size * 0.5 + h],
343    ];
344    let turn = |p: [f32; 2]| match dir {
345        Dir::Up => [cx + p[0], cy + p[1]],
346        Dir::Down => [cx - p[0], cy - p[1]],
347        Dir::Left => [cx + p[1], cy - p[0]],
348        Dir::Right => [cx - p[1], cy + p[0]],
349    };
350    shape::polygon(&points.map(turn))
351}
352
353pub fn shape_of(glyph: Glyph) -> Shape {
354    draw(glyph).oriented()
355}
356
357fn draw(glyph: Glyph) -> Shape {
358    match glyph {
359        Glyph::Disc(inner) => {
360            let mut shape = Shape::new(BOX, BOX);
361            shape.add(shape::circle(C, C, R));
362            shape.extend(mark(inner, C, C, 22.0));
363            shape
364        }
365        Glyph::Pill(inner) => {
366            let mut shape = Shape::new(BOX, BOX);
367            shape.add(shape::pill(C, C, 44.0, 28.0));
368            shape.extend(mark(inner, C, C, 17.0));
369            shape
370        }
371        Glyph::Stick(text) => {
372            let mut shape = Shape::new(BOX, BOX);
373            shape.add(shape::circle(C, C, R));
374            shape.add(shape::circle(C, C, R - 4.5));
375            shape.extend(shape::label(text, C, C, 14.0, 18.0, 3.0));
376            shape
377        }
378        Glyph::Bumper(text) => {
379            let width = 64.0;
380            let mut shape = Shape::new(width, BOX);
381            shape.add(shape::rounded(
382                2.0,
383                10.0,
384                width - 4.0,
385                28.0,
386                [14.0, 14.0, 5.0, 5.0],
387            ));
388            shape.extend(shape::label(text, width / 2.0, 24.0, 40.0, 17.0, 2.6));
389            shape
390        }
391        Glyph::Trigger(text) => {
392            let mut shape = Shape::new(BOX, BOX);
393            shape.add(shape::rounded(5.0, 2.0, 38.0, 44.0, [17.0, 17.0, 5.0, 5.0]));
394            shape.extend(shape::label(text, C, 26.0, 28.0, 18.0, 2.6));
395            shape
396        }
397        Glyph::Grip(text) => {
398            let width = 56.0;
399            let mut shape = Shape::new(width, BOX);
400            shape.add(shape::polygon(&[
401                [8.0, 8.0],
402                [width - 2.0, 8.0],
403                [width - 2.0, 40.0],
404                [8.0, 40.0],
405                [2.0, 24.0],
406            ]));
407            shape.extend(shape::label(text, width / 2.0 + 3.0, 24.0, 36.0, 18.0, 2.6));
408            shape
409        }
410        Glyph::DPad(dir) => {
411            let mut shape = Shape::new(BOX, BOX);
412            shape.add(shape::plus(C, C, 22.0, 8.0));
413            match dir {
414                Some(dir) => {
415                    let (dx, dy) = match dir {
416                        Dir::Up => (0.0, -13.0),
417                        Dir::Down => (0.0, 13.0),
418                        Dir::Left => (-13.0, 0.0),
419                        Dir::Right => (13.0, 0.0),
420                    };
421                    shape.add(shape::triangle(C + dx, C + dy, 5.0, dir));
422                }
423                None => {
424                    shape.add(shape::circle(C, C, 4.0));
425                }
426            }
427            shape
428        }
429        Glyph::Touchpad => {
430            let width = 64.0;
431            let mut shape = Shape::new(width, BOX);
432            shape.add(shape::rect(2.0, 8.0, width - 4.0, 32.0, 8.0));
433            shape.add(shape::rect(6.0, 12.0, width - 12.0, 24.0, 5.0));
434            shape.add(shape::circle(width / 2.0, 24.0, 5.0));
435            shape
436        }
437        Glyph::Keycap(width) => {
438            let width = f32::from(width);
439            let mut shape = Shape::new(width, BOX);
440            shape.add(shape::rect(2.0, 2.0, width - 4.0, BOX - 4.0, 8.0));
441            shape.add(shape::rect(6.0, 6.0, width - 12.0, BOX - 12.0, 5.0));
442            shape
443        }
444        Glyph::Arrow(dir) => {
445            let mut shape = Shape::new(BOX, BOX);
446            shape.add(shape::rect(2.0, 2.0, BOX - 4.0, BOX - 4.0, 8.0));
447            shape.add(shape::rect(6.0, 6.0, BOX - 12.0, BOX - 12.0, 5.0));
448            shape.add(arrow(C, C, 24.0, dir));
449            shape
450        }
451        Glyph::Mouse(inner) => mouse(inner),
452    }
453}
454
455fn mouse(inner: MouseMark) -> Shape {
456    let width = BOX;
457    let (x, y, w, h) = (7.0, 2.0, 34.0, 44.0);
458    let t = 3.5;
459    let radius = 11.0;
460    let mut shape = Shape::new(width, BOX);
461    shape.add(shape::rect(x, y, w, h, radius));
462    shape.add(shape::rect(
463        x + t,
464        y + t,
465        w - 2.0 * t,
466        h - 2.0 * t,
467        radius - t,
468    ));
469    let gap = 2.5;
470    let (ix, iy, iw) = (x + t + gap, y + t + gap, w - 2.0 * t - 2.0 * gap);
471    let split = y + 19.0;
472    let half = iw / 2.0 - gap / 2.0;
473    let wheel = |shape: &mut Shape| {
474        shape.add(shape::pill(x + w / 2.0, y + 13.0, 4.0, 9.0));
475    };
476    match inner {
477        MouseMark::Button(MouseButton::Left) => {
478            shape.add(shape::rounded(
479                ix,
480                iy,
481                half,
482                split - iy,
483                [radius - t - gap, 0.0, 0.0, 0.0],
484            ));
485        }
486        MouseMark::Button(MouseButton::Right) => {
487            shape.add(shape::rounded(
488                ix + half + gap,
489                iy,
490                half,
491                split - iy,
492                [0.0, radius - t - gap, 0.0, 0.0],
493            ));
494        }
495        MouseMark::Button(MouseButton::Middle) => wheel(&mut shape),
496        MouseMark::Button(MouseButton::Back) => {
497            shape.add(shape::triangle(x + w / 2.0 - 1.0, y + 31.0, 6.0, Dir::Left));
498        }
499        MouseMark::Button(MouseButton::Forward) => {
500            shape.add(shape::triangle(
501                x + w / 2.0 + 1.0,
502                y + 31.0,
503                6.0,
504                Dir::Right,
505            ));
506        }
507        MouseMark::Wheel(dir) => {
508            wheel(&mut shape);
509            let (cx, cy, dir) = match dir {
510                Wheel::Up => (x + w / 2.0, y + 28.0, Dir::Up),
511                Wheel::Down => (x + w / 2.0, y + 30.0, Dir::Down),
512                Wheel::Left => (x + w / 2.0 - 1.0, y + 31.0, Dir::Left),
513                Wheel::Right => (x + w / 2.0 + 1.0, y + 31.0, Dir::Right),
514            };
515            shape.add(shape::triangle(cx, cy, 5.0, dir));
516        }
517        MouseMark::Move => {
518            let cx = x + w / 2.0;
519            let cy = y + 29.0;
520            for (dx, dy, dir) in [
521                (0.0, -6.5, Dir::Up),
522                (0.0, 6.5, Dir::Down),
523                (-6.0, 0.0, Dir::Left),
524                (6.0, 0.0, Dir::Right),
525            ] {
526                shape.add(shape::triangle(cx + dx, cy + dy, 3.4, dir));
527            }
528        }
529    }
530    shape
531}
532
533#[derive(Clone, Copy, Debug, PartialEq)]
534pub struct GlyphCell {
535    pub rect: [u32; 4],
536    pub uv: [f32; 4],
537    pub offset: [f32; 2],
538    pub size: [f32; 2],
539    pub box_size: [f32; 2],
540}
541
542const TILE: f32 = 80.0;
543
544fn stretched(base: &GlyphCell, width: f32, from: f32) -> Vec<GlyphCell> {
545    let [ox, oy] = base.offset;
546    let [sx, sy] = base.size;
547    let [u0, v0, u1, v1] = base.uv;
548    let u = |x: f32| u0 + (x - ox) / sx * (u1 - u0);
549    let px = |x: f32| base.rect[0] as f32 + (x - ox) / sx * base.rect[2] as f32;
550    let piece = |at: f32, from: f32, to: f32| {
551        let left = px(from).round().max(0.0) as u32;
552        let right = px(to).round().max(0.0) as u32;
553        GlyphCell {
554            rect: [left, base.rect[1], right.saturating_sub(left), base.rect[3]],
555            uv: [u(from), v0, u(to), v1],
556            offset: [at, oy],
557            size: [to - from, sy],
558            box_size: [width, BOX],
559        }
560    };
561    let middle = from / 2.0;
562    let shift = width - from;
563    let mut pieces = vec![piece(ox, ox, middle)];
564    let mut at = middle;
565    while at < middle + shift {
566        let span = TILE.min(middle + shift - at);
567        let start = middle - TILE / 2.0;
568        pieces.push(piece(at, start, start + span));
569        at += span;
570    }
571    pieces.push(piece(middle + shift, middle, ox + sx));
572    pieces
573}
574
575#[derive(Clone, Debug)]
576pub struct GlyphAtlas {
577    pub atlas: Atlas,
578    cells: BTreeMap<Glyph, GlyphCell>,
579}
580
581impl GlyphAtlas {
582    pub fn build() -> Result<Self, Error> {
583        let glyphs = catalogue();
584        let shapes: Vec<Shape> = glyphs.iter().map(|glyph| shape_of(*glyph)).collect();
585        let bytes = font::build(&shapes);
586        let mut engine = TextEngine::new(&bytes)?;
587        let text: String = (0..glyphs.len() as u32)
588            .filter_map(|index| char::from_u32(font::FIRST_CHAR + index))
589            .collect();
590        let paragraph = engine.layout(
591            &text,
592            Style {
593                family: font::FAMILY,
594                size: PPEM,
595                line_height: PPEM * 1.25,
596                wrap_width: None,
597                pixels_per_unit: 1.0,
598                representation: Representation::Msdf,
599            },
600            &Baseline::Straight {
601                origin: [0.0, 0.0],
602                direction: [1.0, 0.0],
603            },
604        )?;
605        let mut cells = BTreeMap::new();
606        for instance in &paragraph.glyphs {
607            let Some(index) = usize::from(instance.glyph_id).checked_sub(1) else {
608                continue;
609            };
610            let Some(glyph) = glyphs.get(index) else {
611                continue;
612            };
613            let shape = &shapes[index];
614            cells.insert(
615                *glyph,
616                GlyphCell {
617                    rect: instance.atlas_rect,
618                    uv: instance.uv,
619                    offset: [
620                        instance.local_rect[0],
621                        instance.local_rect[1] + shape.height,
622                    ],
623                    size: [instance.local_rect[2], instance.local_rect[3]],
624                    box_size: [shape.width, shape.height],
625                },
626            );
627        }
628        if cells.len() != glyphs.len() {
629            return Err(Error::UnsupportedGlyph);
630        }
631        Ok(Self {
632            atlas: paragraph.atlas,
633            cells,
634        })
635    }
636
637    pub fn get(&self, glyph: Glyph) -> Option<&GlyphCell> {
638        self.cells.get(&glyph)
639    }
640
641    pub fn pieces(&self, glyph: Glyph) -> Option<Vec<GlyphCell>> {
642        if let Some(cell) = self.cells.get(&glyph) {
643            return Some(vec![*cell]);
644        }
645        let Glyph::Keycap(width) = glyph else {
646            return None;
647        };
648        let widest = keycap::widest();
649        if width < widest {
650            return None;
651        }
652        let base = self.cells.get(&Glyph::Keycap(widest))?;
653        Some(stretched(base, f32::from(width), f32::from(widest)))
654    }
655
656    pub fn cell(&self, family: Family, control: Control) -> Option<&GlyphCell> {
657        self.get(glyph(family, control))
658    }
659
660    pub fn len(&self) -> usize {
661        self.cells.len()
662    }
663
664    pub fn is_empty(&self) -> bool {
665        self.cells.is_empty()
666    }
667
668    pub fn iter(&self) -> impl Iterator<Item = (&Glyph, &GlyphCell)> {
669        self.cells.iter()
670    }
671
672    pub fn digest(&self) -> String {
673        let mut hash = Sha256::new();
674        let level = &self.atlas.levels[0];
675        hash.update(self.atlas.channels.to_le_bytes());
676        hash.update(level.width.to_le_bytes());
677        hash.update(level.height.to_le_bytes());
678        hash.update(&level.bytes);
679        for cell in self.cells.values() {
680            for value in cell.rect {
681                hash.update(value.to_le_bytes());
682            }
683            for value in cell.offset.iter().chain(&cell.size).chain(&cell.box_size) {
684                hash.update(value.to_bits().to_le_bytes());
685            }
686        }
687        hash.finalize()
688            .iter()
689            .map(|byte| format!("{byte:02x}"))
690            .collect()
691    }
692}