resvg/
clip.rs

1// Copyright 2019 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4use crate::render::Context;
5
6pub fn apply(
7    clip: &usvg::ClipPath,
8    transform: tiny_skia::Transform,
9    pixmap: &mut tiny_skia::Pixmap,
10) {
11    let mut clip_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();
12    clip_pixmap.fill(tiny_skia::Color::BLACK);
13
14    draw_children(
15        clip.root(),
16        tiny_skia::BlendMode::Clear,
17        transform.pre_concat(clip.transform()),
18        &mut clip_pixmap.as_mut(),
19    );
20
21    if let Some(clip) = clip.clip_path() {
22        apply(clip, transform, pixmap);
23    }
24
25    let mut mask = tiny_skia::Mask::from_pixmap(clip_pixmap.as_ref(), tiny_skia::MaskType::Alpha);
26    mask.invert();
27    pixmap.apply_mask(&mask);
28}
29
30fn draw_children(
31    parent: &usvg::Group,
32    mode: tiny_skia::BlendMode,
33    transform: tiny_skia::Transform,
34    pixmap: &mut tiny_skia::PixmapMut,
35) {
36    for child in parent.children() {
37        match child {
38            usvg::Node::Path(ref path) => {
39                if !path.is_visible() {
40                    continue;
41                }
42
43                // We could use any values here. They will not be used anyway.
44                let ctx = Context {
45                    max_bbox: tiny_skia::IntRect::from_xywh(0, 0, 1, 1).unwrap(),
46                };
47
48                crate::path::fill_path(path, mode, &ctx, transform, pixmap);
49            }
50            usvg::Node::Text(ref text) => {
51                draw_children(text.flattened(), mode, transform, pixmap);
52            }
53            usvg::Node::Group(ref group) => {
54                let transform = transform.pre_concat(group.transform());
55
56                if let Some(clip) = group.clip_path() {
57                    // If a `clipPath` child also has a `clip-path`
58                    // then we should render this child on a new canvas,
59                    // clip it, and only then draw it to the `clipPath`.
60                    clip_group(group, clip, transform, pixmap);
61                } else {
62                    draw_children(group, mode, transform, pixmap);
63                }
64            }
65            _ => {}
66        }
67    }
68}
69
70fn clip_group(
71    children: &usvg::Group,
72    clip: &usvg::ClipPath,
73    transform: tiny_skia::Transform,
74    pixmap: &mut tiny_skia::PixmapMut,
75) -> Option<()> {
76    let mut clip_pixmap = tiny_skia::Pixmap::new(pixmap.width(), pixmap.height()).unwrap();
77
78    draw_children(
79        children,
80        tiny_skia::BlendMode::SourceOver,
81        transform,
82        &mut clip_pixmap.as_mut(),
83    );
84    apply(clip, transform, &mut clip_pixmap);
85
86    let mut paint = tiny_skia::PixmapPaint::default();
87    paint.blend_mode = tiny_skia::BlendMode::Xor;
88    pixmap.draw_pixmap(
89        0,
90        0,
91        clip_pixmap.as_ref(),
92        &paint,
93        tiny_skia::Transform::identity(),
94        None,
95    );
96
97    Some(())
98}