1use crate::OptionLog;
6
7pub struct Context {
8 pub max_bbox: tiny_skia::IntRect,
9}
10
11pub fn render_nodes(
12 parent: &usvg::Group,
13 ctx: &Context,
14 transform: tiny_skia::Transform,
15 pixmap: &mut tiny_skia::PixmapMut,
16) {
17 for node in parent.children() {
18 render_node(node, ctx, transform, pixmap);
19 }
20}
21
22pub fn render_node(
23 node: &usvg::Node,
24 ctx: &Context,
25 transform: tiny_skia::Transform,
26 pixmap: &mut tiny_skia::PixmapMut,
27) {
28 match node {
29 usvg::Node::Group(ref group) => {
30 render_group(group, ctx, transform, pixmap);
31 }
32 usvg::Node::Path(ref path) => {
33 crate::path::render(
34 path,
35 tiny_skia::BlendMode::SourceOver,
36 ctx,
37 transform,
38 pixmap,
39 );
40 }
41 usvg::Node::Image(ref image) => {
42 crate::image::render(image, transform, pixmap);
43 }
44 usvg::Node::Text(ref text) => {
45 render_group(text.flattened(), ctx, transform, pixmap);
46 }
47 }
48}
49
50fn render_group(
51 group: &usvg::Group,
52 ctx: &Context,
53 transform: tiny_skia::Transform,
54 pixmap: &mut tiny_skia::PixmapMut,
55) -> Option<()> {
56 let transform = transform.pre_concat(group.transform());
57
58 if !group.should_isolate() {
59 render_nodes(group, ctx, transform, pixmap);
60 return Some(());
61 }
62
63 let bbox = group.layer_bounding_box().transform(transform)?;
64
65 let mut ibbox = if group.filters().is_empty() {
66 tiny_skia::IntRect::from_xywh(
69 bbox.x().floor() as i32 - 2,
70 bbox.y().floor() as i32 - 2,
71 bbox.width().ceil() as u32 + 4,
72 bbox.height().ceil() as u32 + 4,
73 )?
74 } else {
75 let bbox = bbox.to_int_rect();
78 crate::geom::fit_to_rect(bbox, ctx.max_bbox)?
82 };
83
84 if group.filters().is_empty() {
87 ibbox = crate::geom::fit_to_rect(ibbox, ctx.max_bbox)?;
88 }
89
90 let shift_ts = {
91 let mut dx = bbox.x();
93 let mut dy = bbox.y();
94
95 dx -= bbox.x() - ibbox.x() as f32;
97 dy -= bbox.y() - ibbox.y() as f32;
98
99 tiny_skia::Transform::from_translate(-dx, -dy)
100 };
101
102 let transform = shift_ts.pre_concat(transform);
103
104 let mut sub_pixmap = tiny_skia::Pixmap::new(ibbox.width(), ibbox.height())
105 .log_none(|| log::warn!("Failed to allocate a group layer for: {:?}.", ibbox))?;
106
107 render_nodes(group, ctx, transform, &mut sub_pixmap.as_mut());
108
109 if !group.filters().is_empty() {
110 for filter in group.filters() {
111 crate::filter::apply(filter, transform, &mut sub_pixmap);
112 }
113 }
114
115 if let Some(clip_path) = group.clip_path() {
116 crate::clip::apply(clip_path, transform, &mut sub_pixmap);
117 }
118
119 if let Some(mask) = group.mask() {
120 crate::mask::apply(mask, ctx, transform, &mut sub_pixmap);
121 }
122
123 let paint = tiny_skia::PixmapPaint {
124 opacity: group.opacity().get(),
125 blend_mode: convert_blend_mode(group.blend_mode()),
126 quality: tiny_skia::FilterQuality::Nearest,
127 };
128
129 pixmap.draw_pixmap(
130 ibbox.x(),
131 ibbox.y(),
132 sub_pixmap.as_ref(),
133 &paint,
134 tiny_skia::Transform::identity(),
135 None,
136 );
137
138 Some(())
139}
140
141pub fn convert_blend_mode(mode: usvg::BlendMode) -> tiny_skia::BlendMode {
142 match mode {
143 usvg::BlendMode::Normal => tiny_skia::BlendMode::SourceOver,
144 usvg::BlendMode::Multiply => tiny_skia::BlendMode::Multiply,
145 usvg::BlendMode::Screen => tiny_skia::BlendMode::Screen,
146 usvg::BlendMode::Overlay => tiny_skia::BlendMode::Overlay,
147 usvg::BlendMode::Darken => tiny_skia::BlendMode::Darken,
148 usvg::BlendMode::Lighten => tiny_skia::BlendMode::Lighten,
149 usvg::BlendMode::ColorDodge => tiny_skia::BlendMode::ColorDodge,
150 usvg::BlendMode::ColorBurn => tiny_skia::BlendMode::ColorBurn,
151 usvg::BlendMode::HardLight => tiny_skia::BlendMode::HardLight,
152 usvg::BlendMode::SoftLight => tiny_skia::BlendMode::SoftLight,
153 usvg::BlendMode::Difference => tiny_skia::BlendMode::Difference,
154 usvg::BlendMode::Exclusion => tiny_skia::BlendMode::Exclusion,
155 usvg::BlendMode::Hue => tiny_skia::BlendMode::Hue,
156 usvg::BlendMode::Saturation => tiny_skia::BlendMode::Saturation,
157 usvg::BlendMode::Color => tiny_skia::BlendMode::Color,
158 usvg::BlendMode::Luminosity => tiny_skia::BlendMode::Luminosity,
159 }
160}