]> pintos-os.org Git - pspp/commitdiff
Give patterns solid backgrounds
authorBen Pfaff <blp@cs.stanford.edu>
Fri, 8 May 2026 15:17:57 +0000 (08:17 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Fri, 8 May 2026 15:17:57 +0000 (08:17 -0700)
rust/pspp/build.rs
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/testdata/graph3.expected.svg
rust/pspp/src/spv/testdata/graph3.xml

index 964684f4c8f673f84e84d2e275e44518a2c8ea8c..545489cc2b05a4cbbed759898bc61a2d938b91b1 100644 (file)
@@ -687,12 +687,14 @@ fn build_patterns() -> AnyResult<()> {
                         .filter(|(key, _value)| key != "gifindex" && key != "fill")
                         .collect::<BTreeMap<_, _>>();
                     let id = attributes.remove("id").unwrap();
+                    let height = attributes.remove("height").unwrap().parse::<f64>().unwrap();
+                    let width = attributes.remove("width").unwrap().parse::<f64>().unwrap();
                     let index = id
                         .strip_prefix("pattern")
                         .unwrap()
                         .parse::<usize>()
                         .unwrap();
-                    patterns[index] = Some((attributes, content));
+                    patterns[index] = Some((width, height, attributes, content));
                     //writeln!(&mut output, "{index} {attributes:?} {content:?}").unwrap();
                 }
             }
@@ -714,10 +716,12 @@ fn build_patterns() -> AnyResult<()> {
 pub static PATTERNS: [Pattern; 30] = ["
     )
     .unwrap();
-    for (attributes, content) in &patterns {
+    for (width, height, attributes, content) in &patterns {
         writeln!(
             &mut output,
             "    Pattern {{
+        width: {width:?},
+        height: {height:?},
         attributes: &["
         )
         .unwrap();
index 80863fafc1cd7847b4e5158bd8d0e6ea3b95a6c8..c41656f273f5047907bd2078fd1516c85f6bedc6 100644 (file)
@@ -882,7 +882,7 @@ impl FontStyle {
 }
 
 /// Color used in [FontStyle].
-#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub struct Color {
     /// Alpha channel.
     ///
@@ -943,6 +943,11 @@ impl Color {
         ColorDisplayCss(*self)
     }
 
+    /// Displays opaque colors as `rrggbb` and others as `rrggbbaa`.
+    pub fn display_hex(&self) -> impl Display {
+        ColorDisplayHex(*self)
+    }
+
     /// Returns the red, green, and blue channels of this color.
     pub fn into_rgb(&self) -> (u8, u8, u8) {
         (self.r, self.g, self.b)
@@ -1036,7 +1041,7 @@ struct ColorDisplayCss(Color);
 
 impl Display for ColorDisplayCss {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let Color { alpha, r, g, b } = self.0;
+        let (r, g, b, alpha) = self.0.into_rgba();
         match alpha {
             255 => write!(f, "#{r:02x}{g:02x}{b:02x}"),
             _ => write!(f, "rgb({r}, {g}, {b}, {:.2})", alpha as f64 / 255.0),
@@ -1044,6 +1049,22 @@ impl Display for ColorDisplayCss {
     }
 }
 
+/// A structure for formatting a [Color] as hex digits.
+///
+/// See [Color::display_hex].
+struct ColorDisplayHex(Color);
+
+impl Display for ColorDisplayHex {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let (r, g, b, a) = self.0.into_rgba();
+        write!(f, "{r:02x}{g:02x}{b:02x}")?;
+        if a != 255 {
+            write!(f, "{a:02x}")?;
+        }
+        Ok(())
+    }
+}
+
 /// Style for drawing a border in a pivot table.
 #[derive(Copy, Clone, Debug, PartialEq, Deserialize)]
 pub struct BorderStyle {
index c3ed50f7a63fb04c28460580e8b1c49e508d0cfa..2ed7ea3aa255d2bed292f15ab91be0f7031857dd 100644 (file)
@@ -64,7 +64,7 @@ use crate::{
         read::graph::{
             format::Formatter,
             frames::{Component, Frames, Position},
-            patterns::Patterns,
+            patterns::{ColoredPattern, Patterns},
             style::{Style, StyleCycle},
         },
     },
@@ -917,7 +917,11 @@ 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);
+                        let pattern = patterns.insert(ColoredPattern {
+                            index: style.pattern.unwrap_or_default(),
+                            color,
+                            color2: style.color2.unwrap_or(Color::WHITE),
+                        });
 
                         fn points(
                             rect: &Rect,
index 72590084f09b7765de8e21ed7ae11ea798550d31..7c66006b8d30f311f249bad5b599291874f92a01 100644 (file)
@@ -1,15 +1,17 @@
-use std::{
-    collections::{BTreeMap, BTreeSet},
-    fmt::Display,
-};
+use std::{collections::BTreeSet, fmt::Display};
 
-use svg::{Node, node::Blob};
+use svg::{
+    Node,
+    node::{Blob, element::Rectangle},
+};
 
 use crate::output::pivot::look::Color;
 
 mod data;
 
 pub struct Pattern {
+    width: f64,
+    height: f64,
     attributes: &'static [(&'static str, &'static str)],
     content: &'static str,
 }
@@ -24,8 +26,20 @@ impl Pattern {
         data::PATTERNS.get(index)
     }
 
-    pub fn to_svg(&self) -> svg::node::element::Pattern {
-        let mut group = svg::node::element::Pattern::new().add(Blob::new(self.content));
+    pub fn to_svg(&self, color: Color, color2: Color) -> svg::node::element::Pattern {
+        let mut group = svg::node::element::Pattern::new()
+            .set("fill", color.display_css().to_string())
+            .set("width", self.width)
+            .set("height", self.height)
+            .add(
+                Rectangle::new()
+                    .set("x", 0)
+                    .set("y", 0)
+                    .set("width", self.width)
+                    .set("height", self.height)
+                    .set("fill", color2.display_css().to_string()),
+            )
+            .add(Blob::new(self.content));
         for (name, value) in self.attributes.into_iter().copied() {
             group.assign(name, value);
         }
@@ -33,76 +47,73 @@ impl Pattern {
     }
 }
 
-#[derive(Clone, Debug, Default)]
-pub struct Patterns {
-    instances: BTreeMap<usize, BTreeSet<Color>>,
+#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct ColoredPattern {
+    pub index: usize,
+    pub color: Color,
+    pub color2: Color,
 }
 
-struct PatternName {
-    index: usize,
-    color: Color,
-}
-impl Display for PatternName {
+impl Display for ColoredPattern {
     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}")?;
-        }
+        write!(
+            f,
+            "pattern{}_{}_{}",
+            self.index,
+            self.color.display_hex(),
+            self.color2.display_hex()
+        )?;
         Ok(())
     }
 }
 
+#[derive(Clone, Debug, Default)]
+pub struct Patterns {
+    instances: BTreeSet<ColoredPattern>,
+}
+
 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 })
+    pub fn insert(&mut self, p: ColoredPattern) -> String {
+        if p.index != 0 && Pattern::for_index(p.index).is_some() {
+            self.instances.insert(p);
+            format!("url(#{p})")
         } else {
-            color.display_css().to_string()
+            p.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())
-            })
+        self.instances.iter().map(|cp| {
+            Pattern::for_index(cp.index)
+                .unwrap()
+                .to_svg(cp.color, cp.color2)
+                .set("id", cp.to_string())
         })
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use crate::spv::read::graph::patterns::Pattern;
+    use crate::{output::pivot::look::Color, spv::read::graph::patterns::Pattern};
 
     #[test]
     fn pattern() {
         let pattern = Pattern::for_index(1).unwrap();
-        let svg = pattern.to_svg().to_string();
-
+        let svg = pattern.to_svg(Color::BLACK, Color::WHITE).to_string();
+        println!("{svg}");
         assert_eq!(
             svg,
-            r#"<pattern height="4" patternUnits="userSpaceOnUse" width="10">
+            r##"<pattern fill="#000000" height="4" patternUnits="userSpaceOnUse" width="10">
+<rect fill="#ffffff" height="4" width="10" x="0" y="0"/>
+
       <circle cx="4.5" cy="3.5" r="0.5" height="1" />
       <rect x="9" y="1" width="1" height="1" />
-    </pattern>"#,
-            "This assertion will need to be updated if pattern 1 is changed in patterns.svg."
+    
+</pattern>"##
         );
     }
 }
index 90c070141bd72ae52ad15d2fd354b7b69c43e9e3..a64b0ace26a8469921af27f105becb3570fc74b8 100644 (file)
@@ -57,12 +57,22 @@ Percent
 Percent
 </text>
 <g id="element_bar_1">
-<rect fill="#d3ce97" height="92.56696428571429" stroke="#000000" width="205.6875" x="104.53125" y="126.93303571428571"/>
-<rect fill="#d3ce97" height="204.2857142857143" stroke="#000000" width="205.6875" x="378.78125" y="15.214285714285701"/>
+<rect fill="url(#pattern12_d3ce97_000000)" height="92.56696428571429" stroke="#000000" width="205.6875" x="104.53125" y="126.93303571428571"/>
+<rect fill="url(#pattern12_d3ce97_000000)" height="204.2857142857143" stroke="#000000" width="205.6875" x="378.78125" y="15.214285714285701"/>
 </g>
 <g id="element_bar_1">
-<rect fill="#d3ce97" height="121.29464285714289" stroke="#000000" width="205.6875" x="104.53125" y="317.7053571428571"/>
-<rect fill="#d3ce97" height="127.67857142857144" stroke="#000000" width="205.6875" x="378.78125" y="311.32142857142856"/>
+<rect fill="url(#pattern12_d3ce97_000000)" height="121.29464285714289" stroke="#000000" width="205.6875" x="104.53125" y="317.7053571428571"/>
+<rect fill="url(#pattern12_d3ce97_000000)" height="127.67857142857144" stroke="#000000" width="205.6875" x="378.78125" y="311.32142857142856"/>
 </g>
+<defs>
+<pattern fill="#d3ce97" height="8" id="pattern12_d3ce97_000000" patternUnits="userSpaceOnUse" width="8">
+<rect fill="#000000" height="8" width="8" x="0" y="0"/>
+
+      <rect x="0" y="0" width="1" height="4"/>
+      <rect x="5" y="0" width="3" height="4"/>
+      <rect x="1" y="4" width="4" height="4"/>
+    
+</pattern>
+</defs>
 </g>
 </svg>
index ccbb18be4ff088b92262a5d29db8043c216be63b..52513b9af6db40209a8309559bc455dd4b5f7d8c 100644 (file)
@@ -74,7 +74,7 @@
                <style id="label_panel_1s"/>
                <style id="majorTicks_axis_panel_1markStyle" visible="false"/>
                <style id="majorTicks_axis_panel_1tickLabelStyle"/>
-               <style color="#D3CE97" color2="black" depth="75%" id="element_bar_1s" pattern="0" size="75%"/>
+               <style color="#D3CE97" color2="black" depth="75%" id="element_bar_1s" pattern="12" size="75%"/>
                <style color="black" id="axis_axisx_1s" stroke-width="0.5pt"/>
                <style id="axis_axisx_1baselineStyle"/>
                <style color="black" id="majorTicks_axis_axisx_1markStyle" stroke-width="1pt"/>