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