aboutsummaryrefslogtreecommitdiff
path: root/src/svg/mod.rs
diff options
context:
space:
mode:
authorArne Dußin2020-11-03 23:41:08 +0100
committerArne Dußin2020-11-03 23:41:08 +0100
commitf6d06f6123ec6fce5814ee803e0b85052922ad47 (patch)
tree5bc6b9ad4de24600efa4f8588d6d9dc2f511c02e /src/svg/mod.rs
parentb5603648a4c88585764ac70db9614504b9b57515 (diff)
downloadgraf_karto-f6d06f6123ec6fce5814ee803e0b85052922ad47.tar.gz
graf_karto-f6d06f6123ec6fce5814ee803e0b85052922ad47.zip
Add (very) basic path-drawing
Diffstat (limited to 'src/svg/mod.rs')
-rw-r--r--src/svg/mod.rs176
1 files changed, 176 insertions, 0 deletions
diff --git a/src/svg/mod.rs b/src/svg/mod.rs
new file mode 100644
index 0000000..5527ee0
--- /dev/null
+++ b/src/svg/mod.rs
@@ -0,0 +1,176 @@
+//! Module for drawing SVG files to the screen or maybe a texture etc.
+
+pub mod style;
+
+use crate::math::Vec2;
+use raylib::drawing::RaylibDraw;
+use raylib::ffi::Vector2;
+use std::fs::File;
+use std::io::{self, Read};
+use std::ops::Deref;
+use std::path::Path;
+use std::str::FromStr;
+use style::Style;
+use svgtypes::{Path as SVGPath, PathSegment};
+use xmltree::{Element, XMLNode};
+
+// Find the first XML-Node with the given name. With depth, the maximum depth the
+// algorithm will search to can be set. If it is set to `None`, the algorithm will search the
+// entire sub-tree. Returns `None` if no such child can be found. Returns itself, in case the root
+// node is already of the name given.
+pub fn find_first_node(root: Element, name: &str, depth: Option<usize>) -> Option<Element> {
+ // The abort condition of this recursive function. If the element itself is of the required
+ // name, return it.
+ if root.name == name {
+ return Some(root);
+ }
+ // Also abort, if the depth is reached.
+ if depth == Some(0) {
+ return None;
+ }
+
+ // Decrease the depth by one for all calls on the children, if it is set.
+ let depth = match depth {
+ Some(depth) => Some(depth - 1),
+ None => None,
+ };
+
+ // Recursively look for the element in this node's children.
+ for child in root.children {
+ // We only care for the elements, not for comments, cdata etc.
+ if let XMLNode::Element(element) = child {
+ if let Some(element) = find_first_node(element, name, depth) {
+ return Some(element);
+ }
+ }
+ }
+
+ None
+}
+
+/// Read an svg file from the given path. On success, return the first graphics data that is read
+/// from this file. This can be used to draw an SVG.
+pub fn read_svg_file<P: AsRef<Path>>(file: P) -> io::Result<Element> {
+ let mut file = File::open(file)?;
+ let mut data = String::new();
+ file.read_to_string(&mut data)?;
+
+ let root: Element = match Element::parse(data.as_bytes()) {
+ Ok(root) => root,
+ Err(err) => return Err(io::Error::new(io::ErrorKind::InvalidData, err)),
+ };
+
+ match find_first_node(root, "g", None) {
+ Some(graphics) => Ok(graphics),
+ None => Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "No graphics element in the file",
+ )),
+ }
+}
+
+pub trait DrawSVG {
+ fn draw_svg(&mut self, svg_data: &Element, pixels_per_m: f32, position_px: Vec2<f32>);
+}
+
+impl<D> DrawSVG for D
+where
+ D: RaylibDraw,
+{
+ fn draw_svg(&mut self, svg_data: &Element, pixels_per_m: f32, position_px: Vec2<f32>) {
+ assert_eq!(&svg_data.name, "g");
+
+ // Go through all the graphics children and draw them one by one
+ for child in &svg_data.children {
+ if let XMLNode::Element(child) = child {
+ match child.name.as_str() {
+ "path" => draw_path(self, child, pixels_per_m, position_px),
+ other => println!("Unsupported SVG-Element {}", other),
+ }
+ }
+ }
+ }
+}
+
+// Helper functions to draw specific parts of the SVG file --------------------
+
+fn draw_path(
+ d: &mut impl RaylibDraw,
+ path_data: &Element,
+ pixels_per_m: f32,
+ position_px: Vec2<f32>,
+) {
+ let style = if let Some(style_data) = path_data.attributes.get("style") {
+ match Style::from_str(style_data) {
+ Ok(style) => style,
+ Err(err) => {
+ println!("Could not parse path style: {}", err);
+ println!("Using default style instead");
+ Style::default()
+ }
+ }
+ } else {
+ Style::default()
+ };
+
+ let move_data = match path_data.attributes.get("d") {
+ Some(d) => d,
+ None => {
+ println!("Unable to draw path, no move data found");
+ return;
+ }
+ };
+
+ let mut path: SVGPath = match move_data.parse() {
+ Ok(mv) => mv,
+ Err(err) => {
+ println!(
+ "Unable to draw path, move data not correctly formatted: {}",
+ err
+ );
+ return;
+ }
+ };
+
+ path.conv_to_absolute();
+
+ let mut current_pos: Vec2<f32> = Vec2::new(0., 0.);
+ for segment in path.deref() {
+ match segment {
+ PathSegment::MoveTo { x, y, .. } => {
+ current_pos.x = *x as f32;
+ current_pos.y = *y as f32;
+ }
+ PathSegment::LineTo { x, y, .. } => {
+ d.draw_line_ex(
+ current_pos * pixels_per_m / 1000. + position_px,
+ Vec2::new(*x as f32, *y as f32) * pixels_per_m / 1000. + position_px,
+ pixels_per_m * style.stroke_width / 1000.,
+ style.stroke,
+ );
+ current_pos.x = *x as f32;
+ current_pos.y = *y as f32;
+ }
+ PathSegment::HorizontalLineTo { x, .. } => {
+ d.draw_line_ex(
+ current_pos * pixels_per_m / 1000. + position_px,
+ Vec2::new(*x as f32, current_pos.y) * pixels_per_m / 1000. + position_px,
+ pixels_per_m * style.stroke_width / 1000.,
+ style.stroke,
+ );
+ current_pos.x = *x as f32;
+ }
+ PathSegment::VerticalLineTo { y, .. } => {
+ d.draw_line_ex(
+ current_pos * pixels_per_m / 1000. + position_px,
+ Vec2::new(current_pos.x, *y as f32) * pixels_per_m / 1000. + position_px,
+ pixels_per_m * style.stroke_width / 1000.,
+ style.stroke,
+ );
+ current_pos.y = *y as f32;
+ }
+ PathSegment::ClosePath { .. } => return,
+ other => println!("Ignoring unsupported {:?}", other),
+ }
+ }
+}