usvg/parser/
paint_server.rs

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