1use 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#[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#[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 for source in order
59 .iter()
60 .filter_map(|source| OutlineSource::try_from(source).ok())
61 {
62 match source {
63 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 if matches!(base, Base::None) {
84 varc = None;
85 }
86 Self { font, base, varc }
87 }
88
89 pub fn font(&self) -> &'a Font {
91 self.font
92 }
93
94 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 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 pub(crate) fn has_varc(&self) -> bool {
124 self.varc.is_some()
125 }
126
127 pub(crate) fn varc_covers(&self, glyph: GlyphId) -> bool {
129 self.varc.is_some_and(|varc| varc.covers(glyph))
130 }
131
132 #[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 pub fn is_empty(&self) -> bool {
145 matches!(self.base, Base::None)
146 }
147
148 pub fn len(&self) -> u32 {
152 if self.is_empty() {
153 0
154 } else {
155 self.font.num_glyphs()
156 }
157 }
158
159 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 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 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 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 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 plan.applies_variations = context.has_gvar();
268 plan_buffer_size(&plan)
269 }
270 _ => 0,
271 }
272 }
273}
274
275fn 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#[derive(Clone)]
289pub enum OutlineGlyph<'a> {
290 Glyf(GlyfGlyph<'a>),
292 Cff(CffGlyph<'a>),
294 Type1(Type1Glyph<'a>),
296 Varc(VarcGlyph<'a>),
298}
299
300impl OutlineGlyph<'_> {
301 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 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 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 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 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 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 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 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 pub(crate) fn control_box(&self) -> Result<Option<BoundingBox<f32>>, Error> {
462 let mut pen = ControlBoundsSink::new();
463 self.draw_dyn(&DrawOptions::default(), Precision::Float, None, &mut pen)?;
465 Ok(pen.bounding_box())
466 }
467}
468
469fn 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 assert!(metrics.h_advance.is_some());
565 }
566
567 #[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 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 assert_eq!(
647 glyphs.get(GlyphId::new(0)).unwrap().source(),
648 OutlineSource::Glyf
649 );
650 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 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 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 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}