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 = node.resolve_transform(AId::GradientTransform, state);
58
59    let gradient = LinearGradient {
60        x1: resolve_number(node, AId::X1, units, state, Length::zero()),
61        y1: resolve_number(node, AId::Y1, units, state, Length::zero()),
62        x2: resolve_number(
63            node,
64            AId::X2,
65            units,
66            state,
67            Length::new(100.0, Unit::Percent),
68        ),
69        y2: resolve_number(node, AId::Y2, units, state, Length::zero()),
70        base: BaseGradient {
71            id,
72            units,
73            transform,
74            spread_method: convert_spread_method(node),
75            stops,
76        },
77    };
78
79    Some(ServerOrColor::Server(Paint::LinearGradient(Arc::new(
80        gradient,
81    ))))
82}
83
84#[inline(never)]
85fn convert_radial(node: SvgNode, state: &converter::State) -> Option<ServerOrColor> {
86    let id = NonEmptyString::new(node.element_id().to_string())?;
87
88    let stops = convert_stops(find_gradient_with_stops(node)?);
89    if stops.len() < 2 {
90        return stops_to_color(&stops);
91    }
92
93    let units = convert_units(node, AId::GradientUnits, Units::ObjectBoundingBox);
94    let r = resolve_number(node, AId::R, units, state, Length::new(50.0, Unit::Percent));
95    let fr = resolve_number(node, AId::Fr, units, state, Length::zero());
96
97    // 'A value of zero will cause the area to be painted as a single color
98    // using the color and opacity of the last gradient stop.'
99    //
100    // https://www.w3.org/TR/SVG11/pservers.html#RadialGradientElementRAttribute
101    if !r.is_valid_length() {
102        let stop = stops.last().unwrap();
103        return Some(ServerOrColor::Color {
104            color: stop.color,
105            opacity: stop.opacity,
106        });
107    }
108
109    let spread_method = convert_spread_method(node);
110    let cx = resolve_number(
111        node,
112        AId::Cx,
113        units,
114        state,
115        Length::new(50.0, Unit::Percent),
116    );
117    let cy = resolve_number(
118        node,
119        AId::Cy,
120        units,
121        state,
122        Length::new(50.0, Unit::Percent),
123    );
124    let fx = resolve_number(node, AId::Fx, units, state, Length::new_number(cx as f64));
125    let fy = resolve_number(node, AId::Fy, units, state, Length::new_number(cy as f64));
126    let transform = node.resolve_transform(AId::GradientTransform, state);
127
128    let gradient = RadialGradient {
129        cx,
130        cy,
131        r: PositiveF32::new(r).unwrap(),
132        fx,
133        fy,
134        fr: PositiveF32::new(fr).unwrap_or(PositiveF32::ZERO),
135        base: BaseGradient {
136            id,
137            units,
138            transform,
139            spread_method,
140            stops,
141        },
142    };
143
144    Some(ServerOrColor::Server(Paint::RadialGradient(Arc::new(
145        gradient,
146    ))))
147}
148
149#[inline(never)]
150fn convert_pattern(
151    node: SvgNode,
152    state: &converter::State,
153    cache: &mut converter::Cache,
154) -> Option<ServerOrColor> {
155    let node_with_children = find_pattern_with_children(node)?;
156
157    let id = NonEmptyString::new(node.element_id().to_string())?;
158
159    let view_box = {
160        let n1 = resolve_attr(node, AId::ViewBox);
161        let n2 = resolve_attr(node, AId::PreserveAspectRatio);
162        n1.parse_viewbox().map(|vb| ViewBox {
163            rect: vb,
164            aspect: n2.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
165        })
166    };
167
168    let units = convert_units(node, AId::PatternUnits, Units::ObjectBoundingBox);
169    let content_units = convert_units(node, AId::PatternContentUnits, Units::UserSpaceOnUse);
170
171    let transform = node.resolve_transform(AId::PatternTransform, state);
172
173    let rect = NonZeroRect::from_xywh(
174        resolve_number(node, AId::X, units, state, Length::zero()),
175        resolve_number(node, AId::Y, units, state, Length::zero()),
176        resolve_number(node, AId::Width, units, state, Length::zero()),
177        resolve_number(node, AId::Height, units, state, Length::zero()),
178    );
179    let rect = rect.log_none(|| {
180        log::warn!(
181            "Pattern '{}' has an invalid size. Skipped.",
182            node.element_id()
183        );
184    })?;
185
186    let mut patt = Pattern {
187        id,
188        units,
189        content_units,
190        transform,
191        rect,
192        view_box,
193        root: Group::empty(),
194    };
195
196    // We can apply viewbox transform only for user space coordinates.
197    // Otherwise we need a bounding box, which is unknown at this point.
198    if patt.view_box.is_some()
199        && patt.units == Units::UserSpaceOnUse
200        && patt.content_units == Units::UserSpaceOnUse
201    {
202        let mut g = Group::empty();
203        g.transform = view_box.unwrap().to_transform(rect.size());
204        g.abs_transform = g.transform;
205
206        converter::convert_children(node_with_children, state, cache, &mut g);
207        if !g.has_children() {
208            return None;
209        }
210
211        g.calculate_bounding_boxes();
212        patt.root.children.push(Node::Group(Box::new(g)));
213    } else {
214        converter::convert_children(node_with_children, state, cache, &mut patt.root);
215        if !patt.root.has_children() {
216            return None;
217        }
218    }
219
220    patt.root.calculate_bounding_boxes();
221
222    Some(ServerOrColor::Server(Paint::Pattern(Arc::new(patt))))
223}
224
225fn convert_spread_method(node: SvgNode) -> SpreadMethod {
226    let node = resolve_attr(node, AId::SpreadMethod);
227    node.attribute(AId::SpreadMethod).unwrap_or_default()
228}
229
230pub(crate) fn convert_units(node: SvgNode, name: AId, def: Units) -> Units {
231    let node = resolve_attr(node, name);
232    node.attribute(name).unwrap_or(def)
233}
234
235fn find_gradient_with_stops<'a, 'input: 'a>(
236    node: SvgNode<'a, 'input>,
237) -> Option<SvgNode<'a, 'input>> {
238    for link in node.href_iter() {
239        if !link.tag_name().unwrap().is_gradient() {
240            log::warn!(
241                "Gradient '{}' cannot reference '{}' via 'xlink:href'.",
242                node.element_id(),
243                link.tag_name().unwrap()
244            );
245            return None;
246        }
247
248        if link.children().any(|n| n.tag_name() == Some(EId::Stop)) {
249            return Some(link);
250        }
251    }
252
253    None
254}
255
256fn find_pattern_with_children<'a, 'input: 'a>(
257    node: SvgNode<'a, 'input>,
258) -> Option<SvgNode<'a, 'input>> {
259    for link in node.href_iter() {
260        if link.tag_name() != Some(EId::Pattern) {
261            log::warn!(
262                "Pattern '{}' cannot reference '{}' via 'xlink:href'.",
263                node.element_id(),
264                link.tag_name().unwrap()
265            );
266            return None;
267        }
268
269        if link.has_children() {
270            return Some(link);
271        }
272    }
273
274    None
275}
276
277fn convert_stops(grad: SvgNode) -> Vec<Stop> {
278    let mut stops = Vec::new();
279
280    {
281        let mut prev_offset = Length::zero();
282        for stop in grad.children() {
283            if stop.tag_name() != Some(EId::Stop) {
284                log::warn!("Invalid gradient child: '{:?}'.", stop.tag_name().unwrap());
285                continue;
286            }
287
288            // `number` can be either a number or a percentage.
289            let offset = stop.attribute(AId::Offset).unwrap_or(prev_offset);
290            let offset = match offset.unit {
291                Unit::None => offset.number,
292                Unit::Percent => offset.number / 100.0,
293                _ => prev_offset.number,
294            };
295            prev_offset = Length::new_number(offset);
296            let offset = crate::f32_bound(0.0, offset as f32, 1.0);
297
298            let (color, opacity) = match stop.attribute(AId::StopColor) {
299                Some("currentColor") => stop
300                    .find_attribute(AId::Color)
301                    .unwrap_or_else(svgtypes::Color::black),
302                Some(value) => {
303                    if let Ok(c) = svgtypes::Color::from_str(value) {
304                        c
305                    } else {
306                        log::warn!("Failed to parse stop-color value: '{}'.", value);
307                        svgtypes::Color::black()
308                    }
309                }
310                _ => svgtypes::Color::black(),
311            }
312            .split_alpha();
313
314            let stop_opacity = stop
315                .attribute::<Opacity>(AId::StopOpacity)
316                .unwrap_or(Opacity::ONE);
317            stops.push(Stop {
318                offset: StopOffset::new_clamped(offset),
319                color,
320                opacity: opacity * stop_opacity,
321            });
322        }
323    }
324
325    // Remove stops with equal offset.
326    //
327    // Example:
328    // offset="0.5"
329    // offset="0.7"
330    // offset="0.7" <-- this one should be removed
331    // offset="0.7"
332    // offset="0.9"
333    if stops.len() >= 3 {
334        let mut i = 0;
335        while i < stops.len() - 2 {
336            let offset1 = stops[i + 0].offset.get();
337            let offset2 = stops[i + 1].offset.get();
338            let offset3 = stops[i + 2].offset.get();
339
340            if offset1.approx_eq_ulps(&offset2, 4) && offset2.approx_eq_ulps(&offset3, 4) {
341                // Remove offset in the middle.
342                stops.remove(i + 1);
343            } else {
344                i += 1;
345            }
346        }
347    }
348
349    // Remove zeros.
350    //
351    // From:
352    // offset="0.0"
353    // offset="0.0"
354    // offset="0.7"
355    //
356    // To:
357    // offset="0.0"
358    // offset="0.00000001"
359    // offset="0.7"
360    if stops.len() >= 2 {
361        let mut i = 0;
362        while i < stops.len() - 1 {
363            let offset1 = stops[i + 0].offset.get();
364            let offset2 = stops[i + 1].offset.get();
365
366            if offset1.approx_eq_ulps(&0.0, 4) && offset2.approx_eq_ulps(&0.0, 4) {
367                stops[i + 1].offset = StopOffset::new_clamped(offset1 + f32::EPSILON);
368            }
369
370            i += 1;
371        }
372    }
373
374    // Shift equal offsets.
375    //
376    // From:
377    // offset="0.5"
378    // offset="0.7"
379    // offset="0.7"
380    //
381    // To:
382    // offset="0.5"
383    // offset="0.699999999"
384    // offset="0.7"
385    {
386        let mut i = 1;
387        while i < stops.len() {
388            let offset1 = stops[i - 1].offset.get();
389            let offset2 = stops[i - 0].offset.get();
390
391            // Next offset must be smaller then previous.
392            if offset1 > offset2 || offset1.approx_eq_ulps(&offset2, 4) {
393                // Make previous offset a bit smaller.
394                let new_offset = offset1 - f32::EPSILON;
395                stops[i - 1].offset = StopOffset::new_clamped(new_offset);
396                stops[i - 0].offset = StopOffset::new_clamped(offset1);
397            }
398
399            i += 1;
400        }
401    }
402
403    stops
404}
405
406#[inline(never)]
407pub(crate) fn resolve_number(
408    node: SvgNode,
409    name: AId,
410    units: Units,
411    state: &converter::State,
412    def: Length,
413) -> f32 {
414    resolve_attr(node, name).convert_length(name, units, state, def)
415}
416
417fn resolve_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> {
418    if node.has_attribute(name) {
419        return node;
420    }
421
422    match node.tag_name().unwrap() {
423        EId::LinearGradient => resolve_lg_attr(node, name),
424        EId::RadialGradient => resolve_rg_attr(node, name),
425        EId::Pattern => resolve_pattern_attr(node, name),
426        EId::Filter => resolve_filter_attr(node, name),
427        _ => node,
428    }
429}
430
431fn resolve_lg_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> {
432    for link in node.href_iter() {
433        let tag_name = match link.tag_name() {
434            Some(v) => v,
435            None => return node,
436        };
437
438        match (name, tag_name) {
439            // Coordinates can be resolved only from
440            // ref element with the same type.
441              (AId::X1, EId::LinearGradient)
442            | (AId::Y1, EId::LinearGradient)
443            | (AId::X2, EId::LinearGradient)
444            | (AId::Y2, EId::LinearGradient)
445            // Other attributes can be resolved
446            // from any kind of gradient.
447            | (AId::GradientUnits, EId::LinearGradient)
448            | (AId::GradientUnits, EId::RadialGradient)
449            | (AId::SpreadMethod, EId::LinearGradient)
450            | (AId::SpreadMethod, EId::RadialGradient)
451            | (AId::GradientTransform, EId::LinearGradient)
452            | (AId::GradientTransform, EId::RadialGradient) => {
453                if link.has_attribute(name) {
454                    return link;
455                }
456            }
457            _ => break,
458        }
459    }
460
461    node
462}
463
464fn resolve_rg_attr<'a, 'input>(node: SvgNode<'a, 'input>, name: AId) -> SvgNode<'a, 'input> {
465    for link in node.href_iter() {
466        let tag_name = match link.tag_name() {
467            Some(v) => v,
468            None => return node,
469        };
470
471        match (name, tag_name) {
472            // Coordinates can be resolved only from
473            // ref element with the same type.
474              (AId::Cx, EId::RadialGradient)
475            | (AId::Cy, EId::RadialGradient)
476            | (AId::R,  EId::RadialGradient)
477            | (AId::Fx, EId::RadialGradient)
478            | (AId::Fy, EId::RadialGradient)
479            // Other attributes can be resolved
480            // from any kind of gradient.
481            | (AId::GradientUnits, EId::LinearGradient)
482            | (AId::GradientUnits, EId::RadialGradient)
483            | (AId::SpreadMethod, EId::LinearGradient)
484            | (AId::SpreadMethod, EId::RadialGradient)
485            | (AId::GradientTransform, EId::LinearGradient)
486            | (AId::GradientTransform, EId::RadialGradient) => {
487                if link.has_attribute(name) {
488                    return link;
489                }
490            }
491            _ => break,
492        }
493    }
494
495    node
496}
497
498fn resolve_pattern_attr<'a, 'input: 'a>(
499    node: SvgNode<'a, 'input>,
500    name: AId,
501) -> SvgNode<'a, 'input> {
502    for link in node.href_iter() {
503        let tag_name = match link.tag_name() {
504            Some(v) => v,
505            None => return node,
506        };
507
508        if tag_name != EId::Pattern {
509            break;
510        }
511
512        if link.has_attribute(name) {
513            return link;
514        }
515    }
516
517    node
518}
519
520fn resolve_filter_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, aid: AId) -> SvgNode<'a, 'input> {
521    for link in node.href_iter() {
522        let tag_name = match link.tag_name() {
523            Some(v) => v,
524            None => return node,
525        };
526
527        if tag_name != EId::Filter {
528            break;
529        }
530
531        if link.has_attribute(aid) {
532            return link;
533        }
534    }
535
536    node
537}
538
539fn stops_to_color(stops: &[Stop]) -> Option<ServerOrColor> {
540    if stops.is_empty() {
541        None
542    } else {
543        Some(ServerOrColor::Color {
544            color: stops[0].color,
545            opacity: stops[0].opacity,
546        })
547    }
548}
549
550// Update paints servers by doing the following:
551// 1. Replace context fills/strokes that are linked to
552// a use node with their actual values.
553// 2. Convert all object units to UserSpaceOnUse
554pub fn update_paint_servers(
555    group: &mut Group,
556    context_transform: Transform,
557    context_bbox: Option<Rect>,
558    text_bbox: Option<Rect>,
559    cache: &mut Cache,
560) {
561    for child in &mut group.children {
562        // Set context transform and bbox if applicable if the
563        // current group is a use node.
564        let (context_transform, context_bbox) = if group.is_context_element {
565            (group.abs_transform, Some(group.bounding_box))
566        } else {
567            (context_transform, context_bbox)
568        };
569
570        node_to_user_coordinates(child, context_transform, context_bbox, text_bbox, cache);
571    }
572}
573
574// When parsing clipPaths, masks and filters we already know group's bounding box.
575// But with gradients and patterns we don't, because we have to know text bounding box
576// before we even parsed it. Which is impossible.
577// Therefore our only choice is to parse gradients and patterns preserving their units
578// and then replace them with `userSpaceOnUse` after the whole tree parsing is finished.
579// So while gradients and patterns do still store their units,
580// they are not exposed in the public API and for the caller they are always `userSpaceOnUse`.
581fn node_to_user_coordinates(
582    node: &mut Node,
583    context_transform: Transform,
584    context_bbox: Option<Rect>,
585    text_bbox: Option<Rect>,
586    cache: &mut Cache,
587) {
588    match node {
589        Node::Group(g) => {
590            // No need to check clip paths, because they cannot have paint servers.
591            if let Some(mask) = &mut g.mask {
592                if let Some(mask) = Arc::get_mut(mask) {
593                    update_paint_servers(
594                        &mut mask.root,
595                        context_transform,
596                        context_bbox,
597                        None,
598                        cache,
599                    );
600
601                    if let Some(sub_mask) = &mut mask.mask {
602                        if let Some(sub_mask) = Arc::get_mut(sub_mask) {
603                            update_paint_servers(
604                                &mut sub_mask.root,
605                                context_transform,
606                                context_bbox,
607                                None,
608                                cache,
609                            );
610                        }
611                    }
612                }
613            }
614
615            for filter in &mut g.filters {
616                if let Some(filter) = Arc::get_mut(filter) {
617                    for primitive in &mut filter.primitives {
618                        if let filter::Kind::Image(image) = &mut primitive.kind {
619                            update_paint_servers(
620                                &mut image.root,
621                                context_transform,
622                                context_bbox,
623                                None,
624                                cache,
625                            );
626                        }
627                    }
628                }
629            }
630
631            update_paint_servers(g, context_transform, context_bbox, text_bbox, cache);
632        }
633        Node::Path(path) => {
634            // Paths inside `Text::flattened` are special and must use text's bounding box
635            // instead of their own.
636            let bbox = text_bbox.unwrap_or(path.bounding_box);
637
638            process_fill(
639                &mut path.fill,
640                path.abs_transform,
641                context_transform,
642                context_bbox,
643                bbox,
644                cache,
645            );
646            process_stroke(
647                &mut path.stroke,
648                path.abs_transform,
649                context_transform,
650                context_bbox,
651                bbox,
652                cache,
653            );
654        }
655        Node::Image(image) => {
656            if let ImageKind::SVG(tree) = &mut image.kind {
657                update_paint_servers(&mut tree.root, context_transform, context_bbox, None, cache);
658            }
659        }
660        Node::Text(text) => {
661            // By the SVG spec, `tspan` doesn't have a bbox and uses the parent `text` bbox.
662            // Therefore we have to use text's bbox when converting tspan and flatted text
663            // paint servers.
664            let bbox = text.bounding_box;
665
666            // We need to update three things:
667            // 1. The fills/strokes of the original elements in the usvg tree.
668            // 2. The fills/strokes of the layouted elements of the text.
669            // 3. The fills/strokes of the outlined text.
670
671            // 1.
672            for chunk in &mut text.chunks {
673                for span in &mut chunk.spans {
674                    process_fill(
675                        &mut span.fill,
676                        text.abs_transform,
677                        context_transform,
678                        context_bbox,
679                        bbox,
680                        cache,
681                    );
682                    process_stroke(
683                        &mut span.stroke,
684                        text.abs_transform,
685                        context_transform,
686                        context_bbox,
687                        bbox,
688                        cache,
689                    );
690                    process_text_decoration(&mut span.decoration.underline, bbox, cache);
691                    process_text_decoration(&mut span.decoration.overline, bbox, cache);
692                    process_text_decoration(&mut span.decoration.line_through, bbox, cache);
693                }
694            }
695
696            // 2.
697            #[cfg(feature = "text")]
698            for span in &mut text.layouted {
699                process_fill(
700                    &mut span.fill,
701                    text.abs_transform,
702                    context_transform,
703                    context_bbox,
704                    bbox,
705                    cache,
706                );
707                process_stroke(
708                    &mut span.stroke,
709                    text.abs_transform,
710                    context_transform,
711                    context_bbox,
712                    bbox,
713                    cache,
714                );
715
716                let mut process_decoration = |path: &mut Path| {
717                    process_fill(
718                        &mut path.fill,
719                        text.abs_transform,
720                        context_transform,
721                        context_bbox,
722                        bbox,
723                        cache,
724                    );
725                    process_stroke(
726                        &mut path.stroke,
727                        text.abs_transform,
728                        context_transform,
729                        context_bbox,
730                        bbox,
731                        cache,
732                    );
733                };
734
735                if let Some(path) = &mut span.overline {
736                    process_decoration(path);
737                }
738
739                if let Some(path) = &mut span.underline {
740                    process_decoration(path);
741                }
742
743                if let Some(path) = &mut span.line_through {
744                    process_decoration(path);
745                }
746            }
747
748            // 3.
749            update_paint_servers(
750                &mut text.flattened,
751                context_transform,
752                context_bbox,
753                Some(bbox),
754                cache,
755            );
756        }
757    }
758}
759
760fn process_fill(
761    fill: &mut Option<Fill>,
762    path_transform: Transform,
763    context_transform: Transform,
764    context_bbox: Option<Rect>,
765    bbox: Rect,
766    cache: &mut Cache,
767) {
768    let mut ok = false;
769    if let Some(fill) = fill.as_mut() {
770        // Path context elements (i.e. for  markers) have already been resolved,
771        // so we only care about use nodes.
772        ok = process_paint(
773            &mut fill.paint,
774            matches!(fill.context_element, Some(ContextElement::UseNode)),
775            context_transform,
776            context_bbox,
777            path_transform,
778            bbox,
779            cache,
780        );
781    }
782    if !ok {
783        *fill = None;
784    }
785}
786
787fn process_stroke(
788    stroke: &mut Option<Stroke>,
789    path_transform: Transform,
790    context_transform: Transform,
791    context_bbox: Option<Rect>,
792    bbox: Rect,
793    cache: &mut Cache,
794) {
795    let mut ok = false;
796    if let Some(stroke) = stroke.as_mut() {
797        // Path context elements (i.e. for  markers) have already been resolved,
798        // so we only care about use nodes.
799        ok = process_paint(
800            &mut stroke.paint,
801            matches!(stroke.context_element, Some(ContextElement::UseNode)),
802            context_transform,
803            context_bbox,
804            path_transform,
805            bbox,
806            cache,
807        );
808    }
809    if !ok {
810        *stroke = None;
811    }
812}
813
814fn process_context_paint(
815    paint: &mut Paint,
816    context_transform: Transform,
817    path_transform: Transform,
818    cache: &mut Cache,
819) -> Option<()> {
820    // The idea is the following: We have a certain context element that has
821    // a transform A, and further below in the tree we have for example a path
822    // whose paint has a transform C. In order to get from A to C, there is some
823    // transformation matrix B such that A x B = C. We now need to figure out
824    // a way to get from C back to A, so that the transformation of the paint
825    // matches the one from the context element, even if B was applied. How
826    // do we do that? We calculate CxB^(-1), which will overall then have
827    // the same effect as A. How do we calculate B^(-1)?
828    // --> (A^(-1)xC)^(-1)
829    let rev_transform = context_transform
830        .invert()?
831        .pre_concat(path_transform)
832        .invert()?;
833
834    match paint {
835        Paint::Color(_) => {}
836        Paint::LinearGradient(lg) => {
837            let transform = lg.transform.post_concat(rev_transform);
838            *paint = Paint::LinearGradient(Arc::new(LinearGradient {
839                x1: lg.x1,
840                y1: lg.y1,
841                x2: lg.x2,
842                y2: lg.y2,
843                base: BaseGradient {
844                    id: cache.gen_linear_gradient_id(),
845                    units: lg.units,
846                    transform,
847                    spread_method: lg.spread_method,
848                    stops: lg.stops.clone(),
849                },
850            }));
851        }
852        Paint::RadialGradient(rg) => {
853            let transform = rg.transform.post_concat(rev_transform);
854            *paint = Paint::RadialGradient(Arc::new(RadialGradient {
855                cx: rg.cx,
856                cy: rg.cy,
857                r: rg.r,
858                fx: rg.fx,
859                fy: rg.fy,
860                fr: rg.fr,
861                base: BaseGradient {
862                    id: cache.gen_radial_gradient_id(),
863                    units: rg.units,
864                    transform,
865                    spread_method: rg.spread_method,
866                    stops: rg.stops.clone(),
867                },
868            }));
869        }
870        Paint::Pattern(pat) => {
871            let transform = pat.transform.post_concat(rev_transform);
872            *paint = Paint::Pattern(Arc::new(Pattern {
873                id: cache.gen_pattern_id(),
874                units: pat.units,
875                content_units: pat.content_units,
876                transform,
877                rect: pat.rect,
878                view_box: pat.view_box,
879                root: pat.root.clone(),
880            }));
881        }
882    }
883
884    Some(())
885}
886
887pub(crate) fn process_paint(
888    paint: &mut Paint,
889    has_context: bool,
890    context_transform: Transform,
891    context_bbox: Option<Rect>,
892    path_transform: Transform,
893    bbox: Rect,
894    cache: &mut Cache,
895) -> bool {
896    if paint.units() == Units::ObjectBoundingBox
897        || paint.content_units() == Units::ObjectBoundingBox
898    {
899        let bbox = if has_context {
900            let Some(bbox) = context_bbox else {
901                return false;
902            };
903            bbox
904        } else {
905            bbox
906        };
907
908        if paint.to_user_coordinates(bbox, cache).is_none() {
909            return false;
910        }
911    }
912
913    if let Paint::Pattern(patt) = paint {
914        if let Some(patt) = Arc::get_mut(patt) {
915            update_paint_servers(&mut patt.root, Transform::default(), None, None, cache);
916        }
917    }
918
919    if has_context {
920        process_context_paint(paint, context_transform, path_transform, cache);
921    }
922
923    true
924}
925
926fn process_text_decoration(style: &mut Option<TextDecorationStyle>, bbox: Rect, cache: &mut Cache) {
927    if let Some(style) = style.as_mut() {
928        process_fill(
929            &mut style.fill,
930            Transform::default(),
931            Transform::default(),
932            None,
933            bbox,
934            cache,
935        );
936        process_stroke(
937            &mut style.stroke,
938            Transform::default(),
939            Transform::default(),
940            None,
941            bbox,
942            cache,
943        );
944    }
945}
946
947impl Paint {
948    fn to_user_coordinates(&mut self, bbox: Rect, cache: &mut Cache) -> Option<()> {
949        let name = if matches!(self, Paint::Pattern(_)) {
950            "Pattern"
951        } else {
952            "Gradient"
953        };
954        let bbox = bbox
955            .to_non_zero_rect()
956            .log_none(|| log::warn!("{} on zero-sized shapes is not allowed.", name))?;
957
958        // `Arc::get_mut()` allow us to modify some paint servers in-place.
959        // This reduces the amount of cloning and preserves the original ID as well.
960        match self {
961            Paint::Color(_) => {} // unreachable
962            Paint::LinearGradient(lg) => {
963                let transform = lg.transform.post_concat(Transform::from_bbox(bbox));
964                if let Some(lg) = Arc::get_mut(lg) {
965                    lg.base.transform = transform;
966                    lg.base.units = Units::UserSpaceOnUse;
967                } else {
968                    *lg = Arc::new(LinearGradient {
969                        x1: lg.x1,
970                        y1: lg.y1,
971                        x2: lg.x2,
972                        y2: lg.y2,
973                        base: BaseGradient {
974                            id: cache.gen_linear_gradient_id(),
975                            units: Units::UserSpaceOnUse,
976                            transform,
977                            spread_method: lg.spread_method,
978                            stops: lg.stops.clone(),
979                        },
980                    });
981                }
982            }
983            Paint::RadialGradient(rg) => {
984                let transform = rg.transform.post_concat(Transform::from_bbox(bbox));
985                if let Some(rg) = Arc::get_mut(rg) {
986                    rg.base.transform = transform;
987                    rg.base.units = Units::UserSpaceOnUse;
988                } else {
989                    *rg = Arc::new(RadialGradient {
990                        cx: rg.cx,
991                        cy: rg.cy,
992                        r: rg.r,
993                        fx: rg.fx,
994                        fy: rg.fy,
995                        fr: rg.fr,
996                        base: BaseGradient {
997                            id: cache.gen_radial_gradient_id(),
998                            units: Units::UserSpaceOnUse,
999                            transform,
1000                            spread_method: rg.spread_method,
1001                            stops: rg.stops.clone(),
1002                        },
1003                    });
1004                }
1005            }
1006            Paint::Pattern(patt) => {
1007                let rect = if patt.units == Units::ObjectBoundingBox {
1008                    patt.rect.bbox_transform(bbox)
1009                } else {
1010                    patt.rect
1011                };
1012
1013                if let Some(patt) = Arc::get_mut(patt) {
1014                    patt.rect = rect;
1015                    patt.units = Units::UserSpaceOnUse;
1016
1017                    if patt.content_units == Units::ObjectBoundingBox && patt.view_box.is_none() {
1018                        // No need to shift patterns.
1019                        let transform = Transform::from_scale(bbox.width(), bbox.height());
1020                        push_pattern_transform(&mut patt.root, transform);
1021                    }
1022
1023                    if let Some(view_box) = patt.view_box {
1024                        push_pattern_transform(&mut patt.root, view_box.to_transform(rect.size()));
1025                    }
1026
1027                    patt.content_units = Units::UserSpaceOnUse;
1028                } else {
1029                    let mut root = if patt.content_units == Units::ObjectBoundingBox
1030                        && patt.view_box.is_none()
1031                    {
1032                        // No need to shift patterns.
1033                        let transform = Transform::from_scale(bbox.width(), bbox.height());
1034
1035                        let mut g = patt.root.clone();
1036                        push_pattern_transform(&mut g, transform);
1037                        g
1038                    } else {
1039                        patt.root.clone()
1040                    };
1041
1042                    if let Some(view_box) = patt.view_box {
1043                        push_pattern_transform(&mut root, view_box.to_transform(rect.size()));
1044                    }
1045
1046                    *patt = Arc::new(Pattern {
1047                        id: cache.gen_pattern_id(),
1048                        units: Units::UserSpaceOnUse,
1049                        content_units: Units::UserSpaceOnUse,
1050                        transform: patt.transform,
1051                        rect,
1052                        view_box: patt.view_box,
1053                        root,
1054                    });
1055                }
1056            }
1057        }
1058
1059        Some(())
1060    }
1061}
1062
1063fn push_pattern_transform(root: &mut Group, transform: Transform) {
1064    // TODO: we should update abs_transform in all descendants as well
1065    let mut g = std::mem::replace(root, Group::empty());
1066    g.transform = transform;
1067    g.abs_transform = transform;
1068
1069    root.children.push(Node::Group(Box::new(g)));
1070    root.calculate_bounding_boxes();
1071}
1072
1073impl Paint {
1074    #[inline]
1075    pub(crate) fn units(&self) -> Units {
1076        match self {
1077            Self::Color(_) => Units::UserSpaceOnUse,
1078            Self::LinearGradient(lg) => lg.units,
1079            Self::RadialGradient(rg) => rg.units,
1080            Self::Pattern(patt) => patt.units,
1081        }
1082    }
1083
1084    #[inline]
1085    pub(crate) fn content_units(&self) -> Units {
1086        match self {
1087            Self::Pattern(patt) => patt.content_units,
1088            _ => Units::UserSpaceOnUse,
1089        }
1090    }
1091}