document_svg/document/
vdx.rs1use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12use quick_xml::Reader;
13use quick_xml::Writer;
14use quick_xml::events::{BytesEnd, BytesStart, Event};
15
16use crate::convert::{ConvertOptions, PageConsumer};
17use crate::document::vsdx;
18use crate::error::{Error, Result};
19use crate::ooxml::{attribute, local_name};
20
21const MAX_VDX_PAGE_BYTES: usize = 32 * 1024 * 1024;
22const MAX_VDX_PAGES: usize = 10_000;
23const MAX_VDX_DEPTH: usize = 256;
24
25#[derive(Debug)]
26struct VdxPage {
27 id: String,
28 name: String,
29 is_background: bool,
30 references_background: bool,
31}
32
33pub(crate) fn convert(
34 path: &Path,
35 options: &ConvertOptions,
36 sink: &mut dyn PageConsumer,
37) -> Result<Vec<String>> {
38 let max_bytes = options.max_input_bytes.min(512 * 1024 * 1024);
39 let mut bytes = Vec::new();
40 Read::take(File::open(path)?, max_bytes.saturating_add(1)).read_to_end(&mut bytes)?;
41 if bytes.len() as u64 > max_bytes {
42 return Err(Error::LimitExceeded(format!(
43 "VDX input exceeds maximum limit of {max_bytes} bytes"
44 )));
45 }
46
47 let mut reader = Reader::from_reader(bytes.as_slice());
48 reader.config_mut().trim_text(false);
49 let mut buffer = Vec::new();
50 let mut root_namespaces = Vec::<(String, String)>::new();
51 let mut root_seen = false;
52 let mut depth = 0usize;
53 let mut events = 0usize;
54 let mut page_entry_count = 0usize;
55 let mut rendered_page_count = 0usize;
56 let mut writer: Option<Writer<Vec<u8>>> = None;
57 let mut page: Option<VdxPage> = None;
58 let mut page_depth = 0usize;
59 let mut warnings = Vec::new();
60 let mut saw_background = false;
61
62 loop {
63 events += 1;
64 if events > options.max_xml_events {
65 return Err(Error::LimitExceeded(format!(
66 "VDX document exceeds {} XML events",
67 options.max_xml_events
68 )));
69 }
70 let event = reader.read_event_into(&mut buffer)?;
71 match event {
72 Event::Start(start) => {
73 let name = local_name(start.name().as_ref()).to_vec();
74 if !root_seen {
75 if name.as_slice() != b"VisioDocument" {
76 return Err(Error::InvalidInput(format!(
77 "VDX root is <{}>; expected <VisioDocument>",
78 String::from_utf8_lossy(&name)
79 )));
80 }
81 root_seen = true;
82 root_namespaces = namespace_attributes(&start);
83 }
84 depth += 1;
85 if depth > MAX_VDX_DEPTH {
86 return Err(Error::LimitExceeded(format!(
87 "VDX document exceeds XML nesting limit {MAX_VDX_DEPTH}"
88 )));
89 }
90 if writer.is_none() && name.as_slice() == b"Page" {
91 page_entry_count += 1;
92 if page_entry_count > MAX_VDX_PAGES {
93 return Err(Error::LimitExceeded(format!(
94 "VDX document exceeds {MAX_VDX_PAGES} pages"
95 )));
96 }
97 let parsed_page = page_from_start(&start);
98 saw_background |=
99 parsed_page.is_background || parsed_page.references_background;
100 writer = Some(start_page(&root_namespaces)?);
101 writer
102 .as_mut()
103 .expect("writer was initialized")
104 .write_event(Event::Start(start.into_owned()))?;
105 page = Some(parsed_page);
106 page_depth = 1;
107 } else if let Some(page_writer) = writer.as_mut() {
108 page_writer.write_event(Event::Start(start.into_owned()))?;
109 page_depth += 1;
110 if page_writer.get_ref().len() > MAX_VDX_PAGE_BYTES {
111 return Err(Error::LimitExceeded(format!(
112 "VDX page exceeds {MAX_VDX_PAGE_BYTES} bytes"
113 )));
114 }
115 }
116 }
117 Event::Empty(start) => {
118 let name = local_name(start.name().as_ref()).to_vec();
119 if !root_seen && name.as_slice() == b"VisioDocument" {
120 root_seen = true;
121 root_namespaces = namespace_attributes(&start);
122 }
123 if let Some(page_writer) = writer.as_mut() {
124 page_writer.write_event(Event::Empty(start.into_owned()))?;
125 if page_writer.get_ref().len() > MAX_VDX_PAGE_BYTES {
126 return Err(Error::LimitExceeded(format!(
127 "VDX page exceeds {MAX_VDX_PAGE_BYTES} bytes"
128 )));
129 }
130 } else if name.as_slice() == b"Page" {
131 page_entry_count += 1;
132 if page_entry_count > MAX_VDX_PAGES {
133 return Err(Error::LimitExceeded(format!(
134 "VDX document exceeds {MAX_VDX_PAGES} pages"
135 )));
136 }
137 let page_meta = page_from_start(&start);
138 saw_background |= page_meta.is_background || page_meta.references_background;
139 let mut page_writer = start_page(&root_namespaces)?;
140 page_writer.write_event(Event::Empty(start.into_owned()))?;
141 let page_xml = finish_page(page_writer)?;
142 render_vdx_page(
143 &page_xml,
144 page_meta,
145 options,
146 sink,
147 &mut rendered_page_count,
148 &mut warnings,
149 )?;
150 }
151 }
152 Event::End(end) => {
153 depth = depth.saturating_sub(1);
154 if let Some(page_writer) = writer.as_mut() {
155 let is_page_end = local_name(end.name().as_ref()) == b"Page" && page_depth == 1;
156 page_writer.write_event(Event::End(end.to_owned()))?;
157 page_depth = page_depth.saturating_sub(1);
158 if page_writer.get_ref().len() > MAX_VDX_PAGE_BYTES {
159 return Err(Error::LimitExceeded(format!(
160 "VDX page exceeds {MAX_VDX_PAGE_BYTES} bytes"
161 )));
162 }
163 if is_page_end {
164 let page_writer = writer.take().expect("page writer exists");
165 let page_xml = finish_page(page_writer)?;
166 let page_meta = page.take().expect("page metadata exists");
167 render_vdx_page(
168 &page_xml,
169 page_meta,
170 options,
171 sink,
172 &mut rendered_page_count,
173 &mut warnings,
174 )?;
175 page_depth = 0;
176 }
177 }
178 }
179 Event::DocType(_) => {
180 return Err(Error::InvalidInput(
181 "VDX document type declarations are not supported".into(),
182 ));
183 }
184 Event::Eof => break,
185 other => {
186 if let Some(page_writer) = writer.as_mut() {
187 page_writer.write_event(other.into_owned())?;
188 if page_writer.get_ref().len() > MAX_VDX_PAGE_BYTES {
189 return Err(Error::LimitExceeded(format!(
190 "VDX page exceeds {MAX_VDX_PAGE_BYTES} bytes"
191 )));
192 }
193 }
194 }
195 }
196 buffer.clear();
197 }
198 if !root_seen {
199 return Err(Error::InvalidInput("VDX document is empty".into()));
200 }
201 if writer.is_some() || page.is_some() {
202 return Err(Error::InvalidInput(
203 "VDX document ended inside a Page entry".into(),
204 ));
205 }
206 if rendered_page_count == 0 {
207 return Err(Error::InvalidInput(
208 "VDX document contains no foreground drawing pages".into(),
209 ));
210 }
211 if saw_background {
212 warnings
213 .push("Visio background pages are not rendered or applied to foreground pages".into());
214 }
215 Ok(warnings)
216}
217
218fn render_vdx_page(
219 page_xml: &[u8],
220 page_meta: VdxPage,
221 options: &ConvertOptions,
222 sink: &mut dyn PageConsumer,
223 rendered_page_count: &mut usize,
224 warnings: &mut Vec<String>,
225) -> Result<()> {
226 if page_meta.is_background {
227 return Ok(());
228 }
229 if *rendered_page_count >= options.max_pages.min(MAX_VDX_PAGES) {
230 return Err(Error::LimitExceeded(format!(
231 "VDX document exceeds {} output pages",
232 options.max_pages.min(MAX_VDX_PAGES)
233 )));
234 }
235 *rendered_page_count += 1;
236 let parsed = vsdx::parse_page(page_xml, options.max_xml_events, "VDX Page")?;
237 let mut output =
238 vsdx::render_page(parsed, *rendered_page_count, &page_meta.name, &page_meta.id);
239 if page_meta.name.is_empty() {
240 output.warn(format!(
241 "VDX page {} has no name; a generic title is used",
242 rendered_page_count
243 ));
244 }
245 if output.nodes.is_empty() {
246 output.warn("Visio page contains no supported visible shapes");
247 }
248 for warning in &output.warnings {
249 if !warnings.contains(warning) {
250 warnings.push(warning.clone());
251 }
252 }
253 sink.consume(output)
254}
255
256fn start_page(namespaces: &[(String, String)]) -> Result<Writer<Vec<u8>>> {
257 let mut writer = Writer::new(Vec::new());
258 let mut wrapper = BytesStart::new("PageContents");
259 for (name, value) in namespaces {
260 wrapper.push_attribute((name.as_str(), value.as_str()));
261 }
262 writer.write_event(Event::Start(wrapper))?;
263 Ok(writer)
264}
265
266fn finish_page(mut writer: Writer<Vec<u8>>) -> Result<Vec<u8>> {
267 writer.write_event(Event::End(BytesEnd::new("PageContents")))?;
268 Ok(writer.into_inner())
269}
270
271fn namespace_attributes(start: &BytesStart<'_>) -> Vec<(String, String)> {
272 start
273 .attributes()
274 .with_checks(false)
275 .flatten()
276 .filter_map(|attribute| {
277 let name = std::str::from_utf8(attribute.key.as_ref()).ok()?;
278 (name == "xmlns" || name.starts_with("xmlns:") || name == "xml:space").then(|| {
279 (
280 name.to_owned(),
281 String::from_utf8_lossy(attribute.value.as_ref()).into_owned(),
282 )
283 })
284 })
285 .collect()
286}
287
288fn page_from_start(start: &BytesStart<'_>) -> VdxPage {
289 let back_page = attribute(start, b"BackPage").unwrap_or_default();
290 VdxPage {
291 id: attribute(start, b"ID").unwrap_or_default(),
292 name: attribute(start, b"NameU")
293 .filter(|value| !value.is_empty())
294 .or_else(|| attribute(start, b"Name"))
295 .unwrap_or_default(),
296 is_background: attribute(start, b"IsBackground")
297 .is_some_and(|value| value == "1" || value.eq_ignore_ascii_case("true")),
298 references_background: !back_page.is_empty() && back_page != "0",
299 }
300}