pyxel/
font.rs

1use std::collections::HashMap;
2use std::fs::File;
3use std::io::{BufRead, BufReader};
4
5use crate::canvas::Canvas;
6use crate::image::Color;
7
8#[derive(Copy, Clone)]
9struct BoundingBox {
10    width: i32,
11    height: i32,
12    x: i32,
13    y: i32,
14}
15
16pub struct Glyph {
17    dwidth: i32,
18    bbx: BoundingBox,
19    bitmap: Vec<u32>,
20}
21
22pub struct Font {
23    font_bounding_box: BoundingBox,
24    glyphs: HashMap<i32, Glyph>,
25}
26
27pub type SharedFont = shared_type!(Font);
28
29impl Font {
30    pub fn new(filename: &str) -> SharedFont {
31        let mut font_bounding_box = BoundingBox {
32            width: 0,
33            height: 0,
34            x: 0,
35            y: 0,
36        };
37        let mut glyphs = HashMap::new();
38        let mut code = None;
39        let mut bitmap = None;
40        let mut dwidth = 0;
41        let mut bbx = BoundingBox {
42            width: 0,
43            height: 0,
44            x: 0,
45            y: 0,
46        };
47
48        let file = File::open(filename).unwrap();
49        for line in BufReader::new(file).lines().map_while(Result::ok) {
50            if line.starts_with("FONTBOUNDINGBOX") {
51                let values: Vec<i32> = line
52                    .split_whitespace()
53                    .skip(1)
54                    .map(|v| v.parse().unwrap())
55                    .collect();
56                font_bounding_box = BoundingBox {
57                    width: values[0],
58                    height: values[1],
59                    x: values[2],
60                    y: values[3],
61                };
62            } else if line.starts_with("ENCODING") {
63                code = Some(
64                    line.split_whitespace()
65                        .nth(1)
66                        .unwrap()
67                        .parse::<i32>()
68                        .unwrap(),
69                );
70            } else if line.starts_with("DWIDTH") {
71                dwidth = line
72                    .split_whitespace()
73                    .nth(1)
74                    .map(|v| v.parse().unwrap())
75                    .unwrap();
76            } else if line.starts_with("BBX") {
77                let values: Vec<i32> = line
78                    .split_whitespace()
79                    .skip(1)
80                    .map(|v| v.parse().unwrap())
81                    .collect();
82                bbx = BoundingBox {
83                    width: values[0],
84                    height: values[1],
85                    x: values[2],
86                    y: values[3],
87                };
88            } else if line.starts_with("BITMAP") {
89                bitmap = Some(Vec::new());
90            } else if line.starts_with("ENDCHAR") {
91                if let (Some(code), Some(bitmap)) = (code, bitmap) {
92                    glyphs.insert(
93                        code,
94                        Glyph {
95                            dwidth,
96                            bbx,
97                            bitmap,
98                        },
99                    );
100                }
101                bitmap = None;
102            } else if let Some(ref mut bitmap) = bitmap {
103                let hex_string = line.trim();
104                let bin_string = u32::from_str_radix(hex_string, 16).unwrap();
105                bitmap.push(bin_string.reverse_bits() >> (32 - hex_string.len() * 4));
106            }
107        }
108
109        new_shared_type!(Font {
110            font_bounding_box,
111            glyphs,
112        })
113    }
114
115    pub fn text_width(&self, s: &str) -> i32 {
116        s.chars()
117            .map(|c| self.glyphs.get(&(c as i32)).map_or(0, |glyph| glyph.dwidth))
118            .sum()
119    }
120
121    pub(crate) fn draw(
122        &self,
123        canvas: &mut Canvas<Color>,
124        x: i32,
125        y: i32,
126        text: &str,
127        color: Color,
128    ) {
129        let mut x = x;
130        for c in text.chars() {
131            if let Some(glyph) = self.glyphs.get(&(c as i32)) {
132                self.draw_glyph(canvas, x, y, glyph, color);
133                x += glyph.dwidth;
134            }
135        }
136    }
137
138    fn draw_glyph(&self, canvas: &mut Canvas<Color>, x: i32, y: i32, glyph: &Glyph, color: Color) {
139        let x = x + self.font_bounding_box.x + glyph.bbx.x;
140        let y = y + self.font_bounding_box.y + self.font_bounding_box.height
141            - glyph.bbx.y
142            - glyph.bbx.height;
143
144        for (i, &row) in glyph.bitmap.iter().enumerate() {
145            let value_y = y + i as i32;
146            for j in 0..glyph.bbx.width {
147                let value_x = x + j;
148                if canvas.clip_rect.contains(value_x, value_y) && (row >> j) & 1 == 1 {
149                    canvas.write_data(value_x as usize, value_y as usize, color);
150                }
151            }
152        }
153    }
154}