skrifa/outline/autohint/
instance.rs

1//! Autohinting state for a font instance.
2
3use crate::{attribute::Style, prelude::Size, MetadataProvider};
4
5use super::{
6    super::{
7        pen::PathStyle, AdjustedMetrics, DrawError, OutlineGlyph, OutlineGlyphCollection,
8        OutlinePen, Target,
9    },
10    metrics::{fixed_mul, pix_round, Scale, UnscaledStyleMetricsSet},
11    outline::Outline,
12    shape::{Shaper, ShaperMode},
13    style::GlyphStyleMap,
14};
15use alloc::sync::Arc;
16use raw::{
17    types::{F26Dot6, F2Dot14},
18    FontRef, TableProvider,
19};
20
21/// We enable "best effort" mode by default but allow it to be disabled with
22/// a feature for testing.
23const SHAPER_MODE: ShaperMode = if cfg!(feature = "autohint_shaping") {
24    ShaperMode::BestEffort
25} else {
26    ShaperMode::Nominal
27};
28
29/// Set of derived glyph styles that are used for automatic hinting.
30///
31/// These are invariant per font so can be precomputed and reused for multiple
32/// instances when requesting automatic hinting with [`Engine::Auto`](super::super::hint::Engine::Auto).
33#[derive(Clone, Debug)]
34pub struct GlyphStyles(Arc<GlyphStyleMap>);
35
36impl GlyphStyles {
37    /// Precomputes the full set of glyph styles for the given outlines.
38    pub fn new(outlines: &OutlineGlyphCollection) -> Self {
39        if let Some(font) = outlines.font() {
40            let glyph_count = font
41                .maxp()
42                .map(|maxp| maxp.num_glyphs() as u32)
43                .unwrap_or_default();
44            let shaper = Shaper::new(font, SHAPER_MODE);
45            Self(Arc::new(GlyphStyleMap::new(glyph_count, &shaper)))
46        } else {
47            Self(Default::default())
48        }
49    }
50}
51
52#[derive(Clone)]
53pub(crate) struct Instance {
54    styles: GlyphStyles,
55    metrics: UnscaledStyleMetricsSet,
56    target: Target,
57    is_fixed_width: bool,
58    style: Style,
59}
60
61impl Instance {
62    pub fn new(
63        font: &FontRef,
64        outlines: &OutlineGlyphCollection,
65        coords: &[F2Dot14],
66        target: Target,
67        styles: Option<GlyphStyles>,
68        lazy_metrics: bool,
69    ) -> Self {
70        let styles = styles.unwrap_or_else(|| GlyphStyles::new(outlines));
71        #[cfg(feature = "std")]
72        let metrics = if lazy_metrics {
73            UnscaledStyleMetricsSet::lazy(&styles.0)
74        } else {
75            UnscaledStyleMetricsSet::precomputed(font, coords, SHAPER_MODE, &styles.0)
76        };
77        #[cfg(not(feature = "std"))]
78        let metrics = UnscaledStyleMetricsSet::precomputed(font, coords, SHAPER_MODE, &styles.0);
79        let is_fixed_width = font
80            .post()
81            .map(|post| post.is_fixed_pitch() != 0)
82            .unwrap_or_default();
83        let style = font.attributes().style;
84        Self {
85            styles,
86            metrics,
87            target,
88            is_fixed_width,
89            style,
90        }
91    }
92
93    pub fn draw(
94        &self,
95        size: Size,
96        coords: &[F2Dot14],
97        glyph: &OutlineGlyph,
98        path_style: PathStyle,
99        pen: &mut impl OutlinePen,
100    ) -> Result<AdjustedMetrics, DrawError> {
101        let font = glyph.font();
102        let glyph_id = glyph.glyph_id();
103        let style = self
104            .styles
105            .0
106            .style(glyph_id)
107            .ok_or(DrawError::GlyphNotFound(glyph_id))?;
108        let metrics = self
109            .metrics
110            .get(font, coords, SHAPER_MODE, &self.styles.0, glyph_id)
111            .ok_or(DrawError::GlyphNotFound(glyph_id))?;
112        let units_per_em = glyph.units_per_em() as i32;
113        let scale = Scale::new(
114            size.ppem().unwrap_or(units_per_em as f32),
115            units_per_em,
116            self.style,
117            self.target,
118            metrics.style_class().script.group,
119        );
120        let mut outline = Outline::default();
121        outline.fill(glyph, coords)?;
122        let hinted_metrics = super::hint::hint_outline(&mut outline, &metrics, &scale, Some(style));
123        let h_advance = outline.advance;
124        let mut pp1x = 0;
125        let mut pp2x = fixed_mul(h_advance, hinted_metrics.x_scale);
126        let is_light = self.target.is_light() || self.target.preserve_linear_metrics();
127        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afloader.c#L422>
128        if !is_light {
129            if let (true, Some(edge_metrics)) = (
130                scale.flags & Scale::NO_ADVANCE == 0,
131                hinted_metrics.edge_metrics,
132            ) {
133                let old_rsb = pp2x - edge_metrics.right_opos;
134                let old_lsb = edge_metrics.left_opos;
135                let new_lsb = edge_metrics.left_pos;
136                let mut pp1x_uh = new_lsb - old_lsb;
137                let mut pp2x_uh = edge_metrics.right_pos + old_rsb;
138                if old_lsb < 24 {
139                    pp1x_uh -= 8;
140                }
141                if old_rsb < 24 {
142                    pp2x_uh += 8;
143                }
144                pp1x = pix_round(pp1x_uh);
145                pp2x = pix_round(pp2x_uh);
146                if pp1x >= new_lsb && old_lsb > 0 {
147                    pp1x -= 64;
148                }
149                if pp2x <= edge_metrics.right_pos && old_rsb > 0 {
150                    pp2x += 64;
151                }
152            } else {
153                pp1x = pix_round(pp1x);
154                pp2x = pix_round(pp2x);
155            }
156        } else {
157            pp1x = pix_round(pp1x);
158            pp2x = pix_round(pp2x);
159        }
160        if pp1x != 0 {
161            for point in &mut outline.points {
162                point.x -= pp1x;
163            }
164        }
165        let advance = if !is_light
166            && (self.is_fixed_width || (metrics.digits_have_same_width && style.is_digit()))
167        {
168            fixed_mul(h_advance, scale.x_scale)
169        } else if h_advance != 0 {
170            pp2x - pp1x
171        } else {
172            0
173        };
174        outline.to_path(path_style, pen)?;
175        Ok(AdjustedMetrics {
176            has_overlaps: glyph.has_overlaps().unwrap_or_default(),
177            lsb: None,
178            advance_width: Some(F26Dot6::from_bits(pix_round(advance)).to_f32()),
179        })
180    }
181}