Skip to main content

usvg/parser/
paint_server.rs

1// Copyright 2018 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use 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    // Check for existing.
26    if let Some(paint) = cache.paint.get(node.element_id()) {
27        return Some(ServerOrColor::Server(paint.clone()));
28    }
29
30    // Unwrap is safe, because we already checked for is_paint_server().
31    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    // 'A value of zero will cause the area to be painted as a single color
99    // using the color and opacity of the last gradient stop.'
100    //
101    // https://www.w3.org/TR/SVG11/pservers.html#RadialGradientElementRAttribute
102    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    // We can apply viewbox transform only for user space coordinates.
200    // Otherwise we need a bounding box, which is unknown at this point.
201    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            // `number` can be either a number or a percentage.
293            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    // Remove stops with equal offset.
330    //
331    // Example:
332    // offset="0.5"
333    // offset="0.7"
334    // offset="0.7" <-- this one should be removed
335    // offset="0.7"
336    // offset="0.9"
337    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                // Remove offset in the middle.
346                stops.remove(i + 1);
347            } else {
348                i += 1;
349            }
350        }
351    }
352
353    // Remove zeros.
354    //
355    // From:
356    // offset="0.0"
357    // offset="0.0"
358    // offset="0.7"
359    //
360    // To:
361    // offset="0.0"
362    // offset="0.00000001"
363    // offset="0.7"
364    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    // Shift equal offsets.
379    //
380    // From:
381    // offset="0.5"
382    // offset="0.7"
383    // offset="0.7"
384    //
385    // To:
386    // offset="0.5"
387    // offset="0.699999999"
388    // offset="0.7"
389    {
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            // Next offset must be smaller then previous.
396            if offset1 > offset2 || offset1.approx_eq_ulps(&offset2, 4) {
397                // Make previous offset a bit smaller.
398                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            // Coordinates can be resolved only from
444            // ref element with the same type.
445              (AId::X1, EId::LinearGradient)
446            | (AId::Y1, EId::LinearGradient)
447            | (AId::X2, EId::LinearGradient)
448            | (AId::Y2, EId::LinearGradient)
449            // Other attributes can be resolved
450            // from any kind of gradient.
451            | (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            // Coordinates can be resolved only from
477            // ref element with the same type.
478              (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            // Other attributes can be resolved
485            // from any kind of gradient.
486            | (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
555// Update paints servers by doing the following:
556// 1. Replace context fills/strokes that are linked to
557// a use node with their actual values.
558// 2. Convert all object units to UserSpaceOnUse
559pub 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        // Set context transform and bbox if applicable if the
568        // current group is a use node.
569        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
579// When parsing clipPaths, masks and filters we already know group's bounding box.
580// But with gradients and patterns we don't, because we have to know text bounding box
581// before we even parsed it. Which is impossible.
582// Therefore our only choice is to parse gradients and patterns preserving their units
583// and then replace them with `userSpaceOnUse` after the whole tree parsing is finished.
584// So while gradients and patterns do still store their units,
585// they are not exposed in the public API and for the caller they are always `userSpaceOnUse`.
586fn 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            // Paths inside `Text::flattened` are special and must use text's bounding box
599            // instead of their own.
600            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            // By the SVG spec, `tspan` doesn't have a bbox and uses the parent `text` bbox.
626            // Therefore we have to use text's bbox when converting tspan and flatted text
627            // paint servers.
628            let bbox = text.bounding_box;
629
630            // We need to update three things:
631            // 1. The fills/strokes of the original elements in the usvg tree.
632            // 2. The fills/strokes of the layouted elements of the text.
633            // 3. The fills/strokes of the outlined text.
634
635            // 1.
636            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            // 2.
661            #[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            // 3.
713            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        // Path context elements (i.e. for  markers) have already been resolved,
735        // so we only care about use nodes.
736        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        // Path context elements (i.e. for  markers) have already been resolved,
762        // so we only care about use nodes.
763        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    // The idea is the following: We have a certain context element that has
785    // a transform A, and further below in the tree we have for example a path
786    // whose paint has a transform C. In order to get from A to C, there is some
787    // transformation matrix B such that A x B = C. We now need to figure out
788    // a way to get from C back to A, so that the transformation of the paint
789    // matches the one from the context element, even if B was applied. How
790    // do we do that? We calculate CxB^(-1), which will overall then have
791    // the same effect as A. How do we calculate B^(-1)?
792    // --> (A^(-1)xC)^(-1)
793    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        // `Arc::get_mut()` allow us to modify some paint servers in-place.
917        // This reduces the amount of cloning and preserves the original ID as well.
918        match self {
919            Paint::Color(_) => {} // unreachable
920            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                        // No need to shift patterns.
977                        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                        // No need to shift patterns.
991                        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    // TODO: we should update abs_transform in all descendants as well
1023    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}