From ca230223331385ca7f47526df525ac4cb4719cf2 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 30 Apr 2026 08:41:55 -0700 Subject: [PATCH] Add catalog of graph patterns. --- rust/pspp/Cargo.toml | 1 + rust/pspp/build.rs | 101 ++++++++++++++++++ rust/pspp/src/spv/read/graph.rs | 1 + rust/pspp/src/spv/read/graph/patterns.rs | 60 +++++++++++ .../src/spv/read/graph/patterns/.gitignore | 1 + 5 files changed, 164 insertions(+) create mode 100644 rust/pspp/src/spv/read/graph/patterns.rs create mode 100644 rust/pspp/src/spv/read/graph/patterns/.gitignore diff --git a/rust/pspp/Cargo.toml b/rust/pspp/Cargo.toml index 4df7a4f4f7..09100e97f5 100644 --- a/rust/pspp/Cargo.toml +++ b/rust/pspp/Cargo.toml @@ -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" diff --git a/rust/pspp/build.rs b/rust/pspp/build.rs index 21dd054f96..e93e1e0d69 100644 --- a/rust/pspp/build.rs +++ b/rust/pspp/build.rs @@ -16,9 +16,11 @@ 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> { + 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::>(); + let id = attributes.remove("id").unwrap(); + let index = id + .strip_prefix("pattern") + .unwrap() + .parse::() + .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::>(); + + 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(()) diff --git a/rust/pspp/src/spv/read/graph.rs b/rust/pspp/src/spv/read/graph.rs index 0e23359412..421b7a0064 100644 --- a/rust/pspp/src/spv/read/graph.rs +++ b/rust/pspp/src/spv/read/graph.rs @@ -73,6 +73,7 @@ use crate::{ mod format; mod frames; +mod patterns; #[derive(Debug)] struct Ref { diff --git a/rust/pspp/src/spv/read/graph/patterns.rs b/rust/pspp/src/spv/read/graph/patterns.rs new file mode 100644 index 0000000000..89e74406a3 --- /dev/null +++ b/rust/pspp/src/spv/read/graph/patterns.rs @@ -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#" + + + "#, + "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 index 0000000000..3100bf1d81 --- /dev/null +++ b/rust/pspp/src/spv/read/graph/patterns/.gitignore @@ -0,0 +1 @@ +data.rs -- 2.47.3