]> pintos-os.org Git - pspp/commitdiff
patterns work
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 8 May 2026 03:26:24 +0000 (20:26 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 8 May 2026 03:26:24 +0000 (20:26 -0700)
rust/pspp/src/output/pivot/look.rs
rust/pspp/src/spv/read/graph.rs
rust/pspp/src/spv/read/graph/patterns.rs
rust/pspp/src/spv/read/graph/style.rs

index a46676c4a09edbcf998d26faa566470f1c50f5b4..80863fafc1cd7847b4e5158bd8d0e6ea3b95a6c8 100644 (file)
@@ -882,7 +882,7 @@ impl FontStyle {
 }
 
 /// Color used in [FontStyle].
-#[derive(Copy, Clone, PartialEq, Eq)]
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
 pub struct Color {
     /// Alpha channel.
     ///
@@ -948,6 +948,12 @@ impl Color {
         (self.r, self.g, self.b)
     }
 
+    /// Returns the red, green, blue, and alpha channels of this color, in that
+    /// order.
+    pub fn into_rgba(&self) -> (u8, u8, u8, u8) {
+        (self.r, self.g, self.b, self.alpha)
+    }
+
     /// Returns 16-bit versions of the red, green, and blue channels of this
     /// color.
     pub fn into_rgb16(&self) -> (u16, u16, u16) {
index 2cd3b32fb36f195891cf28e4e2ee0c685c2b71b6..c3ed50f7a63fb04c28460580e8b1c49e508d0cfa 100644 (file)
@@ -46,7 +46,7 @@ use svg::{
     Document, Node,
     node::{
         Comment,
-        element::{Circle, Group, Line, Path, Polygon, Rectangle, path::Data},
+        element::{Circle, Definitions, Group, Line, Path, Polygon, Rectangle, path::Data},
     },
 };
 
@@ -64,6 +64,7 @@ use crate::{
         read::graph::{
             format::Formatter,
             frames::{Component, Frames, Position},
+            patterns::Patterns,
             style::{Style, StyleCycle},
         },
     },
@@ -646,6 +647,7 @@ impl Visualization {
         let mut graph_svg = Group::new().with_id(&graph.id);
         let mut bin_widths = BTreeMap::new();
         let mut axis_domains = EnumMap::<Axis3, AxisDomain>::default();
+        let mut patterns = Patterns::new();
         self.pass(
             &graph,
             &domains,
@@ -915,6 +917,7 @@ impl Visualization {
 
                         let color = style.color.unwrap_or(Color::BLACK);
                         let color2 = style.color2.unwrap_or(Color::BLACK);
+                        let pattern = patterns.insert(style.pattern.unwrap_or_default(), color);
 
                         fn points(
                             rect: &Rect,
@@ -978,7 +981,7 @@ impl Visualization {
                                     let y1 = rect.0[Axis2::Y].1;
                                     element_svg.append(
                                         Rectangle::new()
-                                            .set("fill", color.display_css().to_string())
+                                            .set("fill", pattern.clone())
                                             .set("stroke", color2.display_css().to_string())
                                             .set("x", x.start)
                                             .set("y", y0)
@@ -1106,7 +1109,7 @@ impl Visualization {
                                 }
                                 element_svg.append(
                                     Polygon::new()
-                                        .set("fill", color.display_css().to_string())
+                                        .set("fill", pattern)
                                         .set("stroke", color2.display_css().to_string())
                                         .set("points", data),
                                 );
@@ -1118,6 +1121,15 @@ impl Visualization {
                 }
             }
         }
+        let mut defs = Definitions::new();
+        for node in patterns.to_svg() {
+            defs.append(node);
+        }
+        if let Some(children) = defs.get_children()
+            && !children.is_empty()
+        {
+            graph_svg.append(defs);
+        }
         Ok(graph_svg)
     }
 }
index 89e74406a3e84f20db4d166632b829ac4869ae1c..72590084f09b7765de8e21ed7ae11ea798550d31 100644 (file)
@@ -1,10 +1,12 @@
-use std::io::{Cursor, Write};
-
-use quick_xml::{
-    Writer,
-    events::{BytesEnd, BytesStart, Event},
+use std::{
+    collections::{BTreeMap, BTreeSet},
+    fmt::Display,
 };
 
+use svg::{Node, node::Blob};
+
+use crate::output::pivot::look::Color;
+
 mod data;
 
 pub struct Pattern {
@@ -22,20 +24,66 @@ impl Pattern {
         data::PATTERNS.get(index)
     }
 
-    pub fn to_svg(&self, id: &str, fill: &str) -> String {
-        let mut writer = Writer::new(Cursor::new(Vec::new()));
-        writer
-            .write_event(Event::Start(
-                BytesStart::new("pattern")
-                    .with_attributes([("id", id), ("fill", fill)])
-                    .with_attributes(self.attributes.iter().copied()),
-            ))
-            .unwrap();
-        writer.get_mut().write_all(self.content.as_bytes()).unwrap();
-        writer
-            .write_event(Event::End(BytesEnd::new("pattern")))
-            .unwrap();
-        String::from_utf8(writer.into_inner().into_inner()).unwrap()
+    pub fn to_svg(&self) -> svg::node::element::Pattern {
+        let mut group = svg::node::element::Pattern::new().add(Blob::new(self.content));
+        for (name, value) in self.attributes.into_iter().copied() {
+            group.assign(name, value);
+        }
+        group
+    }
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct Patterns {
+    instances: BTreeMap<usize, BTreeSet<Color>>,
+}
+
+struct PatternName {
+    index: usize,
+    color: Color,
+}
+impl Display for PatternName {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let (r, g, b, a) = self.color.into_rgba();
+        write!(f, "pattern{}_{r:02x}{g:02x}{b:02x}", self.index)?;
+        if a != 255 {
+            write!(f, "_{a:02x}")?;
+        }
+        Ok(())
+    }
+}
+
+impl Patterns {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    pub fn insert(&mut self, index: usize, color: Color) -> String {
+        if index != 0 && Pattern::for_index(index).is_some() {
+            self.instances.entry(index).or_default().insert(color);
+            format!("url(#{})", PatternName { index, color })
+        } else {
+            color.display_css().to_string()
+        }
+    }
+
+    pub fn to_svg(&self) -> impl Iterator<Item = svg::node::element::Pattern> {
+        self.instances.iter().flat_map(|(index, colors)| {
+            colors.iter().map(move |color| {
+                Pattern::for_index(*index)
+                    .unwrap()
+                    .to_svg()
+                    .set(
+                        "id",
+                        PatternName {
+                            index: *index,
+                            color: *color,
+                        }
+                        .to_string(),
+                    )
+                    .set("fill", color.display_css().to_string())
+            })
+        })
     }
 }
 
@@ -46,11 +94,11 @@ mod tests {
     #[test]
     fn pattern() {
         let pattern = Pattern::for_index(1).unwrap();
-        let svg = pattern.to_svg("p1-red", "red");
+        let svg = pattern.to_svg().to_string();
 
         assert_eq!(
             svg,
-            r#"<pattern id="p1-red" fill="red" height="4" patternUnits="userSpaceOnUse" width="10">
+            r#"<pattern height="4" patternUnits="userSpaceOnUse" width="10">
       <circle cx="4.5" cy="3.5" r="0.5" height="1" />
       <rect x="9" y="1" width="1" height="1" />
     </pattern>"#,
index d53b46a488ca63322d518ffcedfe634328f7d856..83ed4074efa86e948cd39b17243249780ecafd8e 100644 (file)
@@ -82,7 +82,7 @@ pub struct Style {
     pub padding: Option<Length>,
 
     #[serde(rename = "@pattern")]
-    pub pattern: Option<u32>,
+    pub pattern: Option<usize>,
 
     #[serde(rename = "@symbol")]
     pub symbol: Option<String>,