blob: 606415e061d5749975431448fb83aeeb0d61b4f7 [file] [edit]
use crate::Hinting;
use super::{FreeTypeInstance, InstanceOptions, RegularizingPen, SkrifaInstance};
use skrifa::{outline::pen::PathElement, GlyphId};
use std::{io::Write, path::Path};
#[allow(clippy::explicit_write)]
pub fn compare_glyphs(
path: &Path,
options: &InstanceOptions,
(mut ft_instance, mut skrifa_instance): (FreeTypeInstance, SkrifaInstance),
exit_on_fail: bool,
) -> bool {
if !ft_instance.is_scalable() {
// Don't run on bitmap fonts (yet)
return true;
}
if let Hinting::Auto(_) = options.hinting {
// This font is a pathological case for autohinting due to the
// extreme number of generated segments and edges. To be
// precise, it takes longer to test this single font than the
// entire remainder of the Google Fonts corpus so we skip it
// here.
// Discussion at <https://github.com/googlefonts/fontations/issues/1147>
if ft_instance
.family_name()
.unwrap_or_default()
.contains("Handjet")
{
return true;
}
}
let glyph_count = skrifa_instance.glyph_count();
let is_scaled = options.ppem != 0.0;
let mut ft_outline = vec![];
let mut skrifa_outline = vec![];
let mut ok = true;
for gid in 0..glyph_count {
let gid = GlyphId::from(gid);
ft_outline.clear();
let ft_advance = ft_instance
.outline(gid, &mut RegularizingPen::new(&mut ft_outline, is_scaled))
.unwrap();
skrifa_outline.clear();
let maybe_skrifa_advance = skrifa_instance
.outline(
gid,
&mut RegularizingPen::new(&mut skrifa_outline, is_scaled),
)
.unwrap();
// Compare against adjusted metrics when skrifa returns them (currently
// only for TrueType glyphs)
if let Some(skrifa_advance) = maybe_skrifa_advance {
if ft_advance != skrifa_advance {
if let Some((hvar_adv, gvar_adv)) =
skrifa_instance.hvar_and_gvar_advance_deltas(gid)
{
// Some fonts have slight discrepancies between HVAR and
// gvar advance deltas.
// If these _are_ the same but the scaler computed advance
// is different then we definitely have a bug.
// If the difference is greater than 1 then we might have
// a bug.
if hvar_adv == gvar_adv || (ft_advance - skrifa_advance).abs() > 1.0 {
writeln!(
std::io::stderr(),
"[{path:?}#{} ppem: {} coords: {:?} hinting: {:?}] glyph id {} advance doesn't match:\nFreeType: {ft_advance:?}\nSkrifa: {skrifa_advance:?}",
options.index,
options.ppem,
options.coords,
options.hinting,
gid.to_u32(),
)
.unwrap();
if exit_on_fail {
std::process::exit(1);
}
}
}
}
}
if ft_outline != skrifa_outline {
ok = false;
fn outline_to_string(outline: &[PathElement]) -> String {
outline
.iter()
.map(|cmd| format!("{cmd:?}"))
.collect::<Vec<_>>()
.join("\n")
}
let ft_cmds = outline_to_string(&ft_outline);
let skrifa_cmds = outline_to_string(&skrifa_outline);
let diff = similar::TextDiff::from_lines(&ft_cmds, &skrifa_cmds);
let mut diff_str = String::default();
for change in diff.iter_all_changes() {
let sign = match change.tag() {
similar::ChangeTag::Delete => "-",
similar::ChangeTag::Insert => "+",
similar::ChangeTag::Equal => " ",
};
diff_str.push_str(&format!("{sign} {change}"));
}
write!(
std::io::stderr(),
"[{path:?}#{} ppem: {} coords: {:?} hinting {:?}] glyph id {} outline doesn't match:\n{diff_str}",
options.index,
options.ppem,
options.coords,
options.hinting,
gid.to_u32(),
)
.unwrap();
if exit_on_fail {
std::process::exit(1);
}
break;
}
}
ok
}