skrifa/outline/glyf/hint/engine/
cvt.rs

1//! Managing the control value table.
2//!
3//! Implements 3 instructions.
4//!
5//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#managing-the-control-value-table>
6
7use super::{super::math::mul, Engine, F26Dot6, OpResult};
8
9impl Engine<'_> {
10    /// Write control value table in pixel units.
11    ///
12    /// WCVTP[] (0x44)
13    ///
14    /// Pops: value: number in pixels (F26Dot6 fixed point number),
15    ///       location: Control Value Table location (uint32)
16    ///
17    /// Pops a location and a value from the stack and puts that value in the
18    /// specified location in the Control Value Table. This instruction assumes
19    /// the value is in pixels and not in FUnits.
20    ///
21    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#write-control-value-table-in-pixel-units>
22    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3044>
23    pub(super) fn op_wcvtp(&mut self) -> OpResult {
24        let value = self.value_stack.pop_f26dot6()?;
25        let location = self.value_stack.pop_usize()?;
26        let result = self.cvt.set(location, value);
27        if self.graphics.is_pedantic {
28            result
29        } else {
30            Ok(())
31        }
32    }
33
34    /// Write control value table in font units.
35    ///
36    /// WCVTF[] (0x70)
37    ///
38    /// Pops: value: number in pixels (F26Dot6 fixed point number),
39    ///       location: Control Value Table location (uint32)
40    ///
41    /// Pops a location and a value from the stack and puts the specified
42    /// value in the specified address in the Control Value Table. This
43    /// instruction assumes the value is expressed in FUnits and not pixels.
44    /// The value is scaled before being written to the table.
45    ///
46    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#write-control-value-table-in-funits>
47    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3067>
48    pub(super) fn op_wcvtf(&mut self) -> OpResult {
49        let value = self.value_stack.pop()?;
50        let location = self.value_stack.pop_usize()?;
51        let result = self.cvt.set(
52            location,
53            F26Dot6::from_bits(mul(value, self.graphics.scale)),
54        );
55        if self.graphics.is_pedantic {
56            result
57        } else {
58            Ok(())
59        }
60    }
61
62    /// Read control value table.
63    ///
64    /// RCVT[] (0x45)
65    ///
66    /// Pops: location: CVT entry number
67    /// Pushes: value: CVT value (F26Dot6)
68    ///
69    /// Pops a location from the stack and pushes the value in the location
70    /// specified in the Control Value Table onto the stack.
71    ///
72    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#read-control-value-table>
73    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3090>
74    pub(super) fn op_rcvt(&mut self) -> OpResult {
75        let location = self.value_stack.pop()? as usize;
76        let maybe_value = self.cvt.get(location);
77        let value = if self.graphics.is_pedantic {
78            maybe_value?
79        } else {
80            maybe_value.unwrap_or_default()
81        };
82        self.value_stack.push(value.to_bits())
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::super::{super::math, HintErrorKind, MockEngine};
89
90    #[test]
91    fn write_read() {
92        let mut mock = MockEngine::new();
93        let mut engine = mock.engine();
94        for i in 0..8 {
95            engine.value_stack.push(i).unwrap();
96            engine.value_stack.push(i * 2).unwrap();
97            engine.op_wcvtp().unwrap();
98        }
99        for i in 0..8 {
100            engine.value_stack.push(i).unwrap();
101            engine.op_rcvt().unwrap();
102            assert_eq!(engine.value_stack.pop().unwrap(), i * 2);
103        }
104    }
105
106    #[test]
107    fn write_scaled_read() {
108        let mut mock = MockEngine::new();
109        let mut engine = mock.engine();
110        let scale = 64;
111        engine.graphics.scale = scale;
112        for i in 0..8 {
113            engine.value_stack.push(i).unwrap();
114            engine.value_stack.push(i * 2).unwrap();
115            // WCVTF takes a value in font units and converts to pixels
116            // with the current scale
117            engine.op_wcvtf().unwrap();
118        }
119        for i in 0..8 {
120            engine.value_stack.push(i).unwrap();
121            engine.op_rcvt().unwrap();
122            let value = engine.value_stack.pop().unwrap();
123            assert_eq!(value, math::mul(i * 2, scale));
124        }
125    }
126
127    #[test]
128    fn pedantry() {
129        let mut mock = MockEngine::new();
130        let mut engine = mock.engine();
131        let oob_index = 1000;
132        // Disable pedantic mode: OOB writes are ignored, OOB reads
133        // push 0
134        engine.graphics.is_pedantic = false;
135        engine.value_stack.push(oob_index).unwrap();
136        engine.value_stack.push(0).unwrap();
137        engine.op_wcvtp().unwrap();
138        engine.value_stack.push(oob_index).unwrap();
139        engine.value_stack.push(0).unwrap();
140        engine.op_wcvtf().unwrap();
141        engine.value_stack.push(oob_index).unwrap();
142        engine.op_rcvt().unwrap();
143        // Enable pedantic mode: OOB reads/writes error
144        engine.graphics.is_pedantic = true;
145        engine.value_stack.push(oob_index).unwrap();
146        engine.value_stack.push(0).unwrap();
147        assert_eq!(
148            engine.op_wcvtp(),
149            Err(HintErrorKind::InvalidCvtIndex(oob_index as _))
150        );
151        engine.value_stack.push(oob_index).unwrap();
152        engine.value_stack.push(0).unwrap();
153        assert_eq!(
154            engine.op_wcvtf(),
155            Err(HintErrorKind::InvalidCvtIndex(oob_index as _))
156        );
157        engine.value_stack.push(oob_index).unwrap();
158        assert_eq!(
159            engine.op_rcvt(),
160            Err(HintErrorKind::InvalidCvtIndex(oob_index as _))
161        );
162    }
163}