From 87216174bb76d039d12ab8de4b63547477c54013 Mon Sep 17 00:00:00 2001 From: nilsk Date: Mon, 30 Jun 2025 22:06:41 +0200 Subject: [PATCH 1/8] added flag to example for debug purposes --- examples/basic_usage.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/examples/basic_usage.rs b/examples/basic_usage.rs index b749e9d..b695c16 100644 --- a/examples/basic_usage.rs +++ b/examples/basic_usage.rs @@ -2,7 +2,7 @@ //! //! This example demonstrates how to open a PPTX file and convert all slides to Markdown. //! -//! Run with: cargo run --example basic_usage +//! Run with: cargo run --example basic_usage use pptx_to_md::{PptxContainer, Result, ParserConfig, ImageHandlingMode}; use std::env; @@ -16,15 +16,22 @@ fn main() -> Result<()> { let pptx_path = if args.len() > 1 { &args[1] } else { - eprintln!("Usage: cargo run --example basic_usage "); + eprintln!("Usage: cargo run --example basic_usage \ncargo run --example basic_usage sample.pptx true"); return Ok(()); }; - + + // Tries to read if the extract_images flag is false else set to true + let extract_images = if args.len() > 2 { + !(args[2] == "false" || args[2] == "False" || args[2] == "0") + } else { + true + }; + println!("Processing PPTX file: {}", pptx_path); // Use the config builder to build your config let config = ParserConfig::builder() - .extract_images(true) + .extract_images(extract_images) .compress_images(true) .quality(75) .image_handling_mode(ImageHandlingMode::InMarkdown) From d1c4c9fd22bab857dee0d24e6fcd01f7abbdc045 Mon Sep 17 00:00:00 2001 From: nilsk Date: Mon, 30 Jun 2025 22:07:00 +0200 Subject: [PATCH 2/8] recursively parsing groups and group element --- src/parse_xml.rs | 57 +++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/parse_xml.rs b/src/parse_xml.rs index 52440ba..1f5da9b 100644 --- a/src/parse_xml.rs +++ b/src/parse_xml.rs @@ -49,27 +49,44 @@ pub fn parse_slide_xml(xml_data: &[u8]) -> Result> { let mut elements = Vec::new(); for child_node in sp_tree.children().filter(|n| n.is_element()) { - let tag_name = child_node.tag_name().name(); - let namespace = child_node.tag_name().namespace().unwrap_or(""); - if namespace == P_NAMESPACE { - match tag_name { - "sp" => { - let slide = parse_sp(&child_node)?; - elements.push(slide); - }, - "graphicFrame" => { - if let Some(element) = parse_graphic_frame(&child_node)? { - elements.push(element); - } - }, - "pic" => { - let image_element = parse_pic(&child_node)?; - elements.push(image_element); - }, - _ => { - elements.push(Unknown) - } + elements.extend(parse_group(&child_node)?); + } + + Ok(elements) +} + +/// Parst eine gesamte Gruppe und alle untergeordneten Kind-Elemente rekursiv +fn parse_group(node: &Node) -> Result> { + let mut elements = Vec::new(); + + let tag_name = node.tag_name().name(); + let namespace = node.tag_name().namespace().unwrap_or(""); + + if namespace != P_NAMESPACE { + return Ok(elements); + } + + match tag_name { + "sp" => { + let slide_element = parse_sp(node)?; + elements.push(slide_element); + }, + "graphicFrame" => { + if let Some(graphic_element) = parse_graphic_frame(node)? { + elements.push(graphic_element); + } + }, + "pic" => { + let image_element = parse_pic(node)?; + elements.push(image_element); + }, + "grpSp" => { + for child in node.children().filter(|n| n.is_element()) { + elements.extend(parse_group(&child)?); } + }, + _ => { + elements.push(SlideElement::Unknown) } } From c8019c1df8af4ef8b5e7da32c3e0e126c4aaf251 Mon Sep 17 00:00:00 2001 From: nilsk Date: Tue, 1 Jul 2025 00:07:13 +0200 Subject: [PATCH 3/8] changed return types to specific not generic slide elements --- src/parse_xml.rs | 91 ++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/src/parse_xml.rs b/src/parse_xml.rs index 1f5da9b..acb47b9 100644 --- a/src/parse_xml.rs +++ b/src/parse_xml.rs @@ -1,9 +1,12 @@ use crate::constants::{A_NAMESPACE, P_NAMESPACE, RELS_NAMESPACE}; use crate::types::{SlideElement, TableCell, TableElement, TableRow, TextElement}; -use crate::SlideElement::Unknown; -use crate::{Error, Formatting, ImageReference, ListElement, ListItem, Result, Run}; +use crate::{ElementPosition, Error, Formatting, ImageReference, ListElement, ListItem, Result, Run}; use roxmltree::{Document, Node}; +enum ParsedContent { + Text(TextElement), + List(ListElement), +} /// Parses raw XML slide data from a PowerPoint (pptx) file and extracts all slide elements. /// @@ -66,28 +69,31 @@ fn parse_group(node: &Node) -> Result> { return Ok(elements); } + let position = extract_position(node); + match tag_name { "sp" => { - let slide_element = parse_sp(node)?; - elements.push(slide_element); + let position = extract_position(node); + match parse_sp(node)? { + ParsedContent::Text(text) => elements.push(SlideElement::Text(text, position)), + ParsedContent::List(list) => elements.push(SlideElement::List(list, position)), + } }, "graphicFrame" => { - if let Some(graphic_element) = parse_graphic_frame(node)? { - elements.push(graphic_element); + if let Some(graphic_element) = parse_graphic_frame(&node)? { + elements.push(SlideElement::Table(graphic_element, position)); } }, "pic" => { - let image_element = parse_pic(node)?; - elements.push(image_element); + let image_reference = parse_pic(&node)?; + elements.push(SlideElement::Image(image_reference, position)); }, "grpSp" => { for child in node.children().filter(|n| n.is_element()) { elements.extend(parse_group(&child)?); } }, - _ => { - elements.push(SlideElement::Unknown) - } + _ => elements.push(SlideElement::Unknown), } Ok(elements) @@ -95,12 +101,10 @@ fn parse_group(node: &Node) -> Result> { /// Parses the text body node (``) ito search for shape nodes (``) and /// evaluates if a shape is a formatted list or a common text -fn parse_sp(sp_node: &Node) -> Result { - let tx_body_node = sp_node.children().find(|n| { - n.is_element() - && n.tag_name().name() == "txBody" - && n.tag_name().namespace() == Some(P_NAMESPACE) - }).ok_or(Error::Unknown)?; +fn parse_sp(sp_node: &Node) -> Result { + let tx_body_node = sp_node.children() + .find(|n| n.tag_name().name() == "txBody" && n.tag_name().namespace() == Some(P_NAMESPACE)) + .ok_or(Error::Unknown)?; let is_list = tx_body_node.descendants().any(|n| { n.is_element() @@ -118,9 +122,9 @@ fn parse_sp(sp_node: &Node) -> Result { }); if is_list { - parse_list(&tx_body_node) + Ok(ParsedContent::List(parse_list(&tx_body_node)?)) } else { - parse_text(&tx_body_node) + Ok(ParsedContent::Text(parse_text(&tx_body_node)?)) } } @@ -129,7 +133,7 @@ fn parse_sp(sp_node: &Node) -> Result { /// Returns a `Result` containing either: /// - `SlideElement::Text`: A text element containing all text runs /// - `Error`: Error information encapsulated in [`crate::Error`] if parsing fails at XML parsing level. -fn parse_text(tx_body_node: &Node) -> Result { +fn parse_text(tx_body_node: &Node) -> Result { let mut runs = Vec::new(); for p_node in tx_body_node.children().filter(|n| { @@ -141,10 +145,10 @@ fn parse_text(tx_body_node: &Node) -> Result { runs.append(&mut paragraph_runs); } - Ok(SlideElement::Text(TextElement { runs })) + Ok(TextElement { runs }) } -fn parse_graphic_frame(node: &Node) -> Result> { +fn parse_graphic_frame(node: &Node) -> Result> { let graphic_data_node = node .descendants() .find(|n| { @@ -160,7 +164,7 @@ fn parse_graphic_frame(node: &Node) -> Result> { .find(|n| n.is_element() && n.tag_name().name() == "tbl" && n.tag_name().namespace() == Some(A_NAMESPACE)) { let table = parse_table(&tbl_node)?; - return Ok(Some(SlideElement::Table(table))); + return Ok(Some(table)); } } @@ -235,7 +239,7 @@ fn parse_table_cell(tc_node: &Node) -> Result { /// Returns a `Result` with: /// - `SlideElement::Image`: A `SlideElement` containing the image's reference `ID` to link it if successfully parsed. /// - `Error::ImageNotFound`: If the `` element or necessary attributes are missing. -fn parse_pic(pic_node: &Node) -> Result { +fn parse_pic(pic_node: &Node) -> Result { let blip_node = pic_node .descendants() .find(|n| n.is_element() && n.tag_name().name() == "blip" && n.tag_name().namespace() == Some(A_NAMESPACE)) @@ -250,7 +254,7 @@ fn parse_pic(pic_node: &Node) -> Result { target: String::new(), }; - Ok(SlideElement::Image(image_ref)) + Ok(image_ref) } /// Parses the paragraph node (``) that is already identified as a list from the text body node (``) @@ -259,7 +263,7 @@ fn parse_pic(pic_node: &Node) -> Result { /// # Returns /// - `SlideElement::List`: A complete lists with all children of type `ListElement` /// - `Error`: Error information encapsulated in [`crate::Error`] if parsing fails at XML parsing level. -fn parse_list(tx_body_node: &Node) -> Result { +fn parse_list(tx_body_node: &Node) -> Result { let mut items = Vec::new(); for p_node in tx_body_node.children().filter(|n| { @@ -274,7 +278,7 @@ fn parse_list(tx_body_node: &Node) -> Result { items.push(ListItem { level, is_ordered, runs }); } - Ok(SlideElement::List(ListElement { items })) + Ok(ListElement { items }) } /// Extracts list properties from a paragraph node (```). @@ -377,11 +381,32 @@ fn parse_run(r_node: &Node) -> Result { Ok(Run { text, formatting }) } +fn extract_position(node: &Node) -> ElementPosition { + let default = ElementPosition::default(); + + node.descendants() + .find(|n| n.tag_name().namespace() == Some(A_NAMESPACE) && n.tag_name().name() == "xfrm") + .and_then(|xfrm| { + let x = xfrm + .children() + .find(|n| n.tag_name().name() == "off" && n.tag_name().namespace() == Some(A_NAMESPACE)) + .and_then(|off| off.attribute("x")?.parse::().ok())?; + + let y = xfrm + .children() + .find(|n| n.tag_name().name() == "off" && n.tag_name().namespace() == Some(A_NAMESPACE)) + .and_then(|off| off.attribute("y")?.parse::().ok())?; + + Some(ElementPosition { x, y }) + }) + .unwrap_or(default) +} + #[cfg(test)] mod tests { + use super::*; use std::fs; use std::path::PathBuf; - use super::*; fn load_xml(filename: &str) -> String { let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -408,7 +433,7 @@ mod tests { let tx_body_node = doc.root_element(); match parse_text(&tx_body_node) { - Ok(SlideElement::Text(text_element)) => { + Ok(text_element) => { assert_eq!(text_element.runs.len(), 3); assert_eq!(normalize_test_string(&text_element.runs[0].text), normalize_test_string("Hello")); assert_eq!(normalize_test_string(&text_element.runs[1].text), normalize_test_string("World")); @@ -603,7 +628,7 @@ mod tests { let tx_body_node = doc.root_element(); match parse_list(&tx_body_node) { - Ok(SlideElement::List(list)) => { + Ok(list) => { assert_eq!(list.items.len(), 3, "List should have 3 items"); // Check the first item @@ -634,7 +659,7 @@ mod tests { let tx_body_node = doc.root_element(); match parse_list(&tx_body_node) { - Ok(SlideElement::List(list)) => { + Ok(list) => { assert_eq!(list.items.len(), 5, "List should have 5 items"); // Check first item (level 0, ordered) @@ -870,7 +895,7 @@ mod tests { let node = doc.root_element(); match parse_graphic_frame(&node) { - Ok(Some(SlideElement::Table(table))) => { + Ok(Some(table)) => { assert_eq!(table.rows.len(), 2, "Table should have 2 rows"); assert_eq!(table.rows[0].cells.len(), 2, "First row should have 2 cells"); @@ -907,7 +932,7 @@ mod tests { let pic_node = doc.root_element(); match parse_pic(&pic_node) { - Ok(SlideElement::Image(image_ref)) => { + Ok(image_ref) => { assert_eq!(image_ref.id, "rId2", "Image reference ID should be 'rId2'"); assert_eq!(image_ref.target, "", "Image target should be empty initially"); }, From 033d7e564570ff404e540e091bfb707df34ee105 Mon Sep 17 00:00:00 2001 From: nilsk Date: Tue, 1 Jul 2025 00:08:49 +0200 Subject: [PATCH 4/8] inlcludes logging of positional data in example --- examples/slide_elements.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/slide_elements.rs b/examples/slide_elements.rs index a60db58..8ef32f2 100644 --- a/examples/slide_elements.rs +++ b/examples/slide_elements.rs @@ -38,10 +38,10 @@ fn main() -> Result<()> { // iterate over each slide element and match them to add custom logic for element in &slide.elements { match element { - SlideElement::Text(text) => { println!("{:?}\n", text) } - SlideElement::Table(table) => { println!("{:?}\n", table) } - SlideElement::Image(image) => { println!("{:?}\n", image) } - SlideElement::List(list) => { println!("{:?}\n", list) } + SlideElement::Text(text, pos) => { println!("{:?}\t{:?}\n", text, pos) } + SlideElement::Table(table, pos) => { println!("{:?}\t{:?}\n", table, pos) } + SlideElement::Image(image, pos) => { println!("{:?}\t{:?}\n", image, pos) } + SlideElement::List(list, pos) => { println!("{:?}\t{:?}\n", list, pos) } SlideElement::Unknown => { println!("An Unknown element was found.\n") } } } From a832dc374d64ca4557b99d39c6bb225ef9aaaf2f Mon Sep 17 00:00:00 2001 From: nilsk Date: Tue, 1 Jul 2025 00:09:20 +0200 Subject: [PATCH 5/8] adding position as parameter to test function --- tests/slide_tests.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/slide_tests.rs b/tests/slide_tests.rs index b870ce2..be7ee06 100644 --- a/tests/slide_tests.rs +++ b/tests/slide_tests.rs @@ -1,4 +1,4 @@ -use pptx_to_md::{Formatting, ListElement, ListItem, ParserConfig, Run, Slide, SlideElement, TableCell, TableElement, TableRow, TextElement}; +use pptx_to_md::{ElementPosition, Formatting, ListElement, ListItem, ParserConfig, Run, Slide, SlideElement, TableCell, TableElement, TableRow, TextElement}; use std::collections::HashMap; use std::fs; use std::path::PathBuf; @@ -39,7 +39,7 @@ fn test_markdown_table_conversion() { TableCell { runs: vec![Run { text: "21".into(), formatting: Formatting::default() }]}, ]}, ] - }) + }, ElementPosition::default()) ], images: vec![], image_data: HashMap::new(), @@ -68,7 +68,7 @@ fn test_markdown_list_conversion() { ListItem { level:1, is_ordered:false, runs: vec![Run{text: "Layer 2 Element 2".into(), formatting: Formatting::default()}]}, ListItem { level:0, is_ordered:false, runs: vec![Run{text: "Layer 1 Element 2".into(), formatting: Formatting::default()}]}, ] - }) + }, ElementPosition::default()) ], images: vec![], image_data: HashMap::new(), @@ -90,11 +90,11 @@ fn test_formatting_conversion() { rel_path: "ppt/slides/slide1.xml".to_string(), slide_number: 1, elements: vec![ - SlideElement::Text(TextElement { runs: vec![Run { text: "bold\n".into(), formatting: Formatting { bold: true, italic: false, underlined: false, lang: "en-US".into() } }]}), - SlideElement::Text(TextElement { runs: vec![Run { text: "cursive\n".into(), formatting: Formatting { bold: false, italic: true, underlined: false, lang: "en-US".into() } }]}), - SlideElement::Text(TextElement { runs: vec![Run { text: "underlined\n".into(), formatting: Formatting { bold: false, italic: false, underlined: true, lang: "en-US".into() } }]}), - SlideElement::Text(TextElement { runs: vec![Run { text: "bold and cursive\n".into(), formatting: Formatting { bold: true, italic: true, underlined: false, lang: "en-US".into() } }]}), - SlideElement::Text(TextElement { runs: vec![Run { text: "bold, cursive and underlined\n".into(), formatting: Formatting { bold: true, italic: true, underlined: true, lang: "en-US".into() } }]}), + SlideElement::Text(TextElement { runs: vec![Run { text: "bold\n".into(), formatting: Formatting { bold: true, italic: false, underlined: false, lang: "en-US".into() } }]}, ElementPosition::default()), + SlideElement::Text(TextElement { runs: vec![Run { text: "cursive\n".into(), formatting: Formatting { bold: false, italic: true, underlined: false, lang: "en-US".into() } }]}, ElementPosition::default()), + SlideElement::Text(TextElement { runs: vec![Run { text: "underlined\n".into(), formatting: Formatting { bold: false, italic: false, underlined: true, lang: "en-US".into() } }]}, ElementPosition::default()), + SlideElement::Text(TextElement { runs: vec![Run { text: "bold and cursive\n".into(), formatting: Formatting { bold: true, italic: true, underlined: false, lang: "en-US".into() } }]}, ElementPosition::default()), + SlideElement::Text(TextElement { runs: vec![Run { text: "bold, cursive and underlined\n".into(), formatting: Formatting { bold: true, italic: true, underlined: true, lang: "en-US".into() } }]}, ElementPosition::default()), ], images: vec![], image_data: HashMap::new(), From 20cd0350ced163115a075ad67f319fc319601a66 Mon Sep 17 00:00:00 2001 From: nilsk Date: Tue, 1 Jul 2025 00:10:43 +0200 Subject: [PATCH 6/8] sorting elements by position --- src/slide.rs | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/slide.rs b/src/slide.rs index c79f166..7eabc76 100644 --- a/src/slide.rs +++ b/src/slide.rs @@ -1,5 +1,5 @@ use crate::parser_config::ImageHandlingMode; -use crate::{ImageReference, ParserConfig, SlideElement}; +use crate::{ElementPosition, ImageReference, ParserConfig, SlideElement}; use base64::{engine::general_purpose, Engine as _}; use image::ImageOutputFormat; use std::collections::HashMap; @@ -75,16 +75,22 @@ impl Slide { let mut slide_txt = String::new(); if self.config.include_slide_comment { slide_txt.push_str(format!("\n\n", self.slide_number).as_str()); } let mut image_count = 0; + + let mut sorted_elements = self.elements.clone(); + sorted_elements.sort_by_key(|element| { + let ElementPosition { y, x } = element.position(); + (y, x) + }); - for element in &self.elements { + for element in sorted_elements { match element { - SlideElement::Text(text) => { + SlideElement::Text(text, _pos) => { for run in &text.runs { slide_txt.push_str(&run.render_as_md()); } slide_txt.push('\n'); }, - SlideElement::Table(table) => { + SlideElement::Table(table, _pos) => { let mut is_header = true; for row in &table.rows { let mut row_texts = Vec::new(); @@ -109,7 +115,7 @@ impl Slide { } slide_txt.push('\n'); }, - SlideElement::Image(image_ref) => { + SlideElement::Image(image_ref, _pos) => { match self.config.image_handling_mode { ImageHandlingMode::InMarkdown => { if let Some(image_data) = self.image_data.get(&image_ref.id) { @@ -158,7 +164,7 @@ impl Slide { } slide_txt.push('\n'); } - SlideElement::List(list_element) => { + SlideElement::List(list_element, _pos) => { let mut counters: Vec = Vec::new(); let mut previous_level = 0; @@ -235,7 +241,7 @@ impl Slide { .collect(); for element in &mut self.elements { - if let SlideElement::Image(ref mut img_ref) = element { + if let SlideElement::Image(ref mut img_ref, _pos) = element { if let Some(target) = id_to_target.get(&img_ref.id) { img_ref.target = target.clone(); } @@ -287,7 +293,7 @@ impl Slide { let image_refs: Vec<&ImageReference> = self.elements .iter() .filter_map(|element| match element { - SlideElement::Image(ref img) => Some(img), + SlideElement::Image(ref img, _pos) => Some(img), _ => None, }) .collect(); @@ -331,7 +337,7 @@ impl Slide { mod tests { use std::fs; use std::path::PathBuf; - + use crate::ElementPosition; use super::*; fn mock_slide() -> Slide { @@ -377,13 +383,14 @@ mod tests { #[test] fn test_link_images() { let mut slide = mock_slide(); - + let _position = ElementPosition::default(); + slide.images.push(ImageReference { id: "rId2".to_string(), target: "../media/image1.png".to_string() }); - slide.elements.push(SlideElement::Image(ImageReference { id: "rId2".to_string(), target: "".to_string() })); + slide.elements.push(SlideElement::Image(ImageReference { id: "rId2".to_string(), target: "".to_string() }, _position)); slide.link_images(); - if let SlideElement::Image(img_ref) = &slide.elements[0] { + if let SlideElement::Image(img_ref, _postion) = &slide.elements[0] { assert_eq!(img_ref.target, "../media/image1.png"); } } From 01a5728e87706fdcd879e5f5119aa4c48539a321 Mon Sep 17 00:00:00 2001 From: nilsk Date: Tue, 1 Jul 2025 00:11:14 +0200 Subject: [PATCH 7/8] removed not reachable pattern --- src/parse_xml.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/parse_xml.rs b/src/parse_xml.rs index acb47b9..482c0ef 100644 --- a/src/parse_xml.rs +++ b/src/parse_xml.rs @@ -440,7 +440,6 @@ mod tests { assert_eq!(normalize_test_string(&text_element.runs[2].text), normalize_test_string("!")); }, Err(_) => panic!("Fehler beim Parsen der XML-Datei"), - _ => {} } } @@ -646,7 +645,6 @@ mod tests { assert!(list.items[2].is_ordered, "Third item should be ordered (has buChar)"); assert_eq!(normalize_test_string(&list.items[2].runs[0].text), normalize_test_string("Third item\n"), "Third item text mismatch"); }, - Ok(_) => panic!("Expected a List element but got something else"), Err(_) => panic!("Failed to parse simple list") } } @@ -682,7 +680,6 @@ mod tests { assert!(list.items[4].is_ordered, "Fifth item should be ordered"); assert_eq!(normalize_test_string(&list.items[4].runs[0].text), normalize_test_string("Second main topic\n"), "Fifth item text mismatch"); }, - Ok(_) => panic!("Expected a List element but got something else"), Err(_) => panic!("Failed to parse multilevel list") } } @@ -903,7 +900,6 @@ mod tests { assert_eq!(normalize_test_string(&table.rows[0].cells[0].runs[0].text), normalize_test_string("Cell 1,1"), "Cell content mismatch"); }, Ok(None) => panic!("Should have found a table, but got None"), - Ok(_) => panic!("Found a different slide element, expected a table"), Err(_) => panic!("Failed to parse graphic frame with table") } } @@ -936,7 +932,6 @@ mod tests { assert_eq!(image_ref.id, "rId2", "Image reference ID should be 'rId2'"); assert_eq!(image_ref.target, "", "Image target should be empty initially"); }, - Ok(_) => panic!("Expected an Image element but got something else"), Err(e) => panic!("Failed to parse picture: {:?}", e) } } From d48aefe4edbf47ea49b62c8e05368e3476bb0df2 Mon Sep 17 00:00:00 2001 From: nilsk Date: Tue, 1 Jul 2025 00:11:30 +0200 Subject: [PATCH 8/8] added clone trait to types --- src/types.rs | 44 +++++++++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/types.rs b/src/types.rs index cc90445..1aeaea4 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,27 +8,39 @@ pub struct Slide { pub elements: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum SlideElement { - Text(TextElement), - Table(TableElement), - Image(ImageReference), - List(ListElement), + Text(TextElement, ElementPosition), + Table(TableElement, ElementPosition), + Image(ImageReference, ElementPosition), + List(ListElement, ElementPosition), Unknown, } +impl SlideElement { + pub fn position(&self) -> ElementPosition { + match self { + SlideElement::Text(_, pos) + | SlideElement::Image(_, pos) + | SlideElement::List(_, pos) + | SlideElement::Table(_, pos) => *pos, + SlideElement::Unknown => ElementPosition::default(), + } + } +} + #[derive(Debug, Clone)] pub struct ImageReference { pub id: String, pub target: String, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TextElement { pub runs: Vec, } -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Formatting { pub bold: bool, pub italic: bool, @@ -36,7 +48,7 @@ pub struct Formatting { pub lang: String, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Run { pub text: String, pub formatting: Formatting, @@ -79,29 +91,35 @@ impl Run { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TableElement { pub rows: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TableRow { pub cells: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TableCell { pub runs: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ListElement { pub items: Vec, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct ListItem { pub level: u32, pub is_ordered: bool, pub runs: Vec, +} + +#[derive(Debug, Clone, Copy, Default, PartialEq)] +pub struct ElementPosition { + pub x: i64, + pub y: i64, } \ No newline at end of file