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}