lib: refactor converter to DFS visitor trait

master
Sameer Puri 4 years ago
parent 669e7d883b
commit f049b76235

@ -19,8 +19,11 @@ use crate::turtle::*;
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
mod length_serde; mod length_serde;
mod visit;
const SVG_TAG_NAME: &str = "svg"; const SVG_TAG_NAME: &str = "svg";
const CLIP_PATH_TAG_NAME: &str = "clipPath";
const PATH_TAG_NAME: &str = "path";
/// High-level output configuration /// High-level output configuration
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -57,58 +60,48 @@ pub struct ConversionOptions {
pub dimensions: [Option<Length>; 2], pub dimensions: [Option<Length>; 2],
} }
struct ConversionVisitor<'a, 'input: 'a> {
pub fn svg2program<'input>( turtle: &'a mut Turtle<'input>,
doc: &Document, name_stack: Vec<String>,
config: &ConversionConfig, program: Vec<Token<'input>>,
config: &'a ConversionConfig,
options: ConversionOptions, options: ConversionOptions,
turtle: &'input mut Turtle<'input>, }
) -> Vec<Token<'input>> {
let mut program = command!(UnitsMillimeters {}) impl<'a, 'input: 'a> ConversionVisitor<'a, 'input> {
.into_token_vec() fn begin(&mut self) {
.drain(..) self.program = command!(UnitsMillimeters {})
.collect::<Vec<_>>(); .into_token_vec()
program.extend(turtle.machine.absolute()); .drain(..)
program.extend(turtle.machine.program_begin()); .collect::<Vec<_>>();
program.extend(turtle.machine.absolute()); self.program.extend(self.turtle.machine.absolute());
self.program.extend(self.turtle.machine.program_begin());
// Part 1 of converting from SVG to g-code coordinates self.program.extend(self.turtle.machine.absolute());
turtle.push_transform(Transform2D::scale(1., -1.));
// Part 1 of converting from SVG to g-code coordinates
// Depth-first SVG DOM traversal self.turtle.push_transform(Transform2D::scale(1., -1.));
let mut node_stack = vec![(doc.root(), doc.root().children())]; }
let mut name_stack: Vec<String> = vec![];
fn end(&mut self) {
while let Some((parent, mut children)) = node_stack.pop() { self.turtle.pop_all_transforms();
let node: Node = match children.next() { self.program.extend(self.turtle.machine.tool_off());
Some(child) => { self.program.extend(self.turtle.machine.absolute());
node_stack.push((parent, children)); self.program.extend(self.turtle.machine.program_end());
child self.program
} .append(&mut command!(ProgramEnd {}).into_token_vec());
// Last node in this group has been processed }
None => { }
if parent.has_attribute("viewBox")
|| parent.has_attribute("transform") impl<'a, 'input: 'a> visit::XmlVisitor for ConversionVisitor<'a, 'input> {
|| parent.has_attribute("width") fn visit(&mut self, node: Node) {
|| parent.has_attribute("height") // Depth-first SVG DOM traversal
|| (parent.has_tag_name(SVG_TAG_NAME)
&& options.dimensions.iter().any(Option::is_some))
{
turtle.pop_transform();
}
name_stack.pop();
continue;
}
};
if node.node_type() != roxmltree::NodeType::Element { if node.node_type() != roxmltree::NodeType::Element {
debug!("Encountered a non-element: {:?}", node); debug!("Encountered a non-element: {:?}", node);
continue;
} }
if node.tag_name().name() == "clipPath" { if node.tag_name().name() == CLIP_PATH_TAG_NAME {
warn!("Clip paths are not supported: {:?}", node); warn!("Clip paths are not supported: {:?}", node);
continue;
} }
let mut transforms = vec![]; let mut transforms = vec![];
@ -126,13 +119,13 @@ pub fn svg2program<'input>(
.and_then(|mut parser| parser.next()) .and_then(|mut parser| parser.next())
.transpose() .transpose()
.expect("could not parse width") .expect("could not parse width")
.map(|width| length_to_mm(width, config.dpi, scale_w)), .map(|width| length_to_mm(width, self.config.dpi, scale_w)),
node.attribute("height") node.attribute("height")
.map(LengthListParser::from) .map(LengthListParser::from)
.and_then(|mut parser| parser.next()) .and_then(|mut parser| parser.next())
.transpose() .transpose()
.expect("could not parse height") .expect("could not parse height")
.map(|height| length_to_mm(height, config.dpi, scale_h)), .map(|height| length_to_mm(height, self.config.dpi, scale_h)),
); );
let aspect_ratio = match (view_box, dimensions) { let aspect_ratio = match (view_box, dimensions) {
(_, (Some(ref width), Some(ref height))) => *width / *height, (_, (Some(ref width), Some(ref height))) => *width / *height,
@ -152,8 +145,8 @@ pub fn svg2program<'input>(
} }
let dimensions_override = [ let dimensions_override = [
options.dimensions[0].map(|dim_x| length_to_mm(dim_x, config.dpi, scale_w)), self.options.dimensions[0].map(|dim_x| length_to_mm(dim_x, self.config.dpi, scale_w)),
options.dimensions[1].map(|dim_y| length_to_mm(dim_y, config.dpi, scale_h)), self.options.dimensions[1].map(|dim_y| length_to_mm(dim_y, self.config.dpi, scale_h)),
]; ];
match (dimensions_override, dimensions) { match (dimensions_override, dimensions) {
@ -199,48 +192,70 @@ pub fn svg2program<'input>(
) )
} }
if !transforms.is_empty() { self.turtle.push_transform(
let transform = transforms transforms
.iter() .iter()
.fold(Transform2D::identity(), |acc, t| acc.then(t)); .fold(Transform2D::identity(), |acc, t| acc.then(t)),
turtle.push_transform(transform); );
}
if node.tag_name().name() == "path" { if node.tag_name().name() == PATH_TAG_NAME {
if let Some(d) = node.attribute("d") { if let Some(d) = node.attribute("d") {
turtle.reset(); self.turtle.reset();
let mut comment = String::new(); let mut comment = String::new();
name_stack.iter().for_each(|name| { self.name_stack.iter().for_each(|name| {
comment += name; comment += name;
comment += " > "; comment += " > ";
}); });
comment += &node_name(&node); comment += &node_name(&node);
program.push(Token::Comment { self.program.push(Token::Comment {
is_inline: false, is_inline: false,
inner: Cow::Owned(comment), inner: Cow::Owned(comment),
}); });
program.extend(apply_path(turtle, config, d)); self.program
.extend(apply_path(&mut self.turtle, &self.config, d));
} else { } else {
warn!("There is a path node containing no actual path: {:?}", node); warn!("There is a path node containing no actual path: {:?}", node);
} }
} }
if node.has_children() { if node.has_children() {
node_stack.push((node, node.children())); self.name_stack.push(node_name(&node));
name_stack.push(node_name(&node)); } else {
} else if !transforms.is_empty() { // Pop transform since this is the only element that has it
// Pop transform early, since this is the only element that has it self.turtle.pop_transform();
turtle.pop_transform();
let mut parent = Some(node);
while let Some(p) = parent {
if p.next_sibling().is_some() || p.is_root() {
break;
}
// Pop the parent transform since this is the last child
self.turtle.pop_transform();
self.name_stack.pop();
parent = p.parent();
}
} }
} }
}
turtle.pop_all_transforms(); pub fn svg2program<'a, 'input: 'a>(
program.extend(turtle.machine.tool_off()); doc: &'a Document,
program.extend(turtle.machine.absolute()); config: &ConversionConfig,
program.extend(turtle.machine.program_end()); options: ConversionOptions,
program.append(&mut command!(ProgramEnd {}).into_token_vec()); turtle: &'a mut Turtle<'input>,
) -> Vec<Token<'input>> {
let mut conversion_visitor = ConversionVisitor {
turtle,
config,
options,
name_stack: vec![],
program: vec![],
};
conversion_visitor.begin();
visit::depth_first_visit(doc, &mut conversion_visitor);
conversion_visitor.end();
program conversion_visitor.program
} }
fn node_name(node: &Node) -> String { fn node_name(node: &Node) -> String {

@ -0,0 +1,13 @@
use roxmltree::{Document, Node};
pub trait XmlVisitor {
fn visit(&mut self, node: Node);
}
pub fn depth_first_visit(doc: &Document, visitor: &mut impl XmlVisitor) {
let mut stack = doc.root().children().rev().collect::<Vec<_>>();
while let Some(node) = stack.pop() {
visitor.visit(node);
stack.extend(node.children().rev());
}
}
Loading…
Cancel
Save