skrifa/outline/glyf/hint/engine/data.rs
1//! Reading and writing data.
2//!
3//! Implements 7 instructions.
4//!
5//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#reading-and-writing-data>
6
7use super::{
8 super::{math, zone::ZonePointer},
9 Engine, OpResult,
10};
11
12impl Engine<'_> {
13 /// Get coordinate project in to the projection vector.
14 ///
15 /// GC\[a\] (0x46 - 0x47)
16 ///
17 /// a: 0: use current position of point p
18 /// 1: use the position of point p in the original outline
19 ///
20 /// Pops: p: point number
21 /// Pushes: value: coordinate location (F26Dot6)
22 ///
23 /// Measures the coordinate value of point p on the current
24 /// projection_vector and pushes the value onto the stack.
25 ///
26 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-coordinate-projected-onto-the-projection_vector>
27 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4512>
28 pub(super) fn op_gc(&mut self, opcode: u8) -> OpResult {
29 let p = self.value_stack.pop_usize()?;
30 let gs = &mut self.graphics;
31 if !gs.is_pedantic && !gs.in_bounds([(gs.zp2, p)]) {
32 self.value_stack.push(0)?;
33 return Ok(());
34 }
35 let value = if (opcode & 1) != 0 {
36 gs.dual_project(gs.zp2().original(p)?, Default::default())
37 } else {
38 gs.project(gs.zp2().point(p)?, Default::default())
39 };
40 self.value_stack.push(value.to_bits())?;
41 Ok(())
42 }
43
44 /// Set coordinate from the stack using projection vector and freedom
45 /// vector.
46 ///
47 /// SCFS[] (0x48)
48 ///
49 /// Pops: value: distance from origin to move point (F26Dot6)
50 /// p: point number
51 ///
52 /// Moves point p from its current position along the freedom_vector so
53 /// that its component along the projection_vector becomes the value popped
54 /// off the stack.
55 ///
56 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#sets-coordinate-from-the-stack-using-projection_vector-and-freedom_vector>
57 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4550>
58 pub(super) fn op_scfs(&mut self) -> OpResult {
59 let value = self.value_stack.pop_f26dot6()?;
60 let p = self.value_stack.pop_usize()?;
61 let gs = &mut self.graphics;
62 let projection = gs.project(gs.zp2().point(p)?, Default::default());
63 gs.move_point(gs.zp2, p, value.wrapping_sub(projection))?;
64 if gs.zp2.is_twilight() {
65 let twilight = gs.zone_mut(ZonePointer::Twilight);
66 *twilight.original_mut(p)? = twilight.point(p)?;
67 }
68 Ok(())
69 }
70
71 /// Measure distance.
72 ///
73 /// MD\[a\] (0x46 - 0x47)
74 ///
75 /// a: 0: measure distance in grid-fitted outline
76 /// 1: measure distance in original outline
77 ///
78 /// Pops: p1: point number
79 /// p2: point number
80 /// Pushes: distance (F26Dot6)
81 ///
82 /// Measures the distance between outline point p1 and outline point p2.
83 /// The value returned is in pixels (F26Dot6) If distance is negative, it
84 /// was measured against the projection vector. Reversing the order in
85 /// which the points are listed will change the sign of the result.
86 ///
87 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#measure-distance>
88 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L4593>
89 pub(super) fn op_md(&mut self, opcode: u8) -> OpResult {
90 let p1 = self.value_stack.pop_usize()?;
91 let p2 = self.value_stack.pop_usize()?;
92 let gs = &self.graphics;
93 if !gs.is_pedantic && !gs.in_bounds([(gs.zp0, p2), (gs.zp1, p1)]) {
94 self.value_stack.push(0)?;
95 return Ok(());
96 }
97 let distance = if (opcode & 1) != 0 {
98 // measure in grid fitted outline
99 gs.project(gs.zp0().point(p2)?, gs.zp1().point(p1)?)
100 .to_bits()
101 } else if gs.zp0.is_twilight() || gs.zp1.is_twilight() {
102 // special case for twilight zone
103 gs.dual_project(gs.zp0().original(p2)?, gs.zp1().original(p1)?)
104 .to_bits()
105 } else {
106 // measure in original unscaled outline
107 math::mul(
108 gs.dual_project_unscaled(gs.zp0().unscaled(p2), gs.zp1().unscaled(p1)),
109 gs.unscaled_to_pixels(),
110 )
111 };
112 self.value_stack.push(distance)
113 }
114
115 /// Measure pixels per em.
116 ///
117 /// MPPEM[] (0x4B)
118 ///
119 /// Pushes: ppem: pixels per em (uint32)
120 ///
121 /// This instruction pushes the number of pixels per em onto the stack.
122 /// Pixels per em is a function of the resolution of the rendering device
123 /// and the current point size and the current transformation matrix.
124 ///
125 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#measure-pixels-per-em>
126 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2609>
127 pub(super) fn op_mppem(&mut self) -> OpResult {
128 self.value_stack.push(self.graphics.ppem)
129 }
130
131 /// Measure point size.
132 ///
133 /// MPS[] (0x4C)
134 ///
135 /// Pushes: pointSize: the size in points of the current glyph (F26Dot6)
136 ///
137 /// Measure point size can be used to obtain a value which serves as the
138 /// basis for choosing whether to branch to an alternative path through the
139 /// instruction stream. It makes it possible to treat point sizes below or
140 /// above a certain threshold differently.
141 ///
142 /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#measure-point-size>
143 /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2623>
144 pub(super) fn op_mps(&mut self) -> OpResult {
145 // Note: FreeType computes this at
146 // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttdriver.c#L392>
147 // which is mul_div(ppem, 64 * 72, resolution) where resolution
148 // is always 72 for our purposes (Skia), resulting in ppem * 64.
149 self.value_stack.push(self.graphics.ppem.saturating_mul(64))
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::super::{super::zone::ZonePointer, math, Engine, MockEngine};
156 use raw::types::F26Dot6;
157
158 #[test]
159 fn measure_ppem_and_point_size() {
160 let mut mock = MockEngine::new();
161 let mut engine = mock.engine();
162 let ppem = 20;
163 engine.graphics.ppem = ppem;
164 engine.op_mppem().unwrap();
165 assert_eq!(engine.value_stack.pop().unwrap(), ppem);
166 engine.op_mps().unwrap();
167 assert_eq!(engine.value_stack.pop().unwrap(), ppem * 64);
168 }
169
170 #[test]
171 fn mps_saturates_on_large_ppem() {
172 let mut mock = MockEngine::new();
173 let mut engine = mock.engine();
174 engine.graphics.ppem = i32::MAX;
175 engine.op_mps().unwrap();
176 assert_eq!(engine.value_stack.pop().unwrap(), i32::MAX);
177 }
178
179 #[test]
180 fn gc() {
181 let mut mock = MockEngine::new();
182 let mut engine = mock.engine();
183 set_test_vectors(&mut engine);
184 // current point projected coord
185 let point = engine.graphics.zones[1].point_mut(1).unwrap();
186 point.x = F26Dot6::from_bits(132);
187 point.y = F26Dot6::from_bits(-256);
188 engine.value_stack.push(1).unwrap();
189 engine.op_gc(0).unwrap();
190 assert_eq!(engine.value_stack.pop().unwrap(), 4);
191 // original point projected coord
192 let point = engine.graphics.zones[1].original_mut(1).unwrap();
193 point.x = F26Dot6::from_bits(-64);
194 point.y = F26Dot6::from_bits(521);
195 engine.value_stack.push(1).unwrap();
196 engine.op_gc(1).unwrap();
197 assert_eq!(engine.value_stack.pop().unwrap(), 176);
198 }
199
200 #[test]
201 fn scfs() {
202 let mut mock = MockEngine::new();
203 let mut engine = mock.engine();
204 set_test_vectors(&mut engine);
205 // This instruction is a nop in backward compatibility mode
206 // and before IUP.
207 engine.graphics.backward_compatibility = false;
208 engine.graphics.did_iup_x = true;
209 engine.graphics.did_iup_y = true;
210 // use the twilight zone to test the optional code path
211 engine.graphics.zp2 = ZonePointer::Twilight;
212 let point = engine.graphics.zones[0].point_mut(1).unwrap();
213 point.x = F26Dot6::from_bits(132);
214 point.y = F26Dot6::from_bits(-256);
215 // assert we're not currently the same
216 assert_ne!(
217 engine.graphics.zones[0].point(1).unwrap(),
218 engine.graphics.zones[0].original(1).unwrap()
219 );
220 // push point number
221 engine.value_stack.push(1).unwrap();
222 // push value to match
223 engine.value_stack.push(42).unwrap();
224 // set coordinate from stack!
225 engine.op_scfs().unwrap();
226 let point = engine.graphics.zones[0].point(1).unwrap();
227 assert_eq!(point.x.to_bits(), 166);
228 assert_eq!(point.y.to_bits(), -239);
229 // ensure that we set original = point
230 assert_eq!(point, engine.graphics.zones[0].original(1).unwrap());
231 }
232
233 #[test]
234 fn md_scaled() {
235 let mut mock = MockEngine::new();
236 let mut engine = mock.engine();
237 set_test_vectors(&mut engine);
238 // first path, measure in grid fitted outline
239 let zone = engine.graphics.zone_mut(ZonePointer::Glyph);
240 let point1 = zone.point_mut(1).unwrap();
241 point1.x = F26Dot6::from_bits(132);
242 point1.y = F26Dot6::from_bits(-256);
243 let point2 = zone.point_mut(3).unwrap();
244 point2.x = F26Dot6::from_bits(-64);
245 point2.y = F26Dot6::from_bits(100);
246 // now measure
247 engine.value_stack.push(1).unwrap();
248 engine.value_stack.push(3).unwrap();
249 engine.op_md(1).unwrap();
250 assert_eq!(engine.value_stack.pop().unwrap(), 16);
251 }
252
253 #[test]
254 fn md_unscaled() {
255 let mut mock = MockEngine::new();
256 let mut engine = mock.engine();
257 set_test_vectors(&mut engine);
258 // second path, measure in original unscaled outline.
259 // unscaled points are set in mock engine but we need a scale
260 engine.graphics.scale = 375912;
261 engine.value_stack.push(1).unwrap();
262 engine.value_stack.push(3).unwrap();
263 engine.op_md(0).unwrap();
264 assert_eq!(engine.value_stack.pop().unwrap(), 11);
265 }
266
267 #[test]
268 fn md_twilight() {
269 let mut mock = MockEngine::new();
270 let mut engine = mock.engine();
271 set_test_vectors(&mut engine);
272 // final path, measure in original outline, in twilight zone
273 engine.graphics.zp0 = ZonePointer::Twilight;
274 engine.graphics.zp1 = ZonePointer::Twilight;
275 // set some points
276 let zone = engine.graphics.zone_mut(ZonePointer::Twilight);
277 let point1 = zone.original_mut(1).unwrap();
278 point1.x = F26Dot6::from_bits(132);
279 point1.y = F26Dot6::from_bits(-256);
280 let point2 = zone.original_mut(3).unwrap();
281 point2.x = F26Dot6::from_bits(-64);
282 point2.y = F26Dot6::from_bits(100);
283 // now measure
284 engine.value_stack.push(1).unwrap();
285 engine.value_stack.push(3).unwrap();
286 engine.op_md(0).unwrap();
287 assert_eq!(engine.value_stack.pop().unwrap(), 16);
288 }
289
290 fn set_test_vectors(engine: &mut Engine) {
291 let v = math::normalize14(100, 50);
292 engine.graphics.proj_vector = v;
293 engine.graphics.dual_proj_vector = v;
294 engine.graphics.freedom_vector = v;
295 engine.graphics.update_projection_state();
296 }
297}