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}