1use crate::{BMFont, Char, CommonBlock, InfoBlock, KerningPair};
7use byteorder::{LittleEndian, ReadBytesExt};
8use std::collections::HashMap;
9use std::io::BufRead;
10use std::io::{self, Cursor, Read};
11
12pub fn from_octets(data: &[u8]) -> io::Result<BMFont> {
13 let mut cursor = Cursor::new(data);
14
15 if cursor.read_u8()? != 66
16 || cursor.read_u8()? != 77
17 || cursor.read_u8()? != 70
18 || cursor.read_u8()? != 3
19 {
20 return Err(io::Error::new(
21 io::ErrorKind::InvalidData,
22 "Invalid BMFont header",
23 ));
24 }
25
26 let mut info = None;
27 let mut common = None;
28 let mut pages = Vec::new();
29 let mut chars = HashMap::new();
30 let mut kernings = Vec::new();
31
32 while let Ok(block_type) = cursor.read_u8() {
33 let block_size = cursor.read_u32::<LittleEndian>()? as usize;
34 let mut block_data = vec![0; block_size];
35 cursor.read_exact(&mut block_data)?;
36
37 match block_type {
38 1 => info = Some(parse_info_block(&block_data)?),
39 2 => common = Some(parse_common_block(&block_data)?),
40 3 => pages = parse_pages_block(&block_data)?,
41 4 => chars = parse_chars_block(&block_data)?,
42 5 => kernings = parse_kerning_block(&block_data)?,
43 _ => (),
44 }
45 }
46
47 Ok(BMFont {
48 info,
49 common,
50 pages,
51 chars,
52 kernings,
53 })
54}
55
56fn parse_info_block(data: &[u8]) -> io::Result<InfoBlock> {
57 let mut cursor = Cursor::new(data);
58 Ok(InfoBlock {
59 font_size: cursor.read_i16::<LittleEndian>()?,
60 bit_field: cursor.read_u8()?,
61 char_set: cursor.read_u8()?,
62 stretch_h: cursor.read_u16::<LittleEndian>()?,
63 aa: cursor.read_u8()?,
64 padding: [
65 cursor.read_u8()?,
66 cursor.read_u8()?,
67 cursor.read_u8()?,
68 cursor.read_u8()?,
69 ],
70 spacing: [cursor.read_u8()?, cursor.read_u8()?],
71 outline: cursor.read_u8()?,
72 font_name: {
73 let mut font_name = Vec::new();
74 cursor.read_to_end(&mut font_name)?;
75 String::from_utf8(font_name)
76 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
77 .trim_end_matches('\0')
78 .to_string()
79 },
80 })
81}
82
83fn parse_common_block(data: &[u8]) -> io::Result<CommonBlock> {
84 let mut cursor = Cursor::new(data);
85 Ok(CommonBlock {
86 line_height: cursor.read_u16::<LittleEndian>()?,
87 base: cursor.read_u16::<LittleEndian>()?,
88 scale_w: cursor.read_u16::<LittleEndian>()?,
89 scale_h: cursor.read_u16::<LittleEndian>()?,
90 pages: cursor.read_u16::<LittleEndian>()?,
91 bit_field: cursor.read_u8()?,
92 alpha_chnl: cursor.read_u8()?,
93 red_chnl: cursor.read_u8()?,
94 green_chnl: cursor.read_u8()?,
95 blue_chnl: cursor.read_u8()?,
96 })
97}
98
99fn parse_pages_block(data: &[u8]) -> io::Result<Vec<String>> {
100 let mut cursor = Cursor::new(data);
101 let mut pages = Vec::new();
102 while cursor.position() < data.len() as u64 {
103 let mut page_name = Vec::new();
104 cursor.read_until(0, &mut page_name)?;
105 pages.push(
106 String::from_utf8(page_name)
107 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
108 .trim_end_matches('\0')
109 .to_string(),
110 );
111 }
112 Ok(pages)
113}
114
115fn parse_chars_block(data: &[u8]) -> io::Result<HashMap<u32, Char>> {
116 let mut cursor = Cursor::new(data);
117 let mut chars = HashMap::new();
118 while cursor.position() < data.len() as u64 {
119 let ch = Char {
120 id: cursor.read_u32::<LittleEndian>()?,
121 x: cursor.read_u16::<LittleEndian>()?,
122 y: cursor.read_u16::<LittleEndian>()?,
123 width: cursor.read_u16::<LittleEndian>()?,
124 height: cursor.read_u16::<LittleEndian>()?,
125 x_offset: cursor.read_i16::<LittleEndian>()?,
126 y_offset: cursor.read_i16::<LittleEndian>()?,
127 x_advance: cursor.read_i16::<LittleEndian>()?,
128 page: cursor.read_u8()?,
129 chnl: cursor.read_u8()?,
130 };
131 chars.insert(ch.id, ch);
132 }
133 Ok(chars)
134}
135
136fn parse_kerning_block(data: &[u8]) -> io::Result<Vec<KerningPair>> {
137 let mut cursor = Cursor::new(data);
138 let mut kernings = Vec::new();
139 while cursor.position() < data.len() as u64 {
140 kernings.push(KerningPair {
141 first: cursor.read_u32::<LittleEndian>()?,
142 second: cursor.read_u32::<LittleEndian>()?,
143 amount: cursor.read_i16::<LittleEndian>()?,
144 });
145 }
146 Ok(kernings)
147}