use super::super::{bytecode::HintError, PointCoord, PointFlags, PHANTOM_POINT_COUNT};
use crate::types::{F2Dot14, GlyphId, Point};
pub trait Hinter<C: PointCoord> {
fn backward_compatibility(&self) -> bool {
true
}
fn hint(&mut self, zone: &mut GlyphZone<'_, C>) -> Result<(), HintError>;
}
pub struct GlyphZone<'a, C: PointCoord> {
pub glyph: GlyphId,
pub bytecode: &'a [u8],
pub points: &'a mut [Point<C>],
pub unscaled: &'a mut [Point<i32>],
pub flags: &'a mut [PointFlags],
pub contours: &'a [u16],
pub phantom: &'a mut [Point<C>; PHANTOM_POINT_COUNT],
pub is_composite: bool,
pub coords: &'a [F2Dot14],
}
#[cfg(test)]
mod tests {
const PPEM: f32 = 16.0;
use super::super::{Outline, OutlineError, OutlinePlan, OutlineTables, Scale26Dot6};
use super::*;
use crate::tables::glyf::bytecode::{HintErrorKind, Opcode, Program};
use crate::{
types::{F26Dot6, GlyphId},
FontRef, TableProvider,
};
#[derive(Default)]
struct RecordingHinter {
calls: usize,
bytecode_len: usize,
points: usize,
composite: bool,
}
#[test]
fn instructed_glyph_reaches_the_hinter() {
let font = FontRef::new(font_test_data::COUSINE_HINT_SUBSET).unwrap();
let context = OutlineTables::new(&font).unwrap();
let n = font.maxp().unwrap().num_glyphs();
let mut outline = Outline::<Scale26Dot6>::new();
let mut hinted = 0;
for gid in 0..n {
let plan = OutlinePlan::new(&context, GlyphId::from(gid)).unwrap();
if !plan.has_hinting || plan.glyph_data.is_none() {
continue;
}
let mut hinter = RecordingHinter::default();
outline
.load_hinted(&context, GlyphId::from(gid), Some(PPEM), &mut hinter)
.unwrap();
assert_eq!(hinter.calls, 1, "gid {gid}");
assert!(hinter.bytecode_len > 0);
assert_eq!(hinter.points as u32, plan.num_points);
hinted += 1;
}
assert!(hinted > 0, "expected some instructed glyphs");
}
#[test]
fn hinting_is_optional() {
let font = FontRef::new(font_test_data::COUSINE_HINT_SUBSET).unwrap();
let context = OutlineTables::new(&font).unwrap();
let n = font.maxp().unwrap().num_glyphs();
let gid = (0..n)
.map(GlyphId::from)
.find(|gid| {
OutlinePlan::new(&context, *gid)
.map(|i| i.has_hinting && i.glyph_data.is_some())
.unwrap_or(false)
})
.unwrap();
let mut outline = Outline::<Scale26Dot6>::new();
assert!(outline.load(&context, gid, Some(PPEM)).is_ok());
}
impl Hinter<F26Dot6> for RecordingHinter {
fn hint(&mut self, zone: &mut GlyphZone<'_, F26Dot6>) -> Result<(), HintError> {
self.calls += 1;
self.bytecode_len = zone.bytecode.len();
self.points = zone.points.len();
self.composite = zone.is_composite;
assert_eq!(zone.unscaled.len(), zone.points.len());
assert!(!zone.contours.is_empty());
Ok(())
}
}
struct FailingHinter;
impl Hinter<F26Dot6> for FailingHinter {
fn hint(&mut self, zone: &mut GlyphZone<'_, F26Dot6>) -> Result<(), HintError> {
Err(HintError {
program: Program::Glyph,
glyph_id: Some(zone.glyph),
pc: 3,
opcode: Some(Opcode::SVTCA0),
kind: HintErrorKind::ValueStackOverflow,
})
}
}
#[test]
fn a_hinters_error_survives() {
let font = FontRef::new(font_test_data::COUSINE_HINT_SUBSET).unwrap();
let context = OutlineTables::new(&font).unwrap();
let n = font.maxp().unwrap().num_glyphs();
let gid = (0..n)
.map(GlyphId::from)
.find(|gid| {
OutlinePlan::new(&context, *gid)
.map(|i| i.has_hinting && i.glyph_data.is_some())
.unwrap_or(false)
})
.unwrap();
let mut outline = Outline::<Scale26Dot6>::new();
let err = outline
.load_hinted(&context, gid, Some(PPEM), &mut FailingHinter)
.unwrap_err();
let OutlineError::Hinting(err) = err else {
panic!("expected a hinting error, got {err:?}");
};
assert_eq!(err.kind, HintErrorKind::ValueStackOverflow);
assert_eq!(err.glyph_id, Some(gid));
assert_eq!(err.pc, 3);
}
}