Skip to main content

skrifa/scaler/
postscript.rs

1//! PostScript hinting for a [`ScalerFont`](super::ScalerFont).
2//!
3//! read-fonts runs the charstrings, and this crate's PostScript hinter hints
4//! them before the font matrix is applied.
5
6use super::{DrawnMetrics, ScaleError};
7use crate::{
8    font::Font,
9    outline::cff::{scale_for_hinting, HintParams, HintState, HintedTransformingSink, HintingSink},
10};
11use alloc::vec::Vec;
12use raw::model::Error;
13use raw::{
14    model::glyph::outline::{cff::CffGlyph, PathSink},
15    ps::{
16        cff::{CffFontRef, Subfont},
17        charstring::NopFilterSink,
18        FontMatrix, HintingParams, Transform,
19    },
20    types::{F2Dot14, Fixed},
21};
22
23/// The number of subfonts whose hinting state is kept for a size. A
24/// subfont past them is read again for each glyph that needs it, so a font
25/// with thousands of subfonts costs no more to configure than one with eight.
26const CACHED_SUBFONTS: u16 = 8;
27
28/// The PostScript hinter, configured for one font at one size.
29#[derive(Default)]
30pub(super) struct PostScriptHinter {
31    /// The first [`CACHED_SUBFONTS`] subfonts. `None` marks one that cannot
32    /// be read, which fails only its own glyphs.
33    subfonts: Vec<Option<HintedSubfont>>,
34    ppem: f32,
35}
36
37/// A subfont and its hinting state at the size.
38struct HintedSubfont {
39    subfont: Subfont,
40    transform: Transform,
41    hint_state: HintState,
42}
43
44impl HintedSubfont {
45    /// Reads subfont `index` at `coords` and sets up its hints for `ppem`.
46    fn new(cff: &CffFontRef<'_>, index: u16, coords: &[F2Dot14], ppem: f32) -> Result<Self, Error> {
47        let (subfont, params) = cff.subfont_hinted(index, coords)?;
48        let transform = cff.transform(&subfont, Some(ppem));
49        let hint_state = HintState::new(&hint_params(params), scale_for_hinting(transform.scale));
50        Ok(Self {
51            subfont,
52            transform,
53            hint_state,
54        })
55    }
56}
57
58impl PostScriptHinter {
59    /// Reads the first subfonts at the font's location and `ppem`.
60    pub(super) fn reconfigure(&mut self, cff: &CffFontRef<'_>, coords: &[F2Dot14], ppem: f32) {
61        self.ppem = ppem;
62        self.subfonts.clear();
63        self.subfonts.extend(
64            (0..cff.num_subfonts().min(CACHED_SUBFONTS))
65                .map(|index| HintedSubfont::new(cff, index, coords, ppem).ok()),
66        );
67    }
68
69    pub(super) fn draw(
70        &self,
71        font: &Font,
72        outline: &CffGlyph<'_>,
73        sink: &mut dyn PathSink,
74    ) -> Result<DrawnMetrics, ScaleError> {
75        let cff = outline.cff_font();
76        let glyph = outline.glyph();
77        let coords = outline.normalized_coords();
78        let index = u16::try_from(outline.subfont_index()).map_err(|_| Error::Malformed)?;
79        let read;
80        let hinted = match self.subfonts.get(usize::from(index)) {
81            Some(hinted) => hinted.as_ref().ok_or(Error::Malformed)?,
82            None => {
83                read = HintedSubfont::new(cff, index, coords, self.ppem)?;
84                &read
85            }
86        };
87        let mut sink = sink;
88        let mut filter = NopFilterSink::new(&mut sink);
89        let width = if hinted.transform.matrix != FontMatrix::IDENTITY {
90            // Hinting runs at scale, so only the matrix is left to apply.
91            let mut transform = HintedTransformingSink::new(&mut filter, hinted.transform.matrix);
92            let mut hinting = HintingSink::new(&hinted.hint_state, &mut transform);
93            cff.evaluate_charstring(&hinted.subfont, glyph, coords, &mut hinting)?
94        } else {
95            let mut hinting = HintingSink::new(&hinted.hint_state, &mut filter);
96            cff.evaluate_charstring(&hinted.subfont, glyph, coords, &mut hinting)?
97        };
98        let advance_width = width
99            // A subfont with no default width leaves the metrics tables,
100            // read in whole units as FreeType rounds them.
101            .or_else(|| {
102                let advance = font.glyph_metrics().h_advance_exact(glyph).round();
103                Some(Fixed::from_i32(advance.to_i32()))
104            })
105            // Hinting was asked for, so the advance is rounded as FreeType
106            // rounds it.
107            .map(|width| hinted.transform.transform_h_metric(width).round().to_f32())
108            // A charstring can state a width below zero. Report none and
109            // leave the caller to its linear metrics.
110            .filter(|width| *width >= 0.0);
111        let mut metrics = DrawnMetrics::default();
112        metrics.h_advance = advance_width;
113        Ok(metrics)
114    }
115}
116
117/// Converts the parameters read by read-fonts into the hinter's parameters.
118///
119/// The two types have the same fields.
120fn hint_params(params: HintingParams) -> HintParams {
121    HintParams {
122        blues: params.blues,
123        family_blues: params.family_blues,
124        other_blues: params.other_blues,
125        family_other_blues: params.family_other_blues,
126        blue_scale: params.blue_scale,
127        blue_shift: params.blue_shift,
128        blue_fuzz: params.blue_fuzz,
129        language_group: params.language_group,
130    }
131}