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 ¶graph.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}