diff options
Diffstat (limited to 'src/svg')
| -rw-r--r-- | src/svg/mod.rs | 178 | ||||
| -rw-r--r-- | src/svg/style.rs | 180 |
2 files changed, 0 insertions, 358 deletions
diff --git a/src/svg/mod.rs b/src/svg/mod.rs deleted file mode 100644 index af066f1..0000000 --- a/src/svg/mod.rs +++ /dev/null @@ -1,178 +0,0 @@ -//! 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<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", - )), - } -} - -/// 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<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 => 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<f32>, -) { - 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<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 => warn!("Ignoring unsupported {:?}", other), - } - } -} diff --git a/src/svg/style.rs b/src/svg/style.rs deleted file mode 100644 index 7a0110e..0000000 --- a/src/svg/style.rs +++ /dev/null @@ -1,180 +0,0 @@ -//! Style options for SVG components. -//! -//! For information on SVG styles, pleas see the SVG documentation. -// TODO: There should be a lib available that can be integrated into the program - -use raylib::ffi::Color; -use std::str::FromStr; - -/// Convert an html-style colour into a raylib Color-struct if possible. If there is an error in -/// the formatting, it returns `None`. -pub fn colour_from_html(html: &str) -> Option<Color> { - /* The html-code must be exactly seven characters long, one for the hash and two per primary - * colour. - */ - if html.len() != 7 { - return None; - } - - let extract_hex = |string: &str, pos: usize| { - u8::from_str_radix( - string.get(pos..pos + 2).expect("Could not split string"), - 16, - ) - .ok() - }; - let red: Option<u8> = extract_hex(html, 1); - let green: Option<u8> = extract_hex(html, 3); - let blue: Option<u8> = extract_hex(html, 5); - - if let (Some(r), Some(g), Some(b)) = (red, green, blue) { - Some(Color { r, g, b, a: 255 }) - } else { - None - } -} - -/// The style of the end of the stroke. -/// See [stroke-line-cap property](https://www.w3.org/TR/SVG11/painting.html#StrokeLinecapProperty) -/// in the SVG Documentation. -#[allow(missing_docs)] -pub enum StrokeLineCap { - Butt, - Round, - Square, -} - -/// The style of the joining corners of the stroke. -/// See [stroke-line-join property](https://www.w3.org/TR/SVG11/painting.html#StrokeLinejoinProperty) -/// in the SVG Documentation -#[allow(missing_docs)] -pub enum StrokeLineJoin { - Miter, - Round, - Bevel, -} - -/// The style of a path drawing instruction. -#[allow(missing_docs)] -pub struct Style { - pub fill: Option<Color>, - pub stroke: Color, - pub stroke_width: f32, - pub stroke_linecap: StrokeLineCap, - pub stroke_linejoin: StrokeLineJoin, - pub stroke_opacity: f32, -} - -impl FromStr for StrokeLineCap { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "butt" => Ok(Self::Butt), - "round" => Ok(Self::Round), - "square" => Ok(Self::Square), - _ => Err("No such line-cap style".to_owned()), - } - } -} - -impl Default for StrokeLineCap { - fn default() -> Self { - StrokeLineCap::Butt - } -} - -impl FromStr for StrokeLineJoin { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - match s { - "miter" => Ok(Self::Miter), - "round" => Ok(Self::Round), - "bevel" => Ok(Self::Bevel), - _ => Err("No such line-join style".to_owned()), - } - } -} - -impl Default for StrokeLineJoin { - fn default() -> Self { - StrokeLineJoin::Miter - } -} - -impl FromStr for Style { - type Err = String; - - fn from_str(s: &str) -> Result<Self, Self::Err> { - // Split into CSS-attributes - let attributes: Vec<&str> = s.split(';').collect(); - // Split the attributes into name and value pairs and parse them into a style struct - let mut style = Style::default(); - for attribute in attributes { - let attribute_parts: Vec<&str> = attribute.split(':').collect(); - if attribute_parts.len() != 2 { - continue; - } - - match attribute_parts[0].trim() { - "fill" => { - style.fill = match attribute_parts[1].trim() { - "none" => None, - colour => colour_from_html(colour), - } - } - "stroke" => { - style.stroke = match colour_from_html(attribute_parts[1].trim()) { - Some(c) => c, - None => { - return Err(format!( - "Could not parse colour from {}", - attribute_parts[1].trim() - )) - } - } - } - "stroke-width" => { - style.stroke_width = match attribute_parts[1].trim().parse::<f32>() { - Ok(width) => width, - Err(err) => return Err(format!("Could not parse stroke-width: {}", err)), - } - } - "stroke-linecap" => { - style.stroke_linecap = StrokeLineCap::from_str(attribute_parts[1].trim())? - } - "stroke-linejoin" => { - style.stroke_linejoin = StrokeLineJoin::from_str(attribute_parts[1].trim())? - } - "stroke-opacity" => { - style.stroke_width = match attribute_parts[1].trim().parse::<f32>() { - Ok(opacity) => opacity, - Err(err) => return Err(format!("Could not parse stroke-opacity: {}", err)), - } - } - attr => return Err(format!("Unknown attribute {}", attr)), - } - } - - Ok(style) - } -} - -impl Default for Style { - fn default() -> Self { - Self { - fill: None, - stroke: Color { - r: 0, - g: 0, - b: 0, - a: 255, - }, - stroke_width: 1., - stroke_linecap: StrokeLineCap::default(), - stroke_linejoin: StrokeLineJoin::default(), - stroke_opacity: 1., - } - } -} |
