[pico_svg] Encode group transform in scene

Build the scene recursively, applying the transform at group nodes, rather than flattening to a single list of items. This causes the transform to be applied correctly to stroke styles, which among other things fixes the rendering of the waves example from the Nehab timings data set.

We're deliberately keeping things minimal, but if we were to add other properties to groups (clips, opacity, etc), then this change would support that.
diff --git a/examples/scenes/src/pico_svg.rs b/examples/scenes/src/pico_svg.rs
index cac14f3..1bc809e 100644
--- a/examples/scenes/src/pico_svg.rs
+++ b/examples/scenes/src/pico_svg.rs
@@ -19,6 +19,7 @@
 pub enum Item {
     Fill(FillItem),
     Stroke(StrokeItem),
+    Group(GroupItem),
 }
 
 pub struct StrokeItem {
@@ -32,17 +33,20 @@
     pub path: BezPath,
 }
 
-struct Parser<'a> {
+pub struct GroupItem {
+    pub affine: Affine,
+    pub children: Vec<Item>,
+}
+
+struct Parser {
     scale: f64,
-    items: &'a mut Vec<Item>,
 }
 
 impl PicoSvg {
     pub fn load(xml_string: &str, scale: f64) -> Result<PicoSvg, Box<dyn std::error::Error>> {
         let doc = Document::parse(xml_string)?;
         let root = doc.root_element();
-        let mut items = Vec::new();
-        let mut parser = Parser::new(&mut items, scale);
+        let mut parser = Parser::new(scale);
         let width = root.attribute("width").and_then(|s| f64::from_str(s).ok());
         let height = root.attribute("height").and_then(|s| f64::from_str(s).ok());
         let (origin, viewbox_size) = root
@@ -99,32 +103,39 @@
             Affine::new([-scale, 0.0, 0.0, scale, 0.0, 0.0])
         };
         let props = RecursiveProperties {
-            transform,
             fill: Some(Color::BLACK),
         };
         // The root element is the svg document element, which we don't care about
+        let mut items = Vec::new();
         for node in root.children() {
-            parser.rec_parse(node, &props)?;
+            parser.rec_parse(node, &props, &mut items)?;
         }
-        Ok(PicoSvg { items, size })
+        let root_group = Item::Group(GroupItem {
+            affine: transform,
+            children: items,
+        });
+        Ok(PicoSvg {
+            items: vec![root_group],
+            size,
+        })
     }
 }
 
 #[derive(Clone)]
 struct RecursiveProperties {
-    transform: Affine,
     fill: Option<Color>,
 }
 
-impl<'a> Parser<'a> {
-    fn new(items: &'a mut Vec<Item>, scale: f64) -> Parser<'a> {
-        Parser { scale, items }
+impl Parser {
+    fn new(scale: f64) -> Parser {
+        Parser { scale }
     }
 
     fn rec_parse(
         &mut self,
         node: Node,
         properties: &RecursiveProperties,
+        items: &mut Vec<Item>,
     ) -> Result<(), Box<dyn std::error::Error>> {
         if node.is_element() {
             let mut properties = properties.clone();
@@ -139,21 +150,24 @@
                     properties.fill = Some(color);
                 }
             }
-            if let Some(transform) = node.attribute("transform") {
-                properties.transform *= parse_transform(transform);
-            }
             match node.tag_name().name() {
                 "g" => {
-                    for child in node.children() {
-                        self.rec_parse(child, &properties)?;
+                    let mut children = Vec::new();
+                    let mut affine = Affine::default();
+                    if let Some(transform) = node.attribute("transform") {
+                        affine = parse_transform(transform);
                     }
+                    for child in node.children() {
+                        self.rec_parse(child, &properties, &mut children)?;
+                    }
+                    items.push(Item::Group(GroupItem { affine, children }));
                 }
                 "path" => {
                     let d = node.attribute("d").ok_or("missing 'd' attribute")?;
                     let bp = BezPath::from_svg(d)?;
-                    let path = properties.transform * bp;
+                    let path = bp;
                     if let Some(color) = properties.fill {
-                        self.items.push(Item::Fill(FillItem {
+                        items.push(Item::Fill(FillItem {
                             color,
                             path: path.clone(),
                         }));
@@ -169,8 +183,7 @@
                             let color = modify_opacity(color, "stroke-opacity", node);
                             // TODO: Handle recursive opacity properly
                             let color = modify_opacity(color, "opacity", node);
-                            self.items
-                                .push(Item::Stroke(StrokeItem { width, color, path }));
+                            items.push(Item::Stroke(StrokeItem { width, color, path }));
                         }
                     }
                 }
diff --git a/examples/scenes/src/svg.rs b/examples/scenes/src/svg.rs
index 8f81cca..9370936 100644
--- a/examples/scenes/src/svg.rs
+++ b/examples/scenes/src/svg.rs
@@ -71,6 +71,38 @@
     }
 }
 
+fn render_svg_rec(items: &[crate::pico_svg::Item]) -> Scene {
+    let mut scene = Scene::new();
+    for item in items {
+        use crate::pico_svg::Item;
+        match item {
+            Item::Fill(fill) => {
+                scene.fill(
+                    Fill::NonZero,
+                    Affine::IDENTITY,
+                    fill.color,
+                    None,
+                    &fill.path,
+                );
+            }
+            Item::Stroke(stroke) => {
+                scene.stroke(
+                    &Stroke::new(stroke.width),
+                    Affine::IDENTITY,
+                    stroke.color,
+                    None,
+                    &stroke.path,
+                );
+            }
+            Item::Group(group) => {
+                let child_scene = render_svg_rec(&group.children);
+                scene.append(&child_scene, Some(group.affine));
+            }
+        }
+    }
+    scene
+}
+
 pub fn svg_function_of<R: AsRef<str>>(
     name: String,
     contents: impl FnOnce() -> R + Send + 'static,
@@ -79,34 +111,12 @@
         use crate::pico_svg::*;
         let start = Instant::now();
         match PicoSvg::load(contents, 1.0) {
-            Ok(PicoSvg { items, size }) => {
+            Ok(svg) => {
                 eprintln!("Parsed svg {name} in {:?}", start.elapsed());
                 let start = Instant::now();
-                let mut new_scene = Scene::new();
-                for item in items {
-                    match item {
-                        Item::Fill(fill) => {
-                            new_scene.fill(
-                                Fill::NonZero,
-                                Affine::IDENTITY,
-                                fill.color,
-                                None,
-                                &fill.path,
-                            );
-                        }
-                        Item::Stroke(stroke) => {
-                            new_scene.stroke(
-                                &Stroke::new(stroke.width),
-                                Affine::IDENTITY,
-                                stroke.color,
-                                None,
-                                &stroke.path,
-                            );
-                        }
-                    }
-                }
+                let scene = render_svg_rec(&svg.items);
                 eprintln!("Encoded svg {name} in {:?}", start.elapsed());
-                (new_scene, size.to_vec2())
+                (scene, svg.size.to_vec2())
             }
             Err(e) => {
                 eprintln!("Failed to load svg: {e}");