]> pintos-os.org Git - pspp/commitdiff
Add catalog of graph patterns.
authorBen Pfaff <blp@cs.stanford.edu>
Thu, 30 Apr 2026 15:41:55 +0000 (08:41 -0700)
committerBen Pfaff <blp@cs.stanford.edu>
Thu, 30 Apr 2026 15:41:55 +0000 (08:41 -0700)
rust/pspp/Cargo.toml
rust/pspp/build.rs
rust/pspp/src/spv/read/graph.rs
rust/pspp/src/spv/read/graph/patterns.rs [new file with mode: 0644]
rust/pspp/src/spv/read/graph/patterns/.gitignore [new file with mode: 0644]

index 4df7a4f4f7023fc5f32fa0794451bd95e3ceb5be..09100e97f51cea66c43d1d85183fa632e137144c 100644 (file)
@@ -73,6 +73,7 @@ anyhow = "1.0.69"
 enum-map = { version = "2.7.3", features = ["serde"] }
 serde = { version = "1.0.218", features = ["derive"] }
 serde_json = "1.0.141"
+quick-xml = { version = "0.38.4" }
 
 zip = "4.0.0"
 
index 21dd054f96678c747730f0cdd8b5ca917e1f93b8..e93e1e0d695840afef369955c348233edf096b3f 100644 (file)
 
 use anyhow::{Result as AnyResult, anyhow};
 use enum_map::{Enum, EnumMap};
+use quick_xml::events::{BytesStart, Event};
 use serde::{Deserialize, Deserializer};
 use serde_json::{Value, from_value};
 use std::{
+    borrow::Cow,
     collections::{BTreeMap, HashMap, HashSet, VecDeque},
     env::var_os,
     ffi::OsStr,
@@ -654,10 +656,109 @@ fn build_locales() -> AnyResult<()> {
     Ok(())
 }
 
+/// Read `src/spv/read/graph/patterns.svg` and write
+/// `src/spv/read/graph/patterns/data.rs`.
+fn build_patterns() -> AnyResult<()> {
+    let input_file = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/spv/read/graph/patterns.svg");
+    println!("cargo:rerun-if-changed={}", input_file.display());
+    let input = read_to_string(&input_file)
+        .map_err(|e| anyhow!("{}: read failed ({e})", input_file.display()))?;
+
+    let mut reader = quick_xml::Reader::from_str(&input);
+    let mut patterns = vec![None; 30];
+    reader.config_mut().expand_empty_elements = true;
+    loop {
+        let event = reader.read_event().unwrap();
+
+        fn id_attribute<'a>(start: &'a BytesStart) -> Option<Cow<'a, str>> {
+            for attribute in start.attributes() {
+                if let Ok(attribute) = attribute
+                    && attribute.key.local_name().as_ref() == b"id"
+                    && let Ok(value) = attribute.unescape_value()
+                {
+                    return Some(value);
+                }
+            }
+            None
+        }
+
+        match &event {
+            Event::Start(start) => {
+                let local_name = start.local_name();
+                if local_name.as_ref() == b"pattern" {
+                    let span = reader.read_to_end(start.to_end().name()).unwrap();
+                    let content = &input[span.start as usize..span.end as usize];
+                    let mut attributes = start
+                        .attributes()
+                        .map(|attribute| {
+                            let attribute = attribute.unwrap();
+                            (
+                                String::from_utf8(Vec::from(attribute.key.0)).unwrap(),
+                                String::from_utf8(attribute.value.into_owned()).unwrap(),
+                            )
+                        })
+                        .filter(|(key, _value)| key != "gifindex" && key != "fill")
+                        .collect::<BTreeMap<_, _>>();
+                    let id = attributes.remove("id").unwrap();
+                    let index = id
+                        .strip_prefix("pattern")
+                        .unwrap()
+                        .parse::<usize>()
+                        .unwrap();
+                    patterns[index] = Some((attributes, content));
+                    //writeln!(&mut output, "{index} {attributes:?} {content:?}").unwrap();
+                }
+            }
+            Event::Eof => break,
+            _ => (),
+        }
+    }
+
+    let patterns = patterns
+        .into_iter()
+        .map(|pattern| pattern.unwrap())
+        .collect::<Vec<_>>();
+
+    let mut output = String::new();
+    writeln!(
+        &mut output,
+        "use super::Pattern;
+
+pub static PATTERNS: [Pattern; 30] = ["
+    )
+    .unwrap();
+    for (attributes, content) in &patterns {
+        writeln!(
+            &mut output,
+            "    Pattern {{
+        attributes: &["
+        )
+        .unwrap();
+        for (name, value) in attributes {
+            writeln!(&mut output, "            ({name:?}, {value:?}),").unwrap();
+        }
+        writeln!(
+            &mut output,
+            "        ],
+        content: {content:?},
+    }},"
+        )
+        .unwrap();
+    }
+    writeln!(&mut output, "];").unwrap();
+
+    let output_file_name =
+        Path::new(env!("CARGO_MANIFEST_DIR")).join("src/spv/read/graph/patterns/data.rs");
+    std::fs::write(output_file_name, &output)?;
+
+    Ok(())
+}
+
 fn main() -> AnyResult<()> {
     println!("cargo:rerun-if-changed=build.rs");
 
     build_encodings()?;
+    build_patterns()?;
     build_locales()?;
 
     Ok(())
index 0e23359412da72ecb242c167c2d08882641717b9..421b7a0064ab361fd876f8afbd5decd13b01900e 100644 (file)
@@ -73,6 +73,7 @@ use crate::{
 
 mod format;
 mod frames;
+mod patterns;
 
 #[derive(Debug)]
 struct Ref<T> {
diff --git a/rust/pspp/src/spv/read/graph/patterns.rs b/rust/pspp/src/spv/read/graph/patterns.rs
new file mode 100644 (file)
index 0000000..89e7440
--- /dev/null
@@ -0,0 +1,60 @@
+use std::io::{Cursor, Write};
+
+use quick_xml::{
+    Writer,
+    events::{BytesEnd, BytesStart, Event},
+};
+
+mod data;
+
+pub struct Pattern {
+    attributes: &'static [(&'static str, &'static str)],
+    content: &'static str,
+}
+
+impl Pattern {
+    /// The number of patterns.
+    pub const N: usize = data::PATTERNS.len();
+
+    /// Returns the pattern with the given `index`, or `None` if the index is
+    /// greater than or equal to [Pattern::N].
+    pub fn for_index(index: usize) -> Option<&'static Self> {
+        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()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::spv::read::graph::patterns::Pattern;
+
+    #[test]
+    fn pattern() {
+        let pattern = Pattern::for_index(1).unwrap();
+        let svg = pattern.to_svg("p1-red", "red");
+
+        assert_eq!(
+            svg,
+            r#"<pattern id="p1-red" fill="red" 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>"#,
+            "This assertion will need to be updated if pattern 1 is changed in patterns.svg."
+        );
+    }
+}
diff --git a/rust/pspp/src/spv/read/graph/patterns/.gitignore b/rust/pspp/src/spv/read/graph/patterns/.gitignore
new file mode 100644 (file)
index 0000000..3100bf1
--- /dev/null
@@ -0,0 +1 @@
+data.rs