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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
//! Managing the control value table.
//!
//! Implements 3 instructions.
//!
//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#managing-the-control-value-table>

use super::{super::math::mul, Engine, F26Dot6, OpResult};

impl<'a> Engine<'a> {
    /// Write control value table in pixel units.
    ///
    /// WCVTP[] (0x44)
    ///
    /// Pops: value: number in pixels (F26Dot6 fixed point number),
    ///       location: Control Value Table location (uint32)
    ///
    /// Pops a location and a value from the stack and puts that value in the
    /// specified location in the Control Value Table. This instruction assumes
    /// the value is in pixels and not in FUnits.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#write-control-value-table-in-pixel-units>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3044>
    pub(super) fn op_wcvtp(&mut self) -> OpResult {
        let value = self.value_stack.pop_f26dot6()?;
        let location = self.value_stack.pop_usize()?;
        let result = self.cvt.set(location, value);
        if self.graphics.is_pedantic {
            result
        } else {
            Ok(())
        }
    }

    /// Write control value table in font units.
    ///
    /// WCVTF[] (0x70)
    ///
    /// Pops: value: number in pixels (F26Dot6 fixed point number),
    ///       location: Control Value Table location (uint32)
    ///
    /// Pops a location and a value from the stack and puts the specified
    /// value in the specified address in the Control Value Table. This
    /// instruction assumes the value is expressed in FUnits and not pixels.
    /// The value is scaled before being written to the table.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#write-control-value-table-in-funits>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3067>
    pub(super) fn op_wcvtf(&mut self) -> OpResult {
        let value = self.value_stack.pop()?;
        let location = self.value_stack.pop_usize()?;
        let result = self.cvt.set(
            location,
            F26Dot6::from_bits(mul(value, self.graphics.scale)),
        );
        if self.graphics.is_pedantic {
            result
        } else {
            Ok(())
        }
    }

    /// Read control value table.
    ///
    /// RCVT[] (0x45)
    ///
    /// Pops: location: CVT entry number
    /// Pushes: value: CVT value (F26Dot6)
    ///
    /// Pops a location from the stack and pushes the value in the location
    /// specified in the Control Value Table onto the stack.
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#read-control-value-table>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L3090>
    pub(super) fn op_rcvt(&mut self) -> OpResult {
        let location = self.value_stack.pop()? as usize;
        let maybe_value = self.cvt.get(location);
        let value = if self.graphics.is_pedantic {
            maybe_value?
        } else {
            maybe_value.unwrap_or_default()
        };
        self.value_stack.push(value.to_bits())
    }
}

#[cfg(test)]
mod tests {
    use super::super::{super::math, HintErrorKind, MockEngine};

    #[test]
    fn write_read() {
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        for i in 0..8 {
            engine.value_stack.push(i).unwrap();
            engine.value_stack.push(i * 2).unwrap();
            engine.op_wcvtp().unwrap();
        }
        for i in 0..8 {
            engine.value_stack.push(i).unwrap();
            engine.op_rcvt().unwrap();
            assert_eq!(engine.value_stack.pop().unwrap(), i * 2);
        }
    }

    #[test]
    fn write_scaled_read() {
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        let scale = 64;
        engine.graphics.scale = scale;
        for i in 0..8 {
            engine.value_stack.push(i).unwrap();
            engine.value_stack.push(i * 2).unwrap();
            // WCVTF takes a value in font units and converts to pixels
            // with the current scale
            engine.op_wcvtf().unwrap();
        }
        for i in 0..8 {
            engine.value_stack.push(i).unwrap();
            engine.op_rcvt().unwrap();
            let value = engine.value_stack.pop().unwrap();
            assert_eq!(value, math::mul(i * 2, scale));
        }
    }

    #[test]
    fn pedantry() {
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        let oob_index = 1000;
        // Disable pedantic mode: OOB writes are ignored, OOB reads
        // push 0
        engine.graphics.is_pedantic = false;
        engine.value_stack.push(oob_index).unwrap();
        engine.value_stack.push(0).unwrap();
        engine.op_wcvtp().unwrap();
        engine.value_stack.push(oob_index).unwrap();
        engine.value_stack.push(0).unwrap();
        engine.op_wcvtf().unwrap();
        engine.value_stack.push(oob_index).unwrap();
        engine.op_rcvt().unwrap();
        // Enable pedantic mode: OOB reads/writes error
        engine.graphics.is_pedantic = true;
        engine.value_stack.push(oob_index).unwrap();
        engine.value_stack.push(0).unwrap();
        assert_eq!(
            engine.op_wcvtp(),
            Err(HintErrorKind::InvalidCvtIndex(oob_index as _))
        );
        engine.value_stack.push(oob_index).unwrap();
        engine.value_stack.push(0).unwrap();
        assert_eq!(
            engine.op_wcvtf(),
            Err(HintErrorKind::InvalidCvtIndex(oob_index as _))
        );
        engine.value_stack.push(oob_index).unwrap();
        assert_eq!(
            engine.op_rcvt(),
            Err(HintErrorKind::InvalidCvtIndex(oob_index as _))
        );
    }
}