#[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: i32,
pub x: i32,
pub y: i32,
pub width: i32,
pub height: i32,
pub xoffset: i32,
pub yoffset: i32,
pub xadvance: i32,
}
#[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)?;
let mut bmfont = BMFont::load_sfl(buffer)?;
if let Some(path) = path.parent() {
let mut image_path = path.to_path_buf();
image_path.push(bmfont.image_path);
bmfont.image_path = image_path;
} else {
return Err(Error::new(
ErrorKind::Other,
format!("Unable to retrieve path parent."),
));
}
Ok(bmfont)
}
pub fn from_loaded<T: Into<String>>(sfl_contents: T, image_path: T) -> Result<BMFont, Error> {
let mut bmfont = BMFont::load_sfl(sfl_contents)?;
let mut pathbuf = PathBuf::new();
pathbuf.push(image_path.into());
bmfont.image_path = pathbuf;
Ok(bmfont)
}
fn load_sfl<T: Into<String>>(sfl_contents: T) -> Result<BMFont, Error> {
let sfl_contents = sfl_contents.into();
let mut lines = sfl_contents.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 = PathBuf::new();
image_path.push(image_name);
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 as u32, 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<i32> = vec![0; 8];
for i in 0..8 {
match parts.nth(0).unwrap().parse::<i32>() {
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()
)
}
}