Skip to main content

read_fonts/model/glyph/outline/
glyphs.rs

1//! Glyph outlines loaded from the sources selected by a source order.
2
3use super::{
4    cff::CffGlyph,
5    glyf::{draw_plan, plan_buffer_size, FontAt, GlyfContext, GlyfGlyph, Precision},
6    type1::Type1Glyph,
7    varc::{self, VarcGlyph},
8    ControlBoundsSink, DrawMemory, DrawOptions, DrawnMetrics, PathSink, PointsFromPath,
9    UnscaledOutlineSink,
10};
11use crate::model::Error;
12use crate::{
13    mem::with_scratch,
14    model::{
15        glyph::{outline::OutlineSource, GlyphSource, GlyphSourceOrder},
16        metrics::GlyphExtents,
17        Font,
18    },
19    ps::{cff::CffFontRef, type1::Type1Instance},
20    tables::glyf::outline::{OutlineContext, OutlinePlan},
21    types::{BoundingBox, F2Dot14, GlyphId},
22};
23use yoke::Yokeable;
24
25/// Provides access to the glyph outlines in a font.
26///
27/// The sources used for drawing are selected when this is created. It is
28/// cheap to create and copy because it only borrows tables that the font
29/// has already parsed.
30#[derive(Clone, Copy, Yokeable)]
31pub struct OutlineGlyphs<'a> {
32    font: &'a Font,
33    base: Base<'a>,
34    varc: Option<&'a varc::Outlines<'a>>,
35}
36
37/// The source used to draw glyphs that are not `VARC` composites.
38#[derive(Clone, Copy)]
39enum Base<'a> {
40    None,
41    Glyf(GlyfContext<'a>),
42    Cff(&'a CffFontRef<'a>, OutlineSource),
43    Type1(Type1Instance<'a>),
44}
45
46impl<'a> OutlineGlyphs<'a> {
47    pub(crate) fn new(font: &'a Font, order: GlyphSourceOrder) -> Self {
48        let mut base = Base::None;
49        let mut varc = None;
50        if let Some(type1) = font.type1_instance() {
51            if order.contains(GlyphSource::Type1) {
52                base = Base::Type1(type1);
53            }
54            return Self { font, base, varc };
55        }
56        // A standalone CFF font has no tables, so it finds no composites or
57        // `glyf` here, only its charstrings as `CFF`.
58        for source in order
59            .iter()
60            .filter_map(|source| OutlineSource::try_from(source).ok())
61        {
62            match source {
63                // Covers only some glyphs, so it layers over a base that
64                // comes after it rather than replacing one.
65                OutlineSource::Varc if varc.is_none() => varc = font.varc_outlines(),
66                OutlineSource::Glyf => {
67                    if let Some(context) = GlyfContext::new(font) {
68                        base = Base::Glyf(context);
69                        break;
70                    }
71                }
72                OutlineSource::Cff2 | OutlineSource::Cff => {
73                    if let Some(cff) = font.charstrings(source) {
74                        base = Base::Cff(cff, source);
75                        break;
76                    }
77                }
78                _ => {}
79            }
80        }
81        // The components of a composite come from the base, so without one
82        // there is nothing to draw.
83        if matches!(base, Base::None) {
84            varc = None;
85        }
86        Self { font, base, varc }
87    }
88
89    /// Returns the font that contains these outlines.
90    pub fn font(&self) -> &'a Font {
91        self.font
92    }
93
94    /// Returns the table that provides the outlines.
95    ///
96    /// This is [`OutlineSource::Varc`] if `VARC` composites are drawn. The
97    /// composites cover only some glyphs, and the rest come from the
98    /// [`underlying_source`](Self::underlying_source). Returns `None` if the
99    /// font has no outlines from the requested sources.
100    pub fn source(&self) -> Option<OutlineSource> {
101        if self.varc.is_some() {
102            return Some(OutlineSource::Varc);
103        }
104        self.underlying_source()
105    }
106
107    /// Returns the table that draws the glyphs `VARC` does not cover and the
108    /// components of those it does.
109    ///
110    /// This is the same as [`source`](Self::source) unless `VARC`
111    /// composites are drawn, and is never [`OutlineSource::Varc`]. Returns
112    /// `None` if the font has no outlines from the requested sources.
113    pub fn underlying_source(&self) -> Option<OutlineSource> {
114        match self.base {
115            Base::None => None,
116            Base::Glyf(_) => Some(OutlineSource::Glyf),
117            Base::Cff(_, source) => Some(source),
118            Base::Type1(_) => Some(OutlineSource::Type1),
119        }
120    }
121
122    /// Returns true if `VARC` composites are drawn.
123    pub(crate) fn has_varc(&self) -> bool {
124        self.varc.is_some()
125    }
126
127    /// Returns true if `glyph` is drawn as a `VARC` composite.
128    pub(crate) fn varc_covers(&self, glyph: GlyphId) -> bool {
129        self.varc.is_some_and(|varc| varc.covers(glyph))
130    }
131
132    /// Returns the `CFF` or `CFF2` font used to draw the base glyphs.
133    ///
134    /// Returns `None` if the base source is not `CFF` or `CFF2`.
135    #[doc(hidden)]
136    pub fn cff_font(&self) -> Option<&'a CffFontRef<'a>> {
137        match self.base {
138            Base::Cff(cff, _) => Some(cff),
139            _ => None,
140        }
141    }
142
143    /// Returns true if the font has no outlines from the requested sources.
144    pub fn is_empty(&self) -> bool {
145        matches!(self.base, Base::None)
146    }
147
148    /// Returns the number of glyphs in the font.
149    ///
150    /// Returns zero if the font has no outlines from the requested sources.
151    pub fn len(&self) -> u32 {
152        if self.is_empty() {
153            0
154        } else {
155            self.font.num_glyphs()
156        }
157    }
158
159    /// Returns the outline for `glyph`, or `None` if it has no outline or
160    /// its data in the base source cannot be read.
161    ///
162    /// If `VARC` is enabled and the table covers the glyph, this returns a
163    /// `VARC` composite. Otherwise, the outline comes from the base source.
164    /// A composite that is malformed or nests past the recursion or edge
165    /// limits returns an error when it is drawn.
166    pub fn get(&self, glyph: GlyphId) -> Option<OutlineGlyph<'a>> {
167        if glyph.to_u32() >= self.len() {
168            return None;
169        }
170        if let Some(varc) = self.varc {
171            if let Some(outline) = varc.outline(glyph) {
172                return Some(OutlineGlyph::Varc(VarcGlyph {
173                    outlines: varc,
174                    outline,
175                    base: *self,
176                }));
177            }
178        }
179        self.get_base(glyph)
180    }
181
182    /// Returns the outline for `glyph` from the base source only.
183    fn get_base(&self, glyph: GlyphId) -> Option<OutlineGlyph<'a>> {
184        match self.base {
185            Base::None => None,
186            Base::Glyf(context) => GlyfGlyph::new(context, glyph).map(OutlineGlyph::Glyf),
187            Base::Cff(cff, source) => {
188                let subfont_index = cff.subfont_index(glyph)?;
189                let subfont = self.font.cff_subfont(source, subfont_index)?;
190                Some(OutlineGlyph::Cff(CffGlyph {
191                    cff,
192                    coords: self.font.normalized_coords(),
193                    glyph,
194                    subfont_index,
195                    subfont,
196                    source,
197                }))
198            }
199            Base::Type1(font) => Some(OutlineGlyph::Type1(Type1Glyph { font, glyph })),
200        }
201    }
202
203    /// Returns an iterator over every glyph that has an outline, in glyph
204    /// identifier order.
205    pub fn iter(&self) -> impl Iterator<Item = (GlyphId, OutlineGlyph<'a>)> + 'a {
206        let this = *self;
207        (0..this.len())
208            .map(GlyphId::new)
209            .filter_map(move |glyph_id| this.get(glyph_id).map(|glyph| (glyph_id, glyph)))
210    }
211
212    /// Draws `glyph` from the base source at `coords`.
213    ///
214    /// This is used for the components of a composite. The `coords` may
215    /// differ from the font's location.
216    pub(super) fn draw_base(
217        &self,
218        glyph: GlyphId,
219        coords: &[F2Dot14],
220        options: &DrawOptions,
221        precision: Precision,
222        buf: &mut [u8],
223        mut sink: &mut dyn PathSink,
224    ) -> Result<(), Error> {
225        let font = self.font;
226        let at_own_location = same_location(coords, font.normalized_coords());
227        match self.base {
228            Base::None => Err(Error::MissingResource),
229            Base::Glyf(context) => {
230                if at_own_location {
231                    let plan = OutlinePlan::new(&context, glyph)?;
232                    draw_plan(&plan, &context, options, precision, buf, sink)?;
233                } else {
234                    let at = FontAt { context, coords };
235                    let plan = OutlinePlan::new(&at, glyph)?;
236                    draw_plan(&plan, &at, options, precision, buf, sink)?;
237                }
238                Ok(())
239            }
240            Base::Cff(cff, source) => {
241                let index = cff.subfont_index(glyph).ok_or(Error::MissingResource)?;
242                let subfont = if at_own_location {
243                    font.cff_subfont(source, index).ok_or(Error::Malformed)?
244                } else {
245                    cff.subfont(index, coords)?
246                };
247                cff.draw(&subfont, glyph, coords, options.ppem, &mut sink)?;
248                Ok(())
249            }
250            Base::Type1(type1) => {
251                type1.draw(glyph, options.ppem, &mut sink)?;
252                Ok(())
253            }
254        }
255    }
256
257    /// Returns the size in bytes of the working memory needed to draw
258    /// `glyph` from the base source.
259    pub(super) fn base_buffer_size(&self, glyph: GlyphId) -> usize {
260        match self.base {
261            Base::Glyf(context) => {
262                let Ok(mut plan) = OutlinePlan::new(&context, glyph) else {
263                    return 0;
264                };
265                // A component may be drawn away from the font's location, so
266                // the deltas need room whenever the font has any.
267                plan.applies_variations = context.has_gvar();
268                plan_buffer_size(&plan)
269            }
270            _ => 0,
271        }
272    }
273}
274
275/// Returns true if two coordinate lists define the same location.
276///
277/// A missing axis is treated as its default value.
278fn same_location(a: &[F2Dot14], b: &[F2Dot14]) -> bool {
279    let (short, long) = if a.len() <= b.len() { (a, b) } else { (b, a) };
280    short == &long[..short.len()] && long[short.len()..].iter().all(|c| *c == F2Dot14::ZERO)
281}
282
283/// A glyph outline from a specific source.
284///
285/// Each variant contains the data needed to draw that source: a `glyf`
286/// plan, a CFF subfont, or a composite tree. To hint an outline, match on
287/// the variant and use the source's own types.
288#[derive(Clone)]
289pub enum OutlineGlyph<'a> {
290    /// A TrueType outline.
291    Glyf(GlyfGlyph<'a>),
292    /// A `CFF` or `CFF2` charstring.
293    Cff(CffGlyph<'a>),
294    /// A Type 1 charstring.
295    Type1(Type1Glyph<'a>),
296    /// A `VARC` composite.
297    Varc(VarcGlyph<'a>),
298}
299
300impl OutlineGlyph<'_> {
301    /// Returns the glyph identifier.
302    pub fn glyph(&self) -> GlyphId {
303        match self {
304            Self::Glyf(glyph) => glyph.glyph(),
305            Self::Cff(glyph) => glyph.glyph(),
306            Self::Type1(glyph) => glyph.glyph(),
307            Self::Varc(glyph) => glyph.glyph(),
308        }
309    }
310
311    /// Returns the table that provides the outline.
312    pub fn source(&self) -> OutlineSource {
313        match self {
314            Self::Glyf(_) => OutlineSource::Glyf,
315            Self::Cff(glyph) => glyph.source(),
316            Self::Type1(_) => OutlineSource::Type1,
317            Self::Varc(_) => OutlineSource::Varc,
318        }
319    }
320
321    /// Returns the size in bytes of the working memory needed by
322    /// [`draw`](Self::draw).
323    ///
324    /// This is zero for charstrings.
325    pub fn required_memory_size(&self) -> usize {
326        match self {
327            Self::Glyf(glyph) => glyph.required_memory_size(),
328            Self::Cff(_) | Self::Type1(_) => 0,
329            Self::Varc(glyph) => glyph.required_memory_size(),
330        }
331    }
332
333    /// Draws the unhinted outline into `sink` with the given options.
334    ///
335    /// Supplied `memory` must hold at least
336    /// [`required_memory_size`](Self::required_memory_size) bytes, or the draw
337    /// fails with [`Error::InsufficientMemory`].
338    ///
339    /// Returns any metrics provided by the outline.
340    pub fn draw(
341        &self,
342        options: &DrawOptions,
343        memory: DrawMemory<'_>,
344        sink: &mut dyn PathSink,
345    ) -> Result<DrawnMetrics, Error> {
346        self.draw_dyn(options, Precision::Fixed, memory.into_buffer(), sink)
347    }
348
349    /// Implements [`draw`](Self::draw) with the given arithmetic precision.
350    ///
351    /// Takes a trait object so the sources are compiled once rather than
352    /// once per sink type.
353    fn draw_dyn(
354        &self,
355        options: &DrawOptions,
356        precision: Precision,
357        memory: Option<&mut [u8]>,
358        sink: &mut dyn PathSink,
359    ) -> Result<DrawnMetrics, Error> {
360        match self {
361            Self::Cff(glyph) => glyph.draw(options, sink),
362            Self::Type1(glyph) => glyph.draw(options, sink),
363            Self::Glyf(glyph) => with_memory(
364                memory,
365                || glyph.required_memory_size(),
366                |buf| glyph.draw(options, precision, buf, sink),
367            ),
368            Self::Varc(glyph) => with_memory(
369                memory,
370                || glyph.required_memory_size(),
371                |buf| glyph.draw(options, precision, buf, sink),
372            ),
373        }
374    }
375
376    /// Returns the size in bytes of the working memory needed by
377    /// [`draw_unscaled`](Self::draw_unscaled).
378    pub fn required_memory_size_unscaled(&self) -> usize {
379        match self {
380            Self::Glyf(glyph) => glyph.required_memory_size_unscaled(),
381            Self::Cff(_) | Self::Type1(_) => 0,
382            Self::Varc(glyph) => glyph.required_memory_size(),
383        }
384    }
385
386    /// Sends the outline's points to `sink` in font units.
387    ///
388    /// A `glyf` outline provides its points as stored, with variations
389    /// applied. Other sources are drawn as a path in font units and converted
390    /// to points. When a contour closes at its start point, that point is not
391    /// repeated.
392    ///
393    /// `memory` works as in [`draw`](Self::draw), with a size given by
394    /// [`required_memory_size_unscaled`](Self::required_memory_size_unscaled).
395    ///
396    /// A `VARC` composite has no advance of its own, so the returned metrics
397    /// have none. Its advance is the one in the font's metrics tables, from
398    /// [`GlyphMetrics::h_advance`](crate::model::metrics::GlyphMetrics::h_advance).
399    pub fn draw_unscaled(
400        &self,
401        memory: DrawMemory<'_>,
402        sink: &mut dyn UnscaledOutlineSink,
403    ) -> Result<DrawnMetrics, Error> {
404        self.draw_unscaled_dyn(memory.into_buffer(), sink)
405    }
406
407    fn draw_unscaled_dyn(
408        &self,
409        memory: Option<&mut [u8]>,
410        sink: &mut dyn UnscaledOutlineSink,
411    ) -> Result<DrawnMetrics, Error> {
412        let options = DrawOptions::default();
413        match self {
414            Self::Glyf(glyph) => match memory {
415                Some(buf) => glyph.draw_unscaled(buf, sink),
416                None => with_scratch(glyph.required_memory_size_unscaled(), |buf| {
417                    glyph.draw_unscaled(buf, sink)
418                }),
419            },
420            Self::Cff(glyph) => {
421                let mut pen = PointsFromPath::new(sink);
422                let metrics = glyph.draw(&options, &mut pen)?;
423                pen.finish();
424                Ok(metrics)
425            }
426            Self::Type1(glyph) => {
427                let mut pen = PointsFromPath::new(sink);
428                let metrics = glyph.draw(&options, &mut pen)?;
429                pen.finish();
430                Ok(metrics)
431            }
432            Self::Varc(glyph) => {
433                let mut pen = PointsFromPath::new(sink);
434                let metrics = match memory {
435                    Some(buf) => glyph.draw(&options, Precision::Fixed, buf, &mut pen)?,
436                    None => with_scratch(glyph.required_memory_size(), |buf| {
437                        glyph.draw(&options, Precision::Fixed, buf, &mut pen)
438                    })?,
439                };
440                pen.finish();
441                Ok(metrics)
442            }
443        }
444    }
445
446    /// Returns the extents of the outline in font units.
447    ///
448    /// The extents are computed from the control points, so a curve's
449    /// control points are included rather than the curve itself. An empty
450    /// glyph has zero extents.
451    pub fn extents(&self) -> Result<GlyphExtents<f32>, Error> {
452        Ok(self
453            .control_box()?
454            .map(GlyphExtents::from)
455            .unwrap_or_default())
456    }
457
458    /// Returns the bounding box of the control points.
459    ///
460    /// This is used for clips that intersect boxes.
461    pub(crate) fn control_box(&self) -> Result<Option<BoundingBox<f32>>, Error> {
462        let mut pen = ControlBoundsSink::new();
463        // HarfBuzz keeps every fraction of a varied point.
464        self.draw_dyn(&DrawOptions::default(), Precision::Float, None, &mut pen)?;
465        Ok(pen.bounding_box())
466    }
467}
468
469/// Runs `draw` with the caller's `memory`, or with scratch memory of the
470/// size `size` returns when the caller has none.
471fn with_memory<R>(
472    memory: Option<&mut [u8]>,
473    size: impl FnOnce() -> usize,
474    mut draw: impl FnMut(&mut [u8]) -> R,
475) -> R {
476    match memory {
477        Some(buf) => draw(buf),
478        None => with_scratch(size(), draw),
479    }
480}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485    use crate::{
486        model::glyph::outline::{NullSink, PathElement, SvgSink},
487        FontRef, TableProvider,
488    };
489    use alloc::vec::Vec;
490
491    fn font(data: &[u8]) -> Font {
492        Font::new(data.to_vec(), 0).unwrap()
493    }
494
495    #[test]
496    fn glyf_font_draws_from_glyf() {
497        let font = font(font_test_data::VAZIRMATN_VAR);
498        let glyphs = font.outline_glyphs();
499        assert_eq!(glyphs.source(), Some(OutlineSource::Glyf));
500        assert!(!glyphs.has_varc());
501        let glyph = glyphs.get(GlyphId::new(1)).unwrap();
502        assert!(matches!(glyph, OutlineGlyph::Glyf(_)));
503        let mut pen = SvgSink::new();
504        let metrics = glyph
505            .draw(&DrawOptions::default(), DrawMemory::Scratch, &mut pen)
506            .unwrap();
507        assert!(!pen.as_ref().is_empty());
508        assert!(metrics.h_advance.is_some());
509    }
510
511    #[test]
512    fn unscaled_fixed_and_float_agree_on_a_static_font() {
513        let font = font(font_test_data::VAZIRMATN_VAR);
514        let glyphs = font.outline_glyphs();
515        for (glyph_id, glyph) in glyphs.iter() {
516            let mut fixed = Vec::<PathElement>::new();
517            glyph
518                .draw(&DrawOptions::default(), DrawMemory::Scratch, &mut fixed)
519                .unwrap();
520            let mut float = Vec::<PathElement>::new();
521            glyph
522                .draw_dyn(&DrawOptions::default(), Precision::Float, None, &mut float)
523                .unwrap();
524            assert_eq!(fixed, float, "glyph {glyph_id}");
525        }
526    }
527
528    #[test]
529    fn caller_memory_too_small_is_refused() {
530        let font = font(font_test_data::VAZIRMATN_VAR);
531        let glyph = font.outline_glyphs().get(GlyphId::new(1)).unwrap();
532        let options = DrawOptions::default();
533        let needed = glyph.required_memory_size();
534        assert!(needed > 0);
535        let mut small = alloc::vec![0u8; needed / 2];
536        let mut pen = NullSink;
537        assert!(matches!(
538            glyph.draw(&options, DrawMemory::Buffer(&mut small), &mut pen),
539            Err(Error::InsufficientMemory)
540        ));
541        let mut enough = alloc::vec![0u8; needed];
542        glyph
543            .draw(&options, DrawMemory::Buffer(&mut enough), &mut pen)
544            .unwrap();
545    }
546
547    #[test]
548    fn cff_font_draws_from_cff() {
549        let font = font(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED);
550        let glyphs = font.outline_glyphs();
551        assert_eq!(glyphs.source(), Some(OutlineSource::Cff));
552        let glyph = glyphs.get(GlyphId::new(1)).unwrap();
553        let OutlineGlyph::Cff(cff) = &glyph else {
554            panic!("expected a charstring");
555        };
556        assert_eq!(cff.source(), OutlineSource::Cff);
557        assert_eq!(glyph.required_memory_size(), 0);
558        let mut pen = SvgSink::new();
559        let metrics = glyph
560            .draw(&DrawOptions::default(), DrawMemory::Scratch, &mut pen)
561            .unwrap();
562        assert!(!pen.as_ref().is_empty());
563        // A CFF charstring states its width.
564        assert!(metrics.h_advance.is_some());
565    }
566
567    // A standalone CFF font draws from its charstrings as an SFNT font draws
568    // from its CFF table, and measures the same.
569    #[test]
570    fn standalone_cff_draws_like_its_table() {
571        let sfnt = font(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED);
572        let raw = FontRef::new(font_test_data::NOTO_SERIF_DISPLAY_TRIMMED)
573            .unwrap()
574            .cff()
575            .unwrap()
576            .offset_data()
577            .as_bytes()
578            .to_vec();
579        let bare = Font::new(raw, 0).unwrap();
580        let (theirs, ours) = (sfnt.outline_glyphs(), bare.outline_glyphs());
581        assert_eq!(ours.source(), Some(OutlineSource::Cff));
582        assert!(ours.cff_font().is_some());
583        assert!(!ours.has_varc());
584        assert_eq!(ours.len(), theirs.len());
585        // An order without CFF finds nothing to draw.
586        assert!(bare
587            .outline_glyphs_with(GlyphSourceOrder::new(&[GlyphSource::Glyf]))
588            .is_empty());
589        let (their_metrics, our_metrics) = (sfnt.glyph_metrics(), bare.glyph_metrics());
590        let mut drawn = 0;
591        for (glyph, outline) in theirs.iter() {
592            let mine = ours.get(glyph).unwrap();
593            assert_eq!(mine.source(), OutlineSource::Cff);
594            for ppem in [None, Some(16.0)] {
595                let options = DrawOptions::default().with_ppem(ppem);
596                let (mut expected, mut actual) = (SvgSink::new(), SvgSink::new());
597                let want = outline
598                    .draw(&options, DrawMemory::Scratch, &mut expected)
599                    .unwrap();
600                let got = mine
601                    .draw(&options, DrawMemory::Scratch, &mut actual)
602                    .unwrap();
603                assert_eq!(
604                    actual.as_ref(),
605                    expected.as_ref(),
606                    "glyph {glyph} at {ppem:?}"
607                );
608                assert_eq!(got, want, "glyph {glyph} at {ppem:?}");
609            }
610            assert_eq!(
611                mine.extents().unwrap(),
612                outline.extents().unwrap(),
613                "glyph {glyph}"
614            );
615            assert_eq!(
616                our_metrics.extents_exact(glyph),
617                their_metrics.extents_exact(glyph),
618                "glyph {glyph}"
619            );
620            drawn += 1;
621        }
622        assert!(drawn > 0);
623    }
624
625    #[test]
626    fn order_without_the_fonts_format_is_empty() {
627        let font = font(font_test_data::VAZIRMATN_VAR);
628        let glyphs = font.outline_glyphs_with(GlyphSourceOrder::new(&[GlyphSource::Cff]));
629        assert!(glyphs.is_empty());
630        assert_eq!(glyphs.len(), 0);
631        assert!(glyphs.get(GlyphId::new(1)).is_none());
632        assert_eq!(glyphs.iter().count(), 0);
633    }
634
635    #[test]
636    fn varc_covers_its_glyphs_and_falls_back_to_the_base() {
637        let font = font(font_test_data::varc::CJK_6868);
638        let glyphs = font.outline_glyphs();
639        assert!(glyphs.has_varc());
640        assert_eq!(glyphs.source(), Some(OutlineSource::Varc));
641        assert_eq!(glyphs.underlying_source(), Some(OutlineSource::Glyf));
642        let gid = font.charmap().map_unicode(0x6868_u32).unwrap();
643        let glyph = glyphs.get(gid).unwrap();
644        assert_eq!(glyph.source(), OutlineSource::Varc);
645        // Glyph zero is a plain outline.
646        assert_eq!(
647            glyphs.get(GlyphId::new(0)).unwrap().source(),
648            OutlineSource::Glyf
649        );
650        // Without VARC in the order, the composite's base glyph is drawn.
651        let base_only = font.outline_glyphs_with(GlyphSourceOrder::BASE_OUTLINES);
652        assert!(!base_only.has_varc());
653        assert_eq!(base_only.source(), Some(OutlineSource::Glyf));
654        assert_eq!(base_only.underlying_source(), Some(OutlineSource::Glyf));
655        assert_eq!(base_only.get(gid).unwrap().source(), OutlineSource::Glyf);
656    }
657
658    #[test]
659    fn varc_draws_in_the_memory_it_was_sized_for() {
660        let font = font(font_test_data::varc::CJK_6868);
661        let glyphs = font.outline_glyphs();
662        let mut composites = 0;
663        for (glyph_id, glyph) in glyphs.iter() {
664            let OutlineGlyph::Varc(_) = &glyph else {
665                continue;
666            };
667            composites += 1;
668            let needed = glyph.required_memory_size();
669            assert!(needed > 0, "glyph {glyph_id}");
670            let options = DrawOptions::default();
671            let mut enough = alloc::vec![0u8; needed];
672            glyph
673                .draw(&options, DrawMemory::Buffer(&mut enough), &mut NullSink)
674                .unwrap_or_else(|error| panic!("glyph {glyph_id}: {error}"));
675            // Half the block is short for the walk or for a leaf, and either
676            // is refused rather than drawn into.
677            let mut short = alloc::vec![0u8; needed / 2];
678            assert!(
679                matches!(
680                    glyph.draw(&options, DrawMemory::Buffer(&mut short), &mut NullSink),
681                    Err(Error::InsufficientMemory)
682                ),
683                "glyph {glyph_id}"
684            );
685        }
686        assert!(composites > 0);
687    }
688
689    #[test]
690    fn varc_draws_the_freetype_path_head() {
691        let font = font(font_test_data::varc::CJK_6868);
692        let gid = font.charmap().map_unicode(0x6868_u32).unwrap();
693        let glyph = font.outline_glyphs().get(gid).unwrap();
694        let mut pen = Vec::<PathElement>::new();
695        glyph
696            .draw(&DrawOptions::default(), DrawMemory::Scratch, &mut pen)
697            .unwrap();
698        let head: Vec<_> = pen
699            .iter()
700            .take(3)
701            .map(|el| match *el {
702                PathElement::MoveTo { x, y } => alloc::format!("M{x:.2},{y:.2}"),
703                PathElement::QuadTo { cx0, cy0, x, y } => {
704                    alloc::format!("Q{cx0:.2},{cy0:.2} {x:.2},{y:.2}")
705                }
706                _ => panic!("unexpected element"),
707            })
708            .collect();
709        assert_eq!(
710            head,
711            [
712                "M454.56,574.77",
713                "Q477.58,585.56 499.80,598.25",
714                "Q522.02,610.95 543.36,625.16"
715            ]
716        );
717    }
718
719    #[test]
720    fn varc_at_a_location_draws_with_its_deltas() {
721        let font = font(font_test_data::varc::DELTA_PRECISION);
722        let instance = font
723            .instance_builder()
724            .normalized_coords([F2Dot14::ONE, F2Dot14::ZERO])
725            .build();
726        let glyphs = instance.outline_glyphs();
727        for (gid, expected) in [
728            (2, "M600,0 L800,0 L600,200 Z"),
729            (3, "M664,0 L864,0 L664,200 Z"),
730            (4, "M101,0 L201,0 L101,100 Z"),
731        ] {
732            let glyph = glyphs.get(GlyphId::new(gid)).unwrap();
733            let mut pen = SvgSink::new();
734            glyph
735                .draw(&DrawOptions::default(), DrawMemory::Scratch, &mut pen)
736                .unwrap();
737            assert_eq!(pen.as_ref(), expected, "glyph {gid}");
738        }
739    }
740
741    #[test]
742    fn varc_without_a_variation_store_does_not_panic() {
743        use crate::types::Tag;
744
745        let mut bytes = font_test_data::varc::CJK_6868.to_vec();
746        let varc_off = {
747            let font = FontRef::new(&bytes).unwrap();
748            font.table_directory()
749                .table_records()
750                .iter()
751                .find(|rec| rec.tag() == Tag::new(b"VARC"))
752                .expect("VARC table present")
753                .offset() as usize
754        };
755        // Null the nullable multi_var_store_offset.
756        for b in &mut bytes[varc_off + 8..varc_off + 12] {
757            *b = 0;
758        }
759        let font = font(&bytes);
760        let gid = font.charmap().map_unicode(0x6868_u32).unwrap();
761        let glyph = font.outline_glyphs().get(gid).expect("covered VARC glyph");
762        let mut pen = NullSink;
763        // Must complete without panicking (Ok, or a graceful error).
764        let _ = glyph.draw(&DrawOptions::default(), DrawMemory::Scratch, &mut pen);
765    }
766
767    #[test]
768    fn extents_of_a_glyf_glyph_match_its_header() {
769        let font = font(font_test_data::VAZIRMATN_VAR);
770        let (glyf, loca) = font.selected_glyf_loca().unwrap();
771        let glyphs = font.outline_glyphs();
772        let mut checked = 0;
773        for (gid, glyph) in glyphs.iter() {
774            let Some(header) = loca.get_glyf(gid, glyf).unwrap() else {
775                continue;
776            };
777            if header.number_of_contours() == 0 {
778                continue;
779            }
780            let extents = glyph.extents().unwrap();
781            assert_eq!(extents.x_bearing, header.x_min() as f32);
782            assert_eq!(extents.y_bearing, header.y_max() as f32);
783            assert_eq!(extents.width, (header.x_max() - header.x_min()) as f32);
784            assert_eq!(extents.height, (header.y_max() - header.y_min()) as f32);
785            checked += 1;
786        }
787        assert!(checked > 0);
788    }
789
790    #[test]
791    fn glyph_past_the_font_is_none() {
792        let font = font(font_test_data::VAZIRMATN_VAR);
793        let glyphs = font.outline_glyphs();
794        assert!(glyphs.get(GlyphId::new(glyphs.len())).is_none());
795        let font = font_ref_glyph_count(font_test_data::VAZIRMATN_VAR);
796        assert_eq!(glyphs.len(), font);
797    }
798
799    fn font_ref_glyph_count(data: &[u8]) -> u32 {
800        FontRef::new(data).unwrap().maxp().unwrap().num_glyphs() as u32
801    }
802}