Skip to main content

svgr/
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::{cache, render::Context, PixmapPool, SvgrCache};
6use tiny_skia::IntSize;
7
8pub fn apply(
9    clip: &usvgr::ClipPath,
10    transform: tiny_skia::Transform,
11    pixmap: &mut tiny_skia::Pixmap,
12    cache: &mut cache::SvgrCache,
13    pixmap_pool: &cache::PixmapPool,
14) {
15    let clip_pixmap = cache
16        .with_subpixmap_cache(
17            clip,
18            transform,
19            pixmap_pool,
20            IntSize::from_wh(pixmap.width(), pixmap.height()).unwrap(),
21            |mut clip_pixmap, cache| {
22                clip_pixmap.fill(tiny_skia::Color::BLACK);
23
24                draw_children(
25                    clip.root(),
26                    tiny_skia::BlendMode::Clear,
27                    transform.pre_concat(clip.transform()),
28                    &mut clip_pixmap.as_mut(),
29                    cache,
30                    &PixmapPool::new(),
31                );
32
33                Some(clip_pixmap)
34            },
35        )
36        .expect("failed to allocate pixmap for clip");
37
38    if let Some(clip) = clip.clip_path() {
39        // here we are handling the recurision on self, and while we hold the reference to the
40        // cache lru instance this will OVERWRITE the existing pixmpa or cache entry
41        apply(
42            clip,
43            transform,
44            pixmap,
45            &mut SvgrCache::none(),
46            &PixmapPool::new(),
47        );
48    }
49
50    let mut mask = tiny_skia::Mask::from_pixmap(clip_pixmap.as_ref(), tiny_skia::MaskType::Alpha);
51    mask.invert();
52    pixmap.apply_mask(&mask);
53}
54
55fn draw_children(
56    parent: &usvgr::Group,
57    mode: tiny_skia::BlendMode,
58    transform: tiny_skia::Transform,
59    pixmap: &mut tiny_skia::PixmapMut,
60    cache: &mut cache::SvgrCache,
61    pixmap_pool: &cache::PixmapPool,
62) {
63    for child in parent.children() {
64        match child {
65            usvgr::Node::Path(ref path) => {
66                if path.visibility() != usvgr::Visibility::Visible {
67                    continue;
68                }
69
70                let ctx = Context {
71                    // We could use any values here. They will not be used anyway.
72                    max_bbox: tiny_skia::IntRect::from_xywh(0, 0, 1, 1).unwrap(),
73                };
74
75                crate::path::fill_path(path, mode, &ctx, transform, pixmap, cache, pixmap_pool);
76            }
77            usvgr::Node::Text(ref text) => {
78                draw_children(
79                    text.flattened(),
80                    mode,
81                    transform,
82                    pixmap,
83                    cache,
84                    pixmap_pool,
85                );
86            }
87            usvgr::Node::Group(ref group) => {
88                let transform = transform.pre_concat(group.transform());
89
90                if let Some(clip) = group.clip_path() {
91                    // If a `clipPath` child also has a `clip-path`
92                    // then we should render this child on a new canvas,
93                    // clip it, and only then draw it to the `clipPath`.
94                    clip_group(group, clip, transform, pixmap, cache, pixmap_pool);
95                } else {
96                    draw_children(group, mode, transform, pixmap, cache, pixmap_pool);
97                }
98            }
99            _ => {}
100        }
101    }
102}
103
104fn clip_group(
105    children: &usvgr::Group,
106    clip: &usvgr::ClipPath,
107    transform: tiny_skia::Transform,
108    pixmap: &mut tiny_skia::PixmapMut,
109    cache: &mut cache::SvgrCache,
110    pixmap_pool: &cache::PixmapPool,
111) -> Option<()> {
112    let mut clip_pixmap = pixmap_pool
113        .take_or_allocate(pixmap.width(), pixmap.height())
114        .unwrap();
115
116    draw_children(
117        children,
118        tiny_skia::BlendMode::SourceOver,
119        transform,
120        &mut clip_pixmap.as_mut(),
121        cache,
122        pixmap_pool,
123    );
124    apply(clip, transform, &mut clip_pixmap, cache, pixmap_pool);
125
126    let mut paint = tiny_skia::PixmapPaint::default();
127    paint.blend_mode = tiny_skia::BlendMode::Xor;
128    pixmap.draw_pixmap(
129        0,
130        0,
131        clip_pixmap.as_ref(),
132        &paint,
133        tiny_skia::Transform::identity(),
134        None,
135    );
136
137    Some(())
138}