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::GradientUnits, EId::LinearGradient)
482 | (AId::GradientUnits, EId::RadialGradient)
483 | (AId::SpreadMethod, EId::LinearGradient)
484 | (AId::SpreadMethod, EId::RadialGradient)
485 | (AId::GradientTransform, EId::LinearGradient)
486 | (AId::GradientTransform, EId::RadialGradient) => {
487 if link.has_attribute(name) {
488 return link;
489 }
490 }
491 _ => break,
492 }
493 }
494
495 node
496}
497
498fn resolve_pattern_attr<'a, 'input: 'a>(
499 node: SvgNode<'a, 'input>,
500 name: AId,
501) -> SvgNode<'a, 'input> {
502 for link in node.href_iter() {
503 let tag_name = match link.tag_name() {
504 Some(v) => v,
505 None => return node,
506 };
507
508 if tag_name != EId::Pattern {
509 break;
510 }
511
512 if link.has_attribute(name) {
513 return link;
514 }
515 }
516
517 node
518}
519
520fn resolve_filter_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, aid: AId) -> SvgNode<'a, 'input> {
521 for link in node.href_iter() {
522 let tag_name = match link.tag_name() {
523 Some(v) => v,
524 None => return node,
525 };
526
527 if tag_name != EId::Filter {
528 break;
529 }
530
531 if link.has_attribute(aid) {
532 return link;
533 }
534 }
535
536 node
537}
538
539fn stops_to_color(stops: &[Stop]) -> Option<ServerOrColor> {
540 if stops.is_empty() {
541 None
542 } else {
543 Some(ServerOrColor::Color {
544 color: stops[0].color,
545 opacity: stops[0].opacity,
546 })
547 }
548}
549
550pub fn update_paint_servers(
555 group: &mut Group,
556 context_transform: Transform,
557 context_bbox: Option<Rect>,
558 text_bbox: Option<Rect>,
559 cache: &mut Cache,
560) {
561 for child in &mut group.children {
562 let (context_transform, context_bbox) = if group.is_context_element {
565 (group.abs_transform, Some(group.bounding_box))
566 } else {
567 (context_transform, context_bbox)
568 };
569
570 node_to_user_coordinates(child, context_transform, context_bbox, text_bbox, cache);
571 }
572}
573
574fn node_to_user_coordinates(
582 node: &mut Node,
583 context_transform: Transform,
584 context_bbox: Option<Rect>,
585 text_bbox: Option<Rect>,
586 cache: &mut Cache,
587) {
588 match node {
589 Node::Group(g) => {
590 if let Some(mask) = &mut g.mask {
592 if let Some(mask) = Arc::get_mut(mask) {
593 update_paint_servers(
594 &mut mask.root,
595 context_transform,
596 context_bbox,
597 None,
598 cache,
599 );
600
601 if let Some(sub_mask) = &mut mask.mask {
602 if let Some(sub_mask) = Arc::get_mut(sub_mask) {
603 update_paint_servers(
604 &mut sub_mask.root,
605 context_transform,
606 context_bbox,
607 None,
608 cache,
609 );
610 }
611 }
612 }
613 }
614
615 for filter in &mut g.filters {
616 if let Some(filter) = Arc::get_mut(filter) {
617 for primitive in &mut filter.primitives {
618 if let filter::Kind::Image(image) = &mut primitive.kind {
619 update_paint_servers(
620 &mut image.root,
621 context_transform,
622 context_bbox,
623 None,
624 cache,
625 );
626 }
627 }
628 }
629 }
630
631 update_paint_servers(g, context_transform, context_bbox, text_bbox, cache);
632 }
633 Node::Path(path) => {
634 let bbox = text_bbox.unwrap_or(path.bounding_box);
637
638 process_fill(
639 &mut path.fill,
640 path.abs_transform,
641 context_transform,
642 context_bbox,
643 bbox,
644 cache,
645 );
646 process_stroke(
647 &mut path.stroke,
648 path.abs_transform,
649 context_transform,
650 context_bbox,
651 bbox,
652 cache,
653 );
654 }
655 Node::Image(image) => {
656 if let ImageKind::SVG(tree) = &mut image.kind {
657 update_paint_servers(&mut tree.root, context_transform, context_bbox, None, cache);
658 }
659 }
660 Node::Text(text) => {
661 let bbox = text.bounding_box;
665
666 for chunk in &mut text.chunks {
673 for span in &mut chunk.spans {
674 process_fill(
675 &mut span.fill,
676 text.abs_transform,
677 context_transform,
678 context_bbox,
679 bbox,
680 cache,
681 );
682 process_stroke(
683 &mut span.stroke,
684 text.abs_transform,
685 context_transform,
686 context_bbox,
687 bbox,
688 cache,
689 );
690 process_text_decoration(&mut span.decoration.underline, bbox, cache);
691 process_text_decoration(&mut span.decoration.overline, bbox, cache);
692 process_text_decoration(&mut span.decoration.line_through, bbox, cache);
693 }
694 }
695
696 #[cfg(feature = "text")]
698 for span in &mut text.layouted {
699 process_fill(
700 &mut span.fill,
701 text.abs_transform,
702 context_transform,
703 context_bbox,
704 bbox,
705 cache,
706 );
707 process_stroke(
708 &mut span.stroke,
709 text.abs_transform,
710 context_transform,
711 context_bbox,
712 bbox,
713 cache,
714 );
715
716 let mut process_decoration = |path: &mut Path| {
717 process_fill(
718 &mut path.fill,
719 text.abs_transform,
720 context_transform,
721 context_bbox,
722 bbox,
723 cache,
724 );
725 process_stroke(
726 &mut path.stroke,
727 text.abs_transform,
728 context_transform,
729 context_bbox,
730 bbox,
731 cache,
732 );
733 };
734
735 if let Some(path) = &mut span.overline {
736 process_decoration(path);
737 }
738
739 if let Some(path) = &mut span.underline {
740 process_decoration(path);
741 }
742
743 if let Some(path) = &mut span.line_through {
744 process_decoration(path);
745 }
746 }
747
748 update_paint_servers(
750 &mut text.flattened,
751 context_transform,
752 context_bbox,
753 Some(bbox),
754 cache,
755 );
756 }
757 }
758}
759
760fn process_fill(
761 fill: &mut Option<Fill>,
762 path_transform: Transform,
763 context_transform: Transform,
764 context_bbox: Option<Rect>,
765 bbox: Rect,
766 cache: &mut Cache,
767) {
768 let mut ok = false;
769 if let Some(fill) = fill.as_mut() {
770 ok = process_paint(
773 &mut fill.paint,
774 matches!(fill.context_element, Some(ContextElement::UseNode)),
775 context_transform,
776 context_bbox,
777 path_transform,
778 bbox,
779 cache,
780 );
781 }
782 if !ok {
783 *fill = None;
784 }
785}
786
787fn process_stroke(
788 stroke: &mut Option<Stroke>,
789 path_transform: Transform,
790 context_transform: Transform,
791 context_bbox: Option<Rect>,
792 bbox: Rect,
793 cache: &mut Cache,
794) {
795 let mut ok = false;
796 if let Some(stroke) = stroke.as_mut() {
797 ok = process_paint(
800 &mut stroke.paint,
801 matches!(stroke.context_element, Some(ContextElement::UseNode)),
802 context_transform,
803 context_bbox,
804 path_transform,
805 bbox,
806 cache,
807 );
808 }
809 if !ok {
810 *stroke = None;
811 }
812}
813
814fn process_context_paint(
815 paint: &mut Paint,
816 context_transform: Transform,
817 path_transform: Transform,
818 cache: &mut Cache,
819) -> Option<()> {
820 let rev_transform = context_transform
830 .invert()?
831 .pre_concat(path_transform)
832 .invert()?;
833
834 match paint {
835 Paint::Color(_) => {}
836 Paint::LinearGradient(lg) => {
837 let transform = lg.transform.post_concat(rev_transform);
838 *paint = Paint::LinearGradient(Arc::new(LinearGradient {
839 x1: lg.x1,
840 y1: lg.y1,
841 x2: lg.x2,
842 y2: lg.y2,
843 base: BaseGradient {
844 id: cache.gen_linear_gradient_id(),
845 units: lg.units,
846 transform,
847 spread_method: lg.spread_method,
848 stops: lg.stops.clone(),
849 },
850 }));
851 }
852 Paint::RadialGradient(rg) => {
853 let transform = rg.transform.post_concat(rev_transform);
854 *paint = Paint::RadialGradient(Arc::new(RadialGradient {
855 cx: rg.cx,
856 cy: rg.cy,
857 r: rg.r,
858 fx: rg.fx,
859 fy: rg.fy,
860 fr: rg.fr,
861 base: BaseGradient {
862 id: cache.gen_radial_gradient_id(),
863 units: rg.units,
864 transform,
865 spread_method: rg.spread_method,
866 stops: rg.stops.clone(),
867 },
868 }));
869 }
870 Paint::Pattern(pat) => {
871 let transform = pat.transform.post_concat(rev_transform);
872 *paint = Paint::Pattern(Arc::new(Pattern {
873 id: cache.gen_pattern_id(),
874 units: pat.units,
875 content_units: pat.content_units,
876 transform,
877 rect: pat.rect,
878 view_box: pat.view_box,
879 root: pat.root.clone(),
880 }));
881 }
882 }
883
884 Some(())
885}
886
887pub(crate) fn process_paint(
888 paint: &mut Paint,
889 has_context: bool,
890 context_transform: Transform,
891 context_bbox: Option<Rect>,
892 path_transform: Transform,
893 bbox: Rect,
894 cache: &mut Cache,
895) -> bool {
896 if paint.units() == Units::ObjectBoundingBox
897 || paint.content_units() == Units::ObjectBoundingBox
898 {
899 let bbox = if has_context {
900 let Some(bbox) = context_bbox else {
901 return false;
902 };
903 bbox
904 } else {
905 bbox
906 };
907
908 if paint.to_user_coordinates(bbox, cache).is_none() {
909 return false;
910 }
911 }
912
913 if let Paint::Pattern(patt) = paint {
914 if let Some(patt) = Arc::get_mut(patt) {
915 update_paint_servers(&mut patt.root, Transform::default(), None, None, cache);
916 }
917 }
918
919 if has_context {
920 process_context_paint(paint, context_transform, path_transform, cache);
921 }
922
923 true
924}
925
926fn process_text_decoration(style: &mut Option<TextDecorationStyle>, bbox: Rect, cache: &mut Cache) {
927 if let Some(style) = style.as_mut() {
928 process_fill(
929 &mut style.fill,
930 Transform::default(),
931 Transform::default(),
932 None,
933 bbox,
934 cache,
935 );
936 process_stroke(
937 &mut style.stroke,
938 Transform::default(),
939 Transform::default(),
940 None,
941 bbox,
942 cache,
943 );
944 }
945}
946
947impl Paint {
948 fn to_user_coordinates(&mut self, bbox: Rect, cache: &mut Cache) -> Option<()> {
949 let name = if matches!(self, Paint::Pattern(_)) {
950 "Pattern"
951 } else {
952 "Gradient"
953 };
954 let bbox = bbox
955 .to_non_zero_rect()
956 .log_none(|| log::warn!("{} on zero-sized shapes is not allowed.", name))?;
957
958 match self {
961 Paint::Color(_) => {} Paint::LinearGradient(lg) => {
963 let transform = lg.transform.post_concat(Transform::from_bbox(bbox));
964 if let Some(lg) = Arc::get_mut(lg) {
965 lg.base.transform = transform;
966 lg.base.units = Units::UserSpaceOnUse;
967 } else {
968 *lg = Arc::new(LinearGradient {
969 x1: lg.x1,
970 y1: lg.y1,
971 x2: lg.x2,
972 y2: lg.y2,
973 base: BaseGradient {
974 id: cache.gen_linear_gradient_id(),
975 units: Units::UserSpaceOnUse,
976 transform,
977 spread_method: lg.spread_method,
978 stops: lg.stops.clone(),
979 },
980 });
981 }
982 }
983 Paint::RadialGradient(rg) => {
984 let transform = rg.transform.post_concat(Transform::from_bbox(bbox));
985 if let Some(rg) = Arc::get_mut(rg) {
986 rg.base.transform = transform;
987 rg.base.units = Units::UserSpaceOnUse;
988 } else {
989 *rg = Arc::new(RadialGradient {
990 cx: rg.cx,
991 cy: rg.cy,
992 r: rg.r,
993 fx: rg.fx,
994 fy: rg.fy,
995 fr: rg.fr,
996 base: BaseGradient {
997 id: cache.gen_radial_gradient_id(),
998 units: Units::UserSpaceOnUse,
999 transform,
1000 spread_method: rg.spread_method,
1001 stops: rg.stops.clone(),
1002 },
1003 });
1004 }
1005 }
1006 Paint::Pattern(patt) => {
1007 let rect = if patt.units == Units::ObjectBoundingBox {
1008 patt.rect.bbox_transform(bbox)
1009 } else {
1010 patt.rect
1011 };
1012
1013 if let Some(patt) = Arc::get_mut(patt) {
1014 patt.rect = rect;
1015 patt.units = Units::UserSpaceOnUse;
1016
1017 if patt.content_units == Units::ObjectBoundingBox && patt.view_box.is_none() {
1018 let transform = Transform::from_scale(bbox.width(), bbox.height());
1020 push_pattern_transform(&mut patt.root, transform);
1021 }
1022
1023 if let Some(view_box) = patt.view_box {
1024 push_pattern_transform(&mut patt.root, view_box.to_transform(rect.size()));
1025 }
1026
1027 patt.content_units = Units::UserSpaceOnUse;
1028 } else {
1029 let mut root = if patt.content_units == Units::ObjectBoundingBox
1030 && patt.view_box.is_none()
1031 {
1032 let transform = Transform::from_scale(bbox.width(), bbox.height());
1034
1035 let mut g = patt.root.clone();
1036 push_pattern_transform(&mut g, transform);
1037 g
1038 } else {
1039 patt.root.clone()
1040 };
1041
1042 if let Some(view_box) = patt.view_box {
1043 push_pattern_transform(&mut root, view_box.to_transform(rect.size()));
1044 }
1045
1046 *patt = Arc::new(Pattern {
1047 id: cache.gen_pattern_id(),
1048 units: Units::UserSpaceOnUse,
1049 content_units: Units::UserSpaceOnUse,
1050 transform: patt.transform,
1051 rect,
1052 view_box: patt.view_box,
1053 root,
1054 });
1055 }
1056 }
1057 }
1058
1059 Some(())
1060 }
1061}
1062
1063fn push_pattern_transform(root: &mut Group, transform: Transform) {
1064 let mut g = std::mem::replace(root, Group::empty());
1066 g.transform = transform;
1067 g.abs_transform = transform;
1068
1069 root.children.push(Node::Group(Box::new(g)));
1070 root.calculate_bounding_boxes();
1071}
1072
1073impl Paint {
1074 #[inline]
1075 pub(crate) fn units(&self) -> Units {
1076 match self {
1077 Self::Color(_) => Units::UserSpaceOnUse,
1078 Self::LinearGradient(lg) => lg.units,
1079 Self::RadialGradient(rg) => rg.units,
1080 Self::Pattern(patt) => patt.units,
1081 }
1082 }
1083
1084 #[inline]
1085 pub(crate) fn content_units(&self) -> Units {
1086 match self {
1087 Self::Pattern(patt) => patt.content_units,
1088 _ => Units::UserSpaceOnUse,
1089 }
1090 }
1091}