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