1
//! TrueType bytecode hinting (grid-fitting) interpreter.
2
//!
3
//! This module implements the TrueType instruction set interpreter that
4
//! executes bytecode embedded in font programs (`fpgm`, `prep`) and
5
//! individual glyph instructions to snap glyph outlines to the pixel grid.
6
//!
7
//! # Usage
8
//!
9
//! ```ignore
10
//! use allsorts::hinting::{HintInstance, HintedGlyph};
11
//! use allsorts::tables::FontTableProvider;
12
//!
13
//! // 1. Create a hint instance from font tables (runs fpgm)
14
//! let instance = HintInstance::new(&font_table_provider)?;
15
//!
16
//! // 2. Set size (runs prep, scales CVT)
17
//! instance.set_size(16.0, 96)?;
18
//!
19
//! // 3. Hint individual glyphs
20
//! let hinted = instance.hint_simple_glyph(&glyph, &outline)?;
21
//! // Use hinted.points for rasterization
22
//! ```
23

            
24
pub mod f26dot6;
25
pub mod graphics_state;
26
pub mod interpreter;
27

            
28
#[cfg(test)]
29
mod tests;
30

            
31
pub use f26dot6::{F26Dot6, F2Dot14};
32
pub use interpreter::{HintError, Interpreter, Point, Zone};
33

            
34
use crate::binary::read::ReadScope;
35
use crate::tables::{CvtTable, FontTableProvider, MaxpTable};
36
use crate::tag;
37

            
38
/// High-level hinting state for a font.
39
///
40
/// Created once per font. Runs `fpgm` at creation time to populate
41
/// function definitions. Call `set_size` to prepare for a specific ppem.
42
pub struct HintInstance {
43
    pub interpreter: Interpreter,
44
    fpgm_executed: bool,
45
    prep_bytecode: Vec<u8>,
46
    cvt_funits: Vec<i16>,
47
    current_ppem: Option<u16>,
48
}
49

            
50
impl HintInstance {
51
    /// Create a new hinting instance from font table data.
52
    ///
53
    /// Parses `maxp`, `cvt`, `fpgm`, and `prep` tables, then executes `fpgm`
54
    /// to populate function definitions.
55
    ///
56
    /// Returns `None` if the font has no TrueType hinting data.
57
22464
    pub fn new(provider: &dyn FontTableProvider) -> Result<Option<Self>, HintError> {
58
        // Read maxp table
59
22464
        let maxp = match provider.table_data(tag::MAXP) {
60
22464
            Ok(Some(data)) => {
61
22464
                let scope = ReadScope::new(&data);
62
22464
                match scope.read::<MaxpTable>() {
63
22464
                    Ok(maxp) => maxp,
64
                    Err(_) => return Ok(None),
65
                }
66
            }
67
            _ => return Ok(None),
68
        };
69

            
70
22464
        let maxp_v1 = match &maxp.version1_sub_table {
71
22464
            Some(v1) => v1,
72
            None => return Ok(None), // CFF font, no TrueType hinting
73
        };
74

            
75
        // Read CVT table (optional)
76
22464
        let cvt_funits: Vec<i16> = match provider.table_data(tag::CVT) {
77
14580
            Ok(Some(data)) => {
78
14580
                let len = data.len() as u32;
79
14580
                let scope = ReadScope::new(&data);
80
14580
                match scope.ctxt().read_dep::<CvtTable<'_>>(len) {
81
14580
                    Ok(cvt) => {
82
14580
                        let mut values = Vec::with_capacity(cvt.values.len());
83
2746008
                        for i in 0..cvt.values.len() {
84
2746008
                            if let Some(v) = cvt.values.get_item(i) {
85
2746008
                                values.push(v);
86
2746008
                            }
87
                        }
88
14580
                        values
89
                    }
90
                    Err(_) => Vec::new(),
91
                }
92
            }
93
7884
            _ => Vec::new(),
94
        };
95

            
96
        // Read fpgm bytecode (optional)
97
22464
        let fpgm_bytecode: Vec<u8> = match provider.table_data(tag::FPGM) {
98
14310
            Ok(Some(data)) => data.into_owned(),
99
8154
            _ => Vec::new(),
100
        };
101

            
102
        // Read prep bytecode (optional)
103
22464
        let prep_bytecode: Vec<u8> = match provider.table_data(tag::PREP) {
104
14310
            Ok(Some(data)) => data.into_owned(),
105
8154
            _ => Vec::new(),
106
        };
107

            
108
        // Create interpreter
109
22464
        let mut interpreter = Interpreter::new(
110
22464
            maxp_v1.max_stack_elements,
111
22464
            maxp_v1.max_storage,
112
22464
            maxp_v1.max_function_defs,
113
22464
            maxp_v1.max_instruction_defs,
114
22464
            maxp_v1.max_twilight_points,
115
            0, // units_per_em set later
116
        );
117

            
118
        // Read head table for units_per_em
119
22464
        if let Ok(Some(head_data)) = provider.table_data(tag::HEAD) {
120
22464
            if head_data.len() >= 20 {
121
22464
                let upem = u16::from_be_bytes([head_data[18], head_data[19]]);
122
22464
                interpreter.units_per_em = upem;
123
22464
            }
124
        }
125

            
126
        // Execute fpgm to populate function definitions
127
22464
        let mut fpgm_executed = false;
128
22464
        if !fpgm_bytecode.is_empty() {
129
14310
            match interpreter.execute_fpgm(&fpgm_bytecode) {
130
14310
                Ok(()) => fpgm_executed = true,
131
                Err(_e) => {
132
                    // fpgm execution failed; continue without hinting functions
133
                    // Some fonts have buggy fpgm programs
134
                }
135
            }
136
8154
        } else {
137
8154
            fpgm_executed = true; // No fpgm = nothing to execute
138
8154
        }
139

            
140
22464
        Ok(Some(HintInstance {
141
22464
            interpreter,
142
22464
            fpgm_executed,
143
22464
            prep_bytecode,
144
22464
            cvt_funits,
145
22464
            current_ppem: None,
146
22464
        }))
147
22464
    }
148

            
149
    /// Prepare the interpreter for a specific point size and DPI.
150
    ///
151
    /// Scales CVT values and executes the `prep` program.
152
    pub fn set_size(&mut self, point_size: f64, dpi: u16) -> Result<(), HintError> {
153
        let ppem = ((point_size * dpi as f64) / 72.0).round() as u16;
154
        self.set_ppem(ppem, point_size)
155
    }
156

            
157
    /// Prepare the interpreter for a specific ppem value.
158
197370
    pub fn set_ppem(&mut self, ppem: u16, point_size: f64) -> Result<(), HintError> {
159
        // Skip if already configured for this ppem
160
197370
        if self.current_ppem == Some(ppem) {
161
183654
            return Ok(());
162
13716
        }
163

            
164
        // Scale CVT from FUnits to F26Dot6
165
13716
        self.interpreter.ppem = ppem;
166
13716
        self.interpreter.scale =
167
13716
            f26dot6::compute_scale(ppem, self.interpreter.units_per_em);
168
13716
        self.interpreter.scale_cvt(&self.cvt_funits);
169

            
170
        // Execute prep program
171
13716
        if !self.prep_bytecode.is_empty() && self.fpgm_executed {
172
6372
            // Ignore prep errors (some fonts have buggy prep programs)
173
6372
            let _ = self
174
6372
                .interpreter
175
6372
                .execute_prep(&self.prep_bytecode, ppem, point_size);
176
7344
        }
177

            
178
13716
        self.current_ppem = Some(ppem);
179
13716
        Ok(())
180
197370
    }
181

            
182
    /// Hint a simple glyph outline.
183
    ///
184
    /// Takes points already scaled to F26Dot6 pixel coordinates, the on-curve
185
    /// flags, contour end indices, the per-glyph instruction bytecode, and the
186
    /// horizontal advance width in F26Dot6.
187
    ///
188
    /// Internally appends 4 TrueType phantom points (origin, advance, top, bottom)
189
    /// required by the bytecode interpreter, then strips them from the result.
190
    ///
191
    /// Returns the hinted outline point positions as (x, y) pairs in F26Dot6
192
    /// (same length as `points_f26dot6`).
193
    pub fn hint_glyph(
194
        &mut self,
195
        points_f26dot6: &[(i32, i32)],
196
        on_curve: &[bool],
197
        contour_ends: &[u16],
198
        instructions: &[u8],
199
        advance_width_f26dot6: i32,
200
    ) -> Result<Vec<(i32, i32)>, HintError> {
201
        self.hint_glyph_with_orus(points_f26dot6, None, on_curve, contour_ends, instructions, advance_width_f26dot6)
202
    }
203

            
204
    /// Hint a glyph with optional unscaled original coordinates.
205
    ///
206
    /// `raw_points_funits` provides the original font-unit coordinates (before scaling).
207
    /// FreeType uses these for IUP interpolation factors, avoiding F26Dot6 rounding errors.
208
    ///
209
    /// `vert_origin_f26dot6` and `vert_advance_f26dot6` set the Y coordinates of
210
    /// phantom points 2 (top) and 3 (bottom).  FreeType computes these from vertical
211
    /// metrics: phantom[2].y = vertBearingY, phantom[3].y = vertBearingY - height.
212
    /// Pass `None` to use (0, 0) as before.
213
    pub fn hint_glyph_with_orus(
214
        &mut self,
215
        points_f26dot6: &[(i32, i32)],
216
        raw_points_funits: Option<&[(i16, i16)]>,
217
        on_curve: &[bool],
218
        contour_ends: &[u16],
219
        instructions: &[u8],
220
        advance_width_f26dot6: i32,
221
    ) -> Result<Vec<(i32, i32)>, HintError> {
222
        self.hint_glyph_full(
223
            points_f26dot6, raw_points_funits, on_curve, contour_ends,
224
            instructions, advance_width_f26dot6, None, None,
225
        )
226
    }
227

            
228
    /// Full hinting with vertical phantom point support.
229
    ///
230
    /// `phantom_top_y` and `phantom_bottom_y` are F26Dot6 Y coordinates for
231
    /// phantom points 2 and 3, computed from vertical metrics.
232
    pub fn hint_glyph_full(
233
        &mut self,
234
        points_f26dot6: &[(i32, i32)],
235
        raw_points_funits: Option<&[(i16, i16)]>,
236
        on_curve: &[bool],
237
        contour_ends: &[u16],
238
        instructions: &[u8],
239
        advance_width_f26dot6: i32,
240
        phantom_top_y: Option<i32>,
241
        phantom_bottom_y: Option<i32>,
242
    ) -> Result<Vec<(i32, i32)>, HintError> {
243
        if instructions.is_empty() || !self.fpgm_executed {
244
            return Ok(points_f26dot6.to_vec());
245
        }
246

            
247
        let real_count = points_f26dot6.len();
248

            
249
        let top_y = phantom_top_y.unwrap_or(0);
250
        let bottom_y = phantom_bottom_y.unwrap_or(0);
251

            
252
        let mut points: Vec<Point> = points_f26dot6
253
            .iter()
254
            .map(|&(x, y)| Point { x, y })
255
            .collect();
256
        // Don't round phantom points — let the glyph program handle them.
257
        // Rounding changes the MD measurements used by conditional logic,
258
        // causing wrong code paths (e.g., ClearType SHPIX on phantom points).
259
        points.push(Point { x: 0, y: 0 });                            // phantom[0]: origin
260
        points.push(Point { x: advance_width_f26dot6, y: 0 });        // phantom[1]: advance
261
        points.push(Point { x: 0, y: top_y });                        // phantom[2]: top
262
        points.push(Point { x: 0, y: bottom_y });                     // phantom[3]: bottom
263

            
264
        let mut on_curve_ext: Vec<bool> = on_curve.to_vec();
265
        on_curve_ext.extend_from_slice(&[true, true, true, true]);
266

            
267
        // Build unscaled orus points if raw coordinates are provided
268
        let orus: Option<Vec<Point>> = raw_points_funits.map(|raw| {
269
            let mut orus_pts: Vec<Point> = raw
270
                .iter()
271
                .map(|&(x, y)| Point { x: x as i32, y: y as i32 })
272
                .collect();
273
            // Phantom points in font units
274
            orus_pts.push(Point { x: 0, y: 0 });
275
            orus_pts.push(Point { x: 0, y: 0 }); // advance in funits not needed for IUP
276
            orus_pts.push(Point { x: 0, y: 0 });
277
            orus_pts.push(Point { x: 0, y: 0 });
278
            orus_pts
279
        });
280

            
281
        self.interpreter.hint_glyph_with_orus(
282
            &points,
283
            orus.as_deref(),
284
            &on_curve_ext,
285
            contour_ends,
286
            instructions,
287
        )?;
288

            
289
        let result: Vec<(i32, i32)> = self.interpreter.zones[1]
290
            .current
291
            .iter()
292
            .take(real_count)
293
            .map(|p| (p.x, p.y))
294
            .collect();
295

            
296
        Ok(result)
297
    }
298

            
299
    /// After a successful `hint_glyph_with_orus` call, returns per-point debug info:
300
    /// `(current, original, orus, touched_x, touched_y)` for each real point.
301
    pub fn zone_debug_info(&self, real_count: usize) -> Vec<((i32,i32),(i32,i32),(i32,i32),bool,bool)> {
302
        let z = &self.interpreter.zones[1];
303
        (0..real_count.min(z.current.len())).map(|i| {
304
            let cur = (z.current[i].x, z.current[i].y);
305
            let orig = (z.original[i].x, z.original[i].y);
306
            let orus = (z.orus[i].x, z.orus[i].y);
307
            let tx = z.flags[i].contains(interpreter::PointFlags::TOUCHED_X);
308
            let ty = z.flags[i].contains(interpreter::PointFlags::TOUCHED_Y);
309
            (cur, orig, orus, tx, ty)
310
        }).collect()
311
    }
312

            
313
    /// Hint a glyph and return the hinted advance width in F26Dot6.
314
    ///
315
    /// This extracts the advance from the hinted phantom point (index n+1),
316
    /// which is what FreeType uses for glyph positioning.
317
    pub fn hinted_advance_f26dot6(
318
        &mut self,
319
        points_f26dot6: &[(i32, i32)],
320
        raw_points_funits: Option<&[(i16, i16)]>,
321
        on_curve: &[bool],
322
        contour_ends: &[u16],
323
        instructions: &[u8],
324
        advance_width_f26dot6: i32,
325
    ) -> Result<i32, HintError> {
326
        if instructions.is_empty() || !self.fpgm_executed {
327
            return Ok(advance_width_f26dot6);
328
        }
329

            
330
        let real_count = points_f26dot6.len();
331

            
332
        let mut points: Vec<Point> = points_f26dot6
333
            .iter()
334
            .map(|&(x, y)| Point { x, y })
335
            .collect();
336
        points.push(Point { x: 0, y: 0 });
337
        points.push(Point { x: advance_width_f26dot6, y: 0 });
338
        points.push(Point { x: 0, y: 0 });
339
        points.push(Point { x: 0, y: 0 });
340

            
341
        let mut on_curve_ext: Vec<bool> = on_curve.to_vec();
342
        on_curve_ext.extend_from_slice(&[true, true, true, true]);
343

            
344
        let orus: Option<Vec<Point>> = raw_points_funits.map(|raw| {
345
            let mut orus_pts: Vec<Point> = raw
346
                .iter()
347
                .map(|&(x, y)| Point { x: x as i32, y: y as i32 })
348
                .collect();
349
            orus_pts.push(Point { x: 0, y: 0 });
350
            orus_pts.push(Point { x: 0, y: 0 });
351
            orus_pts.push(Point { x: 0, y: 0 });
352
            orus_pts.push(Point { x: 0, y: 0 });
353
            orus_pts
354
        });
355

            
356
        self.interpreter.hint_glyph_with_orus(
357
            &points,
358
            orus.as_deref(),
359
            &on_curve_ext,
360
            contour_ends,
361
            instructions,
362
        )?;
363

            
364
        // Phantom point at index real_count+1 is the hinted advance width
365
        let hinted_advance = self.interpreter.zones[1]
366
            .current
367
            .get(real_count + 1)
368
            .map(|p| p.x)
369
            .unwrap_or(advance_width_f26dot6);
370

            
371
        Ok(hinted_advance)
372
    }
373

            
374
    /// Hint a glyph and return both hinted coordinates AND post-hinting on-curve flags.
375
    ///
376
    /// FLIPPT/FLIPRGON/FLIPRGOFF instructions can change on-curve flags during hinting.
377
    /// The path builder MUST use the returned flags, not the original raw_on_curve.
378
    ///
379
    /// This convenience form places phantom point pp1 at x = 0 (the common
380
    /// `lsb == xMin` case). Callers that know the horizontal metrics should use
381
    /// [`Self::hint_glyph_with_flags_pp1`] and pass the scaled `xMin - lsb`.
382
    pub fn hint_glyph_with_flags(
383
        &mut self,
384
        points_f26dot6: &[(i32, i32)],
385
        on_curve: &[bool],
386
        contour_ends: &[u16],
387
        instructions: &[u8],
388
        advance_width_f26dot6: i32,
389
    ) -> Result<(Vec<(i32, i32)>, Vec<bool>), HintError> {
390
        self.hint_glyph_with_flags_pp1(
391
            points_f26dot6, on_curve, contour_ends, instructions, advance_width_f26dot6, 0,
392
        )
393
    }
394

            
395
    /// Like [`Self::hint_glyph_with_flags`] but with an explicit phantom point
396
    /// pp1 x-origin. Per the TrueType spec pp1.x = (xMin - lsb) scaled to
397
    /// F26Dot6 and pp2.x = pp1.x + advance; getting pp1.x right matters for
398
    /// glyph programs that grid-fit the left side bearing relative to pp1.
399
6318
    pub fn hint_glyph_with_flags_pp1(
400
6318
        &mut self,
401
6318
        points_f26dot6: &[(i32, i32)],
402
6318
        on_curve: &[bool],
403
6318
        contour_ends: &[u16],
404
6318
        instructions: &[u8],
405
6318
        advance_width_f26dot6: i32,
406
6318
        phantom_pp1_x: i32,
407
6318
    ) -> Result<(Vec<(i32, i32)>, Vec<bool>), HintError> {
408
6318
        if instructions.is_empty() || !self.fpgm_executed {
409
270
            return Ok((points_f26dot6.to_vec(), on_curve.to_vec()));
410
6048
        }
411

            
412
6048
        let real_count = points_f26dot6.len();
413

            
414
6048
        let mut points: Vec<Point> = points_f26dot6
415
6048
            .iter()
416
196020
            .map(|&(x, y)| Point { x, y })
417
6048
            .collect();
418
6048
        points.push(Point { x: phantom_pp1_x, y: 0 });                     // pp1: origin (xMin - lsb)
419
6048
        points.push(Point { x: phantom_pp1_x + advance_width_f26dot6, y: 0 }); // pp2: advance
420
6048
        points.push(Point { x: 0, y: 0 });
421
6048
        points.push(Point { x: 0, y: 0 });
422

            
423
6048
        let mut on_curve_ext: Vec<bool> = on_curve.to_vec();
424
6048
        on_curve_ext.extend_from_slice(&[true, true, true, true]);
425

            
426
6048
        self.interpreter
427
6048
            .hint_glyph(&points, &on_curve_ext, contour_ends, instructions)?;
428

            
429
6048
        let coords: Vec<(i32, i32)> = self.interpreter.zones[1]
430
6048
            .current
431
6048
            .iter()
432
6048
            .take(real_count)
433
196020
            .map(|p| (p.x, p.y))
434
6048
            .collect();
435

            
436
        use interpreter::PointFlags;
437
6048
        let flags: Vec<bool> = self.interpreter.zones[1]
438
6048
            .flags
439
6048
            .iter()
440
6048
            .take(real_count)
441
196020
            .map(|f| f.contains(PointFlags::ON_CURVE))
442
6048
            .collect();
443

            
444
6048
        Ok((coords, flags))
445
6318
    }
446

            
447
    /// Returns the prep bytecode.
448
    pub fn prep_bytecode(&self) -> &[u8] {
449
        &self.prep_bytecode
450
    }
451

            
452
    /// Returns the CVT values in font units (before scaling).
453
    pub fn cvt_funits(&self) -> &[i16] {
454
        &self.cvt_funits
455
    }
456

            
457
    /// Returns whether fpgm was executed successfully.
458
    pub fn fpgm_executed(&self) -> bool {
459
        self.fpgm_executed
460
    }
461
}