eg_seven_segment/segments.rs
1use bitflags::bitflags;
2use core::convert::TryFrom;
3
4// Segment layout:
5// AAAAA
6// F B
7// F B
8// F B
9// GGGGG
10// E C
11// E C
12// E C
13// DDDDD
14
15bitflags! {
16 /// Segment bit field.
17 ///
18 /// The `Segments` bit field is used to define the active segments in a seven segment digit.
19 /// `Segments` can be constructed converting a [`char`] or by combining the `A`-`G` constants.
20 ///
21 /// Use [`Digit`](crate::Digit) to draw a single digit from a `Segments` bit field or convert
22 /// the `Segments` bit field into a [`char`] to use it in a [`Text`](embedded_graphics::text::Text).
23 ///
24 /// # Examples
25 ///
26 /// ```
27 /// use eg_seven_segment::Segments;
28 ///
29 /// let segments_from_char = Segments::try_from('1').unwrap();
30 /// let segments_from_consts = Segments::B | Segments::C;
31 /// assert_eq!(segments_from_char, segments_from_consts);
32 /// ```
33 ///
34 /// All `Segments` values can be converted into a `char` to include them in a string:
35 ///
36 /// ```
37 /// use eg_seven_segment::Segments;
38 ///
39 /// let as_char = char::from(Segments::B | Segments::C);
40 ///
41 /// // The returned char is guaranteed to be convertible into the same `Segments` bit field:
42 /// assert_eq!(Segments::try_from(as_char).unwrap(), Segments::B | Segments::C);
43 ///
44 /// // But the actual value of the char isn't defined and the application shouldn't depend
45 /// // on the returned value. While the char '1' can be displayed using segments B and C
46 /// // this assertion could fail:
47 /// // assert_eq!(as_char, '1');
48 /// ```
49 ///
50 /// # Segment layout
51 ///
52 /// <center>
53 /// <svg width="108" height="194" version="1.1" viewBox="0 0 28.575 51.329" xmlns="http://www.w3.org/2000/svg">
54 /// <g transform="translate(-51.329 -5.0271)">
55 /// <path d="m51.594 11.377 2.6458-2.6458 2.6458 2.6458v15.875l-2.6458 2.6458-2.6458-2.6458z" fill="#e5e5e5" stop-color="#000000" stroke="#b2b2b2" stroke-linecap="square" stroke-width=".52917" style="-inkscape-stroke:none;font-variation-settings:normal"/>
56 /// <path d="m73.554 5.2917 2.6458 2.6458-2.6458 2.6458h-15.875l-2.6458-2.6458 2.6458-2.6458z" fill="#e5e5e5" stroke="#b2b2b2" stroke-linecap="square" stroke-width=".52917"/>
57 /// <path d="m73.554 28.046 2.6458 2.6458-2.6458 2.6458h-15.875l-2.6458-2.6458 2.6458-2.6458z" fill="#e5e5e5" stop-color="#000000" stroke="#b2b2b2" stroke-linecap="square" stroke-width=".52917" style="-inkscape-stroke:none;font-variation-settings:normal"/>
58 /// <path d="m79.64 27.252-2.6458 2.6458-2.6458-2.6458v-15.875l2.6458-2.6458 2.6458 2.6458z" fill="#e5e5e5" stop-color="#000000" stroke="#b2b2b2" stroke-linecap="square" stroke-width=".52917" style="-inkscape-stroke:none;font-variation-settings:normal"/>
59 /// <path d="m51.594 34.131 2.6458-2.6458 2.6458 2.6458v15.875l-2.6458 2.6458-2.6458-2.6458z" fill="#e5e5e5" stop-color="#000000" stroke="#b2b2b2" stroke-linecap="square" stroke-width=".52917" style="-inkscape-stroke:none;font-variation-settings:normal"/>
60 /// <path d="m73.554 50.8 2.6458 2.6458-2.6458 2.6458h-15.875l-2.6458-2.6458 2.6458-2.6458z" fill="#e5e5e5" stop-color="#000000" stroke="#b2b2b2" stroke-linecap="square" stroke-width=".52917" style="-inkscape-stroke:none;font-variation-settings:normal"/>
61 /// <path d="m79.64 50.006-2.6458 2.6458-2.6458-2.6458v-15.875l2.6458-2.6458 2.6458 2.6458z" fill="#e5e5e5" stop-color="#000000" stroke="#b2b2b2" stroke-linecap="square" stroke-width=".52917" style="-inkscape-stroke:none;font-variation-settings:normal"/>
62 /// <path d="m75.923 20.854v-3.0788h1.0764q0.32171 0 0.53067 0.11124 0.20896 0.10974 0.31118 0.29615 0.10223 0.18491 0.10223 0.4104 0 0.19844-0.07066 0.32772-0.06915 0.12928-0.1834 0.20445-0.11275 0.07517-0.24504 0.11124v0.03007q0.14131 9e-3 0.28413 0.09922 0.14282 0.0902 0.23903 0.25857 0.09621 0.16837 0.09621 0.41191 0 0.23151-0.10523 0.41642-0.10523 0.18491-0.33223 0.29315-0.227 0.10824-0.5908 0.10824zm0.37282-0.33073h0.73963q0.3653 0 0.51864-0.14131 0.15484-0.14282 0.15484-0.34576 0-0.15634-0.07968-0.28864-0.07967-0.1338-0.227-0.21347-0.14732-0.08118-0.34877-0.08118h-0.75767zm0-1.3951h0.69152q0.16837 0 0.30367-0.06615 0.1368-0.06615 0.21648-0.18641 0.08118-0.12026 0.08118-0.28262 0-0.20295-0.14131-0.34426-0.14131-0.14281-0.44799-0.14281h-0.70355z"/>
63 /// <path d="m78.293 41.491h-0.37282q-0.03307-0.16086-0.11576-0.28262-0.08118-0.12177-0.19844-0.20445-0.11576-0.08418-0.25707-0.12628-0.14131-0.04209-0.29465-0.04209-0.27962 0-0.50662 0.14131-0.2255 0.14131-0.35929 0.41642-0.13229 0.27511-0.13229 0.67499t0.13229 0.67499q0.1338 0.27511 0.35929 0.41642 0.227 0.14131 0.50662 0.14131 0.15334 0 0.29465-0.04209 0.14131-0.04209 0.25707-0.12478 0.11726-0.08419 0.19844-0.20595 0.08268-0.12327 0.11576-0.28262h0.37282q-0.04209 0.23602-0.15334 0.42243-0.11125 0.18641-0.27661 0.3172-0.16536 0.12928-0.37132 0.19693-0.20445 0.06765-0.43746 0.06765-0.39387 0-0.70054-0.19242-0.30668-0.19242-0.48256-0.54721t-0.17589-0.84186 0.17589-0.84186 0.48256-0.54721q0.30668-0.19242 0.70054-0.19242 0.23301 0 0.43746 0.06765 0.20595 0.06765 0.37132 0.19844 0.16536 0.12928 0.27661 0.3157 0.11124 0.18491 0.15334 0.42243z"/>
64 /// <path d="m65.358 54.985h-0.95009v-3.0788h0.99219q0.44799 0 0.76669 0.18491 0.3187 0.1834 0.48858 0.52766 0.16987 0.34276 0.16987 0.82081 0 0.48106-0.17138 0.82832-0.17138 0.34576-0.4991 0.53217-0.32772 0.18491-0.79676 0.18491zm-0.57727-0.33073h0.55322q0.38184 0 0.6329-0.14732 0.25105-0.14732 0.37432-0.41942 0.12327-0.2721 0.12327-0.64793 0-0.37282-0.12177-0.64192-0.12177-0.2706-0.3638-0.41492-0.24203-0.14582-0.60283-0.14582h-0.59531z"/>
65 /// <path d="m53.299 43.608v-3.0788h1.8581v0.33073h-1.4853v1.0403h1.3891v0.33073h-1.3891v1.0463h1.5093v0.33073z"/>
66 /// <path d="m53.317 20.854v-3.0788h1.8461v0.33073h-1.4732v1.0403h1.3349v0.33073h-1.3349v1.377z"/>
67 /// <path d="m66.528 30.114q-0.04961-0.15184-0.13079-0.2721-0.07968-0.12177-0.19092-0.20746-0.10974-0.08569-0.24955-0.13079-0.13981-0.0451-0.30668-0.0451-0.2736 0-0.4976 0.14131-0.22399 0.14131-0.35628 0.41642-0.13229 0.27511-0.13229 0.67499t0.1338 0.67499q0.13379 0.27511 0.3623 0.41642 0.2285 0.14131 0.51413 0.14131 0.26458 0 0.46603-0.11275 0.20295-0.11425 0.3157-0.32171 0.11425-0.20896 0.11425-0.49158l0.11425 0.02405h-0.92604v-0.33073h1.1726v0.33073q0 0.38034-0.16236 0.66146-0.16085 0.28112-0.44498 0.43596-0.28262 0.15334-0.64943 0.15334-0.4089 0-0.71858-0.19242-0.30818-0.19242-0.48106-0.54721-0.17138-0.35478-0.17138-0.84186 0-0.48707 0.17138-0.84186 0.17288-0.35478 0.47655-0.54721 0.30517-0.19242 0.69904-0.19242 0.24354 0 0.454 0.07366 0.21197 0.07216 0.37733 0.20595 0.16687 0.13229 0.27811 0.3172 0.11124 0.1834 0.15334 0.4074z"/>
68 /// <path d="m64.685 9.4769h-0.39086l1.1305-3.0788h0.38485l1.1305 3.0788h-0.39086l-0.92003-2.5917h-0.02405zm0.14432-1.2027h1.5755v0.33073h-1.5755z"/>
69 /// </g>
70 /// </svg>
71 /// </center>
72 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
73 pub struct Segments: u8 {
74 /// A segment.
75 const A = 0b01000000;
76 /// B segment.
77 const B = 0b00100000;
78 /// C segment.
79 const C = 0b00010000;
80 /// D segment.
81 const D = 0b00001000;
82 /// E segment.
83 const E = 0b00000100;
84 /// F segment.
85 const F = 0b00000010;
86 /// G segment.
87 const G = 0b00000001;
88 }
89}
90
91impl From<Segments> for char {
92 fn from(segments: Segments) -> Self {
93 char::from_u32(0xE000 + u32::from(segments.bits())).unwrap_or(' ')
94 }
95}
96
97impl TryFrom<char> for Segments {
98 type Error = ();
99
100 fn try_from(value: char) -> Result<Self, Self::Error> {
101 Ok(match value {
102 ' ' => Self::empty(),
103 '0' => Self::A | Self::B | Self::C | Self::D | Self::E | Self::F,
104 '1' => Self::B | Self::C,
105 '2' => Self::A | Self::B | Self::D | Self::E | Self::G,
106 '3' => Self::A | Self::B | Self::C | Self::D | Self::G,
107 '4' => Self::B | Self::C | Self::F | Self::G,
108 '5' => Self::A | Self::C | Self::D | Self::F | Self::G,
109 '6' => Self::A | Self::C | Self::D | Self::E | Self::F | Self::G,
110 '7' => Self::A | Self::B | Self::C,
111 '8' => Self::A | Self::B | Self::C | Self::D | Self::E | Self::F | Self::G,
112 '9' => Self::A | Self::B | Self::C | Self::D | Self::F | Self::G,
113 'a' | 'A' => Self::A | Self::B | Self::C | Self::E | Self::F | Self::G,
114 'b' | 'B' => Self::C | Self::D | Self::E | Self::F | Self::G,
115 'c' => Self::D | Self::E | Self::G,
116 'C' => Self::A | Self::D | Self::E | Self::F,
117 'd' | 'D' => Self::B | Self::C | Self::D | Self::E | Self::G,
118 'e' | 'E' => Self::A | Self::D | Self::E | Self::F | Self::G,
119 'f' | 'F' => Self::A | Self::E | Self::F | Self::G,
120 'g' | 'G' => Self::A | Self::C | Self::D | Self::E | Self::F,
121 'h' => Self::C | Self::E | Self::F | Self::G,
122 'H' => Self::B | Self::C | Self::E | Self::F | Self::G,
123 'i' | 'I' => Self::E | Self::F,
124 'j' | 'J' => Self::B | Self::C | Self::D | Self::E,
125 'l' | 'L' => Self::D | Self::E | Self::F,
126 'n' | 'N' => Self::C | Self::E | Self::G,
127 'o' => Self::C | Self::D | Self::E | Self::G,
128 'O' => Self::A | Self::B | Self::C | Self::D | Self::E | Self::F,
129 'p' | 'P' => Self::A | Self::B | Self::E | Self::F | Self::G,
130 'q' | 'Q' => Self::A | Self::B | Self::C | Self::F | Self::G,
131 'r' | 'R' => Self::E | Self::G,
132 's' | 'S' => Self::A | Self::C | Self::D | Self::F | Self::G,
133 't' | 'T' => Self::D | Self::E | Self::F | Self::G,
134 'u' => Self::C | Self::D | Self::E,
135 'U' => Self::B | Self::C | Self::D | Self::E | Self::F,
136 'y' | 'Y' => Self::B | Self::C | Self::D | Self::F | Self::G,
137 '_' => Self::D,
138 '-' => Self::G,
139 '=' => Self::D | Self::G,
140 '°' => Self::A | Self::B | Self::F | Self::G,
141 '"' => Self::B | Self::F,
142 '\'' => Self::F,
143 '(' | '[' => Self::A | Self::D | Self::E | Self::F,
144 ')' | ']' => Self::A | Self::B | Self::C | Self::D,
145 '?' => Self::A | Self::B | Self::E | Self::G,
146 // TODO: add https://en.wikipedia.org/wiki/Symbols_for_Legacy_Computing ?
147 // TODO: document PUA
148 '\u{E000}'..='\u{E07F}' => Self::from_bits(value as u8).unwrap(),
149 _ => return Err(()),
150 })
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use crate::SevenSegmentStyleBuilder;
157 use embedded_graphics::{
158 mock_display::MockDisplay, pixelcolor::BinaryColor, prelude::*, text::Text,
159 };
160
161 fn test_segments(text: &str, expected_pattern: &[&str]) {
162 let style = SevenSegmentStyleBuilder::new()
163 .digit_size(Size::new(5, 7))
164 .digit_spacing(1)
165 .segment_width(1)
166 .segment_color(BinaryColor::On)
167 .build();
168
169 let mut display = MockDisplay::new();
170 Text::new(text, Point::new(0, 6), style)
171 .draw(&mut display)
172 .unwrap();
173
174 display.assert_pattern(expected_pattern);
175 }
176
177 #[test]
178 fn digits() {
179 test_segments(
180 "0123456789",
181 &[
182 " ### ### ### ### ### ### ### ### ",
183 "# # # # # # # # # # # # # #",
184 "# # # # # # # # # # # # # #",
185 " ### ### ### ### ### ### ### ",
186 "# # # # # # # # # # # # #",
187 "# # # # # # # # # # # # #",
188 " ### ### ### ### ### ### ### ",
189 ],
190 );
191 }
192
193 #[test]
194 fn lower_case_letters() {
195 test_segments(
196 "abcdefghij",
197 &[
198 " ### ### ### ### ",
199 "# # # # # # # # # #",
200 "# # # # # # # # # #",
201 " ### ### ### ### ### ### ### ",
202 "# # # # # # # # # # # # # # # #",
203 "# # # # # # # # # # # # # # # #",
204 " ### ### ### ### ### ### ",
205 ],
206 );
207
208 test_segments(
209 "lnopqrstuy",
210 &[
211 " ### ### ### ",
212 "# # # # # # # # #",
213 "# # # # # # # # #",
214 " ### ### ### ### ### ### ### ### ",
215 "# # # # # # # # # # # # #",
216 "# # # # # # # # # # # # #",
217 " ### ### ### ### ### ### ",
218 ],
219 );
220 }
221
222 #[test]
223 fn upper_case_letters() {
224 test_segments(
225 "ABCDEFGHIJ",
226 &[
227 " ### ### ### ### ### ",
228 "# # # # # # # # # # # #",
229 "# # # # # # # # # # # #",
230 " ### ### ### ### ### ### ",
231 "# # # # # # # # # # # # # # # #",
232 "# # # # # # # # # # # # # # # #",
233 " ### ### ### ### ### ### ",
234 ],
235 );
236
237 test_segments(
238 "LNOPQRSTUY",
239 &[
240 " ### ### ### ### ",
241 "# # # # # # # # # # # # #",
242 "# # # # # # # # # # # # #",
243 " ### ### ### ### ### ### ### ",
244 "# # # # # # # # # # # # #",
245 "# # # # # # # # # # # # #",
246 " ### ### ### ### ### ### ",
247 ],
248 );
249 }
250
251 #[test]
252 fn other_chars() {
253 test_segments(
254 " _-=°\"'",
255 &[
256 " ### ",
257 " # # # # # ",
258 " # # # # # ",
259 " ### ### ### ",
260 " ",
261 " ",
262 " ### ### ",
263 ],
264 );
265
266 test_segments(
267 "([])?",
268 &[
269 " ### ### ### ### ### ",
270 "# # # # #",
271 "# # # # #",
272 " ### ",
273 "# # # # # ",
274 "# # # # # ",
275 " ### ### ### ### ",
276 ],
277 );
278 }
279
280 #[test]
281 fn private_use_area() {
282 test_segments(
283 "\u{E040}\u{E020}\u{E010}\u{E008}\u{E004}\u{E002}\u{E001}\u{E055}\u{E02A}",
284 &[
285 " ### ### ",
286 " # # # #",
287 " # # # #",
288 " ### ### ",
289 " # # # # ",
290 " # # # # ",
291 " ### ### ",
292 ],
293 );
294 }
295}