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