1mod bounds;
35mod decycler;
36mod instance;
37mod traversal;
38
39#[cfg(test)]
40pub(crate) mod test_glyph_defs;
41#[cfg(all(test, feature = "serde", feature = "std"))]
42mod traversal_tests;
43
44use crate::{
45 model::{font::ColrTables, glyph::outline::OutlineGlyphs, metrics::GlyphExtents, Error, Font},
46 tables::{colr, cpal::Cpal},
47 types::{BoundingBox, GlyphId, Matrix, NameId, Point},
48};
49use core::ops::Range;
50use instance::{ColrInstance, PaintId};
51use traversal::{traverse_v0_range, traverse_with_callbacks, PaintDecycler, TraversalState};
52
53#[doc(inline)]
54pub use crate::tables::colr::{CompositeMode, Extend};
55#[doc(inline)]
56pub use crate::tables::cpal::{ColorRecord as Color, PaletteType};
57
58#[derive(Copy, Clone, PartialEq, Debug, Default)]
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[repr(C)]
66pub struct ColorStop {
67 pub offset: f32,
69 pub palette_index: u16,
71 pub alpha: f32,
73}
74
75#[derive(Debug, Clone, PartialEq)]
81pub enum Brush<'a> {
82 Solid { palette_index: u16, alpha: f32 },
84 LinearGradient {
89 p0: Point<f32>,
90 p1: Point<f32>,
91 color_stops: &'a [ColorStop],
92 extend: Extend,
93 },
94 RadialGradient {
99 c0: Point<f32>,
100 r0: f32,
101 c1: Point<f32>,
102 r1: f32,
103 color_stops: &'a [ColorStop],
104 extend: Extend,
105 },
106 SweepGradient {
111 c0: Point<f32>,
112 start_angle: f32,
113 end_angle: f32,
114 color_stops: &'a [ColorStop],
115 extend: Extend,
116 },
117}
118
119pub enum PaintCachedColorGlyph {
124 Ok,
126 Unimplemented,
129}
130
131pub trait PaintSink {
137 fn push_transform(&mut self, transform: Matrix<f32>);
139
140 fn pop_transform(&mut self);
143
144 fn push_clip_glyph(&mut self, glyph: GlyphId);
146
147 fn push_clip_box(&mut self, clip_box: BoundingBox<f32>);
149
150 fn pop_clip(&mut self);
156
157 fn fill(&mut self, brush: Brush<'_>);
159
160 fn fill_glyph(
166 &mut self,
167 glyph: GlyphId,
168 brush_transform: Option<Matrix<f32>>,
169 brush: Brush<'_>,
170 ) {
171 self.push_clip_glyph(glyph);
172 if let Some(wrap_in_transform) = brush_transform {
173 self.push_transform(wrap_in_transform);
174 self.fill(brush);
175 self.pop_transform();
176 } else {
177 self.fill(brush);
178 }
179 self.pop_clip();
180 }
181
182 fn paint_cached_color_glyph(
188 &mut self,
189 _glyph: GlyphId,
190 ) -> Result<PaintCachedColorGlyph, Error> {
191 Ok(PaintCachedColorGlyph::Unimplemented)
192 }
193
194 fn push_layer(&mut self, composite_mode: CompositeMode);
197
198 fn pop_layer(&mut self) {}
201
202 fn pop_layer_with_mode(&mut self, _composite_mode: CompositeMode) {
209 self.pop_layer();
210 }
211}
212
213#[derive(Clone, Copy, PartialEq, Eq, Debug)]
215pub enum ColorGlyphVersion {
216 ColrV0,
218 ColrV1,
220}
221
222#[derive(Clone, Copy)]
228pub struct ColorGlyphs<'a> {
229 font: &'a Font,
230 tables: Option<&'a ColrTables<'a>>,
231}
232
233impl<'a> ColorGlyphs<'a> {
234 pub(crate) fn new(font: &'a Font) -> Self {
235 Self {
236 font,
237 tables: font.colr_tables(),
238 }
239 }
240
241 pub fn font(&self) -> &'a Font {
243 self.font
244 }
245
246 pub fn is_empty(&self) -> bool {
248 self.tables.is_none()
249 }
250
251 pub fn version(&self) -> Option<u16> {
254 Some(self.tables?.colr.version())
255 }
256
257 pub fn get_with_version(
260 &self,
261 glyph: GlyphId,
262 version: ColorGlyphVersion,
263 ) -> Option<ColorGlyph<'a>> {
264 let colr = &self.tables?.colr;
265 let root = match version {
266 ColorGlyphVersion::ColrV0 => Root::V0(colr.v0_base_glyph(glyph)?),
267 ColorGlyphVersion::ColrV1 => {
268 let (paint, paint_id) = colr.v1_base_glyph(glyph)?;
269 Root::V1(paint, paint_id)
270 }
271 };
272 Some(ColorGlyph {
273 glyphs: *self,
274 glyph,
275 root,
276 })
277 }
278
279 pub fn get(&self, glyph: GlyphId) -> Option<ColorGlyph<'a>> {
284 self.get_with_version(glyph, ColorGlyphVersion::ColrV1)
285 .or_else(|| self.get_with_version(glyph, ColorGlyphVersion::ColrV0))
286 }
287
288 pub(crate) fn extents(
297 &self,
298 glyph: GlyphId,
299 outlines: OutlineGlyphs<'a>,
300 ) -> Option<GlyphExtents<f32>> {
301 self.get(glyph)?.extents_with(outlines).ok()
302 }
303
304 fn instance(&self) -> Option<ColrInstance<'a>> {
305 Some(ColrInstance::new(
306 self.tables?,
307 self.font.normalized_coords(),
308 self.font.colr_scalars(),
309 ))
310 }
311}
312
313#[derive(Clone)]
315pub struct ColorGlyph<'a> {
316 glyphs: ColorGlyphs<'a>,
317 glyph: GlyphId,
318 root: Root<'a>,
319}
320
321#[derive(Clone)]
322enum Root<'a> {
323 V0(Range<usize>),
324 V1(colr::Paint<'a>, PaintId),
325}
326
327impl<'a> ColorGlyph<'a> {
328 pub fn glyph(&self) -> GlyphId {
330 self.glyph
331 }
332
333 pub fn version(&self) -> ColorGlyphVersion {
335 match &self.root {
336 Root::V0(_) => ColorGlyphVersion::ColrV0,
337 Root::V1(..) => ColorGlyphVersion::ColrV1,
338 }
339 }
340
341 pub fn clip_box(&self) -> Option<BoundingBox<f32>> {
346 traversal::get_clipbox_font_units(&self.glyphs.instance()?, self.glyph)
347 }
348
349 fn clip_box_for_extents(&self) -> Option<BoundingBox<f32>> {
352 let instance = self.glyphs.instance()?;
353 let clip_box = instance.v1_clip_box(self.glyph)?;
354 Some(instance::resolve_clip_box_for_extents(&instance, &clip_box))
355 }
356
357 pub fn paint(&self, sink: &mut impl PaintSink) -> Result<(), Error> {
363 self.paint_dyn(sink)
364 }
365
366 fn paint_dyn(&self, sink: &mut dyn PaintSink) -> Result<(), Error> {
369 let instance = self.glyphs.instance().ok_or(Error::Malformed)?;
370 match &self.root {
371 Root::V1(paint, paint_id) => {
372 let clipbox = traversal::get_clipbox_font_units(&instance, self.glyph);
373 if let Some(rect) = clipbox {
374 sink.push_clip_box(rect);
375 }
376 let mut decycler = PaintDecycler::default();
377 let mut cycle_guard = decycler.enter(*paint_id)?;
378 let mut state = TraversalState::new(instance, sink);
379 let result = state.resolve_paint(paint).and_then(|resolved| {
380 traverse_with_callbacks(&resolved, &mut state, &mut cycle_guard, 0)
381 });
382 if clipbox.is_some() {
383 sink.pop_clip();
384 }
385 result
386 }
387 Root::V0(range) => traverse_v0_range(range, &instance, sink),
388 }
389 }
390
391 pub fn extents(&self) -> Result<GlyphExtents<f32>, Error> {
398 self.extents_with(self.glyphs.font.outline_glyphs())
399 }
400
401 fn extents_with(&self, outlines: OutlineGlyphs<'a>) -> Result<GlyphExtents<f32>, Error> {
403 if let Some(clip_box) = self.clip_box_for_extents() {
404 return Ok(clip_box.into());
405 }
406 let mut sink = bounds::BoundsSink::new(outlines);
407 self.paint_dyn(&mut sink)?;
408 Ok(sink.bounds().into())
409 }
410}
411
412#[derive(Clone, Copy)]
414pub struct Palette<'a> {
415 cpal: &'a Cpal<'a>,
416 colors: &'a [Color],
418 index: u32,
420}
421
422impl<'a> Palette<'a> {
423 pub fn colors(&self) -> &'a [Color] {
425 self.colors
426 }
427
428 pub fn palette_type(&self) -> Option<PaletteType> {
431 self.cpal
432 .palette_types_array()?
433 .ok()?
434 .get(self.index as usize)
435 .map(|p| p.get())
436 }
437
438 pub fn label(&self) -> Option<NameId> {
440 let name_id = self
441 .cpal
442 .palette_labels_array()?
443 .ok()?
444 .get(self.index as usize)?
445 .get();
446 Some(name_id).filter(|name_id| name_id.to_u16() != 0xFFFF)
447 }
448
449 pub fn index(&self) -> u32 {
451 self.index
452 }
453}
454
455#[derive(Clone, Copy)]
457pub struct Palettes<'a> {
458 cpal: Option<&'a Cpal<'a>>,
459}
460
461impl<'a> Palettes<'a> {
462 pub(crate) fn new(font: &'a Font) -> Self {
463 Self { cpal: font.cpal() }
464 }
465
466 pub fn len(&self) -> u32 {
469 self.cpal.map_or(0, |cpal| cpal.num_palettes() as u32)
470 }
471
472 pub fn is_empty(&self) -> bool {
474 self.len() == 0
475 }
476
477 pub fn get(&self, index: u32) -> Option<Palette<'a>> {
479 let cpal = self.cpal?;
480 let start = usize::from(cpal.color_record_indices().get(index as usize)?.get());
481 let count = usize::from(cpal.num_palette_entries());
482 let colors = cpal
483 .color_records_array()?
484 .ok()?
485 .get(start..start + count)?;
486 Some(Palette {
487 cpal,
488 colors,
489 index,
490 })
491 }
492
493 pub fn iter(&self) -> impl Iterator<Item = Palette<'a>> + 'a {
495 let this = *self;
496 (0..this.len()).filter_map(move |index| this.get(index))
497 }
498
499 pub fn color_label(&self, color_index: u32) -> Option<NameId> {
504 let name_id = self
505 .cpal?
506 .palette_entry_labels_array()?
507 .ok()?
508 .get(color_index as usize)?
509 .get();
510 Some(name_id).filter(|name_id| name_id.to_u16() != 0xFFFF)
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::{
517 test_glyph_defs::{COLORED_CIRCLES_V0, COLORED_CIRCLES_V1, PAINTCOLRGLYPH_CYCLE},
518 *,
519 };
520 use crate::{model::glyph::outline::ControlBoundsSink, FontRef, TableProvider};
521 use alloc::vec::Vec;
522
523 fn font(data: &[u8]) -> Font {
524 Font::new(data.to_vec(), 0).unwrap()
525 }
526
527 fn map(font: &Font, ch: char) -> GlyphId {
528 font.charmap().map_unicode(ch).unwrap()
529 }
530
531 #[derive(Default)]
532 struct DummySink;
533
534 impl PaintSink for DummySink {
535 fn push_transform(&mut self, _transform: Matrix<f32>) {}
536 fn pop_transform(&mut self) {}
537 fn push_clip_glyph(&mut self, _glyph: GlyphId) {}
538 fn push_clip_box(&mut self, _clip_box: BoundingBox<f32>) {}
539 fn pop_clip(&mut self) {}
540 fn fill(&mut self, _brush: Brush) {}
541 fn push_layer(&mut self, _composite_mode: CompositeMode) {}
542 fn pop_layer(&mut self) {}
543 }
544
545 #[test]
546 fn has_colrv1_glyph() {
547 let font = font(font_test_data::COLRV0V1_VARIABLE);
548 let glyphs = font.color_glyphs();
549 assert_eq!(glyphs.version(), Some(1));
550 let v1 = |ch| glyphs.get_with_version(map(&font, ch), ColorGlyphVersion::ColrV1);
551 assert!(v1(COLORED_CIRCLES_V0[0]).is_none());
552 assert!(v1(COLORED_CIRCLES_V1[0]).is_some());
553 assert_eq!(
554 glyphs
555 .get(map(&font, COLORED_CIRCLES_V0[0]))
556 .unwrap()
557 .version(),
558 ColorGlyphVersion::ColrV0
559 );
560 }
561
562 #[test]
563 fn a_font_without_colr_has_no_color_glyphs() {
564 let font = font(font_test_data::VAZIRMATN_VAR);
565 let glyphs = font.color_glyphs();
566 assert!(glyphs.is_empty());
567 assert_eq!(glyphs.version(), None);
568 assert!(glyphs.get(GlyphId::new(1)).is_none());
569 assert!(font.palettes().is_empty());
570 }
571
572 #[test]
573 fn paintcolrglyph_cycle_fails() {
574 let font = font(font_test_data::COLRV0V1_VARIABLE);
575 let glyph = font
576 .color_glyphs()
577 .get_with_version(
578 map(&font, PAINTCOLRGLYPH_CYCLE[0]),
579 ColorGlyphVersion::ColrV1,
580 )
581 .unwrap();
582 assert_eq!(glyph.paint(&mut DummySink), Err(Error::LimitExceeded));
583 }
584
585 #[test]
586 fn paint_glyph2_preserves_boundary_glyph_ids() {
587 use font_test_data::bebuffer::BeBuffer;
588
589 #[derive(Default)]
590 struct Sink {
591 glyphs: Vec<GlyphId>,
592 fills: Vec<(u16, f32)>,
593 clip_depth: usize,
594 }
595 impl PaintSink for Sink {
596 fn push_transform(&mut self, _: Matrix<f32>) {
597 panic!("unexpected transform");
598 }
599 fn pop_transform(&mut self) {
600 panic!("unexpected transform");
601 }
602 fn push_clip_glyph(&mut self, glyph: GlyphId) {
603 self.glyphs.push(glyph);
604 self.clip_depth += 1;
605 }
606 fn push_clip_box(&mut self, _: BoundingBox<f32>) {
607 panic!("unexpected clip box");
608 }
609 fn pop_clip(&mut self) {
610 self.clip_depth = self.clip_depth.checked_sub(1).unwrap();
611 }
612 fn fill(&mut self, brush: Brush) {
613 assert_eq!(self.clip_depth, 1);
614 match brush {
615 Brush::Solid {
616 palette_index,
617 alpha,
618 } => self.fills.push((palette_index, alpha)),
619 _ => panic!("unexpected brush"),
620 }
621 }
622 fn push_layer(&mut self, _: CompositeMode) {
623 panic!("unexpected layer");
624 }
625 fn pop_layer(&mut self) {
626 panic!("unexpected layer");
627 }
628 }
629
630 for gid in [65535u32, 65536, 0xffffff] {
631 let mut colr = font_test_data::colr::paint_glyph2_colr().to_vec();
632 let paint_offset = 34 + 10;
633 colr[paint_offset + 4..paint_offset + 7].copy_from_slice(&gid.to_be_bytes()[1..]);
634 let bytes = BeBuffer::new()
635 .push(0x00010000u32)
636 .push(1u16)
637 .push(16u16)
638 .push(0u16)
639 .push(0u16)
640 .extend(*b"COLR")
641 .push(0u32)
642 .push(28u32)
643 .push(colr.len() as u32)
644 .extend(colr.iter().copied());
645 let font = font(&bytes);
646 let colors = font.color_glyphs();
647 let glyph = colors
648 .get_with_version(GlyphId::new(42), ColorGlyphVersion::ColrV1)
649 .unwrap();
650 assert!(colors
651 .get_with_version(GlyphId::new(43), ColorGlyphVersion::ColrV1)
652 .is_none());
653 let mut sink = Sink::default();
654 for _ in 0..2 {
655 glyph.paint(&mut sink).unwrap();
656 assert_eq!(sink.clip_depth, 0);
657 }
658 assert_eq!(sink.glyphs, [GlyphId::new(gid); 2]);
659 assert_eq!(sink.fills, [(7, 1.0); 2]);
660 }
661 }
662
663 #[test]
664 fn no_cliplist_paints() {
665 let font = font(font_test_data::COLRV1_NO_CLIPLIST);
666 let glyph = font
667 .color_glyphs()
668 .get_with_version(GlyphId::new(1), ColorGlyphVersion::ColrV1)
669 .unwrap();
670 assert!(glyph.clip_box().is_none());
671 assert!(glyph.paint(&mut DummySink).is_ok());
672 }
673
674 #[test]
675 fn colrv0_glyph_has_no_clip_box() {
676 let font = font(font_test_data::COLRV0V1);
677 let glyph = font
678 .color_glyphs()
679 .get_with_version(GlyphId::new(168), ColorGlyphVersion::ColrV0)
680 .unwrap();
681 assert!(glyph.clip_box().is_none());
682 }
683
684 #[test]
685 fn extents_are_the_clip_box_where_there_is_one() {
686 let font = font(font_test_data::COLRV0V1_VARIABLE);
687 let glyphs = font.color_glyphs();
688 let mut checked = 0;
689 for ch in super::test_glyph_defs::CLIPBOX {
690 let glyph = glyphs.get(map(&font, *ch)).unwrap();
691 if let Some(clip_box) = glyph.clip_box() {
692 assert_eq!(glyph.extents().unwrap(), GlyphExtents::from(clip_box));
693 checked += 1;
694 }
695 }
696 assert!(checked > 0);
697 }
698
699 #[test]
700 fn extents_of_layered_outlines_are_their_union() {
701 let font = font(font_test_data::COLRV0V1_VARIABLE);
702 let gid = map(&font, COLORED_CIRCLES_V0[0]);
703 let glyph = font.color_glyphs().get(gid).unwrap();
704 assert_eq!(glyph.version(), ColorGlyphVersion::ColrV0);
705 let colr = FontRef::new(font_test_data::COLRV0V1_VARIABLE)
707 .unwrap()
708 .colr()
709 .unwrap();
710 let outlines = font.outline_glyphs();
711 let mut pen = ControlBoundsSink::new();
712 for layer in colr.v0_base_glyph(gid).unwrap() {
713 let (layer_glyph, _) = colr.v0_layer(layer).unwrap();
714 let outline = outlines.get(layer_glyph.into()).unwrap();
715 outline
716 .draw(
717 &crate::model::glyph::outline::DrawOptions::default(),
718 crate::model::glyph::outline::DrawMemory::Scratch,
719 &mut pen,
720 )
721 .unwrap();
722 }
723 assert_eq!(
724 glyph.extents().unwrap(),
725 GlyphExtents::from(pen.bounding_box().unwrap())
726 );
727 }
728
729 #[test]
730 fn extents_follow_a_translation() {
731 let font = font(font_test_data::COLRV0V1_VARIABLE);
732 let glyphs = font.color_glyphs();
733 let offsets = [
736 (0.0, 0.0),
737 (0.0, 100.0),
738 (0.0, -100.0),
739 (100.0, 0.0),
740 (-100.0, 0.0),
741 (200.0, 200.0),
742 (-200.0, -200.0),
743 ];
744 let extents: Vec<_> = super::test_glyph_defs::PAINT_TRANSLATE
745 .iter()
746 .map(|ch| glyphs.get(map(&font, *ch)).unwrap().extents().unwrap())
747 .collect();
748 assert_eq!(extents.len(), offsets.len());
749 let base = extents[0];
750 for (extents, (dx, dy)) in extents.iter().zip(offsets) {
751 let expected = GlyphExtents {
752 x_bearing: base.x_bearing.min(base.x_bearing + dx),
753 y_bearing: base.y_bearing.max(base.y_bearing + dy),
754 width: base.width + dx.abs(),
755 height: base.height + dy.abs(),
756 };
757 assert_eq!(*extents, expected);
758 }
759 }
760
761 #[test]
762 fn version_0_glyphs_have_extents() {
763 let font = font(font_test_data::COLRV0V1_VARIABLE);
764 let glyphs = font.color_glyphs();
765 let outlines = font.outline_glyphs();
766 let gid = map(&font, COLORED_CIRCLES_V0[0]);
767 assert!(glyphs.extents(gid, outlines).is_some());
768 assert!(glyphs.extents(GlyphId::new(0), outlines).is_none());
770 }
771
772 #[test]
773 fn palettes() {
774 let font = font(font_test_data::COLRV0V1);
775 let palettes = font.palettes();
776 assert_eq!(palettes.len(), 3);
777 let indices: Vec<_> = palettes.iter().map(|palette| palette.index()).collect();
778 assert_eq!(indices, [0, 1, 2]);
779
780 let first = palettes.get(0).unwrap();
781 assert_eq!(first.colors().len(), 14);
782 assert_eq!(
783 first.colors().first(),
784 Some(&Color {
785 blue: 0,
786 green: 0,
787 red: 255,
788 alpha: 255
789 })
790 );
791 assert_eq!(first.colors().get(14), None);
792 assert_eq!(first.palette_type(), Some(PaletteType::empty()));
793 assert_eq!(first.index(), 0);
794
795 let second = palettes.get(1).unwrap();
796 assert_eq!(
797 second.colors().first(),
798 Some(&Color {
799 blue: 74,
800 green: 41,
801 red: 42,
802 alpha: 255
803 })
804 );
805 assert_eq!(
806 second.palette_type(),
807 Some(PaletteType::USABLE_WITH_DARK_BACKGROUND)
808 );
809
810 let third = palettes.get(2).unwrap();
811 assert_eq!(
812 third.colors().first(),
813 Some(&Color {
814 blue: 24,
815 green: 113,
816 red: 252,
817 alpha: 255
818 })
819 );
820 assert_eq!(
821 third.palette_type(),
822 Some(PaletteType::USABLE_WITH_LIGHT_BACKGROUND)
823 );
824
825 assert!(palettes.get(3).is_none());
826 }
827
828 #[test]
832 fn paint_flags_requiring_increased_node_budget() {
833 let font = font(font_test_data::NOTO_COLOR_EMOJI_FLAGS);
834 let colr = FontRef::new(font_test_data::NOTO_COLOR_EMOJI_FLAGS)
835 .unwrap()
836 .colr()
837 .unwrap();
838 let glyph_ids: Vec<_> = colr
839 .base_glyph_list()
840 .unwrap()
841 .unwrap()
842 .base_glyph_paint_records()
843 .iter()
844 .map(|record| GlyphId::from(record.glyph_id()))
845 .collect();
846 assert!(!glyph_ids.is_empty());
847 for gid in glyph_ids {
848 font.color_glyphs()
849 .get_with_version(gid, ColorGlyphVersion::ColrV1)
850 .unwrap_or_else(|| panic!("no COLRv1 glyph for {gid}"))
851 .paint(&mut DummySink)
852 .unwrap_or_else(|e| panic!("failed to paint {gid}: {e}"));
853 }
854 }
855}