From 3c23e1600974c270fa29ce89db529cadea4b6feb Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Mon, 29 Jan 2024 15:30:15 -0500 Subject: [PATCH] [skrifa] tthint: value stack/basic instructions Adds a type for managing the interpreter value stack along with instructions to manipulate the stack as well as basic arithmetic and logical instructions. Covers implementations for 47 opcodes. More progress on #620 --- .../src/outline/glyf/hint/code_state/args.rs | 99 ++++++ .../src/outline/glyf/hint/code_state/mod.rs | 8 + skrifa/src/outline/glyf/hint/engine/arith.rs | 244 +++++++++++++ .../src/outline/glyf/hint/engine/logical.rs | 303 ++++++++++++++++ skrifa/src/outline/glyf/hint/engine/mod.rs | 76 ++++ skrifa/src/outline/glyf/hint/engine/stack.rs | 221 ++++++++++++ skrifa/src/outline/glyf/hint/mod.rs | 3 + skrifa/src/outline/glyf/hint/value_stack.rs | 328 ++++++++++++++++++ 8 files changed, 1282 insertions(+) create mode 100644 skrifa/src/outline/glyf/hint/code_state/args.rs create mode 100644 skrifa/src/outline/glyf/hint/code_state/mod.rs create mode 100644 skrifa/src/outline/glyf/hint/engine/arith.rs create mode 100644 skrifa/src/outline/glyf/hint/engine/logical.rs create mode 100644 skrifa/src/outline/glyf/hint/engine/mod.rs create mode 100644 skrifa/src/outline/glyf/hint/engine/stack.rs create mode 100644 skrifa/src/outline/glyf/hint/value_stack.rs diff --git a/skrifa/src/outline/glyf/hint/code_state/args.rs b/skrifa/src/outline/glyf/hint/code_state/args.rs new file mode 100644 index 000000000..cfa65e1e4 --- /dev/null +++ b/skrifa/src/outline/glyf/hint/code_state/args.rs @@ -0,0 +1,99 @@ +//! Inline instruction arguments. + +/// Support for decoding a sequence of bytes or words from the +/// instruction stream. +#[derive(Copy, Clone, Default, Debug)] +pub struct Args<'a> { + bytes: &'a [u8], + is_words: bool, +} + +impl<'a> Args<'a> { + pub(crate) fn new(bytes: &'a [u8], is_words: bool) -> Self { + Self { bytes, is_words } + } + + /// Returns the number of arguments in the list. + pub fn len(&self) -> usize { + if self.is_words { + self.bytes.len() / 2 + } else { + self.bytes.len() + } + } + + /// Returns true if the argument list is empty. + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } + + /// Returns an iterator over the argument values. + pub fn values(&self) -> impl Iterator + 'a + Clone { + let bytes = if self.is_words { &[] } else { self.bytes }; + let words = if self.is_words { self.bytes } else { &[] }; + bytes + .iter() + .map(|byte| *byte as u32 as i32) + .chain(words.chunks_exact(2).map(|chunk| { + let word = ((chunk[0] as u16) << 8) | chunk[1] as u16; + // Double cast to ensure sign extension + word as i16 as i32 + })) + } +} + +/// Mock for testing arguments. +#[cfg(test)] +pub(crate) struct MockArgs { + bytes: Vec, + is_words: bool, +} + +#[cfg(test)] +impl MockArgs { + pub fn from_bytes(bytes: &[u8]) -> Self { + Self { + bytes: bytes.into(), + is_words: false, + } + } + + pub fn from_words(words: &[i16]) -> Self { + Self { + bytes: words + .iter() + .map(|word| *word as u16) + .flat_map(|word| vec![(word >> 8) as u8, word as u8]) + .collect(), + is_words: true, + } + } + + pub fn args(&self) -> Args { + Args { + bytes: &self.bytes, + is_words: self.is_words, + } + } +} + +#[cfg(test)] +mod tests { + use super::MockArgs; + + #[test] + fn byte_args() { + let values = [5, 2, 85, 92, 26, 42, u8::MIN, u8::MAX]; + let mock = MockArgs::from_bytes(&values); + let decoded = mock.args().values().collect::>(); + assert!(values.iter().map(|x| *x as i32).eq(decoded.iter().copied())); + } + + #[test] + fn word_args() { + let values = [-5, 2, 2845, 92, -26, 42, i16::MIN, i16::MAX]; + let mock = MockArgs::from_words(&values); + let decoded = mock.args().values().collect::>(); + assert!(values.iter().map(|x| *x as i32).eq(decoded.iter().copied())); + } +} diff --git a/skrifa/src/outline/glyf/hint/code_state/mod.rs b/skrifa/src/outline/glyf/hint/code_state/mod.rs new file mode 100644 index 000000000..42c58cb2a --- /dev/null +++ b/skrifa/src/outline/glyf/hint/code_state/mod.rs @@ -0,0 +1,8 @@ +//! State for managing active programs and decoding instructions. + +mod args; + +pub use args::Args; + +#[cfg(test)] +pub(crate) use args::MockArgs; diff --git a/skrifa/src/outline/glyf/hint/engine/arith.rs b/skrifa/src/outline/glyf/hint/engine/arith.rs new file mode 100644 index 000000000..2e73adfc4 --- /dev/null +++ b/skrifa/src/outline/glyf/hint/engine/arith.rs @@ -0,0 +1,244 @@ +//! Arithmetic and math instructions. +//! +//! See + +// 10 instructions + +use super::{super::math, Engine, HintErrorKind, OpResult}; + +impl<'a> Engine<'a> { + /// ADD[] (0x60) + /// + /// Pops: n1, n2 (F26Dot6) + /// Pushes: (n2 + n1) + /// + /// Pops n1 and n2 off the stack and pushes the sum of the two elements + /// onto the stack. + /// + /// See + /// and + pub(super) fn op_add(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok(a.wrapping_add(b))) + } + + /// SUB[] (0x61) + /// + /// Pops: n1, n2 (F26Dot6) + /// Pushes: (n2 - n1) + /// + /// Pops n1 and n2 off the stack and pushes the difference of the two + /// elements onto the stack. + /// + /// See + /// and + pub(super) fn op_sub(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok(a.wrapping_sub(b))) + } + + /// DIV[] (0x62) + /// + /// Pops: n1, n2 (F26Dot6) + /// Pushes: (n2 / n1) + /// + /// Pops n1 and n2 off the stack and pushes onto the stack the quotient + /// obtained by dividing n2 by n1. Note that this truncates rather than + /// rounds the value. + /// + /// See + /// and + pub(super) fn op_div(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| { + if b == 0 { + Err(HintErrorKind::DivideByZero) + } else { + Ok(math::mul_div_no_round(a, 64, b)) + } + }) + } + + /// MUL[] (0x63) + /// + /// Pops: n1, n2 (F26Dot6) + /// Pushes: (n2 * n1) + /// + /// Pops n1 and n2 off the stack and pushes onto the stack the product of + /// the two elements. + /// + /// See + /// and + pub(super) fn op_mul(&mut self) -> OpResult { + self.value_stack + .apply_binary(|a, b| Ok(math::mul_div(a, b, 64))) + } + + /// ABS[] (0x64) + /// + /// Pops: n + /// Pushes: |n|: absolute value of n (F26Dot6) + /// + /// Pops n off the stack and pushes onto the stack the absolute value of n. + /// + /// See + /// and + pub(super) fn op_abs(&mut self) -> OpResult { + self.value_stack.apply_unary(|n| Ok(n.wrapping_abs())) + } + + /// NEG[] (0x65) + /// + /// Pops: n1 + /// Pushes: -n1: negation of n1 (F26Dot6) + /// + /// This instruction pops n1 off the stack and pushes onto the stack the + /// negated value of n1. + /// + /// See + /// and + pub(super) fn op_neg(&mut self) -> OpResult { + self.value_stack.apply_unary(|n1| Ok(n1.wrapping_neg())) + } + + /// FLOOR[] (0x66) + /// + /// Pops: n1: number whose floor is desired (F26Dot6) + /// Pushes: n: floor of n1 (F26Dot6) + /// + /// Pops n1 and returns n, the greatest integer value less than or equal to n1. + /// + /// See + /// and + pub(super) fn op_floor(&mut self) -> OpResult { + self.value_stack.apply_unary(|n1| Ok(math::floor(n1))) + } + + /// CEILING[] (0x67) + /// + /// Pops: n1: number whose ceiling is desired (F26Dot6) + /// Pushes: n: ceiling of n1 (F26Dot6) + /// + /// Pops n1 and returns n, the least integer value greater than or equal to n1. + /// + /// See + /// and + pub(super) fn op_ceiling(&mut self) -> OpResult { + self.value_stack.apply_unary(|n1| Ok(math::ceil(n1))) + } + + /// MAX[] (0x8B) + /// + /// Pops: e1, e2 + /// Pushes: maximum of e1 and e2 + /// + /// Pops two elements, e1 and e2, from the stack and pushes the larger of + /// these two quantities onto the stack. + /// + /// See + /// and + pub(super) fn op_max(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok(a.max(b))) + } + + /// MIN[] (0x8C) + /// + /// Pops: e1, e2 + /// Pushes: minimum of e1 and e2 + /// + /// Pops two elements, e1 and e2, from the stack and pushes the smaller + /// of these two quantities onto the stack. + /// + /// See + /// and + pub(super) fn op_min(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok(a.min(b))) + } +} + +#[cfg(test)] +mod tests { + use super::{super::MockEngine, math, HintErrorKind}; + + /// Test the binary operations that don't require fixed point + /// arithmetic. + #[test] + fn simple_binops() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + for a in -10..=10 { + for b in -10..=10 { + let input = &[a, b]; + engine.test_exec(input, a + b, |engine| { + engine.op_add().unwrap(); + }); + engine.test_exec(input, a - b, |engine| { + engine.op_sub().unwrap(); + }); + engine.test_exec(input, a.max(b), |engine| { + engine.op_max().unwrap(); + }); + engine.test_exec(input, a.min(b), |engine| { + engine.op_min().unwrap(); + }); + } + } + } + + /// Test the unary operations that don't require fixed point + /// arithmetic. + #[test] + fn simple_unops() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + for a in -10..=10 { + let input = &[a]; + engine.test_exec(input, -a, |engine| { + engine.op_neg().unwrap(); + }); + engine.test_exec(input, a.abs(), |engine| { + engine.op_abs().unwrap(); + }); + } + } + + #[test] + fn f26dot6_binops() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + for a in -10..=10 { + for b in -10..=10 { + let a = a * 64 + 30; + let b = b * 64 - 30; + let input = &[a, b]; + engine.test_exec(input, math::mul_div(a, b, 64), |engine| { + engine.op_mul().unwrap(); + }); + if b != 0 { + engine.test_exec(input, math::mul_div_no_round(a, 64, b), |engine| { + engine.op_div().unwrap(); + }); + } else { + engine.value_stack.push(a).unwrap(); + engine.value_stack.push(b).unwrap(); + assert!(matches!(engine.op_div(), Err(HintErrorKind::DivideByZero))); + } + } + } + } + + #[test] + fn f26dot6_unops() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + for a in -10..=10 { + for b in -10..=10 { + let a = a * 64 + b; + let input = &[a]; + engine.test_exec(input, math::floor(a), |engine| { + engine.op_floor().unwrap(); + }); + engine.test_exec(input, math::ceil(a), |engine| { + engine.op_ceiling().unwrap(); + }); + } + } + } +} diff --git a/skrifa/src/outline/glyf/hint/engine/logical.rs b/skrifa/src/outline/glyf/hint/engine/logical.rs new file mode 100644 index 000000000..5283971b4 --- /dev/null +++ b/skrifa/src/outline/glyf/hint/engine/logical.rs @@ -0,0 +1,303 @@ +//! Logical functions. +//! +//! See + +// 11 instructions + +use super::{Engine, OpResult}; + +impl<'a> Engine<'a> { + /// Less than. + /// + /// LT[] (0x50) + /// + /// Pops: e1, e2 + /// Pushes: Boolean value + /// + /// First pops e2, then pops e1 off the stack and compares them: if e1 is + /// less than e2, 1, signifying TRUE, is pushed onto the stack. If e1 is + /// not less than e2, 0, signifying FALSE, is placed onto the stack. + /// + /// See + /// and + pub(super) fn op_lt(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok((a < b) as i32)) + } + + /// Less than or equal. + /// + /// LTEQ[] (0x51) + /// + /// Pops: e1, e2 + /// Pushes: Boolean value + /// + /// Pops e2 and e1 off the stack and compares them. If e1 is less than or + /// equal to e2, 1, signifying TRUE, is pushed onto the stack. If e1 is + /// not less than or equal to e2, 0, signifying FALSE, is placed onto the + /// stack. + /// + /// See + /// and + pub(super) fn op_lteq(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok((a <= b) as i32)) + } + + /// Greather than. + /// + /// GT[] (0x52) + /// + /// Pops: e1, e2 + /// Pushes: Boolean value + /// + /// First pops e2 then pops e1 off the stack and compares them. If e1 is + /// greater than e2, 1, signifying TRUE, is pushed onto the stack. If e1 + /// is not greater than e2, 0, signifying FALSE, is placed onto the stack. + /// + /// See + /// and + pub(super) fn op_gt(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok((a > b) as i32)) + } + + /// Greater than or equal. + /// + /// GTEQ[] (0x53) + /// + /// Pops: e1, e2 + /// Pushes: Boolean value + /// + /// Pops e1 and e2 off the stack and compares them. If e1 is greater than + /// or equal to e2, 1, signifying TRUE, is pushed onto the stack. If e1 + /// is not greater than or equal to e2, 0, signifying FALSE, is placed + /// onto the stack. + /// + /// See + /// and + pub(super) fn op_gteq(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok((a >= b) as i32)) + } + + /// Equal. + /// + /// EQ[] (0x54) + /// + /// Pops: e1, e2 + /// Pushes: Boolean value + /// + /// Pops e1 and e2 off the stack and compares them. If they are equal, 1, + /// signifying TRUE is pushed onto the stack. If they are not equal, 0, + /// signifying FALSE is placed onto the stack. + /// + /// See + /// and + pub(super) fn op_eq(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok((a == b) as i32)) + } + + /// Not equal. + /// + /// NEQ[] (0x55) + /// + /// Pops: e1, e2 + /// Pushes: Boolean value + /// + /// Pops e1 and e2 from the stack and compares them. If they are not equal, + /// 1, signifying TRUE, is pushed onto the stack. If they are equal, 0, + /// signifying FALSE, is placed on the stack. + /// + /// See + /// and + pub(super) fn op_neq(&mut self) -> OpResult { + self.value_stack.apply_binary(|a, b| Ok((a != b) as i32)) + } + + /// Odd. + /// + /// ODD[] (0x56) + /// + /// Pops: e1 + /// Pushes: Boolean value + /// + /// Tests whether the number at the top of the stack is odd. Pops e1 from + /// the stack and rounds it as specified by the round_state before testing + /// it. After the value is rounded, it is shifted from a fixed point value + /// to an integer value (any fractional values are ignored). If the integer + /// value is odd, one, signifying TRUE, is pushed onto the stack. If it is + /// even, zero, signifying FALSE is placed onto the stack. + /// + /// See + /// and + pub(super) fn op_odd(&mut self) -> OpResult { + let round_state = self.graphics_state.round_state; + self.value_stack + .apply_unary(|e1| Ok((round_state.round(e1) & 127 == 64) as i32)) + } + + /// Even. + /// + /// EVEN[] (0x57) + /// + /// Pops: e1 + /// Pushes: Boolean value + /// + /// Tests whether the number at the top of the stack is even. Pops e1 off + /// the stack and rounds it as specified by the round_state before testing + /// it. If the rounded number is even, one, signifying TRUE, is pushed onto + /// the stack if it is odd, zero, signifying FALSE, is placed onto the + /// stack. + /// + /// See + /// and + pub(super) fn op_even(&mut self) -> OpResult { + let round_state = self.graphics_state.round_state; + self.value_stack + .apply_unary(|e1| Ok((round_state.round(e1) & 127 == 0) as i32)) + } + + /// Logical and. + /// + /// AND[] (0x5A) + /// + /// Pops: e1, e2 + /// Pushes: Boolean value + /// + /// Pops e1 and e2 off the stack and pushes onto the stack the result of a + /// logical and of the two elements. Zero is returned if either or both of + /// the elements are FALSE (have the value zero). One is returned if both + /// elements are TRUE (have a non zero value). + /// + /// See + /// and + pub(super) fn op_and(&mut self) -> OpResult { + self.value_stack + .apply_binary(|a, b| Ok((a != 0 && b != 0) as i32)) + } + + /// Logical or. + /// + /// OR[] (0x5B) + /// + /// Pops: e1, e2 + /// Pushes: Boolean value + /// + /// Pops e1 and e2 off the stack and pushes onto the stack the result of a + /// logical or operation between the two elements. Zero is returned if both + /// of the elements are FALSE. One is returned if either one or both of the + /// elements are TRUE (has a nonzero value). + /// + /// See + /// and + pub(super) fn op_or(&mut self) -> OpResult { + self.value_stack + .apply_binary(|a, b| Ok((a != 0 || b != 0) as i32)) + } + + /// Logical not. + /// + /// NOT[] (0x5C) + /// + /// Pops: e + /// Pushes: (not e): logical negation of e + /// + /// Pops e off the stack and returns the result of a logical NOT operation + /// performed on e. If originally zero, one is pushed onto the stack if + /// originally nonzero, zero is pushed onto the stack. + /// + /// See + /// and + pub(super) fn op_not(&mut self) -> OpResult { + self.value_stack.apply_unary(|e| Ok((e == 0) as i32)) + } +} + +#[cfg(test)] +mod tests { + use super::super::MockEngine; + + #[test] + fn compare_ops() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + for a in -10..=10 { + for b in -10..=10 { + let input = &[a, b]; + engine.test_exec(input, a < b, |engine| { + engine.op_lt().unwrap(); + }); + engine.test_exec(input, a <= b, |engine| { + engine.op_lteq().unwrap(); + }); + engine.test_exec(input, a > b, |engine| { + engine.op_gt().unwrap(); + }); + engine.test_exec(input, a >= b, |engine| { + engine.op_gteq().unwrap(); + }); + engine.test_exec(input, a == b, |engine| { + engine.op_eq().unwrap(); + }); + engine.test_exec(input, a != b, |engine| { + engine.op_neq().unwrap(); + }); + } + } + } + + #[test] + fn parity_ops() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + // These operate on 26.6 so values are multiple of 64 + let cases = [ + // (input, is_even) + (0, true), + (64, false), + (128, true), + (192, false), + (256, true), + (57, false), + (-128, true), + ]; + for (input, is_even) in cases { + engine.test_exec(&[input], is_even, |engine| { + engine.op_even().unwrap(); + }); + } + for (input, is_even) in cases { + engine.test_exec(&[input], !is_even, |engine| { + engine.op_odd().unwrap(); + }); + } + } + + #[test] + fn not_op() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + engine.test_exec(&[0], 1, |engine| { + engine.op_not().unwrap(); + }); + engine.test_exec(&[234234], 0, |engine| { + engine.op_not().unwrap(); + }); + } + + #[test] + fn and_or_ops() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + for a in -10..=10 { + for b in -10..=10 { + let input = &[a, b]; + let a = a != 0; + let b = b != 0; + engine.test_exec(input, a && b, |engine| { + engine.op_and().unwrap(); + }); + engine.test_exec(input, a || b, |engine| { + engine.op_or().unwrap(); + }); + } + } + } +} diff --git a/skrifa/src/outline/glyf/hint/engine/mod.rs b/skrifa/src/outline/glyf/hint/engine/mod.rs new file mode 100644 index 000000000..85d7c0764 --- /dev/null +++ b/skrifa/src/outline/glyf/hint/engine/mod.rs @@ -0,0 +1,76 @@ +//! TrueType bytecode interpreter. + +mod arith; +mod logical; +mod stack; + +use super::{error::HintErrorKind, graphics_state::GraphicsState, value_stack::ValueStack}; + +pub type OpResult = Result<(), HintErrorKind>; + +/// TrueType bytecode interpreter. +pub struct Engine<'a> { + graphics_state: GraphicsState<'a>, + value_stack: ValueStack<'a>, +} + +#[cfg(test)] +use mock::MockEngine; + +#[cfg(test)] +mod mock { + use super::{Engine, GraphicsState, ValueStack}; + + /// Mock engine for testing. + pub(super) struct MockEngine { + value_stack: Vec, + } + + impl MockEngine { + pub fn new() -> Self { + Self { + value_stack: vec![0; 32], + } + } + + pub fn engine(&mut self) -> Engine { + Engine { + graphics_state: GraphicsState::default(), + value_stack: ValueStack::new(&mut self.value_stack), + } + } + } + + impl Default for MockEngine { + fn default() -> Self { + Self::new() + } + } + + impl<'a> Engine<'a> { + /// Helper to push values to the stack, invoke a callback and check + /// the expected result. + pub(super) fn test_exec( + &mut self, + push: &[i32], + expected_result: impl Into, + mut f: impl FnMut(&mut Engine), + ) { + for &val in push { + self.value_stack.push(val).unwrap(); + } + f(self); + assert_eq!(self.value_stack.pop().ok(), Some(expected_result.into())); + } + + /// Helper to push values to the stack, invoke a callback and check + /// the expected result. + pub(super) fn test_exec2(&mut self, push: &[i32], mut f: impl FnMut(&mut Engine)) -> i32 { + for &val in push { + self.value_stack.push(val).unwrap(); + } + f(self); + self.value_stack.pop().unwrap() + } + } +} diff --git a/skrifa/src/outline/glyf/hint/engine/stack.rs b/skrifa/src/outline/glyf/hint/engine/stack.rs new file mode 100644 index 000000000..c3f4f8fb3 --- /dev/null +++ b/skrifa/src/outline/glyf/hint/engine/stack.rs @@ -0,0 +1,221 @@ +//! Managing the stack and pushing data onto the interpreter stack. +//! +//! See +//! and + +// 26 instructions + +use super::{super::code_state::Args, Engine, OpResult}; + +impl<'a> Engine<'a> { + /// Duplicate top stack element. + /// + /// DUP[] (0x20) + /// + /// Pops: e + /// Pushes: e, e + /// + /// Duplicates the element at the top of the stack. + /// + /// See + /// and + pub(super) fn op_dup(&mut self) -> OpResult { + self.value_stack.dup() + } + + /// Pop top stack element. + /// + /// POP[] (0x21) + /// + /// Pops: e + /// + /// Pops the top element of the stack. + /// + /// See + /// and + pub(super) fn op_pop(&mut self) -> OpResult { + self.value_stack.pop()?; + Ok(()) + } + + /// Clear the entire stack. + /// + /// CLEAR[] (0x22) + /// + /// Pops: all the items on the stack + /// + /// Clears all elements from the stack. + /// + /// See + /// and + pub(super) fn op_clear(&mut self) -> OpResult { + self.value_stack.clear(); + Ok(()) + } + + /// Swap the top two elements on the stack. + /// + /// SWAP[] (0x23) + /// + /// Pops: e2, e1 + /// Pushes: e1, e2 + /// + /// Swaps the top two elements of the stack making the old top element the + /// second from the top and the old second element the top element. + /// + /// See + /// and + pub(super) fn op_swap(&mut self) -> OpResult { + self.value_stack.swap() + } + + /// Returns the depth of the stack. + /// + /// DEPTH[] (0x24) + /// + /// Pushes: n; number of elements + /// + /// Pushes n, the number of elements currently in the stack onto the stack. + /// + /// See + /// and + pub(super) fn op_depth(&mut self) -> OpResult { + let n = self.value_stack.len(); + self.value_stack.push(n as i32) + } + + /// Copy the indexed element to the top of the stack. + /// + /// CINDEX[] (0x25) + /// + /// Pops: k: stack element number + /// Pushes: ek: indexed element + /// + /// Puts a copy of the kth stack element on the top of the stack. + /// + /// See + /// and + pub(super) fn op_cindex(&mut self) -> OpResult { + self.value_stack.copy_index() + } + + /// Move the indexed element to the top of the stack. + /// + /// MINDEX[] (0x26) + /// + /// Pops: k: stack element number + /// Pushes: ek: indexed element + /// + /// Moves the indexed element to the top of the stack. + /// + /// See + /// and + pub(super) fn op_mindex(&mut self) -> OpResult { + self.value_stack.move_index() + } + + /// Roll the top three stack elements. + /// + /// ROLL[] (0x8a) + /// + /// Pops: a, b, c (top three stack elements) + /// Pushes: b, a, c (elements reordered) + /// + /// Performs a circular shift of the top three objects on the stack with + /// the effect being to move the third element to the top of the stack + /// and to move the first two elements down one position. ROLL is + /// equivalent to MINDEX[] 3. + /// + /// See + /// and + pub(super) fn op_roll(&mut self) -> OpResult { + self.value_stack.roll() + } + + /// Push data onto the interpreter stack. + /// + /// NPUSHB[] (0x8a) + /// + /// Takes n unsigned bytes from the instruction stream, where n is an + /// unsigned integer in the range (0..255), and pushes them onto the stack. + /// n itself is not pushed onto the stack. + /// + /// NPUSHW[] (0x41) + /// + /// Takes n 16-bit signed words from the instruction stream, where n is an + /// unsigned integer in the range (0..255), and pushes them onto the stack. + /// n itself is not pushed onto the stack. + /// + /// PUSHB[abc] (0xB0 - 0xB7) + /// + /// Takes the specified number of bytes from the instruction stream and + /// pushes them onto the interpreter stack. + /// The variables a, b, and c are binary digits representing numbers from + /// 000 to 111 (0-7 in binary). Because the actual number of bytes (n) is + /// from 1 to 8, 1 is automatically added to the ABC figure to obtain the + /// actual number of bytes pushed. + /// + /// PUSHW[abc] (0xB8 - 0xBF) + /// + /// Takes the specified number of words from the instruction stream and + /// pushes them onto the interpreter stack. + /// The variables a, b, and c are binary digits representing numbers from + /// 000 to 111 (0-7 binary). Because the actual number of bytes (n) is from + /// 1 to 8, 1 is automatically added to the abc figure to obtain the actual + /// number of bytes pushed. + /// + /// See + /// and + pub(super) fn op_push(&mut self, args: &Args) -> OpResult { + self.value_stack.push_args(args) + } +} + +#[cfg(test)] +mod tests { + use super::super::{super::code_state::MockArgs, MockEngine}; + + #[test] + fn stack_ops() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + let byte_args = MockArgs::from_bytes(&[2, 4, 6, 8]); + let word_args = MockArgs::from_words(&[-2000, 4000, -6000, 8000]); + let initial_stack = byte_args + .args() + .values() + .chain(word_args.args().values()) + .collect::>(); + // Push instructions + engine.op_push(&byte_args.args()).unwrap(); + engine.op_push(&word_args.args()).unwrap(); + assert_eq!(engine.value_stack.values(), initial_stack); + // DEPTH[] + engine.op_depth().unwrap(); + assert_eq!( + engine.value_stack.pop().ok(), + Some(initial_stack.len() as i32) + ); + // POP[] + engine.op_pop().unwrap(); + engine.op_pop().unwrap(); + assert_eq!( + engine.value_stack.values(), + &initial_stack[..initial_stack.len() - 2] + ); + // SWAP[] + engine.op_swap().unwrap(); + assert_eq!(&engine.value_stack.values()[4..], &[4000, -2000]); + // ROLL[] + engine.op_roll().unwrap(); + assert_eq!(&engine.value_stack.values()[3..], &[4000, -2000, 8]); + // CINDEX[] + engine.value_stack.push(4).unwrap(); + engine.op_cindex().unwrap(); + assert_eq!(engine.value_stack.peek(), Some(6)); + // MINDEX[] + engine.value_stack.push(3).unwrap(); + engine.op_mindex().unwrap(); + assert_eq!(engine.value_stack.peek(), Some(-2000)); + } +} diff --git a/skrifa/src/outline/glyf/hint/mod.rs b/skrifa/src/outline/glyf/hint/mod.rs index 3762076cc..78c95d6ed 100644 --- a/skrifa/src/outline/glyf/hint/mod.rs +++ b/skrifa/src/outline/glyf/hint/mod.rs @@ -1,8 +1,11 @@ //! TrueType hinting. +mod code_state; +mod engine; mod error; mod graphics_state; mod math; +mod value_stack; use read_fonts::{ tables::glyf::PointFlags, diff --git a/skrifa/src/outline/glyf/hint/value_stack.rs b/skrifa/src/outline/glyf/hint/value_stack.rs new file mode 100644 index 000000000..317805290 --- /dev/null +++ b/skrifa/src/outline/glyf/hint/value_stack.rs @@ -0,0 +1,328 @@ +//! Value stack for TrueType interpreter. + +use super::{code_state::Args, error::HintErrorKind}; + +use HintErrorKind::{ValueStackOverflow, ValueStackUnderflow}; + +/// Value stack for the TrueType interpreter. +/// +/// This uses a slice as the backing store rather than a `Vec` to enable +/// support for allocation from user buffers. +/// +/// See +pub struct ValueStack<'a> { + values: &'a mut [i32], + top: usize, +} + +impl<'a> ValueStack<'a> { + pub fn new(values: &'a mut [i32]) -> Self { + Self { values, top: 0 } + } + + /// Returns the depth of the stack + /// + pub fn len(&self) -> usize { + self.top + } + + pub fn is_empty(&self) -> bool { + self.top == 0 + } + + pub fn values(&self) -> &[i32] { + &self.values[..self.top] + } + + pub fn push(&mut self, value: i32) -> Result<(), HintErrorKind> { + let ptr = self + .values + .get_mut(self.top) + .ok_or(HintErrorKind::ValueStackOverflow)?; + *ptr = value; + self.top += 1; + Ok(()) + } + + /// Pushes values that have been decoded from the instruction stream + /// onto the stack. + /// + /// Implements the PUSHB[], PUSHW[], NPUSHB[] and NPUSHW[] instructions. + /// + /// See + pub fn push_args(&mut self, args: &Args) -> Result<(), HintErrorKind> { + let push_count = args.len(); + let stack_base = self.top; + for (stack_value, value) in self + .values + .get_mut(stack_base..stack_base + push_count) + .ok_or(ValueStackOverflow)? + .iter_mut() + .zip(args.values()) + { + *stack_value = value; + } + self.top += push_count; + Ok(()) + } + + pub fn peek(&mut self) -> Option { + if self.top > 0 { + self.values.get(self.top - 1).copied() + } else { + None + } + } + + /// Pops a value from the stack. + /// + /// Implements the POP[] instruction. + /// + /// See + pub fn pop(&mut self) -> Result { + let value = self.peek().ok_or(ValueStackUnderflow)?; + self.top -= 1; + Ok(value) + } + + /// Convenience method for instructions that pop values that are used as an + /// index. + pub fn pop_usize(&mut self) -> Result { + Ok(self.pop()? as usize) + } + + /// Applies a unary operation. + /// + /// Pops `a` from the stack and pushes `op(a)`. + pub fn apply_unary( + &mut self, + mut op: impl FnMut(i32) -> Result, + ) -> Result<(), HintErrorKind> { + let a = self.pop()?; + self.push(op(a)?) + } + + /// Applies a binary operation. + /// + /// Pops `b` and `a` from the stack and pushes `op(a, b)`. + pub fn apply_binary( + &mut self, + mut op: impl FnMut(i32, i32) -> Result, + ) -> Result<(), HintErrorKind> { + let b = self.pop()?; + let a = self.pop()?; + self.push(op(a, b)?) + } + + /// Clear the entire stack. + /// + /// Implements the CLEAR[] instruction. + /// + /// See + pub fn clear(&mut self) { + self.top = 0; + } + + /// Duplicate top stack element. + /// + /// Implements the DUP[] instruction. + /// + /// See + pub fn dup(&mut self) -> Result<(), HintErrorKind> { + let value = self.peek().ok_or(ValueStackUnderflow)?; + self.push(value) + } + + /// Swap the top two elements on the stack. + /// + /// Implements the SWAP[] instruction. + /// + /// See + pub fn swap(&mut self) -> Result<(), HintErrorKind> { + let a = self.pop()?; + let b = self.pop()?; + self.push(a)?; + self.push(b) + } + + /// Copy the indexed element to the top of the stack. + /// + /// Implements the CINDEX[] instruction. + /// + /// See + pub fn copy_index(&mut self) -> Result<(), HintErrorKind> { + let top_ix = self.top.checked_sub(1).ok_or(ValueStackUnderflow)?; + let index = *self.values.get(top_ix).ok_or(ValueStackUnderflow)? as usize; + let element_ix = top_ix.checked_sub(index).ok_or(ValueStackUnderflow)?; + self.values[top_ix] = self.values[element_ix]; + Ok(()) + } + + /// Moves the indexed element to the top of the stack. + /// + /// Implements the MINDEX[] instruction. + /// + /// See + pub fn move_index(&mut self) -> Result<(), HintErrorKind> { + let top_ix = self.top.checked_sub(1).ok_or(ValueStackUnderflow)?; + let index = *self.values.get(top_ix).ok_or(ValueStackUnderflow)? as usize; + let element_ix = top_ix.checked_sub(index).ok_or(ValueStackUnderflow)?; + let value = self.values[element_ix]; + self.values + .copy_within(element_ix + 1..self.top, element_ix); + self.values[top_ix - 1] = value; + self.top -= 1; + Ok(()) + } + + /// Roll the top three stack elements. + /// + /// Implements the ROLL[] instruction. + /// + /// See + pub fn roll(&mut self) -> Result<(), HintErrorKind> { + let a = self.pop()?; + let b = self.pop()?; + let c = self.pop()?; + self.push(b)?; + self.push(a)?; + self.push(c)?; + Ok(()) + } + + fn get(&mut self, index: usize) -> Option { + self.values.get(index).copied() + } + + fn get_mut(&mut self, index: usize) -> Option<&mut i32> { + self.values.get_mut(index) + } +} + +#[cfg(test)] +mod tests { + use super::{super::code_state::MockArgs, HintErrorKind, ValueStack}; + + // The following are macros because functions can't return a new ValueStack + // with a borrowed parameter. + macro_rules! make_stack { + ($values:expr) => { + ValueStack { + values: $values, + top: $values.len(), + } + }; + } + macro_rules! make_empty_stack { + ($values:expr) => { + ValueStack { + values: $values, + top: 0, + } + }; + } + + #[test] + fn push() { + let mut stack = make_empty_stack!(&mut [0; 4]); + for i in 0..4 { + stack.push(i).unwrap(); + assert_eq!(stack.peek(), Some(i)); + } + assert!(matches!( + stack.push(0), + Err(HintErrorKind::ValueStackOverflow) + )); + } + + #[test] + fn push_args() { + let mut stack = make_empty_stack!(&mut [0; 32]); + let values = [-5, 2, 2845, 92, -26, 42, i16::MIN, i16::MAX]; + let mock_args = MockArgs::from_words(&values); + stack.push_args(&mock_args.args()).unwrap(); + let mut popped = vec![]; + while !stack.is_empty() { + popped.push(stack.pop().unwrap()); + } + assert!(values + .iter() + .rev() + .map(|x| *x as i32) + .eq(popped.iter().copied())); + } + + #[test] + fn pop() { + let mut stack = make_stack!(&mut [0, 1, 2, 3]); + for i in (0..4).rev() { + assert_eq!(stack.pop().ok(), Some(i)); + } + assert!(matches!( + stack.pop(), + Err(HintErrorKind::ValueStackUnderflow) + )); + } + + #[test] + fn dup() { + let mut stack = make_stack!(&mut [1, 2, 3, 0]); + // pop extra element so we have room for dup + stack.pop().unwrap(); + stack.dup().unwrap(); + assert_eq!(stack.values(), &[1, 2, 3, 3]); + } + + #[test] + fn swap() { + let mut stack = make_stack!(&mut [1, 2, 3]); + stack.swap().unwrap(); + assert_eq!(stack.values(), &[1, 3, 2]); + } + + #[test] + fn copy_index() { + let mut stack = make_stack!(&mut [4, 10, 2, 1, 3]); + stack.copy_index().unwrap(); + assert_eq!(stack.values(), &[4, 10, 2, 1, 10]); + } + + #[test] + fn move_index() { + let mut stack = make_stack!(&mut [4, 10, 2, 1, 3]); + stack.move_index().unwrap(); + assert_eq!(stack.values(), &[4, 2, 1, 10]); + } + + #[test] + fn roll() { + let mut stack = make_stack!(&mut [1, 2, 3]); + stack.roll().unwrap(); + assert_eq!(stack.values(), &[2, 3, 1]); + } + + #[test] + fn unnop() { + let mut stack = make_stack!(&mut [42]); + stack.apply_unary(|a| Ok(-a)).unwrap(); + assert_eq!(stack.peek(), Some(-42)); + stack.apply_unary(|a| Ok(!a)).unwrap(); + assert_eq!(stack.peek(), Some(!-42)); + } + + #[test] + fn binop() { + let mut stack = make_empty_stack!(&mut [0; 32]); + for value in 1..=5 { + stack.push(value).unwrap(); + } + stack.apply_binary(|a, b| Ok(a + b)).unwrap(); + assert_eq!(stack.peek(), Some(9)); + stack.apply_binary(|a, b| Ok(a * b)).unwrap(); + assert_eq!(stack.peek(), Some(27)); + stack.apply_binary(|a, b| Ok(a - b)).unwrap(); + assert_eq!(stack.peek(), Some(-25)); + stack.apply_binary(|a, b| Ok(a / b)).unwrap(); + assert_eq!(stack.peek(), Some(0)); + } +}