1use std::str::FromStr;
5use std::sync::Arc;
6
7use strict_num::PositiveF32;
8use svgtypes::{Length, LengthUnit as Unit};
9
10use super::OptionLog;
11use super::converter::{self, Cache, SvgColorExt};
12use super::svgtree::{AId, EId, SvgNode};
13use crate::*;
14
15pub(crate) enum ServerOrColor {
16 Server(Paint),
17 Color { color: Color, opacity: Opacity },
18}
19
20pub(crate) fn convert(
21 node: SvgNode,
22 state: &converter::State,
23 cache: &mut converter::Cache,
24) -> Option<ServerOrColor> {
25 if let Some(paint) = cache.paint.get(node.element_id()) {
27 return Some(ServerOrColor::Server(paint.clone()));
28 }
29
30 let paint = match node.tag_name().unwrap() {
32 EId::LinearGradient => convert_linear(node, state),
33 EId::RadialGradient => convert_radial(node, state),
34 EId::Pattern => convert_pattern(node, state, cache),
35 _ => unreachable!(),
36 };
37
38 if let Some(ServerOrColor::Server(paint)) = &paint {
39 cache
40 .paint
41 .insert(node.element_id().to_string(), paint.clone());
42 }
43
44 paint
45}
46
47#[inline(never)]
48fn convert_linear(node: SvgNode, state: &converter::State) -> Option<ServerOrColor> {
49 let id = NonEmptyString::new(node.element_id().to_string())?;
50
51 let stops = convert_stops(find_gradient_with_stops(node)?);
52 if stops.len() < 2 {
53 return stops_to_color(&stops);
54 }
55
56 let units = convert_units(node, AId::GradientUnits, Units::ObjectBoundingBox);
57 let transform = node.resolve_transform(AId::GradientTransform, state);
58
59 let gradient = LinearGradient {
60 x1: resolve_number(node, AId::X1, units, state, Length::zero()),
61 y1: resolve_number(node, AId::Y1, units, state, Length::zero()),
62 x2: resolve_number(
63 node,
64 AId::X2,
65 units,
66 state,
67 Length::new(100.0, Unit::Percent),
68 ),
69 y2: resolve_number(node, AId::Y2, units, state, Length::zero()),
70 base: BaseGradient {
71 id,
72 units,
73 transform,
74 spread_method: convert_spread_method(node),
75 stops,
76 },
77 };
78
79 Some(ServerOrColor::Server(Paint::LinearGradient(Arc::new(
80 gradient,
81 ))))
82}
83
84#[inline(never)]
85fn convert_radial(node: SvgNode, state: &converter::State) -> Option<ServerOrColor> {
86 let id = NonEmptyString::new(node.element_id().to_string())?;
87
88 let stops = convert_stops(find_gradient_with_stops(node)?);
89 if stops.len() < 2 {
90 return stops_to_color(&stops);
91 }
92
93 let units = convert_units(node, AId::GradientUnits, Units::ObjectBoundingBox);
94 let r = resolve_number(node, AId::R, units, state, Length::new(50.0, Unit::Percent));
95 let fr = resolve_number(node, AId::Fr, units, state, Length::zero());
96
97 if !r.is_valid_length() {
102 let stop = stops.last().unwrap();
103 return Some(ServerOrColor::Color {
104 color: stop.color,
105 opacity: stop.opacity,
106 });
107 }
108
109 let spread_method = convert_spread_method(node);
110 let cx = resolve_number(
111 node,
112 AId::Cx,
113 units,
114 state,
115 Length::new(50.0, Unit::Percent),
116 );
117 let cy = resolve_number(
118 node,
119 AId::Cy,
120 units,
121 state,
122 Length::new(50.0, Unit::Percent),
123 );
124 let fx = resolve_number(node, AId::Fx, units, state, Length::new_number(cx as f64));
125 let fy = resolve_number(node, AId::Fy, units, state, Length::new_number(cy as f64));
126 let transform = node.resolve_transform(AId::GradientTransform, state);
127
128 let gradient = RadialGradient {
129 cx,
130 cy,
131 r: PositiveF32::new(r).unwrap(),
132 fx,
133 fy,
134 fr: PositiveF32::new(fr).unwrap_or(PositiveF32::ZERO),
135 base: BaseGradient {
136 id,
137 units,
138 transform,
139 spread_method,
140 stops,
141 },
142 };
143
144 Some(ServerOrColor::Server(Paint::RadialGradient(Arc::new(
145 gradient,
146 ))))
147}
148
149#[inline(never)]
150fn convert_pattern(
151 node: SvgNode,
152 state: &converter::State,
153 cache: &mut converter::Cache,
154) -> Option<ServerOrColor> {
155 let node_with_children = find_pattern_with_children(node)?;
156
157 let id = NonEmptyString::new(node.element_id().to_string())?;
158
159 let view_box = {
160 let n1 = resolve_attr(node, AId::ViewBox);
161 let n2 = resolve_attr(node, AId::PreserveAspectRatio);
162 n1.parse_viewbox().map(|vb| ViewBox {
163 rect: vb,
164 aspect: n2.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
165 })
166 };
167
168 let units = convert_units(node, AId::PatternUnits, Units::ObjectBoundingBox);
169 let content_units = convert_units(node, AId::PatternContentUnits, Units::UserSpaceOnUse);
170
171 let transform = node.resolve_transform(AId::PatternTransform, state);
172
173 let rect = NonZeroRect::from_xywh(
174 resolve_number(node, AId::X, units, state, Length::zero()),
175 resolve_number(node, AId::Y, units, state, Length::zero()),
176 resolve_number(node, AId::Width, units, state, Length::zero()),
177 resolve_number(node, AId::Height, units, state, Length::zero()),
178 );
179 let rect = rect.log_none(|| {
180 log::warn!(
181 "Pattern '{}' has an invalid size. Skipped.",
182 node.element_id()
183 );
184 })?;
185
186 let mut patt = Pattern {
187 id,
188 units,
189 content_units,
190 transform,
191 rect,
192 view_box,
193 root: Group::empty(),
194 };
195
196 if patt.view_box.is_some()
199 && patt.units == Units::UserSpaceOnUse
200 && patt.content_units == Units::UserSpaceOnUse
201 {
202 let mut g = Group::empty();
203 g.transform = view_box.unwrap().to_transform(rect.size());
204 g.abs_transform = g.transform;
205
206 converter::convert_children(node_with_children, state, cache, &mut g);
207 if !g.has_children() {
208 return None;
209 }
210
211 g.calculate_bounding_boxes();
212 patt.root.children.push(Node::Group(Box::new(g)));
213 } else {
214 converter::convert_children(node_with_children, state, cache, &mut patt.root);
215 if !patt.root.has_children() {
216 return None;
217 }
218 }
219
220 patt.root.calculate_bounding_boxes();
221
222 Some(ServerOrColor::Server(Paint::Pattern(Arc::new(patt))))
223}
224
225fn convert_spread_method(node: SvgNode) -> SpreadMethod {
226 let node = resolve_attr(node, AId::SpreadMethod);
227 node.attribute(AId::SpreadMethod).unwrap_or_default()
228}
229
230pub(crate) fn convert_units(node: SvgNode, name: AId, def: Units) -> Units {
231 let node = resolve_attr(node, name);
232 node.attribute(name).unwrap_or(def)
233}
234
235fn find_gradient_with_stops<'a, 'input: 'a>(
236 node: SvgNode<'a, 'input>,
237) -> Option<SvgNode<'a, 'input>> {
238 for link in node.href_iter() {
239 if !link.tag_name().unwrap().is_gradient() {
240 log::warn!(
241 "Gradient '{}' cannot reference '{}' via 'xlink:href'.",
242 node.element_id(),
243 link.tag_name().unwrap()
244 );
245 return None;
246 }
247
248 if link.children().any(|n| n.tag_name() == Some(EId::Stop)) {
249 return Some(link);
250 }
251 }
252
253 None
254}
255
256fn find_pattern_with_children<'a, 'input: 'a>(
257 node: SvgNode<'a, 'input>,
258) -> Option<SvgNode<'a, 'input>> {
259 for link in node.href_iter() {
260 if link.tag_name() != Some(EId::Pattern) {
261 log::warn!(
262 "Pattern '{}' cannot reference '{}' via 'xlink:href'.",
263 node.element_id(),
264 link.tag_name().unwrap()
265 );
266 return None;
267 }
268
269 if link.has_children() {
270 return Some(link);
271 }
272 }
273
274 None
275}
276
277fn convert_stops(grad: SvgNode) -> Vec<Stop> {
278 let mut stops = Vec::new();
279
280 {
281 let mut prev_offset = Length::zero();
282 for stop in grad.children() {
283 if stop.tag_name() != Some(EId::Stop) {
284 log::warn!("Invalid gradient child: '{:?}'.", stop.tag_name().unwrap());
285 continue;
286 }
287
288 let offset = stop.attribute(AId::Offset).unwrap_or(prev_offset);
290 let offset = match offset.unit {
291 Unit::None => offset.number,
292 Unit::Percent => offset.number / 100.0,
293 _ => prev_offset.number,
294 };
295 prev_offset = Length::new_number(offset);
296 let offset = crate::f32_bound(0.0, offset as f32, 1.0);
297
298 let (color, opacity) = match stop.attribute(AId::StopColor) {
299 Some("currentColor") => stop
300 .find_attribute(AId::Color)
301 .unwrap_or_else(svgtypes::Color::black),
302 Some(value) => {
303 if let Ok(c) = svgtypes::Color::from_str(value) {
304 c
305 } else {
306 log::warn!("Failed to parse stop-color value: '{}'.", value);
307 svgtypes::Color::black()
308 }
309 }
310 _ => svgtypes::Color::black(),
311 }
312 .split_alpha();
313
314 let stop_opacity = stop
315 .attribute::<Opacity>(AId::StopOpacity)
316 .unwrap_or(Opacity::ONE);
317 stops.push(Stop {
318 offset: StopOffset::new_clamped(offset),
319 color,
320 opacity: opacity * stop_opacity,
321 });
322 }
323 }
324
325 if stops.len() >= 3 {
334 let mut i = 0;
335 while i < stops.len() - 2 {
336 let offset1 = stops[i + 0].offset.get();
337 let offset2 = stops[i + 1].offset.get();
338 let offset3 = stops[i + 2].offset.get();
339
340 if offset1.approx_eq_ulps(&offset2, 4) && offset2.approx_eq_ulps(&offset3, 4) {
341 stops.remove(i + 1);
343 } else {
344 i += 1;
345 }
346 }
347 }
348
349 if stops.len() >= 2 {
361 let mut i = 0;
362 while i < stops.len() - 1 {
363 let offset1 = stops[i + 0].offset.get();
364 let offset2 = stops[i + 1].offset.get();
365
366 if offset1.approx_eq_ulps(&0.0, 4) && offset2.approx_eq_ulps(&0.0, 4) {
367 stops[i + 1].offset = StopOffset::new_clamped(offset1 + f32::EPSILON);
368 }
369
370 i += 1;
371 }
372 }
373
374 {
386 let mut i = 1;
387 while i < stops.len() {
388 let offset1 = stops[i - 1].offset.get();
389 let offset2 = stops[i - 0].offset.get();
390
391 if offset1 > offset2 || offset1.approx_eq_ulps(&offset2, 4) {
393 let new_offset = offset1 - f32::EPSILON;
395 stops[i - 1].offset = StopOffset::new_clamped(new_offset);
396 stops[i - 0].offset = StopOffset::new_clamped(offset1);
397 }
398
399 i += 1;
400 }
401 }
402
403 stops
404}
405
406#[inline(never)]
407pub(crate) fn resolve_number(
408 node: SvgNode,
409 name: AId,
410 units: Units,
411 state: &converter::State,
412 def: Length,
413) -> f32 {
414 resolve_attr(node, name).convert_length(name, units, state, def)
415}
416
417fn resolve_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> {
418 if node.has_attribute(name) {
419 return node;
420 }
421
422 match node.tag_name().unwrap() {
423 EId::LinearGradient => resolve_lg_attr(node, name),
424 EId::RadialGradient => resolve_rg_attr(node, name),
425 EId::Pattern => resolve_pattern_attr(node, name),
426 EId::Filter => resolve_filter_attr(node, name),
427 _ => node,
428 }
429}
430
431fn resolve_lg_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> {
432 for link in node.href_iter() {
433 let tag_name = match link.tag_name() {
434 Some(v) => v,
435 None => return node,
436 };
437
438 match (name, tag_name) {
439 (AId::X1, EId::LinearGradient)
442 | (AId::Y1, EId::LinearGradient)
443 | (AId::X2, EId::LinearGradient)
444 | (AId::Y2, EId::LinearGradient)
445 | (AId::GradientUnits, EId::LinearGradient)
448 | (AId::GradientUnits, EId::RadialGradient)
449 | (AId::SpreadMethod, EId::LinearGradient)
450 | (AId::SpreadMethod, EId::RadialGradient)
451 | (AId::GradientTransform, EId::LinearGradient)
452 | (AId::GradientTransform, EId::RadialGradient) => {
453 if link.has_attribute(name) {
454 return link;
455 }
456 }
457 _ => break,
458 }
459 }
460
461 node
462}
463
464fn resolve_rg_attr<'a, 'input>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> {
465 for link in node.href_iter() {
466 let tag_name = match link.tag_name() {
467 Some(v) => v,
468 None => return node,
469 };
470
471 match (name, tag_name) {
472 (AId::Cx, EId::RadialGradient)
475 | (AId::Cy, EId::RadialGradient)
476 | (AId::R, EId::RadialGradient)
477 | (AId::Fx, EId::RadialGradient)
478 | (AId::Fy, EId::RadialGradient)
479 | (AId::Fr, EId::RadialGradient)
480 | (AId::GradientUnits, EId::LinearGradient)
483 | (AId::GradientUnits, EId::RadialGradient)
484 | (AId::SpreadMethod, EId::LinearGradient)
485 | (AId::SpreadMethod, EId::RadialGradient)
486 | (AId::GradientTransform, EId::LinearGradient)
487 | (AId::GradientTransform, EId::RadialGradient) => {
488 if link.has_attribute(name) {
489 return link;
490 }
491 }
492 _ => break,
493 }
494 }
495
496 node
497}
498
499fn resolve_pattern_attr<'a, 'input: 'a>(
500 node: SvgNode<'a, 'input>,
501 name: AId,
502) -> SvgNode<'a, 'input> {
503 for link in node.href_iter() {
504 let tag_name = match link.tag_name() {
505 Some(v) => v,
506 None => return node,
507 };
508
509 if tag_name != EId::Pattern {
510 break;
511 }
512
513 if link.has_attribute(name) {
514 return link;
515 }
516 }
517
518 node
519}
520
521fn resolve_filter_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, aid: AId) -> SvgNode<'a, 'input> {
522 for link in node.href_iter() {
523 let tag_name = match link.tag_name() {
524 Some(v) => v,
525 None => return node,
526 };
527
528 if tag_name != EId::Filter {
529 break;
530 }
531
532 if link.has_attribute(aid) {
533 return link;
534 }
535 }
536
537 node
538}
539
540fn stops_to_color(stops: &[Stop]) -> Option<ServerOrColor> {
541 if stops.is_empty() {
542 None
543 } else {
544 Some(ServerOrColor::Color {
545 color: stops[0].color,
546 opacity: stops[0].opacity,
547 })
548 }
549}
550
551pub fn update_paint_servers(
556 group: &mut Group,
557 context_transform: Transform,
558 context_bbox: Option<Rect>,
559 text_bbox: Option<Rect>,
560 cache: &mut Cache,
561) {
562 for child in &mut group.children {
563 let (context_transform, context_bbox) = if group.is_context_element {
566 (group.abs_transform, Some(group.bounding_box))
567 } else {
568 (context_transform, context_bbox)
569 };
570
571 node_to_user_coordinates(child, context_transform, context_bbox, text_bbox, cache);
572 }
573}
574
575fn node_to_user_coordinates(
583 node: &mut Node,
584 context_transform: Transform,
585 context_bbox: Option<Rect>,
586 text_bbox: Option<Rect>,
587 cache: &mut Cache,
588) {
589 match node {
590 Node::Group(g) => {
591 if let Some(mask) = &mut g.mask {
593 if let Some(mask) = Arc::get_mut(mask) {
594 update_paint_servers(
595 &mut mask.root,
596 context_transform,
597 context_bbox,
598 None,
599 cache,
600 );
601
602 if let Some(sub_mask) = &mut mask.mask {
603 if let Some(sub_mask) = Arc::get_mut(sub_mask) {
604 update_paint_servers(
605 &mut sub_mask.root,
606 context_transform,
607 context_bbox,
608 None,
609 cache,
610 );
611 }
612 }
613 }
614 }
615
616 for filter in &mut g.filters {
617 if let Some(filter) = Arc::get_mut(filter) {
618 for primitive in &mut filter.primitives {
619 if let filter::Kind::Image(image) = &mut primitive.kind {
620 update_paint_servers(
621 &mut image.root,
622 context_transform,
623 context_bbox,
624 None,
625 cache,
626 );
627 }
628 }
629 }
630 }
631
632 update_paint_servers(g, context_transform, context_bbox, text_bbox, cache);
633 }
634 Node::Path(path) => {
635 let bbox = text_bbox.unwrap_or(path.bounding_box);
638
639 process_fill(
640 &mut path.fill,
641 path.abs_transform,
642 context_transform,
643 context_bbox,
644 bbox,
645 cache,
646 );
647 process_stroke(
648 &mut path.stroke,
649 path.abs_transform,
650 context_transform,
651 context_bbox,
652 bbox,
653 cache,
654 );
655 }
656 Node::Image(image) => {
657 if let ImageKind::SVG(tree) = &mut image.kind {
658 update_paint_servers(&mut tree.root, context_transform, context_bbox, None, cache);
659 }
660 }
661 Node::Text(text) => {
662 let bbox = text.bounding_box;
666
667 for chunk in &mut text.chunks {
674 for span in &mut chunk.spans {
675 process_fill(
676 &mut span.fill,
677 text.abs_transform,
678 context_transform,
679 context_bbox,
680 bbox,
681 cache,
682 );
683 process_stroke(
684 &mut span.stroke,
685 text.abs_transform,
686 context_transform,
687 context_bbox,
688 bbox,
689 cache,
690 );
691 process_text_decoration(&mut span.decoration.underline, bbox, cache);
692 process_text_decoration(&mut span.decoration.overline, bbox, cache);
693 process_text_decoration(&mut span.decoration.line_through, bbox, cache);
694 }
695 }
696
697 #[cfg(feature = "text")]
699 for span in &mut text.layouted {
700 process_fill(
701 &mut span.fill,
702 text.abs_transform,
703 context_transform,
704 context_bbox,
705 bbox,
706 cache,
707 );
708 process_stroke(
709 &mut span.stroke,
710 text.abs_transform,
711 context_transform,
712 context_bbox,
713 bbox,
714 cache,
715 );
716
717 let mut process_decoration = |path: &mut Path| {
718 process_fill(
719 &mut path.fill,
720 text.abs_transform,
721 context_transform,
722 context_bbox,
723 bbox,
724 cache,
725 );
726 process_stroke(
727 &mut path.stroke,
728 text.abs_transform,
729 context_transform,
730 context_bbox,
731 bbox,
732 cache,
733 );
734 };
735
736 if let Some(path) = &mut span.overline {
737 process_decoration(path);
738 }
739
740 if let Some(path) = &mut span.underline {
741 process_decoration(path);
742 }
743
744 if let Some(path) = &mut span.line_through {
745 process_decoration(path);
746 }
747 }
748
749 update_paint_servers(
751 &mut text.flattened,
752 context_transform,
753 context_bbox,
754 Some(bbox),
755 cache,
756 );
757 }
758 }
759}
760
761fn process_fill(
762 fill: &mut Option<Fill>,
763 path_transform: Transform,
764 context_transform: Transform,
765 context_bbox: Option<Rect>,
766 bbox: Rect,
767 cache: &mut Cache,
768) {
769 let mut ok = false;
770 if let Some(fill) = fill.as_mut() {
771 ok = process_paint(
774 &mut fill.paint,
775 matches!(fill.context_element, Some(ContextElement::UseNode)),
776 context_transform,
777 context_bbox,
778 path_transform,
779 bbox,
780 cache,
781 );
782 }
783 if !ok {
784 *fill = None;
785 }
786}
787
788fn process_stroke(
789 stroke: &mut Option<Stroke>,
790 path_transform: Transform,
791 context_transform: Transform,
792 context_bbox: Option<Rect>,
793 bbox: Rect,
794 cache: &mut Cache,
795) {
796 let mut ok = false;
797 if let Some(stroke) = stroke.as_mut() {
798 ok = process_paint(
801 &mut stroke.paint,
802 matches!(stroke.context_element, Some(ContextElement::UseNode)),
803 context_transform,
804 context_bbox,
805 path_transform,
806 bbox,
807 cache,
808 );
809 }
810 if !ok {
811 *stroke = None;
812 }
813}
814
815fn process_context_paint(
816 paint: &mut Paint,
817 context_transform: Transform,
818 path_transform: Transform,
819 cache: &mut Cache,
820) -> Option<()> {
821 let rev_transform = context_transform
831 .invert()?
832 .pre_concat(path_transform)
833 .invert()?;
834
835 match paint {
836 Paint::Color(_) => {}
837 Paint::LinearGradient(lg) => {
838 let transform = lg.transform.post_concat(rev_transform);
839 *paint = Paint::LinearGradient(Arc::new(LinearGradient {
840 x1: lg.x1,
841 y1: lg.y1,
842 x2: lg.x2,
843 y2: lg.y2,
844 base: BaseGradient {
845 id: cache.gen_linear_gradient_id(),
846 units: lg.units,
847 transform,
848 spread_method: lg.spread_method,
849 stops: lg.stops.clone(),
850 },
851 }));
852 }
853 Paint::RadialGradient(rg) => {
854 let transform = rg.transform.post_concat(rev_transform);
855 *paint = Paint::RadialGradient(Arc::new(RadialGradient {
856 cx: rg.cx,
857 cy: rg.cy,
858 r: rg.r,
859 fx: rg.fx,
860 fy: rg.fy,
861 fr: rg.fr,
862 base: BaseGradient {
863 id: cache.gen_radial_gradient_id(),
864 units: rg.units,
865 transform,
866 spread_method: rg.spread_method,
867 stops: rg.stops.clone(),
868 },
869 }));
870 }
871 Paint::Pattern(pat) => {
872 let transform = pat.transform.post_concat(rev_transform);
873 *paint = Paint::Pattern(Arc::new(Pattern {
874 id: cache.gen_pattern_id(),
875 units: pat.units,
876 content_units: pat.content_units,
877 transform,
878 rect: pat.rect,
879 view_box: pat.view_box,
880 root: pat.root.clone(),
881 }));
882 }
883 }
884
885 Some(())
886}
887
888pub(crate) fn process_paint(
889 paint: &mut Paint,
890 has_context: bool,
891 context_transform: Transform,
892 context_bbox: Option<Rect>,
893 path_transform: Transform,
894 bbox: Rect,
895 cache: &mut Cache,
896) -> bool {
897 if paint.units() == Units::ObjectBoundingBox
898 || paint.content_units() == Units::ObjectBoundingBox
899 {
900 let bbox = if has_context {
901 let Some(bbox) = context_bbox else {
902 return false;
903 };
904 bbox
905 } else {
906 bbox
907 };
908
909 if paint.to_user_coordinates(bbox, cache).is_none() {
910 return false;
911 }
912 }
913
914 if let Paint::Pattern(patt) = paint {
915 if let Some(patt) = Arc::get_mut(patt) {
916 update_paint_servers(&mut patt.root, Transform::default(), None, None, cache);
917 }
918 }
919
920 if has_context {
921 process_context_paint(paint, context_transform, path_transform, cache);
922 }
923
924 true
925}
926
927fn process_text_decoration(style: &mut Option<TextDecorationStyle>, bbox: Rect, cache: &mut Cache) {
928 if let Some(style) = style.as_mut() {
929 process_fill(
930 &mut style.fill,
931 Transform::default(),
932 Transform::default(),
933 None,
934 bbox,
935 cache,
936 );
937 process_stroke(
938 &mut style.stroke,
939 Transform::default(),
940 Transform::default(),
941 None,
942 bbox,
943 cache,
944 );
945 }
946}
947
948impl Paint {
949 fn to_user_coordinates(&mut self, bbox: Rect, cache: &mut Cache) -> Option<()> {
950 let name = if matches!(self, Paint::Pattern(_)) {
951 "Pattern"
952 } else {
953 "Gradient"
954 };
955 let bbox = bbox
956 .to_non_zero_rect()
957 .log_none(|| log::warn!("{} on zero-sized shapes is not allowed.", name))?;
958
959 match self {
962 Paint::Color(_) => {} Paint::LinearGradient(lg) => {
964 let transform = lg.transform.post_concat(Transform::from_bbox(bbox));
965 if let Some(lg) = Arc::get_mut(lg) {
966 lg.base.transform = transform;
967 lg.base.units = Units::UserSpaceOnUse;
968 } else {
969 *lg = Arc::new(LinearGradient {
970 x1: lg.x1,
971 y1: lg.y1,
972 x2: lg.x2,
973 y2: lg.y2,
974 base: BaseGradient {
975 id: cache.gen_linear_gradient_id(),
976 units: Units::UserSpaceOnUse,
977 transform,
978 spread_method: lg.spread_method,
979 stops: lg.stops.clone(),
980 },
981 });
982 }
983 }
984 Paint::RadialGradient(rg) => {
985 let transform = rg.transform.post_concat(Transform::from_bbox(bbox));
986 if let Some(rg) = Arc::get_mut(rg) {
987 rg.base.transform = transform;
988 rg.base.units = Units::UserSpaceOnUse;
989 } else {
990 *rg = Arc::new(RadialGradient {
991 cx: rg.cx,
992 cy: rg.cy,
993 r: rg.r,
994 fx: rg.fx,
995 fy: rg.fy,
996 fr: rg.fr,
997 base: BaseGradient {
998 id: cache.gen_radial_gradient_id(),
999 units: Units::UserSpaceOnUse,
1000 transform,
1001 spread_method: rg.spread_method,
1002 stops: rg.stops.clone(),
1003 },
1004 });
1005 }
1006 }
1007 Paint::Pattern(patt) => {
1008 let rect = if patt.units == Units::ObjectBoundingBox {
1009 patt.rect.bbox_transform(bbox)
1010 } else {
1011 patt.rect
1012 };
1013
1014 if let Some(patt) = Arc::get_mut(patt) {
1015 patt.rect = rect;
1016 patt.units = Units::UserSpaceOnUse;
1017
1018 if patt.content_units == Units::ObjectBoundingBox && patt.view_box.is_none() {
1019 let transform = Transform::from_scale(bbox.width(), bbox.height());
1021 push_pattern_transform(&mut patt.root, transform);
1022 }
1023
1024 if let Some(view_box) = patt.view_box {
1025 push_pattern_transform(&mut patt.root, view_box.to_transform(rect.size()));
1026 }
1027
1028 patt.content_units = Units::UserSpaceOnUse;
1029 } else {
1030 let mut root = if patt.content_units == Units::ObjectBoundingBox
1031 && patt.view_box.is_none()
1032 {
1033 let transform = Transform::from_scale(bbox.width(), bbox.height());
1035
1036 let mut g = patt.root.clone();
1037 push_pattern_transform(&mut g, transform);
1038 g
1039 } else {
1040 patt.root.clone()
1041 };
1042
1043 if let Some(view_box) = patt.view_box {
1044 push_pattern_transform(&mut root, view_box.to_transform(rect.size()));
1045 }
1046
1047 *patt = Arc::new(Pattern {
1048 id: cache.gen_pattern_id(),
1049 units: Units::UserSpaceOnUse,
1050 content_units: Units::UserSpaceOnUse,
1051 transform: patt.transform,
1052 rect,
1053 view_box: patt.view_box,
1054 root,
1055 });
1056 }
1057 }
1058 }
1059
1060 Some(())
1061 }
1062}
1063
1064fn push_pattern_transform(root: &mut Group, transform: Transform) {
1065 let mut g = std::mem::replace(root, Group::empty());
1067 g.transform = transform;
1068 g.abs_transform = transform;
1069
1070 root.children.push(Node::Group(Box::new(g)));
1071 root.calculate_bounding_boxes();
1072}
1073
1074impl Paint {
1075 #[inline]
1076 pub(crate) fn units(&self) -> Units {
1077 match self {
1078 Self::Color(_) => Units::UserSpaceOnUse,
1079 Self::LinearGradient(lg) => lg.units,
1080 Self::RadialGradient(rg) => rg.units,
1081 Self::Pattern(patt) => patt.units,
1082 }
1083 }
1084
1085 #[inline]
1086 pub(crate) fn content_units(&self) -> Units {
1087 match self {
1088 Self::Pattern(patt) => patt.content_units,
1089 _ => Units::UserSpaceOnUse,
1090 }
1091 }
1092}