#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![allow(clippy::field_reassign_with_default)]
#![allow(clippy::identity_op)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::upper_case_acronyms)]
#![allow(clippy::wrong_self_convention)]
pub use tiny_skia;
pub use usvg;
mod clip;
mod filter;
mod geom;
mod image;
mod mask;
mod path;
mod render;
pub fn render(
tree: &usvg::Tree,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
let max_bbox = max_filter_bbox(pixmap.width(), pixmap.height());
let ctx = render::Context { max_bbox };
render::render_nodes(tree.root(), &ctx, transform, pixmap);
}
pub fn render_node(
node: &usvg::Node,
mut transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let bbox = node.abs_layer_bounding_box()?;
let max_bbox = max_filter_bbox(pixmap.width(), pixmap.height());
transform = transform.pre_translate(-bbox.x(), -bbox.y());
let ctx = render::Context { max_bbox };
render::render_node(node, &ctx, transform, pixmap);
Some(())
}
pub(crate) trait OptionLog {
fn log_none<F: FnOnce()>(self, f: F) -> Self;
}
impl<T> OptionLog for Option<T> {
#[inline]
fn log_none<F: FnOnce()>(self, f: F) -> Self {
self.or_else(|| {
f();
None
})
}
}
fn max_filter_bbox(width: u32, height: u32) -> tiny_skia::IntRect {
tiny_skia::IntRect::from_xywh(
i32::try_from(width).unwrap_or(i32::MAX).saturating_mul(-2),
i32::try_from(height).unwrap_or(i32::MAX).saturating_mul(-2),
width.saturating_mul(5),
height.saturating_mul(5),
)
.unwrap_or_else(|| {
tiny_skia::IntRect::from_ltrb(i32::MIN / 2, i32::MIN / 2, i32::MAX / 2, i32::MAX / 2)
.unwrap()
})
}
#[cfg(test)]
mod tests {
#[test]
fn max_filter_bbox_is_clamped() {
let bbox = super::max_filter_bbox(u32::MAX, u32::MAX);
assert_eq!(bbox.left(), i32::MIN / 2);
assert_eq!(bbox.top(), i32::MIN / 2);
assert_eq!(bbox.right(), i32::MAX / 2);
assert_eq!(bbox.bottom(), i32::MAX / 2);
}
}