From e9227818e80617b4eee3ce3410d26b6270b2d25f Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Thu, 21 Mar 2024 13:20:35 -0400 Subject: [PATCH] [skrifa] tthint tests batch 2 More work on #803 Tests for move_point(), move_zp2_point(), move_original(), point_displacement(), SHP, SHC, SHZ, SHPIX, MSIRP, MDAP --- skrifa/src/outline/glyf/hint/engine/mod.rs | 2 +- .../src/outline/glyf/hint/engine/outline.rs | 156 ++++++++++++++- skrifa/src/outline/glyf/hint/zone.rs | 187 +++++++++++++++++- 3 files changed, 340 insertions(+), 5 deletions(-) diff --git a/skrifa/src/outline/glyf/hint/engine/mod.rs b/skrifa/src/outline/glyf/hint/engine/mod.rs index 8089bcf28..ac2c03dae 100644 --- a/skrifa/src/outline/glyf/hint/engine/mod.rs +++ b/skrifa/src/outline/glyf/hint/engine/mod.rs @@ -190,7 +190,7 @@ mod mock { unscaled: vec![Default::default(); 32], points: vec![Default::default(); 64], point_flags: vec![Default::default(); 32], - contours: vec![32], + contours: vec![31], twilight: vec![Default::default(); 32], twilight_flags: vec![Default::default(); 32], } diff --git a/skrifa/src/outline/glyf/hint/engine/outline.rs b/skrifa/src/outline/glyf/hint/engine/outline.rs index a14ceb6c8..b9c6895c2 100644 --- a/skrifa/src/outline/glyf/hint/engine/outline.rs +++ b/skrifa/src/outline/glyf/hint/engine/outline.rs @@ -824,9 +824,11 @@ impl<'a> Engine<'a> { #[cfg(test)] mod tests { - use raw::tables::glyf::PointMarker; - - use super::{super::MockEngine, CoordAxis}; + use super::{super::MockEngine, math, CoordAxis, Engine, ZonePointer}; + use raw::{ + tables::glyf::PointMarker, + types::{F26Dot6, Point}, + }; #[test] fn flip_point() { @@ -960,4 +962,152 @@ mod tests { // untouch point 2 in both axes untouch(2, 1, 1, PointMarker::TOUCHED); } + + #[test] + fn shp() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + set_test_vectors(&mut engine); + engine.graphics.backward_compatibility = false; + engine.graphics.zp0 = ZonePointer::Glyph; + engine.graphics.zp2 = ZonePointer::Glyph; + engine.graphics.rp2 = 1; + let point = engine.graphics.zones[1].point_mut(1).unwrap(); + point.x = F26Dot6::from_bits(132); + point.y = F26Dot6::from_bits(-256); + engine.value_stack.push(1).unwrap(); + engine.op_shp(0).unwrap(); + let point = engine.graphics.zones[1].point(1).unwrap(); + assert_eq!(point.map(F26Dot6::to_bits), Point::new(136, -254)); + } + + #[test] + fn shc() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + set_test_vectors(&mut engine); + engine.graphics.backward_compatibility = false; + engine.graphics.zp0 = ZonePointer::Glyph; + engine.graphics.zp2 = ZonePointer::Glyph; + engine.graphics.rp2 = 1; + let point = engine.graphics.zones[1].point_mut(1).unwrap(); + point.x = F26Dot6::from_bits(132); + point.y = F26Dot6::from_bits(-256); + engine.value_stack.push(0).unwrap(); + engine.op_shc(0).unwrap(); + let points = engine.graphics.zones[1] + .points + .iter() + .map(|p| p.map(F26Dot6::to_bits)) + .take(3) + .collect::>(); + assert_eq!( + points, + &[Point::new(4, 2), Point::new(132, -256), Point::new(4, 2),] + ); + } + + #[test] + fn shz() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + set_test_vectors(&mut engine); + engine.graphics.backward_compatibility = false; + engine.graphics.zp0 = ZonePointer::Glyph; + engine.graphics.zp2 = ZonePointer::Glyph; + engine.graphics.rp2 = 1; + let point = engine.graphics.zones[1].point_mut(1).unwrap(); + point.x = F26Dot6::from_bits(132); + point.y = F26Dot6::from_bits(-256); + engine.value_stack.push(0).unwrap(); + engine.op_shz(0).unwrap(); + let points = engine.graphics.zones[1] + .points + .iter() + .map(|p| p.map(F26Dot6::to_bits)) + .take(3) + .collect::>(); + assert_eq!( + points, + &[Point::new(4, 2), Point::new(132, -256), Point::new(4, 2),] + ); + } + + #[test] + fn shpix() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + set_test_vectors(&mut engine); + engine.graphics.backward_compatibility = false; + engine.graphics.zp2 = ZonePointer::Glyph; + let point = engine.graphics.zones[1].point_mut(1).unwrap(); + point.x = F26Dot6::from_bits(132); + point.y = F26Dot6::from_bits(-256); + // point index + engine.value_stack.push(1).unwrap(); + // amount to move in pixels along freedom vector + engine.value_stack.push(42).unwrap(); + engine.op_shpix().unwrap(); + let point = engine.graphics.zones[1].point(1).unwrap(); + assert_eq!(point.map(F26Dot6::to_bits), Point::new(170, -237)); + } + + #[test] + fn msirp() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + set_test_vectors(&mut engine); + engine.graphics.backward_compatibility = false; + engine.graphics.zp0 = ZonePointer::Glyph; + engine.graphics.zp1 = ZonePointer::Glyph; + let point = engine.graphics.zones[1].point_mut(1).unwrap(); + point.x = F26Dot6::from_bits(132); + point.y = F26Dot6::from_bits(-256); + // point index + engine.value_stack.push(1).unwrap(); + // amount to move in pixels along freedom vector + engine.value_stack.push(-42).unwrap(); + engine.op_msirp(0).unwrap(); + let point = engine.graphics.zones[1].point(1).unwrap(); + assert_eq!(point.map(F26Dot6::to_bits), Point::new(91, -277)); + assert_eq!(engine.graphics.rp0, 0); + // opcode with bit 0 set changes rp0 to point_ix + engine.value_stack.push(4).unwrap(); + engine.value_stack.push(0).unwrap(); + engine.op_msirp(1).unwrap(); + assert_eq!(engine.graphics.rp0, 4); + } + + #[test] + fn mdap() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + set_test_vectors(&mut engine); + engine.graphics.backward_compatibility = false; + engine.graphics.zp0 = ZonePointer::Glyph; + // with rounding + let point = engine.graphics.zones[1].point_mut(1).unwrap(); + point.x = F26Dot6::from_bits(132); + point.y = F26Dot6::from_bits(-256); + engine.value_stack.push(1).unwrap(); + engine.op_mdap(1).unwrap(); + let point = engine.graphics.zones[1].point(1).unwrap(); + assert_eq!(point.map(F26Dot6::to_bits), Point::new(128, -258)); + // without rounding + let point = engine.graphics.zones[1].point_mut(2).unwrap(); + point.x = F26Dot6::from_bits(132); + point.y = F26Dot6::from_bits(-256); + engine.value_stack.push(2).unwrap(); + engine.op_mdap(0).unwrap(); + let point = engine.graphics.zones[1].point(2).unwrap(); + assert_eq!(point.map(F26Dot6::to_bits), Point::new(132, -256)); + } + + fn set_test_vectors(engine: &mut Engine) { + let v = math::normalize14(100, 50); + engine.graphics.proj_vector = v; + engine.graphics.dual_proj_vector = v; + engine.graphics.freedom_vector = v; + engine.graphics.update_projection_state(); + } } diff --git a/skrifa/src/outline/glyf/hint/zone.rs b/skrifa/src/outline/glyf/hint/zone.rs index 296abb0c5..7631450e3 100644 --- a/skrifa/src/outline/glyf/hint/zone.rs +++ b/skrifa/src/outline/glyf/hint/zone.rs @@ -531,6 +531,7 @@ impl GraphicsState<'_> { } } +#[derive(PartialEq, Debug)] pub(crate) struct PointDisplacement { pub zone: ZonePointer, pub point_ix: usize, @@ -550,7 +551,7 @@ impl CoordAxis { #[cfg(test)] mod tests { - use super::{CoordAxis, Zone}; + use super::{math, CoordAxis, GraphicsState, PointDisplacement, Zone, ZonePointer}; use raw::{ tables::glyf::{PointFlags, PointMarker}, types::{F26Dot6, Point}, @@ -637,6 +638,190 @@ mod tests { ); } + #[test] + fn move_point_x() { + let mut mock = MockGraphicsState::new(); + let mut gs = mock.graphics_state(100, 0); + let point_ix = 0; + let orig_x = gs.zones[1].point(point_ix).unwrap().x; + let dx = F26Dot6::from_bits(10); + // backward compatibility is on by default and we don't move x coord + gs.move_point(ZonePointer::Glyph, 0, dx).unwrap(); + assert_eq!(orig_x, gs.zones[1].point(point_ix).unwrap().x); + // disable so we actually move + gs.backward_compatibility = false; + gs.move_point(ZonePointer::Glyph, 0, dx).unwrap(); + let new_x = gs.zones[1].point(point_ix).unwrap().x; + assert_ne!(orig_x, new_x); + assert_eq!(new_x, orig_x + dx) + } + + #[test] + fn move_point_y() { + let mut mock = MockGraphicsState::new(); + let mut gs = mock.graphics_state(0, 100); + let point_ix = 0; + let orig_y = gs.zones[1].point(point_ix).unwrap().y; + let dy = F26Dot6::from_bits(10); + // movement in y is prevented post-iup when backward + // compatibility is enabled + gs.did_iup_x = true; + gs.did_iup_y = true; + gs.move_point(ZonePointer::Glyph, 0, dy).unwrap(); + assert_eq!(orig_y, gs.zones[1].point(point_ix).unwrap().y); + // allow movement + gs.did_iup_x = false; + gs.did_iup_y = false; + gs.move_point(ZonePointer::Glyph, 0, dy).unwrap(); + let new_y = gs.zones[1].point(point_ix).unwrap().y; + assert_ne!(orig_y, new_y); + assert_eq!(new_y, orig_y + dy) + } + + #[test] + fn move_point_x_and_y() { + let mut mock = MockGraphicsState::new(); + let mut gs = mock.graphics_state(100, 50); + let point_ix = 0; + let orig_point = gs.zones[1].point(point_ix).unwrap(); + let dist = F26Dot6::from_bits(10); + // prevent movement in x and y + gs.did_iup_x = true; + gs.did_iup_y = true; + gs.move_point(ZonePointer::Glyph, 0, dist).unwrap(); + assert_eq!(orig_point, gs.zones[1].point(point_ix).unwrap()); + // allow movement + gs.backward_compatibility = false; + gs.did_iup_x = false; + gs.did_iup_y = false; + gs.move_point(ZonePointer::Glyph, 0, dist).unwrap(); + let point = gs.zones[1].point(point_ix).unwrap(); + assert_eq!(point.map(F26Dot6::to_bits), Point::new(4, -16)); + } + + #[test] + fn move_original_x() { + let mut mock = MockGraphicsState::new(); + let mut gs = mock.graphics_state(100, 0); + let point_ix = 0; + let orig_x = gs.zones[1].original(point_ix).unwrap().x; + let dx = F26Dot6::from_bits(10); + gs.move_original(ZonePointer::Glyph, 0, dx).unwrap(); + let new_x = gs.zones[1].original(point_ix).unwrap().x; + assert_eq!(new_x, orig_x + dx) + } + + #[test] + fn move_original_y() { + let mut mock = MockGraphicsState::new(); + let mut gs = mock.graphics_state(0, 100); + let point_ix = 0; + let orig_y = gs.zones[1].original(point_ix).unwrap().y; + let dy = F26Dot6::from_bits(10); + gs.move_original(ZonePointer::Glyph, 0, dy).unwrap(); + let new_y = gs.zones[1].original(point_ix).unwrap().y; + assert_eq!(new_y, orig_y + dy) + } + + #[test] + fn move_original_x_and_y() { + let mut mock = MockGraphicsState::new(); + let mut gs = mock.graphics_state(100, 50); + let point_ix = 0; + let dist = F26Dot6::from_bits(10); + gs.move_original(ZonePointer::Glyph, 0, dist).unwrap(); + let point = gs.zones[1].original(point_ix).unwrap(); + assert_eq!(point.map(F26Dot6::to_bits), Point::new(9, 4)); + } + + #[test] + fn move_zp2_point() { + let mut mock = MockGraphicsState::new(); + let mut gs = mock.graphics_state(100, 50); + gs.zp2 = ZonePointer::Glyph; + let point_ix = 0; + let orig_point = gs.zones[1].point(point_ix).unwrap(); + let dx = F26Dot6::from_bits(10); + let dy = F26Dot6::from_bits(-10); + // prevent movement in x and y + gs.did_iup_x = true; + gs.did_iup_y = true; + gs.move_zp2_point(point_ix, dx, dy, false).unwrap(); + assert_eq!(orig_point, gs.zones[1].point(point_ix).unwrap()); + // allow movement + gs.backward_compatibility = false; + gs.did_iup_x = false; + gs.did_iup_y = false; + gs.move_zp2_point(point_ix, dx, dy, false).unwrap(); + let point = gs.zones[1].point(point_ix).unwrap(); + assert_eq!(point, orig_point + Point::new(dx, dy)); + } + + #[test] + fn point_displacement() { + let mut mock = MockGraphicsState::new(); + let mut gs = mock.graphics_state(100, 50); + gs.zp0 = ZonePointer::Glyph; + gs.rp1 = 0; + assert_eq!( + gs.point_displacement(1).unwrap(), + PointDisplacement { + zone: ZonePointer::Glyph, + point_ix: 0, + dx: F26Dot6::from_f64(-0.1875), + dy: F26Dot6::from_f64(-0.09375), + } + ); + gs.rp2 = 2; + assert_eq!( + gs.point_displacement(0).unwrap(), + PointDisplacement { + zone: ZonePointer::Glyph, + point_ix: 2, + dx: F26Dot6::from_f64(0.390625), + dy: F26Dot6::from_f64(0.203125), + } + ); + } + + struct MockGraphicsState { + points: [Point; 3], + original: [Point; 3], + contours: [u16; 1], + flags: [PointFlags; 3], + } + + impl MockGraphicsState { + fn new() -> Self { + Self { + points: f26dot6_points([(-5, -20), (10, 10), (20, 20)]), + original: f26dot6_points([(0, 0), (10, 10), (20, -42)]), + flags: [PointFlags::default(); 3], + contours: [3], + } + } + + fn graphics_state(&mut self, fv_x: i32, fv_y: i32) -> GraphicsState { + let glyph = Zone { + unscaled: &mut [], + original: &mut self.original, + points: &mut self.points, + contours: &self.contours, + flags: &mut self.flags, + }; + let v = math::normalize14(fv_x, fv_y); + let mut gs = GraphicsState { + zones: [Zone::default(), glyph], + freedom_vector: v, + proj_vector: v, + zp0: ZonePointer::Glyph, + ..Default::default() + }; + gs.update_projection_state(); + gs + } + } + fn point_markers() -> [PointFlags; 2] { let untouched = PointFlags::default(); let mut touched = untouched;