resvg/
path.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 render(
8    path: &usvg::Path,
9    blend_mode: tiny_skia::BlendMode,
10    ctx: &Context,
11    transform: tiny_skia::Transform,
12    pixmap: &mut tiny_skia::PixmapMut,
13) {
14    if !path.is_visible() {
15        return;
16    }
17
18    if path.paint_order() == usvg::PaintOrder::FillAndStroke {
19        fill_path(path, blend_mode, ctx, transform, pixmap);
20        stroke_path(path, blend_mode, ctx, transform, pixmap);
21    } else {
22        stroke_path(path, blend_mode, ctx, transform, pixmap);
23        fill_path(path, blend_mode, ctx, transform, pixmap);
24    }
25}
26
27pub fn fill_path(
28    path: &usvg::Path,
29    blend_mode: tiny_skia::BlendMode,
30    ctx: &Context,
31    transform: tiny_skia::Transform,
32    pixmap: &mut tiny_skia::PixmapMut,
33) -> Option<()> {
34    let fill = path.fill()?;
35
36    // Horizontal and vertical lines cannot be filled. Skip.
37    if path.data().bounds().width() == 0.0 || path.data().bounds().height() == 0.0 {
38        return None;
39    }
40
41    let rule = match fill.rule() {
42        usvg::FillRule::NonZero => tiny_skia::FillRule::Winding,
43        usvg::FillRule::EvenOdd => tiny_skia::FillRule::EvenOdd,
44    };
45
46    let pattern_pixmap;
47    let mut paint = tiny_skia::Paint::default();
48    match fill.paint() {
49        usvg::Paint::Color(c) => {
50            paint.set_color_rgba8(c.red, c.green, c.blue, fill.opacity().to_u8());
51        }
52        usvg::Paint::LinearGradient(ref lg) => {
53            paint.shader = convert_linear_gradient(lg, fill.opacity())?;
54        }
55        usvg::Paint::RadialGradient(ref rg) => {
56            paint.shader = convert_radial_gradient(rg, fill.opacity())?;
57        }
58        usvg::Paint::Pattern(ref pattern) => {
59            let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;
60
61            pattern_pixmap = patt_pix;
62            paint.shader = tiny_skia::Pattern::new(
63                pattern_pixmap.as_ref(),
64                tiny_skia::SpreadMode::Repeat,
65                tiny_skia::FilterQuality::Bicubic,
66                fill.opacity().get(),
67                patt_ts,
68            )
69        }
70    }
71    paint.anti_alias = path.rendering_mode().use_shape_antialiasing();
72    paint.blend_mode = blend_mode;
73
74    pixmap.fill_path(path.data(), &paint, rule, transform, None);
75    Some(())
76}
77
78fn stroke_path(
79    path: &usvg::Path,
80    blend_mode: tiny_skia::BlendMode,
81    ctx: &Context,
82    transform: tiny_skia::Transform,
83    pixmap: &mut tiny_skia::PixmapMut,
84) -> Option<()> {
85    let stroke = path.stroke()?;
86    let pattern_pixmap;
87    let mut paint = tiny_skia::Paint::default();
88    match stroke.paint() {
89        usvg::Paint::Color(c) => {
90            paint.set_color_rgba8(c.red, c.green, c.blue, stroke.opacity().to_u8());
91        }
92        usvg::Paint::LinearGradient(ref lg) => {
93            paint.shader = convert_linear_gradient(lg, stroke.opacity())?;
94        }
95        usvg::Paint::RadialGradient(ref rg) => {
96            paint.shader = convert_radial_gradient(rg, stroke.opacity())?;
97        }
98        usvg::Paint::Pattern(ref pattern) => {
99            let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;
100
101            pattern_pixmap = patt_pix;
102            paint.shader = tiny_skia::Pattern::new(
103                pattern_pixmap.as_ref(),
104                tiny_skia::SpreadMode::Repeat,
105                tiny_skia::FilterQuality::Bicubic,
106                stroke.opacity().get(),
107                patt_ts,
108            )
109        }
110    }
111    paint.anti_alias = path.rendering_mode().use_shape_antialiasing();
112    paint.blend_mode = blend_mode;
113
114    pixmap.stroke_path(path.data(), &paint, &stroke.to_tiny_skia(), transform, None);
115
116    Some(())
117}
118
119fn convert_linear_gradient(
120    gradient: &usvg::LinearGradient,
121    opacity: usvg::Opacity,
122) -> Option<tiny_skia::Shader> {
123    let (mode, points) = convert_base_gradient(gradient, opacity)?;
124
125    let shader = tiny_skia::LinearGradient::new(
126        (gradient.x1(), gradient.y1()).into(),
127        (gradient.x2(), gradient.y2()).into(),
128        points,
129        mode,
130        gradient.transform(),
131    )?;
132
133    Some(shader)
134}
135
136fn convert_radial_gradient(
137    gradient: &usvg::RadialGradient,
138    opacity: usvg::Opacity,
139) -> Option<tiny_skia::Shader> {
140    let (mode, points) = convert_base_gradient(gradient, opacity)?;
141
142    let shader = tiny_skia::RadialGradient::new(
143        (gradient.fx(), gradient.fy()).into(),
144        (gradient.cx(), gradient.cy()).into(),
145        gradient.r().get(),
146        points,
147        mode,
148        gradient.transform(),
149    )?;
150
151    Some(shader)
152}
153
154fn convert_base_gradient(
155    gradient: &usvg::BaseGradient,
156    opacity: usvg::Opacity,
157) -> Option<(tiny_skia::SpreadMode, Vec<tiny_skia::GradientStop>)> {
158    let mode = match gradient.spread_method() {
159        usvg::SpreadMethod::Pad => tiny_skia::SpreadMode::Pad,
160        usvg::SpreadMethod::Reflect => tiny_skia::SpreadMode::Reflect,
161        usvg::SpreadMethod::Repeat => tiny_skia::SpreadMode::Repeat,
162    };
163
164    let mut points = Vec::with_capacity(gradient.stops().len());
165    for stop in gradient.stops() {
166        let alpha = stop.opacity() * opacity;
167        let color = tiny_skia::Color::from_rgba8(
168            stop.color().red,
169            stop.color().green,
170            stop.color().blue,
171            alpha.to_u8(),
172        );
173        points.push(tiny_skia::GradientStop::new(stop.offset().get(), color))
174    }
175
176    Some((mode, points))
177}
178
179fn render_pattern_pixmap(
180    pattern: &usvg::Pattern,
181    ctx: &Context,
182    transform: tiny_skia::Transform,
183) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> {
184    let (sx, sy) = {
185        let ts2 = transform.pre_concat(pattern.transform());
186        ts2.get_scale()
187    };
188
189    let rect = pattern.rect();
190    let img_size = tiny_skia::IntSize::from_wh(
191        (rect.width() * sx).round() as u32,
192        (rect.height() * sy).round() as u32,
193    )?;
194    let mut pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?;
195
196    let transform = tiny_skia::Transform::from_scale(sx, sy);
197    crate::render::render_nodes(pattern.root(), ctx, transform, &mut pixmap.as_mut());
198
199    let mut ts = tiny_skia::Transform::default();
200    ts = ts.pre_concat(pattern.transform());
201    ts = ts.pre_translate(rect.x(), rect.y());
202    ts = ts.pre_scale(1.0 / sx, 1.0 / sy);
203
204    Some((pixmap, ts))
205}