skrifa/outline/autohint/topo/
mod.rs

1//! Topology analysis of segments and edges.
2
3mod edges;
4mod segments;
5
6use super::{
7    metrics::ScaledWidth,
8    outline::{Direction, Orientation, Point},
9};
10use crate::collections::SmallVec;
11
12pub(crate) use edges::{compute_blue_edges, compute_edges};
13pub(crate) use segments::{compute_segments, link_segments};
14
15/// Maximum number of segments and edges stored inline.
16///
17/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afhints.h#L306>
18const MAX_INLINE_SEGMENTS: usize = 18;
19const MAX_INLINE_EDGES: usize = 12;
20
21/// Either horizontal or vertical.
22///
23/// A type alias because it's used as an index.
24pub type Dimension = usize;
25
26/// Segments and edges for one dimension of an outline.
27///
28/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afhints.h#L309>
29#[derive(Clone, Default, Debug)]
30pub(crate) struct Axis {
31    /// Either horizontal or vertical.
32    pub dim: Dimension,
33    /// Depends on dimension and outline orientation.
34    pub major_dir: Direction,
35    /// Collection of segments for the axis.
36    pub segments: SmallVec<Segment, MAX_INLINE_SEGMENTS>,
37    /// Collection of edges for the axis.
38    pub edges: SmallVec<Edge, MAX_INLINE_EDGES>,
39}
40
41impl Axis {
42    /// X coordinates, i.e. vertical segments and edges.
43    pub const HORIZONTAL: Dimension = 0;
44    /// Y coordinates, i.e. horizontal segments and edges.
45    pub const VERTICAL: Dimension = 1;
46}
47
48impl Axis {
49    #[cfg(test)]
50    pub fn new(dim: Dimension, orientation: Option<Orientation>) -> Self {
51        let mut axis = Self::default();
52        axis.reset(dim, orientation);
53        axis
54    }
55
56    pub fn reset(&mut self, dim: Dimension, orientation: Option<Orientation>) {
57        self.dim = dim;
58        self.major_dir = match (dim, orientation) {
59            (Self::HORIZONTAL, Some(Orientation::Clockwise)) => Direction::Down,
60            (Self::VERTICAL, Some(Orientation::Clockwise)) => Direction::Right,
61            (Self::HORIZONTAL, _) => Direction::Up,
62            (Self::VERTICAL, _) => Direction::Left,
63            _ => Direction::None,
64        };
65        self.segments.clear();
66        self.edges.clear();
67    }
68}
69
70impl Axis {
71    /// Inserts the given edge into the sorted edge list.
72    ///
73    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afhints.c#L197>
74    pub fn insert_edge(&mut self, edge: Edge, top_to_bottom_hinting: bool) {
75        self.edges.push(edge);
76        let edges = self.edges.as_mut_slice();
77        // If this is the first edge, we're done.
78        if edges.len() == 1 {
79            return;
80        }
81        // Now move it into place
82        let mut ix = edges.len() - 1;
83        while ix > 0 {
84            let prev_ix = ix - 1;
85            let prev_fpos = edges[prev_ix].fpos;
86            if (top_to_bottom_hinting && prev_fpos > edge.fpos)
87                || (!top_to_bottom_hinting && prev_fpos < edge.fpos)
88            {
89                break;
90            }
91            // Edges with the same position and minor direction should appear
92            // before those with the major direction
93            if prev_fpos == edge.fpos && edge.dir == self.major_dir {
94                break;
95            }
96            let prev_edge = edges[prev_ix];
97            edges[ix] = prev_edge;
98            ix -= 1;
99        }
100        edges[ix] = edge;
101    }
102
103    /// Links the given segment and edge.
104    pub fn append_segment_to_edge(&mut self, segment_ix: usize, edge_ix: usize) {
105        let edge = &mut self.edges[edge_ix];
106        let first_ix = edge.first_ix;
107        let last_ix = edge.last_ix;
108        edge.last_ix = segment_ix as u16;
109        let segment = &mut self.segments[segment_ix];
110        segment.edge_next_ix = Some(first_ix);
111        self.segments[last_ix as usize].edge_next_ix = Some(segment_ix as u16);
112    }
113}
114
115/// Sequence of points with a single dominant direction.
116///
117/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afhints.h#L262>
118#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
119pub(crate) struct Segment {
120    /// Flags describing the properties of the segment.
121    pub flags: u8,
122    /// Dominant direction of the segment.
123    pub dir: Direction,
124    /// Position of the segment.
125    pub pos: i16,
126    /// Deviation from segment position.
127    pub delta: i16,
128    /// Minimum coordinate of the segment.
129    pub min_coord: i16,
130    /// Maximum coordinate of the segment.
131    pub max_coord: i16,
132    /// Hinted segment height.
133    pub height: i16,
134    /// Used during stem matching.
135    pub score: i32,
136    /// Used during stem matching.
137    pub len: i32,
138    /// Index of best candidate for a stem link.
139    pub link_ix: Option<u16>,
140    /// Index of best candidate for a serif link.
141    pub serif_ix: Option<u16>,
142    /// Index of first point in the outline.
143    pub first_ix: u16,
144    /// Index of last point in the outline.
145    pub last_ix: u16,
146    /// Index of edge that is associated with the segment.
147    pub edge_ix: Option<u16>,
148    /// Index of next segment in edge's segment list.
149    pub edge_next_ix: Option<u16>,
150}
151
152/// Segment flags.
153///
154/// Note: these are the same as edge flags.
155///
156/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afhints.h#L227>
157impl Segment {
158    pub const NORMAL: u8 = 0;
159    pub const ROUND: u8 = 1;
160    pub const SERIF: u8 = 2;
161    pub const DONE: u8 = 4;
162    pub const NEUTRAL: u8 = 8;
163}
164
165impl Segment {
166    pub fn first(&self) -> usize {
167        self.first_ix as usize
168    }
169
170    pub fn first_point<'a>(&self, points: &'a [Point]) -> &'a Point {
171        &points[self.first()]
172    }
173
174    pub fn last(&self) -> usize {
175        self.last_ix as usize
176    }
177
178    pub fn last_point<'a>(&self, points: &'a [Point]) -> &'a Point {
179        &points[self.last()]
180    }
181
182    pub fn edge<'a>(&self, edges: &'a [Edge]) -> Option<&'a Edge> {
183        edges.get(self.edge_ix.map(|ix| ix as usize)?)
184    }
185
186    /// Returns the next segment in this segment's parent edge.
187    pub fn next_in_edge<'a>(&self, segments: &'a [Segment]) -> Option<&'a Segment> {
188        segments.get(self.edge_next_ix.map(|ix| ix as usize)?)
189    }
190
191    pub fn link<'a>(&self, segments: &'a [Segment]) -> Option<&'a Segment> {
192        segments.get(self.link_ix.map(|ix| ix as usize)?)
193    }
194}
195
196/// Sequence of segments used for grid-fitting.
197///
198/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afhints.h#L286>
199#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)]
200pub(crate) struct Edge {
201    /// Original, unscaled position in font units.
202    pub fpos: i16,
203    /// Original, scaled position.
204    pub opos: i32,
205    /// Current position.
206    pub pos: i32,
207    /// Edge flags.
208    pub flags: u8,
209    /// Edge direction.
210    pub dir: Direction,
211    /// Present if this is a blue edge.
212    pub blue_edge: Option<ScaledWidth>,
213    /// Index of linked edge.
214    pub link_ix: Option<u16>,
215    /// Index of primary edge for serif.
216    pub serif_ix: Option<u16>,
217    /// Used to speed up edge interpolation.
218    pub scale: i32,
219    /// Index of first segment in edge.
220    pub first_ix: u16,
221    /// Index of last segment in edge.
222    pub last_ix: u16,
223}
224
225/// Edge flags.
226///
227/// Note: these are the same as segment flags.
228///
229/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afhints.h#L227>
230impl Edge {
231    pub const NORMAL: u8 = Segment::NORMAL;
232    pub const ROUND: u8 = Segment::ROUND;
233    pub const SERIF: u8 = Segment::SERIF;
234    pub const DONE: u8 = Segment::DONE;
235    pub const NEUTRAL: u8 = Segment::NEUTRAL;
236}
237
238impl Edge {
239    pub fn link<'a>(&self, edges: &'a [Edge]) -> Option<&'a Edge> {
240        edges.get(self.link_ix.map(|ix| ix as usize)?)
241    }
242
243    pub fn serif<'a>(&self, edges: &'a [Edge]) -> Option<&'a Edge> {
244        edges.get(self.serif_ix.map(|ix| ix as usize)?)
245    }
246}