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,
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(())
--- /dev/null
+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."
+ );
+ }
+}