use crate::OptionLog;
pub struct Context {
pub max_bbox: tiny_skia::IntRect,
}
pub fn render_nodes(
parent: &usvg::Group,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
for node in parent.children() {
render_node(node, ctx, transform, pixmap);
}
}
pub fn render_node(
node: &usvg::Node,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) {
match node {
usvg::Node::Group(ref group) => {
render_group(group, ctx, transform, pixmap);
}
usvg::Node::Path(ref path) => {
crate::path::render(
path,
tiny_skia::BlendMode::SourceOver,
ctx,
transform,
pixmap,
);
}
usvg::Node::Image(ref image) => {
crate::image::render(image, transform, pixmap);
}
usvg::Node::Text(ref text) => {
render_group(text.flattened(), ctx, transform, pixmap);
}
}
}
fn render_group(
group: &usvg::Group,
ctx: &Context,
transform: tiny_skia::Transform,
pixmap: &mut tiny_skia::PixmapMut,
) -> Option<()> {
let transform = transform.pre_concat(group.transform());
if !group.should_isolate() {
render_nodes(group, ctx, transform, pixmap);
return Some(());
}
let bbox = group.layer_bounding_box().transform(transform)?;
let mut ibbox = if group.filters().is_empty() {
tiny_skia::IntRect::from_xywh(
bbox.x().floor() as i32 - 2,
bbox.y().floor() as i32 - 2,
bbox.width().ceil() as u32 + 4,
bbox.height().ceil() as u32 + 4,
)?
} else {
let bbox = bbox.to_int_rect();
crate::geom::fit_to_rect(bbox, ctx.max_bbox)?
};
if group.filters().is_empty() {
ibbox = crate::geom::fit_to_rect(ibbox, ctx.max_bbox)?;
}
let shift_ts = {
let mut dx = bbox.x();
let mut dy = bbox.y();
dx -= bbox.x() - ibbox.x() as f32;
dy -= bbox.y() - ibbox.y() as f32;
tiny_skia::Transform::from_translate(-dx, -dy)
};
let transform = shift_ts.pre_concat(transform);
let mut sub_pixmap = tiny_skia::Pixmap::new(ibbox.width(), ibbox.height())
.log_none(|| log::warn!("Failed to allocate a group layer for: {:?}.", ibbox))?;
render_nodes(group, ctx, transform, &mut sub_pixmap.as_mut());
if !group.filters().is_empty() {
for filter in group.filters() {
crate::filter::apply(filter, transform, &mut sub_pixmap);
}
}
if let Some(clip_path) = group.clip_path() {
crate::clip::apply(clip_path, transform, &mut sub_pixmap);
}
if let Some(mask) = group.mask() {
crate::mask::apply(mask, ctx, transform, &mut sub_pixmap);
}
let paint = tiny_skia::PixmapPaint {
opacity: group.opacity().get(),
blend_mode: convert_blend_mode(group.blend_mode()),
quality: tiny_skia::FilterQuality::Nearest,
};
pixmap.draw_pixmap(
ibbox.x(),
ibbox.y(),
sub_pixmap.as_ref(),
&paint,
tiny_skia::Transform::identity(),
None,
);
Some(())
}
pub fn convert_blend_mode(mode: usvg::BlendMode) -> tiny_skia::BlendMode {
match mode {
usvg::BlendMode::Normal => tiny_skia::BlendMode::SourceOver,
usvg::BlendMode::Multiply => tiny_skia::BlendMode::Multiply,
usvg::BlendMode::Screen => tiny_skia::BlendMode::Screen,
usvg::BlendMode::Overlay => tiny_skia::BlendMode::Overlay,
usvg::BlendMode::Darken => tiny_skia::BlendMode::Darken,
usvg::BlendMode::Lighten => tiny_skia::BlendMode::Lighten,
usvg::BlendMode::ColorDodge => tiny_skia::BlendMode::ColorDodge,
usvg::BlendMode::ColorBurn => tiny_skia::BlendMode::ColorBurn,
usvg::BlendMode::HardLight => tiny_skia::BlendMode::HardLight,
usvg::BlendMode::SoftLight => tiny_skia::BlendMode::SoftLight,
usvg::BlendMode::Difference => tiny_skia::BlendMode::Difference,
usvg::BlendMode::Exclusion => tiny_skia::BlendMode::Exclusion,
usvg::BlendMode::Hue => tiny_skia::BlendMode::Hue,
usvg::BlendMode::Saturation => tiny_skia::BlendMode::Saturation,
usvg::BlendMode::Color => tiny_skia::BlendMode::Color,
usvg::BlendMode::Luminosity => tiny_skia::BlendMode::Luminosity,
}
}