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