bdf_parser/
glyph.rs

1use nom::{
2    bytes::complete::{tag, take, take_until},
3    character::complete::{multispace0, space0},
4    combinator::{eof, map, map_parser, map_res, opt},
5    multi::many0,
6    sequence::{delimited, preceded, terminated},
7    IResult,
8};
9use std::convert::TryFrom;
10
11use crate::{helpers::*, BoundingBox, Coord};
12
13/// Glyph.
14#[derive(Debug, Clone, PartialEq)]
15pub struct Glyph {
16    /// Name.
17    pub name: String,
18
19    /// Encoding.
20    pub encoding: Option<char>,
21
22    /// Scalable width.
23    pub scalable_width: Option<Coord>,
24
25    /// Device width.
26    pub device_width: Coord,
27
28    /// Bounding box.
29    pub bounding_box: BoundingBox,
30
31    /// Bitmap data.
32    pub bitmap: Vec<u8>,
33}
34
35impl Glyph {
36    fn parse(input: &[u8]) -> IResult<&[u8], Self> {
37        let (input, name) = statement("STARTCHAR", parse_string)(input)?;
38        let (input, encoding) = statement("ENCODING", parse_encoding)(input)?;
39        let (input, scalable_width) = opt(statement("SWIDTH", Coord::parse))(input)?;
40        let (input, device_width) = statement("DWIDTH", Coord::parse)(input)?;
41        let (input, bounding_box) = statement("BBX", BoundingBox::parse)(input)?;
42        let (input, bitmap) = parse_bitmap(input)?;
43
44        Ok((
45            input,
46            Self {
47                name,
48                encoding,
49                scalable_width,
50                device_width,
51                bounding_box,
52                bitmap,
53            },
54        ))
55    }
56
57    /// Returns a pixel from the bitmap.
58    ///
59    /// This method doesn't use the BDF coordinate system. The coordinates are relative to the
60    /// top left corner of the bounding box and don't take the offset into account. Y coordinates
61    /// increase downwards.
62    ///
63    /// # Panics
64    ///
65    /// This method panics if the coordinates are outside the bitmap.
66    pub fn pixel(&self, x: usize, y: usize) -> bool {
67        let width = usize::try_from(self.bounding_box.size.x).unwrap();
68
69        let bytes_per_row = (width + 7) / 8;
70        let byte_offset = x / 8;
71        let bit_mask = 0x80 >> (x % 8);
72
73        self.bitmap[byte_offset + bytes_per_row * y] & bit_mask != 0
74    }
75}
76
77fn parse_encoding(input: &[u8]) -> IResult<&[u8], Option<char>> {
78    map(parse_to_i32, |code| {
79        u32::try_from(code).ok().and_then(std::char::from_u32)
80    })(input)
81}
82
83fn parse_bitmap(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
84    map_parser(
85        delimited(
86            statement("BITMAP", eof),
87            take_until("ENDCHAR"),
88            statement("ENDCHAR", eof),
89        ),
90        preceded(multispace0, many0(terminated(parse_hex_byte, multispace0))),
91    )(input)
92}
93
94fn parse_hex_byte(input: &[u8]) -> IResult<&[u8], u8> {
95    map_res(
96        map_res(take(2usize), |bytes| std::str::from_utf8(bytes)),
97        |v| u8::from_str_radix(v, 16),
98    )(input)
99}
100
101/// Glyphs collection.
102#[derive(Debug, Clone, PartialEq)]
103pub struct Glyphs {
104    glyphs: Vec<Glyph>,
105}
106
107impl Glyphs {
108    pub(crate) fn parse(input: &[u8]) -> IResult<&[u8], Self> {
109        map(
110            preceded(
111                terminated(opt(numchars), multispace0),
112                many0(terminated(Glyph::parse, multispace0)),
113            ),
114            |glyphs| Self { glyphs },
115        )(input)
116    }
117
118    /// Gets a glyph by the encoding.
119    pub fn get(&self, c: char) -> Option<&Glyph> {
120        self.glyphs.iter().find(|glyph| glyph.encoding == Some(c))
121    }
122
123    /// Returns an iterator over all glyphs.
124    pub fn iter(&self) -> impl Iterator<Item = &Glyph> {
125        self.glyphs.iter()
126    }
127}
128
129fn numchars(input: &[u8]) -> IResult<&[u8], u32> {
130    preceded(
131        space0,
132        preceded(tag("CHARS"), preceded(space0, parse_to_u32)),
133    )(input)
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use indoc::indoc;
140
141    #[test]
142    fn test_parse_bitmap() {
143        assert_parser_ok!(parse_bitmap(b"BITMAP\n7e\nENDCHAR"), vec![0x7e]);
144        assert_parser_ok!(parse_bitmap(b"BITMAP\nff\nENDCHAR"), vec![0xff]);
145        assert_parser_ok!(parse_bitmap(b"BITMAP\nCCCC\nENDCHAR"), vec![0xcc, 0xcc]);
146        assert_parser_ok!(
147            parse_bitmap(b"BITMAP\nffffffff\nENDCHAR"),
148            vec![0xff, 0xff, 0xff, 0xff]
149        );
150        assert_parser_ok!(
151            parse_bitmap(b"BITMAP\nffffffff\naaaaaaaa\nENDCHAR"),
152            vec![0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, 0xaa]
153        );
154        assert_parser_ok!(
155            parse_bitmap(b"BITMAP\nff\nff\nff\nff\naa\naa\naa\naa\nENDCHAR"),
156            vec![0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, 0xaa]
157        );
158        assert_parser_ok!(
159            parse_bitmap(
160                b"BITMAP\n00\n00\n00\n00\n18\n24\n24\n42\n42\n7E\n42\n42\n42\n42\n00\n00\nENDCHAR"
161            ),
162            vec![
163                0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x24, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x42, 0x42,
164                0x00, 0x00
165            ]
166        );
167    }
168
169    /// Returns test data for a single glyph and the expected parsing result
170    fn test_data() -> (&'static [u8], Glyph) {
171        (
172            indoc! {br#"
173                STARTCHAR ZZZZ
174                ENCODING 65
175                SWIDTH 500 0
176                DWIDTH 8 0
177                BBX 8 16 0 -2
178                BITMAP
179                00
180                00
181                00
182                00
183                18
184                24
185                24
186                42
187                42
188                7E
189                42
190                42
191                42
192                42
193                00
194                00
195                ENDCHAR
196            "#},
197            Glyph {
198                name: "ZZZZ".to_string(),
199                encoding: Some('A'), //65
200                bitmap: vec![
201                    0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x24, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x42,
202                    0x42, 0x00, 0x00,
203                ],
204                bounding_box: BoundingBox {
205                    size: Coord::new(8, 16),
206                    offset: Coord::new(0, -2),
207                },
208                scalable_width: Some(Coord::new(500, 0)),
209                device_width: Coord::new(8, 0),
210            },
211        )
212    }
213
214    #[test]
215    fn parse_single_char() {
216        let (chardata, expected_glyph) = test_data();
217
218        assert_parser_ok!(Glyph::parse(chardata), expected_glyph.clone());
219    }
220
221    #[test]
222    fn get_glyph_by_char() {
223        let (chardata, expected_glyph) = test_data();
224
225        let (input, glyphs) = Glyphs::parse(chardata).unwrap();
226        assert!(input.is_empty());
227        assert_eq!(glyphs.get('A'), Some(&expected_glyph));
228    }
229
230    #[test]
231    fn access_pixels() {
232        let (chardata, _) = test_data();
233        let (input, glyph) = Glyph::parse(chardata).unwrap();
234        assert!(input.is_empty());
235
236        let bitmap = (0..16)
237            .map(|y| {
238                (0..8)
239                    .map(|x| if glyph.pixel(x, y) { '#' } else { ' ' })
240                    .collect::<String>()
241            })
242            .collect::<Vec<_>>();
243
244        assert_eq!(
245            bitmap,
246            [
247                "        ", //
248                "        ", //
249                "        ", //
250                "        ", //
251                "   ##   ", //
252                "  #  #  ", //
253                "  #  #  ", //
254                " #    # ", //
255                " #    # ", //
256                " ###### ", //
257                " #    # ", //
258                " #    # ", //
259                " #    # ", //
260                " #    # ", //
261                "        ", //
262                "        ", //
263            ]
264            .iter()
265            .map(|s| s.to_string())
266            .collect::<Vec<_>>()
267        );
268    }
269
270    #[test]
271    fn parse_glyph_with_no_encoding() {
272        let chardata = indoc! {br#"
273            STARTCHAR 000
274            ENCODING -1
275            SWIDTH 432 0
276            DWIDTH 6 0
277            BBX 0 0 0 0
278            BITMAP
279            ENDCHAR
280        "#};
281
282        assert_parser_ok!(
283            Glyph::parse(chardata),
284            Glyph {
285                bitmap: vec![],
286                bounding_box: BoundingBox {
287                    size: Coord::new(0, 0),
288                    offset: Coord::new(0, 0),
289                },
290                encoding: None,
291                name: "000".to_string(),
292                scalable_width: Some(Coord::new(432, 0)),
293                device_width: Coord::new(6, 0),
294            }
295        );
296    }
297
298    #[test]
299    fn parse_glyph_with_empty_bitmap() {
300        let chardata = indoc! {br#"
301            STARTCHAR 000
302            ENCODING 0
303            SWIDTH 432 0
304            DWIDTH 6 0
305            BBX 0 0 0 0
306            BITMAP
307            ENDCHAR
308        "#};
309
310        assert_parser_ok!(
311            Glyph::parse(chardata),
312            Glyph {
313                bitmap: vec![],
314                bounding_box: BoundingBox {
315                    size: Coord::new(0, 0),
316                    offset: Coord::new(0, 0),
317                },
318                encoding: Some('\x00'),
319                name: "000".to_string(),
320                scalable_width: Some(Coord::new(432, 0)),
321                device_width: Coord::new(6, 0),
322            }
323        );
324    }
325}