bmf_parser/
text.rs

1/*---------------------------------------------------------------------------------------------
2 *  Copyright (c) Peter Bjorklund. All rights reserved.
3 *  Licensed under the MIT License. See LICENSE in the project root for license information.
4 *--------------------------------------------------------------------------------------------*/
5
6use crate::{BMFont, Char, CommonBlock, InfoBlock, KerningPair};
7use std::collections::HashMap;
8use std::fs::File;
9use std::io::{self, Read};
10use std::path::Path;
11use std::str::FromStr;
12
13impl FromStr for BMFont {
14    type Err = io::Error;
15
16    fn from_str(s: &str) -> Result<Self, Self::Err> {
17        let mut bmfont = BMFont {
18            info: None,
19            common: None,
20            pages: Vec::new(),
21            chars: HashMap::new(),
22            kernings: Vec::new(),
23        };
24
25        for line in s.lines() {
26            let line = line.trim();
27
28            if line.is_empty() || line.starts_with('#') {
29                continue;
30            }
31
32            let parts: Vec<&str> = line.splitn(2, ' ').collect();
33            if parts.len() < 2 {
34                continue;
35            }
36
37            let tag = parts[0];
38            let content = parts[1];
39
40            match tag {
41                "info" => bmfont.info = Some(parse_info_block(content)?),
42                "common" => bmfont.common = Some(parse_common_block(content)?),
43                "page" => {
44                    let page_name = parse_page(content)?;
45                    bmfont.pages.push(page_name);
46                }
47                "char" => {
48                    let char_data = parse_char(content)?;
49                    bmfont.chars.insert(char_data.id, char_data);
50                }
51                "kerning" => {
52                    let kerning = parse_kerning(content)?;
53                    bmfont.kernings.push(kerning);
54                }
55                _ => {} // TODO: Report error on unknown tags
56            }
57        }
58
59        Ok(bmfont)
60    }
61}
62
63pub fn parse_bmfont_text<P: AsRef<Path>>(path: P) -> io::Result<BMFont> {
64    let mut file = File::open(path)?;
65    let mut content = String::new();
66    file.read_to_string(&mut content)?;
67    content.parse()
68}
69
70fn parse_key_value_pairs(content: &str) -> HashMap<String, String> {
71    let mut map = HashMap::new();
72    let mut chars = content.chars().peekable();
73
74    while let Some(&c) = chars.peek() {
75        if c.is_whitespace() {
76            chars.next();
77            continue;
78        }
79
80        // Parse the key
81        let mut key = String::new();
82        while let Some(&c) = chars.peek() {
83            if c == '=' {
84                chars.next();
85                break;
86            }
87            key.push(chars.next().unwrap());
88        }
89
90        // Parse value
91        let mut value = String::new();
92        let mut in_quotes = false;
93
94        while let Some(&c) = chars.peek() {
95            if c == '"' {
96                in_quotes = !in_quotes;
97                chars.next();
98                if !in_quotes && value.is_empty() {
99                    break;
100                }
101                continue;
102            }
103
104            if c.is_whitespace() && !in_quotes {
105                break;
106            }
107
108            value.push(chars.next().unwrap());
109        }
110
111        map.insert(key, value);
112    }
113
114    map
115}
116
117fn parse_required<T: FromStr>(pairs: &HashMap<String, String>, key: &str) -> io::Result<T> {
118    pairs
119        .get(key)
120        .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, String::from("Missing ") + key))?
121        .parse::<T>()
122        .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, String::from("Invalid ") + key))
123}
124
125fn parse_optional<T: FromStr>(pairs: &HashMap<String, String>, key: &str, default: T) -> T {
126    pairs
127        .get(key)
128        .and_then(|v| v.parse::<T>().ok())
129        .unwrap_or(default)
130}
131
132fn parse_array<T: FromStr>(text: &str, expected_size: usize) -> io::Result<Vec<T>> {
133    let mut values = Vec::with_capacity(expected_size);
134
135    for part in text.split(',') {
136        let value = part.trim().parse::<T>().map_err(|_| {
137            io::Error::new(
138                io::ErrorKind::InvalidData,
139                String::from("Failed to parse array value"),
140            )
141        })?;
142        values.push(value);
143    }
144
145    if values.len() == expected_size {
146        Ok(values)
147    } else {
148        Err(io::Error::new(
149            io::ErrorKind::InvalidData,
150            String::from("Array has wrong size"),
151        ))
152    }
153}
154
155fn parse_info_block(content: &str) -> io::Result<InfoBlock> {
156    let pairs = parse_key_value_pairs(content);
157
158    let font_size = parse_required::<i16>(&pairs, "size")?;
159
160    let bit_field = parse_optional::<u8>(&pairs, "bold", 0) & 0x01
161        | (parse_optional::<u8>(&pairs, "italic", 0) & 0x01) << 1
162        | (parse_optional::<u8>(&pairs, "unicode", 0) & 0x01) << 2
163        | (parse_optional::<u8>(&pairs, "smooth", 0) & 0x01) << 3;
164
165    let char_set = parse_optional::<u8>(&pairs, "charset", 0);
166    let stretch_h = parse_optional::<u16>(&pairs, "stretchH", 100);
167    let aa = parse_optional::<u8>(&pairs, "aa", 1);
168
169    let default_padding = String::from("0,0,0,0");
170    let padding_str = pairs.get("padding").unwrap_or(&default_padding);
171    let padding = parse_array::<u8>(padding_str, 4)?;
172
173    let default_spacing = String::from("0,0");
174    let spacing_str = pairs.get("spacing").unwrap_or(&default_spacing);
175    let spacing = parse_array::<u8>(spacing_str, 2)?;
176
177    let outline = parse_optional::<u8>(&pairs, "outline", 0);
178    let font_name = parse_required::<String>(&pairs, "face")?;
179
180    Ok(InfoBlock {
181        font_size,
182        bit_field,
183        char_set,
184        stretch_h,
185        aa,
186        padding: [padding[0], padding[1], padding[2], padding[3]],
187        spacing: [spacing[0], spacing[1]],
188        outline,
189        font_name,
190    })
191}
192
193fn parse_common_block(content: &str) -> io::Result<CommonBlock> {
194    let pairs = parse_key_value_pairs(content);
195
196    let line_height = parse_required::<u16>(&pairs, "lineHeight")?;
197    let base = parse_required::<u16>(&pairs, "base")?;
198    let scale_w = parse_required::<u16>(&pairs, "scaleW")?;
199    let scale_h = parse_required::<u16>(&pairs, "scaleH")?;
200    let pages = parse_required::<u16>(&pairs, "pages")?;
201
202    let bit_field = parse_optional::<u8>(&pairs, "packed", 0);
203    let alpha_chnl = parse_optional::<u8>(&pairs, "alphaChnl", 0);
204    let red_chnl = parse_optional::<u8>(&pairs, "redChnl", 0);
205    let green_chnl = parse_optional::<u8>(&pairs, "greenChnl", 0);
206    let blue_chnl = parse_optional::<u8>(&pairs, "blueChnl", 0);
207
208    Ok(CommonBlock {
209        line_height,
210        base,
211        scale_w,
212        scale_h,
213        pages,
214        bit_field,
215        alpha_chnl,
216        red_chnl,
217        green_chnl,
218        blue_chnl,
219    })
220}
221
222fn parse_page(content: &str) -> io::Result<String> {
223    let pairs = parse_key_value_pairs(content);
224    parse_required::<String>(&pairs, "file")
225}
226
227fn parse_char(content: &str) -> io::Result<Char> {
228    let pairs = parse_key_value_pairs(content);
229
230    Ok(Char {
231        id: parse_required::<u32>(&pairs, "id")?,
232        x: parse_required::<u16>(&pairs, "x")?,
233        y: parse_required::<u16>(&pairs, "y")?,
234        width: parse_required::<u16>(&pairs, "width")?,
235        height: parse_required::<u16>(&pairs, "height")?,
236        x_offset: parse_required::<i16>(&pairs, "xoffset")?,
237        y_offset: parse_required::<i16>(&pairs, "yoffset")?,
238        x_advance: parse_required::<i16>(&pairs, "xadvance")?,
239        page: parse_optional::<u8>(&pairs, "page", 0),
240        chnl: parse_optional::<u8>(&pairs, "chnl", 15),
241    })
242}
243
244fn parse_kerning(content: &str) -> io::Result<KerningPair> {
245    let pairs = parse_key_value_pairs(content);
246
247    Ok(KerningPair {
248        first: parse_required::<u32>(&pairs, "first")?,
249        second: parse_required::<u32>(&pairs, "second")?,
250        amount: parse_required::<i16>(&pairs, "amount")?,
251    })
252}