From 015309bf79723bf9375779763137e82dd5e43c2e Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 4 May 2026 17:51:12 -0700 Subject: [PATCH] Add module for symobls. --- rust/pspp/build.rs | 114 +++++- rust/pspp/src/spv/read/graph.rs | 1 + rust/pspp/src/spv/read/graph/symbols.rs | 69 ++++ rust/pspp/src/spv/read/graph/symbols.svg | 377 +++++++++--------- .../src/spv/read/graph/symbols/.gitignore | 1 + 5 files changed, 357 insertions(+), 205 deletions(-) create mode 100644 rust/pspp/src/spv/read/graph/symbols.rs create mode 100644 rust/pspp/src/spv/read/graph/symbols/.gitignore diff --git a/rust/pspp/build.rs b/rust/pspp/build.rs index e93e1e0d69..09f2e017ef 100644 --- a/rust/pspp/build.rs +++ b/rust/pspp/build.rs @@ -16,17 +16,16 @@ use anyhow::{Result as AnyResult, anyhow}; use enum_map::{Enum, EnumMap}; -use quick_xml::events::{BytesStart, Event}; +use quick_xml::{Writer, events::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, fmt::{Display, Write as _}, fs::{File, read_to_string}, - io::{Error as IoError, Write}, + io::{Cursor, Error as IoError, Write}, path::{Path, PathBuf}, }; use zip::ZipArchive; @@ -670,18 +669,6 @@ fn build_patterns() -> AnyResult<()> { 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(); @@ -754,11 +741,108 @@ pub static PATTERNS: [Pattern; 30] = [" Ok(()) } +/// Read `src/spv/read/graph/symbols.svg` and write +/// `src/spv/read/graph/symbols/data.rs`. +fn build_symbols() -> AnyResult<()> { + let input_file = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/spv/read/graph/symbols.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 symbols = Vec::new(); + reader.config_mut().expand_empty_elements = true; + let mut in_defs = false; + loop { + let event = reader.read_event().unwrap(); + + match &event { + Event::Start(start) => { + let local_name = start.local_name(); + if local_name.as_ref() == b"defs" { + in_defs = true; + } else if in_defs && local_name.as_ref() == b"g" { + 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(), + ) + }) + .collect::>(); + let name = attributes.remove("id").unwrap(); + let hollow = match attributes.remove("hollow").unwrap().as_str() { + "true" => true, + "false" => false, + _ => unreachable!(), + }; + symbols.push((name.clone(), hollow, content)); + + if !hollow { + let mut iter = name.chars(); + let first = iter.next().unwrap().to_ascii_uppercase(); + let rest = iter.as_str(); + symbols.push((format!("hollow{first}{rest}"), true, content)); + } + } + } + Event::End(end) => { + if end.local_name().as_ref() == b"defs" { + in_defs = false; + } + } + Event::Eof => break, + _ => (), + } + } + + let mut output = String::new(); + writeln!( + &mut output, + "use super::Symbol; + +pub static SYMBOLS: [Symbol; {}] = [", + symbols.len() + ) + .unwrap(); + for (name, hollow, content) in &symbols { + let mut writer = Writer::new(Cursor::new(Vec::new())); + writer + .create_element("g") + .with_attribute(("id", name.as_str())) + .write_inner_content(|w| w.get_mut().write_all(content.as_bytes())) + .unwrap(); + let definition = String::from_utf8(writer.into_inner().into_inner()).unwrap(); + + writeln!( + &mut output, + " Symbol {{ + name: {name:?}, + hollow: {hollow:?}, + definition: {definition:?}, + }}," + ) + .unwrap(); + } + writeln!(&mut output, "];").unwrap(); + + let output_file_name = + Path::new(env!("CARGO_MANIFEST_DIR")).join("src/spv/read/graph/symbols/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_symbols()?; build_locales()?; Ok(()) diff --git a/rust/pspp/src/spv/read/graph.rs b/rust/pspp/src/spv/read/graph.rs index 421b7a0064..7dd2adb179 100644 --- a/rust/pspp/src/spv/read/graph.rs +++ b/rust/pspp/src/spv/read/graph.rs @@ -74,6 +74,7 @@ use crate::{ mod format; mod frames; mod patterns; +mod symbols; #[derive(Debug)] struct Ref { diff --git a/rust/pspp/src/spv/read/graph/symbols.rs b/rust/pspp/src/spv/read/graph/symbols.rs new file mode 100644 index 0000000000..376fe034fa --- /dev/null +++ b/rust/pspp/src/spv/read/graph/symbols.rs @@ -0,0 +1,69 @@ +use crate::spv::read::graph::symbols::data::SYMBOLS; + +mod data; + +/// A symbol for marking points in a graph. +#[derive(Clone, Debug)] +pub struct Symbol { + /// The symbol's name. + pub name: &'static str, + + /// Whether the symbol is hollow: + /// + /// - A hollow symbol has a stroke color. It should not have a fill color. + /// - A non-hollow symbol has a stroke color and an optional fill color. + pub hollow: bool, + + /// The XML definition of the symbol, as a complete XML element whose `id` + /// attribute is `name`. + /// + /// The definition does not include `fill` or `stroke` attributes. It is + /// intended to be included in a `` element and instantiated with + /// ``. + pub definition: &'static str, +} + +impl Symbol { + /// Returns the symbol with the given name, or `None` if there is none. + /// + /// Symbol names are matched case-insensitively. Use the `name` member of + /// the returned symbol for the canonical name. + pub fn for_name(name: &str) -> Option<&'static Self> { + SYMBOLS + .iter() + .find(|symbol| symbol.name.eq_ignore_ascii_case(name)) + } + + /// Returns true if the symbol is hollow. + pub fn is_hollow(&self) -> bool { + self.hollow + } +} + +#[cfg(test)] +mod tests { + use crate::spv::read::graph::symbols::Symbol; + + #[test] + fn symbol() { + let symbol = Symbol::for_name("SQUARE").unwrap(); + assert_eq!(symbol.name, "square"); + assert_eq!(symbol.hollow, false); + assert_eq!( + symbol.definition, + r#" + + "# + ); + + let symbol = Symbol::for_name("hollowsquare").unwrap(); + assert_eq!(symbol.name, "hollowSquare"); + assert_eq!(symbol.hollow, true); + assert_eq!( + symbol.definition, + r#" + + "# + ); + } +} diff --git a/rust/pspp/src/spv/read/graph/symbols.svg b/rust/pspp/src/spv/read/graph/symbols.svg index 4e153ec95d..cc8960f645 100644 --- a/rust/pspp/src/spv/read/graph/symbols.svg +++ b/rust/pspp/src/spv/read/graph/symbols.svg @@ -1,3 +1,5 @@ + + - - + + - + - + - + - - + + - - - + + + - - + + - - - + + + - + - - - - - + + + + + - - - - + + + + - - - + + + - - - + + + - - + + - - - - - + + + - - - - + + + + - - - - - + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/rust/pspp/src/spv/read/graph/symbols/.gitignore b/rust/pspp/src/spv/read/graph/symbols/.gitignore new file mode 100644 index 0000000000..3100bf1d81 --- /dev/null +++ b/rust/pspp/src/spv/read/graph/symbols/.gitignore @@ -0,0 +1 @@ +data.rs -- 2.47.3