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