1use std::{cmp::Ordering, ops::Range};
2
3use read_fonts::{
4 tables::colr::{CompositeMode, Extend},
5 types::{BoundingBox, GlyphId, Point},
6};
7
8use super::{
9 instance::{
10 resolve_clip_box, resolve_paint, ColorStops, ColrInstance, ResolvedColorStop, ResolvedPaint,
11 },
12 Brush, ColorPainter, ColorStop, PaintCachedColorGlyph, PaintError, Transform,
13};
14
15use crate::decycler::{Decycler, DecyclerError};
16
17#[cfg(feature = "libm")]
18#[allow(unused_imports)]
19use core_maths::*;
20
21pub(crate) type PaintDecycler = Decycler<usize, MAX_TRAVERSAL_DEPTH>;
22
23const MAX_INLINE_COLOR_STOPS: usize = 32;
33
34pub(crate) type ColorStopVec = crate::collections::SmallVec<ColorStop, MAX_INLINE_COLOR_STOPS>;
35
36impl From<DecyclerError> for PaintError {
37 fn from(value: DecyclerError) -> Self {
38 match value {
39 DecyclerError::CycleDetected => Self::PaintCycleDetected,
40 DecyclerError::DepthLimitExceeded => Self::DepthLimitExceeded,
41 }
42 }
43}
44
45const MAX_TRAVERSAL_DEPTH: usize = 64;
54
55pub(crate) fn get_clipbox_font_units(
56 colr_instance: &ColrInstance,
57 glyph_id: GlyphId,
58) -> Option<BoundingBox<f32>> {
59 let maybe_clipbox = (*colr_instance).v1_clip_box(glyph_id).ok().flatten()?;
60 Some(resolve_clip_box(colr_instance, &maybe_clipbox))
61}
62
63impl From<ResolvedColorStop> for ColorStop {
64 fn from(resolved_stop: ResolvedColorStop) -> Self {
65 ColorStop {
66 offset: resolved_stop.offset,
67 alpha: resolved_stop.alpha,
68 palette_index: resolved_stop.palette_index,
69 }
70 }
71}
72
73fn make_sorted_resolved_stops(
74 stops: &ColorStops,
75 instance: &ColrInstance,
76 out_stops: &mut ColorStopVec,
77) {
78 let color_stop_iter = stops.resolve(instance).map(|stop| stop.into());
79 out_stops.clear();
80 for stop in color_stop_iter {
81 out_stops.push(stop);
82 }
83 out_stops.sort_by(|a, b| a.offset.partial_cmp(&b.offset).unwrap_or(Ordering::Equal));
84}
85
86struct CollectFillGlyphPainter<'a> {
87 brush_transform: Option<Transform>,
88 glyph_id: GlyphId,
89 parent_painter: &'a mut dyn ColorPainter,
90 pub optimization_success: bool,
91}
92
93impl<'a> CollectFillGlyphPainter<'a> {
94 fn new(parent_painter: &'a mut dyn ColorPainter, glyph_id: GlyphId) -> Self {
95 Self {
96 brush_transform: None,
97 glyph_id,
98 parent_painter,
99 optimization_success: true,
100 }
101 }
102}
103
104impl ColorPainter for CollectFillGlyphPainter<'_> {
105 fn push_transform(&mut self, transform: Transform) {
106 if self.optimization_success {
107 match self.brush_transform {
108 None => {
109 self.brush_transform = Some(transform);
110 }
111 Some(ref mut existing_transform) => {
112 *existing_transform *= transform;
113 }
114 }
115 }
116 }
117
118 fn pop_transform(&mut self) {
119 }
124
125 fn fill(&mut self, brush: Brush<'_>) {
126 if self.optimization_success {
127 self.parent_painter
128 .fill_glyph(self.glyph_id, self.brush_transform, brush);
129 }
130 }
131
132 fn push_clip_glyph(&mut self, _: GlyphId) {
133 self.optimization_success = false;
134 }
135
136 fn push_clip_box(&mut self, _: BoundingBox<f32>) {
137 self.optimization_success = false;
138 }
139
140 fn pop_clip(&mut self) {
141 self.optimization_success = false;
142 }
143
144 fn push_layer(&mut self, _: CompositeMode) {
145 self.optimization_success = false;
146 }
147
148 fn pop_layer(&mut self) {
149 self.optimization_success = false;
150 }
151}
152
153pub(crate) fn traverse_with_callbacks(
154 paint: &ResolvedPaint,
155 instance: &ColrInstance,
156 painter: &mut impl ColorPainter,
157 decycler: &mut PaintDecycler,
158 resolved_stops: &mut ColorStopVec,
159 recurse_depth: usize,
160) -> Result<(), PaintError> {
161 if recurse_depth >= MAX_TRAVERSAL_DEPTH {
162 return Err(PaintError::DepthLimitExceeded);
163 }
164 match paint {
165 ResolvedPaint::ColrLayers { range } => {
166 for layer_index in range.clone() {
167 let (layer_paint, paint_id) = (*instance).v1_layer(layer_index)?;
169 let mut cycle_guard = decycler.enter(paint_id)?;
170 traverse_with_callbacks(
171 &resolve_paint(instance, &layer_paint)?,
172 instance,
173 painter,
174 &mut cycle_guard,
175 resolved_stops,
176 recurse_depth + 1,
177 )?;
178 }
179 Ok(())
180 }
181 ResolvedPaint::Solid {
182 palette_index,
183 alpha,
184 } => {
185 painter.fill(Brush::Solid {
186 palette_index: *palette_index,
187 alpha: *alpha,
188 });
189 Ok(())
190 }
191 ResolvedPaint::LinearGradient {
192 x0,
193 y0,
194 x1,
195 y1,
196 x2,
197 y2,
198 color_stops,
199 extend,
200 } => {
201 let mut p0 = Point::new(*x0, *y0);
202 let p1 = Point::new(*x1, *y1);
203 let p2 = Point::new(*x2, *y2);
204
205 let dot_product = |a: Point<f32>, b: Point<f32>| -> f32 { a.x * b.x + a.y * b.y };
206 let cross_product = |a: Point<f32>, b: Point<f32>| -> f32 { a.x * b.y - a.y * b.x };
207 let project_onto = |vector: Point<f32>, point: Point<f32>| -> Point<f32> {
208 let length = (point.x * point.x + point.y * point.y).sqrt();
209 if length == 0.0 {
210 return Point::default();
211 }
212 let mut point_normalized = point / length;
213 point_normalized *= dot_product(vector, point) / length;
214 point_normalized
215 };
216
217 make_sorted_resolved_stops(color_stops, instance, resolved_stops);
218
219 if p1 == p0 || p2 == p0 || cross_product(p1 - p0, p2 - p0) == 0.0 {
224 if let Some(stop) = resolved_stops.first() {
225 painter.fill(Brush::Solid {
226 palette_index: stop.palette_index,
227 alpha: stop.alpha,
228 });
229 };
230 return Ok(());
231 }
232
233 let mut perpendicular_to_p2 = p2 - p0;
239 perpendicular_to_p2 = Point::new(perpendicular_to_p2.y, -perpendicular_to_p2.x);
240 let mut p3 = p0 + project_onto(p1 - p0, perpendicular_to_p2);
241
242 match (
243 resolved_stops.first().cloned(),
244 resolved_stops.last().cloned(),
245 ) {
246 (None, _) | (_, None) => {}
247 (Some(first_stop), Some(last_stop)) => {
248 let mut color_stop_range = last_stop.offset - first_stop.offset;
249
250 if color_stop_range == 0.0 && extend != &Extend::Pad {
252 return Ok(());
253 }
254
255 if color_stop_range == 0.0 && extend == &Extend::Pad {
263 let mut extra_stop = last_stop;
264 extra_stop.offset += 1.0;
265 resolved_stops.push(extra_stop);
266
267 color_stop_range = 1.0;
268 }
269
270 debug_assert!(color_stop_range != 0.0);
271
272 if color_stop_range != 1.0 || first_stop.offset != 0.0 {
273 let p0_p3 = p3 - p0;
274 let p0_offset = p0_p3 * first_stop.offset;
275 let p3_offset = p0_p3 * last_stop.offset;
276
277 p3 = p0 + p3_offset;
278 p0 += p0_offset;
279
280 let scale_factor = 1.0 / color_stop_range;
281 let start_offset = first_stop.offset;
282
283 for stop in resolved_stops.iter_mut() {
284 stop.offset = (stop.offset - start_offset) * scale_factor;
285 }
286 }
287
288 painter.fill(Brush::LinearGradient {
289 p0,
290 p1: p3,
291 color_stops: resolved_stops.as_slice(),
292 extend: *extend,
293 });
294 }
295 }
296
297 Ok(())
298 }
299 ResolvedPaint::RadialGradient {
300 x0,
301 y0,
302 radius0,
303 x1,
304 y1,
305 radius1,
306 color_stops,
307 extend,
308 } => {
309 let mut c0 = Point::new(*x0, *y0);
310 let mut c1 = Point::new(*x1, *y1);
311 let mut radius0 = *radius0;
312 let mut radius1 = *radius1;
313
314 make_sorted_resolved_stops(color_stops, instance, resolved_stops);
315
316 match (
317 resolved_stops.first().cloned(),
318 resolved_stops.last().cloned(),
319 ) {
320 (None, _) | (_, None) => {}
321 (Some(first_stop), Some(last_stop)) => {
322 let mut color_stop_range = last_stop.offset - first_stop.offset;
323 if color_stop_range == 0.0 && extend != &Extend::Pad {
325 return Ok(());
326 }
327
328 if color_stop_range == 0.0 && extend == &Extend::Pad {
332 let mut extra_stop = last_stop;
333 extra_stop.offset += 1.0;
334 resolved_stops.push(extra_stop);
335 color_stop_range = 1.0;
336 }
337
338 debug_assert!(color_stop_range != 0.0);
339
340 if color_stop_range != 1.0 || first_stop.offset != 0.0 {
345 let c0_to_c1 = c1 - c0;
346 let radius_diff = radius1 - radius0;
347 let scale_factor = 1.0 / color_stop_range;
348
349 let c0_offset = c0_to_c1 * first_stop.offset;
350 let c1_offset = c0_to_c1 * last_stop.offset;
351 let stops_start_offset = first_stop.offset;
352
353 c1 = c0 + c1_offset;
355 c0 += c0_offset;
356 radius1 = radius0 + radius_diff * last_stop.offset;
357 radius0 += radius_diff * first_stop.offset;
358
359 for stop in resolved_stops.iter_mut() {
360 stop.offset = (stop.offset - stops_start_offset) * scale_factor;
361 }
362 }
363
364 painter.fill(Brush::RadialGradient {
365 c0,
366 r0: radius0,
367 c1,
368 r1: radius1,
369 color_stops: resolved_stops.as_slice(),
370 extend: *extend,
371 });
372 }
373 }
374 Ok(())
375 }
376 ResolvedPaint::SweepGradient {
377 center_x,
378 center_y,
379 start_angle,
380 end_angle,
381 color_stops,
382 extend,
383 } => {
384 let sweep_angle_to_degrees = |angle| angle * 180.0 + 180.0;
387
388 let start_angle = sweep_angle_to_degrees(start_angle);
389 let end_angle = sweep_angle_to_degrees(end_angle);
390
391 let sector_angle = end_angle - start_angle;
394
395 make_sorted_resolved_stops(color_stops, instance, resolved_stops);
396 if resolved_stops.is_empty() {
397 return Ok(());
398 }
399
400 match (
401 resolved_stops.first().cloned(),
402 resolved_stops.last().cloned(),
403 ) {
404 (None, _) | (_, None) => {}
405 (Some(first_stop), Some(last_stop)) => {
406 let mut color_stop_range = last_stop.offset - first_stop.offset;
407
408 let mut start_angle_scaled = start_angle + sector_angle * first_stop.offset;
409 let mut end_angle_scaled = start_angle + sector_angle * last_stop.offset;
410
411 let start_offset = first_stop.offset;
412
413 if color_stop_range == 0.0 && extend != &Extend::Pad {
415 return Ok(());
416 }
417
418 if color_stop_range == 0.0 && extend == &Extend::Pad {
425 let mut offset_last = last_stop;
426 offset_last.offset += 1.0;
427 resolved_stops.push(offset_last);
428 color_stop_range = 1.0;
429 }
430
431 debug_assert!(color_stop_range != 0.0);
432
433 let scale_factor = 1.0 / color_stop_range;
434
435 for shift_stop in resolved_stops.iter_mut() {
436 shift_stop.offset = (shift_stop.offset - start_offset) * scale_factor;
437 }
438
439 start_angle_scaled = 360.0 - start_angle_scaled;
448 end_angle_scaled = 360.0 - end_angle_scaled;
449
450 if start_angle_scaled >= end_angle_scaled {
451 (start_angle_scaled, end_angle_scaled) =
452 (end_angle_scaled, start_angle_scaled);
453 resolved_stops.reverse();
454 for stop in resolved_stops.iter_mut() {
455 stop.offset = 1.0 - stop.offset;
456 }
457 }
458
459 if start_angle_scaled == end_angle_scaled && extend != &Extend::Pad {
463 return Ok(());
464 }
465
466 painter.fill(Brush::SweepGradient {
467 c0: Point::new(*center_x, *center_y),
468 start_angle: start_angle_scaled,
469 end_angle: end_angle_scaled,
470 color_stops: resolved_stops.as_slice(),
471 extend: *extend,
472 });
473 }
474 }
475 Ok(())
476 }
477
478 ResolvedPaint::Glyph { glyph_id, paint } => {
479 let glyph_id = (*glyph_id).into();
480 let mut optimizer = CollectFillGlyphPainter::new(painter, glyph_id);
481 let mut result = traverse_with_callbacks(
482 &resolve_paint(instance, paint)?,
483 instance,
484 &mut optimizer,
485 decycler,
486 resolved_stops,
487 recurse_depth + 1,
488 );
489
490 if !optimizer.optimization_success {
492 painter.push_clip_glyph(glyph_id);
493 result = traverse_with_callbacks(
494 &resolve_paint(instance, paint)?,
495 instance,
496 painter,
497 decycler,
498 resolved_stops,
499 recurse_depth + 1,
500 );
501 painter.pop_clip();
502 }
503
504 result
505 }
506 ResolvedPaint::ColrGlyph { glyph_id } => {
507 let glyph_id = (*glyph_id).into();
508 match (*instance).v1_base_glyph(glyph_id)? {
509 Some((base_glyph, base_glyph_paint_id)) => {
510 let mut cycle_guard = decycler.enter(base_glyph_paint_id)?;
511 let draw_result = painter.paint_cached_color_glyph(glyph_id)?;
512 match draw_result {
513 PaintCachedColorGlyph::Ok => Ok(()),
514 PaintCachedColorGlyph::Unimplemented => {
515 let clipbox = get_clipbox_font_units(instance, glyph_id);
516
517 if let Some(rect) = clipbox {
518 painter.push_clip_box(rect);
519 }
520
521 let result = traverse_with_callbacks(
522 &resolve_paint(instance, &base_glyph)?,
523 instance,
524 painter,
525 &mut cycle_guard,
526 resolved_stops,
527 recurse_depth + 1,
528 );
529 if clipbox.is_some() {
530 painter.pop_clip();
531 }
532 result
533 }
534 }
535 }
536 None => Err(PaintError::GlyphNotFound(glyph_id)),
537 }
538 }
539 ResolvedPaint::Transform {
540 paint: next_paint, ..
541 }
542 | ResolvedPaint::Translate {
543 paint: next_paint, ..
544 }
545 | ResolvedPaint::Scale {
546 paint: next_paint, ..
547 }
548 | ResolvedPaint::Rotate {
549 paint: next_paint, ..
550 }
551 | ResolvedPaint::Skew {
552 paint: next_paint, ..
553 } => {
554 painter.push_transform(paint.try_into()?);
555 let result = traverse_with_callbacks(
556 &resolve_paint(instance, next_paint)?,
557 instance,
558 painter,
559 decycler,
560 resolved_stops,
561 recurse_depth + 1,
562 );
563 painter.pop_transform();
564 result
565 }
566 ResolvedPaint::Composite {
567 source_paint,
568 mode,
569 backdrop_paint,
570 } => {
571 painter.push_layer(CompositeMode::SrcOver);
572 let mut result = traverse_with_callbacks(
573 &resolve_paint(instance, backdrop_paint)?,
574 instance,
575 painter,
576 decycler,
577 resolved_stops,
578 recurse_depth + 1,
579 );
580 result?;
581 painter.push_layer(*mode);
582 result = traverse_with_callbacks(
583 &resolve_paint(instance, source_paint)?,
584 instance,
585 painter,
586 decycler,
587 resolved_stops,
588 recurse_depth + 1,
589 );
590 painter.pop_layer_with_mode(*mode);
591 painter.pop_layer_with_mode(CompositeMode::SrcOver);
592 result
593 }
594 }
595}
596
597pub(crate) fn traverse_v0_range(
598 range: &Range<usize>,
599 instance: &ColrInstance,
600 painter: &mut impl ColorPainter,
601) -> Result<(), PaintError> {
602 for layer_index in range.clone() {
603 let (layer_glyph, palette_index) = (*instance).v0_layer(layer_index)?;
604 painter.fill_glyph(
605 layer_glyph.into(),
606 None,
607 Brush::Solid {
608 palette_index,
609 alpha: 1.0,
610 },
611 );
612 }
613 Ok(())
614}
615
616#[cfg(test)]
617mod tests {
618 use raw::types::GlyphId;
619 use read_fonts::{types::BoundingBox, FontRef, TableProvider};
620
621 use crate::{
622 color::{
623 instance::ColrInstance, traversal::get_clipbox_font_units,
624 traversal_tests::test_glyph_defs::CLIPBOX, Brush, ColorGlyphFormat, ColorPainter,
625 CompositeMode, Transform,
626 },
627 prelude::LocationRef,
628 MetadataProvider,
629 };
630
631 #[test]
632 fn clipbox_test() {
633 let colr_font = font_test_data::COLRV0V1_VARIABLE;
634 let font = FontRef::new(colr_font).unwrap();
635 let test_glyph_id = font.charmap().map(CLIPBOX[0]).unwrap();
636 let upem = font.head().unwrap().units_per_em();
637
638 let base_bounding_box = BoundingBox {
639 x_min: 0.0,
640 x_max: upem as f32 / 2.0,
641 y_min: upem as f32 / 2.0,
642 y_max: upem as f32,
643 };
644 const CLIPBOX_SHIFT: f32 = 200.0122;
646
647 macro_rules! test_entry {
648 ($axis:literal, $shift:expr, $field:ident) => {
649 (
650 $axis,
651 $shift,
652 BoundingBox {
653 $field: base_bounding_box.$field + ($shift),
654 ..base_bounding_box
655 },
656 )
657 };
658 }
659
660 let test_data_expectations = [
661 ("", 0.0, base_bounding_box),
662 test_entry!("CLXI", CLIPBOX_SHIFT, x_min),
663 test_entry!("CLXA", -CLIPBOX_SHIFT, x_max),
664 test_entry!("CLYI", CLIPBOX_SHIFT, y_min),
665 test_entry!("CLYA", -CLIPBOX_SHIFT, y_max),
666 ];
667
668 for axis_test in test_data_expectations {
669 let axis_coordinate = (axis_test.0, axis_test.1);
670 let location = font.axes().location([axis_coordinate]);
671 let color_instance = ColrInstance::new(font.colr().unwrap(), location.coords());
672 let clip_box = get_clipbox_font_units(&color_instance, test_glyph_id);
673 assert!(clip_box.is_some());
674 assert!(
675 clip_box.unwrap() == axis_test.2,
676 "Clip boxes do not match. Actual: {:?}, expected: {:?}",
677 clip_box.unwrap(),
678 axis_test.2
679 );
680 }
681 }
682
683 struct NopPainter;
684
685 impl ColorPainter for NopPainter {
686 fn push_transform(&mut self, _transform: Transform) {
687 }
689
690 fn pop_transform(&mut self) {
691 }
693
694 fn push_clip_glyph(&mut self, _glyph_id: GlyphId) {
695 }
697
698 fn push_clip_box(&mut self, _clip_box: BoundingBox<f32>) {
699 }
701
702 fn pop_clip(&mut self) {
703 }
705
706 fn fill(&mut self, _brush: Brush<'_>) {
707 }
709
710 fn push_layer(&mut self, _composite_mode: CompositeMode) {
711 }
713
714 fn pop_layer(&mut self) {
715 }
717 }
718
719 #[test]
720 fn no_panic_on_empty_colorline() {
721 let test_case = &[
723 0, 1, 0, 0, 0, 3, 32, 32, 32, 32, 32, 32, 0, 32, 32, 32, 32, 32, 32, 32, 255, 32, 32,
724 32, 32, 32, 32, 32, 67, 79, 76, 82, 32, 32, 32, 32, 0, 0, 0, 229, 0, 0, 0, 178, 99,
725 109, 97, 112, 32, 32, 32, 32, 0, 0, 0, 10, 0, 0, 1, 32, 32, 32, 32, 255, 32, 32, 32, 0,
726 4, 32, 255, 32, 32, 0, 32, 32, 32, 32, 32, 32, 32, 255, 32, 32, 32, 32, 32, 32, 32, 32,
727 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 255, 32, 0, 0,
728 32, 32, 0, 0, 0, 57, 32, 32, 32, 32, 32, 32, 32, 255, 32, 32, 32, 32, 32, 32, 32, 32,
729 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
730 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
731 32, 0, 0, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
732 32, 32, 32, 32, 32, 32, 32, 32, 32, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
733 255, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 0, 0, 0, 4, 32, 32, 32, 32, 32, 32, 32,
734 32, 32, 0, 0, 0, 1, 32, 32, 32, 32, 32, 32, 255, 0, 0, 0, 40, 32, 32, 32, 32, 32, 32,
735 32, 255, 255, 32, 32, 32, 4, 0, 0, 32, 32, 32, 32, 32, 0, 0, 0, 0, 0, 0, 0, 0, 32, 32,
736 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 0, 0, 32, 32, 32, 255, 255,
737 255, 255, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
738 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
739 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 255, 255, 255, 255, 255, 255, 255, 255, 255,
740 255, 255, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
741 255, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
742 ];
743
744 let font = FontRef::new(test_case).unwrap();
745 font.cmap().unwrap();
746 font.colr().unwrap();
747
748 let color_glyph = font
749 .color_glyphs()
750 .get_with_format(GlyphId::new(8447), ColorGlyphFormat::ColrV1)
751 .unwrap();
752 let _ = color_glyph.paint(LocationRef::default(), &mut NopPainter);
753 }
754}