use crate::setting::parse_location;
use read_fonts::{types::NameId, FontRef, ReadError, TableProvider};
use skrifa::{instance::Location, setting::VariationSetting, MetadataProvider};
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
};
use ttj::monkeypatching::DenormalizeLocation;
use ucd::Codepoint;
#[derive(Debug, Clone)]
pub struct DFont {
pub backing: Vec<u8>,
pub location: Vec<VariationSetting>,
pub normalized_location: Location,
pub codepoints: HashSet<u32>,
}
impl DFont {
pub fn new(string: &[u8]) -> Self {
let backing: Vec<u8> = string.to_vec();
let mut fnt = DFont {
backing,
codepoints: HashSet::new(),
normalized_location: Location::default(),
location: vec![],
};
let cmap = fnt.fontref().charmap();
fnt.codepoints = cmap.mappings().map(|(cp, _)| cp).collect();
fnt
}
pub fn normalize_location(&mut self) {
self.normalized_location = self.fontref().axes().location(&self.location);
}
pub fn set_location(&mut self, variations: &str) -> Result<(), String> {
self.location = parse_location(variations)?;
self.normalize_location();
Ok(())
}
pub fn instances(&self) -> Vec<String> {
self.fontref()
.named_instances()
.iter()
.flat_map(|ni| {
self.fontref()
.localized_strings(ni.subfamily_name_id())
.english_or_first()
})
.map(|s| s.to_string())
.collect()
}
pub fn set_instance(&mut self, instance: &str) -> Result<(), String> {
let instance = self
.fontref()
.named_instances()
.iter()
.find(|ni| {
self.fontref()
.localized_strings(ni.subfamily_name_id())
.any(|s| instance == s.chars().collect::<Cow<str>>())
})
.ok_or_else(|| format!("No instance named {}", instance))?;
let user_coords = instance.user_coords();
let location = instance.location();
self.location = self
.fontref()
.axes()
.iter()
.zip(user_coords)
.map(|(a, v)| (a.tag(), v).into())
.collect();
self.normalized_location = location;
Ok(())
}
pub fn fontref(&self) -> FontRef<'_> {
FontRef::new(&self.backing).expect("Couldn't parse font")
}
pub fn family_name(&self) -> String {
self.fontref()
.localized_strings(NameId::FAMILY_NAME)
.english_or_first()
.map_or_else(|| "Unknown".to_string(), |s| s.chars().collect())
}
pub fn style_name(&self) -> String {
self.fontref()
.localized_strings(NameId::SUBFAMILY_NAME)
.english_or_first()
.map_or_else(|| "Regular".to_string(), |s| s.chars().collect())
}
pub fn axis_info(&self) -> HashMap<String, (f32, f32, f32)> {
self.fontref()
.axes()
.iter()
.map(|axis| {
(
axis.tag().to_string(),
(axis.min_value(), axis.default_value(), axis.max_value()),
)
})
.collect()
}
pub fn supported_scripts(&self) -> HashSet<String> {
let cmap = self.fontref().charmap();
let mut strings = HashSet::new();
for (codepoint, _glyphid) in cmap.mappings() {
if let Some(script) = char::from_u32(codepoint).and_then(|c| c.script()) {
strings.insert(format!("{:?}", script));
}
}
strings
}
pub fn masters(&self) -> Result<Vec<Vec<VariationSetting>>, ReadError> {
let gvar = self.fontref().gvar()?;
let tuples = gvar.shared_tuples()?.tuples();
let peaks: Vec<Vec<VariationSetting>> = tuples
.iter()
.flatten()
.flat_map(|tuple| {
let location = tuple
.values()
.iter()
.map(|x| x.get().to_f32())
.collect::<Vec<f32>>();
self.fontref().denormalize_location(&location)
})
.collect();
Ok(peaks)
}
}
type InstancePositions = Vec<(String, HashMap<String, f32>)>;
type AxisDescription = HashMap<String, (f32, f32, f32)>;
pub fn shared_axes(f_a: &DFont, f_b: &DFont) -> (AxisDescription, InstancePositions) {
let mut axes = f_a.axis_info();
let b_axes = f_b.axis_info();
let a_axes_names: Vec<String> = axes.keys().cloned().collect();
for axis_tag in a_axes_names.iter() {
if !b_axes.contains_key(axis_tag) {
axes.remove(axis_tag);
}
}
for (axis_tag, values) in b_axes.iter() {
let (our_min, _our_default, our_max) = values;
axes.entry(axis_tag.clone())
.and_modify(|(their_min, _their_default, their_max)| {
*their_min = their_min.max(*our_min);
*their_max = their_max.min(*our_max);
});
}
let axis_names: Vec<String> = f_a
.fontref()
.axes()
.iter()
.map(|axis| axis.tag().to_string())
.collect();
let instances = f_a
.fontref()
.named_instances()
.iter()
.map(|ni| {
let name = f_a
.fontref()
.localized_strings(ni.subfamily_name_id())
.english_or_first()
.map_or_else(|| "Unknown".to_string(), |s| s.chars().collect());
let location_map = axis_names.iter().cloned().zip(ni.user_coords()).collect();
(name, location_map)
})
.collect::<Vec<(String, HashMap<String, f32>)>>();
(axes, instances)
}