use crate::font::{Char, Common, Font, Info, Kerning};
use std::io;
pub fn to_string(font: &Font) -> crate::Result<String> {
let vec = to_vec(font)?;
String::from_utf8(vec).map_err(|e| crate::Error::Parse {
line: None,
entity: "font".to_owned(),
err: e.to_string(),
})
}
pub fn to_vec(font: &Font) -> crate::Result<Vec<u8>> {
let mut vec: Vec<u8> = Vec::default();
to_writer(&mut vec, font)?;
Ok(vec)
}
pub fn to_writer<W: io::Write>(mut writer: W, font: &Font) -> crate::Result<()> {
font.store(&mut writer)
}
trait StoreFnt {
fn store<W: io::Write>(&self, writer: W) -> crate::Result<()>;
}
impl StoreFnt for Font {
fn store<W: io::Write>(&self, mut writer: W) -> crate::Result<()> {
self.info.store(&mut writer)?;
self.common.store(&mut writer)?;
for (i, page) in self.pages.iter().enumerate() {
write!(writer, "page id={} file=\"{}\"\r\n", i, check_value("page id", page)?)?;
}
write!(writer, "chars count={}\r\n", self.chars.len())?;
self.chars.iter().try_for_each(|u| u.store(&mut writer))?;
write!(writer, "kernings count={}\r\n", self.kernings.len())?;
self.kernings.iter().try_for_each(|u| u.store(&mut writer))?;
Ok(())
}
}
impl StoreFnt for Char {
fn store<W: io::Write>(&self, mut writer: W) -> crate::Result<()> {
write!(
writer,
"char id={:<4} \
x={:<5} \
y={:<5} \
width={:<5} \
height={:<5} \
xoffset={:<5} \
yoffset={:<5} \
xadvance={:<5} \
page={:<2} \
chnl={:<2}\
\r\n",
self.id,
self.x,
self.y,
self.width,
self.height,
self.xoffset,
self.yoffset,
self.xadvance,
self.page,
u8::from(self.chnl)
)
.map_err(Into::into)
}
}
impl StoreFnt for Common {
fn store<W: io::Write>(&self, mut writer: W) -> crate::Result<()> {
write!(
writer,
"common \
lineHeight={} \
base={} \
scaleW={} \
scaleH={} \
pages={} \
packed={} \
alphaChnl={} \
redChnl={} \
greenChnl={} \
blueChnl={}\
\r\n",
self.line_height,
self.base,
self.scale_w,
self.scale_h,
self.pages,
self.packed as u32,
self.alpha_chnl as u8,
self.red_chnl as u8,
self.green_chnl as u8,
self.blue_chnl as u8
)
.map_err(Into::into)
}
}
impl StoreFnt for Info {
fn store<W: io::Write>(&self, mut writer: W) -> crate::Result<()> {
write!(
writer,
"info \
face=\"{}\" \
size={} \
bold={} \
italic={} \
charset=\"{}\" \
unicode={} \
stretchH={} \
smooth={} \
aa={} \
padding={},{},{},{} \
spacing={},{} \
outline={}\
\r\n",
check_value("info face", &self.face)?,
self.size,
self.bold as u32,
self.italic as u32,
self.charset,
self.unicode as u32,
self.stretch_h as u32,
self.smooth as u32,
self.aa as u32,
self.padding.up,
self.padding.right,
self.padding.down,
self.padding.left,
self.spacing.horizontal,
self.spacing.vertical,
self.outline
)
.map_err(Into::into)
}
}
impl StoreFnt for Kerning {
fn store<W: io::Write>(&self, mut writer: W) -> crate::Result<()> {
write!(
writer,
"kerning first={:<3} second={:<3} amount={:<4}\r\n",
self.first, self.second, self.amount
)
.map_err(Into::into)
}
}
fn check_value<'a>(path: &'a str, value: &'a str) -> crate::Result<&'a str> {
for c in value.chars() {
match c {
'\x00'..='\x1F' | '"' | '\x7F' => {
return Err(crate::Error::UnsupportedValueEncoding {
path: path.to_owned(),
value: value.to_owned(),
})
}
_ => {}
}
}
Ok(value)
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! check_ok {
($name:ident, $str:expr) => {
#[test]
fn $name() -> crate::Result<()> {
assert!(check_value("test", $str).is_ok());
Ok(())
}
};
}
check_ok!(check_ok_null, "");
check_ok!(check_ok_space, " ");
check_ok!(check_ok_tilde, "~");
check_ok!(check_ok_unicode_face, "☺");
macro_rules! check_err {
($name:ident, $str:expr) => {
#[test]
fn $name() -> crate::Result<()> {
assert!(check_value("test", $str).is_err());
Ok(())
}
};
}
check_err!(check_err_nul, "\x00");
check_err!(check_err_us, "\x1F");
check_err!(check_err_del, "\x7F");
check_err!(check_err_quote, "\"");
check_err!(check_err_nl, "\n");
check_err!(check_err_cr, "\r");
}