use std::borrow::Cow;
use std::sync::Arc;
use svgrtypes::Length;
use super::svgtree::{AId, SvgNode};
use super::{converter, OptionLog};
use crate::svgtree::SvgAttributeValueRef;
use crate::{Group, Image, ImageKind, Node, NonZeroRect, Size, ViewBox};
#[derive(Debug, PartialEq)]
pub struct PreloadedImageData {
pub data: Cow<'static, [u8]>,
pub width: u32,
pub height: u32,
pub id: String,
}
impl std::fmt::Display for PreloadedImageData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.id)
}
}
impl PreloadedImageData {
pub fn blend_rgba_slice(rgba_slice: &[u8]) -> Vec<u8> {
let mut data = vec![0; rgba_slice.len()];
for i in (0..rgba_slice.len()).step_by(4) {
let r = rgba_slice[i];
let g = rgba_slice[i + 1];
let b = rgba_slice[i + 2];
let a = rgba_slice[i + 3];
let alpha = a as f32 / 255.0;
data[i] = (r as f32 * alpha + 0.5) as u8;
data[i + 1] = (g as f32 * alpha + 0.5) as u8;
data[i + 2] = (b as f32 * alpha + 0.5) as u8;
data[i + 3] = a;
}
data
}
pub fn new(id: String, width: u32, height: u32, rgba_data: &[u8]) -> Self {
Self {
id,
data: Cow::Owned(Self::blend_rgba_slice(rgba_data)),
width,
height,
}
}
pub fn new_blended(id: String, width: u32, height: u32, rgba_data: &'static [u8]) -> Self {
Self {
id,
data: Cow::Borrowed(rgba_data),
width,
height,
}
}
}
pub(crate) fn convert(node: SvgNode, state: &converter::State, parent: &mut Group) -> Option<()> {
let attr = node
.attributes()
.iter()
.find(|a| a.name == AId::Href)
.log_none(|| log::warn!("Image lacks the 'xlink:href' attribute. Skipped."))?;
let (href, kind) = match attr.value.as_ref() {
SvgAttributeValueRef::Str(href) => Some((href, get_href_data(href, state)?)),
SvgAttributeValueRef::ImageData(image_data) => Some((
image_data.id.as_str(),
ImageKind::DATA(Arc::clone(image_data)),
)),
_ => None,
}?;
let visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
let rendering_mode = node
.find_attribute(AId::ImageRendering)
.unwrap_or(state.opt.image_rendering);
let actual_size = match kind {
ImageKind::DATA(ref data) => Size::from_wh(data.width as f32, data.height as f32)?,
ImageKind::SVG { ref tree, .. } => tree.size,
};
let x = node.convert_user_length(AId::X, state, Length::zero());
let y = node.convert_user_length(AId::Y, state, Length::zero());
let mut width = node.convert_user_length(
AId::Width,
state,
Length::new_number(actual_size.width() as f64),
);
let mut height = node.convert_user_length(
AId::Height,
state,
Length::new_number(actual_size.height() as f64),
);
match (
node.attribute::<Length>(AId::Width),
node.attribute::<Length>(AId::Height),
) {
(Some(_), None) => {
height = actual_size.height() * (width / actual_size.width());
}
(None, Some(_)) => {
width = actual_size.width() * (height / actual_size.height());
}
_ => {}
};
let rect = NonZeroRect::from_xywh(x, y, width, height);
let rect = rect.log_none(|| log::warn!("Image has an invalid size. Skipped."))?;
let view_box = ViewBox {
rect,
aspect: node.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
};
let id = if state.parent_markers.is_empty() {
node.element_id().to_string()
} else {
String::new()
};
let abs_bounding_box = view_box.rect.transform(parent.abs_transform)?;
parent.children.push(Node::Image(Box::new(Image {
origin_href: href.to_string(),
id,
visibility,
view_box,
rendering_mode,
kind,
abs_transform: parent.abs_transform,
abs_bounding_box,
})));
Some(())
}
pub(crate) fn get_href_data(href: &str, state: &converter::State) -> Option<ImageKind> {
let preloaded_image = state.opt.image_data?.get(href).map(Arc::clone);
if let Some(data) = preloaded_image {
return Some(ImageKind::DATA(data));
}
if let Some(sub_svg) = state.opt.sub_svg_data?.get(href) {
return Some(ImageKind::SVG {
tree: Arc::clone(sub_svg),
original_href: href.to_string(),
});
}
return None;
}