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 { /* 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 = extract_hex(html, 1); let green: Option = extract_hex(html, 3); let blue: Option = 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. 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 pub enum StrokeLineJoin { Miter, Round, Bevel, } /// The style of a path drawing instruction. pub struct Style { pub fill: Option, 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 { 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 { 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 { // 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::() { 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::() { 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., } } }