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