chromium/third_party/rust/chromium_crates_io/vendor/skrifa-0.20.0/src/outline/glyf/hint/engine/graphics.rs

//! Managing the graphics state.
//!
//! Implements 45 instructions.
//!
//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#managing-the-graphics-state>

use super::{
    super::{math, program::Program, round::RoundMode},
    Engine, F26Dot6, HintErrorKind, OpResult, Point,
};

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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom-and-projection-vectors-to-coordinate-axis>
    ///
    /// SPVTCA\[a\] (0x02 - 0x03)
    ///
    /// Sets the projection_vector to one of the coordinate axes depending on
    /// the value of the flag a.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-projection_vector-to-coordinate-axis>
    ///
    /// SFVTCA\[a\] (0x04 - 0x05)
    ///
    /// Sets the freedom_vector to one of the coordinate axes depending on
    /// the value of the flag a.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom_vector-to-coordinate-axis>
    ///
    /// FreeType combines these into a single function using some bit magic on
    /// the opcode to determine which axes and vectors to set.
    ///
    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4051>
    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.proj_vector.x = x;
            self.graphics.proj_vector.y = y;
            self.graphics.dual_proj_vector.x = x;
            self.graphics.dual_proj_vector.y = y;
        }
        // Opcodes with bit 2 unset modify the freedom vector.
        if opcode & 2 == 0 {
            self.graphics.freedom_vector.x = x;
            self.graphics.freedom_vector.y = y;
        }
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-projection_vector-to-line>
    ///
    /// SFVTL\[a\] (0x08 - 0x09)
    ///
    /// Sets the freedom_vector to a unit vector parallel or perpendicular
    /// to the line segment from point p1 to point p2.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom_vector-to-line>
    ///
    /// Pops: p1, p2 (point number)
    ///
    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3986>
    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.zp1().point(index2)?;
        let p2 = self.graphics.zp2().point(index1)?;
        let vector = line_vector(p1, p2, is_parallel);
        if opcode < 8 {
            self.graphics.proj_vector = vector;
            self.graphics.dual_proj_vector = vector;
        } else {
            self.graphics.freedom_vector = vector;
        }
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom_vector-to-projection-vector>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4128>
    pub(super) fn op_sfvtpv(&mut self) -> OpResult {
        self.graphics.freedom_vector = self.graphics.proj_vector;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-dual-projection_vector-to-line>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4663>
    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.zp1().original(index2)?;
        let p2 = self.graphics.zp2().original(index1)?;
        self.graphics.dual_proj_vector = line_vector(p1, p2, is_parallel);
        // Now set the projection vector from the *current* points.
        let p1 = self.graphics.zp1().point(index2)?;
        let p2 = self.graphics.zp2().point(index1)?;
        self.graphics.proj_vector = line_vector(p1, p2, is_parallel);
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-projection_vector-from-stack>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4142>
    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 = if (x, y) == (0, 0) {
            self.graphics.proj_vector
        } else {
            math::normalize14(x, y)
        };
        self.graphics.proj_vector = vector;
        self.graphics.dual_proj_vector = vector;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-freedom_vector-from-stack>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4169>
    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 = if (x, y) == (0, 0) {
            self.graphics.freedom_vector
        } else {
            math::normalize14(x, y)
        };
        self.graphics.freedom_vector = vector;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-projection_vector>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4194>
    pub(super) fn op_gpv(&mut self) -> OpResult {
        let vector = self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-freedom_vector>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4209>
    pub(super) fn op_gfv(&mut self) -> OpResult {
        let vector = self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-reference-point-0>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4224>
    pub(super) fn op_srp0(&mut self) -> OpResult {
        let p = self.value_stack.pop_usize()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-reference-point-1>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4238>
    pub(super) fn op_srp1(&mut self) -> OpResult {
        let p = self.value_stack.pop_usize()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-reference-point-2>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4252>
    pub(super) fn op_srp2(&mut self) -> OpResult {
        let p = self.value_stack.pop_usize()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-zone-pointer-0>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4746>
    pub(super) fn op_szp0(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-zone-pointer-1>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4776>
    pub(super) fn op_szp1(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-zone-pointer-2>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4806>
    pub(super) fn op_szp2(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-zone-pointers>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4836>
    pub(super) fn op_szps(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        let zp = n.try_into()?;
        self.graphics.zp0 = zp;
        self.graphics.zp1 = zp;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-to-half-grid>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4393>
    pub(super) fn op_rthg(&mut self) -> OpResult {
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-to-grid>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4407>
    pub(super) fn op_rtg(&mut self) -> OpResult {
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-to-double-grid>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4420>
    pub(super) fn op_rtdg(&mut self) -> OpResult {
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-down-to-grid>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4447>
    pub(super) fn op_rdtg(&mut self) -> OpResult {
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-up-to-grid>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4433>
    pub(super) fn op_rutg(&mut self) -> OpResult {
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#round-off>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4461>
    pub(super) fn op_roff(&mut self) -> OpResult {
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#super-round>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4475>
    pub(super) fn op_sround(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        self.super_round(0x4000, n);
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#super-round-45-degrees>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4492>
    pub(super) fn op_s45round(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        self.super_round(0x2D41, n);
        self.graphics.round_state.mode = RoundMode::Super45;
        Ok(())
    }

    /// Helper function for decomposing period, phase and threshold for
    /// the SROUND[] and SROUND45[] instructions.
    ///
    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2299>
    fn super_round(&mut self, grid_period: i32, selector: i32) {
        let round_state = &mut self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-loop-variable>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3287>
    pub(super) fn op_sloop(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        if n < 0 {
            return Err(HintErrorKind::NegativeLoopCounter);
        }
        // As in FreeType, heuristically limit the number of loops to 16 bits.
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-minimum_distance>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4266>
    pub(super) fn op_smd(&mut self) -> OpResult {
        let distance = self.value_stack.pop_f26dot6()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#instruction-execution-control>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4871>
    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(());
        }
        // If preserving linear metrics, prevent modification of the backward
        // compatibility flag.
        if selector == 3 && self.graphics.mode.preserve_linear_metrics() {
            return Ok(());
        }
        match (self.program.initial, selector) {
            // Typically, this instruction can only be executed in the prep table.
            (Program::ControlValue, _) => {
                self.graphics.instruct_control &= !(selector_flag as u8);
                self.graphics.instruct_control |= value as u8;
            }
            // Allow an exception in the glyph program for selector 3 which can
            // temporarily disable backward compatibility mode.
            (Program::Glyph, 3) => {
                self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#scan-conversion-control>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4933>
    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 scan_control for all
            // sizes.
            0xFF => self.graphics.scan_control = true,
            // A value of 0 in bits 0-7 means never invoke scan_control.
            0 => self.graphics.scan_control = false,
            _ => {
                let ppem = self.graphics.ppem;
                let is_rotated = self.graphics.is_rotated;
                let is_stretched = self.graphics.is_stretched;
                let scan_control = &mut self.graphics.scan_control;
                // 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 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 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 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 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 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 scan_control to FALSE based on rotation
                    // state.
                    *scan_control = false;
                }
                if (n & 0x2000) != 0 && is_stretched {
                    // Bit 13: Set scan_control to FALSE based on stretched
                    // state.
                    *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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#scantype>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4980>
    pub(super) fn op_scantype(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-control-value-table-cut-in>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4280>
    pub(super) fn op_scvtci(&mut self) -> OpResult {
        let n = self.value_stack.pop_f26dot6()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-single_width_cut_in>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4294>
    pub(super) fn op_sswci(&mut self) -> OpResult {
        let n = self.value_stack.pop_f26dot6()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-single-width>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4308>
    pub(super) fn op_ssw(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        self.graphics.single_width = F26Dot6::from_bits(math::mul(n, self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-the-auto_flip-boolean-to-on>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4323>
    pub(super) fn op_flipon(&mut self) -> OpResult {
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-the-auto_flip-boolean-to-off>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4336>
    pub(super) fn op_flipoff(&mut self) -> OpResult {
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-angle_weight>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4349>
    pub(super) fn op_sangw(&mut self) -> OpResult {
        // totally unsupported but we still need to pop the stack value
        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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-delta_base-in-the-graphics-state>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4362>
    pub(super) fn op_sdb(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        self.graphics.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 <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#set-delta_shift-in-the-graphics-state>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4376>
    pub(super) fn op_sds(&mut self) -> OpResult {
        let n = self.value_stack.pop()?;
        if n as u32 > 6 {
            Err(HintErrorKind::InvalidStackValue(n))
        } else {
            self.graphics.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 <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4009>
fn line_vector(p1: Point<F26Dot6>, p2: Point<F26Dot6>, is_parallel: bool) -> Point<i32> {
    let mut a = (p1.x - p2.x).to_bits();
    let mut b = (p1.y - p2.y).to_bits();
    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)
}

#[cfg(test)]
mod tests {
    use super::{
        super::{
            super::zone::{Zone, ZonePointer},
            math, F2Dot14, MockEngine,
        },
        F26Dot6, HintErrorKind, Point, Program, RoundMode,
    };

    // Some helpful constants for testing vectors
    const ONE: i32 = F2Dot14::ONE.to_bits() as i32;

    const X_AXIS: Point<i32> = Point::new(ONE, 0);
    const Y_AXIS: Point<i32> = 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.freedom_vector, Y_AXIS);
        assert_eq!(engine.graphics.proj_vector, Y_AXIS);
        // freedom and projection vector to x axis
        engine.op_svtca(0x01).unwrap();
        assert_eq!(engine.graphics.freedom_vector, X_AXIS);
        assert_eq!(engine.graphics.proj_vector, X_AXIS);
        // projection vector to y axis
        engine.op_svtca(0x02).unwrap();
        assert_eq!(engine.graphics.proj_vector, Y_AXIS);
        // projection vector to x axis
        engine.op_svtca(0x03).unwrap();
        assert_eq!(engine.graphics.proj_vector, X_AXIS);
        // freedom vector to y axis
        engine.op_svtca(0x04).unwrap();
        assert_eq!(engine.graphics.freedom_vector, Y_AXIS);
        // freedom vector to x axis
        engine.op_svtca(0x05).unwrap();
        assert_eq!(engine.graphics.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.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.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)].map(|p| p.map(F26Dot6::from_bits));
        let original =
            &mut [Point::new(0, 64), Point::new(0, -64)].map(|p| p.map(F26Dot6::from_bits));
        engine.graphics.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.proj_vector, X_AXIS);
            // (perpendicular)
            engine.op_svtl(0x7).unwrap();
            assert_eq!(engine.graphics.proj_vector, Point::new(0, ONE));
        }
        // SFVTL: set freedom vector to line:
        {
            // (parallel)
            engine.op_svtl(0x8).unwrap();
            assert_eq!(engine.graphics.freedom_vector, X_AXIS);
            // (perpendicular)
            engine.op_svtl(0x9).unwrap();
            assert_eq!(engine.graphics.freedom_vector, Point::new(0, ONE));
        }
        // SDPVTL: set dual projection vector to line:
        {
            // (parallel)
            engine.op_sdpvtl(0x86).unwrap();
            assert_eq!(engine.graphics.dual_proj_vector, Point::new(0, -ONE));
            // (perpendicular)
            engine.op_sdpvtl(0x87).unwrap();
            assert_eq!(engine.graphics.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.rp0, 111);
        // srp1
        engine.value_stack.push(222).unwrap();
        engine.op_srp1().unwrap();
        assert_eq!(engine.graphics.rp1, 222);
        // srp2
        engine.value_stack.push(333).unwrap();
        engine.op_srp2().unwrap();
        assert_eq!(engine.graphics.rp2, 333);
        // zp0
        engine.value_stack.push(1).unwrap();
        engine.op_szp0().unwrap();
        assert_eq!(engine.graphics.zp0, ZonePointer::Glyph);
        // zp1
        engine.value_stack.push(0).unwrap();
        engine.op_szp1().unwrap();
        assert_eq!(engine.graphics.zp1, ZonePointer::Twilight);
        // zp2
        engine.value_stack.push(1).unwrap();
        engine.op_szp2().unwrap();
        assert_eq!(engine.graphics.zp2, ZonePointer::Glyph);
        // zps
        engine.value_stack.push(0).unwrap();
        engine.op_szps().unwrap();
        assert_eq!(
            [
                engine.graphics.zp0,
                engine.graphics.zp1,
                engine.graphics.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.round_state.mode, RoundMode::Grid);
        // rtdg
        engine.op_rtdg().unwrap();
        assert_eq!(engine.graphics.round_state.mode, RoundMode::DoubleGrid);
        // rdtg
        engine.op_rdtg().unwrap();
        assert_eq!(engine.graphics.round_state.mode, RoundMode::DownToGrid);
        // rutg
        engine.op_rutg().unwrap();
        assert_eq!(engine.graphics.round_state.mode, RoundMode::UpToGrid);
        // roff
        engine.op_roff().unwrap();
        assert_eq!(engine.graphics.round_state.mode, RoundMode::Off);
        // sround
        engine.value_stack.push(0).unwrap();
        engine.op_sround().unwrap();
        assert_eq!(engine.graphics.round_state.mode, RoundMode::Super);
        // s45round
        engine.value_stack.push(0).unwrap();
        engine.op_s45round().unwrap();
        assert_eq!(engine.graphics.round_state.mode, RoundMode::Super45);
        // sloop
        engine.value_stack.push(10).unwrap();
        engine.op_sloop().unwrap();
        assert_eq!(engine.graphics.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.min_distance, F26Dot6::from_bits(64));
        // scantype
        engine.value_stack.push(50).unwrap();
        engine.op_scantype().unwrap();
        assert_eq!(engine.graphics.scan_type, 50);
        // scvtci
        engine.value_stack.push(128).unwrap();
        engine.op_scvtci().unwrap();
        assert_eq!(engine.graphics.control_value_cutin, F26Dot6::from_bits(128));
        // sswci
        engine.value_stack.push(100).unwrap();
        engine.op_sswci().unwrap();
        assert_eq!(engine.graphics.single_width_cutin, F26Dot6::from_bits(100));
        // ssw
        engine.graphics.scale = 64;
        engine.value_stack.push(100).unwrap();
        engine.op_ssw().unwrap();
        assert_eq!(
            engine.graphics.single_width,
            F26Dot6::from_bits(math::mul(100, engine.graphics.scale))
        );
        // flipoff
        engine.op_flipoff().unwrap();
        assert!(!engine.graphics.auto_flip);
        // flipon
        engine.op_flipon().unwrap();
        assert!(engine.graphics.auto_flip);
        // sdb
        engine.value_stack.push(172).unwrap();
        engine.op_sdb().unwrap();
        assert_eq!(engine.graphics.delta_base, 172);
        // sds
        engine.value_stack.push(4).unwrap();
        engine.op_sds().unwrap();
        assert_eq!(engine.graphics.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.program.initial = Program::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.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.instruct_control & enable_mask == 0);
        }
        // in glyph programs, selector 3 can be used to toggle
        // backward_compatibility
        engine.program.initial = Program::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.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.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.scan_control);
        // 0x01FF   Always do dropout control
        engine.value_stack.push(0x01FF).unwrap();
        engine.op_scanctrl().unwrap();
        assert!(engine.graphics.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.is_rotated = true;
        engine.graphics.ppem = 12;
        engine.op_scanctrl().unwrap();
        assert!(engine.graphics.scan_control);
    }
}