Skip to main content

skrifa/outline/autohint/hint/
mod.rs

1//! Entry point to hinting algorithm.
2
3mod edges;
4mod outline;
5
6use super::{
7    metrics::{scale_style_metrics, Scale, UnscaledStyleMetrics},
8    outline::Outline,
9    recorder::HintsRecorder,
10    style::{GlyphStyle, ScriptGroup},
11    topo::{self, Axis, Dimension},
12    QuirksMode, ScaleFlags,
13};
14
15/// Captures adjusted horizontal scale and outer edge positions to be used
16/// for horizontal metrics adjustments.
17#[derive(Copy, Clone, PartialEq, Default, Debug)]
18pub(crate) struct EdgeMetrics {
19    pub left_opos: i32,
20    pub left_pos: i32,
21    pub right_opos: i32,
22    pub right_pos: i32,
23}
24
25#[derive(Copy, Clone, PartialEq, Default, Debug)]
26pub(crate) struct HintedMetrics {
27    pub x_scale: i32,
28    /// This will be `None` when we've identified fewer than two horizontal
29    /// edges in the outline. This will occur for empty outlines and outlines
30    /// that are degenerate (all x coordinates have the same value, within
31    /// a threshold). In these cases, horizontal metrics will not be adjusted.
32    pub edge_metrics: Option<EdgeMetrics>,
33}
34
35/// Applies the complete hinting process to a latin outline.
36///
37/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/aflatin.c#L3554>
38pub(crate) fn hint_outline(
39    outline: &mut Outline,
40    metrics: &UnscaledStyleMetrics,
41    scale: &Scale,
42    glyph_style: Option<GlyphStyle>,
43) -> HintedMetrics {
44    hint_outline_impl(outline, metrics, scale, glyph_style, None).hinted_metrics
45}
46
47pub(crate) struct HintedPlan {
48    pub hinted_metrics: HintedMetrics,
49    pub axes: [Option<Axis>; 2],
50}
51
52pub(crate) fn hint_outline_with_recorder(
53    outline: &mut Outline,
54    metrics: &UnscaledStyleMetrics,
55    scale: &Scale,
56    glyph_style: Option<GlyphStyle>,
57    recorder: &mut HintsRecorder,
58) -> HintedPlan {
59    hint_outline_impl(outline, metrics, scale, glyph_style, Some(recorder))
60}
61
62fn hint_outline_impl(
63    outline: &mut Outline,
64    metrics: &UnscaledStyleMetrics,
65    scale: &Scale,
66    glyph_style: Option<GlyphStyle>,
67    mut recorder: Option<&mut HintsRecorder>,
68) -> HintedPlan {
69    let quirks = if recorder.is_some() {
70        QuirksMode::Aot
71    } else {
72        QuirksMode::Jit
73    };
74    let scaled_metrics = scale_style_metrics(metrics, *scale, quirks);
75    let scale = &scaled_metrics.scale;
76    let mut axis = Axis::default();
77    let hint_top_to_bottom = metrics.style_class().script.hint_top_to_bottom;
78    outline.scale(&scaled_metrics.scale);
79    let mut hinted_metrics = HintedMetrics {
80        x_scale: scale.x_scale,
81        ..Default::default()
82    };
83    let group = metrics.style_class().script.group;
84    let mut axes = [const { None }; 2];
85    // For default script group, we don't proceed with hinting if we're
86    // missing alignment zones. FreeType swaps in a "dummy" hinter here
87    // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afglobal.c#L475>
88    if group == ScriptGroup::Default && scaled_metrics.axes[1].blues.is_empty() {
89        return HintedPlan {
90            hinted_metrics,
91            axes,
92        };
93    }
94    for dim in [Dimension::Horizontal, Dimension::Vertical] {
95        if (dim == Dimension::Horizontal && scale.flags.contains(ScaleFlags::NO_HORIZONTAL))
96            || (dim == Dimension::Vertical && scale.flags.contains(ScaleFlags::NO_VERTICAL))
97        {
98            continue;
99        }
100        axis.reset(dim, outline.orientation);
101        topo::compute_segments(outline, &mut axis, group);
102        topo::link_segments(
103            outline,
104            &mut axis,
105            scaled_metrics.axes[dim].scale,
106            group,
107            metrics.axes[dim].max_width(),
108        );
109        topo::compute_edges(
110            &mut axis,
111            &scaled_metrics.axes[dim],
112            hint_top_to_bottom,
113            scaled_metrics.scale.y_scale,
114            group,
115        );
116        if dim == Dimension::Vertical {
117            if group != ScriptGroup::Default
118                || glyph_style
119                    .map(|style| !style.is_non_base())
120                    .unwrap_or(true)
121            {
122                topo::compute_blue_edges(
123                    &mut axis,
124                    scale,
125                    &metrics.axes[dim].blues,
126                    &scaled_metrics.axes[dim].blues,
127                    group,
128                );
129            }
130        } else {
131            hinted_metrics.x_scale = scaled_metrics.axes[0].scale;
132        }
133        edges::hint_edges(
134            &mut axis,
135            &scaled_metrics.axes[dim],
136            group,
137            scale,
138            hint_top_to_bottom,
139            recorder.as_deref_mut(),
140        );
141        outline::align_edge_points(outline, &axis, group, scale);
142        outline::align_strong_points(outline, &mut axis, recorder.as_deref_mut());
143        outline::align_weak_points(outline, dim);
144        if dim == Dimension::Horizontal && axis.edges.len() > 1 {
145            let left = axis.edges.first().unwrap();
146            let right = axis.edges.last().unwrap();
147            hinted_metrics.edge_metrics = Some(EdgeMetrics {
148                left_pos: left.pos,
149                left_opos: left.opos,
150                right_pos: right.pos,
151                right_opos: right.opos,
152            });
153        }
154        if recorder.is_some() {
155            axes[dim as usize] = Some(axis.clone());
156        }
157    }
158    HintedPlan {
159        hinted_metrics,
160        axes,
161    }
162}