use std::sync::Arc;
use usvg::roxmltree;
#[derive(Debug, Clone, Copy, Default)]
pub struct SvgIntrinsicDimensions {
pub width: Option<svgtypes::Length>,
pub height: Option<svgtypes::Length>,
pub view_box_size: Option<(f32, f32)>,
pub degenerate_view_box: bool,
}
impl SvgIntrinsicDimensions {
pub fn from_xmltree(doc: &roxmltree::Document) -> Self {
let root = doc.root_element();
let parse_length = |name: &str| -> Option<svgtypes::Length> {
root.attribute(name)?.parse::<svgtypes::Length>().ok()
};
let view_box_dims = root.attribute("viewBox").and_then(|s| {
let mut numbers = svgtypes::NumberListParser::from(s);
let _x = numbers.next()?.ok()?;
let _y = numbers.next()?.ok()?;
let w = numbers.next()?.ok()? as f32;
let h = numbers.next()?.ok()? as f32;
(numbers.next().is_none() && w.is_finite() && h.is_finite() && w >= 0.0 && h >= 0.0)
.then_some((w, h))
});
let view_box_size = view_box_dims.filter(|&(w, h)| w > 0.0 && h > 0.0);
let degenerate_view_box = view_box_dims.is_some_and(|(w, h)| w == 0.0 || h == 0.0);
Self {
width: parse_length("width"),
height: parse_length("height"),
view_box_size,
degenerate_view_box,
}
}
}
#[derive(Debug, Clone)]
pub struct SvgImageData {
pub tree: Arc<usvg::Tree>,
pub intrinsic_dimensions: SvgIntrinsicDimensions,
}
impl SvgImageData {
pub fn from_data(data: &[u8], options: &usvg::Options) -> Result<Self, usvg::Error> {
let decompressed;
let data = if data.starts_with(&[0x1f, 0x8b]) {
decompressed = usvg::decompress_svgz(data)?;
decompressed.as_slice()
} else {
data
};
let text = std::str::from_utf8(data).map_err(|_| usvg::Error::NotAnUtf8Str)?;
let xml_options = roxmltree::ParsingOptions {
allow_dtd: true,
..Default::default()
};
let doc = roxmltree::Document::parse_with_options(text, xml_options)
.map_err(usvg::Error::ParsingFailed)?;
let tree = usvg::Tree::from_xmltree(&doc, options)?;
Ok(Self {
tree: Arc::new(tree),
intrinsic_dimensions: SvgIntrinsicDimensions::from_xmltree(&doc),
})
}
pub fn intrinsic_width(&self) -> Option<f32> {
use svgtypes::LengthUnit;
let declared = self
.intrinsic_dimensions
.width
.is_some_and(|len| len.unit != LengthUnit::Percent);
declared.then(|| self.tree.size().width())
}
pub fn intrinsic_height(&self) -> Option<f32> {
use svgtypes::LengthUnit;
let declared = self
.intrinsic_dimensions
.height
.is_some_and(|len| len.unit != LengthUnit::Percent);
declared.then(|| self.tree.size().height())
}
pub fn viewbox_aspect_ratio(&self) -> Option<f32> {
self.intrinsic_dimensions.view_box_size.map(|(w, h)| w / h)
}
pub fn resolved_width(&self, container_width: Option<f32>) -> Option<f32> {
use svgtypes::LengthUnit;
match self.intrinsic_dimensions.width {
Some(len) if len.unit != LengthUnit::Percent => Some(self.tree.size().width()),
Some(len) => container_width.map(|cw| cw * (len.number as f32) / 100.0),
None => None,
}
}
pub fn resolved_height(&self, container_height: Option<f32>) -> Option<f32> {
use svgtypes::LengthUnit;
match self.intrinsic_dimensions.height {
Some(len) if len.unit != LengthUnit::Percent => Some(self.tree.size().height()),
Some(len) => container_height.map(|ch| ch * (len.number as f32) / 100.0),
None => None,
}
}
pub fn aspect_ratio(&self) -> f32 {
match (self.intrinsic_width(), self.intrinsic_height()) {
(Some(w), Some(h)) => w / h,
_ => self.viewbox_aspect_ratio().unwrap_or_else(|| {
let size = self.tree.size();
size.width() / size.height()
}),
}
}
pub fn intrinsic_size(&self) -> (f32, f32) {
let aspect_ratio = self.aspect_ratio();
match (self.intrinsic_width(), self.intrinsic_height()) {
(Some(w), Some(h)) => (w, h),
(Some(w), None) => (w, w / aspect_ratio),
(None, Some(h)) => (h * aspect_ratio, h),
(None, None) => {
if self.viewbox_aspect_ratio().is_some() {
let scale = (300.0 / aspect_ratio).min(150.0);
(scale * aspect_ratio, scale)
} else {
let size = self.tree.size();
(size.width(), size.height())
}
}
}
}
}