Skip to main content

openpol/
fontdat.rs

1//! font.dat data access operations.
2//!
3//! # font.dat file format
4//!
5//! font.dat is an [image13h-encoded](../image13h/index.html) image. It contains 3 rows of 13-pixel
6//! high characters. The rows start at the following pixels (all indices/offsets/positions are 0-based
7//! unless specified otherwise) and have the following number of characters:
8//! * The first row: line 8, column 7, 33 characters
9//! * The second row: line 32, column 8, 31 characters
10//! * The third row: line 56, column 7, 27 characters
11//!
12//! The widths of the characters are hardcoded, `CHARACTER_WIDTHS` array is provided for convenience.
13
14use crate::image13h;
15use std::io;
16
17/// Where do rows start.
18pub const ROWS: usize = 3;
19
20/// How many characters are there, in total.
21pub const CHARACTERS: usize = 91;
22
23pub const CHARACTER_HEIGHT: usize = 13;
24
25/// Where do rows start, (x, y).
26pub const ROW_OFFSETS: [(usize, usize); ROWS] = [(7, 8), (8, 32), (7, 56)];
27
28/// How many characters are there in every row.
29pub const ROW_CHARACTERS: [usize; ROWS] = [33, 31, 27];
30
31/// The width and height of the smallest image that's capable of storing a font.
32pub const MINIMUM_IMAGE_DIMENSIONS: (usize, usize) = (223, 69);
33
34/// The widths of all characters.
35pub const CHARACTER_WIDTHS: [usize; CHARACTERS] = [
36    // The first row
37    4, 2, 4, 6, 6, 6, 6, 6, 4, 4, 6, 6, 2, 4, 2, 6, 6, 5, 6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 8, 6, 8, 6,
38    7, // The second row
39    8, 7, 7, 7, 6, 6, 8, 7, 2, 5, 7, 6, 8, 7, 8, 6, 8, 7, 7, 6, 7, 8, 11, 7, 8, 7, 7, 7, 7, 6, 7,
40    // The third row
41    4, 6, 6, 6, 6, 6, 4, 6, 6, 2, 2, 5, 2, 8, 6, 6, 6, 6, 4, 6, 3, 6, 6, 10, 6, 6, 6,
42];
43
44/// The x positions of the characters in the font image.
45pub const CHARACTER_X_POSITIONS: [usize; CHARACTERS] = [
46    // THe first row
47    7, 11, 13, 17, 23, 29, 35, 41, 47, 51, 55, 61, 67, 69, 73, 75, 81, 87, 92, 98, 104, 110, 116,
48    122, 128, 134, 140, 142, 144, 152, 158, 166, 172, // The second row
49    8, 16, 23, 30, 37, 43, 49, 57, 64, 66, 71, 78, 84, 92, 99, 107, 113, 121, 128, 135, 141, 148,
50    156, 167, 174, 182, 189, 196, 203, 210, 216, // The third row
51    7, 11, 17, 23, 29, 35, 41, 45, 51, 57, 59, 61, 66, 68, 76, 82, 88, 94, 100, 104, 110, 113, 119,
52    125, 135, 141, 147,
53];
54
55#[derive(Debug, Eq, PartialEq)]
56pub struct Fontdat {
57    glyphs: Vec<image13h::Image13h>,
58}
59
60impl Fontdat {
61    /// Load a font from a reader. This function will return None if:
62    ///
63    /// * The image can't be loaded
64    /// * The image loaded is too small (see `MINIMUM_IMAGE_DIMENSIONS`)
65    pub fn load<T: io::Read>(reader: T) -> Option<Fontdat> {
66        let image = match image13h::Image13h::load(reader) {
67            None => return None,
68            Some(image) => image,
69        };
70        if (image.width(), image.height()) < MINIMUM_IMAGE_DIMENSIONS {
71            return None;
72        }
73        let mut glyphs = Vec::new();
74        for character in 0..CHARACTERS {
75            let rect = character_rect(character);
76            let glyph = image.subimage(&rect);
77            glyphs.push(glyph);
78        }
79        Some(Fontdat { glyphs })
80    }
81
82    /// Create a new empty font (all characters are filled with color 0).
83    pub fn empty() -> Fontdat {
84        let mut glyphs = Vec::new();
85        for character in 0..CHARACTERS {
86            let rect = character_rect(character);
87            let glyph = image13h::Image13h::empty(rect.width, rect.height);
88            glyphs.push(glyph);
89        }
90        Fontdat { glyphs }
91    }
92
93    /// Save the font to a writer.
94    pub fn save<T: io::Write>(&self, writer: T) {
95        let mut image =
96            image13h::Image13h::empty(MINIMUM_IMAGE_DIMENSIONS.0, MINIMUM_IMAGE_DIMENSIONS.1);
97        for character in 0..CHARACTERS {
98            let rect = character_rect(character);
99            image.blit(&self.glyphs[character], &rect);
100        }
101        image.save(writer);
102    }
103
104    /// Get a reference to a character glyph.
105    pub fn glyph(&self, character: usize) -> &image13h::Image13h {
106        &self.glyphs[character]
107    }
108
109    /// Get a mutable reference to character glyph.
110    pub fn glyph_mut(&mut self, character: usize) -> &mut image13h::Image13h {
111        &mut self.glyphs[character]
112    }
113}
114
115pub fn character_rect(character: usize) -> image13h::Rect {
116    debug_assert!(character < CHARACTERS);
117    let line = if character < ROW_CHARACTERS[0] {
118        0
119    } else if character < ROW_CHARACTERS[0] + ROW_CHARACTERS[1] {
120        1
121    } else {
122        2
123    };
124
125    let x = CHARACTER_X_POSITIONS[character];
126    let y = ROW_OFFSETS[line].1;
127    image13h::Rect::from_ranges(x..x + CHARACTER_WIDTHS[character], y..y + CHARACTER_HEIGHT)
128}
129
130#[cfg(test)]
131mod tests {
132    use crate::fontdat::{Fontdat, CHARACTERS};
133    use std::fs;
134
135    #[test]
136    fn test_loading_and_saving_works() {
137        // dummy_font.dat contains a font with every character filled with color equal to the
138        // character's index + 100.
139        let dummy_font_dat = fs::read("dummy_font.dat").unwrap();
140        let fontdat = Fontdat::load(&dummy_font_dat[..]).unwrap();
141        let mut expected_fontdat = Fontdat::empty();
142        for i in 0..CHARACTERS {
143            expected_fontdat.glyph_mut(i).fill(100 + i as u8);
144        }
145        // First let's verify that after loading from disk we get the expected glyphs...
146        assert_eq!(fontdat, expected_fontdat);
147        // ...then make sure that after saving a font we get the exact save binary content.
148        let mut buf = Vec::new();
149        fontdat.save(&mut buf);
150        assert_eq!(buf, dummy_font_dat);
151    }
152}