Skip to main content

skrifa/outline/autohint/
instance.rs

1//! Autohinting state for a font instance.
2
3use super::{
4    super::{
5        pen::PathStyle, AdjustedMetrics, DrawError, OutlineGlyph, OutlineGlyphCollection,
6        OutlinePen, Target,
7    },
8    metrics::{fixed_mul, pix_round, Scale, UnscaledStyleMetricsSet},
9    outline::Outline,
10    shape::{Shaper, ShaperMode},
11    style::{GlyphStyle, GlyphStyleMap},
12    ScaleFlags,
13};
14use crate::{attribute::Style, prelude::Size, MetadataProvider};
15use alloc::sync::Arc;
16use raw::{
17    types::{F26Dot6, F2Dot14, GlyphId},
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    /// Returns the style for the given glyph.
52    pub fn get(&self, gid: GlyphId) -> Option<GlyphStyle> {
53        self.0.style(gid)
54    }
55}
56
57#[derive(Clone)]
58pub(crate) struct Instance {
59    styles: GlyphStyles,
60    metrics: UnscaledStyleMetricsSet,
61    target: Target,
62    is_fixed_width: bool,
63    style: Style,
64}
65
66impl Instance {
67    pub fn new(
68        font: &FontRef,
69        outlines: &OutlineGlyphCollection,
70        coords: &[F2Dot14],
71        target: Target,
72        styles: Option<GlyphStyles>,
73        lazy_metrics: bool,
74    ) -> Self {
75        let styles = styles.unwrap_or_else(|| GlyphStyles::new(outlines));
76        #[cfg(feature = "std")]
77        let metrics = if lazy_metrics {
78            UnscaledStyleMetricsSet::lazy(&styles.0)
79        } else {
80            UnscaledStyleMetricsSet::precomputed(font, coords, SHAPER_MODE, &styles.0)
81        };
82        #[cfg(not(feature = "std"))]
83        let metrics = UnscaledStyleMetricsSet::precomputed(font, coords, SHAPER_MODE, &styles.0);
84        let is_fixed_width = font
85            .post()
86            .map(|post| post.is_fixed_pitch() != 0)
87            .unwrap_or_default();
88        let style = font.attributes().style;
89        Self {
90            styles,
91            metrics,
92            target,
93            is_fixed_width,
94            style,
95        }
96    }
97
98    pub fn draw(
99        &self,
100        size: Size,
101        coords: &[F2Dot14],
102        glyph: &OutlineGlyph,
103        path_style: PathStyle,
104        pen: &mut impl OutlinePen,
105    ) -> Result<AdjustedMetrics, DrawError> {
106        let font = glyph.font();
107        let glyph_id = glyph.glyph_id();
108        let style = self
109            .styles
110            .0
111            .style(glyph_id)
112            .ok_or(DrawError::GlyphNotFound(glyph_id))?;
113        let metrics = self
114            .metrics
115            .get(font, coords, SHAPER_MODE, &self.styles.0, glyph_id)
116            .ok_or(DrawError::GlyphNotFound(glyph_id))?;
117        let units_per_em = glyph.units_per_em() as i32;
118        let scale = Scale::new(
119            size.ppem().unwrap_or(units_per_em as f32),
120            units_per_em,
121            self.style,
122            self.target,
123            metrics.style_class().script.group,
124        );
125        let mut outline = Outline::default();
126        outline.fill(glyph, coords, super::QuirksMode::default())?;
127        let hinted_metrics = super::hint::hint_outline(&mut outline, &metrics, &scale, Some(style));
128        let h_advance = outline.advance;
129        let mut pp1x = 0;
130        let mut pp2x = fixed_mul(h_advance, hinted_metrics.x_scale);
131        let is_light = self.target.is_light() || self.target.preserve_linear_metrics();
132        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/autofit/afloader.c#L422>
133        if !is_light {
134            if let (true, Some(edge_metrics)) = (
135                !scale.flags.contains(ScaleFlags::NO_ADVANCE),
136                hinted_metrics.edge_metrics,
137            ) {
138                let old_rsb = pp2x - edge_metrics.right_opos;
139                let old_lsb = edge_metrics.left_opos;
140                let new_lsb = edge_metrics.left_pos;
141                let mut pp1x_uh = new_lsb - old_lsb;
142                let mut pp2x_uh = edge_metrics.right_pos + old_rsb;
143                if old_lsb < 24 {
144                    pp1x_uh -= 8;
145                }
146                if old_rsb < 24 {
147                    pp2x_uh += 8;
148                }
149                pp1x = pix_round(pp1x_uh);
150                pp2x = pix_round(pp2x_uh);
151                if pp1x >= new_lsb && old_lsb > 0 {
152                    pp1x -= 64;
153                }
154                if pp2x <= edge_metrics.right_pos && old_rsb > 0 {
155                    pp2x += 64;
156                }
157            } else {
158                pp1x = pix_round(pp1x);
159                pp2x = pix_round(pp2x);
160            }
161        } else {
162            pp1x = pix_round(pp1x);
163            pp2x = pix_round(pp2x);
164        }
165        if pp1x != 0 {
166            for point in &mut outline.points {
167                point.x -= pp1x;
168            }
169        }
170        let advance = if !is_light
171            && (self.is_fixed_width || (metrics.digits_have_same_width && style.is_digit()))
172        {
173            fixed_mul(h_advance, scale.x_scale)
174        } else if h_advance != 0 {
175            pp2x - pp1x
176        } else {
177            0
178        };
179        outline.to_path(path_style, pen)?;
180        Ok(AdjustedMetrics {
181            has_overlaps: glyph.has_overlaps().unwrap_or_default(),
182            lsb: None,
183            advance_width: Some(F26Dot6::from_bits(pix_round(advance)).to_f32()),
184        })
185    }
186}