Skip to main content

read_fonts/model/metrics/
glyph.rs

1//! Per-glyph metrics.
2
3#[cfg(feature = "libm")]
4#[allow(unused_imports)]
5use core_maths::CoreFloat;
6
7use crate::tables::glyf::outline::{
8    Buffers, OutlineContext, OutlinePlan, ScaleF32 as OutlineScaleF32,
9};
10use crate::{
11    mem::with_scratch,
12    model::{
13        glyph::{
14            bitmap::BitmapStrikes,
15            color::ColorGlyphs,
16            outline::{OutlineGlyphs, OutlineSource},
17            GlyphSource, GlyphSourceOrder,
18        },
19        metrics::{LineExtents, Metrics, Scale, Scale26Dot6, ScaleF32, ScaledGlyphMetrics},
20        Font, Kind,
21    },
22    ps::{cff::CffFontRef, charstring::CommandSink, type1::Type1Instance},
23    tables::{glyf::Glyf, hmtx::GlyphMetricRecords, loca::Loca},
24};
25use types::{BoundingBox, F2Dot14, F48Dot16, Fixed, GlyphId, Point};
26
27/// Where a glyph's ink sits, relative to its origin.
28///
29/// A bearing and a size rather than two corners, which is the shape the
30/// metrics built on it use.
31#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
32pub struct GlyphExtents<T> {
33    /// Distance from the origin to the left edge of the ink.
34    pub x_bearing: T,
35    /// Distance from the origin to the top edge of the ink.
36    pub y_bearing: T,
37    /// Width of the ink, rightward from `x_bearing`.
38    pub width: T,
39    /// Height of the ink, downward from `y_bearing`.
40    ///
41    /// Never negative. A caller whose own extents count upward negates it.
42    pub height: T,
43}
44
45impl<T> From<BoundingBox<T>> for GlyphExtents<T>
46where
47    T: Copy + PartialOrd + core::ops::Sub<Output = T>,
48{
49    /// Converts the corners of a box into a bearing and a size.
50    ///
51    /// The corners can be in either order. A reversed box still produces a
52    /// non-negative size.
53    fn from(bounds: BoundingBox<T>) -> Self {
54        let (left, right) = if bounds.x_min <= bounds.x_max {
55            (bounds.x_min, bounds.x_max)
56        } else {
57            (bounds.x_max, bounds.x_min)
58        };
59        let (bottom, top) = if bounds.y_min <= bounds.y_max {
60            (bounds.y_min, bounds.y_max)
61        } else {
62            (bounds.y_max, bounds.y_min)
63        };
64        Self {
65            x_bearing: left,
66            y_bearing: top,
67            width: right - left,
68            height: top - bottom,
69        }
70    }
71}
72
73/// Measurements of individual glyphs at one location.
74///
75/// Cheap to obtain, so it can be taken per query rather than held.
76#[derive(Clone, Copy)]
77pub struct GlyphMetrics<'a> {
78    h_metrics: &'a GlyphMetricRecords<'a>,
79    font: &'a Font,
80    global: &'a Metrics,
81    coords: &'a [F2Dot14],
82    num_glyphs: u32,
83    units_per_em: u16,
84    order: GlyphSourceOrder,
85    bitmap_ppem: Option<f32>,
86}
87
88impl<'a> GlyphMetrics<'a> {
89    /// Creates metrics for `font` at the location in `coords`.
90    #[inline]
91    pub(crate) fn new(font: &'a Font, global: &'a Metrics, coords: &'a [F2Dot14]) -> Self {
92        Self {
93            h_metrics: font.h_metric_records(),
94            font,
95            global,
96            coords,
97            num_glyphs: global.num_glyphs,
98            units_per_em: global.units_per_em,
99            order: GlyphSourceOrder::DEFAULT,
100            bitmap_ppem: None,
101        }
102    }
103
104    /// Returns these metrics in the units `scale` describes.
105    #[inline]
106    pub fn scaled<S: Scale>(self, scale: S) -> ScaledGlyphMetrics<'a, 'static, S> {
107        ScaledGlyphMetrics::new(self, scale)
108    }
109
110    /// Returns these metrics in pixels at `ppem`, as `f32`.
111    #[inline]
112    pub fn scaled_f32(self, ppem: f32) -> ScaledGlyphMetrics<'a, 'static, ScaleF32> {
113        let units_per_em = self.units_per_em;
114        self.scaled(ScaleF32::from_ppem(ppem, units_per_em))
115    }
116
117    /// Returns these metrics in pixels at `ppem`, in 26.6 fixed point.
118    #[inline]
119    pub fn scaled_26dot6(self, ppem: f32) -> ScaledGlyphMetrics<'a, 'static, Scale26Dot6> {
120        let units_per_em = self.units_per_em;
121        self.scaled(Scale26Dot6::from_ppem(ppem, units_per_em))
122    }
123
124    /// Measures ink using the sources in `order`.
125    ///
126    /// Each source in the font is tried in turn until one provides extents
127    /// for the glyph. [`GlyphSourceOrder::DEFAULT`] tries bitmaps, then color,
128    /// then outlines.
129    pub fn with_extents_source_order(self, order: GlyphSourceOrder) -> Self {
130        Self { order, ..self }
131    }
132
133    /// Measures bitmaps using the strike for `ppem`.
134    ///
135    /// This selects the smallest strike that is at least `ppem`, or the
136    /// largest strike if none is that large. `None` (the default) or a size
137    /// that is not positive selects the largest strike. A glyph missing from
138    /// that strike is measured by the next source in the order, even if
139    /// another strike contains it. No other metric depends on the size.
140    pub(crate) fn with_bitmap_ppem(self, ppem: Option<f32>) -> Self {
141        Self {
142            bitmap_ppem: ppem,
143            ..self
144        }
145    }
146
147    /// Returns the order used to measure ink.
148    pub fn extents_source_order(&self) -> GlyphSourceOrder {
149        self.order
150    }
151
152    /// Returns the advance width of `glyph`, in font units.
153    ///
154    /// See [`h_advance_exact`](Self::h_advance_exact) for the width before
155    /// it is narrowed to `f32`.
156    #[inline]
157    pub fn h_advance(&self, glyph: GlyphId) -> f32 {
158        self.h_advance_exact(glyph).to_f32()
159    }
160
161    /// Returns the exact advance width of `glyph`, in font units.
162    ///
163    /// `hmtx` contains whole units and variations can add a fraction, so the
164    /// result may not be a whole number. A varied advance below zero is
165    /// clamped to zero before scaling. The caller decides how to round it.
166    #[inline]
167    pub fn h_advance_exact(&self, glyph: GlyphId) -> F48Dot16 {
168        let mut width = F48Dot16::ZERO;
169        self.h_advance_batched(|value| value, core::iter::once((glyph, &mut width)));
170        width
171    }
172
173    /// Writes the advance width of each glyph to its slot, in order.
174    ///
175    /// Each width passes through `convert`, so a caller working in another
176    /// number type writes into that type directly.
177    #[inline]
178    pub fn h_advance_batched<'o, V: 'o>(
179        &self,
180        convert: impl Fn(F48Dot16) -> V,
181        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
182    ) {
183        let raw = self.h_metrics;
184        if raw.is_empty() {
185            // A Type 1 font states its widths in the charstrings rather than
186            // a table, so it reaches here and is measured another way. The
187            // test sits inside this branch so that a font with `hmtx`, which
188            // is nearly all of them, never makes it.
189            match self.font.kind() {
190                Kind::Type1(_) => {
191                    if let Some(font) = self.font.type1_instance() {
192                        return self.h_advance_batched_type1(font, convert, glyphs);
193                    }
194                }
195                Kind::Cff(cff, _) => {
196                    return self.h_advance_batched_standalone_cff(&cff, convert, glyphs)
197                }
198                _ => {}
199            }
200            // Otherwise the font states no widths at all, and every glyph
201            // gets half an em; no location can move that.
202            let half_em = F48Dot16::from_i32(self.units_per_em as i32 / 2);
203            for (_, out) in glyphs {
204                *out = convert(half_em);
205            }
206            return;
207        }
208        let coords = self.coords;
209        if coords.is_empty() {
210            return raw.run(self.num_glyphs, convert, glyphs);
211        }
212        self.h_advance_batched_varied(raw, coords, convert, glyphs)
213    }
214
215    /// Returns the advance height of `glyph`, in font units.
216    ///
217    /// See [`v_advance_exact`](Self::v_advance_exact) for the height before
218    /// it is narrowed to `f32`.
219    #[inline]
220    pub fn v_advance(&self, glyph: GlyphId) -> f32 {
221        self.v_advance_exact(glyph).to_f32()
222    }
223
224    /// Returns the exact advance height of `glyph`, in font units.
225    ///
226    /// The height is non-negative, as stored in the font. A varied advance
227    /// below zero is clamped to zero before scaling. A caller whose y axis
228    /// points down must negate it.
229    #[inline]
230    pub fn v_advance_exact(&self, glyph: GlyphId) -> F48Dot16 {
231        let mut height = F48Dot16::ZERO;
232        self.v_advance_batched(|value| value, core::iter::once((glyph, &mut height)));
233        height
234    }
235
236    /// Writes the advance height of each glyph to its slot, in order.
237    ///
238    /// Each height passes through `convert`, so a caller working in another
239    /// number type writes into that type directly.
240    #[inline]
241    pub fn v_advance_batched<'o, V: 'o>(
242        &self,
243        convert: impl Fn(F48Dot16) -> V,
244        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
245    ) {
246        let raw = self.font.v_metric_records();
247        if raw.is_empty() {
248            // Such a font stacks its glyphs by the line it lays horizontal
249            // text on, so every glyph advances the same and no location
250            // moves it.
251            let height = self.line_height();
252            for (_, out) in glyphs {
253                *out = convert(height);
254            }
255            return;
256        }
257        let coords = self.coords;
258        if coords.is_empty() {
259            return raw.run(self.num_glyphs, convert, glyphs);
260        }
261        self.v_advance_batched_varied(raw, coords, convert, glyphs)
262    }
263
264    /// Returns the y coordinate of `glyph`'s vertical origin, in font units.
265    ///
266    /// The x coordinate is half its advance width. See
267    /// [`v_origin_y_exact`](Self::v_origin_y_exact) for the value before it is
268    /// narrowed to `f32`.
269    #[inline]
270    pub fn v_origin_y(&self, glyph: GlyphId) -> f32 {
271        self.v_origin_y_exact(glyph).to_f32()
272    }
273
274    /// Returns the exact y coordinate of `glyph`'s vertical origin.
275    #[inline]
276    pub fn v_origin_y_exact(&self, glyph: GlyphId) -> F48Dot16 {
277        let mut origin = F48Dot16::ZERO;
278        self.v_origin_y_batched(|value| value, core::iter::once((glyph, &mut origin)));
279        origin
280    }
281
282    /// Writes the vertical origin of each glyph in font units to its slot.
283    ///
284    /// `VORG` answers first, followed by the glyf top phantom point. Other
285    /// glyphs are centered in the horizontal line by their extents, or
286    /// placed on the ascender when extents are unavailable.
287    pub fn v_origin_y_batched<'o, V: 'o>(
288        &self,
289        convert: impl Fn(F48Dot16) -> V,
290        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
291    ) {
292        let mut line = None;
293        self.v_origin_y_batched_with(
294            |glyph, origin| {
295                convert(origin.unwrap_or_else(|| {
296                    self.origin_against_ink(glyph, *line.get_or_insert_with(|| self.origin_line()))
297                }))
298            },
299            |glyphs| {
300                let line = self.origin_line();
301                self.extents_batched(
302                    |extents| convert(Self::origin_from_extents(extents, line)),
303                    glyphs,
304                );
305            },
306            glyphs,
307        );
308    }
309
310    /// Returns where `glyph`'s ink sits, in font units.
311    ///
312    /// An empty glyph has zero extents. `None` means the glyph is unavailable
313    /// or its extents could not be read. A bitmap is measured from the font's
314    /// largest strike; [`scaled`](Self::scaled) metrics use the strike for
315    /// the scale's size.
316    ///
317    /// See [`extents_exact`](Self::extents_exact) for the values before they
318    /// are narrowed to `f32`.
319    #[inline]
320    pub fn extents(&self, glyph: GlyphId) -> Option<GlyphExtents<f32>> {
321        self.extents_exact(glyph).map(|extents| GlyphExtents {
322            x_bearing: extents.x_bearing.to_f32(),
323            y_bearing: extents.y_bearing.to_f32(),
324            width: extents.width.to_f32(),
325            height: extents.height.to_f32(),
326        })
327    }
328
329    /// Returns the exact extents of `glyph` in font units.
330    ///
331    /// See [`extents_batched`](Self::extents_batched) to compute several
332    /// glyphs' extents with one choice of source for the batch.
333    #[inline]
334    pub fn extents_exact(&self, glyph: GlyphId) -> Option<GlyphExtents<F48Dot16>> {
335        let mut extents = None;
336        self.extents_batched(|value| value, core::iter::once((glyph, &mut extents)));
337        extents
338    }
339
340    /// Writes where each glyph's ink sits to its slot, in order.
341    ///
342    /// Each measurement passes through `convert`, so a caller working in
343    /// another number type writes into that type directly.
344    ///
345    /// The sources are resolved once for the batch, because the available
346    /// tables depend on the font rather than on the glyph. Each glyph then
347    /// tries them in order until one provides extents.
348    pub fn extents_batched<'o, V: 'o>(
349        &self,
350        convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
351        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
352    ) {
353        // Nearly every font has one source, its outlines, and a run over
354        // them is kept free of the per-glyph dispatch below.
355        if let Some(base) = self.only_base_source() {
356            return self.extents_batched_one(base, convert, glyphs);
357        }
358        let sources = ExtentsSources::new(self);
359        if let Some(only) = sources.only() {
360            return self.extents_batched_one(only, convert, glyphs);
361        }
362        if sources.is_empty() {
363            // A font stating its glyphs in a way none of the order reads.
364            for (_, out) in glyphs {
365                *out = convert(None);
366            }
367            return;
368        }
369        self.extents_batched_mixed(&sources, convert, glyphs);
370    }
371
372    /// Returns the base outline source, where it is the only source in the
373    /// order that the font has.
374    ///
375    /// This needs no list of sources: the font's answers about its bitmap,
376    /// color and composite tables are kept, and the base is chosen as
377    /// [`OutlineGlyphs`] chooses it.
378    fn only_base_source(&self) -> Option<ExtentsSource<'a>> {
379        let font = self.font;
380        if font.has_layered_source_in(self.order) {
381            return None;
382        }
383        if let Some(type1) = font.type1_instance() {
384            return self
385                .order
386                .contains(GlyphSource::Type1)
387                .then_some(ExtentsSource::Type1(type1));
388        }
389        self.order.iter().find_map(|source| match source {
390            GlyphSource::Glyf => font
391                .selected_glyf_loca()
392                .map(|(glyf, loca)| ExtentsSource::Glyf(glyf, loca)),
393            GlyphSource::Cff2 => font
394                .charstrings(OutlineSource::Cff2)
395                .map(|cff| ExtentsSource::Cff(cff, OutlineSource::Cff2)),
396            GlyphSource::Cff => font
397                .charstrings(OutlineSource::Cff)
398                .map(|cff| ExtentsSource::Cff(cff, OutlineSource::Cff)),
399            _ => None,
400        })
401    }
402
403    /// Measures every glyph from the one source that answers for them all.
404    fn extents_batched_one<'o, V: 'o>(
405        &self,
406        source: ExtentsSource<'_>,
407        convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
408        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
409    ) {
410        match source {
411            ExtentsSource::Glyf(glyf, loca) => {
412                if self.coords.is_empty() {
413                    for (glyph, out) in glyphs {
414                        *out = convert(self.stated_glyf_extents(glyf, loca, glyph));
415                    }
416                } else {
417                    self.extents_batched_glyf_varied(convert, glyphs);
418                }
419            }
420            ExtentsSource::Cff(cff, source) => {
421                self.extents_batched_cff(cff, source, convert, glyphs)
422            }
423            ExtentsSource::Type1(font) => self.extents_batched_type1(font, convert, glyphs),
424            source => {
425                for (glyph, out) in glyphs {
426                    *out = convert(self.extents_from_source(&source, glyph));
427                }
428            }
429        }
430    }
431
432    /// Returns the bounding box in a `glyf` glyph's header, with its bearing
433    /// from `hmtx`.
434    ///
435    /// The glyph is not loaded. The header contains the box at the default
436    /// location, so this is used only at that location.
437    fn stated_glyf_extents(
438        &self,
439        glyf: &Glyf<'_>,
440        loca: &Loca<'_>,
441        glyph: GlyphId,
442    ) -> Option<GlyphExtents<F48Dot16>> {
443        if glyph.to_u32() >= self.num_glyphs {
444            return None;
445        }
446        let outline = loca.get_glyf(glyph, glyf).ok()?;
447        let Some(outline) = outline else {
448            return Some(GlyphExtents::default());
449        };
450        if outline.number_of_contours() == 0 {
451            return Some(GlyphExtents::default());
452        }
453        let mut extents = extents_from_corners(
454            F48Dot16::from_i32(outline.x_min() as i32),
455            F48Dot16::from_i32(outline.y_min() as i32),
456            F48Dot16::from_i32(outline.x_max() as i32),
457            F48Dot16::from_i32(outline.y_max() as i32),
458        );
459        extents.x_bearing = self
460            .h_metrics
461            .stated_side_bearing(glyph)
462            .unwrap_or(F48Dot16::ZERO);
463        Some(extents)
464    }
465
466    /// Returns the bounding box of a charstring.
467    fn cff_extents(
468        &self,
469        cff: &CffFontRef<'_>,
470        source: OutlineSource,
471        glyph: GlyphId,
472    ) -> Option<GlyphExtents<F48Dot16>> {
473        let index = cff.subfont_index(glyph)?;
474        let subfont = self.font.cff_subfont(source, index)?;
475        let bounds = cff.evaluate_extents(&subfont, glyph, self.coords).ok()?;
476        Some(bounds.map(extents_from_bounds).unwrap_or_default())
477    }
478
479    /// Returns the bounding box of a Type 1 charstring.
480    fn type1_extents(
481        &self,
482        font: Type1Instance<'_>,
483        glyph: GlyphId,
484    ) -> Option<GlyphExtents<F48Dot16>> {
485        let bounds = font.evaluate_extents(glyph).ok()?;
486        Some(bounds.map(extents_from_bounds).unwrap_or_default())
487    }
488
489    /// Returns the box that `source` measures for `glyph`, or `None` if the
490    /// source does not contain the glyph.
491    fn extents_from_source(
492        &self,
493        source: &ExtentsSource<'_>,
494        glyph: GlyphId,
495    ) -> Option<GlyphExtents<F48Dot16>> {
496        match *source {
497            ExtentsSource::Bitmap(strikes) => {
498                // As in HarfBuzz, the strike selected for the size answers
499                // for every glyph, and a glyph it lacks is measured by the
500                // next source.
501                let bitmap = strikes.select(self.bitmap_ppem)?.get(glyph)?;
502                Some(exact_extents(bitmap.extents()))
503            }
504            ExtentsSource::Color(colors, outlines) => {
505                colors.extents(glyph, outlines).map(exact_extents)
506            }
507            ExtentsSource::Varc(outlines) => {
508                // Only the composites: the outlines underneath them answer
509                // for themselves, through their own source.
510                if !outlines.varc_covers(glyph) {
511                    return None;
512                }
513                Some(exact_extents(outlines.get(glyph)?.extents().ok()?))
514            }
515            ExtentsSource::Glyf(glyf, loca) => {
516                if self.coords.is_empty() {
517                    return self.stated_glyf_extents(glyf, loca, glyph);
518                }
519                if glyph.to_u32() >= self.num_glyphs {
520                    return None;
521                }
522                let context = self.font;
523                varied_control_box(&context, glyph)
524            }
525            ExtentsSource::Cff(cff, source) => self.cff_extents(cff, source, glyph),
526            ExtentsSource::Type1(font) => self.type1_extents(font, glyph),
527        }
528    }
529
530    /// Runs [`extents_batched`](Self::extents_batched) over more than one
531    /// source.
532    ///
533    /// Marked `#[inline(never)]` because fonts with bitmaps or color are
534    /// uncommon, and the loop over outlines alone should not include this
535    /// code.
536    #[inline(never)]
537    fn extents_batched_mixed<'o, V: 'o>(
538        &self,
539        sources: &ExtentsSources<'_>,
540        convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
541        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
542    ) {
543        for (glyph, out) in glyphs {
544            let extents = if glyph.to_u32() < self.num_glyphs {
545                sources
546                    .iter()
547                    .find_map(|source| self.extents_from_source(source, glyph))
548            } else {
549                None
550            };
551            *out = convert(extents);
552        }
553    }
554
555    /// Returns the line this font's glyphs stack by, where it provides one.
556    #[inline]
557    pub(crate) fn line_extents(&self) -> Option<LineExtents<F48Dot16>> {
558        self.global.h_line().map(|line| line.extents())
559    }
560
561    /// Returns the vertical origin of `glyph` from `VORG`, in font units.
562    ///
563    /// Returns `None` if the font has no `VORG`, as for every font with `glyf`
564    /// outlines.
565    #[cfg(test)]
566    pub(crate) fn stated_v_origin(&self, glyph: GlyphId) -> Option<F48Dot16> {
567        let vorg = self.font.vorg()?;
568        let origin = F48Dot16::from_i32(vorg.vertical_origin_y(glyph) as i32);
569        let delta = if self.coords.is_empty() {
570            F48Dot16::ZERO
571        } else {
572            self.font
573                .vvar()
574                .and_then(|vvar| {
575                    vvar.v_origin_y_delta_with_scalars(glyph, self.coords, self.font.vvar_scalars())
576                })
577                .unwrap_or(F48Dot16::ZERO)
578        };
579        Some(origin.saturating_add(delta))
580    }
581
582    /// The top side bearing `vmtx` states for `glyph`, before any location.
583    ///
584    /// `None` for a font that states none and past the last glyph one covers.
585    #[cfg(test)]
586    pub(crate) fn stated_v_bearing(&self, glyph: GlyphId) -> Option<F48Dot16> {
587        self.font.v_metric_records().stated_side_bearing(glyph)
588    }
589
590    /// The top phantom point of a glyf glyph with its vertical metrics.
591    /// Loading the outline also handles composite `USE_MY_METRICS` and gvar.
592    #[cfg(test)]
593    pub(crate) fn glyf_v_origin(&self, glyph: GlyphId) -> Option<F48Dot16> {
594        if self.font.v_metric_records().is_empty() || self.font.selected_glyf_loca().is_none() {
595            return None;
596        }
597        let context = self.font;
598        glyf_v_origin_from_context(
599            &context,
600            &OutlineScaleF32::new(None, self.units_per_em),
601            glyph,
602        )
603    }
604
605    /// Selects the source of vertical origins once for the whole run.
606    ///
607    /// `fallback` receives the whole run when neither table answers. A
608    /// malformed glyf glyph can still reach `convert` with `None`.
609    pub(crate) fn v_origin_y_batched_with<'o, V: 'o, I>(
610        &self,
611        mut convert: impl FnMut(GlyphId, Option<F48Dot16>) -> V,
612        fallback: impl FnOnce(I),
613        glyphs: I,
614    ) where
615        I: Iterator<Item = (GlyphId, &'o mut V)>,
616    {
617        if let Some(vorg) = self.font.vorg() {
618            let vvar = if self.coords.is_empty() {
619                None
620            } else {
621                self.font.vvar()
622            };
623            let scalars = if vvar.is_some() {
624                self.font.vvar_scalars()
625            } else {
626                &[]
627            };
628            for (glyph, out) in glyphs {
629                let origin = F48Dot16::from_i32(vorg.vertical_origin_y(glyph) as i32);
630                let delta = vvar
631                    .and_then(|vvar| {
632                        vvar.v_origin_y_delta_with_scalars(glyph, self.coords, scalars)
633                    })
634                    .unwrap_or(F48Dot16::ZERO);
635                *out = convert(glyph, Some(origin.saturating_add(delta)));
636            }
637            return;
638        }
639        if !self.font.v_metric_records().is_empty() && self.font.selected_glyf_loca().is_some() {
640            let context = self.font;
641            let scale = OutlineScaleF32::new(None, self.units_per_em);
642            for (glyph, out) in glyphs {
643                *out = convert(glyph, glyf_v_origin_from_context(&context, &scale, glyph));
644            }
645            return;
646        }
647        fallback(glyphs);
648    }
649
650    /// Returns `true` where the font provides vertical metrics of its own.
651    ///
652    /// One that does not stacks its glyphs by the line, which is the only
653    /// part of a vertical advance a caller can supply.
654    #[inline]
655    pub(crate) fn states_vertical_advances(&self) -> bool {
656        !self.font.v_metric_records().is_empty()
657    }
658
659    pub(crate) fn origin_line(&self) -> LineExtents<F48Dot16> {
660        self.line_extents().unwrap_or_else(|| {
661            let em = self.units_per_em as f64;
662            let ascender = F48Dot16::from_f64(em * 0.8);
663            LineExtents {
664                ascender,
665                descender: ascender - F48Dot16::from_i32(self.units_per_em as i32),
666            }
667        })
668    }
669
670    fn origin_against_ink(&self, glyph: GlyphId, line: LineExtents<F48Dot16>) -> F48Dot16 {
671        Self::origin_from_extents(self.extents_exact(glyph), line)
672    }
673
674    fn origin_from_extents(
675        extents: Option<GlyphExtents<F48Dot16>>,
676        line: LineExtents<F48Dot16>,
677    ) -> F48Dot16 {
678        let Some(extents) = extents else {
679            return line.ascender;
680        };
681        let height = line.ascender - line.descender;
682        extents.y_bearing + F48Dot16::from_bits((height - extents.height).to_bits() >> 1)
683    }
684
685    /// The varied half of [`extents_batched`](Self::extents_batched).
686    ///
687    /// A location moves the points, and the box a glyph states is the one it
688    /// has at the default location, so the answer comes from loading the
689    /// outline. Floating-point deltas are kept until the four bounds are
690    /// measured; the scale decides how to round them.
691    /// `#[inline(never)]` because that pulls in the loader, whose size no
692    /// unvaried measurement should pay for.
693    #[inline(never)]
694    fn extents_batched_glyf_varied<'o, V: 'o>(
695        &self,
696        convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
697        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
698    ) {
699        let context = self.font;
700        for (glyph, out) in glyphs {
701            *out = convert(
702                (glyph.to_u32() < self.num_glyphs)
703                    .then(|| varied_control_box(&context, glyph))
704                    .flatten(),
705            );
706        }
707    }
708
709    /// The charstring half of [`extents_batched`](Self::extents_batched).
710    ///
711    /// A charstring states no box, so reading one means running it.
712    /// `#[inline(never)]` for the same reason as the varied arm.
713    #[inline(never)]
714    fn extents_batched_cff<'o, V: 'o>(
715        &self,
716        cff: &CffFontRef<'_>,
717        source: OutlineSource,
718        convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
719        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
720    ) {
721        for (glyph, out) in glyphs {
722            *out = convert(self.cff_extents(cff, source, glyph));
723        }
724    }
725
726    /// The Type 1 half of [`extents_batched`](Self::extents_batched).
727    ///
728    /// `#[inline(never)]` for the same reason as
729    /// [`h_advance_batched_type1`](Self::h_advance_batched_type1).
730    #[inline(never)]
731    fn extents_batched_type1<'o, V: 'o>(
732        &self,
733        font: Type1Instance<'_>,
734        convert: impl Fn(Option<GlyphExtents<F48Dot16>>) -> V,
735        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
736    ) {
737        for (glyph, out) in glyphs {
738            *out = convert(self.type1_extents(font, glyph));
739        }
740    }
741
742    /// The height a glyph advances where the font has no `vmtx`.
743    ///
744    /// The extent of the line, without the gap after it, as HarfBuzz and
745    /// FreeType both take it. A font that states no line at all measures one
746    /// em.
747    fn line_height(&self) -> F48Dot16 {
748        match self.global.h_line() {
749            Some(line) => line.ascender - line.descender,
750            None => F48Dot16::from_i32(self.units_per_em as i32),
751        }
752    }
753
754    /// The varied half of [`v_advance_batched`](Self::v_advance_batched).
755    ///
756    /// `#[inline(never)]` for the same reason as the horizontal one.
757    #[inline(never)]
758    fn v_advance_batched_varied<'o, V: 'o>(
759        &self,
760        raw: &GlyphMetricRecords<'_>,
761        coords: &[F2Dot14],
762        convert: impl Fn(F48Dot16) -> V,
763        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
764    ) {
765        // Ask the table that answers directly, and stop there if it does.
766        if let Some(vvar) = self.font.vvar() {
767            let scalars = self.font.vvar_scalars();
768            return raw.run_varied(
769                self.num_glyphs,
770                |gid| {
771                    vvar.advance_delta_with_scalars(gid, coords, scalars)
772                        .unwrap_or(F48Dot16::ZERO)
773                },
774                convert,
775                glyphs,
776            );
777        }
778        // Without `VVAR`, the phantom points answer. The vertical pair runs
779        // downward from the origin, so the height is the top less the
780        // bottom, the reverse of the width.
781        if let (Some(gvar), Some((glyf, loca))) =
782            (self.font.selected_gvar(), self.font.selected_glyf_loca())
783        {
784            let scalars = self.font.gvar_scalars();
785            return raw.run_varied(
786                self.num_glyphs,
787                |gid| {
788                    // A glyph the table says nothing readable about does
789                    // not move.
790                    gvar.phantom_point_deltas_with_scalars(glyf, loca, coords, scalars, gid)
791                        .map_or(F48Dot16::ZERO, |deltas| {
792                            (deltas[2].y - deltas[3].y).to_f48dot16()
793                        })
794                },
795                convert,
796                glyphs,
797            );
798        }
799        // A location, but nothing stating what it changes.
800        raw.run(self.num_glyphs, convert, glyphs)
801    }
802
803    /// The varied half of [`h_advance_batched`](Self::h_advance_batched).
804    ///
805    /// `#[inline(never)]` because reading `gvar` pulls in a large amount of
806    /// code, whose size would otherwise be charged to every unvaried
807    /// measurement.
808    #[inline(never)]
809    fn h_advance_batched_varied<'o, V: 'o>(
810        &self,
811        raw: &GlyphMetricRecords<'_>,
812        coords: &[F2Dot14],
813        convert: impl Fn(F48Dot16) -> V,
814        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
815    ) {
816        // Ask the table that answers directly, and stop there if it does.
817        if let Some(hvar) = self.font.hvar() {
818            let scalars = self.font.hvar_scalars();
819            return raw.run_varied(
820                self.num_glyphs,
821                |gid| {
822                    hvar.advance_delta_with_scalars(gid, coords, scalars)
823                        .unwrap_or(F48Dot16::ZERO)
824                },
825                convert,
826                glyphs,
827            );
828        }
829        // Without `HVAR`, the answer comes from the phantom points on the
830        // outline: an advance spans the two horizontal ones, so a change in
831        // it is a change in that span.
832        if let (Some(gvar), Some((glyf, loca))) =
833            (self.font.selected_gvar(), self.font.selected_glyf_loca())
834        {
835            let scalars = self.font.gvar_scalars();
836            return raw.run_varied(
837                self.num_glyphs,
838                |gid| {
839                    // A glyph the table says nothing readable about does
840                    // not move.
841                    gvar.phantom_point_deltas_with_scalars(glyf, loca, coords, scalars, gid)
842                        .map_or(F48Dot16::ZERO, |deltas| {
843                            (deltas[1].x - deltas[0].x).to_f48dot16()
844                        })
845                },
846                convert,
847                glyphs,
848            );
849        }
850        // A location, but nothing stating what it changes.
851        raw.run(self.num_glyphs, convert, glyphs)
852    }
853
854    /// Reads widths from standalone CFF charstrings.
855    #[inline(never)]
856    fn h_advance_batched_standalone_cff<'o, V: 'o>(
857        &self,
858        cff: &CffFontRef<'_>,
859        convert: impl Fn(F48Dot16) -> V,
860        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
861    ) {
862        for (glyph, out) in glyphs {
863            let width = (glyph.to_u32() < self.num_glyphs)
864                .then(|| cff.subfont_index(glyph))
865                .flatten()
866                .and_then(|index| self.font.cff_subfont(OutlineSource::Cff, index))
867                .and_then(|subfont| {
868                    cff.evaluate_width(&subfont, glyph, self.coords)
869                        .ok()
870                        .flatten()
871                })
872                .map(|width| width.to_f48dot16())
873                .unwrap_or(F48Dot16::ZERO)
874                .max(F48Dot16::ZERO);
875            *out = convert(width);
876        }
877    }
878
879    /// The Type 1 half of [`h_advance_batched`](Self::h_advance_batched).
880    ///
881    /// A Type 1 charstring states its own width, so reading one means
882    /// running it. `#[inline(never)]` because that has nothing in common with
883    /// reading a table, and no `sfnt` should pay for its presence.
884    #[inline(never)]
885    fn h_advance_batched_type1<'o, V: 'o>(
886        &self,
887        font: Type1Instance<'_>,
888        convert: impl Fn(F48Dot16) -> V,
889        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
890    ) {
891        /// The width is all that is wanted, so the outline is discarded.
892        struct WidthOnly;
893
894        impl CommandSink for WidthOnly {
895            fn move_to(&mut self, _x: Fixed, _y: Fixed) {}
896            fn line_to(&mut self, _x: Fixed, _y: Fixed) {}
897            fn curve_to(
898                &mut self,
899                _cx0: Fixed,
900                _cy0: Fixed,
901                _cx1: Fixed,
902                _cy1: Fixed,
903                _x: Fixed,
904                _y: Fixed,
905            ) {
906            }
907            fn close(&mut self) {}
908        }
909
910        // A charstring states its width in its own space, which the font
911        // matrix maps to design units. No size is applied: that is the
912        // caller's to do.
913        let transform = font.font().transform(None);
914        for (gid, out) in glyphs {
915            // A charstring states its width before it draws anything, so one
916            // that states none is malformed and measures as nothing.
917            let width = font
918                .evaluate_charstring(gid, &mut WidthOnly)
919                .ok()
920                .flatten()
921                .map(|width| transform.transform_h_metric(width).to_f48dot16())
922                .unwrap_or(F48Dot16::ZERO)
923                .max(F48Dot16::ZERO);
924            *out = convert(width);
925        }
926    }
927}
928
929impl<'a> GlyphMetricRecords<'a> {
930    /// Returns `true` if the table states no metrics.
931    #[inline]
932    pub(crate) fn is_empty(&self) -> bool {
933        self.metrics.is_empty()
934    }
935
936    /// Writes the table's value for each glyph.
937    ///
938    /// A stored advance is a `u16`, so this needs neither the saturating sum
939    /// nor the lower bound of [`run_varied`](Self::run_varied).
940    #[inline]
941    fn run<'o, V: 'o>(
942        &self,
943        num_glyphs: u32,
944        convert: impl Fn(F48Dot16) -> V,
945        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
946    ) {
947        for (gid, out) in glyphs {
948            *out = convert(self.stated_advance(num_glyphs, gid));
949        }
950    }
951
952    /// Writes each glyph's advance with `delta` applied.
953    ///
954    /// `delta` is settled before the run, leaving only the branches that turn
955    /// on the glyph. The sum saturates rather than wrapping, and is clamped
956    /// to zero before conversion or scaling.
957    ///
958    /// Glyphs beyond the end of the font measure zero here, as they do
959    /// unvaried.
960    #[inline]
961    fn run_varied<'o, V: 'o>(
962        &self,
963        num_glyphs: u32,
964        delta: impl Fn(GlyphId) -> F48Dot16,
965        convert: impl Fn(F48Dot16) -> V,
966        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
967    ) {
968        for (gid, out) in glyphs {
969            // Glyphs beyond the end of the font measure zero, and no
970            // delta applies to them. The index map clamps an out of range
971            // glyph onto its last entry, so asking would answer with some
972            // other glyph's delta.
973            let advance = if gid.to_u32() < num_glyphs {
974                self.stated_advance(num_glyphs, gid)
975                    .saturating_add(delta(gid))
976                    .max(F48Dot16::ZERO)
977            } else {
978                F48Dot16::ZERO
979            };
980            *out = convert(advance);
981        }
982    }
983
984    /// Returns the advance the table states for `glyph`, before any location.
985    ///
986    /// A glyph past the end of the font advances by nothing. The last record
987    /// covers every glyph after it, and clamping the index rather than
988    /// falling back on a failed lookup keeps that bound provable, so a run
989    /// compiles without a branch per glyph.
990    ///
991    /// `self.metrics` is never empty here: a caller settles that font before
992    /// the run starts.
993    #[inline]
994    pub(crate) fn stated_advance(&self, num_glyphs: u32, glyph: GlyphId) -> F48Dot16 {
995        if glyph.to_u32() >= num_glyphs {
996            return F48Dot16::ZERO;
997        }
998        let index = (glyph.to_u32() as usize).min(self.metrics.len().saturating_sub(1));
999        match self.metrics.get(index) {
1000            Some(metric) => F48Dot16::from_i32(metric.advance() as i32),
1001            None => F48Dot16::ZERO,
1002        }
1003    }
1004
1005    /// Returns the side bearing stated for `glyph`, including the bare
1006    /// bearings following the last long metric.
1007    pub(crate) fn stated_side_bearing(&self, glyph: GlyphId) -> Option<F48Dot16> {
1008        let index = glyph.to_u32() as usize;
1009        if let Some(metric) = self.metrics.get(index) {
1010            return Some(F48Dot16::from_i32(metric.side_bearing() as i32));
1011        }
1012        let tail = index.checked_sub(self.metrics.len())?;
1013        Some(F48Dot16::from_i32(self.bearings.get(tail)?.get() as i32))
1014    }
1015}
1016
1017/// A table that can measure a glyph's ink.
1018#[derive(Clone, Copy)]
1019enum ExtentsSource<'a> {
1020    /// A bitmap table. The strike selected for the size measures every glyph.
1021    Bitmap(BitmapStrikes<'a>),
1022    /// A `COLR` table of either version.
1023    /// The outlines provide the clip glyphs.
1024    Color(ColorGlyphs<'a>, OutlineGlyphs<'a>),
1025    /// A `VARC` table, used for the glyphs it covers.
1026    Varc(OutlineGlyphs<'a>),
1027    Glyf(&'a Glyf<'a>, &'a Loca<'a>),
1028    Cff(&'a CffFontRef<'a>, OutlineSource),
1029    Type1(Type1Instance<'a>),
1030}
1031
1032/// The sources available in a font, in the order they are tried.
1033///
1034/// The three bitmap tables, `COLR` and `VARC` take an entry each, and the
1035/// four outline sources share one, so six entries always suffice.
1036struct ExtentsSources<'a> {
1037    items: [Option<ExtentsSource<'a>>; MAX_EXTENTS_SOURCES],
1038    len: usize,
1039}
1040
1041/// The most entries [`ExtentsSources`] holds.
1042const MAX_EXTENTS_SOURCES: usize = 6;
1043
1044impl<'a> ExtentsSources<'a> {
1045    fn new(metrics: &GlyphMetrics<'a>) -> Self {
1046        let font = metrics.font;
1047        let mut sources = Self {
1048            items: [None; MAX_EXTENTS_SOURCES],
1049            len: 0,
1050        };
1051        // `VARC` composites are drawn over the base the order chooses, so
1052        // both come from one view of the outlines.
1053        let outlines = font.outline_glyphs_with(metrics.order);
1054        let mut base_taken = false;
1055        for format in metrics.order.iter() {
1056            let source = match format {
1057                GlyphSource::Sbix | GlyphSource::Cbdt | GlyphSource::Ebdt => {
1058                    let bitmaps = font.bitmap_strikes_with(GlyphSourceOrder::new(&[format]));
1059                    (!bitmaps.is_empty()).then_some(ExtentsSource::Bitmap(bitmaps))
1060                }
1061                GlyphSource::Colr => {
1062                    let colors = font.color_glyphs();
1063                    // Clips are outlines whatever the order says, so an
1064                    // order with no outline source clips with the font's
1065                    // default outlines, as `ColorGlyph::extents` does.
1066                    let clips = if outlines.is_empty() {
1067                        font.outline_glyphs()
1068                    } else {
1069                        outlines
1070                    };
1071                    (!colors.is_empty()).then_some(ExtentsSource::Color(colors, clips))
1072                }
1073                GlyphSource::Varc => outlines.has_varc().then_some(ExtentsSource::Varc(outlines)),
1074                GlyphSource::Glyf | GlyphSource::Cff2 | GlyphSource::Cff | GlyphSource::Type1 => {
1075                    // Only the base the outlines chose answers, at its own
1076                    // place in the order.
1077                    let base = outlines
1078                        .underlying_source()
1079                        .filter(|base| !base_taken && GlyphSource::from(*base) == format);
1080                    let Some(base) = base else {
1081                        continue;
1082                    };
1083                    base_taken = true;
1084                    match base {
1085                        OutlineSource::Glyf => font
1086                            .selected_glyf_loca()
1087                            .map(|(glyf, loca)| ExtentsSource::Glyf(glyf, loca)),
1088                        // The outlines found the charstrings, in the tables
1089                        // or standing alone.
1090                        OutlineSource::Cff2 | OutlineSource::Cff => {
1091                            outlines.cff_font().map(|cff| ExtentsSource::Cff(cff, base))
1092                        }
1093                        OutlineSource::Type1 => match font.kind() {
1094                            Kind::Type1(_) => font.type1_instance().map(ExtentsSource::Type1),
1095                            _ => None,
1096                        },
1097                        OutlineSource::Varc => None,
1098                    }
1099                }
1100            };
1101            if let Some(source) = source {
1102                sources.items[sources.len] = Some(source);
1103                sources.len += 1;
1104            }
1105        }
1106        sources
1107    }
1108
1109    fn is_empty(&self) -> bool {
1110        self.len == 0
1111    }
1112
1113    /// Returns the source if there is exactly one.
1114    fn only(&self) -> Option<ExtentsSource<'a>> {
1115        if self.len == 1 {
1116            self.items[0]
1117        } else {
1118            None
1119        }
1120    }
1121
1122    fn iter(&self) -> impl Iterator<Item = &ExtentsSource<'a>> {
1123        self.items[..self.len].iter().flatten()
1124    }
1125}
1126
1127/// Widens floating-point extents to the fixed-point type the metrics report.
1128fn exact_extents(extents: GlyphExtents<f32>) -> GlyphExtents<F48Dot16> {
1129    let exact = |value: f32| F48Dot16::from_f64(value as f64);
1130    GlyphExtents {
1131        x_bearing: exact(extents.x_bearing),
1132        y_bearing: exact(extents.y_bearing),
1133        width: exact(extents.width),
1134        height: exact(extents.height),
1135    }
1136}
1137
1138/// Returns the metrics of a font that states none.
1139pub(crate) fn empty() -> &'static GlyphMetricRecords<'static> {
1140    static EMPTY: GlyphMetricRecords<'static> = GlyphMetricRecords {
1141        metrics: &[],
1142        bearings: &[],
1143    };
1144    &EMPTY
1145}
1146
1147/// Turns the corners of a box into a bearing and a size.
1148///
1149/// The corners can be in either order. A reversed box still has a
1150/// non-negative size.
1151fn extents_from_corners(
1152    x_min: F48Dot16,
1153    y_min: F48Dot16,
1154    x_max: F48Dot16,
1155    y_max: F48Dot16,
1156) -> GlyphExtents<F48Dot16> {
1157    GlyphExtents::from(BoundingBox {
1158        x_min,
1159        y_min,
1160        x_max,
1161        y_max,
1162    })
1163}
1164
1165/// Turns a charstring's box into a bearing and a size.
1166fn extents_from_bounds(bounds: BoundingBox<Fixed>) -> GlyphExtents<F48Dot16> {
1167    extents_from_corners(
1168        bounds.x_min.to_f48dot16(),
1169        bounds.y_min.to_f48dot16(),
1170        bounds.x_max.to_f48dot16(),
1171        bounds.y_max.to_f48dot16(),
1172    )
1173}
1174
1175/// The box a loaded outline's points stay within.
1176///
1177/// The control points count, so this is what the glyph states as its own box
1178/// rather than the tighter box its curves keep to. A scale can round the
1179/// finished bounds after applying floating-point variation deltas.
1180fn control_box(points: &[Point<f32>]) -> Option<GlyphExtents<F48Dot16>> {
1181    let first = points.first()?;
1182    let (mut x_min, mut y_min) = (first.x, first.y);
1183    let (mut x_max, mut y_max) = (first.x, first.y);
1184    for point in &points[1..] {
1185        x_min = x_min.min(point.x);
1186        y_min = y_min.min(point.y);
1187        x_max = x_max.max(point.x);
1188        y_max = y_max.max(point.y);
1189    }
1190    let as_fixed = |value: f32| F48Dot16::from_f64(value as f64);
1191    Some(extents_from_corners(
1192        as_fixed(x_min),
1193        as_fixed(y_min),
1194        as_fixed(x_max),
1195        as_fixed(y_max),
1196    ))
1197}
1198
1199/// The box a glyph's points stay within, at the location `tables` states.
1200///
1201/// How much scratch space a glyph takes is known only once its plan is read,
1202/// and it is finished with by the time this returns, so the space comes from
1203/// [`with_scratch`] rather than an allocation per glyph.
1204fn varied_control_box<'a>(
1205    context: &'a dyn OutlineContext<'a>,
1206    glyph: GlyphId,
1207) -> Option<GlyphExtents<F48Dot16>> {
1208    let plan = OutlinePlan::new(context, glyph).ok()?;
1209    let scale = OutlineScaleF32::new(None, context.units_per_em());
1210    let lengths = plan.buffer_lengths::<OutlineScaleF32>();
1211    with_scratch(lengths.packed_len::<OutlineScaleF32>(), |block| {
1212        let buffers = Buffers::from_bytes(block, &lengths).ok()?;
1213        let outline = plan.load(context, &scale, buffers, None).ok()?;
1214        Some(control_box(outline.points()).unwrap_or_default())
1215    })
1216}
1217
1218/// The loaded top phantom point, retaining fractional gvar deltas.
1219fn glyf_v_origin_from_context<'a>(
1220    context: &'a dyn OutlineContext<'a>,
1221    scale: &OutlineScaleF32,
1222    glyph: GlyphId,
1223) -> Option<F48Dot16> {
1224    let plan = OutlinePlan::new(context, glyph).ok()?;
1225    let lengths = plan.buffer_lengths::<OutlineScaleF32>();
1226    with_scratch(lengths.packed_len::<OutlineScaleF32>(), |block| {
1227        let buffers = Buffers::from_bytes(block, &lengths).ok()?;
1228        let outline = plan.load(context, scale, buffers, None).ok()?;
1229        Some(F48Dot16::from_f64(outline.phantom_points()[2].y as f64))
1230    })
1231}
1232
1233#[cfg(test)]
1234mod tests {
1235    use super::*;
1236    use crate::{
1237        model::{
1238            glyph::outline::{ControlBoundsSink, NullSink},
1239            Blob, NormalizedCoord,
1240        },
1241        tables::glyf::outline::{Outline, OutlineTables, PathContourStart, Unscaled},
1242        FontRef, TableProvider,
1243    };
1244    use alloc::{sync::Arc, vec, vec::Vec};
1245    use std::sync::Mutex;
1246    use types::Tag;
1247
1248    const STATIC: &[u8] = font_test_data::TINOS_SUBSET;
1249
1250    #[test]
1251    fn extended_metrics_in_the_cached_font_model() {
1252        use font_test_data::extended::metrics_font;
1253        for outline in [None, Some(*b"glyf"), Some(*b"GLYF")] {
1254            for legacy in [false, true] {
1255                let data = metrics_font(true, legacy, outline);
1256                let font = Font::new(data, 0).unwrap();
1257                assert_eq!(font.num_glyphs(), 70002);
1258                assert_eq!(
1259                    font.tables().maxp_extended().unwrap().num_glyphs().to_u32(),
1260                    70002
1261                );
1262                assert_eq!(
1263                    font.tables().hhea_extended().unwrap().number_of_h_metrics(),
1264                    70000
1265                );
1266                assert_eq!(
1267                    font.tables()
1268                        .vhea_extended()
1269                        .unwrap()
1270                        .number_of_long_ver_metrics(),
1271                    70000
1272                );
1273                let metrics = font.glyph_metrics();
1274                for gid in [0, 65535, 65536, 69999] {
1275                    assert_eq!(
1276                        metrics.h_advance_exact(GlyphId::new(gid)),
1277                        F48Dot16::from_i32((1000 + gid % 100) as i32)
1278                    );
1279                    assert_eq!(
1280                        metrics.v_advance_exact(GlyphId::new(gid)),
1281                        F48Dot16::from_i32((1200 + gid % 100) as i32)
1282                    );
1283                }
1284                assert_eq!(
1285                    metrics.h_advance_exact(GlyphId::new(70001)),
1286                    F48Dot16::from_i32(1099)
1287                );
1288                assert_eq!(
1289                    metrics.v_advance_exact(GlyphId::new(70001)),
1290                    F48Dot16::from_i32(1299)
1291                );
1292                assert_eq!(metrics.h_advance_exact(GlyphId::new(70002)), F48Dot16::ZERO);
1293            }
1294        }
1295    }
1296
1297    #[test]
1298    fn extended_metrics_from_callback_tables_are_cached() {
1299        let data = font_test_data::extended::metrics_font(true, true, None);
1300        let asked = Arc::new(Mutex::new(Vec::new()));
1301        let requests = asked.clone();
1302        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1303            requests.lock().unwrap().push(tag);
1304            let font = FontRef::new(&data).ok()?;
1305            Some(Blob::from(font.table_data(tag)?.as_bytes().to_vec()))
1306        });
1307        let font = Font::new(source, 0).unwrap();
1308        for _ in 0..2 {
1309            let metrics = font.glyph_metrics();
1310            assert_eq!(font.num_glyphs(), 70002);
1311            assert_eq!(
1312                metrics.h_advance_exact(GlyphId::new(65536)),
1313                F48Dot16::from_i32(1036)
1314            );
1315            assert_eq!(
1316                metrics.v_advance_exact(GlyphId::new(65536)),
1317                F48Dot16::from_i32(1236)
1318            );
1319        }
1320        let asked = asked.lock().unwrap();
1321        for tag in [b"MAXP", b"HHEA", b"HMTX", b"VHEA", b"VMTX"] {
1322            assert_eq!(
1323                asked.iter().filter(|&&seen| seen == Tag::new(tag)).count(),
1324                1
1325            );
1326        }
1327        for tag in [b"maxp", b"hhea", b"hmtx", b"vhea", b"vmtx"] {
1328            assert!(!asked.contains(&Tag::new(tag)));
1329        }
1330    }
1331
1332    #[test]
1333    fn extended_outline_extents_in_the_cached_font_model() {
1334        for long_loca in [false, true] {
1335            let data = font_test_data::extended::outlines_font(true, true, true, long_loca);
1336            let font = Font::new(data, 0).unwrap();
1337            let metrics = font.glyph_metrics();
1338            for gid in [1, 65536] {
1339                let extents = metrics.extents_exact(GlyphId::new(gid)).unwrap();
1340                assert_eq!(extents.width, F48Dot16::from_i32(80));
1341                assert_eq!(extents.height, F48Dot16::from_i32(80));
1342            }
1343        }
1344    }
1345
1346    #[test]
1347    fn extended_gvar_metrics_and_extents_in_the_cached_font_model() {
1348        for long_offsets in [false, true] {
1349            for legacy in [false, true] {
1350                let data =
1351                    font_test_data::extended::variable_outlines_font(true, legacy, long_offsets);
1352                let font = Font::new(data, 0).unwrap();
1353                assert_eq!(font.tables().selected_gvar().unwrap().glyph_count(), 65537);
1354                for coord in [0.0, 0.5, 1.0] {
1355                    let instance = at(&font, coord);
1356                    let metrics = instance.glyph_metrics();
1357                    for (gid, delta, shift) in [(65536, 100.0, 40.0), (1, 60.0, 60.0)] {
1358                        let id = GlyphId::new(gid);
1359                        assert_eq!(
1360                            metrics.h_advance_exact(id),
1361                            F48Dot16::from_f64(1000.0 + delta * coord as f64)
1362                        );
1363                        let v_delta = if gid == 65536 { 60.0 } else { 0.0 };
1364                        assert_eq!(
1365                            metrics.v_advance_exact(id),
1366                            F48Dot16::from_f64(1200.0 + v_delta * coord as f64)
1367                        );
1368                        let extents = metrics.extents_exact(id).unwrap();
1369                        assert_eq!(
1370                            extents.x_bearing,
1371                            F48Dot16::from_f64(shift * coord as f64),
1372                            "gid {gid}, coord {coord}"
1373                        );
1374                        assert_eq!(extents.width, F48Dot16::from_i32(80));
1375                        assert_eq!(extents.height, F48Dot16::from_i32(80));
1376                    }
1377                }
1378            }
1379        }
1380    }
1381
1382    /// Has both `HVAR` and `gvar`, so it can answer either way.
1383    const VAR: &[u8] = font_test_data::VAZIRMATN_VAR;
1384    /// Eleven glyphs but one long metric, so ten of them are in the tail.
1385    const TAIL: &[u8] = font_test_data::MATERIAL_SYMBOLS_SUBSET;
1386    /// Charstring outlines, with no `glyf` to answer ahead of them.
1387    const CFF: &[u8] = font_test_data::NOTO_SANS_JP_CFF;
1388    const CFF2: &[u8] = font_test_data::ift::CFF2_FONT;
1389
1390    #[test]
1391    fn vertical_origins_in_design_units_match_their_sources_and_batch() {
1392        for data in [
1393            font_test_data::VORG,
1394            font_test_data::MPLUS1CODE_VERTICAL_SUBSET,
1395            STATIC,
1396        ] {
1397            let font = Font::new(data, 0).unwrap();
1398            let metrics = font.glyph_metrics();
1399            let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
1400            let mut batched = vec![F48Dot16::ZERO; glyphs.len()];
1401            metrics.v_origin_y_batched(
1402                |origin| origin,
1403                glyphs.iter().copied().zip(batched.iter_mut()),
1404            );
1405            for (glyph, origin) in glyphs.into_iter().zip(batched) {
1406                assert_eq!(origin, metrics.v_origin_y_exact(glyph), "glyph {glyph}");
1407                assert_eq!(origin.to_f32(), metrics.v_origin_y(glyph));
1408                if let Some(stated) = metrics.stated_v_origin(glyph) {
1409                    assert_eq!(origin, stated);
1410                } else if let Some(phantom) = metrics.glyf_v_origin(glyph) {
1411                    assert_eq!(origin, phantom);
1412                }
1413            }
1414        }
1415    }
1416
1417    #[test]
1418    fn a_width_is_what_hmtx_stores() {
1419        // A font with a tail, so this covers both halves of `hmtx`.
1420        let font = Font::new(TAIL, 0).unwrap();
1421        let direct = FontRef::new(TAIL).unwrap();
1422        let hmtx = direct.hmtx().unwrap();
1423        let num_glyphs = direct.maxp().unwrap().num_glyphs();
1424        assert!(num_glyphs > 1);
1425        for gid in 0..num_glyphs as u32 {
1426            let expected = hmtx.advance(GlyphId::new(gid)).unwrap();
1427            assert_eq!(
1428                font.glyph_metrics().h_advance_exact(GlyphId::new(gid)),
1429                F48Dot16::from_i32(expected as i32)
1430            );
1431        }
1432    }
1433
1434    #[test]
1435    fn a_glyph_past_the_end_advances_by_nothing() {
1436        // Deliberately unlike `Hmtx::advance`, which clamps to the last long
1437        // metric and so reports a width for a glyph the font does not have.
1438        // HarfBuzz returns zero once a font has metrics at all, and a shaper
1439        // handed a bad glyph id is better served by nothing than by whatever
1440        // the last real glyph happened to measure.
1441        let font = Font::new(STATIC, 0).unwrap();
1442        let direct = FontRef::new(STATIC).unwrap();
1443        let past = GlyphId::new(direct.maxp().unwrap().num_glyphs() as u32);
1444
1445        assert_eq!(font.glyph_metrics().h_advance_exact(past), F48Dot16::ZERO);
1446        assert!(direct.hmtx().unwrap().advance(past).unwrap() > 0);
1447        assert_eq!(
1448            font.glyph_metrics().h_advance_exact(GlyphId::new(60000)),
1449            F48Dot16::ZERO
1450        );
1451    }
1452
1453    #[test]
1454    fn a_font_with_no_widths_gives_every_glyph_half_an_em() {
1455        let font = Font::new(font_test_data::NAMES_ONLY, 0).unwrap();
1456        // That font has no `head` either, so the em is 1000.
1457        assert_eq!(
1458            font.glyph_metrics().h_advance_exact(GlyphId::new(1)),
1459            F48Dot16::from_i32(500)
1460        );
1461    }
1462
1463    #[test]
1464    fn glyphs_past_the_long_metrics_share_the_last_advance() {
1465        // `hmtx` stores a full metric for the first `numberOfHMetrics`
1466        // glyphs and a bare side bearing for the rest, which all advance by
1467        // the last stated width. That tail is how a font records a run of
1468        // glyphs of equal width, and it is most of this font.
1469        let font = Font::new(TAIL, 0).unwrap();
1470        let direct = FontRef::new(TAIL).unwrap();
1471        let num_glyphs = direct.maxp().unwrap().num_glyphs() as u32;
1472        let num_long = direct.hhea().unwrap().number_of_h_metrics() as u32;
1473        assert!(num_long < num_glyphs, "this font has no tail to test");
1474
1475        let last = font
1476            .glyph_metrics()
1477            .h_advance_exact(GlyphId::new(num_long - 1));
1478        assert!(last > F48Dot16::ZERO);
1479        for gid in num_long..num_glyphs {
1480            assert_eq!(
1481                font.glyph_metrics().h_advance_exact(GlyphId::new(gid)),
1482                last,
1483                "glyph {gid} is in the tail and should share the last advance"
1484            );
1485        }
1486        // And the tail stops at the end of the font rather than running on.
1487        assert_eq!(
1488            font.glyph_metrics()
1489                .h_advance_exact(GlyphId::new(num_glyphs)),
1490            F48Dot16::ZERO
1491        );
1492    }
1493
1494    #[test]
1495    fn glyphs_beyond_the_font_measure_zero_at_any_location() {
1496        // The delta set index map clamps an out of range glyph onto its
1497        // last entry, so without a check these would take the last real
1498        // glyph's delta and report an advance, and a negative one at that.
1499        let font = Font::new(VAR, 0).unwrap();
1500        let num_glyphs = font.num_glyphs();
1501        let instance = at(&font, -1.0);
1502        assert!(
1503            instance
1504                .glyph_metrics()
1505                .h_advance_exact(GlyphId::new(num_glyphs - 1))
1506                != font
1507                    .glyph_metrics()
1508                    .h_advance_exact(GlyphId::new(num_glyphs - 1)),
1509            "the last real glyph should move, or this proves nothing"
1510        );
1511        for gid in [num_glyphs, num_glyphs + 1, u32::MAX] {
1512            let gid = GlyphId::new(gid);
1513            assert_eq!(
1514                instance.glyph_metrics().h_advance_exact(gid),
1515                F48Dot16::ZERO,
1516                "glyph {gid} is beyond the font"
1517            );
1518            assert_eq!(font.glyph_metrics().h_advance_exact(gid), F48Dot16::ZERO);
1519        }
1520    }
1521
1522    /// Has `vmtx`, `VVAR` and one axis, so it can vary vertically.
1523    const VERT_VAR: &[u8] = font_test_data::ift::CFF2_FONT;
1524    /// Has `vmtx`, `VVAR` and `gvar`, which disagree about the advance.
1525    const VERT: &[u8] = font_test_data::MPLUS1CODE_VERTICAL_SUBSET;
1526
1527    #[test]
1528    fn a_type1_glyph_stacks_by_the_line() {
1529        // Type 1 has no `vmtx`, so its glyphs take the line, which such a
1530        // font derives from its bounding box. Every glyph gets the same
1531        // height while their widths differ, and no location moves either.
1532        //
1533        // FreeType instead gives each Type 1 glyph 12/10 of its own ink
1534        // height. Nothing here follows it: one rule for every font without
1535        // vertical metrics is easier to predict than a per-glyph heuristic,
1536        // and HarfBuzz has no opinion because it does not read Type 1.
1537        let font = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA, 0).unwrap();
1538        let line = font.metrics().h_line().unwrap();
1539        let height = line.ascender - line.descender;
1540        assert!(height > F48Dot16::ZERO);
1541        let metrics = font.glyph_metrics();
1542        let mut widths = std::collections::HashSet::new();
1543        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1544            assert_eq!(metrics.v_advance_exact(gid), height, "glyph {gid}");
1545            widths.insert(metrics.h_advance_exact(gid).to_bits());
1546        }
1547        assert!(
1548            widths.len() > 1,
1549            "every glyph is the same width, so this proves nothing"
1550        );
1551    }
1552
1553    #[test]
1554    fn vertical_advances_come_from_vmtx() {
1555        let font = Font::new(VERT_VAR, 0).unwrap();
1556        let direct = FontRef::new(VERT_VAR).unwrap();
1557        let vmtx = direct.vmtx().unwrap();
1558        let metrics = font.glyph_metrics();
1559        let mut checked = 0;
1560        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1561            let Some(stated) = vmtx.advance(gid) else {
1562                continue;
1563            };
1564            assert_eq!(
1565                metrics.v_advance_exact(gid),
1566                F48Dot16::from_i32(stated as i32),
1567                "glyph {gid}"
1568            );
1569            checked += 1;
1570        }
1571        assert!(checked > 1, "this font states no vertical advances");
1572    }
1573
1574    #[test]
1575    fn vorg_reads_the_vvar_origin_delta_at_a_location() {
1576        // Pair the VORG fixture with a variable CFF2 font so both tables
1577        // answer. This VVAR fixture's origin deltas are zero, but requesting
1578        // the table proves the varied branch is taken.
1579        let asked = Arc::new(Mutex::new(Vec::new()));
1580        let calls = asked.clone();
1581        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1582            calls.lock().unwrap().push(tag);
1583            let data = if tag == Tag::new(b"VORG") {
1584                font_test_data::VORG
1585            } else {
1586                VERT_VAR
1587            };
1588            let font = FontRef::new(data).ok()?;
1589            Some(Blob::from(font.table_data(tag)?.as_bytes().to_vec()))
1590        });
1591        let font = Font::new(source, 0).unwrap();
1592        let instance = at(&font, -1.0);
1593        let gid = GlyphId::new(1);
1594        let vorg = FontRef::new(font_test_data::VORG).unwrap().vorg().unwrap();
1595        assert_eq!(
1596            instance.glyph_metrics().stated_v_origin(gid),
1597            Some(F48Dot16::from_i32(vorg.vertical_origin_y(gid) as i32))
1598        );
1599        assert!(asked_for(&asked, b"VVAR"));
1600    }
1601
1602    #[test]
1603    fn without_vmtx_glyphs_advance_by_the_line() {
1604        // A horizontal font stacks by the line it lays text on, so every
1605        // glyph gets the same height whatever its width.
1606        let font = Font::new(STATIC, 0).unwrap();
1607        let line = font.metrics().h_line().unwrap();
1608        let expected = line.ascender - line.descender;
1609        assert!(expected > F48Dot16::ZERO);
1610        let metrics = font.glyph_metrics();
1611        for gid in (0..font.num_glyphs().min(16)).map(GlyphId::new) {
1612            assert_eq!(metrics.v_advance_exact(gid), expected, "glyph {gid}");
1613        }
1614    }
1615
1616    #[test]
1617    fn a_location_moves_a_vertical_advance() {
1618        let font = Font::new(VERT, 0).unwrap();
1619        let instance = at(&font, -1.0);
1620        let (varied, plain) = (instance.glyph_metrics(), font.glyph_metrics());
1621        let moved = (0..font.num_glyphs())
1622            .map(GlyphId::new)
1623            .filter(|gid| varied.v_advance_exact(*gid) != plain.v_advance_exact(*gid))
1624            .count();
1625        assert!(moved > 0, "no glyph moved at the light end of the axis");
1626    }
1627
1628    #[test]
1629    fn phantom_points_measure_a_glyph_the_same_as_vvar() {
1630        // Both describe the same font, so where the font is written
1631        // consistently they agree. Withholding `VVAR` drives the phantom
1632        // point path rather than reimplementing it alongside.
1633        let with = Font::new(VERT, 0).unwrap();
1634        let asked = Arc::new(Mutex::new(Vec::new()));
1635        let without = callback_font(VERT, asked.clone(), &[b"VVAR"]);
1636        let (a, b) = (at(&with, -1.0), at(&without, -1.0));
1637        let (ma, mb) = (a.glyph_metrics(), b.glyph_metrics());
1638        // The composite is the exception: it carries its component's metrics
1639        // through `USE_MY_METRICS`, and this font states a delta there that
1640        // its own `vmtx` has already counted, so the two differ by it.
1641        let composite = GlyphId::new(2);
1642        for gid in (0..with.num_glyphs()).map(GlyphId::new) {
1643            if gid == composite {
1644                continue;
1645            }
1646            assert_eq!(
1647                ma.v_advance_exact(gid),
1648                mb.v_advance_exact(gid),
1649                "glyph {gid}"
1650            );
1651        }
1652        assert_eq!(
1653            mb.v_advance_exact(composite) - ma.v_advance_exact(composite),
1654            F48Dot16::from_i32(235)
1655        );
1656    }
1657
1658    #[test]
1659    fn vvar_answering_keeps_gvar_and_the_outlines_unread() {
1660        let asked = Arc::new(Mutex::new(Vec::new()));
1661        let font = callback_font(VERT, asked.clone(), &[]);
1662        let instance = at(&font, -1.0);
1663        let metrics = instance.glyph_metrics();
1664        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1665            assert!(metrics.v_advance_exact(gid) > F48Dot16::ZERO);
1666        }
1667        assert!(asked_for(&asked, b"VVAR"));
1668        for unread in [b"gvar", b"glyf", b"loca"] {
1669            assert!(
1670                !asked_for(&asked, unread),
1671                "{:?} was read",
1672                Tag::new(unread)
1673            );
1674        }
1675    }
1676
1677    #[test]
1678    fn a_vertical_batch_agrees_with_one_at_a_time() {
1679        for (data, coord) in [(VERT_VAR, 0.0), (VERT_VAR, -1.0), (STATIC, 0.0)] {
1680            let font = Font::new(data, 0).unwrap();
1681            let instance = at(&font, coord);
1682            let metrics = instance.glyph_metrics();
1683            let gids: Vec<_> = (0..16u32).map(GlyphId::new).collect();
1684            let mut batch = vec![F48Dot16::ZERO; 16];
1685            metrics.v_advance_batched(|v| v, gids.iter().copied().zip(batch.iter_mut()));
1686            for (gid, expected) in gids.iter().zip(&batch) {
1687                assert_eq!(metrics.v_advance_exact(*gid), *expected, "glyph {gid}");
1688            }
1689        }
1690    }
1691
1692    #[test]
1693    fn the_tables_are_parsed_once_for_the_font() {
1694        let font = Font::new(VAR, 0).unwrap();
1695        let first = font.h_metric_records() as *const GlyphMetricRecords<'_>;
1696        for _ in 0..8 {
1697            assert!(core::ptr::eq(
1698                font.h_metric_records() as *const GlyphMetricRecords<'_>,
1699                first
1700            ));
1701        }
1702        // And clones share it, since they share one `Arc<FontRepr>`.
1703        assert!(core::ptr::eq(
1704            font.clone().h_metric_records() as *const GlyphMetricRecords<'_>,
1705            first
1706        ));
1707    }
1708
1709    #[test]
1710    fn a_batch_agrees_with_one_at_a_time() {
1711        let font = Font::new(STATIC, 0).unwrap();
1712        let gids: Vec<_> = (0..16u32).map(GlyphId::new).collect();
1713        let mut batch = vec![F48Dot16::ZERO; 16];
1714        font.glyph_metrics()
1715            .h_advance_batched(|v| v, gids.iter().copied().zip(batch.iter_mut()));
1716        for (gid, expected) in gids.iter().zip(&batch) {
1717            assert_eq!(font.glyph_metrics().h_advance_exact(*gid), *expected);
1718        }
1719    }
1720
1721    #[test]
1722    fn a_type1_glyph_is_measured_by_its_charstring() {
1723        // Type 1 states no metrics table, so a width that is neither zero
1724        // nor half an em can only have come from running the charstring.
1725        let font = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFB, 0).unwrap();
1726        let metrics = font.glyph_metrics();
1727        let half_em = F48Dot16::from_i32(font.units_per_em() as i32 / 2);
1728        assert!(font.num_glyphs() > 1);
1729
1730        let widths: Vec<_> = (0..font.num_glyphs())
1731            .map(|gid| metrics.h_advance_exact(GlyphId::new(gid)))
1732            .collect();
1733        assert!(widths.iter().any(|width| *width > F48Dot16::ZERO));
1734        assert!(
1735            widths.iter().any(|width| *width != half_em),
1736            "every glyph reported the fallback, so no charstring was run"
1737        );
1738    }
1739
1740    /// The fixture with its font matrix replaced, same length so the rest of
1741    /// the header is untouched. `xx` becomes 2, so every advance doubles.
1742    fn type1_with_a_stretched_matrix() -> Vec<u8> {
1743        const FROM: &[u8] = b"/FontMatrix [0.001 0 0 0.001 0 0 ]";
1744        const TO: &[u8] = b"/FontMatrix [0.002 0 0 0.001 0 0 ]";
1745        let base = font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA;
1746        let at = base
1747            .windows(FROM.len())
1748            .position(|window| window == FROM)
1749            .expect("the fixture states the matrix this test rewrites");
1750        let mut data = base.to_vec();
1751        data[at..at + TO.len()].copy_from_slice(TO);
1752        data
1753    }
1754
1755    #[test]
1756    fn a_type1_width_is_mapped_by_the_font_matrix() {
1757        // The charstring states a width in its own space, which the matrix
1758        // maps to design units. The fixture states the usual matrix, where
1759        // that mapping is the identity and a missing one would go unnoticed,
1760        // so this stretches it: every advance must double.
1761        let plain = Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA, 0).unwrap();
1762        let stretched = Font::new(type1_with_a_stretched_matrix(), 0).unwrap();
1763        let (a, b) = (plain.glyph_metrics(), stretched.glyph_metrics());
1764        assert_eq!(plain.num_glyphs(), stretched.num_glyphs());
1765
1766        let mut stretched_any = false;
1767        for gid in (0..plain.num_glyphs()).map(GlyphId::new) {
1768            let width = a.h_advance_exact(gid);
1769            assert_eq!(
1770                b.h_advance_exact(gid),
1771                width.saturating_add(width),
1772                "glyph {gid}"
1773            );
1774            stretched_any |= width > F48Dot16::ZERO;
1775        }
1776        assert!(stretched_any, "no glyph had a width to stretch");
1777    }
1778
1779    #[test]
1780    fn a_type1_advance_is_what_drawing_the_glyph_reports() {
1781        // `draw` is the reference: it maps the charstring's own space to
1782        // design units through the font matrix. Note that this font states
1783        // the usual matrix for an em of 1000, where that mapping is the
1784        // identity, so this pins agreement with `draw` rather than proving
1785        // the matrix is applied.
1786        let data = font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA;
1787        let font = Font::new(data, 0).unwrap();
1788        let direct = crate::ps::type1::Type1Font::new(data).unwrap();
1789        let metrics = font.glyph_metrics();
1790        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1791            let expected = direct.draw(gid, None, &mut NullSink).ok().flatten();
1792            assert_eq!(
1793                metrics.h_advance(gid),
1794                expected.unwrap_or(0.0),
1795                "glyph {gid}"
1796            );
1797        }
1798    }
1799
1800    #[test]
1801    fn the_narrowed_advance_agrees_with_the_exact_one() {
1802        for data in [STATIC, TAIL, VAR] {
1803            let font = Font::new(data, 0).unwrap();
1804            let metrics = font.glyph_metrics();
1805            for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1806                assert_eq!(
1807                    metrics.h_advance(gid),
1808                    metrics.h_advance_exact(gid).to_f32()
1809                );
1810            }
1811        }
1812    }
1813
1814    #[test]
1815    fn the_conversion_decides_what_a_caller_gets_back() {
1816        let font = Font::new(STATIC, 0).unwrap();
1817        let gids = [GlyphId::new(1), GlyphId::new(2)];
1818        let mut exact = [F48Dot16::ZERO; 2];
1819        let mut floats = [0.0f32; 2];
1820        font.glyph_metrics()
1821            .h_advance_batched(|v| v, gids.iter().copied().zip(exact.iter_mut()));
1822        font.glyph_metrics()
1823            .h_advance_batched(|v| v.to_f32(), gids.iter().copied().zip(floats.iter_mut()));
1824        for i in 0..2 {
1825            assert_eq!(floats[i], exact[i].to_f32());
1826        }
1827    }
1828
1829    #[test]
1830    fn a_box_is_the_one_the_glyph_states() {
1831        // Nothing has to be loaded at the default location: the glyph header
1832        // carries the box.
1833        let font = Font::new(STATIC, 0).unwrap();
1834        let metrics = font.glyph_metrics();
1835        let direct = FontRef::new(STATIC).unwrap();
1836        let (glyf, loca, hmtx) = (
1837            direct.glyf().unwrap(),
1838            direct.loca(None).unwrap(),
1839            direct.hmtx().unwrap(),
1840        );
1841        let mut drawn = 0;
1842        let mut empty = 0;
1843        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1844            match loca.get_glyf(gid, &glyf).unwrap() {
1845                Some(glyph) => {
1846                    if glyph.number_of_contours() == 0 {
1847                        assert_eq!(
1848                            metrics.extents_exact(gid),
1849                            Some(GlyphExtents::default()),
1850                            "glyph {gid}"
1851                        );
1852                        empty += 1;
1853                        continue;
1854                    }
1855                    let unit = |value: i16| F48Dot16::from_i32(value as i32);
1856                    assert_eq!(
1857                        metrics.extents_exact(gid),
1858                        Some(GlyphExtents {
1859                            x_bearing: hmtx.side_bearing(gid).map(unit).unwrap_or(F48Dot16::ZERO),
1860                            y_bearing: unit(glyph.y_max()),
1861                            width: unit(glyph.x_max()) - unit(glyph.x_min()),
1862                            height: unit(glyph.y_max()) - unit(glyph.y_min()),
1863                        }),
1864                        "glyph {gid}"
1865                    );
1866                    drawn += 1;
1867                }
1868                // A valid empty glyph has a zero box.
1869                None => {
1870                    assert_eq!(
1871                        metrics.extents_exact(gid),
1872                        Some(GlyphExtents::default()),
1873                        "glyph {gid}"
1874                    );
1875                    empty += 1;
1876                }
1877            }
1878        }
1879        assert!(drawn > 0 && empty > 0, "{drawn} drawn, {empty} empty");
1880    }
1881
1882    #[test]
1883    fn glyf_ink_starts_at_the_stated_left_side_bearing() {
1884        let direct = FontRef::new(STATIC).unwrap();
1885        let loca = direct.loca(None).unwrap();
1886        let glyf = direct.glyf().unwrap();
1887        let gid = (0..direct.hmtx().unwrap().h_metrics().len() as u32)
1888            .map(GlyphId::new)
1889            .find(|gid| {
1890                loca.get_glyf(*gid, &glyf)
1891                    .ok()
1892                    .flatten()
1893                    .is_some_and(|glyph| glyph.number_of_contours() > 0)
1894            })
1895            .unwrap();
1896        let outline = loca.get_glyf(gid, &glyf).unwrap().unwrap();
1897        let bearing = outline.x_min() + 17;
1898        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1899            let font = FontRef::new(STATIC).ok()?;
1900            let mut data = font.table_data(tag)?.as_bytes().to_vec();
1901            if tag == Tag::new(b"hmtx") {
1902                let offset = gid.to_u32() as usize * 4 + 2;
1903                data[offset..offset + 2].copy_from_slice(&bearing.to_be_bytes());
1904            }
1905            Some(Blob::from(data))
1906        });
1907        let font = Font::new(source, 0).unwrap();
1908        assert_ne!(bearing, outline.x_min());
1909        let tables = OutlineTables::new(&font.tables()).unwrap();
1910        let mut outline = Outline::<Unscaled>::new();
1911        let loaded = outline.load(&tables, gid).unwrap();
1912        let points = loaded.points();
1913        let x_min = points.iter().map(|point| point.x).min().unwrap();
1914        let x_max = points.iter().map(|point| point.x).max().unwrap();
1915        let y_min = points.iter().map(|point| point.y).min().unwrap();
1916        let y_max = points.iter().map(|point| point.y).max().unwrap();
1917        let unit = F48Dot16::from_i32;
1918        assert_eq!(
1919            font.glyph_metrics().extents_exact(gid),
1920            Some(GlyphExtents {
1921                x_bearing: unit(x_min),
1922                y_bearing: unit(y_max),
1923                width: unit(x_max - x_min),
1924                height: unit(y_max - y_min),
1925            })
1926        );
1927    }
1928
1929    #[test]
1930    fn glyf_bearing_uses_hmtx_tail_then_falls_back_to_zero() {
1931        let direct = FontRef::new(TAIL).unwrap();
1932        let loca = direct.loca(None).unwrap();
1933        let glyf = direct.glyf().unwrap();
1934        let hmtx = direct.hmtx().unwrap();
1935        let long_count = hmtx.h_metrics().len();
1936        let gid = (long_count as u32..direct.maxp().unwrap().num_glyphs() as u32)
1937            .map(GlyphId::new)
1938            .find(|gid| loca.get_glyf(*gid, &glyf).ok().flatten().is_some())
1939            .unwrap();
1940        let font = Font::new(TAIL, 0).unwrap();
1941        assert_eq!(
1942            font.glyph_metrics().extents_exact(gid).unwrap().x_bearing,
1943            F48Dot16::from_i32(hmtx.side_bearing(gid).unwrap() as i32)
1944        );
1945
1946        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag| {
1947            let font = FontRef::new(TAIL).ok()?;
1948            let mut data = font.table_data(tag)?.as_bytes().to_vec();
1949            if tag == Tag::new(b"hmtx") {
1950                data.truncate(long_count * 4);
1951            }
1952            Some(Blob::from(data))
1953        });
1954        let shortened = Font::new(source, 0).unwrap();
1955        assert_eq!(
1956            shortened
1957                .glyph_metrics()
1958                .extents_exact(gid)
1959                .unwrap()
1960                .x_bearing,
1961            F48Dot16::ZERO
1962        );
1963        let tables = OutlineTables::new(&shortened.tables()).unwrap();
1964        let mut outline = Outline::<Unscaled>::new();
1965        let loaded = outline.load(&tables, gid).unwrap();
1966        assert_eq!(loaded.points().iter().map(|point| point.x).min(), Some(0));
1967    }
1968
1969    #[test]
1970    fn a_varied_box_holds_the_outline_it_describes() {
1971        // The stated box is the one the glyph has at the default location, so
1972        // a location away from it is measured from the loaded points. Drawing
1973        // those points reaches the same box by another route, which is what
1974        // catches a phantom point counted as ink.
1975        let font = Font::new(VAR, 0).unwrap();
1976        let instance = at(&font, -1.0);
1977        let metrics = instance.glyph_metrics();
1978        let direct = FontRef::new(VAR).unwrap();
1979        let tables = OutlineTables::new(&direct)
1980            .unwrap()
1981            .at(instance.normalized_coords(), &[]);
1982        let mut outline = Outline::<OutlineScaleF32>::new();
1983        let mut checked = 0;
1984        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1985            let Ok(loaded) = outline.load(&tables, gid, None) else {
1986                continue;
1987            };
1988            let mut pen = ControlBoundsSink::new();
1989            let _ = loaded.to_path(PathContourStart::ScanBackward, &mut pen);
1990            let expected = pen
1991                .bounding_box()
1992                .map(|b| {
1993                    extents_from_corners(
1994                        F48Dot16::from_f64(b.x_min as f64),
1995                        F48Dot16::from_f64(b.y_min as f64),
1996                        F48Dot16::from_f64(b.x_max as f64),
1997                        F48Dot16::from_f64(b.y_max as f64),
1998                    )
1999                })
2000                .or(Some(GlyphExtents::default()));
2001            assert_eq!(metrics.extents_exact(gid), expected, "glyph {gid}");
2002            checked += expected.is_some() as u32;
2003        }
2004        assert!(checked > 0);
2005    }
2006
2007    #[test]
2008    fn a_location_moves_the_box() {
2009        let font = Font::new(VAR, 0).unwrap();
2010        let instance = at(&font, -1.0);
2011        let (varied, plain) = (instance.glyph_metrics(), font.glyph_metrics());
2012        let moved = (0..font.num_glyphs())
2013            .map(GlyphId::new)
2014            .filter(|gid| varied.extents_exact(*gid) != plain.extents_exact(*gid))
2015            .count();
2016        assert!(moved > 0, "no glyph ink moved at the end of the axis");
2017    }
2018
2019    #[test]
2020    fn a_charstring_box_is_the_box_it_draws_within() {
2021        // A charstring states no box, so the two have to agree by running it
2022        // the same way twice: once collecting bounds, once drawing.
2023        for data in [CFF, CFF2] {
2024            let font = Font::new(data, 0).unwrap();
2025            let metrics = font.glyph_metrics();
2026            let direct = FontRef::new(data).unwrap();
2027            let cff = font
2028                .charstrings(OutlineSource::Cff2)
2029                .or_else(|| font.charstrings(OutlineSource::Cff))
2030                .expect("no charstrings");
2031            assert!(direct.glyf().is_err(), "glyf would answer first");
2032            let mut checked = 0;
2033            for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2034                let subfont = cff
2035                    .subfont(cff.subfont_index(gid).unwrap(), &[])
2036                    .expect("no subfont");
2037                let mut pen = ControlBoundsSink::new();
2038                cff.draw(&subfont, gid, &[], None, &mut pen).unwrap();
2039                let expected = pen
2040                    .bounding_box()
2041                    .map(|b| GlyphExtents {
2042                        x_bearing: F48Dot16::from_f64(b.x_min as f64),
2043                        y_bearing: F48Dot16::from_f64(b.y_max as f64),
2044                        width: F48Dot16::from_f64((b.x_max - b.x_min) as f64),
2045                        height: F48Dot16::from_f64((b.y_max - b.y_min) as f64),
2046                    })
2047                    .or(Some(GlyphExtents::default()));
2048                assert_eq!(metrics.extents_exact(gid), expected, "glyph {gid}");
2049                checked += expected.is_some() as u32;
2050            }
2051            assert!(checked > 0);
2052        }
2053    }
2054
2055    #[test]
2056    fn a_type1_box_is_the_box_it_draws_within() {
2057        let data = font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA;
2058        let font = Font::new(data, 0).unwrap();
2059        let metrics = font.glyph_metrics();
2060        let direct = crate::ps::type1::Type1Font::new(data).unwrap();
2061        let mut checked = 0;
2062        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2063            let mut pen = ControlBoundsSink::new();
2064            direct.draw(gid, None, &mut pen).unwrap();
2065            let expected = pen
2066                .bounding_box()
2067                .map(|b| GlyphExtents {
2068                    x_bearing: F48Dot16::from_f64(b.x_min as f64),
2069                    y_bearing: F48Dot16::from_f64(b.y_max as f64),
2070                    width: F48Dot16::from_f64((b.x_max - b.x_min) as f64),
2071                    height: F48Dot16::from_f64((b.y_max - b.y_min) as f64),
2072                })
2073                .or(Some(GlyphExtents::default()));
2074            assert_eq!(metrics.extents_exact(gid), expected, "glyph {gid}");
2075            checked += expected.is_some() as u32;
2076        }
2077        assert!(checked > 0);
2078    }
2079
2080    #[test]
2081    fn every_source_measures_a_run_as_it_measures_one() {
2082        // Each source keeps something across a run: buffers, a subfont, a
2083        // table read once. None of it may change an answer.
2084        for (data, coord) in [(STATIC, 0.0), (VAR, -1.0), (CFF, 0.0), (CFF2, 0.0)] {
2085            let font = Font::new(data, 0).unwrap();
2086            let instance = if coord == 0.0 {
2087                font.clone()
2088            } else {
2089                at(&font, coord)
2090            };
2091            let metrics = instance.glyph_metrics();
2092            let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
2093            let mut batched = vec![None; glyphs.len()];
2094            metrics.extents_batched(
2095                |extents| extents,
2096                glyphs.iter().copied().zip(batched.iter_mut()),
2097            );
2098            for (gid, batched) in glyphs.iter().copied().zip(batched) {
2099                assert_eq!(metrics.extents_exact(gid), batched, "glyph {gid}");
2100            }
2101        }
2102    }
2103
2104    /// Converts a box in font units the same way the sources do.
2105    fn extents_of(bounds: BoundingBox<f32>) -> GlyphExtents<F48Dot16> {
2106        exact_extents(bounds.into())
2107    }
2108
2109    #[test]
2110    fn bitmaps_answer_before_outlines_in_the_default_order() {
2111        let font = Font::new(font_test_data::CBDT, 0).unwrap();
2112        let gid = GlyphId::new(2);
2113        let bitmaps = font.bitmap_strikes();
2114        let from_strike = |ppem| exact_extents(bitmaps.select_bitmap(gid, ppem).unwrap().extents());
2115        // No size picks the largest strike, as HarfBuzz does for a font
2116        // with no size set.
2117        let metrics = font.glyph_metrics();
2118        assert_eq!(metrics.extents_exact(gid), Some(from_strike(None)));
2119        // A size picks the smallest strike that covers it.
2120        let at_16 = metrics.with_bitmap_ppem(Some(12.0));
2121        assert_eq!(at_16.extents_exact(gid), Some(from_strike(Some(12.0))));
2122        assert_ne!(from_strike(None), from_strike(Some(12.0)));
2123        // Leaving the bitmaps out of the order measures the outlines.
2124        let outlines_only = metrics.with_extents_source_order(GlyphSourceOrder::OUTLINES);
2125        let expected = font
2126            .outline_glyphs()
2127            .get(gid)
2128            .map(|glyph| exact_extents(glyph.extents().unwrap()));
2129        assert_eq!(outlines_only.extents_exact(gid), expected);
2130        assert_ne!(outlines_only.extents_exact(gid), metrics.extents_exact(gid));
2131        // And a glyph the strike lacks falls through to the next source.
2132        let past = GlyphId::new(font.num_glyphs());
2133        assert_eq!(metrics.extents_exact(past), None);
2134    }
2135
2136    /// Returns the `CBDT` test font with glyph 2 left out of its 16 ppem
2137    /// strike, so that only the larger strikes have it.
2138    fn cbdt_without_glyph_2_at_16() -> alloc::vec::Vec<u8> {
2139        let source = crate::FontRef::new(font_test_data::CBDT).unwrap();
2140        let cblc_start = source
2141            .table_directory()
2142            .table_records()
2143            .iter()
2144            .find(|record| record.tag() == crate::types::Tag::new(b"CBLC"))
2145            .unwrap()
2146            .offset() as usize;
2147        let cblc = source.cblc().unwrap();
2148        let size = cblc
2149            .bitmap_sizes()
2150            .iter()
2151            .find(|size| size.ppem_x() == 16)
2152            .unwrap();
2153        let mut data = font_test_data::CBDT.to_vec();
2154        let list = cblc_start + size.index_subtable_list_offset() as usize;
2155        for index in 0..size.number_of_index_subtables() as usize {
2156            let record = list + index * 8;
2157            let read = |at: usize| u16::from_be_bytes([data[at], data[at + 1]]);
2158            let (first, last) = (read(record), read(record + 2));
2159            if (first..=last).contains(&2) {
2160                // End the range before the glyph, or start it after.
2161                let (at, value) = if first < 2 {
2162                    (record + 2, 1u16)
2163                } else {
2164                    (record, 3u16)
2165                };
2166                data[at..at + 2].copy_from_slice(&value.to_be_bytes());
2167            }
2168        }
2169        data
2170    }
2171
2172    // A glyph missing from the strike for the size is measured by the next
2173    // source, as in HarfBuzz, though drawing still finds a larger strike.
2174    #[test]
2175    fn a_bitmap_the_selected_strike_lacks_is_measured_by_the_next_source() {
2176        let font = Font::new(cbdt_without_glyph_2_at_16(), 0).unwrap();
2177        let gid = GlyphId::new(2);
2178        let strikes = font.bitmap_strikes();
2179        let selected = strikes.select(Some(12.0)).unwrap();
2180        assert_eq!(selected.ppem(), 16);
2181        assert!(selected.get(gid).is_none());
2182        let larger = strikes.select_bitmap(gid, Some(12.0)).unwrap();
2183        assert_eq!(larger.ppem(), 64);
2184        let larger = Some(exact_extents(larger.extents()));
2185        // The font has no outline for the glyph, so nothing measures it.
2186        let outline = font
2187            .outline_glyphs()
2188            .get(gid)
2189            .and_then(|glyph| glyph.extents().ok().map(exact_extents));
2190        let at_12 = font.glyph_metrics().with_bitmap_ppem(Some(12.0));
2191        assert_eq!(at_12.extents_exact(gid), outline);
2192        assert_ne!(at_12.extents_exact(gid), larger);
2193        // At 64 ppem the selected strike has it.
2194        let at_64 = font.glyph_metrics().with_bitmap_ppem(Some(64.0));
2195        assert_eq!(at_64.extents_exact(gid), larger);
2196    }
2197
2198    #[test]
2199    fn a_color_glyph_measures_by_its_clip_box_or_its_paint() {
2200        use crate::model::glyph::color::test_glyph_defs::{CLIPBOX, COLORED_CIRCLES_V0};
2201        let font = Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap();
2202        let metrics = font.glyph_metrics();
2203        let colors = font.color_glyphs();
2204        let map = |ch: char| font.charmap().map_unicode(ch).unwrap();
2205        // A glyph with a clip box is measured by it.
2206        let clipped = map(CLIPBOX[0]);
2207        let clip_box = colors.get(clipped).unwrap().clip_box().unwrap();
2208        assert_eq!(metrics.extents_exact(clipped), Some(extents_of(clip_box)));
2209        // One without is measured by what it paints, which here is a set of
2210        // layered outlines and so the union of their boxes.
2211        let painted = map(COLORED_CIRCLES_V0[0]);
2212        let glyph = colors.get(painted).unwrap();
2213        assert!(glyph.clip_box().is_none());
2214        assert_eq!(
2215            metrics.extents_exact(painted),
2216            Some(exact_extents(glyph.extents().unwrap()))
2217        );
2218        // The base glyph of a color glyph is usually empty, so taking color
2219        // out of the order measures something else.
2220        let outlines_only = metrics.with_extents_source_order(GlyphSourceOrder::OUTLINES);
2221        assert_ne!(
2222            outlines_only.extents_exact(clipped),
2223            metrics.extents_exact(clipped)
2224        );
2225        // A glyph the table does not paint is measured by its outline.
2226        let plain = GlyphId::new(1);
2227        assert!(colors.get(plain).is_none());
2228        assert_eq!(
2229            metrics.extents_exact(plain),
2230            outlines_only.extents_exact(plain)
2231        );
2232    }
2233
2234    // HarfBuzz measures a version 0 table's layered glyphs too.
2235    // A color-only order still clips with outlines, so a painted glyph
2236    // measures as it does on its own.
2237    #[test]
2238    fn a_color_only_order_keeps_the_clips() {
2239        let font = Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap();
2240        let metrics = font
2241            .glyph_metrics()
2242            .with_extents_source_order(GlyphSourceOrder::COLOR);
2243        let mut measured = 0;
2244        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2245            let Some(color) = font.color_glyphs().get(gid) else {
2246                continue;
2247            };
2248            let Ok(direct) = color.extents() else {
2249                continue;
2250            };
2251            if color.clip_box().is_some() || direct.width == 0.0 || direct.height == 0.0 {
2252                continue;
2253            }
2254            assert_eq!(metrics.extents(gid), Some(direct), "glyph {gid}");
2255            measured += 1;
2256        }
2257        assert!(measured > 0);
2258    }
2259
2260    #[test]
2261    fn a_version_0_color_glyph_measures_by_its_layers() {
2262        use crate::model::glyph::color::test_glyph_defs::COLORED_CIRCLES_V0;
2263        let mut data = font_test_data::COLRV0V1.to_vec();
2264        let colr = crate::FontRef::new(&data)
2265            .unwrap()
2266            .table_directory()
2267            .table_records()
2268            .iter()
2269            .find(|record| record.tag() == Tag::new(b"COLR"))
2270            .unwrap()
2271            .offset() as usize;
2272        // A version 0 reader stops before the version 1 fields, so this is a
2273        // valid version 0 table holding the same layer records.
2274        data[colr..colr + 2].copy_from_slice(&0u16.to_be_bytes());
2275        let font = Font::new(data, 0).unwrap();
2276        let colors = font.color_glyphs();
2277        assert_eq!(colors.version(), Some(0));
2278        let metrics = font.glyph_metrics();
2279        let outlines_only = metrics.with_extents_source_order(GlyphSourceOrder::OUTLINES);
2280        let glyph = font.charmap().map_unicode(COLORED_CIRCLES_V0[0]).unwrap();
2281        let layers = exact_extents(colors.get(glyph).unwrap().extents().unwrap());
2282        assert_eq!(metrics.extents_exact(glyph), Some(layers));
2283        assert_ne!(outlines_only.extents_exact(glyph), Some(layers));
2284        // A glyph without layers is measured by its outline.
2285        let plain = GlyphId::new(1);
2286        assert!(colors.get(plain).is_none());
2287        assert_eq!(
2288            metrics.extents_exact(plain),
2289            outlines_only.extents_exact(plain)
2290        );
2291    }
2292
2293    #[test]
2294    fn a_color_glyph_at_a_location_moves_its_clip_box() {
2295        use crate::model::glyph::color::test_glyph_defs::CLIPBOX;
2296        let font = Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap();
2297        let gid = font.charmap().map_unicode(CLIPBOX[0]).unwrap();
2298        let moved = font
2299            .instance_builder()
2300            .variations([("CLXI", 200.0)])
2301            .build();
2302        let (at_default, at_moved) = (
2303            font.glyph_metrics().extents_exact(gid).unwrap(),
2304            moved.glyph_metrics().extents_exact(gid).unwrap(),
2305        );
2306        assert_ne!(at_default.x_bearing, at_moved.x_bearing);
2307        // The clip box moves by a fraction of a unit, and the extents by the
2308        // whole units HarfBuzz rounds each delta to.
2309        let clip_box = moved.color_glyphs().get(gid).unwrap().clip_box().unwrap();
2310        assert_ne!(clip_box.x_min, clip_box.x_min.round());
2311        let rounded = BoundingBox {
2312            x_min: clip_box.x_min.round(),
2313            y_min: clip_box.y_min.round(),
2314            x_max: clip_box.x_max.round(),
2315            y_max: clip_box.y_max.round(),
2316        };
2317        assert_eq!(at_moved, extents_of(rounded));
2318    }
2319
2320    #[test]
2321    fn a_varc_composite_measures_by_what_it_draws() {
2322        let font = Font::new(font_test_data::varc::CJK_6868, 0).unwrap();
2323        let outlines = font.outline_glyphs();
2324        assert!(outlines.has_varc(), "the fixture has VARC");
2325        let metrics = font.glyph_metrics();
2326        let base_only = metrics.with_extents_source_order(GlyphSourceOrder::BASE_OUTLINES);
2327        let mut composites = 0;
2328        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2329            let glyph = outlines.get(gid).unwrap();
2330            let expected = exact_extents(glyph.extents().unwrap());
2331            if outlines.varc_covers(gid) {
2332                assert!(matches!(
2333                    glyph,
2334                    crate::model::glyph::outline::OutlineGlyph::Varc(_)
2335                ));
2336                assert_eq!(metrics.extents_exact(gid), Some(expected), "glyph {gid}");
2337                composites += 1;
2338            } else {
2339                // The base measures it either way.
2340                assert_eq!(
2341                    metrics.extents_exact(gid),
2342                    base_only.extents_exact(gid),
2343                    "glyph {gid}"
2344                );
2345            }
2346        }
2347        assert!(composites > 0);
2348        // Without VARC in the order, a composite is measured by the empty
2349        // base glyph beneath it.
2350        let composite = (0..font.num_glyphs())
2351            .map(GlyphId::new)
2352            .find(|gid| outlines.varc_covers(*gid))
2353            .unwrap();
2354        assert_ne!(
2355            base_only.extents_exact(composite),
2356            metrics.extents_exact(composite)
2357        );
2358    }
2359
2360    #[test]
2361    fn mixed_sources_measure_a_run_as_they_measure_one() {
2362        for data in [
2363            font_test_data::CBDT,
2364            font_test_data::COLRV0V1_VARIABLE,
2365            font_test_data::varc::CJK_6868,
2366        ] {
2367            let font = Font::new(data, 0).unwrap();
2368            let metrics = font.glyph_metrics();
2369            let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
2370            let mut batched = vec![None; glyphs.len()];
2371            metrics.extents_batched(
2372                |extents| extents,
2373                glyphs.iter().copied().zip(batched.iter_mut()),
2374            );
2375            for (gid, batched) in glyphs.iter().copied().zip(batched) {
2376                assert_eq!(metrics.extents_exact(gid), batched, "glyph {gid}");
2377            }
2378        }
2379    }
2380
2381    /// Measures `glyph` by trying every source the order finds, as the
2382    /// general path does.
2383    fn extents_through_every_source(
2384        metrics: &GlyphMetrics<'_>,
2385        glyph: GlyphId,
2386    ) -> Option<GlyphExtents<F48Dot16>> {
2387        let sources = ExtentsSources::new(metrics);
2388        let extents = sources
2389            .iter()
2390            .find_map(|source| metrics.extents_from_source(source, glyph));
2391        extents
2392    }
2393
2394    // Skipping the list of sources when only a base outline can answer gives
2395    // the same extents as trying every source.
2396    #[test]
2397    fn the_base_outline_shortcut_matches_every_source() {
2398        let mut fonts = alloc::vec![
2399            Font::new(STATIC, 0).unwrap(),
2400            Font::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED, 0).unwrap(),
2401            Font::new(font_test_data::CBDT, 0).unwrap(),
2402            Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap(),
2403            Font::new(font_test_data::varc::CJK_6868, 0).unwrap(),
2404            Font::new(font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA, 0).unwrap(),
2405        ];
2406        let cantarell = Font::new(font_test_data::CANTARELL_VF_TRIMMED, 0).unwrap();
2407        fonts.push(
2408            cantarell
2409                .instance_builder()
2410                .variations([("wght", 650.0)])
2411                .build(),
2412        );
2413        fonts.push(cantarell);
2414        let orders = [
2415            GlyphSourceOrder::DEFAULT,
2416            GlyphSourceOrder::OUTLINES,
2417            GlyphSourceOrder::BASE_OUTLINES,
2418            GlyphSourceOrder::BITMAPS,
2419            GlyphSourceOrder::COLOR,
2420            GlyphSourceOrder::new(&[GlyphSource::Cff, GlyphSource::Glyf, GlyphSource::Colr]),
2421            GlyphSourceOrder::EMPTY,
2422        ];
2423        for (index, font) in fonts.iter().enumerate() {
2424            for order in orders {
2425                let metrics = font.glyph_metrics().with_extents_source_order(order);
2426                for glyph in (0..font.num_glyphs() + 1).map(GlyphId::new) {
2427                    assert_eq!(
2428                        metrics.extents_exact(glyph),
2429                        extents_through_every_source(&metrics, glyph),
2430                        "font {index} order {order:?} glyph {glyph}"
2431                    );
2432                }
2433            }
2434        }
2435    }
2436
2437    // An order without bitmap, color or composite sources probes none of
2438    // those tables.
2439    #[test]
2440    fn an_outline_order_probes_no_other_tables() {
2441        let font = Font::new(font_test_data::CBDT, 0).unwrap();
2442        let probed = |font: &Font| font.probed_glyph_sources();
2443        let base = font
2444            .glyph_metrics()
2445            .with_extents_source_order(GlyphSourceOrder::BASE_OUTLINES);
2446        base.extents_exact(GlyphId::new(1));
2447        assert_eq!(probed(&font), 0);
2448        // The default order asks for the bitmaps, and the answer is kept.
2449        font.glyph_metrics().extents_exact(GlyphId::new(1));
2450        assert_ne!(probed(&font), 0);
2451        assert!(font.has_layered_source(GlyphSource::Cbdt));
2452        assert!(!font.has_layered_source(GlyphSource::Colr));
2453    }
2454
2455    #[test]
2456    fn an_empty_order_measures_nothing() {
2457        let font = Font::new(STATIC, 0).unwrap();
2458        let metrics = font
2459            .glyph_metrics()
2460            .with_extents_source_order(GlyphSourceOrder::EMPTY);
2461        assert_eq!(metrics.extents_exact(GlyphId::new(1)), None);
2462        assert_eq!(metrics.extents_source_order(), GlyphSourceOrder::EMPTY);
2463    }
2464
2465    #[test]
2466    fn a_font_stating_no_outlines_measures_nothing() {
2467        let font = Font::new(font_test_data::NAMES_ONLY, 0).unwrap();
2468        let metrics = font.glyph_metrics();
2469        for gid in (0..8).map(GlyphId::new) {
2470            assert_eq!(metrics.extents_exact(gid), None);
2471        }
2472    }
2473
2474    #[test]
2475    fn a_glyph_past_the_font_has_no_extents() {
2476        for data in [
2477            STATIC,
2478            VAR,
2479            CFF,
2480            CFF2,
2481            font_test_data::type1::NOTO_SERIF_REGULAR_SUBSET_PFA,
2482        ] {
2483            let font = Font::new(data, 0).unwrap();
2484            let past = GlyphId::new(font.num_glyphs());
2485            assert_eq!(font.glyph_metrics().extents_exact(past), None);
2486            if data == VAR {
2487                assert_eq!(at(&font, -1.0).glyph_metrics().extents_exact(past), None);
2488            }
2489        }
2490    }
2491
2492    #[test]
2493    fn a_reversed_box_still_measures_a_size() {
2494        // A size is never negative, whichever way round a font states its
2495        // corners.
2496        let unit = F48Dot16::from_i32;
2497        assert_eq!(
2498            extents_from_corners(unit(70), unit(50), unit(10), unit(-30)),
2499            GlyphExtents {
2500                x_bearing: unit(10),
2501                y_bearing: unit(50),
2502                width: unit(60),
2503                height: unit(80),
2504            }
2505        );
2506    }
2507
2508    fn at(font: &Font, coord: f32) -> Font {
2509        font.instance_builder()
2510            .normalized_coords([NormalizedCoord::from_f32(coord)])
2511            .build()
2512    }
2513
2514    /// A font whose tables arrive one at a time, recording what was asked
2515    /// for and withholding any tag in `hide`.
2516    fn callback_font(
2517        data: &'static [u8],
2518        asked: Arc<Mutex<Vec<Tag>>>,
2519        hide: &'static [&[u8; 4]],
2520    ) -> Font {
2521        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag: Tag| {
2522            asked.lock().unwrap().push(tag);
2523            if hide.iter().any(|hidden| Tag::new(hidden) == tag) {
2524                return None;
2525            }
2526            let font = FontRef::new(data).ok()?;
2527            Some(Blob::from(font.table_data(tag)?.as_bytes().to_vec()))
2528        });
2529        Font::new(source, 0).unwrap()
2530    }
2531
2532    fn asked_for(asked: &Arc<Mutex<Vec<Tag>>>, tag: &[u8; 4]) -> bool {
2533        asked.lock().unwrap().contains(&Tag::new(tag))
2534    }
2535
2536    #[test]
2537    fn an_instance_varies_what_the_font_does_not() {
2538        let font = Font::new(VAR, 0).unwrap();
2539        let instance = at(&font, -1.0);
2540        let (varied, plain) = (instance.glyph_metrics(), font.glyph_metrics());
2541        let moved = (0..font.num_glyphs())
2542            .map(GlyphId::new)
2543            .any(|gid| varied.h_advance_exact(gid) != plain.h_advance_exact(gid));
2544        assert!(moved, "no glyph moved at the far end of the axis");
2545    }
2546
2547    #[test]
2548    fn an_advance_keeps_the_fraction_a_location_adds() {
2549        // The stated width is whole and the delta is not, so a varied advance
2550        // should carry a fraction that rounding would have thrown away. If
2551        // nothing here is fractional the exactness is untested.
2552        let font = Font::new(VAR, 0).unwrap();
2553        let metrics = at(&font, -0.4);
2554        let metrics = metrics.glyph_metrics();
2555        let fractional = (0..font.num_glyphs())
2556            .map(GlyphId::new)
2557            .any(|gid| metrics.h_advance_exact(gid).to_bits() & 0xFFFF != 0);
2558        assert!(fractional, "no advance carried a fraction");
2559    }
2560
2561    #[test]
2562    fn negative_varied_advances_are_clamped_before_scaling() {
2563        const DATA: &[u8] = font_test_data::NEGATIVE_VAR_ADVANCES;
2564        for hide in [&[][..], &[b"HVAR", b"VVAR"][..]] {
2565            let asked = Arc::new(Mutex::new(Vec::new()));
2566            let font = callback_font(DATA, asked.clone(), hide);
2567            // A crosses zero; B remains positive, with fractional advances
2568            // at intermediate locations. The two metric sources agree.
2569            for (coord, widths, heights) in [
2570                (0.0, [500.0, 500.0], [1000.0, 1000.0]),
2571                (0.5, [199.5, 550.5], [399.5, 1100.5]),
2572                (1.0, [0.0, 601.0], [0.0, 1201.0]),
2573            ] {
2574                let instance = at(&font, coord);
2575                let metrics = instance.glyph_metrics();
2576                let gids = [GlyphId::new(1), GlyphId::new(2), GlyphId::new(3)];
2577                let widths = [widths[0], widths[1], 0.0];
2578                let heights = [heights[0], heights[1], 0.0];
2579                let mut h_batch = [F48Dot16::ZERO; 3];
2580                let mut v_batch = [F48Dot16::ZERO; 3];
2581                metrics.h_advance_batched(|v| v, gids.into_iter().zip(h_batch.iter_mut()));
2582                metrics.v_advance_batched(|v| v, gids.into_iter().zip(v_batch.iter_mut()));
2583                for (i, gid) in gids.into_iter().enumerate() {
2584                    assert_eq!(metrics.h_advance_exact(gid), F48Dot16::from_f64(widths[i]));
2585                    assert_eq!(metrics.v_advance_exact(gid), F48Dot16::from_f64(heights[i]));
2586                    assert_eq!(h_batch[i], metrics.h_advance_exact(gid));
2587                    assert_eq!(v_batch[i], metrics.v_advance_exact(gid));
2588                    assert_eq!(metrics.h_advance(gid), widths[i] as f32);
2589                    assert_eq!(metrics.v_advance(gid), heights[i] as f32);
2590                }
2591                for factor in [-2.0, -1.0, 0.0, 1.0, 2.0] {
2592                    let scaled =
2593                        metrics.scaled(ScaleF32::new(factor * 1000.0, -factor * 1000.0, 1000));
2594                    let mut h_batch = [0.0; 3];
2595                    let mut v_batch = [0.0; 3];
2596                    scaled.h_advance_batched(|v| v, gids.into_iter().zip(h_batch.iter_mut()));
2597                    scaled.v_advance_batched(|v| v, gids.into_iter().zip(v_batch.iter_mut()));
2598                    for (i, gid) in gids.into_iter().enumerate() {
2599                        assert_eq!(scaled.h_advance(gid), widths[i] as f32 * factor);
2600                        assert_eq!(scaled.v_advance(gid), heights[i] as f32 * -factor);
2601                        assert_eq!(h_batch[i], scaled.h_advance(gid));
2602                        assert_eq!(v_batch[i], scaled.v_advance(gid));
2603                    }
2604                }
2605            }
2606            if !hide.is_empty() {
2607                assert!(asked_for(&asked, b"gvar"));
2608            }
2609        }
2610    }
2611
2612    #[test]
2613    fn a_batch_agrees_with_one_at_a_time_at_a_location() {
2614        let font = Font::new(VAR, 0).unwrap();
2615        let instance = at(&font, -0.6);
2616        let metrics = instance.glyph_metrics();
2617        let gids: Vec<_> = (0..16u32).map(GlyphId::new).collect();
2618        let mut batch = vec![F48Dot16::ZERO; 16];
2619        metrics.h_advance_batched(|v| v, gids.iter().copied().zip(batch.iter_mut()));
2620        for (gid, expected) in gids.iter().zip(&batch) {
2621            assert_eq!(metrics.h_advance_exact(*gid), *expected);
2622        }
2623    }
2624
2625    #[test]
2626    fn gvar_answers_the_same_as_hvar() {
2627        // The two rungs of the ladder describe the same font, so a font that
2628        // has both must measure the same either way. Withholding `HVAR`
2629        // forces the second rung, which exercises the real fallback rather
2630        // than a reimplementation of it.
2631        let font = Font::new(VAR, 0).unwrap();
2632        let asked = Arc::new(Mutex::new(Vec::new()));
2633        let no_hvar = callback_font(VAR, asked.clone(), &[b"HVAR"]);
2634        for coord in [-1.0, -0.8, -0.25, 0.75, 1.0] {
2635            let (with, without) = (at(&font, coord), at(&no_hvar, coord));
2636            let (with, without) = (with.glyph_metrics(), without.glyph_metrics());
2637            for gid in (0..font.num_glyphs()).map(GlyphId::new) {
2638                assert_eq!(
2639                    with.h_advance_exact(gid),
2640                    without.h_advance_exact(gid),
2641                    "glyph {gid} disagrees at {coord}"
2642                );
2643            }
2644        }
2645        // And it really did take the other path.
2646        assert!(asked_for(&asked, b"gvar"));
2647    }
2648
2649    #[test]
2650    fn a_default_location_reads_no_variation_table() {
2651        // The point of the whole arrangement: `gvar` is typically about half
2652        // a variable font, and on a platform that hands tables over one at a
2653        // time, asking for it means copying it. Static text must not.
2654        let asked = Arc::new(Mutex::new(Vec::new()));
2655        let font = callback_font(VAR, asked.clone(), &[]);
2656        let metrics = font.glyph_metrics();
2657        for gid in (0..8).map(GlyphId::new) {
2658            let _ = metrics.h_advance_exact(gid);
2659        }
2660        assert!(!asked_for(&asked, b"gvar"));
2661        assert!(!asked_for(&asked, b"HVAR"));
2662        assert!(asked_for(&asked, b"hmtx"), "but it should read hmtx");
2663    }
2664
2665    #[test]
2666    fn an_instance_at_the_default_location_reads_no_variation_table() {
2667        let asked = Arc::new(Mutex::new(Vec::new()));
2668        let font = callback_font(VAR, asked.clone(), &[]);
2669        // All-zero coordinates are the default location, and an instance
2670        // collapses them to none.
2671        let instance = at(&font, 0.0);
2672        assert!(instance.normalized_coords().is_empty());
2673        let _ = instance.glyph_metrics().h_advance_exact(GlyphId::new(1));
2674        assert!(!asked_for(&asked, b"gvar"));
2675        assert!(!asked_for(&asked, b"HVAR"));
2676    }
2677
2678    #[test]
2679    fn hvar_answering_keeps_gvar_and_the_outlines_unread() {
2680        let asked = Arc::new(Mutex::new(Vec::new()));
2681        let font = callback_font(VAR, asked.clone(), &[]);
2682        let _ = at(&font, -0.75)
2683            .glyph_metrics()
2684            .h_advance_exact(GlyphId::new(1));
2685        assert!(asked_for(&asked, b"HVAR"));
2686        for cold in [b"gvar", b"glyf", b"loca"] {
2687            assert!(
2688                !asked_for(&asked, cold),
2689                "HVAR answered, so {} should never have been copied",
2690                Tag::new(cold)
2691            );
2692        }
2693    }
2694
2695    #[test]
2696    fn taking_the_metrics_reads_hmtx_but_nothing_that_varies() {
2697        // Every measurement needs `hmtx`, so it is read up front. What a
2698        // location changes is not: that waits until something asks.
2699        let asked = Arc::new(Mutex::new(Vec::new()));
2700        let font = callback_font(VAR, asked.clone(), &[]);
2701        let instance = at(&font, -0.75);
2702        let metrics = instance.glyph_metrics();
2703        assert!(asked_for(&asked, b"hmtx"));
2704        for cold in [b"HVAR", b"gvar", b"glyf", b"loca"] {
2705            assert!(!asked_for(&asked, cold), "read {}", Tag::new(cold));
2706        }
2707        // And measuring one glyph then reads only what answers.
2708        let _ = metrics.h_advance_exact(GlyphId::new(1));
2709        assert!(asked_for(&asked, b"HVAR"));
2710        assert!(!asked_for(&asked, b"gvar"));
2711    }
2712}