use std::{
iter::{Chain, FlatMap},
path::PathBuf,
slice::Iter,
};
use serde::{Deserialize, Serialize};
use crate::{
collections::{HashMap, hash_map::Keys},
core::{IGlyphOffset, IGlyphRegion, UGlyphRegion, UGlyphSize, Unique},
layout::OrdTokenLayout,
token::{Sequence, Token},
};
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct FontMeta {
pub name: Option<String>,
#[serde(alias = "texture")]
pub image: PathBuf,
#[serde(flatten)]
pub layout: FontLayout,
}
#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct FontLayout {
#[serde(rename = "layout")]
pub layout_tokens: OrdTokenLayout,
#[serde(rename = "pack")]
pub packing_mode: PackingMode,
#[serde(default, rename = "override", alias = "overrides")]
pub sequence_overrides: HashMap<Sequence, GlyphOverride>,
#[serde(default, rename = "extract")]
pub manual_tokens: HashMap<Token, CustomGlyph>,
}
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum PackingMode {
Uniform {
#[serde(flatten)]
track: FontTrack,
},
Dynamic {
#[serde(flatten)]
tracks: HashMap<Token, FontTrack>,
},
}
#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
pub struct FontTrack {
#[serde(alias = "size")]
pub grid_tile_size: UGlyphSize,
#[serde(alias = "region")]
pub glyph_region: IGlyphRegion,
#[serde(default)]
pub offset: IGlyphOffset,
}
#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
pub struct GlyphOverride {
#[serde(default)]
pub region: Option<IGlyphRegion>,
#[serde(default)]
pub offset: Option<IGlyphOffset>,
}
#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(untagged)]
pub enum CustomGlyph {
Relative {
#[serde(alias = "ref")]
reference: Sequence,
region: IGlyphRegion,
#[serde(default)]
offset: IGlyphOffset,
},
Absolute {
region: UGlyphRegion,
#[serde(default)]
offset: IGlyphOffset,
},
}
impl Default for PackingMode {
fn default() -> Self {
Self::Uniform {
track: Default::default(),
}
}
}
type GlyphIter<'a> = Chain<Iter<'a, Token>, Keys<'a, Token, CustomGlyph>>;
impl FontLayout {
#[inline]
pub fn packing_mode(&self) -> &PackingMode {
&self.packing_mode
}
#[inline]
pub fn get_override(&self, token: &Sequence) -> Option<&GlyphOverride> {
self.sequence_overrides.get(token)
}
#[inline]
pub fn ord_layout(&self) -> &[Token] {
self.layout_tokens.as_ref()
}
#[inline]
pub fn custom(&self) -> &HashMap<Token, CustomGlyph> {
&self.manual_tokens
}
#[inline]
pub fn iter_tokens(&'_ self) -> GlyphIter<'_> {
self.layout_tokens
.iter_tokens()
.chain(self.manual_tokens.keys())
}
#[inline]
#[allow(clippy::type_complexity)]
pub fn iter_sequences(
&'_ self,
) -> FlatMap<GlyphIter<'_>, Iter<'_, Sequence>, fn(&Token) -> Iter<Sequence>> {
self.iter_tokens().flat_map(Token::iter)
}
#[doc(alias = "glyph count")]
#[doc(alias = "region count")]
#[doc(alias = "unique sequence")]
#[inline]
pub fn unique<'a>(&'a self) -> Unique<'a> {
self.iter_tokens().into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn serde() {
let meta = FontMeta {
name: Some("Test Font".into()),
image: "font.png".into(),
layout: FontLayout {
layout_tokens: r#"0123456789 ABCDEFG.$($btn_face_south|$btn_dpad_south)"#
.parse()
.unwrap(),
packing_mode: PackingMode::Uniform {
track: FontTrack {
grid_tile_size: UGlyphSize::new(8, 8),
glyph_region: IGlyphRegion::new(0, 0, 8, 8),
offset: IGlyphOffset::new(0, 0),
},
},
sequence_overrides: HashMap::from([(
' '.into(),
GlyphOverride {
region: Some(IGlyphRegion::new(0, 0, 4, 8)),
offset: None,
},
)]),
manual_tokens: HashMap::from([(
"$($btn_face_south)".parse().unwrap(),
CustomGlyph::Relative {
reference: '0'.into(),
region: IGlyphRegion::new(1, 5, 7, 12),
offset: IGlyphOffset::new(0, 2),
},
)]),
},
};
println!("Original:\n{}", toml::to_string_pretty(&meta).unwrap());
let ex = r#"
layout = "0123456789 ABCDEFG.$($btn_face_south|$btn_dpad_south)"
image = "font.png"
[pack]
size = [8, 8]
region = { min = [0, 0], max = [8, 8] }
# offset = [20000, 0]
[override." "]
region = [[0, 0], [4, 8]]
[extract."$($btn_face_south)"]
ref = "0"
region = [[1, 5], [7, 12]]
offset = [0, 2]
"#;
let deserialized: FontMeta = toml::from_str(ex).unwrap();
assert_eq!(&meta.image, &deserialized.image);
assert_eq!(
&meta.layout.sequence_overrides,
&deserialized.layout.sequence_overrides
);
println!(
"Deserialized:\n{}",
toml::to_string_pretty(&deserialized).unwrap()
);
assert_eq!(
&meta.layout.manual_tokens,
&deserialized.layout.manual_tokens
);
assert_eq!(meta.layout.packing_mode, deserialized.layout.packing_mode);
assert_eq!(meta.layout.layout_tokens, deserialized.layout.layout_tokens);
}
}