Skip to main content

resvg/
lib.rs

1// Copyright 2017 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4/*!
5[resvg](https://github.com/linebender/resvg) is an SVG rendering library.
6*/
7
8#![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
28/// Renders a tree onto the pixmap.
29///
30/// `transform` will be used as a root transform.
31/// Can be used to position SVG inside the `pixmap`.
32///
33/// The produced content is in the sRGB color space.
34pub 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
45/// Renders a node onto the pixmap.
46///
47/// `transform` will be used as a root transform.
48/// Can be used to position SVG inside the `pixmap`.
49///
50/// The expected pixmap size can be retrieved from `usvg::Node::abs_layer_bounding_box()`.
51///
52/// Returns `None` when `node` has a zero size.
53///
54/// The produced content is in the sRGB color space.
55pub 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}