Skip to main content

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