Skip to main content

copper_bdf_parser/
glyph.rs

1use std::convert::TryFrom;
2
3use crate::{
4    parser::{Line, Lines},
5    BoundingBox, Coord, Metadata, ParserError,
6};
7
8/// Glyph encoding
9#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
10pub enum Encoding {
11    /// Standard encoding
12    Standard(u32),
13    /// Non standard encoding
14    NonStandard(u32),
15    /// Unspecified encoding
16    #[default]
17    Unspecified,
18}
19
20/// Glyph width.
21#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
22pub struct GlyphWidth {
23    /// Scalable width in 1/1000th of the size.
24    pub scalable: Coord,
25    /// Device width in device pixels.
26    pub device: Coord,
27}
28
29/// Glyph.
30#[derive(Debug, Clone, PartialEq, Default)]
31pub struct Glyph {
32    /// Name.
33    ///
34    /// Specified by `STARTCHAR`.
35    pub name: String,
36
37    /// Encoding.
38    ///
39    /// Specified by `ENCODING`.
40    pub encoding: Encoding,
41
42    /// Width for writing mode 0.
43    ///
44    /// Specified by `DWIDTH` and `SWIDTH` and used for `METRICSSET 0` and
45    /// `METRICSSET 2`.
46    pub width_horizontal: Option<GlyphWidth>,
47
48    /// Width for writing mode 1.
49    ///
50    /// Specified by `DWIDTH1` and `SWIDTH1` and used for `METRICSSET 1` and
51    /// `METRICSSET 2`.
52    pub width_vertical: Option<GlyphWidth>,
53
54    /// Bounding box.
55    ///
56    /// Specified by `BBX`.
57    pub bounding_box: BoundingBox,
58
59    /// Origin offset between writing mode 0 and 1.
60    ///
61    /// Specified by `VVECTOR`.
62    pub origin_offset: Option<Coord>,
63
64    /// Bitmap data.
65    ///
66    /// Specified by the hex values between `BITMAP` and `ENDCHAR`.
67    pub bitmap: Vec<u8>,
68}
69
70fn parse_bitmap_row(line: &Line<'_>, bitmap: &mut Vec<u8>) -> Result<(), ()> {
71    if !line.parameters.is_empty() || line.keyword.len() % 2 != 0 {
72        return Err(());
73    }
74
75    // Accessing the UTF-8 string by byte and not by char is OK because the
76    // hex conversion will fail for non ASCII inputs.
77    for hex in line.keyword.as_bytes().chunks_exact(2) {
78        let byte = str::from_utf8(hex)
79            .ok()
80            .and_then(|s| u8::from_str_radix(s, 16).ok())
81            .ok_or(())?;
82        bitmap.push(byte);
83    }
84
85    Ok(())
86}
87
88/// Approximate SWIDTH based on DWIDTH and the font metadata.
89fn calculate_swidth(device_width: Coord, metadata: &Metadata) -> Coord {
90    Coord {
91        x: device_width.x * 1000 * 72 / metadata.point_size / metadata.resolution.x,
92        y: device_width.y * 1000 * 72 / metadata.point_size / metadata.resolution.y,
93    }
94}
95
96impl Glyph {
97    pub(crate) fn parse(
98        mut lines: &mut Lines<'_>,
99        metadata: &Metadata,
100    ) -> Result<Self, crate::ParserError> {
101        let mut encoding = Encoding::Unspecified;
102        let mut swidth = None;
103        let mut dwidth = None;
104        let mut swidth1 = None;
105        let mut dwidth1 = None;
106        let mut bbx = BoundingBox {
107            size: Coord::new(0, 0),
108            offset: Coord::new(0, 0),
109        };
110        let mut vvector = None;
111
112        let start = lines.next().unwrap();
113        assert_eq!(start.keyword, "STARTCHAR");
114        let name = start.parameters;
115
116        for line in &mut lines {
117            match line.keyword {
118                "ENCODING" => {
119                    encoding = if let Some([index1, index2]) = line.parse_integer_parameters() {
120                        if index1 >= 0 || index2 < 0 {
121                            return Err(ParserError::with_line("invalid \"ENCODING\"", &line));
122                        }
123
124                        Encoding::NonStandard(index2 as u32)
125                    } else if let Some([index]) = line.parse_integer_parameters() {
126                        if index >= 0 {
127                            Encoding::Standard(index as u32)
128                        } else {
129                            Encoding::Unspecified
130                        }
131                    } else {
132                        return Err(ParserError::with_line("invalid \"ENCODING\"", &line));
133                    };
134                }
135                "SWIDTH" => {
136                    swidth = Some(
137                        Coord::parse(&line)
138                            .ok_or_else(|| ParserError::with_line("invalid \"SWIDTH\"", &line))?,
139                    );
140                }
141                "DWIDTH" => {
142                    dwidth = Some(
143                        Coord::parse(&line)
144                            .ok_or_else(|| ParserError::with_line("invalid \"DWIDTH\"", &line))?,
145                    );
146                }
147                "SWIDTH1" => {
148                    swidth1 = Some(
149                        Coord::parse(&line)
150                            .ok_or_else(|| ParserError::with_line("invalid \"SWIDTH1\"", &line))?,
151                    );
152                }
153                "DWIDTH1" => {
154                    dwidth1 = Some(
155                        Coord::parse(&line)
156                            .ok_or_else(|| ParserError::with_line("invalid \"DWIDTH1\"", &line))?,
157                    );
158                }
159                "BBX" => {
160                    bbx = BoundingBox::parse(&line)
161                        .ok_or_else(|| ParserError::with_line("invalid \"BBX\"", &line))?;
162                }
163                "VVECTOR" => {
164                    vvector = Some(
165                        Coord::parse(&line)
166                            .ok_or_else(|| ParserError::with_line("invalid \"VVECTOR\"", &line))?,
167                    );
168                }
169                "BITMAP" => {
170                    break;
171                }
172                _ => {
173                    return Err(ParserError::with_line(
174                        &format!("unknown keyword in glyphs: \"{}\"", line.keyword),
175                        &line,
176                    ))
177                }
178            }
179        }
180
181        let mut bitmap = Vec::new();
182        for line in &mut lines {
183            if line.keyword == "ENDCHAR" {
184                break;
185            }
186
187            parse_bitmap_row(&line, &mut bitmap)
188                .map_err(|_| ParserError::with_line("invalid hex data in BITMAP", &line))?;
189        }
190
191        let width_horizontal = if swidth.is_some() || dwidth.is_some() {
192            let device =
193                dwidth.ok_or_else(|| ParserError::with_line("missing \"DWIDTH\"", &start))?;
194
195            // According to the specs SWIDTH is required, but there are BDF
196            // files which are missing this value. The parser will try to
197            // approximate the value in this case.
198            let scalable = swidth.unwrap_or_else(|| calculate_swidth(device, metadata));
199
200            Some(GlyphWidth { scalable, device })
201        } else {
202            None
203        };
204
205        let width_vertical = if swidth1.is_some() || dwidth1.is_some() {
206            let device =
207                dwidth1.ok_or_else(|| ParserError::with_line("missing \"DWIDTH1\"", &start))?;
208
209            // According to the specs SWIDTH is required, but there are BDF
210            // files which are missing this value. The parser will try to
211            // approximate the value in this case.
212            let scalable = swidth1.unwrap_or_else(|| calculate_swidth(device, metadata));
213
214            Some(GlyphWidth { scalable, device })
215        } else {
216            None
217        };
218
219        Ok(Self {
220            name: name.to_string(),
221            encoding,
222            width_horizontal,
223            width_vertical,
224            bounding_box: bbx,
225            bitmap,
226            origin_offset: vvector,
227        })
228    }
229
230    /// Returns a pixel from the bitmap.
231    ///
232    /// This method doesn't use the BDF coordinate system. The coordinates are relative to the
233    /// top left corner of the bounding box and don't take the offset into account. Y coordinates
234    /// increase downwards.
235    ///
236    /// Returns `None` if the coordinates are outside the bitmap.
237    pub fn pixel(&self, x: usize, y: usize) -> Option<bool> {
238        let width = usize::try_from(self.bounding_box.size.x).unwrap();
239
240        if x >= width {
241            return None;
242        }
243
244        let bytes_per_row = width.div_ceil(8);
245        let byte_offset = x / 8;
246        let bit_mask = 0x80 >> (x % 8);
247
248        self.bitmap
249            .get(byte_offset + bytes_per_row * y)
250            .map(|v| v & bit_mask != 0)
251    }
252
253    /// Returns an iterator over the pixels in the glyph bitmap.
254    ///
255    /// Iteration starts at the top left corner of the bounding box and ends at the bottom right
256    /// corner.
257    pub fn pixels(&self) -> impl Iterator<Item = bool> + '_ {
258        let width = usize::try_from(self.bounding_box.size.x).unwrap();
259        let height = usize::try_from(self.bounding_box.size.y).unwrap();
260
261        (0..height).flat_map(move |y| (0..width).map(move |x| self.pixel(x, y).unwrap()))
262    }
263}
264
265/// Glyphs collection.
266#[derive(Debug, Clone, PartialEq)]
267pub struct Glyphs {
268    glyphs: Vec<Glyph>,
269}
270
271impl Glyphs {
272    pub(crate) fn parse(lines: &mut Lines<'_>, metadata: &Metadata) -> Result<Self, ParserError> {
273        let mut glyphs = Vec::new();
274
275        while let Some(line) = lines.next() {
276            match line.keyword {
277                "CHARS" => {
278                    // TODO: handle
279                }
280                "STARTCHAR" => {
281                    lines.backtrack(line);
282                    glyphs.push(Glyph::parse(lines, metadata)?);
283                }
284                "ENDFONT" => {
285                    break;
286                }
287                _ => {
288                    return Err(ParserError::with_line(
289                        &format!("unknown keyword: \"{}\"", line.keyword),
290                        &line,
291                    ))
292                }
293            }
294        }
295
296        if glyphs.is_empty() {
297            return Err(ParserError::new("no CHARS in font"));
298        }
299
300        Ok(Self { glyphs })
301    }
302
303    /// Gets a glyph by the encoding.
304    pub fn get(&self, c: char) -> Option<&Glyph> {
305        // TODO: this assumes that the font uses unicode
306        let encoding = Encoding::Standard(c as u32);
307
308        self.glyphs
309            .binary_search_by_key(&encoding, |glyph| glyph.encoding)
310            .map_or(None, |i| Some(&self.glyphs[i]))
311    }
312
313    /// Returns `true` if the collection contains the given character.
314    pub fn contains(&self, c: char) -> bool {
315        self.get(c).is_some()
316    }
317
318    /// Returns an iterator over all glyphs.
319    pub fn iter(&self) -> impl Iterator<Item = &Glyph> {
320        self.glyphs.iter()
321    }
322
323    /// Approximates the ascent.
324    ///
325    /// See section 8.2.1 FONT_ASCENT in https://www.x.org/docs/XLFD/xlfd.pdf.
326    pub(crate) fn approximate_ascent(&self) -> u32 {
327        self.glyphs
328            .iter()
329            .map(|glyph| glyph.bounding_box.size.y - glyph.bounding_box.offset.y)
330            .max()
331            .unwrap_or_default()
332            .try_into()
333            .unwrap()
334    }
335
336    /// Approximates the descent.
337    ///
338    /// See section 8.2.2 FONT_DESCENT in https://www.x.org/docs/XLFD/xlfd.pdf.
339    pub(crate) fn approximate_descent(&self) -> u32 {
340        self.glyphs
341            .iter()
342            .map(|glyph| -glyph.bounding_box.offset.y)
343            .max()
344            .unwrap_or_default()
345            .try_into()
346            .unwrap()
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use crate::Properties;
353
354    use super::*;
355    use indoc::indoc;
356
357    fn mock_metadata() -> Metadata {
358        Metadata {
359            name: "test".to_string(),
360            point_size: 16,
361            resolution: Coord::new(100, 100),
362            bounding_box: BoundingBox::default(),
363            metrics_set: crate::MetricsSet::Horizontal,
364            properties: Properties::default(),
365        }
366    }
367
368    #[track_caller]
369    fn parse_glyph(input: &str) -> Glyph {
370        let mut lines = Lines::new(input);
371        Glyph::parse(&mut lines, &mock_metadata()).unwrap()
372    }
373
374    #[test]
375    fn test_parse_bitmap() {
376        let prefix = "STARTCHAR 0\nSWIDTH 0 0\nDWIDTH 0 0\nBITMAP\n";
377        let suffix = "\nENDCHAR";
378
379        for (input, expected) in [
380            ("7e", vec![0x7e]),
381            ("ff", vec![0xff]),
382            ("CCCC", vec![0xcc, 0xcc]),
383            ("ffffffff", vec![0xff, 0xff, 0xff, 0xff]),
384            (
385                "ffffffff\naaaaaaaa",
386                vec![0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, 0xaa],
387            ),
388            (
389                "ff\nff\nff\nff\naa\naa\naa\naa",
390                vec![0xff, 0xff, 0xff, 0xff, 0xaa, 0xaa, 0xaa, 0xaa],
391            ),
392            (
393                "00\n00\n00\n00\n18\n24\n24\n42\n42\n7E\n42\n42\n42\n42\n00\n00",
394                vec![
395                    0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x24, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x42,
396                    0x42, 0x00, 0x00,
397                ],
398            ),
399        ] {
400            let glyph = parse_glyph(&format!("{prefix}{input}{suffix}"));
401            assert_eq!(glyph.bitmap, expected);
402        }
403    }
404
405    /// Returns test data for a single glyph and the expected parsing result
406    fn test_data() -> (&'static str, Glyph) {
407        (
408            indoc! {r#"
409                STARTCHAR ZZZZ
410                ENCODING 65
411                SWIDTH 500 0
412                DWIDTH 8 0
413                BBX 8 16 0 -2
414                BITMAP
415                00
416                00
417                00
418                00
419                18
420                24
421                24
422                42
423                42
424                7E
425                42
426                42
427                42
428                42
429                00
430                00
431                ENDCHAR
432            "#},
433            Glyph {
434                name: "ZZZZ".to_string(),
435                encoding: Encoding::Standard(65), // 'A'
436                bitmap: vec![
437                    0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x24, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x42,
438                    0x42, 0x00, 0x00,
439                ],
440                bounding_box: BoundingBox {
441                    size: Coord::new(8, 16),
442                    offset: Coord::new(0, -2),
443                },
444                width_horizontal: Some(GlyphWidth {
445                    scalable: Coord::new(500, 0),
446                    device: Coord::new(8, 0),
447                }),
448                width_vertical: None,
449                origin_offset: None,
450            },
451        )
452    }
453
454    #[test]
455    fn parse_single_char() {
456        let (chardata, expected_glyph) = test_data();
457        assert_eq!(parse_glyph(chardata), expected_glyph);
458    }
459
460    #[test]
461    fn get_glyph_by_char() {
462        let (chardata, expected_glyph) = test_data();
463
464        let mut lines = Lines::new(chardata);
465
466        let glyphs = Glyphs::parse(&mut lines, &mock_metadata()).unwrap();
467        assert_eq!(glyphs.get('A'), Some(&expected_glyph));
468    }
469
470    #[test]
471    fn pixel_getter() {
472        let (chardata, _) = test_data();
473        let glyph = parse_glyph(chardata);
474
475        let bitmap = (0..16)
476            .map(|y| {
477                (0..8)
478                    .map(|x| if glyph.pixel(x, y).unwrap() { '#' } else { ' ' })
479                    .collect::<String>()
480            })
481            .collect::<Vec<_>>();
482
483        assert_eq!(
484            bitmap,
485            [
486                "        ", //
487                "        ", //
488                "        ", //
489                "        ", //
490                "   ##   ", //
491                "  #  #  ", //
492                "  #  #  ", //
493                " #    # ", //
494                " #    # ", //
495                " ###### ", //
496                " #    # ", //
497                " #    # ", //
498                " #    # ", //
499                " #    # ", //
500                "        ", //
501                "        ", //
502            ]
503            .iter()
504            .map(|s| s.to_string())
505            .collect::<Vec<_>>()
506        );
507    }
508
509    #[test]
510    fn pixels_iterator() {
511        let (chardata, _) = test_data();
512        let glyph = parse_glyph(chardata);
513
514        let bitmap = glyph
515            .pixels()
516            .map(|v| if v { '#' } else { ' ' })
517            .collect::<String>();
518
519        assert_eq!(
520            bitmap,
521            concat!(
522                "        ", //
523                "        ", //
524                "        ", //
525                "        ", //
526                "   ##   ", //
527                "  #  #  ", //
528                "  #  #  ", //
529                " #    # ", //
530                " #    # ", //
531                " ###### ", //
532                " #    # ", //
533                " #    # ", //
534                " #    # ", //
535                " #    # ", //
536                "        ", //
537                "        ", //
538            )
539        );
540    }
541
542    #[test]
543    fn pixel_getter_outside() {
544        let (chardata, _) = test_data();
545        let glyph = parse_glyph(chardata);
546
547        assert_eq!(glyph.pixel(8, 0), None);
548        assert_eq!(glyph.pixel(0, 16), None);
549        assert_eq!(glyph.pixel(8, 16), None);
550    }
551
552    #[test]
553    fn parse_glyph_with_no_encoding() {
554        let chardata = indoc! {r#"
555            STARTCHAR 000
556            ENCODING -1
557            SWIDTH 432 0
558            DWIDTH 6 0
559            BBX 0 0 0 0
560            BITMAP
561            ENDCHAR
562        "#};
563
564        assert_eq!(
565            parse_glyph(chardata),
566            Glyph {
567                bitmap: vec![],
568                bounding_box: BoundingBox {
569                    size: Coord::new(0, 0),
570                    offset: Coord::new(0, 0),
571                },
572                encoding: Encoding::Unspecified,
573                name: "000".to_string(),
574                width_horizontal: Some(GlyphWidth {
575                    scalable: Coord::new(432, 0),
576                    device: Coord::new(6, 0),
577                }),
578                width_vertical: None,
579                origin_offset: None,
580            }
581        );
582    }
583
584    #[test]
585    fn parse_glyph_with_no_encoding_and_index() {
586        let chardata = indoc! {r#"
587            STARTCHAR 000
588            ENCODING -1 123
589            SWIDTH 432 0
590            DWIDTH 6 0
591            BBX 0 0 0 0
592            BITMAP
593            ENDCHAR
594        "#};
595
596        assert_eq!(
597            parse_glyph(chardata),
598            Glyph {
599                bitmap: vec![],
600                bounding_box: BoundingBox {
601                    size: Coord::new(0, 0),
602                    offset: Coord::new(0, 0),
603                },
604                encoding: Encoding::NonStandard(123),
605                name: "000".to_string(),
606                width_horizontal: Some(GlyphWidth {
607                    scalable: Coord::new(432, 0),
608                    device: Coord::new(6, 0),
609                }),
610                width_vertical: None,
611                origin_offset: None,
612            }
613        );
614    }
615
616    #[test]
617    fn parse_glyph_with_writing_mode1_metrics() {
618        let chardata = indoc! {r#"
619            STARTCHAR 000
620            ENCODING -1
621            SWIDTH1 0 432
622            DWIDTH1 0 6
623            VVECTOR 1 2
624            BBX 0 0 0 0
625            BITMAP
626            ENDCHAR
627        "#};
628
629        assert_eq!(
630            parse_glyph(chardata),
631            Glyph {
632                bitmap: vec![],
633                bounding_box: BoundingBox {
634                    size: Coord::new(0, 0),
635                    offset: Coord::new(0, 0),
636                },
637                encoding: Encoding::Unspecified,
638                name: "000".to_string(),
639                width_horizontal: None,
640                width_vertical: Some(GlyphWidth {
641                    scalable: Coord::new(0, 432),
642                    device: Coord::new(0, 6),
643                }),
644                origin_offset: Some(Coord::new(1, 2)),
645            }
646        );
647    }
648
649    #[test]
650    fn parse_glyph_with_empty_bitmap() {
651        let chardata = indoc! {r#"
652            STARTCHAR 000
653            ENCODING 0
654            SWIDTH 432 0
655            DWIDTH 6 0
656            BBX 0 0 0 0
657            BITMAP
658            ENDCHAR
659        "#};
660
661        assert_eq!(
662            parse_glyph(chardata),
663            Glyph {
664                bitmap: vec![],
665                bounding_box: BoundingBox {
666                    size: Coord::new(0, 0),
667                    offset: Coord::new(0, 0),
668                },
669                encoding: Encoding::Standard(0),
670                name: "000".to_string(),
671                width_horizontal: Some(GlyphWidth {
672                    scalable: Coord::new(432, 0),
673                    device: Coord::new(6, 0),
674                }),
675                width_vertical: None,
676                origin_offset: None,
677            }
678        );
679    }
680}