#[cfg(test)]
mod tests;
use std::fmt::{Display, Formatter};
use std::fs::File;
use std::path::PathBuf;
use std::collections::HashMap;
use std::io::{Error, ErrorKind, Read};
#[derive(Debug)]
pub struct BMCharacter {
pub id: u32,
pub x: u32,
pub y: u32,
pub width: u32,
pub height: u32,
pub xoffset: u32,
pub yoffset: u32,
pub xadvance: u32,
}
#[derive(Debug)]
pub struct BMFont {
pub font_name: String,
pub image_path: PathBuf,
pub chars: HashMap<u32, BMCharacter>,
pub line_height: u32,
pub size: u32,
}
impl BMFont {
pub fn from_path<T: Into<PathBuf>>(path: T) -> Result<BMFont, Error> {
let path = path.into();
let mut file = File::open(&path)?;
let mut buffer = String::new();
file.read_to_string(&mut buffer).unwrap();
let mut lines = buffer.lines();
if lines.clone().count() < 5 {
return Err(Error::new(
ErrorKind::Other,
"Erronous .sfl file; too few lines to initialize.",
));
}
let font_name = lines.next().unwrap().to_owned();
let line_h_and_size = lines.next().unwrap().to_owned();
let mut parts = line_h_and_size.split(' ');
let size;
let line_height;
if parts.clone().count() == 2 {
match parts.nth(0).unwrap().parse::<u32>() {
Ok(number) => size = number,
Err(error) => {
return Err(Error::new(
ErrorKind::Other,
format!("Error parsing line height: '{}'", error),
))
}
}
match parts.nth(0).unwrap().parse::<u32>() {
Ok(number) => line_height = number,
Err(error) => {
return Err(Error::new(
ErrorKind::Other,
format!("Error parsing size: '{}'", error),
))
}
}
} else {
return Err(Error::new(
ErrorKind::Other,
format!("Second line does not contain two values formatted as 'line-height size'"),
));
}
let image_name = lines.next().unwrap().to_owned();
let mut image_path;
if let Some(path) = path.parent() {
image_path = path.to_path_buf();
image_path.push(image_name);
} else {
return Err(Error::new(
ErrorKind::Other,
format!("Unable to retrieve path parent."),
));
}
let character_amount;
match lines.next().unwrap().to_owned().parse::<u32>() {
Ok(amount) => character_amount = amount,
Err(_) => {
return Err(Error::new(
ErrorKind::Other,
format!("Error while parsing character amount at line: 4"),
))
}
}
if lines.clone().count() + 5 < 5 + character_amount as usize {
return Err(Error::new(
ErrorKind::Other, format!("Erronous .sfl file; character amount (line 4) does not match actual character amount; is {}, should be {}", lines.count() + 5, 5 + character_amount)));
}
let mut chars = HashMap::<u32, BMCharacter>::new();
for i in 0..character_amount {
let character = BMFont::read_character(lines.next().unwrap().to_owned(), i + 1);
match character {
Ok(ch) => chars.insert(ch.id, ch),
Err(error) => return Err(Error::new(ErrorKind::Other, error)),
};
}
return Ok(BMFont {
font_name,
image_path,
chars,
line_height,
size,
});
}
fn read_character(line: String, line_number: u32) -> Result<BMCharacter, String> {
let mut parts = line.split(' ');
if parts.clone().count() < 8 {
return Err(format!(
"Too few parts in character at line: {}",
line_number
));
}
let mut numbers: Vec<u32> = vec![0; 8];
for i in 0..8 {
match parts.nth(0).unwrap().parse::<u32>() {
Ok(number) => numbers[i] = number,
Err(_) => {
return Err(format!(
"Error while parsing number at line: {}",
line_number
));
}
}
}
Ok(BMCharacter {
id: numbers[0],
x: numbers[1],
y: numbers[2],
width: numbers[3],
height: numbers[4],
xoffset: numbers[5],
yoffset: numbers[6],
xadvance: numbers[7],
})
}
}
impl Display for BMFont {
fn fmt<'a>(&self, f: &mut Formatter<'a>) -> std::fmt::Result {
write!(
f,
"BMFont: {{ name: {}, image_path: {:?}, line_height: {}, size: {}, amount of characters: {} }}",
self.font_name,
self.image_path,
self.line_height,
self.size,
self.chars.len()
)
}
}