use crate::font::{Char, Common, Font, Info, Kerning};
use std::io;
const ESCAPER_CAPACITY: usize = 256;
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: format!("UTF8: {}", e),
})
}
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<()> {
let mut escaper = Escaper::with_capacity(ESCAPER_CAPACITY);
font.store(&mut writer, &mut escaper)
}
trait StoreXml {
fn store<W: io::Write>(&self, writer: W, escaper: &mut Escaper) -> crate::Result<()>;
}
impl StoreXml for Font {
fn store<W: io::Write>(&self, mut writer: W, escaper: &mut Escaper) -> crate::Result<()> {
writeln!(writer, "<?xml version=\"1.0\"?>")?;
writeln!(writer, "<font>")?;
self.info.store(&mut writer, escaper)?;
self.common.store(&mut writer, escaper)?;
writeln!(writer, " <pages>")?;
for (i, page) in self.pages.iter().enumerate() {
write!(
writer,
" <page id=\"{}\" file=\"{}\" />",
i,
escaper.escape_value("page id", page)?
)?;
}
writeln!(writer, " </pages>")?;
writeln!(writer, " <chars count=\"{}\">", self.chars.len())?;
self.chars.iter().try_for_each(|u| u.store(&mut writer, escaper))?;
writeln!(writer, " </chars>")?;
writeln!(writer, " <kernings count=\"{}\">", self.kernings.len())?;
self.kernings.iter().try_for_each(|u| u.store(&mut writer, escaper))?;
writeln!(writer, " </kernings>")?;
writeln!(writer, "</font>")?;
Ok(())
}
}
impl StoreXml for Char {
fn store<W: io::Write>(&self, mut writer: W, _: &mut Escaper) -> crate::Result<()> {
writeln!(
writer,
" <char \
id=\"{}\" \
x=\"{}\" \
y=\"{}\" \
width=\"{}\" \
height=\"{}\" \
xoffset=\"{}\" \
yoffset=\"{}\" \
xadvance=\"{}\" \
page=\"{}\" \
chnl=\"{}\" \
/>",
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 StoreXml for Common {
fn store<W: io::Write>(&self, mut writer: W, _: &mut Escaper) -> crate::Result<()> {
writeln!(
writer,
" <common \
lineHeight=\"{}\" \
base=\"{}\" \
scaleW=\"{}\" \
scaleH=\"{}\" \
pages=\"{}\" \
packed=\"{}\" \
alphaChnl=\"{}\" \
redChnl=\"{}\" \
greenChnl=\"{}\" \
blueChnl=\"{}\" \
/>",
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 StoreXml for Info {
fn store<W: io::Write>(&self, mut writer: W, escaper: &mut Escaper) -> crate::Result<()> {
writeln!(
writer,
" <info \
face=\"{}\" \
size=\"{}\" \
bold=\"{}\" \
italic=\"{}\" \
charset=\"{}\" \
unicode=\"{}\" \
stretchH=\"{}\" \
smooth=\"{}\" \
aa=\"{}\" \
padding=\"{},{},{},{}\" \
spacing=\"{},{}\" \
outline=\"{}\" \
/>",
escaper.escape_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 StoreXml for Kerning {
fn store<W: io::Write>(&self, mut writer: W, _: &mut Escaper) -> crate::Result<()> {
writeln!(
writer,
" <kerning first=\"{}\" second=\"{}\" amount=\"{}\" />",
self.first, self.second, self.amount
)
.map_err(Into::into)
}
}
#[derive(Debug, Default)]
struct Escaper {
builder: String,
}
impl Escaper {
fn with_capacity(capacity: usize) -> Self {
Self { builder: String::with_capacity(capacity) }
}
fn escape_value<'a>(&'a mut self, path: &str, value: &str) -> crate::Result<&'a str> {
self.builder.clear();
for c in value.chars() {
match c {
'"' => self.builder.push_str("""),
'\'' => self.builder.push_str("'"),
'<' => self.builder.push_str("<"),
'>' => self.builder.push_str(">"),
'&' => self.builder.push_str("&"),
'\x00'..='\x1F' | '\x7F' => {
return Err(crate::Error::UnsupportedValueEncoding {
path: path.to_owned(),
value: value.to_owned(),
})
}
_ => self.builder.push(c),
}
}
Ok(&self.builder)
}
}
#[cfg(test)]
mod tests {
use super::*;
macro_rules! escape_ok {
($name:ident, $str:expr, $string:expr) => {
#[test]
fn $name() -> crate::Result<()> {
assert_eq!(Escaper::default().escape_value("test", $str)?, $string);
Ok(())
}
};
}
escape_ok!(escape_ok_quot, "\"", """);
escape_ok!(escape_ok_apos, "'", "'");
escape_ok!(escape_ok_lt, "<", "<");
escape_ok!(escape_ok_gt, ">", ">");
escape_ok!(escape_ok_amp, "&", "&");
escape_ok!(escape_ok_space, " ", " ");
escape_ok!(escape_ok_multi, "head\"'<>&tail", "head"'<>&tail");
escape_ok!(escape_ok_multi_space, "\" ' < > &", "" ' < > &");
escape_ok!(escape_ok_unicode_face, "☺", "☺");
macro_rules! escape_err {
($name:ident, $str:expr) => {
#[test]
fn $name() -> crate::Result<()> {
assert!(Escaper::default().escape_value("test", $str).is_err());
Ok(())
}
};
}
escape_err!(escape_err_nul, "\x00");
escape_err!(escape_err_us, "\x1F");
escape_err!(escape_err_del, "\x7F");
}