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 * 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 gc() {
172 let mut mock = MockEngine::new();
173 let mut engine = mock.engine();
174 set_test_vectors(&mut engine);
175 // current point projected coord
176 let point = engine.graphics.zones[1].point_mut(1).unwrap();
177 point.x = F26Dot6::from_bits(132);
178 point.y = F26Dot6::from_bits(-256);
179 engine.value_stack.push(1).unwrap();
180 engine.op_gc(0).unwrap();
181 assert_eq!(engine.value_stack.pop().unwrap(), 4);
182 // original point projected coord
183 let point = engine.graphics.zones[1].original_mut(1).unwrap();
184 point.x = F26Dot6::from_bits(-64);
185 point.y = F26Dot6::from_bits(521);
186 engine.value_stack.push(1).unwrap();
187 engine.op_gc(1).unwrap();
188 assert_eq!(engine.value_stack.pop().unwrap(), 176);
189 }
190
191 #[test]
192 fn scfs() {
193 let mut mock = MockEngine::new();
194 let mut engine = mock.engine();
195 set_test_vectors(&mut engine);
196 // This instruction is a nop in backward compatibility mode
197 // and before IUP.
198 engine.graphics.backward_compatibility = false;
199 engine.graphics.did_iup_x = true;
200 engine.graphics.did_iup_y = true;
201 // use the twilight zone to test the optional code path
202 engine.graphics.zp2 = ZonePointer::Twilight;
203 let point = engine.graphics.zones[0].point_mut(1).unwrap();
204 point.x = F26Dot6::from_bits(132);
205 point.y = F26Dot6::from_bits(-256);
206 // assert we're not currently the same
207 assert_ne!(
208 engine.graphics.zones[0].point(1).unwrap(),
209 engine.graphics.zones[0].original(1).unwrap()
210 );
211 // push point number
212 engine.value_stack.push(1).unwrap();
213 // push value to match
214 engine.value_stack.push(42).unwrap();
215 // set coordinate from stack!
216 engine.op_scfs().unwrap();
217 let point = engine.graphics.zones[0].point(1).unwrap();
218 assert_eq!(point.x.to_bits(), 166);
219 assert_eq!(point.y.to_bits(), -239);
220 // ensure that we set original = point
221 assert_eq!(point, engine.graphics.zones[0].original(1).unwrap());
222 }
223
224 #[test]
225 fn md_scaled() {
226 let mut mock = MockEngine::new();
227 let mut engine = mock.engine();
228 set_test_vectors(&mut engine);
229 // first path, measure in grid fitted outline
230 let zone = engine.graphics.zone_mut(ZonePointer::Glyph);
231 let point1 = zone.point_mut(1).unwrap();
232 point1.x = F26Dot6::from_bits(132);
233 point1.y = F26Dot6::from_bits(-256);
234 let point2 = zone.point_mut(3).unwrap();
235 point2.x = F26Dot6::from_bits(-64);
236 point2.y = F26Dot6::from_bits(100);
237 // now measure
238 engine.value_stack.push(1).unwrap();
239 engine.value_stack.push(3).unwrap();
240 engine.op_md(1).unwrap();
241 assert_eq!(engine.value_stack.pop().unwrap(), 16);
242 }
243
244 #[test]
245 fn md_unscaled() {
246 let mut mock = MockEngine::new();
247 let mut engine = mock.engine();
248 set_test_vectors(&mut engine);
249 // second path, measure in original unscaled outline.
250 // unscaled points are set in mock engine but we need a scale
251 engine.graphics.scale = 375912;
252 engine.value_stack.push(1).unwrap();
253 engine.value_stack.push(3).unwrap();
254 engine.op_md(0).unwrap();
255 assert_eq!(engine.value_stack.pop().unwrap(), 11);
256 }
257
258 #[test]
259 fn md_twilight() {
260 let mut mock = MockEngine::new();
261 let mut engine = mock.engine();
262 set_test_vectors(&mut engine);
263 // final path, measure in original outline, in twilight zone
264 engine.graphics.zp0 = ZonePointer::Twilight;
265 engine.graphics.zp1 = ZonePointer::Twilight;
266 // set some points
267 let zone = engine.graphics.zone_mut(ZonePointer::Twilight);
268 let point1 = zone.original_mut(1).unwrap();
269 point1.x = F26Dot6::from_bits(132);
270 point1.y = F26Dot6::from_bits(-256);
271 let point2 = zone.original_mut(3).unwrap();
272 point2.x = F26Dot6::from_bits(-64);
273 point2.y = F26Dot6::from_bits(100);
274 // now measure
275 engine.value_stack.push(1).unwrap();
276 engine.value_stack.push(3).unwrap();
277 engine.op_md(0).unwrap();
278 assert_eq!(engine.value_stack.pop().unwrap(), 16);
279 }
280
281 fn set_test_vectors(engine: &mut Engine) {
282 let v = math::normalize14(100, 50);
283 engine.graphics.proj_vector = v;
284 engine.graphics.dual_proj_vector = v;
285 engine.graphics.freedom_vector = v;
286 engine.graphics.update_projection_state();
287 }
288}