skrifa/outline/glyf/hint/
instance.rs

1//! Instance state for TrueType hinting.
2
3use super::{
4    super::Outlines,
5    cow_slice::CowSlice,
6    definition::{Definition, DefinitionMap, DefinitionState},
7    engine::Engine,
8    error::HintError,
9    graphics::RetainedGraphicsState,
10    program::{Program, ProgramState},
11    value_stack::ValueStack,
12    zone::Zone,
13    HintOutline, PointFlags, Target,
14};
15use alloc::vec::Vec;
16use raw::{
17    types::{F26Dot6, F2Dot14, Fixed, Point},
18    TableProvider,
19};
20
21#[derive(Clone, Default)]
22pub struct HintInstance {
23    functions: Vec<Definition>,
24    instructions: Vec<Definition>,
25    cvt: Vec<i32>,
26    storage: Vec<i32>,
27    graphics: RetainedGraphicsState,
28    twilight_scaled: Vec<Point<F26Dot6>>,
29    twilight_original_scaled: Vec<Point<F26Dot6>>,
30    twilight_flags: Vec<PointFlags>,
31    axis_count: u16,
32    max_stack: usize,
33}
34
35impl HintInstance {
36    pub fn reconfigure(
37        &mut self,
38        outlines: &Outlines,
39        scale: i32,
40        ppem: i32,
41        target: Target,
42        coords: &[F2Dot14],
43    ) -> Result<(), HintError> {
44        self.setup(outlines, scale, coords);
45        let twilight_contours = [self.twilight_scaled.len() as u16];
46        let twilight = Zone::new(
47            &[],
48            &mut self.twilight_original_scaled,
49            &mut self.twilight_scaled,
50            &mut self.twilight_flags,
51            &twilight_contours,
52        );
53        let glyph = Zone::default();
54        let mut stack_buf = vec![0; self.max_stack];
55        let value_stack = ValueStack::new(&mut stack_buf, false);
56        let graphics = RetainedGraphicsState::new(scale, ppem, target);
57        let mut engine = Engine::new(
58            outlines,
59            ProgramState::new(outlines.fpgm, outlines.prep, &[], Program::Font),
60            graphics,
61            DefinitionState::new(
62                DefinitionMap::Mut(&mut self.functions),
63                DefinitionMap::Mut(&mut self.instructions),
64            ),
65            CowSlice::new_mut(&mut self.cvt),
66            CowSlice::new_mut(&mut self.storage),
67            value_stack,
68            twilight,
69            glyph,
70            self.axis_count,
71            coords,
72            false,
73        );
74        // Run the font program (fpgm)
75        engine.run_program(Program::Font, false)?;
76        // Run the control value program (prep)
77        engine.run_program(Program::ControlValue, false)?;
78        // Save the retained state from the CV program
79        self.graphics = *engine.retained_graphics_state();
80        Ok(())
81    }
82
83    /// Returns true if we should actually apply hinting.
84    ///
85    /// Hinting can be completely disabled by the control value program.
86    pub fn is_enabled(&self) -> bool {
87        // If bit 0 is set, disables hinting entirely
88        self.graphics.instruct_control & 1 == 0
89    }
90
91    /// Returns true if backward compatibility mode has been activated
92    /// by the hinter settings or the `prep` table.
93    pub fn backward_compatibility(&self) -> bool {
94        // Set backward compatibility mode
95        if self.graphics.target.preserve_linear_metrics() {
96            true
97        } else if self.graphics.target.is_smooth() {
98            (self.graphics.instruct_control & 0x4) == 0
99        } else {
100            false
101        }
102    }
103
104    pub fn hint(
105        &self,
106        outlines: &Outlines,
107        outline: &mut HintOutline,
108        is_pedantic: bool,
109    ) -> Result<(), HintError> {
110        // Twilight zone
111        let twilight_count = outline.twilight_scaled.len();
112        let twilight_contours = [twilight_count as u16];
113        outline
114            .twilight_original_scaled
115            .copy_from_slice(&self.twilight_original_scaled);
116        outline
117            .twilight_scaled
118            .copy_from_slice(&self.twilight_scaled);
119        outline.twilight_flags.copy_from_slice(&self.twilight_flags);
120        let twilight = Zone::new(
121            &[],
122            outline.twilight_original_scaled,
123            outline.twilight_scaled,
124            outline.twilight_flags,
125            &twilight_contours,
126        );
127        // Glyph zone
128        let glyph = Zone::new(
129            outline.unscaled,
130            outline.original_scaled,
131            outline.scaled,
132            outline.flags,
133            outline.contours,
134        );
135        let value_stack = ValueStack::new(outline.stack, is_pedantic);
136        let cvt = CowSlice::new(&self.cvt, outline.cvt).unwrap();
137        let storage = CowSlice::new(&self.storage, outline.storage).unwrap();
138        let mut engine = Engine::new(
139            outlines,
140            ProgramState::new(
141                outlines.fpgm,
142                outlines.prep,
143                outline.bytecode,
144                Program::Glyph,
145            ),
146            self.graphics,
147            DefinitionState::new(
148                DefinitionMap::Ref(&self.functions),
149                DefinitionMap::Ref(&self.instructions),
150            ),
151            cvt,
152            storage,
153            value_stack,
154            twilight,
155            glyph,
156            self.axis_count,
157            outline.coords,
158            outline.is_composite,
159        );
160        engine
161            .run_program(Program::Glyph, is_pedantic)
162            .map_err(|mut e| {
163                e.glyph_id = Some(outline.glyph_id);
164                e
165            })?;
166        // If we're not running in backward compatibility mode, capture
167        // modified phantom points.
168        if !engine.backward_compatibility() {
169            for (i, p) in (outline.scaled[outline.scaled.len() - 4..])
170                .iter()
171                .enumerate()
172            {
173                outline.phantom[i] = *p;
174            }
175        }
176        Ok(())
177    }
178
179    /// Captures limits, resizes buffers and scales the CVT.
180    fn setup(&mut self, outlines: &Outlines, scale: i32, coords: &[F2Dot14]) {
181        let axis_count = outlines
182            .gvar
183            .as_ref()
184            .map(|gvar| gvar.axis_count())
185            .unwrap_or_default();
186        self.functions.clear();
187        self.functions
188            .resize(outlines.max_function_defs as usize, Definition::default());
189        self.instructions.resize(
190            outlines.max_instruction_defs as usize,
191            Definition::default(),
192        );
193        self.cvt.clear();
194        let cvt = outlines.font.cvt().unwrap_or_default();
195        if let Ok(cvar) = outlines.font.cvar() {
196            // First accumulate all the deltas in 16.16
197            self.cvt.resize(cvt.len(), 0);
198            let _ = cvar.deltas(axis_count, coords, &mut self.cvt);
199            // Now add the base CVT values
200            for (value, base_value) in self.cvt.iter_mut().zip(cvt.iter()) {
201                // Deltas are converted from 16.16 to 26.6
202                // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttgxvar.c#L3822>
203                let delta = Fixed::from_bits(*value).to_f26dot6().to_bits();
204                let base_value = base_value.get() as i32 * 64;
205                *value = base_value + delta;
206            }
207        } else {
208            // CVT values are converted to 26.6 on load
209            // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttpload.c#L350>
210            self.cvt
211                .extend(cvt.iter().map(|value| (value.get() as i32) * 64));
212        }
213        // More weird scaling. This is due to the fact that CVT values are
214        // already in 26.6
215        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttobjs.c#L996>
216        let scale = Fixed::from_bits(scale >> 6);
217        for value in &mut self.cvt {
218            *value = (Fixed::from_bits(*value) * scale).to_bits();
219        }
220        self.storage.clear();
221        self.storage.resize(outlines.max_storage as usize, 0);
222        let max_twilight_points = outlines.max_twilight_points as usize;
223        self.twilight_scaled.clear();
224        self.twilight_scaled
225            .resize(max_twilight_points, Default::default());
226        self.twilight_original_scaled.clear();
227        self.twilight_original_scaled
228            .resize(max_twilight_points, Default::default());
229        self.twilight_flags.clear();
230        self.twilight_flags
231            .resize(max_twilight_points, Default::default());
232        self.axis_count = axis_count;
233        self.max_stack = outlines.max_stack_elements as usize;
234        self.graphics = RetainedGraphicsState::default();
235    }
236}
237
238#[cfg(test)]
239impl HintInstance {
240    /// Enable instruct control bit 1 which effectively disables hinting.
241    ///
242    /// This mimics what the `prep` table might do for various configurations
243    /// and font sizes. Used for testing.    
244    pub fn simulate_prep_flag_suppress_hinting(&mut self) {
245        self.graphics.instruct_control |= 1;
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::{super::super::Outlines, HintInstance};
252    use read_fonts::{types::F2Dot14, FontRef};
253
254    #[test]
255    fn scaled_cvar_cvt() {
256        let font = FontRef::new(font_test_data::CVAR).unwrap();
257        let outlines = Outlines::new(&font).unwrap();
258        let mut instance = HintInstance::default();
259        let coords = [0.5, -0.5].map(F2Dot14::from_f32);
260        let ppem = 16;
261        // ppem * 64 / upem
262        let scale = 67109;
263        instance
264            .reconfigure(&outlines, scale, ppem, Default::default(), &coords)
265            .unwrap();
266        let expected = [
267            778, 10, 731, 0, 731, 10, 549, 10, 0, 0, 0, -10, 0, -10, -256, -10, 0, 0, 0, 0, 0, 0,
268            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
269            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 95, 137, 99, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
270            0, 0, 0, 0, 0, 0, 60, 0, 81, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
271            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
272        ];
273        assert_eq!(&instance.cvt, &expected);
274    }
275}