use std::collections::{HashMap, HashSet};
use std::hash::{Hash, Hasher};
use svgrtypes::{Length, LengthUnit as Unit};
use crate::svgtree::{self, AId, EId};
use crate::*;
#[derive(Clone)]
pub struct State<'a> {
pub(crate) parent_clip_path: Option<svgtree::Node<'a>>,
pub(crate) parent_marker: Option<svgtree::Node<'a>>,
pub(crate) fe_image_link: bool,
pub(crate) size: Size,
pub(crate) view_box: Rect,
pub(crate) use_size: (Option<f64>, Option<f64>),
pub(crate) opt: &'a Options<'a>,
}
#[derive(Default)]
pub struct Cache {
pub clip_paths: HashMap<String, Rc<ClipPath>>,
pub masks: HashMap<String, Rc<Mask>>,
pub filters: HashMap<String, Rc<filter::Filter>>,
pub paint: HashMap<String, Paint>,
pub all_ids: HashSet<u64>,
pub clip_path_index: usize,
pub filter_index: usize,
}
impl Cache {
pub fn gen_clip_path_id(&mut self) -> String {
loop {
self.clip_path_index += 1;
let new_id = format!("clipPath{}", self.clip_path_index);
let new_hash = string_hash(&new_id);
if !self.all_ids.contains(&new_hash) {
return new_id;
}
}
}
pub fn gen_filter_id(&mut self) -> String {
loop {
self.filter_index += 1;
let new_id = format!("filter{}", self.filter_index);
let new_hash = string_hash(&new_id);
if !self.all_ids.contains(&new_hash) {
return new_id;
}
}
}
}
fn string_hash(s: &str) -> u64 {
let mut h = std::collections::hash_map::DefaultHasher::new();
s.hash(&mut h);
h.finish()
}
pub(crate) fn convert_doc(svg_doc: &svgtree::Document, opt: &Options) -> Result<Tree, Error> {
let svg = svg_doc.root_element();
let (size, restore_viewbox) = resolve_svg_size(&svg, opt);
let size = size?;
let view_box = ViewBox {
rect: svg.get_viewbox().unwrap_or_else(|| size.to_rect(0.0, 0.0)),
aspect: svg.attribute(AId::PreserveAspectRatio).unwrap_or_default(),
};
let mut tree = Tree {
size,
view_box,
root: Node::new(NodeKind::Group(Group::default())),
};
if !svg.is_visible_element(opt) {
return Ok(tree);
}
let state = State {
parent_clip_path: None,
parent_marker: None,
fe_image_link: false,
size,
view_box: view_box.rect,
use_size: (None, None),
opt,
};
let mut cache = Cache::default();
for node in svg_doc.descendants() {
if let Some(tag) = node.tag_name() {
if matches!(tag, EId::Filter | EId::ClipPath) {
if node.has_element_id() {
cache.all_ids.insert(string_hash(node.element_id()));
}
}
}
}
convert_children(svg_doc.root(), &state, &mut cache, &mut tree.root);
remove_empty_groups(&mut tree);
ungroup_groups(tree.root.clone(), opt.keep_named_groups);
if restore_viewbox {
calculate_svg_bbox(&mut tree);
}
Ok(tree)
}
fn resolve_svg_size(svg: &svgtree::Node, opt: &Options) -> (Result<Size, Error>, bool) {
let mut state = State {
parent_clip_path: None,
parent_marker: None,
fe_image_link: false,
size: Size::new(100.0, 100.0).unwrap(),
view_box: Rect::new(0.0, 0.0, 100.0, 100.0).unwrap(),
use_size: (None, None),
opt,
};
let def = Length::new(100.0, Unit::Percent);
let mut width: Length = svg.attribute(AId::Width).unwrap_or(def);
let mut height: Length = svg.attribute(AId::Height).unwrap_or(def);
let view_box = svg.get_viewbox();
let restore_viewbox =
if (width.unit == Unit::Percent || height.unit == Unit::Percent) && view_box.is_none() {
if width.unit == Unit::Percent {
width = Length::new(
(width.number / 100.0) * state.opt.default_size.width(),
Unit::None,
);
}
if height.unit == Unit::Percent {
height = Length::new(
(height.number / 100.0) * state.opt.default_size.height(),
Unit::None,
);
}
true
} else {
false
};
let size = if let Some(vbox) = view_box {
state.view_box = vbox;
let w = if width.unit == Unit::Percent {
vbox.width() * (width.number / 100.0)
} else {
svg.convert_user_length(AId::Width, &state, def)
};
let h = if height.unit == Unit::Percent {
vbox.height() * (height.number / 100.0)
} else {
svg.convert_user_length(AId::Height, &state, def)
};
Size::new(w, h)
} else {
Size::new(
svg.convert_user_length(AId::Width, &state, def),
svg.convert_user_length(AId::Height, &state, def),
)
};
(size.ok_or(Error::InvalidSize), restore_viewbox)
}
fn calculate_svg_bbox(tree: &mut Tree) {
let mut right = 0.0;
let mut bottom = 0.0;
for node in tree.root.descendants() {
if let Some(bbox) = node.calculate_bbox() {
if bbox.right() > right {
right = bbox.right();
}
if bbox.bottom() > bottom {
bottom = bbox.bottom();
}
}
}
if let Some(rect) = Rect::new(0.0, 0.0, right, bottom) {
tree.view_box.rect = rect;
}
if let Some(size) = Size::new(right, bottom) {
tree.size = size;
}
}
#[inline(never)]
pub(crate) fn convert_children(
parent_node: svgtree::Node,
state: &State,
cache: &mut converter::Cache,
parent: &mut Node,
) {
for node in parent_node.children() {
convert_element(node, state, cache, parent);
}
}
#[inline(never)]
pub(crate) fn convert_element(
node: svgtree::Node,
state: &State,
cache: &mut converter::Cache,
parent: &mut Node,
) -> Option<Node> {
let tag_name = node.tag_name()?;
if !tag_name.is_graphic() && !matches!(tag_name, EId::G | EId::Switch | EId::Svg) {
return None;
}
if !node.is_visible_element(state.opt) {
return None;
}
if tag_name == EId::Use {
use_node::convert(node, state, cache, parent);
return None;
}
if tag_name == EId::Switch {
switch::convert(node, state, cache, parent);
return None;
}
let parent = &mut match convert_group(node, state, false, cache, parent) {
GroupKind::Create(g) => g,
GroupKind::Skip => parent.clone(),
GroupKind::Ignore => return None,
};
match tag_name {
EId::Rect
| EId::Circle
| EId::Ellipse
| EId::Line
| EId::Polyline
| EId::Polygon
| EId::Path => {
if let Some(path) = shapes::convert(node, state) {
convert_path(node, path, state, cache, parent);
}
}
EId::Image => {
image::convert(node, state, parent);
}
EId::Text => {
text::convert(node, state, cache, parent);
}
EId::Svg => {
if node.parent_element().is_some() {
use_node::convert_svg(node, state, cache, parent);
} else {
convert_children(node, state, cache, parent);
}
}
EId::G => {
convert_children(node, state, cache, parent);
}
_ => {}
}
Some(parent.clone())
}
#[inline(never)]
pub(crate) fn convert_clip_path_elements(
clip_node: svgtree::Node,
state: &State,
cache: &mut converter::Cache,
parent: &mut Node,
) {
for node in clip_node.children() {
let tag_name = match node.tag_name() {
Some(v) => v,
None => continue,
};
if !tag_name.is_graphic() {
continue;
}
if !node.is_visible_element(state.opt) {
continue;
}
if tag_name == EId::Use {
use_node::convert(node, state, cache, parent);
continue;
}
let parent = &mut match convert_group(node, state, false, cache, parent) {
GroupKind::Create(g) => g,
GroupKind::Skip => parent.clone(),
GroupKind::Ignore => continue,
};
match tag_name {
EId::Rect | EId::Circle | EId::Ellipse | EId::Polyline | EId::Polygon | EId::Path => {
if let Some(path) = shapes::convert(node, state) {
convert_path(node, path, state, cache, parent);
}
}
EId::Text => {
text::convert(node, state, cache, parent);
}
_ => {
log::warn!("'{}' is no a valid 'clip-path' child.", tag_name);
}
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
enum Isolation {
Auto,
Isolate,
}
impl_enum_default!(Isolation, Auto);
impl_enum_from_str!(Isolation,
"auto" => Isolation::Auto,
"isolate" => Isolation::Isolate
);
#[derive(Debug)]
pub enum GroupKind {
Create(Node),
Skip,
Ignore,
}
pub(crate) fn convert_group(
node: svgtree::Node,
state: &State,
force: bool,
cache: &mut Cache,
parent: &mut Node,
) -> GroupKind {
let opacity = if state.parent_clip_path.is_none() {
node.attribute(AId::Opacity).unwrap_or(Opacity::ONE)
} else {
Opacity::ONE
};
macro_rules! resolve_link {
($aid:expr, $f:expr) => {{
let mut v = None;
if let Some(link) = node.attribute::<svgtree::Node>($aid) {
v = $f(link, state, cache);
if v.is_none() {
return GroupKind::Ignore;
}
}
v
}};
}
let clip_path = resolve_link!(AId::ClipPath, clippath::convert);
let mask = if state.parent_clip_path.is_none() {
resolve_link!(AId::Mask, mask::convert)
} else {
None
};
let (filters, filter_fill, filter_stroke) = {
let mut filters = Vec::new();
if state.parent_clip_path.is_none() {
if node.attribute(AId::Filter) == Some("none") {
} else if node.has_attribute(AId::Filter) {
if let Ok(f) = filter::convert(node, state, cache) {
filters = f;
} else {
return GroupKind::Ignore;
}
}
}
let filter_fill = resolve_filter_fill(node, state, &filters, cache);
let filter_stroke = resolve_filter_stroke(node, state, &filters, cache);
(filters, filter_fill, filter_stroke)
};
let transform: Transform = node.resolve_transform(state).unwrap_or_default();
let blend_mode: BlendMode = node.attribute(AId::MixBlendMode).unwrap_or_default();
let isolation: Isolation = node.attribute(AId::Isolation).unwrap_or_default();
let isolate = isolation == Isolation::Isolate;
let enable_background = node.attribute(AId::EnableBackground);
let is_g_or_use = node.has_tag_name(EId::G) || node.has_tag_name(EId::Use);
let required = opacity.get().fuzzy_ne(&1.0)
|| clip_path.is_some()
|| mask.is_some()
|| !filters.is_empty()
|| !transform.is_default()
|| enable_background.is_some()
|| blend_mode != BlendMode::Normal
|| isolate
|| (is_g_or_use
&& node.has_element_id()
&& (state.opt.keep_named_groups || state.fe_image_link))
|| force;
if required {
let id = if is_g_or_use {
node.element_id().to_string()
} else {
String::new()
};
let g = parent.append_kind(NodeKind::Group(Group {
id,
transform,
opacity,
blend_mode,
isolate,
clip_path,
mask,
filters,
filter_fill,
filter_stroke,
enable_background,
}));
GroupKind::Create(g)
} else {
GroupKind::Skip
}
}
fn resolve_filter_fill(
node: svgtree::Node,
state: &State,
filters: &[Rc<filter::Filter>],
cache: &mut converter::Cache,
) -> Option<Paint> {
let mut has_fill_paint = false;
for filter in filters {
if filter
.primitives
.iter()
.any(|c| c.kind.has_input(&filter::Input::FillPaint))
{
has_fill_paint = true;
break;
}
}
if !has_fill_paint {
return None;
}
let stroke = style::resolve_fill(node, true, state, cache)?;
Some(stroke.paint)
}
fn resolve_filter_stroke(
node: svgtree::Node,
state: &State,
filters: &[Rc<filter::Filter>],
cache: &mut converter::Cache,
) -> Option<Paint> {
let mut has_stroke_paint = false;
for filter in filters {
if filter
.primitives
.iter()
.any(|c| c.kind.has_input(&filter::Input::StrokePaint))
{
has_stroke_paint = true;
break;
}
}
if !has_stroke_paint {
return None;
}
let stroke = style::resolve_stroke(node, true, state, cache)?;
Some(stroke.paint)
}
fn remove_empty_groups(tree: &mut Tree) {
fn rm(parent: Node) -> bool {
let mut changed = false;
let mut curr_node = parent.first_child();
while let Some(node) = curr_node {
curr_node = node.next_sibling();
let is_g = if let NodeKind::Group(ref g) = *node.borrow() {
g.filters.is_empty()
} else {
false
};
if is_g && !node.has_children() {
node.detach();
changed = true;
} else {
if rm(node) {
changed = true;
}
}
}
changed
}
while rm(tree.root.clone()) {}
}
pub(crate) fn ungroup_groups(root: Node, keep_named_groups: bool) {
fn ungroup(root: Node, parent: Node, keep_named_groups: bool) -> bool {
let mut changed = false;
let mut curr_node = parent.first_child();
while let Some(node) = curr_node {
curr_node = node.next_sibling();
let mut ts = Transform::default();
let is_ok = if let NodeKind::Group(ref g) = *node.borrow() {
ts = g.transform;
g.opacity == Opacity::ONE
&& g.clip_path.is_none()
&& g.mask.is_none()
&& g.filters.is_empty()
&& g.enable_background.is_none()
&& g.blend_mode == BlendMode::Normal
&& !g.isolate
&& !(keep_named_groups && !g.id.is_empty())
} else {
false
};
if is_ok {
let mut curr_child = node.last_child();
while let Some(child) = curr_child {
curr_child = child.previous_sibling();
match *child.borrow_mut() {
NodeKind::Path(ref mut path) => {
path.transform.prepend(&ts);
}
NodeKind::Image(ref mut img) => {
img.transform.prepend(&ts);
}
NodeKind::Group(ref mut g) => {
g.transform.prepend(&ts);
}
NodeKind::Text(ref mut text) => {
text.transform.prepend(&ts);
}
}
child.detach();
node.insert_after(child.clone());
}
node.detach();
changed = true;
} else {
if ungroup(root.clone(), node, keep_named_groups) {
changed = true;
}
}
}
changed
}
while ungroup(root.clone(), root.clone(), keep_named_groups) {}
}
fn convert_path(
node: svgtree::Node,
path: SharedPathData,
state: &State,
cache: &mut converter::Cache,
parent: &mut Node,
) {
debug_assert!(path.len() >= 2);
if path.len() < 2 {
return;
}
let has_bbox = path.has_bbox();
let fill = style::resolve_fill(node, has_bbox, state, cache);
let stroke = style::resolve_stroke(node, has_bbox, state, cache);
let mut visibility = node.find_attribute(AId::Visibility).unwrap_or_default();
let rendering_mode = node
.find_attribute(AId::ShapeRendering)
.unwrap_or(state.opt.shape_rendering);
let raw_paint_order: svgrtypes::PaintOrder =
node.find_attribute(AId::PaintOrder).unwrap_or_default();
let paint_order = svg_paint_order_to_usvg(raw_paint_order);
if fill.is_none() && stroke.is_none() {
visibility = Visibility::Hidden;
}
let mut markers_group = None;
if marker::is_valid(node) && visibility == Visibility::Visible {
let mut g = parent.append_kind(NodeKind::Group(Group::default()));
marker::convert(node, &path, state, cache, &mut g);
markers_group = Some(g);
}
parent.append_kind(NodeKind::Path(Path {
id: node.element_id().to_string(),
transform: Default::default(),
visibility,
fill,
stroke,
paint_order,
rendering_mode,
text_bbox: None,
data: path,
}));
if raw_paint_order.order[2] == svgrtypes::PaintOrderKind::Markers {
if let Some(g) = markers_group {
g.detach();
parent.append(g);
}
}
}
pub fn svg_paint_order_to_usvg(order: svgrtypes::PaintOrder) -> PaintOrder {
match (order.order[0], order.order[1]) {
(svgrtypes::PaintOrderKind::Stroke, _) => PaintOrder::StrokeAndFill,
(svgrtypes::PaintOrderKind::Markers, svgrtypes::PaintOrderKind::Stroke) => {
PaintOrder::StrokeAndFill
}
_ => PaintOrder::FillAndStroke,
}
}