use std::collections::{BTreeMap, HashSet};
use crate::{language::Language, reporter::Reporter, GlyphId, ResultCode};
use harfrust::{Shaper, ShaperData};
pub struct Checker<'a> {
pub fontref: harfrust::FontRef<'a>,
pub shaper_data: ShaperData,
pub glyph_names: Vec<String>,
pub features: HashSet<String>,
pub cmap: BTreeMap<u32, GlyphId>,
reversed_cmap: BTreeMap<GlyphId, u32>,
}
impl<'a> Checker<'a> {
#[cfg(feature = "skrifa")]
pub fn new(data: &'a [u8]) -> Result<Self, Box<dyn std::error::Error>> {
use skrifa::MetadataProvider;
let font_for_charmap = skrifa::FontRef::from_index(data, 0)?; let font_for_shaping = harfrust::FontRef::from_index(data, 0)?;
Ok(Self::from_parts(
font_for_shaping,
crate::font::glyph_names(&font_for_charmap)?,
crate::font::feature_tags(&font_for_charmap)?,
font_for_charmap
.charmap()
.mappings()
.map(|(character, glyph)| (character, glyph.to_u32()))
.collect(),
))
}
pub fn from_parts(
fontref: harfrust::FontRef<'a>,
glyph_names: Vec<String>,
features: HashSet<String>,
cmap: BTreeMap<u32, GlyphId>,
) -> Self {
let reversed_cmap = cmap.iter().map(|(k, v)| (*v, *k)).collect();
let shaper_data = harfrust::ShaperData::new(&fontref);
Self {
fontref,
glyph_names,
features,
cmap,
reversed_cmap,
shaper_data,
}
}
pub fn shaper(&'a self) -> Shaper<'a> {
self.shaper_data.shaper(&self.fontref).build()
}
pub fn can_shape(&self, text: &str) -> bool {
if text.chars().any(|c| !self.cmap.contains_key(&(c as u32))) {
return false;
}
let mut buffer = harfrust::UnicodeBuffer::new();
buffer.push_str(text);
buffer.guess_segment_properties();
let glyph_buffer = self.shaper().shape(buffer, &[]);
glyph_buffer.glyph_infos().iter().all(|x| x.glyph_id != 0)
}
pub fn codepoint_for(&self, gid: GlyphId) -> Option<u32> {
self.reversed_cmap.get(&gid).copied()
}
pub fn check(&self, language: &Language) -> Reporter {
let mut results = Reporter::default();
for check_object in language.checks.iter() {
let checkresult = check_object.execute(self);
let status = checkresult.status;
results.add(checkresult);
if status == ResultCode::StopNow {
break;
}
}
results
}
}