Skip to main content

oxml_drawing/
lib.rs

1pub mod color;
2pub mod effect;
3pub mod fill;
4pub mod geometry;
5pub mod line;
6pub mod namespace;
7pub mod order;
8mod preset_shape_data;
9pub mod shape_props;
10pub mod style_ref;
11pub mod table;
12pub mod text;
13pub mod theme;
14pub mod xfrm;
15
16#[cfg(test)]
17mod table_gate_tests {
18    use crate::table::CT_Table;
19
20    #[test]
21    fn merged_table_round_trips_with_merge_origins_intact() {
22        let xml = br#"<a:tbl xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:tblGrid><a:gridCol w="1000"/><a:gridCol w="2000"/><a:gridCol w="3000"/></a:tblGrid><a:tr h="4000"><a:tc rowSpan="2" gridSpan="2"><a:txBody><a:bodyPr/><a:lstStyle/><a:p/></a:txBody><a:tcPr/></a:tc><a:tc hMerge="1"><a:tcPr/></a:tc><a:tc><a:tcPr/></a:tc></a:tr><a:tr h="5000"><a:tc vMerge="1"><a:tcPr/></a:tc><a:tc hMerge="1" vMerge="1"><a:tcPr/></a:tc><a:tc><a:tcPr/></a:tc></a:tr></a:tbl>"#;
23        let table = CT_Table::from_xml(xml).unwrap();
24        assert_eq!(
25            table
26                .grid
27                .columns
28                .iter()
29                .map(|width| width.0)
30                .collect::<Vec<_>>(),
31            vec![1000, 2000, 3000]
32        );
33        assert_eq!(table.rows[0].height.0, 4000);
34        assert_eq!(table.rows[1].height.0, 5000);
35        let origin = &table.rows[0].cells[0];
36        assert_eq!((origin.row_span, origin.grid_span), (2, 2));
37        assert!(!origin.horizontal_merge && !origin.vertical_merge);
38        assert!(table.rows[0].cells[1].horizontal_merge);
39        assert!(table.rows[1].cells[0].vertical_merge);
40        assert!(table.rows[1].cells[1].horizontal_merge);
41        assert!(table.rows[1].cells[1].vertical_merge);
42        let written = table.to_xml().unwrap();
43        assert_eq!(table, CT_Table::from_xml(&written).unwrap());
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use std::collections::HashSet;
50    use std::path::PathBuf;
51    use std::process::Command;
52
53    use quick_xml::Reader;
54    use quick_xml::events::Event;
55
56    use super::geometry::CT_CustomGeometry2D;
57    use super::namespace::{A_NS, A_PREFIX, PIC_NS, PIC_PREFIX};
58    use super::preset_shape_data::preset_shape_definition;
59
60    fn workspace_file(path: &str) -> PathBuf {
61        PathBuf::from(env!("CARGO_MANIFEST_DIR"))
62            .join("../..")
63            .join(path)
64    }
65
66    fn run_generator(arguments: &[&str]) -> std::process::Output {
67        Command::new("python3")
68            .arg(workspace_file("tools/gen-presets/generate.py"))
69            .args(arguments)
70            .current_dir(workspace_file("."))
71            .output()
72            .expect("run the preset shape generator")
73    }
74
75    #[test]
76    fn generator_reproduces_checked_in_table() {
77        let output = run_generator(&["--check"]);
78        assert!(
79            output.status.success(),
80            "generator check failed: {}",
81            String::from_utf8_lossy(&output.stderr)
82        );
83    }
84
85    #[test]
86    fn generated_table_covers_every_corpus_preset() {
87        let corpus = std::env::var_os("RDOCX_PPTX_CORPUS_DIR")
88            .map(PathBuf::from)
89            .unwrap_or_else(|| workspace_file("corpus/pptx"));
90        if !corpus.is_dir() {
91            assert_ne!(
92                std::env::var_os("RDOCX_PPTX_CORPUS_REQUIRED").as_deref(),
93                Some(std::ffi::OsStr::new("1")),
94                "the pinned corpus is required but {} does not exist",
95                corpus.display()
96            );
97            eprintln!("corpus gate skipped because {} is absent", corpus.display());
98            return;
99        }
100        let corpus = corpus.to_str().expect("corpus path is UTF-8");
101        let output = run_generator(&["--check", "--corpus", corpus]);
102        assert!(
103            output.status.success(),
104            "corpus preset check failed: {}",
105            String::from_utf8_lossy(&output.stderr)
106        );
107    }
108
109    #[test]
110    fn source_has_187_direct_definitions() {
111        let source = std::fs::read(workspace_file(
112            "tools/gen-presets/presetShapeDefinitions.xml",
113        ))
114        .expect("read the pinned official preset source");
115        let mut reader = Reader::from_reader(source.as_slice());
116        let mut buffer = Vec::new();
117        let mut depth = 0usize;
118        let mut direct_definitions = 0usize;
119        let mut names = HashSet::new();
120        loop {
121            match reader
122                .read_event_into(&mut buffer)
123                .expect("parse source XML")
124            {
125                Event::Start(element) => {
126                    if depth == 1 {
127                        direct_definitions += 1;
128                        names.insert(element.name().as_ref().to_vec());
129                    }
130                    depth += 1;
131                }
132                Event::Empty(element) if depth == 1 => {
133                    direct_definitions += 1;
134                    names.insert(element.name().as_ref().to_vec());
135                }
136                Event::End(_) => depth -= 1,
137                Event::Eof => break,
138                _ => {}
139            }
140            buffer.clear();
141        }
142        assert_eq!(direct_definitions, 187);
143        assert_eq!(names.len(), 186);
144        let source = String::from_utf8(source).expect("source XML is UTF-8");
145        let duplicates: Vec<_> = source
146            .split("<upDownArrow>")
147            .skip(1)
148            .map(|suffix| {
149                suffix
150                    .split("</upDownArrow>")
151                    .next()
152                    .expect("complete upDownArrow definition")
153            })
154            .collect();
155        assert_eq!(duplicates.len(), 2);
156        assert_eq!(duplicates[0].as_bytes(), duplicates[1].as_bytes());
157    }
158
159    #[test]
160    fn generated_lookup_has_known_and_unknown_cases() {
161        let rectangle = preset_shape_definition("rect").expect("known rectangle preset");
162        assert!(CT_CustomGeometry2D::from_xml(rectangle).is_ok());
163        assert!(preset_shape_definition("notAStandardPreset").is_none());
164    }
165
166    #[test]
167    fn drawingml_namespace_uris_match_the_specification() {
168        assert_eq!(
169            A_NS,
170            "http://schemas.openxmlformats.org/drawingml/2006/main"
171        );
172        assert_eq!(A_PREFIX, "a");
173        assert_eq!(
174            PIC_NS,
175            "http://schemas.openxmlformats.org/drawingml/2006/picture"
176        );
177        assert_eq!(PIC_PREFIX, "pic");
178    }
179
180    #[test]
181    fn oxml_drawing_is_an_explicit_publication_candidate() {
182        let manifest = include_str!("../Cargo.toml");
183        assert!(manifest.contains("version = \"0.2.0\""));
184        assert!(manifest.contains("publish = true"));
185    }
186}