From f34a0e9cccc4f6c3a42482e74eb23019822f6af7 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Sun, 4 Feb 2024 20:00:19 -0500 Subject: [PATCH 1/4] truetype graphics state instructions Add implementations for the "managing the graphics state" group of TrueType instructions. See https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#managing-the-graphics-state --- .../src/outline/glyf/hint/code_state/mod.rs | 15 + .../glyf/hint/engine/graphics_state.rs | 822 ++++++++++++++++++ skrifa/src/outline/glyf/hint/engine/mod.rs | 10 +- .../outline/glyf/hint/graphics_state/mod.rs | 25 +- 4 files changed, 868 insertions(+), 4 deletions(-) create mode 100644 skrifa/src/outline/glyf/hint/engine/graphics_state.rs diff --git a/skrifa/src/outline/glyf/hint/code_state/mod.rs b/skrifa/src/outline/glyf/hint/code_state/mod.rs index 42c58cb2a..9b69444cb 100644 --- a/skrifa/src/outline/glyf/hint/code_state/mod.rs +++ b/skrifa/src/outline/glyf/hint/code_state/mod.rs @@ -4,5 +4,20 @@ mod args; pub use args::Args; +/// Describes the source for a piece of bytecode. +#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)] +#[repr(u8)] +pub enum ProgramKind { + /// Program that initializes the function and instruction tables. Stored + /// in the `fpgm` table. + #[default] + Font = 0, + /// Program that initializes CVT and storage based on font size and other + /// parameters. Stored in the `prep` table. + ControlValue = 1, + /// Glyph specified program. Stored per-glyph in the `glyf` table. + Glyph = 2, +} + #[cfg(test)] pub(crate) use args::MockArgs; diff --git a/skrifa/src/outline/glyf/hint/engine/graphics_state.rs b/skrifa/src/outline/glyf/hint/engine/graphics_state.rs new file mode 100644 index 000000000..54f6f3e60 --- /dev/null +++ b/skrifa/src/outline/glyf/hint/engine/graphics_state.rs @@ -0,0 +1,822 @@ +//! Managing the graphics state. +//! +//! See + +// 45 instructions + +use read_fonts::types::Point; + +use super::{ + super::{code_state::ProgramKind, graphics_state::RoundMode, math}, + Engine, HintErrorKind, OpResult, +}; + +impl<'a> Engine<'a> { + /// Set vectors to coordinate axis. + /// + /// SVTCA\[a\] (0x00 - 0x01) + /// + /// Sets both the projection_vector and freedom_vector to the same one of + /// the coordinate axes. + /// + /// See + /// + /// SPVTCA\[a\] (0x02 - 0x03) + /// + /// Sets the projection_vector to one of the coordinate axes depending on + /// the value of the flag a. + /// + /// See + /// + /// SFVTCA\[a\] (0x04 - 0x05) + /// + /// Sets the freedom_vector to one of the coordinate axes depending on + /// the value of the flag a. + /// + /// See + /// + /// FreeType combines these into a single function using some bit magic on + /// the opcode to determine which axes and vectors to set. + /// + /// See + pub(super) fn op_svtca(&mut self, opcode: u8) -> OpResult { + let opcode = opcode as i32; + // The low bit of the opcode determines the axis to set (1 = x, 0 = y). + let x = (opcode & 1) << 14; + let y = x ^ 0x4000; + // Opcodes 0..4 set the projection vector. + if opcode < 4 { + self.graphics_state.proj_vector.x = x; + self.graphics_state.proj_vector.y = y; + self.graphics_state.dual_proj_vector.x = x; + self.graphics_state.dual_proj_vector.y = y; + } + // Opcodes with bit 2 unset modify the freedom vector. + if opcode & 2 == 0 { + self.graphics_state.freedom_vector.x = x; + self.graphics_state.freedom_vector.y = y; + } + self.graphics_state.update_projection_state(); + Ok(()) + } + + /// Set vectors to line. + /// + /// SPVTL\[a\] (0x06 - 0x07) + /// + /// Sets the projection_vector to a unit vector parallel or perpendicular + /// to the line segment from point p1 to point p2. + /// + /// See + /// + /// SFVTL\[a\] (0x08 - 0x09) + /// + /// Sets the projection_vector to a unit vector parallel or perpendicular + /// to the line segment from point p1 to point p2. + /// + /// See + /// + /// Pops: p1, p2 (point number) + /// + /// See + pub(super) fn op_svtl(&mut self, opcode: u8) -> OpResult { + let index1 = self.value_stack.pop_usize()?; + let index2 = self.value_stack.pop_usize()?; + let is_parallel = opcode & 1 == 0; + let p1 = self.graphics_state.zp1().point(index2)?; + let p2 = self.graphics_state.zp2().point(index1)?; + let vector = line_vector(p1, p2, is_parallel); + if opcode < 8 { + self.graphics_state.proj_vector = vector; + self.graphics_state.dual_proj_vector = vector; + } else { + self.graphics_state.freedom_vector = vector; + } + self.graphics_state.update_projection_state(); + Ok(()) + } + + /// Set freedom vector to projection vector. + /// + /// SFVTPV[] (0x0E) + /// + /// Sets the freedom_vector to be the same as the projection_vector. + /// + /// See + /// and + pub(super) fn op_sfvtpv(&mut self) -> OpResult { + self.graphics_state.freedom_vector = self.graphics_state.proj_vector; + self.graphics_state.update_projection_state(); + Ok(()) + } + + /// Set dual projection vector to line. + /// + /// SDPVTL\[a\] (0x86 - 0x87) + /// + /// Pops: p1, p2 (point number) + /// + /// Pops two point numbers from the stack and uses them to specify a line + /// that defines a second, dual_projection_vector. + /// + /// See + /// and + pub(super) fn op_sdpvtl(&mut self, opcode: u8) -> OpResult { + let index1 = self.value_stack.pop_usize()?; + let index2 = self.value_stack.pop_usize()?; + let is_parallel = opcode & 1 == 0; + // First set the dual projection vector from *original* points. + let p1 = self.graphics_state.zp1().original(index2)?; + let p2 = self.graphics_state.zp2().original(index1)?; + self.graphics_state.dual_proj_vector = line_vector(p1, p2, is_parallel); + // Now set the projection vector from the *current* points. + let p1 = self.graphics_state.zp1().point(index2)?; + let p2 = self.graphics_state.zp2().point(index1)?; + self.graphics_state.proj_vector = line_vector(p1, p2, is_parallel); + self.graphics_state.update_projection_state(); + Ok(()) + } + + /// Set projection vector from stack. + /// + /// SPVFS[] (0x0A) + /// + /// Pops: y, x (2.14 fixed point numbers padded with zeroes) + /// + /// Sets the direction of the projection_vector, using values x and y taken + /// from the stack, so that its projections onto the x and y-axes are x and + /// y, which are specified as signed (two’s complement) fixed-point (2.14) + /// numbers. The square root of (x2 + y2) must be equal to 0x4000 (hex) + /// + /// See + /// and + pub(super) fn op_spvfs(&mut self) -> OpResult { + let y = self.value_stack.pop()? as i16 as i32; + let x = self.value_stack.pop()? as i16 as i32; + let vector = math::normalize14(x, y); + self.graphics_state.proj_vector = vector; + self.graphics_state.dual_proj_vector = vector; + self.graphics_state.update_projection_state(); + Ok(()) + } + + /// Set freedom vector from stack. + /// + /// SFVFS[] (0x0B) + /// + /// Pops: y, x (2.14 fixed point numbers padded with zeroes) + /// + /// Sets the direction of the freedom_vector, using values x and y taken + /// from the stack, so that its projections onto the x and y-axes are x and + /// y, which are specified as signed (two’s complement) fixed-point (2.14) + /// numbers. The square root of (x2 + y2) must be equal to 0x4000 (hex) + /// + /// See + /// and + pub(super) fn op_sfvfs(&mut self) -> OpResult { + let y = self.value_stack.pop()? as i16 as i32; + let x = self.value_stack.pop()? as i16 as i32; + let vector = math::normalize14(x, y); + self.graphics_state.freedom_vector = vector; + self.graphics_state.update_projection_state(); + Ok(()) + } + + /// Get projection vector. + /// + /// GPV[] (0x0C) + /// + /// Pushes: x, y (2.14 fixed point numbers padded with zeroes) + /// + /// Pushes the x and y components of the projection_vector onto the stack + /// as two 2.14 numbers. + /// + /// See + /// and + pub(super) fn op_gpv(&mut self) -> OpResult { + let vector = self.graphics_state.proj_vector; + self.value_stack.push(vector.x)?; + self.value_stack.push(vector.y) + } + + /// Get freedom vector. + /// + /// GFV[] (0x0D) + /// + /// Pushes: x, y (2.14 fixed point numbers padded with zeroes) + /// + /// Pushes the x and y components of the freedom_vector onto the stack as + /// two 2.14 numbers. + /// + /// See + /// and + pub(super) fn op_gfv(&mut self) -> OpResult { + let vector = self.graphics_state.freedom_vector; + self.value_stack.push(vector.x)?; + self.value_stack.push(vector.y) + } + + /// Set reference point 0. + /// + /// SRP0[] (0x10) + /// + /// Pops: p (point number) + /// + /// Pops a point number from the stack and sets rp0 to that point number. + /// + /// See + /// and + pub(super) fn op_srp0(&mut self) -> OpResult { + let p = self.value_stack.pop_usize()?; + self.graphics_state.rp0 = p; + Ok(()) + } + + /// Set reference point 1. + /// + /// SRP1[] (0x11) + /// + /// Pops: p (point number) + /// + /// Pops a point number from the stack and sets rp1 to that point number. + /// + /// See + /// and + pub(super) fn op_srp1(&mut self) -> OpResult { + let p = self.value_stack.pop_usize()?; + self.graphics_state.rp1 = p; + Ok(()) + } + + /// Set reference point 2. + /// + /// SRP2[] (0x12) + /// + /// Pops: p (point number) + /// + /// Pops a point number from the stack and sets rp2 to that point number. + /// + /// See + /// and + pub(super) fn op_srp2(&mut self) -> OpResult { + let p = self.value_stack.pop_usize()?; + self.graphics_state.rp2 = p; + Ok(()) + } + + /// Set zone pointer 0. + /// + /// SZP0[] (0x13) + /// + /// Pops: n (zone number) + /// + /// Pops a zone number, n, from the stack and sets zp0 to the zone with + /// that number. If n is 0, zp0 points to zone 0. If n is 1, zp0 points + /// to zone 1. Any other value for n is an error. + /// + /// See + /// and + pub(super) fn op_szp0(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.graphics_state.zp0 = n.try_into()?; + Ok(()) + } + + /// Set zone pointer 1. + /// + /// SZP1[] (0x14) + /// + /// Pops: n (zone number) + /// + /// Pops a zone number, n, from the stack and sets zp0 to the zone with + /// that number. If n is 0, zp1 points to zone 0. If n is 1, zp0 points + /// to zone 1. Any other value for n is an error. + /// + /// See + /// and + pub(super) fn op_szp1(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.graphics_state.zp1 = n.try_into()?; + Ok(()) + } + + /// Set zone pointer 2. + /// + /// SZP2[] (0x15) + /// + /// Pops: n (zone number) + /// + /// Pops a zone number, n, from the stack and sets zp0 to the zone with + /// that number. If n is 0, zp2 points to zone 0. If n is 1, zp0 points + /// to zone 1. Any other value for n is an error. + /// + /// See + /// and + pub(super) fn op_szp2(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.graphics_state.zp2 = n.try_into()?; + Ok(()) + } + + /// Set zone pointers. + /// + /// SZPS[] (0x16) + /// + /// Pops: n (zone number) + /// + /// Pops a zone number from the stack and sets all of the zone pointers to + /// point to the zone with that number. If n is 0, all three zone pointers + /// will point to zone 0. If n is 1, all three zone pointers will point to + /// zone 1. Any other value for n is an error. + /// + /// See + /// and + pub(super) fn op_szps(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + let zp = n.try_into()?; + self.graphics_state.zp0 = zp; + self.graphics_state.zp1 = zp; + self.graphics_state.zp2 = zp; + Ok(()) + } + + /// Round to half grid. + /// + /// RTHG[] (0x19) + /// + /// Sets the round_state variable to state 0 (hg). In this state, the + /// coordinates of a point are rounded to the nearest half grid line. + /// + /// See + /// and + pub(super) fn op_rthg(&mut self) -> OpResult { + self.graphics_state.round_state.mode = RoundMode::HalfGrid; + Ok(()) + } + + /// Round to grid. + /// + /// RTG[] (0x18) + /// + /// Sets the round_state variable to state 1 (g). In this state, distances + /// are rounded to the closest grid line. + /// + /// See + /// and + pub(super) fn op_rtg(&mut self) -> OpResult { + self.graphics_state.round_state.mode = RoundMode::Grid; + Ok(()) + } + + /// Round to double grid. + /// + /// RTDG[] (0x3D) + /// + /// Sets the round_state variable to state 2 (dg). In this state, distances + /// are rounded to the closest half or integer pixel. + /// + /// See + /// and + pub(super) fn op_rtdg(&mut self) -> OpResult { + self.graphics_state.round_state.mode = RoundMode::DoubleGrid; + Ok(()) + } + + /// Round down to grid. + /// + /// RDTG[] (0x7D) + /// + /// Sets the round_state variable to state 3 (dtg). In this state, distances + /// are rounded down to the closest integer grid line. + /// + /// See + /// and + pub(super) fn op_rdtg(&mut self) -> OpResult { + self.graphics_state.round_state.mode = RoundMode::DownToGrid; + Ok(()) + } + + /// Round up to grid. + /// + /// RUTG[] (0x7C) + /// + /// Sets the round_state variable to state 4 (utg). In this state distances + /// are rounded up to the closest integer pixel boundary. + /// + /// See + /// and + pub(super) fn op_rutg(&mut self) -> OpResult { + self.graphics_state.round_state.mode = RoundMode::UpToGrid; + Ok(()) + } + + /// Round off. + /// + /// ROFF[] (0x7A) + /// + /// Sets the round_state variable to state 5 (off). In this state rounding + /// is turned off. + /// + /// See + /// and + pub(super) fn op_roff(&mut self) -> OpResult { + self.graphics_state.round_state.mode = RoundMode::Off; + Ok(()) + } + + /// Super round. + /// + /// SROUND[] (0x76) + /// + /// Pops: n (number decomposed to obtain period, phase threshold) + /// + /// SROUND allows you fine control over the effects of the round_state + /// variable by allowing you to set the values of three components of + /// the round_state: period, phase, and threshold. + /// + /// More formally, SROUND maps the domain of 26.6 fixed point numbers into + /// a set of discrete values that are separated by equal distances. + /// + /// See + /// and + pub(super) fn op_sround(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.super_round(0x4000, n); + self.graphics_state.round_state.mode = RoundMode::Super; + Ok(()) + } + + /// Super round 45 degrees. + /// + /// S45ROUND[] (0x77) + /// + /// Pops: n (number decomposed to obtain period, phase threshold) + /// + /// S45ROUND is analogous to SROUND. The gridPeriod is SQRT(2)/2 pixels + /// rather than 1 pixel. It is useful for measuring at a 45 degree angle + /// with the coordinate axes. + /// + /// See + /// and + pub(super) fn op_s45round(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.super_round(0x2D41, n); + self.graphics_state.round_state.mode = RoundMode::Super45; + Ok(()) + } + + /// Helper function for decomposing period, phase and threshold for + /// the SROUND[] and SROUND45[] instructions. + /// + /// See + fn super_round(&mut self, grid_period: i32, selector: i32) { + let round_state = &mut self.graphics_state.round_state; + let period = match selector & 0xC0 { + 0 => grid_period / 2, + 0x40 => grid_period, + 0x80 => grid_period * 2, + 0xC0 => grid_period, + _ => round_state.period, + }; + let phase = match selector & 0x30 { + 0 => 0, + 0x10 => period / 4, + 0x20 => period / 2, + 0x30 => period * 3 / 4, + _ => round_state.phase, + }; + let threshold = if (selector & 0x0F) == 0 { + period - 1 + } else { + ((selector & 0x0F) - 4) * period / 8 + }; + round_state.period = period >> 8; + round_state.phase = phase >> 8; + round_state.threshold = threshold >> 8; + } + + /// Set loop variable. + /// + /// SLOOP[] (0x17) + /// + /// Pops: n (value for loop Graphics State variable (integer)) + /// + /// Pops a value, n, from the stack and sets the loop variable count to + /// that value. The loop variable works with the SHP\[a\], SHPIX[], IP[], + /// FLIPPT[], and ALIGNRP[]. The value n indicates the number of times + /// the instruction is to be repeated. After the instruction executes, + /// the loop variable is reset to 1. + /// + /// See + /// and + pub(super) fn op_sloop(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + if n < 0 { + return Err(HintErrorKind::NegativeLoopCounter); + } + // As FreeType, heuristically limit the number of loops to 16 bits. + self.graphics_state.loop_counter = (n as u32).min(0xFFFF); + Ok(()) + } + + /// Set minimum distance. + /// + /// SMD[] (0x1A) + /// + /// Pops: distance: value for minimum_distance (F26Dot6) + /// + /// Pops a value from the stack and sets the minimum_distance variable + /// to that value. The distance is assumed to be expressed in sixty-fourths + /// of a pixel. + /// + /// See + /// and + pub(super) fn op_smd(&mut self) -> OpResult { + let distance = self.value_stack.pop()?; + self.graphics_state.min_distance = distance; + Ok(()) + } + + /// Instruction execution control. + /// + /// INSTCTRL[] (0x8E) + /// + /// Pops: s: selector flag (int32) + /// value: used to set value of instruction_control (uint16 padded) + /// + /// Sets the instruction control state variable making it possible to turn + /// on or off the execution of instructions and to regulate use of + /// parameters set in the CVT program. INSTCTRL[ ] can only be executed in + /// the CVT program. + /// + /// See + /// and + pub(super) fn op_instctrl(&mut self) -> OpResult { + let selector = self.value_stack.pop()? as u32; + let value = self.value_stack.pop()? as u32; + // Selectors are indices starting with 1; not flags. + // Convert index to flag. + let selector_flag = 1 << (selector - 1); + if !(1..=3).contains(&selector) || (value != 0 && value != selector_flag) { + return Ok(()); + } + match (self.initial_program, selector) { + // Typically, this instruction can only be executed in the prep table. + (ProgramKind::ControlValue, _) => { + self.graphics_state.instruct_control &= !(selector_flag as u8); + self.graphics_state.instruct_control |= value as u8; + } + // Allow an exception in the glyph program for selector 3 which can + // temporarily disable backward compability mode. + (ProgramKind::Glyph, 3) => { + self.graphics_state.backward_compatibility = value != 4; + } + _ => {} + } + Ok(()) + } + + /// Scan conversion control. + /// + /// SCANCTRL[] (0x85) + /// + /// Pops: n: flags indicating when to turn on dropout control mode + /// + /// SCANCTRL is used to set the value of the Graphics State variable + /// scan_control which in turn determines whether the scan converter + /// will activate dropout control for this glyph. + /// + /// See + /// and + pub(super) fn op_scanctrl(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + // Bits 0-7 represent the threshold value for ppem. + let threshold = n & 0xFF; + match threshold { + // A value of FF in bits 0-7 means invoke dropout_control for all + // sizes. + 0xFF => self.graphics_state.scan_control = true, + // A value of 0 in bits 0-7 means never invoke dropout_control. + 0 => self.graphics_state.scan_control = false, + _ => { + let ppem = self.graphics_state.ppem; + let is_rotated = self.graphics_state.is_rotated; + let is_stretched = self.graphics_state.is_stretched; + let scan_control = &mut self.graphics_state.scan_control; + // Bits 8-13 are used to turn on dropout_control in cases where + // the specified conditions are met. Bits 8, 9 and 10 are used + // to turn on the dropout_control mode (assuming other + // conditions do not block it). Bits 11, 12, and 13 are used to + // turn off the dropout mode unless other conditions force it + if (n & 0x100) != 0 && ppem <= threshold { + // Bit 8: Set dropout_control to TRUE if other conditions + // do not block and ppem is less than or equal to the + // threshold value. + *scan_control = true; + } + if (n & 0x200) != 0 && is_rotated { + // Bit 9: Set dropout_control to TRUE if other conditions + // do not block and the glyph is rotated + *scan_control = true; + } + if (n & 0x400) != 0 && is_stretched { + // Bit 10: Set dropout_control to TRUE if other conditions + // do not block and the glyph is stretched. + *scan_control = true; + } + if (n & 0x800) != 0 && ppem > threshold { + // Bit 11: Set dropout_control to FALSE unless ppem is less + // than or equal to the threshold value. + *scan_control = false; + } + if (n & 0x1000) != 0 && is_rotated { + // Bit 12: Set dropout_control to FALSE unless the glyph is + // rotated. + *scan_control = false; + } + if (n & 0x2000) != 0 && is_stretched { + // Bit 13: Set dropout_control to FALSE unless the glyph is + // stretched. + *scan_control = false; + } + } + } + Ok(()) + } + + /// Scan type. + /// + /// SCANTYPE[] (0x8D) + /// + /// Pops: n: 16 bit integer + /// + /// Pops a 16-bit integer whose value is used to determine which rules the + /// scan converter will use. + /// + /// See + /// and + pub(super) fn op_scantype(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.graphics_state.scan_type = n & 0xFFFF; + Ok(()) + } + + /// Set control value table cut in. + /// + /// SCVTCI[] (0x1D) + /// + /// Pops: n: value for cut_in (F26Dot6) + /// + /// Sets the control_value_cut_in in the Graphics State. The value n is + /// expressed in sixty-fourths of a pixel. + /// + /// See + /// and + pub(super) fn op_scvtci(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.graphics_state.control_value_cutin = n; + Ok(()) + } + + /// Set single width cut in. + /// + /// SSWCI[] (0x1E) + /// + /// Pops: n: value for single_width_cut_in (F26Dot6) + /// + /// Sets the single_width_cut_in in the Graphics State. The value n is + /// expressed in sixty-fourths of a pixel. + /// + /// See + /// and + pub(super) fn op_sswci(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.graphics_state.single_width_cutin = n; + Ok(()) + } + + /// Set single width. + /// + /// SSW[] (0x1F) + /// + /// Pops: n: value for single_width_value (FUnits) + /// + /// Sets the single_width_value in the Graphics State. The + /// single_width_value is expressed in FUnits, which the + /// interpreter converts to pixels (F26Dot6). + /// + /// See + /// and + pub(super) fn op_ssw(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.graphics_state.single_width = math::mul(n, self.graphics_state.scale); + Ok(()) + } + + /// Set auto flip on. + /// + /// FLIPON[] (0x4D) + /// + /// Sets the auto_flip Boolean in the Graphics State to TRUE causing the + /// MIRP instructions to ignore the sign of Control Value Table entries. + /// The default auto_flip Boolean value is TRUE. + /// + /// See + /// and + pub(super) fn op_flipon(&mut self) -> OpResult { + self.graphics_state.auto_flip = true; + Ok(()) + } + + /// Set auto flip off. + /// + /// FLIPOFF[] (0x4E) + /// + /// Set the auto_flip Boolean in the Graphics State to FALSE causing the + /// MIRP instructions to use the sign of Control Value Table entries. + /// The default auto_flip Boolean value is TRUE. + /// + /// See + /// and + pub(super) fn op_flipoff(&mut self) -> OpResult { + self.graphics_state.auto_flip = false; + Ok(()) + } + + /// Set angle weight. + /// + /// SANGW[] (0x7E) + /// + /// Pops: weight: value for angle_weight + /// + /// SANGW is no longer needed because of dropped support to the AA + /// (Adjust Angle) instruction. AA was the only instruction that used + /// angle_weight in the global graphics state. + /// + /// See + /// and + pub(super) fn op_sangw(&mut self) -> OpResult { + // totally unsupported + let _weight = self.value_stack.pop()?; + Ok(()) + } + + /// Set delta base in graphics state. + /// + /// SDB[] (0x5E) + /// + /// Pops: n: value for delta_base + /// + /// Pops a number, n, and sets delta_base to the value n. The default for + /// delta_base is 9. + /// + /// See + /// and + pub(super) fn op_sdb(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + self.graphics_state.delta_base = n as u16; + Ok(()) + } + + /// Set delta shift in graphics state. + /// + /// SDS[] (0x5F) + /// + /// Pops: n: value for delta_shift + /// + /// Sets delta_shift to the value n. The default for delta_shift is 3. + /// + /// See + /// and + pub(super) fn op_sds(&mut self) -> OpResult { + let n = self.value_stack.pop()?; + if n > 6 { + Err(HintErrorKind::InvalidStackValue(n)) + } else { + self.graphics_state.delta_shift = n as u16; + Ok(()) + } + } +} + +/// Computes a parallel or perpendicular normalized vector for the line +/// between the two given points. +/// +/// This is common code for the "set vector to line" instructions. +/// +/// See +fn line_vector(p1: Point, p2: Point, is_parallel: bool) -> Point { + let mut a = p1.x - p2.x; + let mut b = p1.y - p2.y; + if a == 0 && b == 0 { + // If the points are equal, set to the x axis. + a = 0x4000; + } else if !is_parallel { + // Perform a counter-clockwise rotation by 90 degrees to form a + // perpendicular line. + let c = b; + b = a; + a = -c; + } + math::normalize14(a, b) +} diff --git a/skrifa/src/outline/glyf/hint/engine/mod.rs b/skrifa/src/outline/glyf/hint/engine/mod.rs index d6d5cfac7..6a91eada4 100644 --- a/skrifa/src/outline/glyf/hint/engine/mod.rs +++ b/skrifa/src/outline/glyf/hint/engine/mod.rs @@ -1,10 +1,14 @@ //! TrueType bytecode interpreter. mod arith; +mod graphics_state; mod logical; mod stack; -use super::{error::HintErrorKind, graphics_state::GraphicsState, value_stack::ValueStack}; +use super::{ + code_state::ProgramKind, error::HintErrorKind, graphics_state::GraphicsState, + value_stack::ValueStack, +}; pub type OpResult = Result<(), HintErrorKind>; @@ -12,6 +16,7 @@ pub type OpResult = Result<(), HintErrorKind>; pub struct Engine<'a> { graphics_state: GraphicsState<'a>, value_stack: ValueStack<'a>, + initial_program: ProgramKind, } #[cfg(test)] @@ -19,7 +24,7 @@ use mock::MockEngine; #[cfg(test)] mod mock { - use super::{Engine, GraphicsState, ValueStack}; + use super::{Engine, GraphicsState, ProgramKind, ValueStack}; /// Mock engine for testing. pub(super) struct MockEngine { @@ -37,6 +42,7 @@ mod mock { Engine { graphics_state: GraphicsState::default(), value_stack: ValueStack::new(&mut self.value_stack), + initial_program: ProgramKind::Font, } } } diff --git a/skrifa/src/outline/glyf/hint/graphics_state/mod.rs b/skrifa/src/outline/glyf/hint/graphics_state/mod.rs index 7d8abcf8d..9ec3e5b98 100644 --- a/skrifa/src/outline/glyf/hint/graphics_state/mod.rs +++ b/skrifa/src/outline/glyf/hint/graphics_state/mod.rs @@ -8,7 +8,7 @@ use core::ops::{Deref, DerefMut}; use read_fonts::types::Point; pub use { - round::RoundState, + round::{RoundMode, RoundState}, zone::{Zone, ZonePointer}, }; @@ -91,6 +91,13 @@ pub struct GraphicsState<'a> { /// /// This array contains the twilight and glyph zones, in that order. pub zones: [Zone<'a>; 2], + /// If true, enables a set of backward compabitility heuristics. + /// Otherwise, the interpreter operates in "native ClearType mode". + /// + /// Defaults to true. + /// + /// See + pub backward_compatibility: bool, } impl Default for GraphicsState<'_> { @@ -112,10 +119,11 @@ impl Default for GraphicsState<'_> { rp1: 0, rp2: 0, loop_counter: 1, - zones: [Zone::default(), Zone::default()], zp0: ZonePointer::default(), zp1: ZonePointer::default(), zp2: ZonePointer::default(), + zones: [Zone::default(), Zone::default()], + backward_compatibility: true, } } } @@ -179,6 +187,15 @@ pub struct RetainedGraphicsState { /// /// See pub single_width: i32, + /// The scale factor for the current instance. Conversion from font units + /// to 26.6 for current ppem. + pub scale: i32, + /// The nominal pixels per em value for the current instance. + pub ppem: i32, + /// True if a rotation is being applied. + pub is_rotated: bool, + /// True if a non-uniform scale is being applied. + pub is_stretched: bool, } impl Default for RetainedGraphicsState { @@ -198,6 +215,10 @@ impl Default for RetainedGraphicsState { scan_type: 0, single_width_cutin: 0, single_width: 0, + scale: 0, + ppem: 0, + is_rotated: false, + is_stretched: false, } } } From 94f9742eef07bc960537ecd1d877610db498ba2e Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Mon, 5 Feb 2024 01:10:51 -0500 Subject: [PATCH 2/4] add tests --- .../glyf/hint/engine/graphics_state.rs | 334 +++++++++++++++++- 1 file changed, 321 insertions(+), 13 deletions(-) diff --git a/skrifa/src/outline/glyf/hint/engine/graphics_state.rs b/skrifa/src/outline/glyf/hint/engine/graphics_state.rs index 54f6f3e60..987f9fbdd 100644 --- a/skrifa/src/outline/glyf/hint/engine/graphics_state.rs +++ b/skrifa/src/outline/glyf/hint/engine/graphics_state.rs @@ -71,7 +71,7 @@ impl<'a> Engine<'a> { /// /// SFVTL\[a\] (0x08 - 0x09) /// - /// Sets the projection_vector to a unit vector parallel or perpendicular + /// Sets the freedom_vector to a unit vector parallel or perpendicular /// to the line segment from point p1 to point p2. /// /// See @@ -567,7 +567,7 @@ impl<'a> Engine<'a> { self.graphics_state.instruct_control |= value as u8; } // Allow an exception in the glyph program for selector 3 which can - // temporarily disable backward compability mode. + // temporarily disable backward compatibility mode. (ProgramKind::Glyph, 3) => { self.graphics_state.backward_compatibility = value != 4; } @@ -593,49 +593,49 @@ impl<'a> Engine<'a> { // Bits 0-7 represent the threshold value for ppem. let threshold = n & 0xFF; match threshold { - // A value of FF in bits 0-7 means invoke dropout_control for all + // A value of FF in bits 0-7 means invoke scan_control for all // sizes. 0xFF => self.graphics_state.scan_control = true, - // A value of 0 in bits 0-7 means never invoke dropout_control. + // A value of 0 in bits 0-7 means never invoke scan_control. 0 => self.graphics_state.scan_control = false, _ => { let ppem = self.graphics_state.ppem; let is_rotated = self.graphics_state.is_rotated; let is_stretched = self.graphics_state.is_stretched; let scan_control = &mut self.graphics_state.scan_control; - // Bits 8-13 are used to turn on dropout_control in cases where + // Bits 8-13 are used to turn on scan_control in cases where // the specified conditions are met. Bits 8, 9 and 10 are used - // to turn on the dropout_control mode (assuming other + // to turn on the scan_control mode (assuming other // conditions do not block it). Bits 11, 12, and 13 are used to // turn off the dropout mode unless other conditions force it if (n & 0x100) != 0 && ppem <= threshold { - // Bit 8: Set dropout_control to TRUE if other conditions + // Bit 8: Set scan_control to TRUE if other conditions // do not block and ppem is less than or equal to the // threshold value. *scan_control = true; } if (n & 0x200) != 0 && is_rotated { - // Bit 9: Set dropout_control to TRUE if other conditions + // Bit 9: Set scan_control to TRUE if other conditions // do not block and the glyph is rotated *scan_control = true; } if (n & 0x400) != 0 && is_stretched { - // Bit 10: Set dropout_control to TRUE if other conditions + // Bit 10: Set scan_control to TRUE if other conditions // do not block and the glyph is stretched. *scan_control = true; } if (n & 0x800) != 0 && ppem > threshold { - // Bit 11: Set dropout_control to FALSE unless ppem is less + // Bit 11: Set scan_control to FALSE unless ppem is less // than or equal to the threshold value. *scan_control = false; } if (n & 0x1000) != 0 && is_rotated { - // Bit 12: Set dropout_control to FALSE unless the glyph is + // Bit 12: Set scan_control to FALSE unless the glyph is // rotated. *scan_control = false; } if (n & 0x2000) != 0 && is_stretched { - // Bit 13: Set dropout_control to FALSE unless the glyph is + // Bit 13: Set scan_control to FALSE unless the glyph is // stretched. *scan_control = false; } @@ -756,7 +756,7 @@ impl<'a> Engine<'a> { /// See /// and pub(super) fn op_sangw(&mut self) -> OpResult { - // totally unsupported + // totally unsupported but we still need to pop the stack value let _weight = self.value_stack.pop()?; Ok(()) } @@ -820,3 +820,311 @@ fn line_vector(p1: Point, p2: Point, is_parallel: bool) -> Point } math::normalize14(a, b) } + +#[cfg(test)] +mod tests { + use super::{ + super::{ + super::graphics_state::{Zone, ZonePointer}, + MockEngine, + }, + HintErrorKind, Point, ProgramKind, RoundMode, + }; + use read_fonts::types::F2Dot14; + + // Some helpful constants for testing vectors + const ONE: i32 = F2Dot14::ONE.to_bits() as i32; + + const X_AXIS: Point = Point::new(ONE, 0); + const Y_AXIS: Point = Point::new(0, ONE); + + #[test] + fn set_vectors_to_coord_axis() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + // freedom and projection vector to y axis + engine.op_svtca(0x00).unwrap(); + assert_eq!(engine.graphics_state.freedom_vector, Y_AXIS); + assert_eq!(engine.graphics_state.proj_vector, Y_AXIS); + // freedom and projection vector to x axis + engine.op_svtca(0x01).unwrap(); + assert_eq!(engine.graphics_state.freedom_vector, X_AXIS); + assert_eq!(engine.graphics_state.proj_vector, X_AXIS); + // projection vector to y axis + engine.op_svtca(0x02).unwrap(); + assert_eq!(engine.graphics_state.proj_vector, Y_AXIS); + // projection vector to x axis + engine.op_svtca(0x03).unwrap(); + assert_eq!(engine.graphics_state.proj_vector, X_AXIS); + // freedom vector to y axis + engine.op_svtca(0x04).unwrap(); + assert_eq!(engine.graphics_state.freedom_vector, Y_AXIS); + // freedom vector to x axis + engine.op_svtca(0x05).unwrap(); + assert_eq!(engine.graphics_state.freedom_vector, X_AXIS); + } + + #[test] + fn set_get_vectors_from_stack() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + // projection vector + engine.value_stack.push(X_AXIS.x).unwrap(); + engine.value_stack.push(X_AXIS.y).unwrap(); + engine.op_spvfs().unwrap(); + assert_eq!(engine.graphics_state.proj_vector, X_AXIS); + engine.op_gpv().unwrap(); + let y = engine.value_stack.pop().unwrap(); + let x = engine.value_stack.pop().unwrap(); + assert_eq!(Point::new(x, y), X_AXIS); + // freedom vector + engine.value_stack.push(Y_AXIS.x).unwrap(); + engine.value_stack.push(Y_AXIS.y).unwrap(); + engine.op_sfvfs().unwrap(); + assert_eq!(engine.graphics_state.freedom_vector, Y_AXIS); + engine.op_gfv().unwrap(); + let y = engine.value_stack.pop().unwrap(); + let x = engine.value_stack.pop().unwrap(); + assert_eq!(Point::new(x, y), Y_AXIS); + } + + #[test] + fn set_vectors_to_line() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + // Set up a zone for testing and set all the zone pointers to it. + let points = &mut [Point::new(0, 0), Point::new(64, 0)]; + let original = &mut [Point::new(0, 64), Point::new(0, -64)]; + engine.graphics_state.zones[1] = Zone { + points, + original, + unscaled: &mut [], + flags: &mut [], + contours: &[], + }; + engine.value_stack.push(1).unwrap(); + engine.op_szps().unwrap(); + // First, push point indices (a few times for reuse) + for _ in 0..6 { + engine.value_stack.push(1).unwrap(); + engine.value_stack.push(0).unwrap(); + } + // SPVTL: set projection vector to line: + { + // (parallel) + engine.op_svtl(0x6).unwrap(); + assert_eq!(engine.graphics_state.proj_vector, X_AXIS); + // (perpendicular) + engine.op_svtl(0x7).unwrap(); + assert_eq!(engine.graphics_state.proj_vector, Point::new(0, ONE)); + } + // SFVTL: set freedom vector to line: + { + // (parallel) + engine.op_svtl(0x8).unwrap(); + assert_eq!(engine.graphics_state.freedom_vector, X_AXIS); + // (perpendicular) + engine.op_svtl(0x9).unwrap(); + assert_eq!(engine.graphics_state.freedom_vector, Point::new(0, ONE)); + } + // SDPVTL: set dual projection vector to line: + { + // (parallel) + engine.op_sdpvtl(0x86).unwrap(); + assert_eq!(engine.graphics_state.dual_proj_vector, Point::new(0, -ONE)); + // (perpendicular) + engine.op_sdpvtl(0x87).unwrap(); + assert_eq!(engine.graphics_state.dual_proj_vector, Point::new(ONE, 0)); + } + } + + /// Lots of little tests for instructions that just set fields on + /// the graphics state. + #[test] + fn simple_state_setting() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + // srp0 + engine.value_stack.push(111).unwrap(); + engine.op_srp0().unwrap(); + assert_eq!(engine.graphics_state.rp0, 111); + // srp1 + engine.value_stack.push(222).unwrap(); + engine.op_srp1().unwrap(); + assert_eq!(engine.graphics_state.rp1, 222); + // srp2 + engine.value_stack.push(333).unwrap(); + engine.op_srp2().unwrap(); + assert_eq!(engine.graphics_state.rp2, 333); + // zp0 + engine.value_stack.push(1).unwrap(); + engine.op_szp0().unwrap(); + assert_eq!(engine.graphics_state.zp0, ZonePointer::Glyph); + // zp1 + engine.value_stack.push(0).unwrap(); + engine.op_szp1().unwrap(); + assert_eq!(engine.graphics_state.zp1, ZonePointer::Twilight); + // zp2 + engine.value_stack.push(1).unwrap(); + engine.op_szp2().unwrap(); + assert_eq!(engine.graphics_state.zp2, ZonePointer::Glyph); + // zps + engine.value_stack.push(0).unwrap(); + engine.op_szps().unwrap(); + assert_eq!( + [ + engine.graphics_state.zp0, + engine.graphics_state.zp1, + engine.graphics_state.zp2 + ], + [ZonePointer::Twilight; 3] + ); + // zp failure + engine.value_stack.push(2).unwrap(); + assert!(matches!( + engine.op_szps(), + Err(HintErrorKind::InvalidZoneIndex(2)) + )); + // rtg + engine.op_rtg().unwrap(); + assert_eq!(engine.graphics_state.round_state.mode, RoundMode::Grid); + // rtdg + engine.op_rtdg().unwrap(); + assert_eq!( + engine.graphics_state.round_state.mode, + RoundMode::DoubleGrid + ); + // rdtg + engine.op_rdtg().unwrap(); + assert_eq!( + engine.graphics_state.round_state.mode, + RoundMode::DownToGrid + ); + // rutg + engine.op_rutg().unwrap(); + assert_eq!(engine.graphics_state.round_state.mode, RoundMode::UpToGrid); + // roff + engine.op_roff().unwrap(); + assert_eq!(engine.graphics_state.round_state.mode, RoundMode::Off); + // sround + engine.value_stack.push(0).unwrap(); + engine.op_sround().unwrap(); + assert_eq!(engine.graphics_state.round_state.mode, RoundMode::Super); + // s45round + engine.value_stack.push(0).unwrap(); + engine.op_s45round().unwrap(); + assert_eq!(engine.graphics_state.round_state.mode, RoundMode::Super45); + // sloop + engine.value_stack.push(10).unwrap(); + engine.op_sloop().unwrap(); + assert_eq!(engine.graphics_state.loop_counter, 10); + // loop variable cannot be negative + engine.value_stack.push(-10).unwrap(); + assert!(matches!( + engine.op_sloop(), + Err(HintErrorKind::NegativeLoopCounter) + )); + // smd + engine.value_stack.push(64).unwrap(); + engine.op_smd().unwrap(); + assert_eq!(engine.graphics_state.min_distance, 64); + // scantype + engine.value_stack.push(50).unwrap(); + engine.op_scantype().unwrap(); + assert_eq!(engine.graphics_state.scan_type, 50); + // scvtci + engine.value_stack.push(128).unwrap(); + engine.op_scvtci().unwrap(); + assert_eq!(engine.graphics_state.control_value_cutin, 128); + // sswci + engine.value_stack.push(100).unwrap(); + engine.op_sswci().unwrap(); + assert_eq!(engine.graphics_state.single_width_cutin, 100); + // ssw + engine.graphics_state.scale = 64; + engine.value_stack.push(100).unwrap(); + engine.op_ssw().unwrap(); + assert_eq!( + engine.graphics_state.single_width, + super::math::mul(100, engine.graphics_state.scale) + ); + // flipoff + engine.op_flipoff().unwrap(); + assert!(!engine.graphics_state.auto_flip); + // flipon + engine.op_flipon().unwrap(); + assert!(engine.graphics_state.auto_flip); + // sdb + engine.value_stack.push(172).unwrap(); + engine.op_sdb().unwrap(); + assert_eq!(engine.graphics_state.delta_base, 172); + // sds + engine.value_stack.push(4).unwrap(); + engine.op_sds().unwrap(); + assert_eq!(engine.graphics_state.delta_shift, 4); + // delta_shift has a max value of 6 + engine.value_stack.push(7).unwrap(); + assert!(matches!( + engine.op_sds(), + Err(HintErrorKind::InvalidStackValue(7)) + )); + } + + #[test] + fn instctrl() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + engine.initial_program = ProgramKind::ControlValue; + // selectors 1..=3 are valid and values for each selector + // can be 0, which disables the field, or 1 << (selector - 1) to + // enable it + for selector in 1..=3 { + // enable first + let enable_mask = (1 << (selector - 1)) as u8; + engine.value_stack.push(enable_mask as i32).unwrap(); + engine.value_stack.push(selector).unwrap(); + engine.op_instctrl().unwrap(); + assert!(engine.graphics_state.instruct_control & enable_mask != 0); + // now disable + engine.value_stack.push(0).unwrap(); + engine.value_stack.push(selector).unwrap(); + engine.op_instctrl().unwrap(); + assert!(engine.graphics_state.instruct_control & enable_mask == 0); + } + // in glyph programs, selector 3 can be used to toggle + // backward_compatibility + engine.initial_program = ProgramKind::Glyph; + // enabling this flag opts into "native ClearType mode" + // which disables backward compatibility + engine.value_stack.push((3 - 1) << 1).unwrap(); + engine.value_stack.push(3).unwrap(); + engine.op_instctrl().unwrap(); + assert!(!engine.graphics_state.backward_compatibility); + // and disabling it enables backward compatibility + engine.value_stack.push(0).unwrap(); + engine.value_stack.push(3).unwrap(); + engine.op_instctrl().unwrap(); + assert!(engine.graphics_state.backward_compatibility); + } + + #[test] + fn scanctrl() { + let mut mock = MockEngine::new(); + let mut engine = mock.engine(); + // Example modes from specification: + // 0x0000 No dropout control is invoked + engine.value_stack.push(0x0000).unwrap(); + engine.op_scanctrl().unwrap(); + assert!(!engine.graphics_state.scan_control); + // 0x01FF Always do dropout control + engine.value_stack.push(0x01FF).unwrap(); + engine.op_scanctrl().unwrap(); + assert!(engine.graphics_state.scan_control); + // 0x0A10 Do dropout control if the glyph is rotated and has less than 16 pixels per-em + engine.value_stack.push(0x0A10).unwrap(); + engine.graphics_state.is_rotated = true; + engine.graphics_state.ppem = 12; + engine.op_scanctrl().unwrap(); + assert!(engine.graphics_state.scan_control); + } +} From 83f05a79b279e1ad2e5b46a9ac876650c6bc33d4 Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Mon, 5 Feb 2024 01:30:49 -0500 Subject: [PATCH 3/4] add missing cast, cleanup comments --- .../src/outline/glyf/hint/engine/graphics_state.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/skrifa/src/outline/glyf/hint/engine/graphics_state.rs b/skrifa/src/outline/glyf/hint/engine/graphics_state.rs index 987f9fbdd..89bad9052 100644 --- a/skrifa/src/outline/glyf/hint/engine/graphics_state.rs +++ b/skrifa/src/outline/glyf/hint/engine/graphics_state.rs @@ -514,7 +514,7 @@ impl<'a> Engine<'a> { if n < 0 { return Err(HintErrorKind::NegativeLoopCounter); } - // As FreeType, heuristically limit the number of loops to 16 bits. + // As in FreeType, heuristically limit the number of loops to 16 bits. self.graphics_state.loop_counter = (n as u32).min(0xFFFF); Ok(()) } @@ -630,13 +630,13 @@ impl<'a> Engine<'a> { *scan_control = false; } if (n & 0x1000) != 0 && is_rotated { - // Bit 12: Set scan_control to FALSE unless the glyph is - // rotated. + // Bit 12: Set scan_control to FALSE based on rotation + // state. *scan_control = false; } if (n & 0x2000) != 0 && is_stretched { - // Bit 13: Set scan_control to FALSE unless the glyph is - // stretched. + // Bit 13: Set scan_control to FALSE based on stretched + // state. *scan_control = false; } } @@ -790,7 +790,7 @@ impl<'a> Engine<'a> { /// and pub(super) fn op_sds(&mut self) -> OpResult { let n = self.value_stack.pop()?; - if n > 6 { + if n as u32 > 6 { Err(HintErrorKind::InvalidStackValue(n)) } else { self.graphics_state.delta_shift = n as u16; From 8f77de9621f9916408c22856b2ca5c643ae0ed6b Mon Sep 17 00:00:00 2001 From: Chad Brokaw Date: Mon, 5 Feb 2024 12:25:10 -0500 Subject: [PATCH 4/4] move instruction counts to mod docs --- skrifa/src/outline/glyf/hint/engine/arith.rs | 4 ++-- skrifa/src/outline/glyf/hint/engine/graphics_state.rs | 4 ++-- skrifa/src/outline/glyf/hint/engine/logical.rs | 4 ++-- skrifa/src/outline/glyf/hint/engine/stack.rs | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/skrifa/src/outline/glyf/hint/engine/arith.rs b/skrifa/src/outline/glyf/hint/engine/arith.rs index 2e73adfc4..ee33128bc 100644 --- a/skrifa/src/outline/glyf/hint/engine/arith.rs +++ b/skrifa/src/outline/glyf/hint/engine/arith.rs @@ -1,9 +1,9 @@ //! Arithmetic and math instructions. //! +//! Implements 10 instructions. +//! //! See -// 10 instructions - use super::{super::math, Engine, HintErrorKind, OpResult}; impl<'a> Engine<'a> { diff --git a/skrifa/src/outline/glyf/hint/engine/graphics_state.rs b/skrifa/src/outline/glyf/hint/engine/graphics_state.rs index 89bad9052..450c920c3 100644 --- a/skrifa/src/outline/glyf/hint/engine/graphics_state.rs +++ b/skrifa/src/outline/glyf/hint/engine/graphics_state.rs @@ -1,9 +1,9 @@ //! Managing the graphics state. //! +//! Implements 45 instructions. +//! //! See -// 45 instructions - use read_fonts::types::Point; use super::{ diff --git a/skrifa/src/outline/glyf/hint/engine/logical.rs b/skrifa/src/outline/glyf/hint/engine/logical.rs index 5283971b4..87b4e0750 100644 --- a/skrifa/src/outline/glyf/hint/engine/logical.rs +++ b/skrifa/src/outline/glyf/hint/engine/logical.rs @@ -1,9 +1,9 @@ //! Logical functions. //! +//! Implements 11 instructions. +//! //! See -// 11 instructions - use super::{Engine, OpResult}; impl<'a> Engine<'a> { diff --git a/skrifa/src/outline/glyf/hint/engine/stack.rs b/skrifa/src/outline/glyf/hint/engine/stack.rs index 20edcbff7..6b148cbce 100644 --- a/skrifa/src/outline/glyf/hint/engine/stack.rs +++ b/skrifa/src/outline/glyf/hint/engine/stack.rs @@ -1,10 +1,10 @@ //! Managing the stack and pushing data onto the interpreter stack. //! +//! Implements 26 instructions. +//! //! See //! and -// 26 instructions - use super::{super::code_state::Args, Engine, OpResult}; impl<'a> Engine<'a> {