From 3e839726060964e2e5b049232a79962ce0aa16ae Mon Sep 17 00:00:00 2001 From: Raph Levien Date: Thu, 28 May 2020 11:48:36 -0700 Subject: [PATCH] Improve SVG parsing WIP --- piet-gpu/src/pico_svg.rs | 88 ++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/piet-gpu/src/pico_svg.rs b/piet-gpu/src/pico_svg.rs index a4c92d0..7b52f5a 100644 --- a/piet-gpu/src/pico_svg.rs +++ b/piet-gpu/src/pico_svg.rs @@ -2,7 +2,7 @@ use std::str::FromStr; -use roxmltree::Document; +use roxmltree::{Document, Node}; use piet::kurbo::{Affine, BezPath}; @@ -28,27 +28,18 @@ pub struct FillItem { path: BezPath, } +struct Parser<'a> { + items: &'a mut Vec, +} + impl PicoSvg { pub fn load(xml_string: &str, scale: f64) -> Result> { let doc = Document::parse(xml_string)?; let root = doc.root_element(); - let g = root.first_element_child().ok_or("no root element")?; let mut items = Vec::new(); - for el in g.children() { - if el.is_element() { - let d = el.attribute("d").ok_or("missing 'd' attribute")?; - let bp = BezPath::from_svg(d)?; - let path = Affine::scale(scale) * bp; - if let Some(fill_color) = el.attribute("fill") { - let color = parse_color(fill_color); - items.push(Item::Fill(FillItem { color, path: path.clone() })); - } - if let Some(stroke_color) = el.attribute("stroke") { - let width = f64::from_str(el.attribute("stroke-width").ok_or("missing width")?)?; - let color = parse_color(stroke_color); - items.push(Item::Stroke(StrokeItem { width, color, path })); - } - } + let mut parser = Parser::new(&mut items); + for node in root.children() { + parser.rec_parse(node)?; } Ok(PicoSvg { items }) } @@ -67,6 +58,49 @@ impl PicoSvg { } } +impl<'a> Parser<'a> { + fn new(items: &'a mut Vec) -> Parser<'a> { + Parser { + items + } + } + + fn rec_parse(&mut self, node: Node) -> Result<(), Box> { + if node.is_element() { + match node.tag_name().name() { + "g" => { + for child in node.children() { + self.rec_parse(child)?; + } + } + "path" => { + let d = node.attribute("d").ok_or("missing 'd' attribute")?; + let bp = BezPath::from_svg(d)?; + let path = Affine::new([1.5, 0.0, 0.0, -1.5, 0.0, 1500.0]) * bp; + // TODO: default fill color is black, but this is overridden in tiger to this logic. + if let Some(fill_color) = node.attribute("fill") { + if fill_color != "none" { + let color = parse_color(fill_color); + let color = modify_opacity(color, "fill-opacity", node); + self.items.push(Item::Fill(FillItem { color, path: path.clone() })); + } + } + if let Some(stroke_color) = node.attribute("stroke") { + if stroke_color != "none" { + let width = 1.5 * f64::from_str(node.attribute("stroke-width").ok_or("missing width")?)?; + let color = parse_color(stroke_color); + let color = modify_opacity(color, "stroke-opacity", node); + self.items.push(Item::Stroke(StrokeItem { width, color, path })); + } + } + } + _ => (), + } + } + Ok(()) + } +} + fn parse_color(color: &str) -> Color { if color.as_bytes()[0] == b'#' { let mut hex = u32::from_str_radix(&color[1..], 16).unwrap(); @@ -74,7 +108,27 @@ fn parse_color(color: &str) -> Color { hex = (hex >> 8) * 0x110000 + ((hex >> 4) & 0xf) * 0x1100 + (hex & 0xf) * 0x11; } Color::from_rgba32_u32((hex << 8) + 0xff) + } else if color.starts_with("rgb(") { + let mut iter = color[4..color.len() - 1].split(','); + let r = u8::from_str(iter.next().unwrap()).unwrap(); + let g = u8::from_str(iter.next().unwrap()).unwrap(); + let b = u8::from_str(iter.next().unwrap()).unwrap(); + Color::rgb8(r, g, b) } else { Color::from_rgba32_u32(0xff00ff80) } } + +fn modify_opacity(color: Color, attr_name: &str, node: Node) -> Color { + if let Some(opacity) = node.attribute(attr_name) { + let alpha = if opacity.ends_with("%") { + let pctg = opacity[..opacity.len() - 1].parse().unwrap_or(100.0); + pctg * 0.01 + } else { + opacity.parse().unwrap_or(1.0) + }; + color.with_alpha(alpha) + } else { + color + } +}