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