From f92e9f6f07b1e3834c2ca58ce3510734819d08e4 Mon Sep 17 00:00:00 2001 From: Arne Dußin Date: Wed, 27 Jan 2021 14:01:50 +0100 Subject: Rework graf karto to fit the client/server structure --- src/client/svg/mod.rs | 178 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 src/client/svg/mod.rs (limited to 'src/client/svg/mod.rs') diff --git a/src/client/svg/mod.rs b/src/client/svg/mod.rs new file mode 100644 index 0000000..af066f1 --- /dev/null +++ b/src/client/svg/mod.rs @@ -0,0 +1,178 @@ +//! 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 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) -> Option { + // 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>(file: P) -> io::Result { + 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", + )), + } +} + +/// Trait that indicates a struct is capable of drawing SVG-elements. +pub trait DrawSVG { + /// Draw the elements given by `svg_data` and all its children to the implementor, with the + /// specified scale and translated by `position_px`. + fn draw_svg(&mut self, svg_data: &Element, pixels_per_m: f32, position_px: Vec2); +} + +impl DrawSVG for D +where + D: RaylibDraw, +{ + fn draw_svg(&mut self, svg_data: &Element, pixels_per_m: f32, position_px: Vec2) { + 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 => warn!("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, +) { + let style = if let Some(style_data) = path_data.attributes.get("style") { + match Style::from_str(style_data) { + Ok(style) => style, + Err(err) => { + warn!("Could not parse path style: {}", err); + warn!("Using default style instead"); + Style::default() + } + } + } else { + Style::default() + }; + + let move_data = match path_data.attributes.get("d") { + Some(d) => d, + None => { + error!("Unable to draw path, no move data found"); + return; + } + }; + + let mut path: SVGPath = match move_data.parse() { + Ok(mv) => mv, + Err(err) => { + error!( + "Unable to draw path, move data not correctly formatted: {}", + err + ); + return; + } + }; + + path.conv_to_absolute(); + + let mut current_pos: Vec2 = 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 => warn!("Ignoring unsupported {:?}", other), + } + } +} -- cgit v1.2.3-70-g09d2