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}