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.visibility() != usvg::Visibility::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
76 Some(())
77}
78
79fn stroke_path(
80 path: &usvg::Path,
81 blend_mode: tiny_skia::BlendMode,
82 ctx: &Context,
83 transform: tiny_skia::Transform,
84 pixmap: &mut tiny_skia::PixmapMut,
85) -> Option<()> {
86 let stroke = path.stroke()?;
87 let pattern_pixmap;
88 let mut paint = tiny_skia::Paint::default();
89 match stroke.paint() {
90 usvg::Paint::Color(c) => {
91 paint.set_color_rgba8(c.red, c.green, c.blue, stroke.opacity().to_u8());
92 }
93 usvg::Paint::LinearGradient(ref lg) => {
94 paint.shader = convert_linear_gradient(lg, stroke.opacity())?;
95 }
96 usvg::Paint::RadialGradient(ref rg) => {
97 paint.shader = convert_radial_gradient(rg, stroke.opacity())?;
98 }
99 usvg::Paint::Pattern(ref pattern) => {
100 let (patt_pix, patt_ts) = render_pattern_pixmap(pattern, ctx, transform)?;
101
102 pattern_pixmap = patt_pix;
103 paint.shader = tiny_skia::Pattern::new(
104 pattern_pixmap.as_ref(),
105 tiny_skia::SpreadMode::Repeat,
106 tiny_skia::FilterQuality::Bicubic,
107 stroke.opacity().get(),
108 patt_ts,
109 )
110 }
111 }
112 paint.anti_alias = path.rendering_mode().use_shape_antialiasing();
113 paint.blend_mode = blend_mode;
114
115 pixmap.stroke_path(path.data(), &paint, &stroke.to_tiny_skia(), transform, None);
116
117 Some(())
118}
119
120fn convert_linear_gradient(
121 gradient: &usvg::LinearGradient,
122 opacity: usvg::Opacity,
123) -> Option<tiny_skia::Shader> {
124 let (mode: SpreadMode, points: Vec) = convert_base_gradient(gradient, opacity)?;
125
126 let shader: Shader<'_> = tiny_skia::LinearGradient::new(
127 (gradient.x1(), gradient.y1()).into(),
128 (gradient.x2(), gradient.y2()).into(),
129 stops:points,
130 mode,
131 gradient.transform(),
132 )?;
133
134 Some(shader)
135}
136
137fn convert_radial_gradient(
138 gradient: &usvg::RadialGradient,
139 opacity: usvg::Opacity,
140) -> Option<tiny_skia::Shader> {
141 let (mode: SpreadMode, points: Vec) = convert_base_gradient(gradient, opacity)?;
142
143 let shader: Shader<'_> = tiny_skia::RadialGradient::new(
144 (gradient.fx(), gradient.fy()).into(),
145 (gradient.cx(), gradient.cy()).into(),
146 radius:gradient.r().get(),
147 stops:points,
148 mode,
149 gradient.transform(),
150 )?;
151
152 Some(shader)
153}
154
155fn convert_base_gradient(
156 gradient: &usvg::BaseGradient,
157 opacity: usvg::Opacity,
158) -> Option<(tiny_skia::SpreadMode, Vec<tiny_skia::GradientStop>)> {
159 let mode: SpreadMode = match gradient.spread_method() {
160 usvg::SpreadMethod::Pad => tiny_skia::SpreadMode::Pad,
161 usvg::SpreadMethod::Reflect => tiny_skia::SpreadMode::Reflect,
162 usvg::SpreadMethod::Repeat => tiny_skia::SpreadMode::Repeat,
163 };
164
165 let mut points: Vec = Vec::with_capacity(gradient.stops().len());
166 for stop: &Stop in gradient.stops() {
167 let alpha: NormalizedF32 = stop.opacity() * opacity;
168 let color: Color = tiny_skia::Color::from_rgba8(
169 r:stop.color().red,
170 g:stop.color().green,
171 b:stop.color().blue,
172 a:alpha.to_u8(),
173 );
174 points.push(tiny_skia::GradientStop::new(position:stop.offset().get(), color))
175 }
176
177 Some((mode, points))
178}
179
180fn render_pattern_pixmap(
181 pattern: &usvg::Pattern,
182 ctx: &Context,
183 transform: tiny_skia::Transform,
184) -> Option<(tiny_skia::Pixmap, tiny_skia::Transform)> {
185 let (sx, sy) = {
186 let ts2 = transform.pre_concat(pattern.transform());
187 ts2.get_scale()
188 };
189
190 let rect = pattern.rect();
191 let img_size = tiny_skia::IntSize::from_wh(
192 (rect.width() * sx).round() as u32,
193 (rect.height() * sy).round() as u32,
194 )?;
195 let mut pixmap = tiny_skia::Pixmap::new(img_size.width(), img_size.height())?;
196
197 let mut transform = tiny_skia::Transform::from_scale(sx, sy);
198 if let Some(vbox) = pattern.view_box() {
199 let ts = vbox.to_transform(rect.size());
200 transform = transform.pre_concat(ts);
201 }
202
203 crate::render::render_nodes(pattern.root(), ctx, transform, &mut pixmap.as_mut());
204
205 let mut ts = tiny_skia::Transform::default();
206 ts = ts.pre_concat(pattern.transform());
207 ts = ts.pre_translate(rect.x(), rect.y());
208 ts = ts.pre_scale(1.0 / sx, 1.0 / sy);
209
210 Some((pixmap, ts))
211}
212