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            | (AId::Fr, EId::RadialGradient)
480            // Other attributes can be resolved
481            // from any kind of gradient.
482            | (AId::GradientUnits, EId::LinearGradient)
483            | (AId::GradientUnits, EId::RadialGradient)
484            | (AId::SpreadMethod, EId::LinearGradient)
485            | (AId::SpreadMethod, EId::RadialGradient)
486            | (AId::GradientTransform, EId::LinearGradient)
487            | (AId::GradientTransform, EId::RadialGradient) => {
488                if link.has_attribute(name) {
489                    return link;
490                }
491            }
492            _ => break,
493        }
494    }
495
496    node
497}
498
499fn resolve_pattern_attr<'a, 'input: 'a>(
500    node: SvgNode<'a, 'input>,
501    name: AId,
502) -> SvgNode<'a, 'input> {
503    for link in node.href_iter() {
504        let tag_name = match link.tag_name() {
505            Some(v) => v,
506            None => return node,
507        };
508
509        if tag_name != EId::Pattern {
510            break;
511        }
512
513        if link.has_attribute(name) {
514            return link;
515        }
516    }
517
518    node
519}
520
521fn resolve_filter_attr<'a, 'input: 'a>(node: SvgNode<'a, 'input>, aid: AId) -> SvgNode<'a, 'input> {
522    for link in node.href_iter() {
523        let tag_name = match link.tag_name() {
524            Some(v) => v,
525            None => return node,
526        };
527
528        if tag_name != EId::Filter {
529            break;
530        }
531
532        if link.has_attribute(aid) {
533            return link;
534        }
535    }
536
537    node
538}
539
540fn stops_to_color(stops: &[Stop]) -> Option<ServerOrColor> {
541    if stops.is_empty() {
542        None
543    } else {
544        Some(ServerOrColor::Color {
545            color: stops[0].color,
546            opacity: stops[0].opacity,
547        })
548    }
549}
550
551// Update paints servers by doing the following:
552// 1. Replace context fills/strokes that are linked to
553// a use node with their actual values.
554// 2. Convert all object units to UserSpaceOnUse
555pub fn update_paint_servers(
556    group: &mut Group,
557    context_transform: Transform,
558    context_bbox: Option<Rect>,
559    text_bbox: Option<Rect>,
560    cache: &mut Cache,
561) {
562    for child in &mut group.children {
563        // Set context transform and bbox if applicable if the
564        // current group is a use node.
565        let (context_transform, context_bbox) = if group.is_context_element {
566            (group.abs_transform, Some(group.bounding_box))
567        } else {
568            (context_transform, context_bbox)
569        };
570
571        node_to_user_coordinates(child, context_transform, context_bbox, text_bbox, cache);
572    }
573}
574
575// When parsing clipPaths, masks and filters we already know group's bounding box.
576// But with gradients and patterns we don't, because we have to know text bounding box
577// before we even parsed it. Which is impossible.
578// Therefore our only choice is to parse gradients and patterns preserving their units
579// and then replace them with `userSpaceOnUse` after the whole tree parsing is finished.
580// So while gradients and patterns do still store their units,
581// they are not exposed in the public API and for the caller they are always `userSpaceOnUse`.
582fn node_to_user_coordinates(
583    node: &mut Node,
584    context_transform: Transform,
585    context_bbox: Option<Rect>,
586    text_bbox: Option<Rect>,
587    cache: &mut Cache,
588) {
589    match node {
590        Node::Group(g) => {
591            // No need to check clip paths, because they cannot have paint servers.
592            if let Some(mask) = &mut g.mask {
593                if let Some(mask) = Arc::get_mut(mask) {
594                    update_paint_servers(
595                        &mut mask.root,
596                        context_transform,
597                        context_bbox,
598                        None,
599                        cache,
600                    );
601
602                    if let Some(sub_mask) = &mut mask.mask {
603                        if let Some(sub_mask) = Arc::get_mut(sub_mask) {
604                            update_paint_servers(
605                                &mut sub_mask.root,
606                                context_transform,
607                                context_bbox,
608                                None,
609                                cache,
610                            );
611                        }
612                    }
613                }
614            }
615
616            for filter in &mut g.filters {
617                if let Some(filter) = Arc::get_mut(filter) {
618                    for primitive in &mut filter.primitives {
619                        if let filter::Kind::Image(image) = &mut primitive.kind {
620                            update_paint_servers(
621                                &mut image.root,
622                                context_transform,
623                                context_bbox,
624                                None,
625                                cache,
626                            );
627                        }
628                    }
629                }
630            }
631
632            update_paint_servers(g, context_transform, context_bbox, text_bbox, cache);
633        }
634        Node::Path(path) => {
635            // Paths inside `Text::flattened` are special and must use text's bounding box
636            // instead of their own.
637            let bbox = text_bbox.unwrap_or(path.bounding_box);
638
639            process_fill(
640                &mut path.fill,
641                path.abs_transform,
642                context_transform,
643                context_bbox,
644                bbox,
645                cache,
646            );
647            process_stroke(
648                &mut path.stroke,
649                path.abs_transform,
650                context_transform,
651                context_bbox,
652                bbox,
653                cache,
654            );
655        }
656        Node::Image(image) => {
657            if let ImageKind::SVG(tree) = &mut image.kind {
658                update_paint_servers(&mut tree.root, context_transform, context_bbox, None, cache);
659            }
660        }
661        Node::Text(text) => {
662            // By the SVG spec, `tspan` doesn't have a bbox and uses the parent `text` bbox.
663            // Therefore we have to use text's bbox when converting tspan and flatted text
664            // paint servers.
665            let bbox = text.bounding_box;
666
667            // We need to update three things:
668            // 1. The fills/strokes of the original elements in the usvg tree.
669            // 2. The fills/strokes of the layouted elements of the text.
670            // 3. The fills/strokes of the outlined text.
671
672            // 1.
673            for chunk in &mut text.chunks {
674                for span in &mut chunk.spans {
675                    process_fill(
676                        &mut span.fill,
677                        text.abs_transform,
678                        context_transform,
679                        context_bbox,
680                        bbox,
681                        cache,
682                    );
683                    process_stroke(
684                        &mut span.stroke,
685                        text.abs_transform,
686                        context_transform,
687                        context_bbox,
688                        bbox,
689                        cache,
690                    );
691                    process_text_decoration(&mut span.decoration.underline, bbox, cache);
692                    process_text_decoration(&mut span.decoration.overline, bbox, cache);
693                    process_text_decoration(&mut span.decoration.line_through, bbox, cache);
694                }
695            }
696
697            // 2.
698            #[cfg(feature = "text")]
699            for span in &mut text.layouted {
700                process_fill(
701                    &mut span.fill,
702                    text.abs_transform,
703                    context_transform,
704                    context_bbox,
705                    bbox,
706                    cache,
707                );
708                process_stroke(
709                    &mut span.stroke,
710                    text.abs_transform,
711                    context_transform,
712                    context_bbox,
713                    bbox,
714                    cache,
715                );
716
717                let mut process_decoration = |path: &mut Path| {
718                    process_fill(
719                        &mut path.fill,
720                        text.abs_transform,
721                        context_transform,
722                        context_bbox,
723                        bbox,
724                        cache,
725                    );
726                    process_stroke(
727                        &mut path.stroke,
728                        text.abs_transform,
729                        context_transform,
730                        context_bbox,
731                        bbox,
732                        cache,
733                    );
734                };
735
736                if let Some(path) = &mut span.overline {
737                    process_decoration(path);
738                }
739
740                if let Some(path) = &mut span.underline {
741                    process_decoration(path);
742                }
743
744                if let Some(path) = &mut span.line_through {
745                    process_decoration(path);
746                }
747            }
748
749            // 3.
750            update_paint_servers(
751                &mut text.flattened,
752                context_transform,
753                context_bbox,
754                Some(bbox),
755                cache,
756            );
757        }
758    }
759}
760
761fn process_fill(
762    fill: &mut Option<Fill>,
763    path_transform: Transform,
764    context_transform: Transform,
765    context_bbox: Option<Rect>,
766    bbox: Rect,
767    cache: &mut Cache,
768) {
769    let mut ok = false;
770    if let Some(fill) = fill.as_mut() {
771        // Path context elements (i.e. for  markers) have already been resolved,
772        // so we only care about use nodes.
773        ok = process_paint(
774            &mut fill.paint,
775            matches!(fill.context_element, Some(ContextElement::UseNode)),
776            context_transform,
777            context_bbox,
778            path_transform,
779            bbox,
780            cache,
781        );
782    }
783    if !ok {
784        *fill = None;
785    }
786}
787
788fn process_stroke(
789    stroke: &mut Option<Stroke>,
790    path_transform: Transform,
791    context_transform: Transform,
792    context_bbox: Option<Rect>,
793    bbox: Rect,
794    cache: &mut Cache,
795) {
796    let mut ok = false;
797    if let Some(stroke) = stroke.as_mut() {
798        // Path context elements (i.e. for  markers) have already been resolved,
799        // so we only care about use nodes.
800        ok = process_paint(
801            &mut stroke.paint,
802            matches!(stroke.context_element, Some(ContextElement::UseNode)),
803            context_transform,
804            context_bbox,
805            path_transform,
806            bbox,
807            cache,
808        );
809    }
810    if !ok {
811        *stroke = None;
812    }
813}
814
815fn process_context_paint(
816    paint: &mut Paint,
817    context_transform: Transform,
818    path_transform: Transform,
819    cache: &mut Cache,
820) -> Option<()> {
821    // The idea is the following: We have a certain context element that has
822    // a transform A, and further below in the tree we have for example a path
823    // whose paint has a transform C. In order to get from A to C, there is some
824    // transformation matrix B such that A x B = C. We now need to figure out
825    // a way to get from C back to A, so that the transformation of the paint
826    // matches the one from the context element, even if B was applied. How
827    // do we do that? We calculate CxB^(-1), which will overall then have
828    // the same effect as A. How do we calculate B^(-1)?
829    // --> (A^(-1)xC)^(-1)
830    let rev_transform = context_transform
831        .invert()?
832        .pre_concat(path_transform)
833        .invert()?;
834
835    match paint {
836        Paint::Color(_) => {}
837        Paint::LinearGradient(lg) => {
838            let transform = lg.transform.post_concat(rev_transform);
839            *paint = Paint::LinearGradient(Arc::new(LinearGradient {
840                x1: lg.x1,
841                y1: lg.y1,
842                x2: lg.x2,
843                y2: lg.y2,
844                base: BaseGradient {
845                    id: cache.gen_linear_gradient_id(),
846                    units: lg.units,
847                    transform,
848                    spread_method: lg.spread_method,
849                    stops: lg.stops.clone(),
850                },
851            }));
852        }
853        Paint::RadialGradient(rg) => {
854            let transform = rg.transform.post_concat(rev_transform);
855            *paint = Paint::RadialGradient(Arc::new(RadialGradient {
856                cx: rg.cx,
857                cy: rg.cy,
858                r: rg.r,
859                fx: rg.fx,
860                fy: rg.fy,
861                fr: rg.fr,
862                base: BaseGradient {
863                    id: cache.gen_radial_gradient_id(),
864                    units: rg.units,
865                    transform,
866                    spread_method: rg.spread_method,
867                    stops: rg.stops.clone(),
868                },
869            }));
870        }
871        Paint::Pattern(pat) => {
872            let transform = pat.transform.post_concat(rev_transform);
873            *paint = Paint::Pattern(Arc::new(Pattern {
874                id: cache.gen_pattern_id(),
875                units: pat.units,
876                content_units: pat.content_units,
877                transform,
878                rect: pat.rect,
879                view_box: pat.view_box,
880                root: pat.root.clone(),
881            }));
882        }
883    }
884
885    Some(())
886}
887
888pub(crate) fn process_paint(
889    paint: &mut Paint,
890    has_context: bool,
891    context_transform: Transform,
892    context_bbox: Option<Rect>,
893    path_transform: Transform,
894    bbox: Rect,
895    cache: &mut Cache,
896) -> bool {
897    if paint.units() == Units::ObjectBoundingBox
898        || paint.content_units() == Units::ObjectBoundingBox
899    {
900        let bbox = if has_context {
901            let Some(bbox) = context_bbox else {
902                return false;
903            };
904            bbox
905        } else {
906            bbox
907        };
908
909        if paint.to_user_coordinates(bbox, cache).is_none() {
910            return false;
911        }
912    }
913
914    if let Paint::Pattern(patt) = paint {
915        if let Some(patt) = Arc::get_mut(patt) {
916            update_paint_servers(&mut patt.root, Transform::default(), None, None, cache);
917        }
918    }
919
920    if has_context {
921        process_context_paint(paint, context_transform, path_transform, cache);
922    }
923
924    true
925}
926
927fn process_text_decoration(style: &mut Option<TextDecorationStyle>, bbox: Rect, cache: &mut Cache) {
928    if let Some(style) = style.as_mut() {
929        process_fill(
930            &mut style.fill,
931            Transform::default(),
932            Transform::default(),
933            None,
934            bbox,
935            cache,
936        );
937        process_stroke(
938            &mut style.stroke,
939            Transform::default(),
940            Transform::default(),
941            None,
942            bbox,
943            cache,
944        );
945    }
946}
947
948impl Paint {
949    fn to_user_coordinates(&mut self, bbox: Rect, cache: &mut Cache) -> Option<()> {
950        let name = if matches!(self, Paint::Pattern(_)) {
951            "Pattern"
952        } else {
953            "Gradient"
954        };
955        let bbox = bbox
956            .to_non_zero_rect()
957            .log_none(|| log::warn!("{} on zero-sized shapes is not allowed.", name))?;
958
959        // `Arc::get_mut()` allow us to modify some paint servers in-place.
960        // This reduces the amount of cloning and preserves the original ID as well.
961        match self {
962            Paint::Color(_) => {} // unreachable
963            Paint::LinearGradient(lg) => {
964                let transform = lg.transform.post_concat(Transform::from_bbox(bbox));
965                if let Some(lg) = Arc::get_mut(lg) {
966                    lg.base.transform = transform;
967                    lg.base.units = Units::UserSpaceOnUse;
968                } else {
969                    *lg = Arc::new(LinearGradient {
970                        x1: lg.x1,
971                        y1: lg.y1,
972                        x2: lg.x2,
973                        y2: lg.y2,
974                        base: BaseGradient {
975                            id: cache.gen_linear_gradient_id(),
976                            units: Units::UserSpaceOnUse,
977                            transform,
978                            spread_method: lg.spread_method,
979                            stops: lg.stops.clone(),
980                        },
981                    });
982                }
983            }
984            Paint::RadialGradient(rg) => {
985                let transform = rg.transform.post_concat(Transform::from_bbox(bbox));
986                if let Some(rg) = Arc::get_mut(rg) {
987                    rg.base.transform = transform;
988                    rg.base.units = Units::UserSpaceOnUse;
989                } else {
990                    *rg = Arc::new(RadialGradient {
991                        cx: rg.cx,
992                        cy: rg.cy,
993                        r: rg.r,
994                        fx: rg.fx,
995                        fy: rg.fy,
996                        fr: rg.fr,
997                        base: BaseGradient {
998                            id: cache.gen_radial_gradient_id(),
999                            units: Units::UserSpaceOnUse,
1000                            transform,
1001                            spread_method: rg.spread_method,
1002                            stops: rg.stops.clone(),
1003                        },
1004                    });
1005                }
1006            }
1007            Paint::Pattern(patt) => {
1008                let rect = if patt.units == Units::ObjectBoundingBox {
1009                    patt.rect.bbox_transform(bbox)
1010                } else {
1011                    patt.rect
1012                };
1013
1014                if let Some(patt) = Arc::get_mut(patt) {
1015                    patt.rect = rect;
1016                    patt.units = Units::UserSpaceOnUse;
1017
1018                    if patt.content_units == Units::ObjectBoundingBox && patt.view_box.is_none() {
1019                        // No need to shift patterns.
1020                        let transform = Transform::from_scale(bbox.width(), bbox.height());
1021                        push_pattern_transform(&mut patt.root, transform);
1022                    }
1023
1024                    if let Some(view_box) = patt.view_box {
1025                        push_pattern_transform(&mut patt.root, view_box.to_transform(rect.size()));
1026                    }
1027
1028                    patt.content_units = Units::UserSpaceOnUse;
1029                } else {
1030                    let mut root = if patt.content_units == Units::ObjectBoundingBox
1031                        && patt.view_box.is_none()
1032                    {
1033                        // No need to shift patterns.
1034                        let transform = Transform::from_scale(bbox.width(), bbox.height());
1035
1036                        let mut g = patt.root.clone();
1037                        push_pattern_transform(&mut g, transform);
1038                        g
1039                    } else {
1040                        patt.root.clone()
1041                    };
1042
1043                    if let Some(view_box) = patt.view_box {
1044                        push_pattern_transform(&mut root, view_box.to_transform(rect.size()));
1045                    }
1046
1047                    *patt = Arc::new(Pattern {
1048                        id: cache.gen_pattern_id(),
1049                        units: Units::UserSpaceOnUse,
1050                        content_units: Units::UserSpaceOnUse,
1051                        transform: patt.transform,
1052                        rect,
1053                        view_box: patt.view_box,
1054                        root,
1055                    });
1056                }
1057            }
1058        }
1059
1060        Some(())
1061    }
1062}
1063
1064fn push_pattern_transform(root: &mut Group, transform: Transform) {
1065    // TODO: we should update abs_transform in all descendants as well
1066    let mut g = std::mem::replace(root, Group::empty());
1067    g.transform = transform;
1068    g.abs_transform = transform;
1069
1070    root.children.push(Node::Group(Box::new(g)));
1071    root.calculate_bounding_boxes();
1072}
1073
1074impl Paint {
1075    #[inline]
1076    pub(crate) fn units(&self) -> Units {
1077        match self {
1078            Self::Color(_) => Units::UserSpaceOnUse,
1079            Self::LinearGradient(lg) => lg.units,
1080            Self::RadialGradient(rg) => rg.units,
1081            Self::Pattern(patt) => patt.units,
1082        }
1083    }
1084
1085    #[inline]
1086    pub(crate) fn content_units(&self) -> Units {
1087        match self {
1088            Self::Pattern(patt) => patt.content_units,
1089            _ => Units::UserSpaceOnUse,
1090        }
1091    }
1092}