Skip to main content

read_fonts/model/metrics/
scaled.rs

1//! Metrics in a caller's own units.
2
3use super::{
4    Decoration, GlyphExtents, GlyphMetrics, LineBox, LineExtents, Metrics, ScriptMetrics,
5    StyleMetrics,
6};
7use crate::model::glyph::GlyphSourceOrder;
8use types::{BoundingBox, F26Dot6, F48Dot16, Fixed, GlyphId};
9
10/// Converts a measurement in font units into a caller's own units.
11///
12/// Written by whatever owns scaling in a text stack: a shaper, or the bridge
13/// between one and this crate. Callers that want font units read them from
14/// [`GlyphMetrics`], [`Metrics`] and [`StyleMetrics`] and never implement this.
15///
16/// An implementation chooses the number format, the rounding, and the order
17/// of operations for each axis. This crate defines how measurements combine
18/// into a metric.
19///
20/// The arithmetic is part of the trait for the same reason: an
21/// implementation decides what an overflowing sum does and which way a
22/// halved odd number rounds.
23pub trait Scale {
24    /// What a scaled measurement is expressed as.
25    type Value: Copy;
26
27    /// Returns `a` plus `b`.
28    ///
29    /// This and the two below take no scale. They describe
30    /// [`Value`](Self::Value) itself, so every scale of that type answers
31    /// them alike.
32    fn add(a: Self::Value, b: Self::Value) -> Self::Value;
33
34    /// Returns `a` less `b`.
35    fn sub(a: Self::Value, b: Self::Value) -> Self::Value;
36
37    /// Returns half of `value`.
38    fn half(value: Self::Value) -> Self::Value;
39
40    /// Scales a measurement along the x axis.
41    fn scale_x(&self, value: F48Dot16) -> Self::Value;
42
43    /// Scales a measurement along the y axis.
44    ///
45    /// Separate from [`scale_x`](Self::scale_x) because the two axes need
46    /// not agree, and because a caller whose y runs down the page says so
47    /// here.
48    fn scale_y(&self, value: F48Dot16) -> Self::Value;
49
50    /// Scales where a glyph's ink sits.
51    ///
52    /// One call rather than four, so that a scale running the other way up
53    /// settles in one place what that means for a bearing and a size. The
54    /// scale also decides whether to round the box's corners before scaling.
55    fn scale_glyph_extents(&self, extents: GlyphExtents<F48Dot16>) -> GlyphExtents<Self::Value>;
56
57    /// Scales a region given by its corners.
58    ///
59    /// One call for the same reason as [`scale_glyph_extents`](Self::scale_glyph_extents):
60    /// a scale running the other way up decides here which corner ends up
61    /// least.
62    fn scale_rect(&self, bounds: BoundingBox<F48Dot16>) -> BoundingBox<Self::Value>;
63
64    /// Returns the size in pixels per em, if the scale has one.
65    ///
66    /// Embedded bitmaps are measured from the strike selected for this size.
67    /// `None`, the default, selects the largest strike.
68    fn ppem(&self) -> Option<f32> {
69        None
70    }
71}
72
73/// Scales design-unit metrics to 26.6 pixels with 16.16 fixed-point
74/// arithmetic.
75///
76/// Sums saturate and halves round toward negative infinity. A measurement
77/// with a fraction is rounded to a whole unit before it is scaled;
78/// [`ScaleF32`] keeps the fraction.
79#[derive(Copy, Clone, Debug)]
80pub struct Scale26Dot6 {
81    x: Fixed,
82    y: Fixed,
83    ppem: f32,
84}
85
86impl Scale26Dot6 {
87    /// Creates a scale with a separate size for each axis.
88    pub fn new(x_ppem: f32, y_ppem: f32, units_per_em: u16) -> Self {
89        Self {
90            x: Self::factor(x_ppem, units_per_em),
91            y: Self::factor(y_ppem, units_per_em),
92            ppem: x_ppem.max(y_ppem),
93        }
94    }
95
96    /// Creates a scale that maps `units_per_em` font units to `ppem` pixels.
97    pub fn from_ppem(ppem: f32, units_per_em: u16) -> Self {
98        Self::new(ppem, ppem, units_per_em)
99    }
100
101    /// Returns the factor that converts whole font units to 26.6 pixels.
102    ///
103    /// The factor includes the conversion to 1/64 pixel, so a 16.16 multiply
104    /// of a whole design unit produces 26.6 bits.
105    fn factor(ppem: f32, units_per_em: u16) -> Fixed {
106        Fixed::from_bits((ppem * 64.0) as i32) / Fixed::from_bits(units_per_em.max(1) as i32)
107    }
108
109    fn scale(value: F48Dot16, factor: Fixed) -> F26Dot6 {
110        F26Dot6::from_bits((Fixed::from_bits(value.to_i32()) * factor).to_bits())
111    }
112}
113
114impl Scale for Scale26Dot6 {
115    type Value = F26Dot6;
116
117    fn add(a: F26Dot6, b: F26Dot6) -> F26Dot6 {
118        a.saturating_add(b)
119    }
120
121    fn sub(a: F26Dot6, b: F26Dot6) -> F26Dot6 {
122        a.saturating_sub(b)
123    }
124
125    fn half(value: F26Dot6) -> F26Dot6 {
126        F26Dot6::from_bits(value.to_bits() >> 1)
127    }
128
129    fn scale_x(&self, value: F48Dot16) -> F26Dot6 {
130        Self::scale(value, self.x)
131    }
132
133    fn scale_y(&self, value: F48Dot16) -> F26Dot6 {
134        Self::scale(value, self.y)
135    }
136
137    fn scale_glyph_extents(&self, e: GlyphExtents<F48Dot16>) -> GlyphExtents<F26Dot6> {
138        if e.width <= F48Dot16::ZERO || e.height <= F48Dot16::ZERO {
139            return GlyphExtents::default();
140        }
141        // Match FreeType's FT_RoundFix: ties round away from zero.
142        let round = |value: F48Dot16| {
143            let bits = value.to_bits();
144            F48Dot16::from_bits(bits.wrapping_add(0x8000 - i64::from(bits < 0)) & !0xffff)
145        };
146        let left = round(e.x_bearing);
147        let top = round(e.y_bearing);
148        let right = round(e.x_bearing + e.width);
149        let bottom = round(e.y_bearing - e.height);
150        GlyphExtents {
151            x_bearing: self.scale_x(left),
152            y_bearing: self.scale_y(top),
153            width: self.scale_x(right - left),
154            height: self.scale_y(top - bottom),
155        }
156    }
157
158    fn scale_rect(&self, b: BoundingBox<F48Dot16>) -> BoundingBox<F26Dot6> {
159        BoundingBox {
160            x_min: self.scale_x(b.x_min),
161            y_min: self.scale_y(b.y_min),
162            x_max: self.scale_x(b.x_max),
163            y_max: self.scale_y(b.y_max),
164        }
165    }
166
167    fn ppem(&self) -> Option<f32> {
168        Some(self.ppem)
169    }
170}
171
172/// Scales font units to `f32` pixels.
173///
174/// Nothing here rounds, so a measurement carrying a fraction keeps it, which
175/// [`Scale26Dot6`] does not.
176#[derive(Copy, Clone, Debug)]
177pub struct ScaleF32 {
178    x: f32,
179    y: f32,
180    ppem: f32,
181}
182
183impl ScaleF32 {
184    /// Creates a scale with a separate size for each axis.
185    pub fn new(x_ppem: f32, y_ppem: f32, units_per_em: u16) -> Self {
186        let per_unit = |ppem: f32| ppem / units_per_em.max(1) as f32;
187        Self {
188            x: per_unit(x_ppem),
189            y: per_unit(y_ppem),
190            ppem: x_ppem.max(y_ppem),
191        }
192    }
193
194    /// Creates a scale that maps `units_per_em` font units to `ppem` pixels.
195    pub fn from_ppem(ppem: f32, units_per_em: u16) -> Self {
196        Self::new(ppem, ppem, units_per_em)
197    }
198}
199
200impl Scale for ScaleF32 {
201    type Value = f32;
202
203    fn add(a: f32, b: f32) -> f32 {
204        a + b
205    }
206
207    fn sub(a: f32, b: f32) -> f32 {
208        a - b
209    }
210
211    fn half(value: f32) -> f32 {
212        value * 0.5
213    }
214
215    fn scale_x(&self, value: F48Dot16) -> f32 {
216        value.to_f32() * self.x
217    }
218
219    fn scale_y(&self, value: F48Dot16) -> f32 {
220        value.to_f32() * self.y
221    }
222
223    fn scale_glyph_extents(&self, e: GlyphExtents<F48Dot16>) -> GlyphExtents<f32> {
224        GlyphExtents {
225            x_bearing: self.scale_x(e.x_bearing),
226            y_bearing: self.scale_y(e.y_bearing),
227            width: self.scale_x(e.width),
228            height: self.scale_y(e.height),
229        }
230    }
231
232    fn scale_rect(&self, b: BoundingBox<F48Dot16>) -> BoundingBox<f32> {
233        BoundingBox {
234            x_min: self.scale_x(b.x_min),
235            y_min: self.scale_y(b.y_min),
236            x_max: self.scale_x(b.x_max),
237            y_max: self.scale_y(b.y_max),
238        }
239    }
240
241    fn ppem(&self) -> Option<f32> {
242        Some(self.ppem)
243    }
244}
245
246/// Measurements of a font as a whole, in a caller's own units.
247///
248/// A view rather than a set of scaled fields, so reading one measurement
249/// does not compute the rest. What carries no unit, such as the glyph count,
250/// is reported unchanged.
251#[derive(Clone, Copy)]
252pub struct ScaledMetrics<'a, S: Scale> {
253    metrics: &'a Metrics,
254    scale: S,
255}
256
257impl<'a, S: Scale> ScaledMetrics<'a, S> {
258    pub(crate) fn new(metrics: &'a Metrics, scale: S) -> Self {
259        Self { metrics, scale }
260    }
261
262    /// Returns the number of glyphs in the font.
263    #[inline]
264    pub fn num_glyphs(&self) -> u32 {
265        self.metrics.num_glyphs
266    }
267
268    /// Returns the size of the font's design em.
269    #[inline]
270    pub fn units_per_em(&self) -> u16 {
271        self.metrics.units_per_em
272    }
273
274    /// Returns the box enclosing every glyph in the font.
275    #[inline]
276    pub fn bounds(&self) -> BoundingBox<S::Value> {
277        self.scale.scale_rect(self.metrics.bounds)
278    }
279
280    /// Returns the ascender, descender and gap for horizontal text.
281    ///
282    /// Resolved as [`Metrics::h_line`] resolves it, with each end
283    /// scaled on its own so that a caller subtracting them gets the height
284    /// the glyph metrics report for a font that stacks by this line.
285    #[inline]
286    pub fn h_line(&self) -> Option<LineBox<S::Value>> {
287        self.metrics.h_line().map(|line| self.scale_line_y(line))
288    }
289
290    /// Returns the line `hhea` provides.
291    #[inline]
292    pub fn hhea_line(&self) -> Option<LineBox<S::Value>> {
293        self.metrics.hhea_line.map(|line| self.scale_line_y(line))
294    }
295
296    /// Returns the typographic line `OS/2` provides.
297    #[inline]
298    pub fn typo_line(&self) -> Option<LineBox<S::Value>> {
299        self.metrics.typo_line.map(|line| self.scale_line_y(line))
300    }
301
302    /// Returns whether the font asks for its typographic line to be read.
303    #[inline]
304    pub fn use_typo_metrics(&self) -> bool {
305        self.metrics.use_typo_metrics
306    }
307
308    /// Returns the line `vhea` provides, where the font has one.
309    ///
310    /// A vertical line runs across the page, so its measurements scale along
311    /// x where a horizontal line's scale along y.
312    #[inline]
313    pub fn vhea_line(&self) -> Option<LineBox<S::Value>> {
314        self.metrics.vhea_line.map(|line| LineBox {
315            ascender: self.scale.scale_x(line.ascender),
316            descender: self.scale.scale_x(line.descender),
317            line_gap: self.scale.scale_x(line.line_gap),
318        })
319    }
320
321    /// Returns the height of a lowercase letter, where the font provides one.
322    #[inline]
323    pub fn x_height(&self) -> Option<S::Value> {
324        self.scaled_y(self.metrics.x_height)
325    }
326
327    /// Returns the height of a capital letter, where the font provides one.
328    #[inline]
329    pub fn cap_height(&self) -> Option<S::Value> {
330        self.scaled_y(self.metrics.cap_height)
331    }
332
333    /// Returns the widest advance in the font, where it provides one.
334    #[inline]
335    pub fn max_h_advance(&self) -> Option<S::Value> {
336        self.metrics
337            .max_h_advance
338            .map(|value| self.scale.scale_x(value))
339    }
340
341    /// Returns the tallest advance in the font, where it provides one.
342    #[inline]
343    pub fn max_v_advance(&self) -> Option<S::Value> {
344        self.scaled_y(self.metrics.max_v_advance)
345    }
346
347    /// Returns the average advance in the font, where it provides one.
348    #[inline]
349    pub fn average_char_width(&self) -> Option<S::Value> {
350        self.metrics
351            .average_char_width
352            .map(|value| self.scale.scale_x(value))
353    }
354
355    /// Returns the line outside which the font asks not to be clipped.
356    #[inline]
357    pub fn win_line(&self) -> Option<LineExtents<S::Value>> {
358        self.metrics.win_line.map(|line| self.scale_line(line))
359    }
360
361    #[inline]
362    fn scale_line_y(&self, line: LineBox<F48Dot16>) -> LineBox<S::Value> {
363        LineBox {
364            ascender: self.scale.scale_y(line.ascender),
365            descender: self.scale.scale_y(line.descender),
366            line_gap: self.scale.scale_y(line.line_gap),
367        }
368    }
369
370    #[inline]
371    fn scale_line(&self, line: LineExtents<F48Dot16>) -> LineExtents<S::Value> {
372        LineExtents {
373            ascender: self.scale.scale_y(line.ascender),
374            descender: self.scale.scale_y(line.descender),
375        }
376    }
377
378    #[inline]
379    fn scaled_y(&self, value: Option<F48Dot16>) -> Option<S::Value> {
380        value.map(|value| self.scale.scale_y(value))
381    }
382}
383
384impl Metrics {
385    /// Returns these metrics in the units `scale` describes.
386    #[inline]
387    pub fn scaled<S: Scale>(&self, scale: S) -> ScaledMetrics<'_, S> {
388        ScaledMetrics::new(self, scale)
389    }
390
391    /// Returns these metrics in pixels at `ppem`, as `f32`.
392    #[inline]
393    pub fn scaled_f32(&self, ppem: f32) -> ScaledMetrics<'_, ScaleF32> {
394        self.scaled(ScaleF32::from_ppem(ppem, self.units_per_em))
395    }
396
397    /// Returns these metrics in pixels at `ppem`, in 26.6 fixed point.
398    #[inline]
399    pub fn scaled_26dot6(&self, ppem: f32) -> ScaledMetrics<'_, Scale26Dot6> {
400        self.scaled(Scale26Dot6::from_ppem(ppem, self.units_per_em))
401    }
402}
403
404/// Style measurements in a caller's own units.
405///
406/// Scaling is done when a measurement is requested. The italic angle and
407/// fixed-pitch flag have no length to scale and are returned unchanged.
408#[derive(Clone, Copy)]
409pub struct ScaledStyleMetrics<'a, S: Scale> {
410    metrics: &'a StyleMetrics,
411    scale: S,
412}
413
414impl<'a, S: Scale> ScaledStyleMetrics<'a, S> {
415    pub(crate) fn new(metrics: &'a StyleMetrics, scale: S) -> Self {
416        Self { metrics, scale }
417    }
418
419    /// Returns the suggested subscript em box and placement.
420    pub fn subscript(&self) -> Option<ScriptMetrics<S::Value>> {
421        self.metrics
422            .subscript
423            .map(|script| self.scale_script(script))
424    }
425
426    /// Returns the suggested superscript em box and placement.
427    pub fn superscript(&self) -> Option<ScriptMetrics<S::Value>> {
428        self.metrics
429            .superscript
430            .map(|script| self.scale_script(script))
431    }
432
433    /// Returns the suggested underline position and thickness.
434    pub fn underline(&self) -> Option<Decoration<S::Value>> {
435        self.metrics
436            .underline
437            .map(|decoration| self.scale_decoration(decoration))
438    }
439
440    /// Returns the suggested strikethrough position and thickness.
441    pub fn strike_through(&self) -> Option<Decoration<S::Value>> {
442        self.metrics
443            .strike_through
444            .map(|decoration| self.scale_decoration(decoration))
445    }
446
447    /// Returns the italic angle in degrees, unaffected by scaling.
448    pub fn italic_angle(&self) -> Option<Fixed> {
449        self.metrics.italic_angle
450    }
451
452    /// Returns whether the font reports a fixed pitch.
453    pub fn is_fixed_pitch(&self) -> Option<bool> {
454        self.metrics.is_fixed_pitch
455    }
456
457    fn scale_script(&self, script: ScriptMetrics) -> ScriptMetrics<S::Value> {
458        ScriptMetrics {
459            x_size: self.scale.scale_x(script.x_size),
460            y_size: self.scale.scale_y(script.y_size),
461            x_offset: self.scale.scale_x(script.x_offset),
462            y_offset: self.scale.scale_y(script.y_offset),
463        }
464    }
465
466    fn scale_decoration(&self, decoration: Decoration) -> Decoration<S::Value> {
467        Decoration {
468            position: self.scale.scale_y(decoration.position),
469            thickness: self.scale.scale_y(decoration.thickness),
470        }
471    }
472}
473
474impl StyleMetrics {
475    /// Returns these style measurements in the units `scale` describes.
476    pub fn scaled<S: Scale>(&self, scale: S) -> ScaledStyleMetrics<'_, S> {
477        ScaledStyleMetrics::new(self, scale)
478    }
479
480    /// Returns these style measurements in pixels at `ppem`, as `f32`.
481    #[inline]
482    pub fn scaled_f32(&self, ppem: f32) -> ScaledStyleMetrics<'_, ScaleF32> {
483        self.scaled(ScaleF32::from_ppem(ppem, self.units_per_em))
484    }
485
486    /// Returns these style measurements in pixels at `ppem`, in 26.6 fixed
487    /// point.
488    #[inline]
489    pub fn scaled_26dot6(&self, ppem: f32) -> ScaledStyleMetrics<'_, Scale26Dot6> {
490        self.scaled(Scale26Dot6::from_ppem(ppem, self.units_per_em))
491    }
492}
493
494/// Measurements of individual glyphs, in a caller's own units.
495///
496/// Holds borrows and a scale, so it is as cheap to make as the metrics it
497/// scales and can be taken per query. It remembers nothing between calls: a
498/// caller wanting measurements kept holds them itself.
499#[derive(Clone, Copy)]
500pub struct ScaledGlyphMetrics<'a, 'extents, S: Scale> {
501    metrics: GlyphMetrics<'a>,
502    scale: S,
503    line: Option<LineExtents<S::Value>>,
504    extents: Option<&'extents dyn Fn(GlyphId) -> Option<GlyphExtents<S::Value>>>,
505}
506
507impl<'a, 'extents, S: Scale> ScaledGlyphMetrics<'a, 'extents, S> {
508    pub(crate) fn new(metrics: GlyphMetrics<'a>, scale: S) -> Self {
509        Self {
510            metrics: metrics.with_bitmap_ppem(scale.ppem()),
511            scale,
512            line: None,
513            extents: None,
514        }
515    }
516
517    /// Measures against a line the caller supplies rather than the font's own.
518    ///
519    /// A caller that reads its line from elsewhere supplies it here, so the
520    /// metrics built on it agree with the rest of its layout. The line is in
521    /// the same units as everything else this type reports, so nothing is
522    /// converted back into font units.
523    ///
524    /// `None` leaves the font's own line in place, so a caller can pass
525    /// whatever it happens to have without first checking.
526    pub fn with_line_extents(self, line: Option<LineExtents<S::Value>>) -> Self {
527        Self { line, ..self }
528    }
529
530    /// Measures ink using the sources in `order`.
531    ///
532    /// See [`GlyphMetrics::with_extents_source_order`].
533    pub fn with_extents_source_order(self, order: GlyphSourceOrder) -> Self {
534        Self {
535            metrics: self.metrics.with_extents_source_order(order),
536            ..self
537        }
538    }
539
540    /// Measures ink by a function the caller supplies rather than the font.
541    ///
542    /// A caller that measures glyphs elsewhere supplies that here, so the
543    /// metrics built on ink agree with the rest of what it reports. The
544    /// function answers in the same units as everything else this type
545    /// reports, so nothing is converted back into font units. `None` from
546    /// the function means extents are unavailable; return
547    /// `Some(GlyphExtents::default())` for a present glyph with no ink.
548    ///
549    /// `None` leaves the font's own measurement in place, so a caller can
550    /// pass whatever it happens to have without first checking.
551    ///
552    /// The function is borrowed only for the lifetime of the returned view.
553    pub fn with_glyph_extents<'new>(
554        self,
555        extents: Option<&'new dyn Fn(GlyphId) -> Option<GlyphExtents<S::Value>>>,
556    ) -> ScaledGlyphMetrics<'a, 'new, S> {
557        ScaledGlyphMetrics {
558            metrics: self.metrics,
559            scale: self.scale,
560            line: self.line,
561            extents,
562        }
563    }
564
565    /// Returns the advance width of `glyph`.
566    #[inline]
567    pub fn h_advance(&self, glyph: GlyphId) -> S::Value {
568        self.scale.scale_x(self.metrics.h_advance_exact(glyph))
569    }
570
571    /// Writes the advance width of each glyph to its slot, in order.
572    #[inline]
573    pub fn h_advance_batched<'o, V: 'o>(
574        &self,
575        convert: impl Fn(S::Value) -> V,
576        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
577    ) {
578        self.metrics
579            .h_advance_batched(|value| convert(self.scale.scale_x(value)), glyphs);
580    }
581
582    /// Returns the advance height of `glyph`.
583    ///
584    /// A font with no vertical metrics stacks its glyphs by the line, the
585    /// only measurement here a caller can supply. One that has them is
586    /// measured by what it says, and a supplied line changes nothing.
587    #[inline]
588    pub fn v_advance(&self, glyph: GlyphId) -> S::Value {
589        // Only a font with no vertical metrics stacks by the line; one
590        // that has them is read below, supplied line or not.
591        match self.line_height_fallback() {
592            Some(height) => height,
593            None => self.scale.scale_y(self.metrics.v_advance_exact(glyph)),
594        }
595    }
596
597    /// Writes the advance height of each glyph to its slot, in order.
598    #[inline]
599    pub fn v_advance_batched<'o, V: 'o>(
600        &self,
601        convert: impl Fn(S::Value) -> V,
602        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
603    ) {
604        // Settled before the run, as the table to read is. Only a font
605        // with no vertical metrics reaches this, and then all its glyphs
606        // advance the same; one that has them is read below, supplied line
607        // or not.
608        if let Some(height) = self.line_height_fallback() {
609            for (_, out) in glyphs {
610                *out = convert(height);
611            }
612            return;
613        }
614        self.metrics
615            .v_advance_batched(|value| convert(self.scale.scale_y(value)), glyphs);
616    }
617
618    /// Returns the y coordinate of `glyph`'s vertical origin.
619    ///
620    /// This places the glyph relative to the pen in vertical text. The x
621    /// coordinate is half the advance width, so only y is returned.
622    ///
623    /// The origin comes from the first available of: `VORG`, the `glyf` top
624    /// phantom point when the font has vertical metrics, the ink centered in
625    /// the line, and the ascender. The last two use the supplied ink
626    /// measurement.
627    pub fn v_origin_y(&self, glyph: GlyphId) -> S::Value {
628        let mut origin = self.scale.scale_y(F48Dot16::ZERO);
629        self.v_origin_y_batched(|value| value, core::iter::once((glyph, &mut origin)));
630        origin
631    }
632
633    /// Writes the vertical origin of each glyph to its slot, in order.
634    ///
635    /// The line is measured only if a glyph reaches the extents fallback, and
636    /// then reused for the rest of the run.
637    pub fn v_origin_y_batched<'o, V: 'o>(
638        &self,
639        convert: impl Fn(S::Value) -> V,
640        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
641    ) {
642        let mut line = None;
643        self.metrics.v_origin_y_batched_with(
644            |glyph, origin| {
645                let value = match origin {
646                    Some(origin) => self.scale.scale_y(origin),
647                    None => self
648                        .origin_against_ink(glyph, *line.get_or_insert_with(|| self.origin_line())),
649                };
650                convert(value)
651            },
652            |glyphs| {
653                let line = self.origin_line();
654                self.extents_batched(
655                    |extents| convert(self.origin_from_extents(extents, line)),
656                    glyphs,
657                );
658            },
659            glyphs,
660        );
661    }
662
663    /// Returns where `glyph`'s ink sits.
664    ///
665    /// An empty glyph has zero extents. `None` means the glyph is unavailable
666    /// or its extents could not be read. A supplied extents function may use
667    /// `None` for any glyph it does not measure.
668    #[inline]
669    pub fn extents(&self, glyph: GlyphId) -> Option<GlyphExtents<S::Value>> {
670        match self.extents {
671            Some(extents) => extents(glyph),
672            None => {
673                let mut extents = None;
674                self.metrics.extents_batched(
675                    |value| value.map(|extents| self.scale.scale_glyph_extents(extents)),
676                    core::iter::once((glyph, &mut extents)),
677                );
678                extents
679            }
680        }
681    }
682
683    /// Writes where each glyph's ink sits to its slot, in order.
684    #[inline]
685    pub fn extents_batched<'o, V: 'o>(
686        &self,
687        convert: impl Fn(Option<GlyphExtents<S::Value>>) -> V,
688        glyphs: impl Iterator<Item = (GlyphId, &'o mut V)>,
689    ) {
690        // Settled before the run, as the source to read is: a supplied
691        // function answers for every glyph, and the font is never read.
692        if let Some(extents) = self.extents {
693            for (glyph, out) in glyphs {
694                *out = convert(extents(glyph));
695            }
696            return;
697        }
698        self.metrics.extents_batched(
699            |extents| convert(extents.map(|extents| self.scale.scale_glyph_extents(extents))),
700            glyphs,
701        );
702    }
703
704    /// The height a glyph takes from the line, where it takes one.
705    ///
706    /// `None` for two reasons that mean the same thing here: the font has
707    /// vertical metrics of its own, so the line is not what its glyphs stack
708    /// by; or it has no line either, which the unscaled metrics answer with
709    /// an em. A supplied line applies only in the first case, so supplying
710    /// one never overrides what a font says about itself.
711    ///
712    /// Each end is scaled before the two are subtracted, so a scale that
713    /// rounds rounds them as a caller supplying its own line would, and the
714    /// answer does not turn on whether one was supplied.
715    #[inline]
716    fn line_height_fallback(&self) -> Option<S::Value> {
717        if self.metrics.states_vertical_advances() {
718            return None;
719        }
720        let line = self.origin_line();
721        Some(S::sub(line.ascender, line.descender))
722    }
723
724    /// The line a glyph stacks by, in the units this type reports.
725    ///
726    /// The supplied line if there is one, and the font's otherwise. A font
727    /// with no line metrics gets one proportioned to its em.
728    fn origin_line(&self) -> LineExtents<S::Value> {
729        // A supplied line is reported as it was given: a caller stating its
730        // own has already decided which way its ends run.
731        if let Some(line) = self.line {
732            return line;
733        }
734        let line = self.metrics.origin_line();
735        // Each end is scaled, rather than the height between them, so a scale
736        // that rounds reports ends a caller can lay out against.
737        LineExtents {
738            ascender: self.scale.scale_y(line.ascender),
739            descender: self.scale.scale_y(line.descender),
740        }
741    }
742
743    /// The last two rungs of [`v_origin_y`](Self::v_origin_y), which measure
744    /// against the ink.
745    fn origin_against_ink(&self, glyph: GlyphId, line: LineExtents<S::Value>) -> S::Value {
746        self.origin_from_extents(self.extents(glyph), line)
747    }
748
749    fn origin_from_extents(
750        &self,
751        extents: Option<GlyphExtents<S::Value>>,
752        line: LineExtents<S::Value>,
753    ) -> S::Value {
754        let Some(extents) = extents else {
755            return line.ascender;
756        };
757        let line_height = S::sub(line.ascender, line.descender);
758        S::add(
759            extents.y_bearing,
760            S::half(S::sub(line_height, extents.height)),
761        )
762    }
763}
764
765#[cfg(test)]
766mod tests {
767    use super::*;
768    use crate::model::Blob;
769    use crate::model::Font;
770    use alloc::sync::Arc;
771    use types::{GlyphId, Tag};
772
773    /// Scales font units to pixels at a size, halving toward zero.
774    #[derive(Clone, Copy)]
775    struct Ppem {
776        ppem: f32,
777        upem: u16,
778    }
779
780    impl Scale for Ppem {
781        type Value = f32;
782
783        fn scale_x(&self, value: F48Dot16) -> f32 {
784            value.to_f32() * self.ppem / self.upem as f32
785        }
786
787        fn scale_y(&self, value: F48Dot16) -> f32 {
788            self.scale_x(value)
789        }
790
791        fn scale_glyph_extents(&self, extents: GlyphExtents<F48Dot16>) -> GlyphExtents<f32> {
792            GlyphExtents {
793                x_bearing: self.scale_x(extents.x_bearing),
794                y_bearing: self.scale_y(extents.y_bearing),
795                width: self.scale_x(extents.width),
796                height: self.scale_y(extents.height),
797            }
798        }
799
800        fn scale_rect(&self, b: BoundingBox<F48Dot16>) -> BoundingBox<f32> {
801            BoundingBox {
802                x_min: self.scale_x(b.x_min),
803                y_min: self.scale_y(b.y_min),
804                x_max: self.scale_x(b.x_max),
805                y_max: self.scale_y(b.y_max),
806            }
807        }
808
809        fn add(a: f32, b: f32) -> f32 {
810            a + b
811        }
812
813        fn sub(a: f32, b: f32) -> f32 {
814            a - b
815        }
816
817        fn half(value: f32) -> f32 {
818            value / 2.0
819        }
820    }
821
822    const STATIC: &[u8] = font_test_data::TINOS_SUBSET;
823    const VERT: &[u8] = font_test_data::MPLUS1CODE_VERTICAL_SUBSET;
824    /// States its vertical origins outright.
825    const VORG: &[u8] = font_test_data::VORG;
826
827    fn scaled(data: &[u8], ppem: f32) -> (Font, Ppem) {
828        let font = Font::new(data.to_vec(), 0).unwrap();
829        let upem = font.metrics().units_per_em;
830        (font, Ppem { ppem, upem })
831    }
832
833    fn line(ascender: f32, descender: f32) -> LineExtents<f32> {
834        LineExtents {
835            ascender,
836            descender,
837        }
838    }
839
840    /// Rounds every scaled value to a whole number, as a hinted scale does.
841    #[derive(Clone, Copy)]
842    struct Rounding(f32, u16);
843
844    impl Scale for Rounding {
845        type Value = f32;
846        fn scale_x(&self, value: F48Dot16) -> f32 {
847            (value.to_f32() * self.0 / self.1 as f32).round()
848        }
849        fn scale_y(&self, value: F48Dot16) -> f32 {
850            self.scale_x(value)
851        }
852        fn scale_glyph_extents(&self, e: GlyphExtents<F48Dot16>) -> GlyphExtents<f32> {
853            GlyphExtents {
854                x_bearing: self.scale_x(e.x_bearing),
855                y_bearing: self.scale_y(e.y_bearing),
856                width: self.scale_x(e.width),
857                height: self.scale_y(e.height),
858            }
859        }
860
861        fn scale_rect(&self, b: BoundingBox<F48Dot16>) -> BoundingBox<f32> {
862            BoundingBox {
863                x_min: self.scale_x(b.x_min),
864                y_min: self.scale_y(b.y_min),
865                x_max: self.scale_x(b.x_max),
866                y_max: self.scale_y(b.y_max),
867            }
868        }
869        fn add(a: f32, b: f32) -> f32 {
870            a + b
871        }
872        fn sub(a: f32, b: f32) -> f32 {
873            a - b
874        }
875        fn half(value: f32) -> f32 {
876            value / 2.0
877        }
878    }
879
880    #[test]
881    fn a_rounding_scale_rounds_the_ends_of_the_line_not_the_height() {
882        // A font with no `vmtx` stacks by the line, and the height comes
883        // from two ends. Scaling each before subtracting is what a caller
884        // stating its own line does, so doing it the other way round would
885        // answer differently depending on whether one was supplied. It shows
886        // up only where the scale rounds, and then by a whole unit.
887        let font = Font::new(STATIC.to_vec(), 0).unwrap();
888        let upem = font.metrics().units_per_em;
889        let from_font = font.metrics().h_line().unwrap().extents();
890        let mut rounded_apart = 0;
891        for ppem in [11.0f32, 12.0, 13.0, 14.0, 16.0, 19.0, 24.0] {
892            let scale = Rounding(ppem, upem);
893            let metrics = font.glyph_metrics().scaled(scale);
894            let ends = LineExtents {
895                ascender: scale.scale_y(from_font.ascender),
896                descender: scale.scale_y(from_font.descender),
897            };
898            let from_ends = Rounding::sub(ends.ascender, ends.descender);
899            assert_eq!(
900                metrics.v_advance(GlyphId::new(1)),
901                from_ends,
902                "at {ppem}ppem"
903            );
904            // And stating the same line back gives the same answer.
905            assert_eq!(
906                metrics
907                    .with_line_extents(Some(ends))
908                    .v_advance(GlyphId::new(1)),
909                from_ends,
910                "at {ppem}ppem, supplied"
911            );
912            // The other way round: subtract in design units, scale once.
913            if scale.scale_y(from_font.ascender - from_font.descender) != from_ends {
914                rounded_apart += 1;
915            }
916        }
917        assert!(
918            rounded_apart > 0,
919            "no size here rounds the two ways apart, so this proves nothing"
920        );
921    }
922
923    #[test]
924    fn the_scaled_line_is_the_one_the_glyph_metrics_stack_by() {
925        // The reason both views exist. A caller reading the line from the
926        // global metrics and subtracting its ends must land on the height
927        // the glyph metrics give a font that stacks by that line, or the two
928        // disagree about the same font at the same size.
929        let font = Font::new(STATIC.to_vec(), 0).unwrap();
930        let upem = font.metrics().units_per_em;
931        for ppem in [11.0f32, 12.0, 13.0, 16.0, 19.0, 24.0] {
932            let scale = Rounding(ppem, upem);
933            let line = font.metrics().scaled(scale).h_line().unwrap();
934            let height = Rounding::sub(line.ascender, line.descender);
935            let glyphs = font.glyph_metrics().scaled(scale);
936            for gid in (0..font.num_glyphs()).map(GlyphId::new) {
937                assert_eq!(glyphs.v_advance(gid), height, "glyph {gid} at {ppem}ppem");
938            }
939        }
940    }
941
942    #[test]
943    fn every_measurement_is_the_unscaled_one_through_the_scale() {
944        // One case per field of `Metrics`, so that a field added there
945        // without one here is a gap someone has to notice.
946        let font = Font::new(STATIC.to_vec(), 0).unwrap();
947        let global = font.metrics();
948        let scale = Ppem {
949            ppem: 16.0,
950            upem: global.units_per_em,
951        };
952        let scaled = global.scaled(scale);
953        let y = |line: LineBox<F48Dot16>| LineBox {
954            ascender: scale.scale_y(line.ascender),
955            descender: scale.scale_y(line.descender),
956            line_gap: scale.scale_y(line.line_gap),
957        };
958        let x = |line: LineBox<F48Dot16>| LineBox {
959            ascender: scale.scale_x(line.ascender),
960            descender: scale.scale_x(line.descender),
961            line_gap: scale.scale_x(line.line_gap),
962        };
963        assert_eq!(scaled.bounds(), scale.scale_rect(global.bounds));
964        assert_eq!(scaled.hhea_line(), global.hhea_line.map(y));
965        assert_eq!(scaled.typo_line(), global.typo_line.map(y));
966        assert_eq!(scaled.vhea_line(), global.vhea_line.map(x));
967        assert_eq!(scaled.h_line(), global.h_line().map(y));
968        assert_eq!(
969            scaled.win_line(),
970            global.win_line.map(|l| LineExtents {
971                ascender: scale.scale_y(l.ascender),
972                descender: scale.scale_y(l.descender),
973            })
974        );
975        assert_eq!(scaled.x_height(), global.x_height.map(|v| scale.scale_y(v)));
976        assert_eq!(
977            scaled.cap_height(),
978            global.cap_height.map(|v| scale.scale_y(v))
979        );
980        assert_eq!(
981            scaled.max_h_advance(),
982            global.max_h_advance.map(|v| scale.scale_x(v))
983        );
984        assert_eq!(
985            scaled.max_v_advance(),
986            global.max_v_advance.map(|v| scale.scale_y(v))
987        );
988        assert_eq!(
989            scaled.average_char_width(),
990            global.average_char_width.map(|v| scale.scale_x(v))
991        );
992        // What carries no unit is reported unchanged.
993        assert_eq!(scaled.units_per_em(), global.units_per_em);
994        assert_eq!(scaled.num_glyphs(), global.num_glyphs);
995        assert_eq!(scaled.use_typo_metrics(), global.use_typo_metrics);
996        // And this font provides enough for the comparison to mean something.
997        assert!(scaled.hhea_line().is_some() && scaled.typo_line().is_some());
998    }
999
1000    #[test]
1001    fn the_clipping_line_reports_its_descent_as_a_position() {
1002        // `OS/2` gives the descent as a positive number below the baseline.
1003        // Reporting it as a position keeps the pair readable as a line.
1004        let font = Font::new(STATIC.to_vec(), 0).unwrap();
1005        let global = font.metrics();
1006        let win = global
1007            .win_line
1008            .expect("this font provides clipping metrics");
1009        assert!(win.ascender > F48Dot16::ZERO);
1010        assert!(win.descender < F48Dot16::ZERO);
1011        let scale = Ppem {
1012            ppem: 16.0,
1013            upem: global.units_per_em,
1014        };
1015        let scaled = global.scaled(scale).win_line().unwrap();
1016        assert_eq!(scaled.ascender, scale.scale_y(win.ascender));
1017        assert_eq!(scaled.descender, scale.scale_y(win.descender));
1018    }
1019
1020    #[test]
1021    fn the_two_scales_agree_within_what_each_can_hold() {
1022        // The same font at the same size, in two number types. They differ by
1023        // what each can represent, so the coarser rounds the finer rather
1024        // than disagreeing with it.
1025        let font = Font::new(STATIC.to_vec(), 0).unwrap();
1026        let upem = font.metrics().units_per_em;
1027        let coarse = font
1028            .glyph_metrics()
1029            .scaled(Scale26Dot6::from_ppem(16.0, upem));
1030        let fine = font.glyph_metrics().scaled(ScaleF32::from_ppem(16.0, upem));
1031        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1032            // 26.6 resolves 1/64 pixel, so it lands within one of those.
1033            let apart = (coarse.h_advance(gid).to_f32() - fine.h_advance(gid)).abs();
1034            assert!(apart < 1.0 / 32.0, "glyph {gid} is {apart} apart");
1035        }
1036    }
1037
1038    #[test]
1039    fn a_scale_with_a_size_per_axis_uses_each_in_its_own_direction() {
1040        // The axes are apart because HarfBuzz keeps them apart, so a scale
1041        // built from two factors has to use the right one in each direction.
1042        let font = Font::new(VERT.to_vec(), 0).unwrap();
1043        let upem = font.metrics().units_per_em;
1044        let em = upem as f32;
1045        let wide = ScaleF32::new(em * 2.0, em, upem);
1046        let tall = ScaleF32::new(em, em * 2.0, upem);
1047        let plain = ScaleF32::new(em, em, upem);
1048        let gid = GlyphId::new(1);
1049        let (w, t, p) = (
1050            font.glyph_metrics().scaled(wide),
1051            font.glyph_metrics().scaled(tall),
1052            font.glyph_metrics().scaled(plain),
1053        );
1054        assert_eq!(w.h_advance(gid), p.h_advance(gid) * 2.0);
1055        assert_eq!(w.v_advance(gid), p.v_advance(gid));
1056        assert_eq!(t.v_advance(gid), p.v_advance(gid) * 2.0);
1057        assert_eq!(t.h_advance(gid), p.h_advance(gid));
1058    }
1059
1060    #[test]
1061    fn freetype_scales_whole_design_units() {
1062        // What separates the two: FreeType multiplies a whole design unit,
1063        // so a measurement carrying a fraction loses it before the multiply.
1064        // A varied advance carries one, which is why the other exists.
1065        let upem = 1000;
1066        let fraction = F48Dot16::from_f64(10.5);
1067        assert_eq!(
1068            Scale26Dot6::from_ppem(upem as f32, upem).scale_x(fraction),
1069            F26Dot6::from_f64(11.0)
1070        );
1071        assert_eq!(
1072            ScaleF32::from_ppem(upem as f32, upem).scale_x(fraction),
1073            10.5
1074        );
1075    }
1076
1077    #[test]
1078    fn a_rounding_scale_rounds_extents_corners_before_size() {
1079        let extents = GlyphExtents {
1080            x_bearing: F48Dot16::from_f64(10.6),
1081            y_bearing: F48Dot16::from_f64(-0.5),
1082            width: F48Dot16::from_f64(9.8),
1083            height: F48Dot16::from_f64(20.0),
1084        };
1085        let scale = Scale26Dot6::from_ppem(1000.0, 1000);
1086        assert_eq!(
1087            scale.scale_glyph_extents(extents),
1088            GlyphExtents {
1089                x_bearing: F26Dot6::from_f64(11.0),
1090                y_bearing: F26Dot6::from_f64(-1.0),
1091                width: F26Dot6::from_f64(9.0),
1092                height: F26Dot6::from_f64(20.0),
1093            }
1094        );
1095
1096        let below_half = F48Dot16::from_bits((100_000 << 16) + 0x7fff);
1097        let extents = GlyphExtents {
1098            x_bearing: below_half,
1099            y_bearing: F48Dot16::from_i32(10),
1100            width: F48Dot16::from_i32(2),
1101            height: F48Dot16::from_i32(2),
1102        };
1103        assert_eq!(
1104            scale.scale_glyph_extents(extents).x_bearing,
1105            F26Dot6::from_i32(100_000)
1106        );
1107    }
1108
1109    #[test]
1110    fn halving_rounds_toward_negative_infinity_in_26_6() {
1111        // Documented, because HarfBuzz halves two ways within one function
1112        // and a caller writing its own needs to know which it is getting.
1113        assert_eq!(
1114            Scale26Dot6::half(F26Dot6::from_bits(-3)),
1115            F26Dot6::from_bits(-2)
1116        );
1117        assert_eq!(ScaleF32::half(-3.0), -1.5);
1118    }
1119
1120    #[test]
1121    fn a_scaled_advance_is_the_design_unit_one_through_the_scale() {
1122        let (font, scale) = scaled(STATIC, 16.0);
1123        let metrics = font.glyph_metrics();
1124        let scaled = metrics.scaled(scale);
1125        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1126            assert_eq!(
1127                scaled.h_advance(gid),
1128                scale.scale_x(metrics.h_advance_exact(gid)),
1129                "glyph {gid}"
1130            );
1131        }
1132    }
1133
1134    #[test]
1135    fn a_stated_line_measures_a_font_that_states_none() {
1136        // This font has no `vmtx`, so its glyphs stack by the line.
1137        let (font, scale) = scaled(STATIC, 16.0);
1138        let metrics = font.glyph_metrics().scaled(scale);
1139        let supplied = metrics.with_line_extents(Some(line(12.0, -4.0)));
1140        let gid = GlyphId::new(1);
1141        assert_eq!(supplied.v_advance(gid), 16.0);
1142        assert!(metrics.v_advance(gid) != 16.0);
1143    }
1144
1145    #[test]
1146    fn a_font_that_states_its_own_ignores_the_line() {
1147        // This one has `vmtx`, so the line is not what its glyphs stack by,
1148        // and stating one changes nothing.
1149        let (font, scale) = scaled(VERT, 16.0);
1150        let metrics = font.glyph_metrics().scaled(scale);
1151        let supplied = metrics.with_line_extents(Some(line(99.0, -99.0)));
1152        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1153            assert_eq!(
1154                metrics.v_advance(gid),
1155                supplied.v_advance(gid),
1156                "glyph {gid}"
1157            );
1158        }
1159    }
1160
1161    #[test]
1162    fn stating_nothing_is_stating_nothing() {
1163        let (font, scale) = scaled(STATIC, 16.0);
1164        let metrics = font.glyph_metrics().scaled(scale);
1165        let gid = GlyphId::new(1);
1166        assert_eq!(
1167            metrics.with_line_extents(None).v_advance(gid),
1168            metrics.v_advance(gid)
1169        );
1170    }
1171
1172    #[test]
1173    fn a_batch_agrees_with_one_at_a_time() {
1174        for (data, supplied) in [
1175            (STATIC, None),
1176            (STATIC, Some(line(12.0, -4.0))),
1177            (VERT, None),
1178            (VERT, Some(line(12.0, -4.0))),
1179        ] {
1180            let (font, scale) = scaled(data, 16.0);
1181            let metrics = font
1182                .glyph_metrics()
1183                .scaled(scale)
1184                .with_line_extents(supplied);
1185            let gids: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
1186            let mut h = vec![0.0f32; gids.len()];
1187            let mut v = vec![0.0f32; gids.len()];
1188            metrics.h_advance_batched(|value| value, gids.iter().copied().zip(h.iter_mut()));
1189            metrics.v_advance_batched(|value| value, gids.iter().copied().zip(v.iter_mut()));
1190            for (i, gid) in gids.iter().enumerate() {
1191                assert_eq!(metrics.h_advance(*gid), h[i], "h, glyph {gid}");
1192                assert_eq!(metrics.v_advance(*gid), v[i], "v, glyph {gid}");
1193            }
1194        }
1195    }
1196
1197    #[test]
1198    fn a_scaled_box_is_the_font_box_through_the_scale() {
1199        let (font, scale) = scaled(STATIC, 16.0);
1200        let metrics = font.glyph_metrics();
1201        let scaled = metrics.scaled(scale);
1202        let mut measured = 0;
1203        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1204            let expected = metrics
1205                .extents_exact(gid)
1206                .map(|extents| scale.scale_glyph_extents(extents));
1207            assert_eq!(scaled.extents(gid), expected, "glyph {gid}");
1208            measured += expected.is_some() as u32;
1209        }
1210        assert!(measured > 0);
1211    }
1212
1213    #[test]
1214    fn a_supplied_measurement_replaces_the_font_one() {
1215        // A caller measuring ink elsewhere answers for every glyph, including
1216        // one it cannot measure where the font states a box.
1217        let (font, scale) = scaled(STATIC, 16.0);
1218        let ink = GlyphExtents {
1219            x_bearing: 1.0,
1220            y_bearing: 2.0,
1221            width: 3.0,
1222            height: 4.0,
1223        };
1224        let supplied = |glyph: GlyphId| (glyph.to_u32() % 2 == 0).then_some(ink);
1225        let metrics = font.glyph_metrics().scaled(scale);
1226        let overridden = metrics.with_glyph_extents(Some(&supplied));
1227        let mut replaced = 0;
1228        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1229            assert_eq!(overridden.extents(gid), supplied(gid), "glyph {gid}");
1230            replaced += (metrics.extents(gid) != supplied(gid)) as u32;
1231        }
1232        assert!(replaced > 0, "the font already agreed everywhere");
1233    }
1234
1235    #[test]
1236    fn a_supplied_measurement_answers_a_whole_run() {
1237        let (font, scale) = scaled(STATIC, 16.0);
1238        let ink = GlyphExtents {
1239            x_bearing: 5.0,
1240            y_bearing: 6.0,
1241            width: 7.0,
1242            height: 8.0,
1243        };
1244        let supplied = |glyph: GlyphId| (glyph.to_u32() % 3 != 0).then_some(ink);
1245        let metrics = font
1246            .glyph_metrics()
1247            .scaled(scale)
1248            .with_glyph_extents(Some(&supplied));
1249        let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
1250        let mut batched = vec![None; glyphs.len()];
1251        metrics.extents_batched(
1252            |extents| extents,
1253            glyphs.iter().copied().zip(batched.iter_mut()),
1254        );
1255        for (gid, batched) in glyphs.iter().copied().zip(batched) {
1256            assert_eq!(batched, supplied(gid), "glyph {gid}");
1257        }
1258    }
1259
1260    #[test]
1261    fn no_supplied_measurement_leaves_the_font_in_place() {
1262        let (font, scale) = scaled(STATIC, 16.0);
1263        let metrics = font.glyph_metrics().scaled(scale);
1264        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1265            assert_eq!(
1266                metrics.with_glyph_extents(None).extents(gid),
1267                metrics.extents(gid),
1268                "glyph {gid}"
1269            );
1270        }
1271    }
1272
1273    #[test]
1274    fn a_supplied_measurement_can_borrow_for_one_query() {
1275        let (font, scale) = scaled(STATIC, 16.0);
1276        let metrics = font.glyph_metrics().scaled(scale);
1277        let glyph = GlyphId::new(1);
1278        let original = metrics.extents(glyph);
1279        let supplied = {
1280            let ink = GlyphExtents {
1281                x_bearing: 1.0,
1282                y_bearing: 2.0,
1283                width: 3.0,
1284                height: 4.0,
1285            };
1286            let callback = |_: GlyphId| Some(ink);
1287            metrics.with_glyph_extents(Some(&callback)).extents(glyph)
1288        };
1289        assert_ne!(supplied, original);
1290        assert_eq!(metrics.extents(glyph), original);
1291    }
1292
1293    #[test]
1294    fn vorg_answers_ahead_of_everything_else() {
1295        // A font stating this states it outright, whatever its ink or its
1296        // vertical metrics would otherwise say.
1297        let (font, scale) = scaled(VORG, 16.0);
1298        let metrics = font.glyph_metrics().scaled(scale);
1299        let direct = crate::FontRef::new(VORG).unwrap();
1300        let vorg = crate::TableProvider::vorg(&direct).unwrap();
1301        for gid in (0..4).map(GlyphId::new) {
1302            assert_eq!(
1303                metrics.v_origin_y(gid),
1304                scale.scale_y(F48Dot16::from_i32(vorg.vertical_origin_y(gid) as i32)),
1305                "glyph {gid}"
1306            );
1307        }
1308        // And it answers even where the ink is measured differently.
1309        let ink = GlyphExtents {
1310            x_bearing: 0.0,
1311            y_bearing: 999.0,
1312            width: 1.0,
1313            height: 1.0,
1314        };
1315        let supplied = |_: GlyphId| Some(ink);
1316        let overridden = metrics.with_glyph_extents(Some(&supplied));
1317        for gid in (0..4).map(GlyphId::new) {
1318            assert_eq!(overridden.v_origin_y(gid), metrics.v_origin_y(gid));
1319        }
1320    }
1321
1322    #[test]
1323    fn vmtx_uses_the_glyf_top_phantom_point() {
1324        // With no `VORG`, the top phantom point includes the top side bearing.
1325        let (font, scale) = scaled(VERT, 16.0);
1326        let glyph_metrics = font.glyph_metrics();
1327        let metrics = glyph_metrics.scaled(scale);
1328        assert!(glyph_metrics.stated_v_origin(GlyphId::new(1)).is_none());
1329        let mut checked = 0;
1330        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1331            let (Some(extents), Some(bearing)) = (
1332                glyph_metrics.extents_exact(gid),
1333                glyph_metrics.stated_v_bearing(gid),
1334            ) else {
1335                continue;
1336            };
1337            assert_eq!(
1338                metrics.v_origin_y(gid),
1339                scale.scale_y(glyph_metrics.glyf_v_origin(gid).unwrap()),
1340                "glyph {gid}"
1341            );
1342            assert_eq!(
1343                glyph_metrics.glyf_v_origin(gid).unwrap(),
1344                extents.y_bearing + bearing,
1345                "glyph {gid}"
1346            );
1347            checked += 1;
1348        }
1349        assert!(checked > 0, "no glyph stated both");
1350
1351        let supplied = |_: GlyphId| {
1352            Some(GlyphExtents {
1353                x_bearing: 0.0,
1354                y_bearing: 999.0,
1355                width: 1.0,
1356                height: 1.0,
1357            })
1358        };
1359        let overridden = metrics.with_glyph_extents(Some(&supplied));
1360        assert_eq!(
1361            overridden.v_origin_y(GlyphId::new(1)),
1362            metrics.v_origin_y(GlyphId::new(1))
1363        );
1364    }
1365
1366    #[test]
1367    fn without_vmtx_the_ink_is_centered_in_the_line() {
1368        // The leftover of the line, half above the ink and half below it.
1369        let (font, scale) = scaled(STATIC, 16.0);
1370        let glyph_metrics = font.glyph_metrics();
1371        let metrics = glyph_metrics.scaled(scale);
1372        assert!(glyph_metrics.stated_v_bearing(GlyphId::new(1)).is_none());
1373        let line = glyph_metrics.line_extents().unwrap();
1374        let height = scale.scale_y(line.ascender) - scale.scale_y(line.descender);
1375        let mut checked = 0;
1376        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1377            let Some(extents) = glyph_metrics.extents_exact(gid) else {
1378                continue;
1379            };
1380            let extents = scale.scale_glyph_extents(extents);
1381            assert_eq!(
1382                metrics.v_origin_y(gid),
1383                extents.y_bearing + (height - extents.height) / 2.0,
1384                "glyph {gid}"
1385            );
1386            checked += 1;
1387        }
1388        assert!(checked > 0);
1389    }
1390
1391    #[test]
1392    fn an_empty_glyph_is_centered_but_unavailable_extents_use_the_ascender() {
1393        let (font, scale) = scaled(STATIC, 16.0);
1394        let glyph_metrics = font.glyph_metrics();
1395        let metrics = glyph_metrics.scaled(scale);
1396        let line = metrics.origin_line();
1397        let absent = |_: GlyphId| None;
1398        let overridden = metrics.with_glyph_extents(Some(&absent));
1399        let mut checked = 0;
1400        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1401            if glyph_metrics.extents(gid) != Some(GlyphExtents::default()) {
1402                continue;
1403            }
1404            assert_eq!(
1405                metrics.v_origin_y(gid),
1406                (line.ascender - line.descender) / 2.0,
1407                "glyph {gid}"
1408            );
1409            assert_eq!(overridden.v_origin_y(gid), line.ascender);
1410            checked += 1;
1411        }
1412        assert!(checked > 0, "no glyph was empty");
1413    }
1414
1415    #[test]
1416    fn a_supplied_measurement_moves_the_origin_with_it() {
1417        // The two middle rungs read the ink, so a caller substituting its own
1418        // gets an origin consistent with it rather than with the font.
1419        let (font, scale) = scaled(STATIC, 16.0);
1420        let metrics = font.glyph_metrics().scaled(scale);
1421        let ink = GlyphExtents {
1422            x_bearing: 0.0,
1423            y_bearing: 100.0,
1424            width: 10.0,
1425            height: 20.0,
1426        };
1427        let supplied = |_: GlyphId| Some(ink);
1428        let overridden = metrics.with_glyph_extents(Some(&supplied));
1429        let line = metrics.origin_line();
1430        let height = line.ascender - line.descender;
1431        let expected = ink.y_bearing + (height - ink.height) / 2.0;
1432        let mut moved = 0;
1433        for gid in (0..font.num_glyphs()).map(GlyphId::new) {
1434            assert_eq!(overridden.v_origin_y(gid), expected, "glyph {gid}");
1435            moved += (metrics.v_origin_y(gid) != expected) as u32;
1436        }
1437        assert!(moved > 0, "the font already agreed everywhere");
1438
1439        let gids: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
1440        let mut origins = vec![0.0; gids.len()];
1441        overridden.v_origin_y_batched(
1442            |origin| origin,
1443            gids.iter().copied().zip(origins.iter_mut()),
1444        );
1445        assert!(origins.iter().all(|origin| *origin == expected));
1446    }
1447
1448    #[test]
1449    fn a_supplied_line_moves_the_origin_with_it() {
1450        // The centered rung measures against the line, so a caller laying out
1451        // against its own line gets an origin in that line.
1452        let (font, scale) = scaled(STATIC, 16.0);
1453        let metrics = font.glyph_metrics().scaled(scale);
1454        let taller = metrics.with_line_extents(Some(line(100.0, -20.0)));
1455        let gid = (0..font.num_glyphs())
1456            .map(GlyphId::new)
1457            .find(|gid| font.glyph_metrics().extents(*gid).is_some())
1458            .unwrap();
1459        assert_ne!(taller.v_origin_y(gid), metrics.v_origin_y(gid));
1460    }
1461
1462    #[test]
1463    fn a_run_of_origins_agrees_with_one_at_a_time() {
1464        for data in [VORG, VERT, STATIC] {
1465            let (font, scale) = scaled(data, 16.0);
1466            let metrics = font.glyph_metrics().scaled(scale);
1467            let glyphs: Vec<_> = (0..font.num_glyphs()).map(GlyphId::new).collect();
1468            let mut batched = vec![0.0; glyphs.len()];
1469            metrics.v_origin_y_batched(
1470                |origin| origin,
1471                glyphs.iter().copied().zip(batched.iter_mut()),
1472            );
1473            for (gid, batched) in glyphs.iter().copied().zip(batched) {
1474                assert_eq!(metrics.v_origin_y(gid), batched, "glyph {gid}");
1475            }
1476        }
1477    }
1478
1479    #[test]
1480    fn a_font_stating_no_line_is_given_one_proportioned_to_its_em() {
1481        // Four fifths of the em above the baseline and the rest below, which
1482        // is what a glyph with no ink to measure then sits on.
1483        let font = font_without(&[b"hhea", b"OS/2"]);
1484        let glyph_metrics = font.glyph_metrics();
1485        assert!(glyph_metrics.line_extents().is_none());
1486        let em = font.units_per_em();
1487        assert!(
1488            em > 0,
1489            "the em itself has to survive for this to mean anything"
1490        );
1491
1492        let scale = Ppem {
1493            ppem: 16.0,
1494            upem: em,
1495        };
1496        let metrics = glyph_metrics.scaled(scale);
1497        let ascender = F48Dot16::from_f64(em as f64 * 0.8);
1498        let empty = empty_glyph(&font);
1499        assert_eq!(
1500            metrics.v_origin_y(empty),
1501            scale.scale_y(F48Dot16::from_i32(em as i32)) / 2.0
1502        );
1503        let absent = |_: GlyphId| None;
1504        assert_eq!(
1505            metrics.with_glyph_extents(Some(&absent)).v_origin_y(empty),
1506            scale.scale_y(ascender)
1507        );
1508        // And the line it implies is a whole em tall, so a glyph with no
1509        // vertical metrics still advances by one.
1510        assert_eq!(
1511            metrics.v_advance(empty),
1512            scale.scale_y(ascender) - scale.scale_y(ascender - F48Dot16::from_i32(em as i32))
1513        );
1514    }
1515
1516    /// A font serving every table but the ones named.
1517    fn font_without(hide: &'static [&[u8; 4]]) -> Font {
1518        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag: Tag| {
1519            if hide.iter().any(|hidden| Tag::new(hidden) == tag) {
1520                return None;
1521            }
1522            let font = crate::FontRef::new(STATIC).ok()?;
1523            Some(Blob::from(font.table_data(tag)?.as_bytes().to_vec()))
1524        });
1525        Font::new(source, 0).unwrap()
1526    }
1527
1528    #[test]
1529    fn the_ends_of_the_line_are_signed_whatever_the_font_says() {
1530        // Fonts state these both ways round: some as positions, where the
1531        // descender runs down and is negative, and some as magnitudes, where
1532        // it is not. All three describe one line and have to measure as one.
1533        let stated = font_with_hhea_ends(1600, -400);
1534        let scale = Ppem {
1535            ppem: 16.0,
1536            upem: stated.units_per_em(),
1537        };
1538        let empty = empty_glyph(&stated);
1539        let ink = drawn_glyph(&stated);
1540        let expected = stated.glyph_metrics().scaled(scale);
1541        for (ascender, descender) in [(1600, 400), (-1600, 400), (-1600, -400)] {
1542            let font = font_with_hhea_ends(ascender, descender);
1543            let actual = font.glyph_metrics().scaled(scale);
1544            // The line itself, through the two rungs that read it.
1545            assert_eq!(
1546                actual.v_origin_y(empty),
1547                expected.v_origin_y(empty),
1548                "ascender {ascender}, descender {descender}"
1549            );
1550            assert_eq!(
1551                actual.v_origin_y(ink),
1552                expected.v_origin_y(ink),
1553                "ascender {ascender}, descender {descender}"
1554            );
1555            // And the height a glyph advances by, which is that line too.
1556            assert_eq!(
1557                actual.v_advance(ink),
1558                expected.v_advance(ink),
1559                "ascender {ascender}, descender {descender}"
1560            );
1561        }
1562        // And the line really is the one the font states, not a fallback that
1563        // would agree by accident.
1564        assert_eq!(
1565            expected.v_origin_y(empty),
1566            scale.scale_y(F48Dot16::from_i32(1000))
1567        );
1568    }
1569
1570    // A bitmap is measured from the strike for the scale's size, and a scale
1571    // with no size measures from the largest strike.
1572    #[test]
1573    fn the_scale_chooses_the_bitmap_strike() {
1574        let font = Font::new(font_test_data::CBDT, 0).unwrap();
1575        let glyph = GlyphId::new(2);
1576        let upem = font.units_per_em();
1577        let metrics = font.glyph_metrics();
1578        for scale in [
1579            ScaleF32::from_ppem(12.0, upem),
1580            ScaleF32::new(12.0, 10.0, upem),
1581        ] {
1582            assert_eq!(scale.ppem(), Some(12.0));
1583            let expected = metrics
1584                .with_bitmap_ppem(Some(12.0))
1585                .extents_exact(glyph)
1586                .map(|extents| scale.scale_glyph_extents(extents));
1587            assert_eq!(metrics.scaled(scale).extents(glyph), expected);
1588        }
1589        assert_eq!(Scale26Dot6::new(10.0, 14.0, upem).ppem(), Some(14.0));
1590        // The 12 ppem strike and the largest measure the glyph differently.
1591        assert_ne!(
1592            metrics.with_bitmap_ppem(Some(12.0)).extents_exact(glyph),
1593            metrics.with_bitmap_ppem(None).extents_exact(glyph)
1594        );
1595        // A scale of its own with no size gets the largest strike.
1596        struct NoSize(ScaleF32);
1597        impl Scale for NoSize {
1598            type Value = f32;
1599            fn add(a: f32, b: f32) -> f32 {
1600                ScaleF32::add(a, b)
1601            }
1602            fn sub(a: f32, b: f32) -> f32 {
1603                ScaleF32::sub(a, b)
1604            }
1605            fn half(value: f32) -> f32 {
1606                ScaleF32::half(value)
1607            }
1608            fn scale_x(&self, value: F48Dot16) -> f32 {
1609                self.0.scale_x(value)
1610            }
1611            fn scale_y(&self, value: F48Dot16) -> f32 {
1612                self.0.scale_y(value)
1613            }
1614            fn scale_glyph_extents(&self, extents: GlyphExtents<F48Dot16>) -> GlyphExtents<f32> {
1615                self.0.scale_glyph_extents(extents)
1616            }
1617            fn scale_rect(&self, bounds: BoundingBox<F48Dot16>) -> BoundingBox<f32> {
1618                self.0.scale_rect(bounds)
1619            }
1620        }
1621        let no_size = NoSize(ScaleF32::from_ppem(12.0, upem));
1622        let largest = metrics
1623            .with_bitmap_ppem(None)
1624            .extents_exact(glyph)
1625            .map(|extents| no_size.scale_glyph_extents(extents));
1626        assert_eq!(metrics.scaled(no_size).extents(glyph), largest);
1627    }
1628
1629    #[test]
1630    fn a_supplied_line_is_taken_as_it_was_given() {
1631        // The signing is what HarfBuzz applies to what it reads from a font.
1632        // A caller stating its own line has already decided, so an ascender
1633        // below the baseline stays there.
1634        let (font, scale) = scaled(STATIC, 16.0);
1635        let metrics = font
1636            .glyph_metrics()
1637            .scaled(scale)
1638            .with_line_extents(Some(line(-100.0, 20.0)));
1639        let absent = |_: GlyphId| None;
1640        assert_eq!(
1641            metrics
1642                .with_glyph_extents(Some(&absent))
1643                .v_origin_y(empty_glyph(&font)),
1644            -100.0
1645        );
1646    }
1647
1648    /// `STATIC` with the ends of its `hhea` line replaced, and no `OS/2` to
1649    /// be read ahead of it.
1650    fn font_with_hhea_ends(ascender: i16, descender: i16) -> Font {
1651        let source: Arc<dyn Fn(Tag) -> Option<Blob> + Send + Sync> = Arc::new(move |tag: Tag| {
1652            if tag == Tag::new(b"OS/2") {
1653                return None;
1654            }
1655            let font = crate::FontRef::new(STATIC).ok()?;
1656            let mut data = font.table_data(tag)?.as_bytes().to_vec();
1657            if tag == Tag::new(b"hhea") {
1658                // Both ends sit right after the version.
1659                data.get_mut(4..6)?.copy_from_slice(&ascender.to_be_bytes());
1660                data.get_mut(6..8)?
1661                    .copy_from_slice(&descender.to_be_bytes());
1662            }
1663            Some(Blob::from(data))
1664        });
1665        Font::new(source, 0).unwrap()
1666    }
1667
1668    /// A present glyph of `font` with no outline and a zero box.
1669    fn empty_glyph(font: &Font) -> GlyphId {
1670        let metrics = font.glyph_metrics();
1671        (0..font.num_glyphs())
1672            .map(GlyphId::new)
1673            .find(|gid| metrics.extents(*gid) == Some(GlyphExtents::default()))
1674            .expect("no glyph has a zero box")
1675    }
1676
1677    /// A glyph of `font` with ink to measure against.
1678    fn drawn_glyph(font: &Font) -> GlyphId {
1679        let metrics = font.glyph_metrics();
1680        (0..font.num_glyphs())
1681            .map(GlyphId::new)
1682            .find(|gid| metrics.extents(*gid).is_some())
1683            .expect("no glyph drew anything")
1684    }
1685}