skrifa/outline/glyf/
mod.rs

1//! Scaling support for TrueType outlines.
2
3mod deltas;
4mod hint;
5mod memory;
6mod outline;
7
8#[cfg(feature = "libm")]
9#[allow(unused_imports)]
10use core_maths::CoreFloat;
11
12pub use hint::{HintError, HintInstance, HintOutline};
13pub use outline::{Outline, ScaledOutline};
14use raw::{FontRef, ReadError};
15
16use super::{DrawError, GlyphHMetrics, Hinting};
17use crate::GLYF_COMPOSITE_RECURSION_LIMIT;
18use memory::{FreeTypeOutlineMemory, HarfBuzzOutlineMemory};
19
20use read_fonts::{
21    tables::{
22        glyf::{
23            Anchor, CompositeGlyph, CompositeGlyphFlags, Glyf, Glyph, PointMarker, SimpleGlyph,
24        },
25        gvar::Gvar,
26        hdmx::Hdmx,
27        loca::Loca,
28    },
29    types::{F26Dot6, F2Dot14, Fixed, GlyphId, Point, Tag},
30    TableProvider,
31};
32
33/// Number of phantom points generated at the end of an outline.
34pub const PHANTOM_POINT_COUNT: usize = 4;
35
36/// Scaler state for TrueType outlines.
37#[derive(Clone)]
38pub struct Outlines<'a> {
39    pub(crate) font: FontRef<'a>,
40    pub(crate) glyph_metrics: GlyphHMetrics<'a>,
41    loca: Loca<'a>,
42    glyf: Glyf<'a>,
43    gvar: Option<Gvar<'a>>,
44    hdmx: Option<Hdmx<'a>>,
45    fpgm: &'a [u8],
46    prep: &'a [u8],
47    cvt_len: u32,
48    max_function_defs: u16,
49    max_instruction_defs: u16,
50    max_twilight_points: u16,
51    max_stack_elements: u16,
52    max_storage: u16,
53    glyph_count: u16,
54    units_per_em: u16,
55    os2_vmetrics: [i16; 2],
56    prefer_interpreter: bool,
57}
58
59impl<'a> Outlines<'a> {
60    pub fn new(font: &FontRef<'a>) -> Option<Self> {
61        let loca = font.loca(None).ok()?;
62        let glyf = font.glyf().ok()?;
63        let glyph_metrics = GlyphHMetrics::new(font)?;
64        let (
65            glyph_count,
66            max_function_defs,
67            max_instruction_defs,
68            max_twilight_points,
69            max_stack_elements,
70            max_storage,
71            max_instructions,
72        ) = font
73            .maxp()
74            .map(|maxp| {
75                (
76                    maxp.num_glyphs(),
77                    maxp.max_function_defs().unwrap_or_default(),
78                    maxp.max_instruction_defs().unwrap_or_default(),
79                    // Add 4 for phantom points
80                    // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttobjs.c#L1188>
81                    maxp.max_twilight_points()
82                        .unwrap_or_default()
83                        .saturating_add(4),
84                    // Add 32 to match FreeType's heuristic for buggy fonts
85                    // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/truetype/ttinterp.c#L356>
86                    maxp.max_stack_elements()
87                        .unwrap_or_default()
88                        .saturating_add(32),
89                    maxp.max_storage().unwrap_or_default(),
90                    maxp.max_size_of_instructions().unwrap_or_default(),
91                )
92            })
93            .unwrap_or_default();
94        let os2_vmetrics = font
95            .os2()
96            .map(|os2| [os2.s_typo_ascender(), os2.s_typo_descender()])
97            .unwrap_or_default();
98        let fpgm = font
99            .data_for_tag(Tag::new(b"fpgm"))
100            .unwrap_or_default()
101            .as_bytes();
102        let prep = font
103            .data_for_tag(Tag::new(b"prep"))
104            .unwrap_or_default()
105            .as_bytes();
106        // Copy FreeType's logic on whether to use the interpreter:
107        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/base/ftobjs.c#L1001>
108        let prefer_interpreter = !(max_instructions == 0 && fpgm.is_empty() && prep.is_empty());
109        let cvt_len = font.cvt().map(|cvt| cvt.len() as u32).unwrap_or_default();
110        Some(Self {
111            font: font.clone(),
112            glyph_metrics,
113            loca,
114            glyf,
115            gvar: font.gvar().ok(),
116            hdmx: font.hdmx().ok(),
117            fpgm,
118            prep,
119            cvt_len,
120            max_function_defs,
121            max_instruction_defs,
122            max_twilight_points,
123            max_stack_elements,
124            max_storage,
125            glyph_count,
126            units_per_em: font.head().ok()?.units_per_em(),
127            os2_vmetrics,
128            prefer_interpreter,
129        })
130    }
131
132    pub fn units_per_em(&self) -> u16 {
133        self.units_per_em
134    }
135
136    pub fn glyph_count(&self) -> usize {
137        self.glyph_count as usize
138    }
139
140    pub fn prefer_interpreter(&self) -> bool {
141        self.prefer_interpreter
142    }
143
144    pub fn outline(&self, glyph_id: GlyphId) -> Result<Outline<'a>, DrawError> {
145        let mut outline = Outline {
146            glyph_id,
147            has_variations: self.gvar.is_some(),
148            ..Default::default()
149        };
150        let glyph = self.loca.get_glyf(glyph_id, &self.glyf)?;
151        if let Some(glyph) = glyph.as_ref() {
152            self.outline_rec(glyph, &mut outline, 0, 0)?;
153        }
154        outline.points += PHANTOM_POINT_COUNT;
155        outline.max_stack = self.max_stack_elements as usize;
156        outline.cvt_count = self.cvt_len as usize;
157        outline.storage_count = self.max_storage as usize;
158        outline.max_twilight_points = self.max_twilight_points as usize;
159        outline.glyph = glyph;
160        Ok(outline)
161    }
162
163    pub fn compute_scale(&self, ppem: Option<f32>) -> (bool, F26Dot6) {
164        if let Some(ppem) = ppem {
165            if self.units_per_em > 0 {
166                return (
167                    true,
168                    F26Dot6::from_bits((ppem * 64.) as i32)
169                        / F26Dot6::from_bits(self.units_per_em as i32),
170                );
171            }
172        }
173        (false, F26Dot6::from_bits(0x10000))
174    }
175}
176
177impl Outlines<'_> {
178    fn outline_rec(
179        &self,
180        glyph: &Glyph,
181        outline: &mut Outline,
182        component_depth: usize,
183        recurse_depth: usize,
184    ) -> Result<(), DrawError> {
185        if recurse_depth > GLYF_COMPOSITE_RECURSION_LIMIT {
186            return Err(DrawError::RecursionLimitExceeded(outline.glyph_id));
187        }
188        match glyph {
189            Glyph::Simple(simple) => {
190                let num_points = simple.num_points();
191                let num_points_with_phantom = num_points + PHANTOM_POINT_COUNT;
192                outline.max_simple_points = outline.max_simple_points.max(num_points_with_phantom);
193                outline.points += num_points;
194                outline.contours += simple.end_pts_of_contours().len();
195                outline.has_hinting = outline.has_hinting || simple.instruction_length() != 0;
196                outline.max_other_points = outline.max_other_points.max(num_points_with_phantom);
197                outline.has_overlaps |= simple.has_overlapping_contours();
198            }
199            Glyph::Composite(composite) => {
200                let (mut count, instructions) = composite.count_and_instructions();
201                count += PHANTOM_POINT_COUNT;
202                let point_base = outline.points;
203                for (component, flags) in composite.component_glyphs_and_flags() {
204                    outline.has_overlaps |= flags.contains(CompositeGlyphFlags::OVERLAP_COMPOUND);
205                    let component_glyph = self.loca.get_glyf(component.into(), &self.glyf)?;
206                    let Some(component_glyph) = component_glyph else {
207                        continue;
208                    };
209                    self.outline_rec(
210                        &component_glyph,
211                        outline,
212                        component_depth + count,
213                        recurse_depth + 1,
214                    )?;
215                }
216                let has_hinting = !instructions.unwrap_or_default().is_empty();
217                if has_hinting {
218                    // We only need the "other points" buffers if the
219                    // composite glyph has instructions.
220                    let num_points_in_composite = outline.points - point_base + PHANTOM_POINT_COUNT;
221                    outline.max_other_points =
222                        outline.max_other_points.max(num_points_in_composite);
223                }
224                outline.max_component_delta_stack = outline
225                    .max_component_delta_stack
226                    .max(component_depth + count);
227                outline.has_hinting = outline.has_hinting || has_hinting;
228            }
229        }
230        Ok(())
231    }
232
233    fn hdmx_width(&self, ppem: f32, glyph_id: GlyphId) -> Option<u8> {
234        let hdmx = self.hdmx.as_ref()?;
235        let ppem_u8 = ppem as u8;
236        // Make sure our ppem is integral and fits into u8
237        if ppem_u8 as f32 == ppem {
238            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttgload.c#L1996>
239            hdmx.record_for_size(ppem_u8)?
240                .widths
241                .get(glyph_id.to_u32() as usize)
242                .copied()
243        } else {
244            None
245        }
246    }
247}
248
249trait Scaler {
250    fn outlines(&self) -> &Outlines;
251    fn setup_phantom_points(
252        &mut self,
253        bounds: [i16; 4],
254        lsb: i32,
255        advance: i32,
256        tsb: i32,
257        vadvance: i32,
258    );
259    fn load_empty(&mut self, glyph_id: GlyphId) -> Result<(), DrawError>;
260    fn load_simple(&mut self, glyph: &SimpleGlyph, glyph_id: GlyphId) -> Result<(), DrawError>;
261    fn load_composite(
262        &mut self,
263        glyph: &CompositeGlyph,
264        glyph_id: GlyphId,
265        recurse_depth: usize,
266    ) -> Result<(), DrawError>;
267
268    fn load(
269        &mut self,
270        glyph: &Option<Glyph>,
271        glyph_id: GlyphId,
272        recurse_depth: usize,
273    ) -> Result<(), DrawError> {
274        if recurse_depth > GLYF_COMPOSITE_RECURSION_LIMIT {
275            return Err(DrawError::RecursionLimitExceeded(glyph_id));
276        }
277        let bounds = match &glyph {
278            Some(glyph) => [glyph.x_min(), glyph.x_max(), glyph.y_min(), glyph.y_max()],
279            _ => [0; 4],
280        };
281        let outlines = self.outlines();
282        let lsb = outlines.glyph_metrics.lsb(glyph_id, &[]);
283        let advance = outlines.glyph_metrics.advance_width(glyph_id, &[]);
284        let [ascent, descent] = outlines.os2_vmetrics.map(|x| x as i32);
285        let tsb = ascent - bounds[3] as i32;
286        let vadvance = ascent - descent;
287        self.setup_phantom_points(bounds, lsb, advance, tsb, vadvance);
288        match glyph {
289            Some(Glyph::Simple(simple)) => self.load_simple(simple, glyph_id),
290            Some(Glyph::Composite(composite)) => {
291                self.load_composite(composite, glyph_id, recurse_depth)
292            }
293            None => self.load_empty(glyph_id),
294        }
295    }
296}
297
298/// f32 all the things. Hold your rounding. No hinting.
299pub(crate) struct HarfBuzzScaler<'a> {
300    outlines: &'a Outlines<'a>,
301    memory: HarfBuzzOutlineMemory<'a>,
302    coords: &'a [F2Dot14],
303    point_count: usize,
304    contour_count: usize,
305    component_delta_count: usize,
306    ppem: f32,
307    scale: F26Dot6,
308    is_scaled: bool,
309    /// Phantom points. These are 4 extra points appended to the end of an
310    /// outline that allow the bytecode interpreter to produce hinted
311    /// metrics.
312    ///
313    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructing_glyphs#phantom-points>
314    phantom: [Point<f32>; PHANTOM_POINT_COUNT],
315}
316
317impl<'a> HarfBuzzScaler<'a> {
318    pub(crate) fn unhinted(
319        outlines: &'a Outlines<'a>,
320        outline: &'a Outline,
321        buf: &'a mut [u8],
322        ppem: Option<f32>,
323        coords: &'a [F2Dot14],
324    ) -> Result<Self, DrawError> {
325        outline.ensure_point_count_limit()?;
326        let (is_scaled, scale) = outlines.compute_scale(ppem);
327        let memory =
328            HarfBuzzOutlineMemory::new(outline, buf).ok_or(DrawError::InsufficientMemory)?;
329        Ok(Self {
330            outlines,
331            memory,
332            coords,
333            point_count: 0,
334            contour_count: 0,
335            component_delta_count: 0,
336            ppem: ppem.unwrap_or_default(),
337            scale,
338            is_scaled,
339            phantom: Default::default(),
340        })
341    }
342
343    pub(crate) fn scale(
344        mut self,
345        glyph: &Option<Glyph>,
346        glyph_id: GlyphId,
347    ) -> Result<ScaledOutline<'a, f32>, DrawError> {
348        self.load(glyph, glyph_id, 0)?;
349        Ok(ScaledOutline::new(
350            &mut self.memory.points[..self.point_count],
351            self.phantom,
352            &mut self.memory.flags[..self.point_count],
353            &mut self.memory.contours[..self.contour_count],
354            self.outlines.hdmx_width(self.ppem, glyph_id),
355        ))
356    }
357}
358
359/// F26Dot6 coords, Fixed deltas, and a penchant for rounding
360pub(crate) struct FreeTypeScaler<'a> {
361    outlines: &'a Outlines<'a>,
362    memory: FreeTypeOutlineMemory<'a>,
363    coords: &'a [F2Dot14],
364    point_count: usize,
365    contour_count: usize,
366    component_delta_count: usize,
367    ppem: f32,
368    scale: F26Dot6,
369    is_scaled: bool,
370    is_hinted: bool,
371    pedantic_hinting: bool,
372    /// Phantom points. These are 4 extra points appended to the end of an
373    /// outline that allow the bytecode interpreter to produce hinted
374    /// metrics.
375    ///
376    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructing_glyphs#phantom-points>
377    phantom: [Point<F26Dot6>; PHANTOM_POINT_COUNT],
378    hinter: Option<&'a HintInstance>,
379}
380
381impl<'a> FreeTypeScaler<'a> {
382    pub(crate) fn unhinted(
383        outlines: &'a Outlines<'a>,
384        outline: &'a Outline,
385        buf: &'a mut [u8],
386        ppem: Option<f32>,
387        coords: &'a [F2Dot14],
388    ) -> Result<Self, DrawError> {
389        outline.ensure_point_count_limit()?;
390        let (is_scaled, scale) = outlines.compute_scale(ppem);
391        let memory = FreeTypeOutlineMemory::new(outline, buf, Hinting::None)
392            .ok_or(DrawError::InsufficientMemory)?;
393        Ok(Self {
394            outlines,
395            memory,
396            coords,
397            point_count: 0,
398            contour_count: 0,
399            component_delta_count: 0,
400            ppem: ppem.unwrap_or_default(),
401            scale,
402            is_scaled,
403            is_hinted: false,
404            pedantic_hinting: false,
405            phantom: Default::default(),
406            hinter: None,
407        })
408    }
409
410    pub(crate) fn hinted(
411        outlines: &'a Outlines<'a>,
412        outline: &'a Outline,
413        buf: &'a mut [u8],
414        ppem: Option<f32>,
415        coords: &'a [F2Dot14],
416        hinter: &'a HintInstance,
417        pedantic_hinting: bool,
418    ) -> Result<Self, DrawError> {
419        outline.ensure_point_count_limit()?;
420        let (is_scaled, scale) = outlines.compute_scale(ppem);
421        let memory = FreeTypeOutlineMemory::new(outline, buf, Hinting::Embedded)
422            .ok_or(DrawError::InsufficientMemory)?;
423        Ok(Self {
424            outlines,
425            memory,
426            coords,
427            point_count: 0,
428            contour_count: 0,
429            component_delta_count: 0,
430            ppem: ppem.unwrap_or_default(),
431            scale,
432            is_scaled,
433            // We don't hint unscaled outlines
434            is_hinted: is_scaled,
435            pedantic_hinting,
436            phantom: Default::default(),
437            hinter: Some(hinter),
438        })
439    }
440
441    pub(crate) fn scale(
442        mut self,
443        glyph: &Option<Glyph>,
444        glyph_id: GlyphId,
445    ) -> Result<ScaledOutline<'a, F26Dot6>, DrawError> {
446        self.load(glyph, glyph_id, 0)?;
447        // Use hdmx if hinting is requested and backward compatibility mode
448        // is not enabled.
449        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/truetype/ttgload.c#L2559>
450        let hdmx_width = if self.is_hinted
451            && self
452                .hinter
453                .as_ref()
454                .map(|hinter| !hinter.backward_compatibility())
455                .unwrap_or(true)
456        {
457            self.outlines.hdmx_width(self.ppem, glyph_id)
458        } else {
459            None
460        };
461        Ok(ScaledOutline::new(
462            &mut self.memory.scaled[..self.point_count],
463            self.phantom,
464            &mut self.memory.flags[..self.point_count],
465            &mut self.memory.contours[..self.contour_count],
466            hdmx_width,
467        ))
468    }
469}
470
471impl Scaler for FreeTypeScaler<'_> {
472    fn setup_phantom_points(
473        &mut self,
474        bounds: [i16; 4],
475        lsb: i32,
476        advance: i32,
477        tsb: i32,
478        vadvance: i32,
479    ) {
480        // The four "phantom" points as computed by FreeType.
481        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttgload.c#L1365>
482        // horizontal:
483        self.phantom[0].x = F26Dot6::from_bits(bounds[0] as i32 - lsb);
484        self.phantom[0].y = F26Dot6::ZERO;
485        self.phantom[1].x = self.phantom[0].x + F26Dot6::from_bits(advance);
486        self.phantom[1].y = F26Dot6::ZERO;
487        // vertical:
488        self.phantom[2].x = F26Dot6::ZERO;
489        self.phantom[2].y = F26Dot6::from_bits(bounds[3] as i32 + tsb);
490        self.phantom[3].x = F26Dot6::ZERO;
491        self.phantom[3].y = self.phantom[2].y - F26Dot6::from_bits(vadvance);
492    }
493
494    fn outlines(&self) -> &Outlines {
495        self.outlines
496    }
497
498    fn load_empty(&mut self, glyph_id: GlyphId) -> Result<(), DrawError> {
499        // Roughly corresponds to the FreeType code at
500        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttgload.c#L1572>
501        let scale = self.scale;
502        let mut unscaled = self.phantom.map(|point| point.map(|x| x.to_bits()));
503        if self.outlines.gvar.is_some() && !self.coords.is_empty() {
504            if let Ok(Some(deltas)) = self.outlines.gvar.as_ref().unwrap().phantom_point_deltas(
505                &self.outlines.glyf,
506                &self.outlines.loca,
507                self.coords,
508                glyph_id,
509            ) {
510                unscaled[0] += deltas[0].map(Fixed::to_i32);
511                unscaled[1] += deltas[1].map(Fixed::to_i32);
512            }
513        }
514        if self.is_scaled {
515            for (phantom, unscaled) in self.phantom.iter_mut().zip(&unscaled) {
516                *phantom = unscaled.map(F26Dot6::from_bits) * scale;
517            }
518        } else {
519            for (phantom, unscaled) in self.phantom.iter_mut().zip(&unscaled) {
520                *phantom = unscaled.map(F26Dot6::from_i32);
521            }
522        }
523        Ok(())
524    }
525
526    fn load_simple(&mut self, glyph: &SimpleGlyph, glyph_id: GlyphId) -> Result<(), DrawError> {
527        use DrawError::InsufficientMemory;
528        // Compute the ranges for our point/flag buffers and slice them.
529        let points_start = self.point_count;
530        let point_count = glyph.num_points();
531        let phantom_start = point_count;
532        let points_end = points_start + point_count + PHANTOM_POINT_COUNT;
533        let point_range = points_start..points_end;
534        let other_points_end = point_count + PHANTOM_POINT_COUNT;
535        // Scaled points and flags are accumulated as we load the outline.
536        let scaled = self
537            .memory
538            .scaled
539            .get_mut(point_range.clone())
540            .ok_or(InsufficientMemory)?;
541        let flags = self
542            .memory
543            .flags
544            .get_mut(point_range)
545            .ok_or(InsufficientMemory)?;
546        // Unscaled points are temporary and are allocated as needed. We only
547        // ever need one copy in memory for any simple or composite glyph so
548        // allocate from the base of the buffer.
549        let unscaled = self
550            .memory
551            .unscaled
552            .get_mut(..other_points_end)
553            .ok_or(InsufficientMemory)?;
554        // Read our unscaled points and flags (up to point_count which does not
555        // include phantom points).
556        glyph.read_points_fast(&mut unscaled[..point_count], &mut flags[..point_count])?;
557        // Compute the range for our contour end point buffer and slice it.
558        let contours_start = self.contour_count;
559        let contour_end_pts = glyph.end_pts_of_contours();
560        let contour_count = contour_end_pts.len();
561        let contours_end = contours_start + contour_count;
562        let contours = self
563            .memory
564            .contours
565            .get_mut(contours_start..contours_end)
566            .ok_or(InsufficientMemory)?;
567        // Read the contour end points, ensuring that they are properly
568        // ordered.
569        let mut last_end_pt = 0;
570        for (end_pt, contour) in contour_end_pts.iter().zip(contours.iter_mut()) {
571            let end_pt = end_pt.get();
572            if end_pt < last_end_pt {
573                return Err(ReadError::MalformedData(
574                    "unordered contour end points in TrueType glyph",
575                )
576                .into());
577            }
578            last_end_pt = end_pt;
579            *contour = end_pt;
580        }
581        // Adjust the running point/contour total counts
582        self.point_count += point_count;
583        self.contour_count += contour_count;
584        // Append phantom points to the outline.
585        for (i, phantom) in self.phantom.iter().enumerate() {
586            unscaled[phantom_start + i] = phantom.map(|x| x.to_bits());
587            flags[phantom_start + i] = Default::default();
588        }
589        let mut have_deltas = false;
590        if self.outlines.gvar.is_some() && !self.coords.is_empty() {
591            let gvar = self.outlines.gvar.as_ref().unwrap();
592            let glyph = deltas::SimpleGlyph {
593                points: &mut unscaled[..],
594                flags: &mut flags[..],
595                contours,
596            };
597            let deltas = self
598                .memory
599                .deltas
600                .get_mut(..point_count + PHANTOM_POINT_COUNT)
601                .ok_or(InsufficientMemory)?;
602            let iup_buffer = self
603                .memory
604                .iup_buffer
605                .get_mut(..point_count + PHANTOM_POINT_COUNT)
606                .ok_or(InsufficientMemory)?;
607            if deltas::simple_glyph(gvar, glyph_id, self.coords, glyph, iup_buffer, deltas).is_ok()
608            {
609                have_deltas = true;
610            }
611        }
612        let ins = glyph.instructions();
613        let is_hinted = self.is_hinted;
614        if self.is_scaled {
615            let scale = self.scale;
616            if have_deltas {
617                for ((point, unscaled), delta) in scaled
618                    .iter_mut()
619                    .zip(unscaled.iter())
620                    .zip(self.memory.deltas.iter())
621                {
622                    let delta = delta.map(Fixed::to_f26dot6);
623                    let scaled = (unscaled.map(F26Dot6::from_i32) + delta) * scale;
624                    // The computed scale factor has an i32 -> 26.26 conversion built in. This undoes the
625                    // extra shift.
626                    *point = scaled.map(|v| F26Dot6::from_bits(v.to_i32()));
627                }
628                // FreeType applies different rounding to HVAR deltas. Since
629                // we're only using gvar, mimic that behavior for phantom point
630                // deltas when an HVAR table is present
631                if self.outlines.glyph_metrics.hvar.is_some() {
632                    for ((point, unscaled), delta) in scaled[phantom_start..]
633                        .iter_mut()
634                        .zip(&unscaled[phantom_start..])
635                        .zip(&self.memory.deltas[phantom_start..])
636                    {
637                        let delta = delta.map(Fixed::to_i32).map(F26Dot6::from_i32);
638                        let scaled = (unscaled.map(F26Dot6::from_i32) + delta) * scale;
639                        *point = scaled.map(|v| F26Dot6::from_bits(v.to_i32()));
640                    }
641                }
642                if is_hinted {
643                    // For hinting, we need to adjust the unscaled points as well.
644                    // Round off deltas for unscaled outlines.
645                    for (unscaled, delta) in unscaled.iter_mut().zip(self.memory.deltas.iter()) {
646                        *unscaled += delta.map(Fixed::to_i32);
647                    }
648                }
649            } else {
650                for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter_mut()) {
651                    *point = unscaled.map(|v| F26Dot6::from_bits(v) * scale);
652                }
653            }
654        } else {
655            if have_deltas {
656                // Round off deltas for unscaled outlines.
657                for (unscaled, delta) in unscaled.iter_mut().zip(self.memory.deltas.iter()) {
658                    *unscaled += delta.map(Fixed::to_i32);
659                }
660            }
661            // Unlike FreeType, we also store unscaled outlines in 26.6.
662            for (point, unscaled) in scaled.iter_mut().zip(unscaled.iter()) {
663                *point = unscaled.map(F26Dot6::from_i32);
664            }
665        }
666        // Commit our potentially modified phantom points.
667        self.phantom.copy_from_slice(&scaled[phantom_start..]);
668        if let (Some(hinter), true) = (self.hinter.as_ref(), is_hinted) {
669            if !ins.is_empty() {
670                // Create a copy of our scaled points in original_scaled.
671                let original_scaled = self
672                    .memory
673                    .original_scaled
674                    .get_mut(..other_points_end)
675                    .ok_or(InsufficientMemory)?;
676                original_scaled.copy_from_slice(scaled);
677                // When hinting, round the phantom points.
678                for point in &mut scaled[phantom_start..] {
679                    point.x = point.x.round();
680                    point.y = point.y.round();
681                }
682                let mut input = HintOutline {
683                    glyph_id,
684                    unscaled,
685                    scaled,
686                    original_scaled,
687                    flags,
688                    contours,
689                    bytecode: ins,
690                    phantom: &mut self.phantom,
691                    stack: self.memory.stack,
692                    cvt: self.memory.cvt,
693                    storage: self.memory.storage,
694                    twilight_scaled: self.memory.twilight_scaled,
695                    twilight_original_scaled: self.memory.twilight_original_scaled,
696                    twilight_flags: self.memory.twilight_flags,
697                    is_composite: false,
698                    coords: self.coords,
699                };
700                let hint_res = hinter.hint(self.outlines, &mut input, self.pedantic_hinting);
701                if let (Err(e), true) = (hint_res, self.pedantic_hinting) {
702                    return Err(e)?;
703                }
704            } else if !hinter.backward_compatibility() {
705                // Even when missing instructions, FreeType uses rounded
706                // phantom points when hinting is requested and backward
707                // compatibility mode is disabled.
708                // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttgload.c#L823>
709                // Notably, FreeType never calls TT_Hint_Glyph for composite
710                // glyphs when instructions are missing so this only applies
711                // to simple glyphs.
712                for (scaled, phantom) in scaled[phantom_start..].iter().zip(&mut self.phantom) {
713                    *phantom = scaled.map(|x| x.round());
714                }
715            }
716        }
717        if points_start != 0 {
718            // If we're not the first component, shift our contour end points.
719            for contour_end in contours.iter_mut() {
720                *contour_end += points_start as u16;
721            }
722        }
723        Ok(())
724    }
725
726    fn load_composite(
727        &mut self,
728        glyph: &CompositeGlyph,
729        glyph_id: GlyphId,
730        recurse_depth: usize,
731    ) -> Result<(), DrawError> {
732        use DrawError::InsufficientMemory;
733        let scale = self.scale;
734        // The base indices of the points and contours for the current glyph.
735        let point_base = self.point_count;
736        let contour_base = self.contour_count;
737        // Compute the per component deltas. Since composites can be nested, we
738        // use a stack and keep track of the base.
739        let mut have_deltas = false;
740        let delta_base = self.component_delta_count;
741        if self.outlines.gvar.is_some() && !self.coords.is_empty() {
742            let gvar = self.outlines.gvar.as_ref().unwrap();
743            let count = glyph.components().count() + PHANTOM_POINT_COUNT;
744            let deltas = self
745                .memory
746                .composite_deltas
747                .get_mut(delta_base..delta_base + count)
748                .ok_or(InsufficientMemory)?;
749            if deltas::composite_glyph(gvar, glyph_id, self.coords, &mut deltas[..]).is_ok() {
750                // Apply deltas to phantom points.
751                for (phantom, delta) in self
752                    .phantom
753                    .iter_mut()
754                    .zip(&deltas[deltas.len() - PHANTOM_POINT_COUNT..])
755                {
756                    *phantom += delta.map(Fixed::to_i32).map(F26Dot6::from_bits);
757                }
758                have_deltas = true;
759            }
760            self.component_delta_count += count;
761        }
762        if self.is_scaled {
763            for point in self.phantom.iter_mut() {
764                *point *= scale;
765            }
766        } else {
767            for point in self.phantom.iter_mut() {
768                *point = point.map(|x| F26Dot6::from_i32(x.to_bits()));
769            }
770        }
771        for (i, component) in glyph.components().enumerate() {
772            // Loading a component glyph will override phantom points so save a copy. We'll
773            // restore them unless the USE_MY_METRICS flag is set.
774            let phantom = self.phantom;
775            // Load the component glyph and keep track of the points range.
776            let start_point = self.point_count;
777            let component_glyph = self
778                .outlines
779                .loca
780                .get_glyf(component.glyph.into(), &self.outlines.glyf)?;
781            self.load(&component_glyph, component.glyph.into(), recurse_depth + 1)?;
782            let end_point = self.point_count;
783            if !component
784                .flags
785                .contains(CompositeGlyphFlags::USE_MY_METRICS)
786            {
787                // If the USE_MY_METRICS flag is missing, we restore the phantom points we
788                // saved at the start of the loop.
789                self.phantom = phantom;
790            }
791            // Prepares the transform components for our conversion math below.
792            fn scale_component(x: F2Dot14) -> F26Dot6 {
793                F26Dot6::from_bits(x.to_bits() as i32 * 4)
794            }
795            let xform = &component.transform;
796            let xx = scale_component(xform.xx);
797            let yx = scale_component(xform.yx);
798            let xy = scale_component(xform.xy);
799            let yy = scale_component(xform.yy);
800            let have_xform = component.flags.intersects(
801                CompositeGlyphFlags::WE_HAVE_A_SCALE
802                    | CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE
803                    | CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO,
804            );
805            if have_xform {
806                let scaled = &mut self.memory.scaled[start_point..end_point];
807                if self.is_scaled {
808                    for point in scaled {
809                        let x = point.x * xx + point.y * xy;
810                        let y = point.x * yx + point.y * yy;
811                        point.x = x;
812                        point.y = y;
813                    }
814                } else {
815                    for point in scaled {
816                        // This juggling is necessary because, unlike FreeType, we also
817                        // return unscaled outlines in 26.6 format for a consistent interface.
818                        let unscaled = point.map(|c| F26Dot6::from_bits(c.to_i32()));
819                        let x = unscaled.x * xx + unscaled.y * xy;
820                        let y = unscaled.x * yx + unscaled.y * yy;
821                        *point = Point::new(x, y).map(|c| F26Dot6::from_i32(c.to_bits()));
822                    }
823                }
824            }
825            let anchor_offset = match component.anchor {
826                Anchor::Offset { x, y } => {
827                    let (mut x, mut y) = (x as i32, y as i32);
828                    if have_xform
829                        && component.flags
830                            & (CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
831                                | CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET)
832                            == CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
833                    {
834                        // According to FreeType, this algorithm is a "guess"
835                        // and works better than the one documented by Apple.
836                        // https://github.com/freetype/freetype/blob/b1c90733ee6a04882b133101d61b12e352eeb290/src/truetype/ttgload.c#L1259
837                        fn hypot(a: F26Dot6, b: F26Dot6) -> Fixed {
838                            let a = a.to_bits().abs();
839                            let b = b.to_bits().abs();
840                            Fixed::from_bits(if a > b {
841                                a + ((3 * b) >> 3)
842                            } else {
843                                b + ((3 * a) >> 3)
844                            })
845                        }
846                        // FreeType uses a fixed point multiplication here.
847                        x = (Fixed::from_bits(x) * hypot(xx, xy)).to_bits();
848                        y = (Fixed::from_bits(y) * hypot(yy, yx)).to_bits();
849                    }
850                    if have_deltas {
851                        let delta = self
852                            .memory
853                            .composite_deltas
854                            .get(delta_base + i)
855                            .copied()
856                            .unwrap_or_default();
857                        // For composite glyphs, we copy FreeType and round off
858                        // the fractional parts of deltas.
859                        x += delta.x.to_i32();
860                        y += delta.y.to_i32();
861                    }
862                    if self.is_scaled {
863                        let mut offset = Point::new(x, y).map(F26Dot6::from_bits) * scale;
864                        if self.is_hinted
865                            && component
866                                .flags
867                                .contains(CompositeGlyphFlags::ROUND_XY_TO_GRID)
868                        {
869                            // Only round the y-coordinate, per FreeType.
870                            offset.y = offset.y.round();
871                        }
872                        offset
873                    } else {
874                        Point::new(x, y).map(F26Dot6::from_i32)
875                    }
876                }
877                Anchor::Point { base, component } => {
878                    let (base_offset, component_offset) = (base as usize, component as usize);
879                    let base_point = self
880                        .memory
881                        .scaled
882                        .get(point_base + base_offset)
883                        .ok_or(DrawError::InvalidAnchorPoint(glyph_id, base))?;
884                    let component_point = self
885                        .memory
886                        .scaled
887                        .get(start_point + component_offset)
888                        .ok_or(DrawError::InvalidAnchorPoint(glyph_id, component))?;
889                    *base_point - *component_point
890                }
891            };
892            if anchor_offset.x != F26Dot6::ZERO || anchor_offset.y != F26Dot6::ZERO {
893                for point in &mut self.memory.scaled[start_point..end_point] {
894                    *point += anchor_offset;
895                }
896            }
897        }
898        if have_deltas {
899            self.component_delta_count = delta_base;
900        }
901        if let (Some(hinter), true) = (self.hinter.as_ref(), self.is_hinted) {
902            let ins = glyph.instructions().unwrap_or_default();
903            if !ins.is_empty() {
904                // For composite glyphs, the unscaled and original points are
905                // simply copies of the current point set.
906                let start_point = point_base;
907                let end_point = self.point_count + PHANTOM_POINT_COUNT;
908                let point_range = start_point..end_point;
909                let phantom_start = point_range.len() - PHANTOM_POINT_COUNT;
910                let scaled = &mut self.memory.scaled[point_range.clone()];
911                let flags = self
912                    .memory
913                    .flags
914                    .get_mut(point_range.clone())
915                    .ok_or(InsufficientMemory)?;
916                // Append the current phantom points to the outline.
917                for (i, phantom) in self.phantom.iter().enumerate() {
918                    scaled[phantom_start + i] = *phantom;
919                    flags[phantom_start + i] = Default::default();
920                }
921                let other_points_end = point_range.len();
922                let unscaled = self
923                    .memory
924                    .unscaled
925                    .get_mut(..other_points_end)
926                    .ok_or(InsufficientMemory)?;
927                for (scaled, unscaled) in scaled.iter().zip(unscaled.iter_mut()) {
928                    *unscaled = scaled.map(|x| x.to_bits());
929                }
930                let original_scaled = self
931                    .memory
932                    .original_scaled
933                    .get_mut(..other_points_end)
934                    .ok_or(InsufficientMemory)?;
935                original_scaled.copy_from_slice(scaled);
936                let contours = self
937                    .memory
938                    .contours
939                    .get_mut(contour_base..self.contour_count)
940                    .ok_or(InsufficientMemory)?;
941                // Round the phantom points.
942                for p in &mut scaled[phantom_start..] {
943                    p.x = p.x.round();
944                    p.y = p.y.round();
945                }
946                // Clear the "touched" flags that are used during IUP processing.
947                for flag in flags.iter_mut() {
948                    flag.clear_marker(PointMarker::TOUCHED);
949                }
950                // Make sure our contour end points accurately reflect the
951                // outline slices.
952                if point_base != 0 {
953                    let delta = point_base as u16;
954                    for contour in contours.iter_mut() {
955                        *contour -= delta;
956                    }
957                }
958                let mut input = HintOutline {
959                    glyph_id,
960                    unscaled,
961                    scaled,
962                    original_scaled,
963                    flags,
964                    contours,
965                    bytecode: ins,
966                    phantom: &mut self.phantom,
967                    stack: self.memory.stack,
968                    cvt: self.memory.cvt,
969                    storage: self.memory.storage,
970                    twilight_scaled: self.memory.twilight_scaled,
971                    twilight_original_scaled: self.memory.twilight_original_scaled,
972                    twilight_flags: self.memory.twilight_flags,
973                    is_composite: true,
974                    coords: self.coords,
975                };
976                let hint_res = hinter.hint(self.outlines, &mut input, self.pedantic_hinting);
977                if let (Err(e), true) = (hint_res, self.pedantic_hinting) {
978                    return Err(e)?;
979                }
980                // Undo the contour shifts if we applied them above.
981                if point_base != 0 {
982                    let delta = point_base as u16;
983                    for contour in contours.iter_mut() {
984                        *contour += delta;
985                    }
986                }
987            }
988        }
989        Ok(())
990    }
991}
992
993impl Scaler for HarfBuzzScaler<'_> {
994    fn setup_phantom_points(
995        &mut self,
996        bounds: [i16; 4],
997        lsb: i32,
998        advance: i32,
999        tsb: i32,
1000        vadvance: i32,
1001    ) {
1002        // Same pattern as FreeType, just f32
1003        // horizontal:
1004        self.phantom[0].x = bounds[0] as f32 - lsb as f32;
1005        self.phantom[0].y = 0.0;
1006        self.phantom[1].x = self.phantom[0].x + advance as f32;
1007        self.phantom[1].y = 0.0;
1008        // vertical:
1009        self.phantom[2].x = 0.0;
1010        self.phantom[2].y = bounds[3] as f32 + tsb as f32;
1011        self.phantom[3].x = 0.0;
1012        self.phantom[3].y = self.phantom[2].y - vadvance as f32;
1013    }
1014
1015    fn outlines(&self) -> &Outlines {
1016        self.outlines
1017    }
1018
1019    fn load_empty(&mut self, glyph_id: GlyphId) -> Result<(), DrawError> {
1020        // HB doesn't have an equivalent so this version just copies the
1021        // FreeType version above but changed to use floating point
1022        let scale = self.scale.to_f32();
1023        let mut unscaled = self.phantom;
1024        if self.outlines.glyph_metrics.hvar.is_none()
1025            && self.outlines.gvar.is_some()
1026            && !self.coords.is_empty()
1027        {
1028            if let Ok(Some(deltas)) = self.outlines.gvar.as_ref().unwrap().phantom_point_deltas(
1029                &self.outlines.glyf,
1030                &self.outlines.loca,
1031                self.coords,
1032                glyph_id,
1033            ) {
1034                unscaled[0] += deltas[0].map(Fixed::to_f32);
1035                unscaled[1] += deltas[1].map(Fixed::to_f32);
1036            }
1037        }
1038        if self.is_scaled {
1039            for (phantom, unscaled) in self.phantom.iter_mut().zip(&unscaled) {
1040                *phantom = *unscaled * scale;
1041            }
1042        } else {
1043            for (phantom, unscaled) in self.phantom.iter_mut().zip(&unscaled) {
1044                *phantom = *unscaled;
1045            }
1046        }
1047        Ok(())
1048    }
1049
1050    fn load_simple(&mut self, glyph: &SimpleGlyph, glyph_id: GlyphId) -> Result<(), DrawError> {
1051        use DrawError::InsufficientMemory;
1052        // Compute the ranges for our point/flag buffers and slice them.
1053        let points_start = self.point_count;
1054        let point_count = glyph.num_points();
1055        let phantom_start = point_count;
1056        let points_end = points_start + point_count + PHANTOM_POINT_COUNT;
1057        let point_range = points_start..points_end;
1058        // Points and flags are accumulated as we load the outline.
1059        let points = self
1060            .memory
1061            .points
1062            .get_mut(point_range.clone())
1063            .ok_or(InsufficientMemory)?;
1064        let flags = self
1065            .memory
1066            .flags
1067            .get_mut(point_range)
1068            .ok_or(InsufficientMemory)?;
1069        glyph.read_points_fast(&mut points[..point_count], &mut flags[..point_count])?;
1070        // Compute the range for our contour end point buffer and slice it.
1071        let contours_start = self.contour_count;
1072        let contour_end_pts = glyph.end_pts_of_contours();
1073        let contour_count = contour_end_pts.len();
1074        let contours_end = contours_start + contour_count;
1075        let contours = self
1076            .memory
1077            .contours
1078            .get_mut(contours_start..contours_end)
1079            .ok_or(InsufficientMemory)?;
1080        // Read the contour end points.
1081        for (end_pt, contour) in contour_end_pts.iter().zip(contours.iter_mut()) {
1082            *contour = end_pt.get();
1083        }
1084        // Adjust the running point/contour total counts
1085        self.point_count += point_count;
1086        self.contour_count += contour_count;
1087        // Append phantom points to the outline.
1088        for (i, phantom) in self.phantom.iter().enumerate() {
1089            points[phantom_start + i] = *phantom;
1090            flags[phantom_start + i] = Default::default();
1091        }
1092        // Acquire deltas
1093        if self.outlines.gvar.is_some() && !self.coords.is_empty() {
1094            let gvar = self.outlines.gvar.as_ref().unwrap();
1095            let glyph = deltas::SimpleGlyph {
1096                points: &mut points[..],
1097                flags: &mut flags[..],
1098                contours,
1099            };
1100            let deltas = self
1101                .memory
1102                .deltas
1103                .get_mut(..point_count + PHANTOM_POINT_COUNT)
1104                .ok_or(InsufficientMemory)?;
1105            let iup_buffer = self
1106                .memory
1107                .iup_buffer
1108                .get_mut(..point_count + PHANTOM_POINT_COUNT)
1109                .ok_or(InsufficientMemory)?;
1110            if deltas::simple_glyph(gvar, glyph_id, self.coords, glyph, iup_buffer, deltas).is_ok()
1111            {
1112                for (point, delta) in points.iter_mut().zip(deltas) {
1113                    *point += *delta;
1114                }
1115            }
1116        }
1117        // Apply scaling
1118        if self.is_scaled {
1119            let scale = self.scale.to_f32();
1120            for point in points.iter_mut() {
1121                *point = point.map(|c| c * scale);
1122            }
1123        }
1124
1125        if points_start != 0 {
1126            // If we're not the first component, shift our contour end points.
1127            for contour_end in contours.iter_mut() {
1128                *contour_end += points_start as u16;
1129            }
1130        }
1131        Ok(())
1132    }
1133
1134    fn load_composite(
1135        &mut self,
1136        glyph: &CompositeGlyph,
1137        glyph_id: GlyphId,
1138        recurse_depth: usize,
1139    ) -> Result<(), DrawError> {
1140        use DrawError::InsufficientMemory;
1141        let scale = self.scale.to_f32();
1142        // The base indices of the points for the current glyph.
1143        let point_base = self.point_count;
1144        // Compute the per component deltas. Since composites can be nested, we
1145        // use a stack and keep track of the base.
1146        let mut have_deltas = false;
1147        let delta_base = self.component_delta_count;
1148        if self.outlines.gvar.is_some() && !self.coords.is_empty() {
1149            let gvar = self.outlines.gvar.as_ref().unwrap();
1150            let count = glyph.components().count() + PHANTOM_POINT_COUNT;
1151            let deltas = self
1152                .memory
1153                .composite_deltas
1154                .get_mut(delta_base..delta_base + count)
1155                .ok_or(InsufficientMemory)?;
1156            if deltas::composite_glyph(gvar, glyph_id, self.coords, &mut deltas[..]).is_ok() {
1157                // Apply deltas to phantom points.
1158                for (phantom, delta) in self
1159                    .phantom
1160                    .iter_mut()
1161                    .zip(&deltas[deltas.len() - PHANTOM_POINT_COUNT..])
1162                {
1163                    *phantom += *delta;
1164                }
1165                have_deltas = true;
1166            }
1167            self.component_delta_count += count;
1168        }
1169        if self.is_scaled {
1170            for point in self.phantom.iter_mut() {
1171                *point *= scale;
1172            }
1173        }
1174        for (i, component) in glyph.components().enumerate() {
1175            // Loading a component glyph will override phantom points so save a copy. We'll
1176            // restore them unless the USE_MY_METRICS flag is set.
1177            let phantom = self.phantom;
1178            // Load the component glyph and keep track of the points range.
1179            let start_point = self.point_count;
1180            let component_glyph = self
1181                .outlines
1182                .loca
1183                .get_glyf(component.glyph.into(), &self.outlines.glyf)?;
1184            self.load(&component_glyph, component.glyph.into(), recurse_depth + 1)?;
1185            let end_point = self.point_count;
1186            if !component
1187                .flags
1188                .contains(CompositeGlyphFlags::USE_MY_METRICS)
1189            {
1190                // If the USE_MY_METRICS flag is missing, we restore the phantom points we
1191                // saved at the start of the loop.
1192                self.phantom = phantom;
1193            }
1194            let have_xform = component.flags.intersects(
1195                CompositeGlyphFlags::WE_HAVE_A_SCALE
1196                    | CompositeGlyphFlags::WE_HAVE_AN_X_AND_Y_SCALE
1197                    | CompositeGlyphFlags::WE_HAVE_A_TWO_BY_TWO,
1198            );
1199            let mut transform = if have_xform {
1200                let xform = &component.transform;
1201                [
1202                    xform.xx,
1203                    xform.yx,
1204                    xform.xy,
1205                    xform.yy,
1206                    F2Dot14::ZERO,
1207                    F2Dot14::ZERO,
1208                ]
1209                .map(|x| x.to_f32())
1210            } else {
1211                [1.0, 0.0, 0.0, 1.0, 0.0, 0.0] // identity
1212            };
1213
1214            let anchor_offset = match component.anchor {
1215                Anchor::Offset { x, y } => {
1216                    let (mut x, mut y) = (x as f32, y as f32);
1217                    if have_xform
1218                        && component.flags
1219                            & (CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
1220                                | CompositeGlyphFlags::UNSCALED_COMPONENT_OFFSET)
1221                            == CompositeGlyphFlags::SCALED_COMPONENT_OFFSET
1222                    {
1223                        // Scale x by the magnitude of the x-basis, y by the y-basis
1224                        // FreeType implements hypot, we can just use the provided implementation
1225                        x *= hypot(transform[0], transform[2]);
1226                        y *= hypot(transform[1], transform[3]);
1227                    }
1228                    Point::new(x, y)
1229                        + self
1230                            .memory
1231                            .composite_deltas
1232                            .get(delta_base + i)
1233                            .copied()
1234                            .unwrap_or_default()
1235                }
1236                Anchor::Point { base, component } => {
1237                    let (base_offset, component_offset) = (base as usize, component as usize);
1238                    let base_point = self
1239                        .memory
1240                        .points
1241                        .get(point_base + base_offset)
1242                        .ok_or(DrawError::InvalidAnchorPoint(glyph_id, base))?;
1243                    let component_point = self
1244                        .memory
1245                        .points
1246                        .get(start_point + component_offset)
1247                        .ok_or(DrawError::InvalidAnchorPoint(glyph_id, component))?;
1248                    *base_point - *component_point
1249                }
1250            };
1251            transform[4] = anchor_offset.x;
1252            transform[5] = anchor_offset.y;
1253
1254            let points = &mut self.memory.points[start_point..end_point];
1255            for point in points.iter_mut() {
1256                *point = map_point(transform, *point);
1257            }
1258        }
1259        if have_deltas {
1260            self.component_delta_count = delta_base;
1261        }
1262        Ok(())
1263    }
1264}
1265
1266/// Magnitude of the vector (x, y)
1267fn hypot(x: f32, y: f32) -> f32 {
1268    x.hypot(y)
1269}
1270
1271fn map_point(transform: [f32; 6], p: Point<f32>) -> Point<f32> {
1272    Point {
1273        x: transform[0] * p.x + transform[2] * p.y + transform[4],
1274        y: transform[1] * p.x + transform[3] * p.y + transform[5],
1275    }
1276}
1277
1278#[cfg(test)]
1279mod tests {
1280    use super::*;
1281    use crate::MetadataProvider;
1282    use raw::{
1283        tables::{
1284            glyf::{CompositeGlyphFlags, Glyf, SimpleGlyphFlags},
1285            loca::Loca,
1286        },
1287        FontRead, FontRef, TableProvider,
1288    };
1289
1290    #[test]
1291    fn overlap_flags() {
1292        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
1293        let scaler = Outlines::new(&font).unwrap();
1294        let glyph_count = font.maxp().unwrap().num_glyphs();
1295        // GID 2 is a composite glyph with the overlap bit on a component
1296        // GID 3 is a simple glyph with the overlap bit on the first flag
1297        let expected_gids_with_overlap = vec![2, 3];
1298        assert_eq!(
1299            expected_gids_with_overlap,
1300            (0..glyph_count)
1301                .filter(|gid| scaler.outline(GlyphId::from(*gid)).unwrap().has_overlaps)
1302                .collect::<Vec<_>>()
1303        );
1304    }
1305
1306    #[test]
1307    fn interpreter_preference() {
1308        // no instructions in this font...
1309        let font = FontRef::new(font_test_data::COLRV0V1).unwrap();
1310        let outlines = Outlines::new(&font).unwrap();
1311        // thus no preference for the interpreter
1312        assert!(!outlines.prefer_interpreter());
1313        // but this one has instructions...
1314        let font = FontRef::new(font_test_data::TTHINT_SUBSET).unwrap();
1315        let outlines = Outlines::new(&font).unwrap();
1316        // so let's use it
1317        assert!(outlines.prefer_interpreter());
1318    }
1319
1320    #[test]
1321    fn empty_glyph_advance() {
1322        let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1323        let outlines = Outlines::new(&font).unwrap();
1324        let coords = [F2Dot14::from_f32(0.5)];
1325        let ppem = Some(24.0);
1326        let gid = font.charmap().map(' ').unwrap();
1327        let outline = outlines.outline(gid).unwrap();
1328        // Make sure this is an empty outline since that's what we're testing
1329        assert!(outline.glyph.is_none());
1330        let mut buf = [0u8; 128];
1331        let scaler =
1332            FreeTypeScaler::unhinted(&outlines, &outline, &mut buf, ppem, &coords).unwrap();
1333        let scaled = scaler.scale(&outline.glyph, gid).unwrap();
1334        let advance = scaled.adjusted_advance_width();
1335        assert!(advance != F26Dot6::ZERO);
1336    }
1337
1338    #[test]
1339    fn empty_glyphs_have_phantom_points_too() {
1340        let font = FontRef::new(font_test_data::HVAR_WITH_TRUNCATED_ADVANCE_INDEX_MAP).unwrap();
1341        let outlines = Outlines::new(&font).unwrap();
1342        let gid = font.charmap().map(' ').unwrap();
1343        let outline = outlines.outline(gid).unwrap();
1344        assert!(outline.glyph.is_none());
1345        assert_eq!(outline.points, PHANTOM_POINT_COUNT);
1346    }
1347
1348    // fuzzer overflow for composite glyph with too many total points
1349    // <https://issues.oss-fuzz.com/issues/391753684
1350    #[test]
1351    fn composite_with_too_many_points() {
1352        let font = FontRef::new(font_test_data::GLYF_COMPONENTS).unwrap();
1353        let mut outlines = Outlines::new(&font).unwrap();
1354        // Hack glyf and loca to build a glyph that contains more than 64k
1355        // total points
1356        let mut glyf_buf = font_test_data::bebuffer::BeBuffer::new();
1357        // Make a component glyph with 40k points so we overflow the
1358        // total limit in a composite
1359        let simple_glyph_point_count = 40000;
1360        glyf_buf = glyf_buf.push(1u16); // number of contours
1361        glyf_buf = glyf_buf.extend([0i16; 4]); // bbox
1362        glyf_buf = glyf_buf.push((simple_glyph_point_count - 1) as u16); // contour ends
1363        glyf_buf = glyf_buf.push(0u16); // instruction count
1364        for _ in 0..simple_glyph_point_count {
1365            glyf_buf =
1366                glyf_buf.push(SimpleGlyphFlags::X_SHORT_VECTOR | SimpleGlyphFlags::Y_SHORT_VECTOR);
1367        }
1368        // x/y coords
1369        for _ in 0..simple_glyph_point_count * 2 {
1370            glyf_buf = glyf_buf.push(0u8);
1371        }
1372        let glyph0_end = glyf_buf.len();
1373        // Now make a composite with two components
1374        glyf_buf = glyf_buf.push(-1i16); // negative signifies composite
1375        glyf_buf = glyf_buf.extend([0i16; 4]); // bbox
1376        for i in 0..2 {
1377            let flags = if i == 0 {
1378                CompositeGlyphFlags::MORE_COMPONENTS | CompositeGlyphFlags::ARGS_ARE_XY_VALUES
1379            } else {
1380                CompositeGlyphFlags::ARGS_ARE_XY_VALUES
1381            };
1382            glyf_buf = glyf_buf.push(flags); // component flag
1383            glyf_buf = glyf_buf.push(0u16); // component gid
1384            glyf_buf = glyf_buf.extend([0u8; 2]); // x/y offset
1385        }
1386        let glyph1_end = glyf_buf.len();
1387        outlines.glyf = Glyf::read(glyf_buf.data().into()).unwrap();
1388        // Now create a loca table
1389        let mut loca_buf = font_test_data::bebuffer::BeBuffer::new();
1390        loca_buf = loca_buf.extend([0u32, glyph0_end as u32, glyph1_end as u32]);
1391        outlines.loca = Loca::read(loca_buf.data().into(), true).unwrap();
1392        let gid = GlyphId::new(1);
1393        let outline = outlines.outline(gid).unwrap();
1394        let mut mem_buf = vec![0u8; outline.required_buffer_size(Default::default())];
1395        // This outline has more than 64k points...
1396        assert!(outline.points > u16::MAX as usize);
1397        let result = FreeTypeScaler::unhinted(&outlines, &outline, &mut mem_buf, None, &[]);
1398        // And we get an error instead of an overflow panic
1399        assert!(matches!(result, Err(DrawError::TooManyPoints(_))));
1400    }
1401}