1use 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 _ => {} }
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 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 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}