use std::collections::{BTreeMap, HashSet};
use std::path::Path;
use crate::error::{FontLoadError, GroupsValidationError};
use crate::Name;
pub const FIRST_KERNING_GROUP_PREFIX: &str = "public.kern1.";
pub const SECOND_KERNING_GROUP_PREFIX: &str = "public.kern2.";
pub type Groups = BTreeMap<Name, Vec<Name>>;
pub(crate) fn deserialize_groups<P: AsRef<Path>>(path: P) -> Result<Groups, FontLoadError> {
struct GroupsDeHelper(Groups);
impl<'de> serde::Deserialize<'de> for GroupsDeHelper {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
BTreeMap::<Name, Vec<String>>::deserialize(deserializer)?
.into_iter()
.map(|(k, v)| {
let members = v
.into_iter()
.filter(|s| !s.is_empty())
.map(|s| s.parse::<Name>().map_err(serde::de::Error::custom))
.collect::<Result<_, _>>()?;
Ok((k, members))
})
.collect::<Result<Groups, _>>()
.map(GroupsDeHelper)
}
}
plist::from_file::<_, GroupsDeHelper>(path.as_ref())
.map(|h| h.0)
.map_err(|source| FontLoadError::ParsePlist { name: "groups.plist", source })
}
pub(crate) fn validate_groups(groups_map: &Groups) -> Result<(), GroupsValidationError> {
let mut kern1_set = HashSet::new();
let mut kern2_set = HashSet::new();
for (group_name, group_glyph_names) in groups_map {
if group_name.is_empty() {
return Err(GroupsValidationError::InvalidName);
}
if group_name.starts_with(FIRST_KERNING_GROUP_PREFIX) {
if group_name.len() == FIRST_KERNING_GROUP_PREFIX.len() {
return Err(GroupsValidationError::InvalidName);
}
for glyph_name in group_glyph_names {
if !kern1_set.insert(glyph_name) {
return Err(GroupsValidationError::OverlappingKerningGroups {
glyph_name: glyph_name.clone(),
group_name: group_name.clone(),
});
}
}
} else if group_name.starts_with(SECOND_KERNING_GROUP_PREFIX) {
if group_name.len() == SECOND_KERNING_GROUP_PREFIX.len() {
return Err(GroupsValidationError::InvalidName);
}
for glyph_name in group_glyph_names {
if !kern2_set.insert(glyph_name) {
return Err(GroupsValidationError::OverlappingKerningGroups {
glyph_name: glyph_name.clone(),
group_name: group_name.clone(),
});
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_skip_empty_group_member() {
let group = deserialize_groups("testdata/groups_empty_entries.plist").unwrap();
assert_eq!(group.get("derpy_group").unwrap()[0], "hi");
}
}