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