skrifa/outline/glyf/hint/
projection.rs

1//! Point projection.
2
3use super::graphics::{CoordAxis, GraphicsState};
4use raw::types::{F26Dot6, Point};
5
6impl GraphicsState<'_> {
7    /// Updates cached state that is derived from projection vectors.
8    pub fn update_projection_state(&mut self) {
9        // 1.0 in 2.14 fixed point.
10        const ONE: i32 = 0x4000;
11        // Based on Compute_Funcs() at
12        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2482>.
13        // FreeType uses function pointers to select between various "modes"
14        // but we use the CoordAxis type instead.
15        if self.freedom_vector.x == ONE {
16            self.fdotp = self.proj_vector.x;
17        } else if self.freedom_vector.y == ONE {
18            self.fdotp = self.proj_vector.y;
19        } else {
20            let px = self.proj_vector.x;
21            let py = self.proj_vector.y;
22            let fx = self.freedom_vector.x;
23            let fy = self.freedom_vector.y;
24            self.fdotp = (px * fx + py * fy) >> 14;
25        }
26        self.proj_axis = CoordAxis::Both;
27        if self.proj_vector.x == ONE {
28            self.proj_axis = CoordAxis::X;
29        } else if self.proj_vector.y == ONE {
30            self.proj_axis = CoordAxis::Y;
31        }
32        self.dual_proj_axis = CoordAxis::Both;
33        if self.dual_proj_vector.x == ONE {
34            self.dual_proj_axis = CoordAxis::X;
35        } else if self.dual_proj_vector.y == ONE {
36            self.dual_proj_axis = CoordAxis::Y;
37        }
38        self.freedom_axis = CoordAxis::Both;
39        if self.fdotp == ONE {
40            if self.freedom_vector.x == ONE {
41                self.freedom_axis = CoordAxis::X;
42            } else if self.freedom_vector.y == ONE {
43                self.freedom_axis = CoordAxis::Y;
44            }
45        }
46        // At small sizes, fdotp can become too small resulting in overflows
47        // and spikes.
48        if self.fdotp.abs() < 0x400 {
49            self.fdotp = ONE;
50        }
51    }
52
53    /// Computes the projection of vector given by (v1 - v2) along the
54    /// current projection vector.
55    #[inline(always)]
56    pub fn project(&self, v1: Point<F26Dot6>, v2: Point<F26Dot6>) -> F26Dot6 {
57        match self.proj_axis {
58            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2431>
59            CoordAxis::X => v1.x - v2.x,
60            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2461>
61            CoordAxis::Y => v1.y - v2.y,
62            CoordAxis::Both => {
63                // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2373>
64                let dx = v1.x - v2.x;
65                let dy = v1.y - v2.y;
66                F26Dot6::from_bits(dot14(
67                    dx.to_bits(),
68                    dy.to_bits(),
69                    self.proj_vector.x,
70                    self.proj_vector.y,
71                ))
72            }
73        }
74    }
75
76    /// Computes the projection of vector given by (v1 - v2) along the
77    /// current dual projection vector.
78    #[inline(always)]
79    pub fn dual_project(&self, v1: Point<F26Dot6>, v2: Point<F26Dot6>) -> F26Dot6 {
80        match self.dual_proj_axis {
81            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2431>
82            CoordAxis::X => v1.x - v2.x,
83            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2461>
84            CoordAxis::Y => v1.y - v2.y,
85            CoordAxis::Both => {
86                // https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2402
87                let dx = v1.x - v2.x;
88                let dy = v1.y - v2.y;
89                F26Dot6::from_bits(dot14(
90                    dx.to_bits(),
91                    dy.to_bits(),
92                    self.dual_proj_vector.x,
93                    self.dual_proj_vector.y,
94                ))
95            }
96        }
97    }
98
99    /// Computes the projection of vector given by (v1 - v2) along the
100    /// current dual projection vector for unscaled points.
101    #[inline(always)]
102    pub fn dual_project_unscaled(&self, v1: Point<i32>, v2: Point<i32>) -> i32 {
103        match self.dual_proj_axis {
104            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2431>
105            CoordAxis::X => v1.x - v2.x,
106            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2461>
107            CoordAxis::Y => v1.y - v2.y,
108            CoordAxis::Both => {
109                // https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L2402
110                let dx = v1.x - v2.x;
111                let dy = v1.y - v2.y;
112                dot14(dx, dy, self.dual_proj_vector.x, self.dual_proj_vector.y)
113            }
114        }
115    }
116}
117
118/// Dot product for vectors in 2.14 fixed point.
119fn dot14(ax: i32, ay: i32, bx: i32, by: i32) -> i32 {
120    let mut v1 = ax as i64 * bx as i64;
121    let v2 = ay as i64 * by as i64;
122    v1 += v2;
123    v1 += 0x2000 + (v1 >> 63);
124    (v1 >> 14) as i32
125}
126
127#[cfg(test)]
128mod tests {
129    use super::{super::math, CoordAxis, F26Dot6, GraphicsState, Point};
130
131    #[test]
132    fn project_one_axis() {
133        let mut state = GraphicsState {
134            proj_vector: math::normalize14(1, 0),
135            ..Default::default()
136        };
137        state.update_projection_state();
138        assert_eq!(state.proj_axis, CoordAxis::X);
139        assert_eq!(state.proj_vector, Point::new(0x4000, 0));
140        let cases = &[
141            (Point::new(0, 0), Point::new(0, 0), 0),
142            (Point::new(100, 100), Point::new(0, 0), 100),
143            (Point::new(42, 100), Point::new(100, 0), -58),
144            (Point::new(0, 0), Point::new(100, 100), -100),
145        ];
146        test_project_cases(&state, cases);
147    }
148
149    #[test]
150    fn project_both_axes() {
151        let mut state = GraphicsState {
152            proj_vector: math::normalize14(0x4000, 0x4000),
153            ..Default::default()
154        };
155        state.update_projection_state();
156        assert_eq!(state.proj_axis, CoordAxis::Both);
157        let cases = &[
158            (Point::new(0, 0), Point::new(0, 0), 0),
159            (Point::new(100, 100), Point::new(0, 0), 141),
160            (Point::new(42, 100), Point::new(100, 0), 30),
161            (Point::new(0, 0), Point::new(100, 100), -141),
162        ];
163        test_project_cases(&state, cases);
164    }
165
166    fn test_project_cases(state: &GraphicsState, cases: &[(Point<i32>, Point<i32>, i32)]) {
167        for (v1, v2, expected) in cases.iter().copied() {
168            let v1 = v1.map(F26Dot6::from_bits);
169            let v2 = v2.map(F26Dot6::from_bits);
170            let result = state.project(v1, v2).to_bits();
171            assert_eq!(
172                result, expected,
173                "project({v1:?}, {v2:?}) = {result} (expected {expected})"
174            );
175        }
176    }
177}