1#![forbid(unsafe_code)]
9#![warn(missing_docs)]
10#![allow(clippy::field_reassign_with_default)]
11#![allow(clippy::identity_op)]
12#![allow(clippy::too_many_arguments)]
13#![allow(clippy::uninlined_format_args)]
14#![allow(clippy::upper_case_acronyms)]
15#![allow(clippy::wrong_self_convention)]
16
17pub use tiny_skia;
18pub use usvg;
19
20mod clip;
21mod filter;
22mod geom;
23mod image;
24mod mask;
25mod path;
26mod render;
27
28pub fn render(
35 tree: &usvg::Tree,
36 transform: tiny_skia::Transform,
37 pixmap: &mut tiny_skia::PixmapMut,
38) {
39 let max_bbox = max_filter_bbox(pixmap.width(), pixmap.height());
40
41 let ctx = render::Context { max_bbox };
42 render::render_nodes(tree.root(), &ctx, transform, pixmap);
43}
44
45pub fn render_node(
56 node: &usvg::Node,
57 mut transform: tiny_skia::Transform,
58 pixmap: &mut tiny_skia::PixmapMut,
59) -> Option<()> {
60 let bbox = node.abs_layer_bounding_box()?;
61
62 let max_bbox = max_filter_bbox(pixmap.width(), pixmap.height());
63
64 transform = transform.pre_translate(-bbox.x(), -bbox.y());
65
66 let ctx = render::Context { max_bbox };
67 render::render_node(node, &ctx, transform, pixmap);
68
69 Some(())
70}
71
72pub(crate) trait OptionLog {
73 fn log_none<F: FnOnce()>(self, f: F) -> Self;
74}
75
76impl<T> OptionLog for Option<T> {
77 #[inline]
78 fn log_none<F: FnOnce()>(self, f: F) -> Self {
79 self.or_else(|| {
80 f();
81 None
82 })
83 }
84}
85
86fn max_filter_bbox(width: u32, height: u32) -> tiny_skia::IntRect {
87 tiny_skia::IntRect::from_xywh(
88 i32::try_from(width).unwrap_or(i32::MAX).saturating_mul(-2),
89 i32::try_from(height).unwrap_or(i32::MAX).saturating_mul(-2),
90 width.saturating_mul(5),
91 height.saturating_mul(5),
92 )
93 .unwrap_or_else(|| {
94 tiny_skia::IntRect::from_ltrb(i32::MIN / 2, i32::MIN / 2, i32::MAX / 2, i32::MAX / 2)
95 .unwrap()
96 })
97}
98
99#[cfg(test)]
100mod tests {
101 #[test]
102 fn max_filter_bbox_is_clamped() {
103 let bbox = super::max_filter_bbox(u32::MAX, u32::MAX);
104 assert_eq!(bbox.left(), i32::MIN / 2);
105 assert_eq!(bbox.top(), i32::MIN / 2);
106 assert_eq!(bbox.right(), i32::MAX / 2);
107 assert_eq!(bbox.bottom(), i32::MAX / 2);
108 }
109}