1use pfx_text::{Anchor, Error, Face, Placed, Placement, Span, TextEngine};
2
3use super::{BOX, Glyph, glyph};
4use crate::device::{Control, Key};
5use crate::family::Family;
6use crate::layout::KeyLabels;
7
8pub const KEYCAP_WIDTHS: [u16; 6] = [48, 64, 80, 96, 128, 160];
9pub const INSET: f32 = 10.0;
10pub const LETTER_EM: f32 = 26.0;
11pub const WORD_EM: f32 = 18.0;
12pub const CAP_HEIGHT: f32 = 0.7;
13
14pub fn room(width: u16) -> f32 {
15 f32::from(width) - 2.0 * INSET
16}
17
18pub fn keycap_for(label_width: f32) -> Glyph {
19 Glyph::Keycap(
20 KEYCAP_WIDTHS
21 .into_iter()
22 .find(|width| room(*width) >= label_width)
23 .unwrap_or(widest()),
24 )
25}
26
27pub fn label_em(text: &str) -> f32 {
28 if text.chars().count() == 1 {
29 LETTER_EM
30 } else {
31 WORD_EM
32 }
33}
34
35pub fn estimate(text: &str) -> f32 {
36 text.chars().count() as f32 * 0.6 * label_em(text)
37}
38
39#[derive(Clone, Debug, PartialEq)]
40pub struct KeycapLabel {
41 pub glyph: Glyph,
42 pub text: String,
43 pub size: f32,
44 pub origin: [f32; 2],
45}
46
47impl KeycapLabel {
48 pub fn key(
49 engine: &mut TextEngine,
50 face: &Face,
51 labels: &KeyLabels,
52 key: Key,
53 ) -> Result<Self, Error> {
54 Self::key_with_floor(engine, face, labels, key, 0.0)
55 }
56
57 pub fn key_with_floor(
58 engine: &mut TextEngine,
59 face: &Face,
60 labels: &KeyLabels,
61 key: Key,
62 floor: f32,
63 ) -> Result<Self, Error> {
64 match glyph(Family::Generic, Control::Key(key)) {
65 Glyph::Keycap(_) => Self::fit_with_floor(engine, face, labels.label(key), floor),
66 other => Ok(Self {
67 glyph: other,
68 text: String::new(),
69 size: 0.0,
70 origin: [0.0, 0.0],
71 }),
72 }
73 }
74
75 pub fn fit(engine: &mut TextEngine, face: &Face, text: &str) -> Result<Self, Error> {
76 Self::fit_with_floor(engine, face, text, 0.0)
77 }
78
79 pub fn fit_with_floor(
80 engine: &mut TextEngine,
81 face: &Face,
82 text: &str,
83 floor: f32,
84 ) -> Result<Self, Error> {
85 if !floor.is_finite() || floor < 0.0 {
86 return Err(Error::InvalidStyle);
87 }
88 let em = label_em(text);
89 let span = span(text, face, em, [0.0; 4]);
90 let block = engine.layout_spans(&[span], None, 1.0, Anchor::Start)?;
91 let (width, baseline) = (block.width, block.baseline);
92 let Glyph::Keycap(fitted) = keycap_for(width) else {
93 return Err(Error::InvalidStyle);
94 };
95 let mut cap = fitted;
96 let mut shrink = if width > room(cap) {
97 room(cap) / width
98 } else {
99 1.0
100 };
101 if em * shrink < floor {
102 shrink = floor.min(em) / em;
103 cap = widened(width * shrink).max(fitted);
104 }
105 let glyph = Glyph::Keycap(cap);
106 let size = em * shrink;
107 Ok(Self {
108 glyph,
109 text: text.to_owned(),
110 size,
111 origin: [
112 (f32::from(cap) - width * shrink) / 2.0,
113 BOX / 2.0 + CAP_HEIGHT * size / 2.0 - baseline * shrink,
114 ],
115 })
116 }
117
118 pub fn place(
119 &self,
120 engine: &mut TextEngine,
121 face: &Face,
122 color: [f32; 4],
123 origin: [f32; 2],
124 height: f32,
125 placement: &Placement,
126 ) -> Result<Placed, Error> {
127 if self.text.is_empty() {
128 return Ok(Placed::default());
129 }
130 let k = height / BOX;
131 let span = span(&self.text, face, self.size * k, color);
132 engine.place_spans(
133 &[span],
134 None,
135 Anchor::Start,
136 &Placement {
137 origin: [
138 origin[0] + self.origin[0] * k,
139 origin[1] + self.origin[1] * k,
140 ],
141 ..*placement
142 },
143 )
144 }
145}
146
147pub fn widened(label_width: f32) -> u16 {
148 (label_width + 2.0 * INSET)
149 .ceil()
150 .clamp(0.0, f32::from(u16::MAX)) as u16
151}
152
153pub fn widest() -> u16 {
154 KEYCAP_WIDTHS[KEYCAP_WIDTHS.len() - 1]
155}
156
157fn span(text: &str, face: &Face, size: f32, color: [f32; 4]) -> Span {
158 Span {
159 text: text.to_owned(),
160 face: Face {
161 size,
162 line: face.line / face.size * size,
163 ..face.clone()
164 },
165 color,
166 }
167}