use svgdom::{
Length,
NumberList,
ViewBox,
};
use super::prelude::*;
pub fn convert_units(svg: &mut Node, opt: &Options) {
convert_font_size(svg, opt.dpi);
let view_box = resolve_view_box(svg, opt.dpi);
let vb_len = (
view_box.width * view_box.width + view_box.height * view_box.height
).sqrt() / 2.0_f64.sqrt();
let convert_len = |len: Length, aid: AId, font_size: f64| {
if len.unit == Unit::Percent {
match aid {
AId::X | AId::Cx | AId::Width => convert_percent(len, view_box.width),
AId::Y | AId::Cy | AId::Height => convert_percent(len, view_box.height),
_ => convert_percent(len, vb_len),
}
} else {
convert(len, font_size, opt.dpi)
}
};
let mut is_bbox_gradient;
for (_, mut node) in svg.descendants().svg() {
is_bbox_gradient = false;
if node.is_paint_server() {
is_bbox_gradient = true;
let av = node.attributes().get_value(AId::GradientUnits).cloned();
if let Some(AValue::String(text)) = av {
if text == "userSpaceOnUse" {
is_bbox_gradient = false;
}
}
}
let font_size = match node.find_attribute(AId::FontSize) {
Some(v) => v,
None => {
warn!("'font-size' must be resolved before units conversion.");
DEFAULT_FONT_SIZE
}
};
let mut attrs = node.attributes_mut();
for (aid, ref mut attr) in attrs.iter_mut().svg() {
if let AValue::Length(len) = attr.value {
let n = if is_bbox_gradient && len.unit == Unit::Percent && !len.num.is_fuzzy_zero() {
len.num / 100.0
} else if aid == AId::Offset && len.unit == Unit::Percent {
len.num / 100.0
} else {
convert_len(len, aid, font_size)
};
attr.value = AValue::Number(n);
}
}
for (aid, ref mut attr) in attrs.iter_mut().svg() {
let mut list = None;
if let AValue::LengthList(ref len_list) = attr.value {
list = Some(NumberList(len_list.iter()
.map(|len| convert_len(*len, aid, font_size))
.collect()));
}
if let Some(list) = list {
attr.value = AValue::NumberList(list);
}
}
}
}
fn convert_font_size(svg: &Node, dpi: f64) {
for (_, mut node) in svg.descendants().svg() {
let parent_size = match node.parent() {
Some(p) => {
if p.is_root() {
DEFAULT_FONT_SIZE
} else {
debug_assert_eq!(p.attributes().get_value(AId::FontSize)
.map(|v| v.is_number()), Some(true));
p.attributes().get_number_or(AId::FontSize, DEFAULT_FONT_SIZE)
}
}
None => DEFAULT_FONT_SIZE,
};
let mut attrs = node.attributes_mut();
if let Some(attr) = attrs.get_mut(AId::FontSize) {
if let AValue::Length(len) = attr.value {
let n = convert(len, parent_size, dpi);
attr.value = AValue::Number(n);
} else {
warn!("'font-size' should have a Length type.");
attr.value = AValue::Number(DEFAULT_FONT_SIZE);
}
}
}
}
fn resolve_view_box(svg: &mut Node, dpi: f64) -> Rect {
match svg.get_viewbox() {
Some(vb) => vb,
None => {
debug_assert!(svg.has_attribute(AId::FontSize));
let font_size = svg.attributes().get_number(AId::FontSize).unwrap();
let width = svg.attributes().get_length(AId::Width).unwrap();
let height = svg.attributes().get_length(AId::Height).unwrap();
let width = convert(width, font_size, dpi);
let height = convert(height, font_size, dpi);
let vb = ViewBox::new(0.0, 0.0, width, height);
svg.set_attribute((AId::ViewBox, vb));
(0.0, 0.0, width, height).into()
}
}
}
fn convert(len: Length, font_size: f64, dpi: f64) -> f64 {
let n = len.num;
match len.unit {
Unit::None | Unit::Px => n,
Unit::Em => n * font_size,
Unit::Ex => n * font_size / 2.0, Unit::In => n * dpi,
Unit::Cm => n * dpi / 2.54,
Unit::Mm => n * dpi / 25.4,
Unit::Pt => n * dpi / 72.0,
Unit::Pc => n * dpi / 6.0,
Unit::Percent => unreachable!("must be already converted"),
}
}
fn convert_percent(len: Length, base: f64) -> f64 {
base * len.num / 100.0
}