Skip to main content

Slide

Struct Slide 

Source
pub struct Slide {
    pub rel_path: String,
    pub slide_number: u32,
    pub elements: Vec<SlideElement>,
    pub speaker_notes: Vec<TextElement>,
    pub comments: Vec<TextElement>,
    pub images: Vec<ImageReference>,
    pub image_data: HashMap<String, Vec<u8>>,
    pub config: ParserConfig,
    pub blocks: Vec<SlideBlock>,
    pub diagnostics: Vec<ParseDiagnostic>,
}
Expand description

Represents a single slide extracted from a PowerPoint (pptx) file.

Contains structured slide data including slide number, parsed content elements (text, tables, images, lists), speaker notes, and associated image references.

A Slide can be converted into other formats, such as Markdown, or its contained images can be extracted in base64 representation.

Typically, you retrieve instances of Slide through [PptxContainer::parse()].

Fields§

§rel_path: String§slide_number: u32§elements: Vec<SlideElement>§speaker_notes: Vec<TextElement>§comments: Vec<TextElement>§images: Vec<ImageReference>§image_data: HashMap<String, Vec<u8>>§config: ParserConfig§blocks: Vec<SlideBlock>§diagnostics: Vec<ParseDiagnostic>

Implementations§

Source§

impl Slide

Source

pub fn new( rel_path: String, slide_number: u32, elements: Vec<SlideElement>, speaker_notes: Vec<TextElement>, comments: Vec<TextElement>, images: Vec<ImageReference>, image_data: HashMap<String, Vec<u8>>, config: ParserConfig, ) -> Self

Source

pub fn new_semantic( rel_path: String, slide_number: u32, elements: Vec<SlideElement>, blocks: Vec<SlideBlock>, speaker_notes: Vec<TextElement>, comments: Vec<TextElement>, images: Vec<ImageReference>, image_data: HashMap<String, Vec<u8>>, config: ParserConfig, diagnostics: Vec<ParseDiagnostic>, ) -> Self

Source

pub fn convert_to_md(&self) -> Result<String>

Converts slide contents into a Markdown formatted string.

Translates internal slide elements (text, tables, lists, images) to valid and readable Markdown. Embedded images will be encoded as base64 inline images.

§Returns

Returns an Option<String>:

  • Some(String): Markdown representation of slide if conversion succeeds.
  • None: If a conversion error occurs during image encoding.
Examples found in repository?
examples/legacy_pptx_api.rs (line 28)
13fn main() -> Result<()> {
14    let args: Vec<String> = env::args().collect();
15    let Some(input_path) = args.get(1) else {
16        eprintln!("Usage: cargo run --example legacy_pptx_api <presentation.pptx> [output.md]");
17        return Ok(());
18    };
19    let output_path = args.get(2).map(String::as_str).unwrap_or("output.md");
20
21    // This is the pre-PresentationContainer flow: open a PPTX-specific
22    // container, parse every slide, and render each slide separately. It does
23    // not add the presentation-level metadata header.
24    let mut container = PptxContainer::open(Path::new(input_path), ParserConfig::default())?;
25    let markdown = container
26        .parse_all()?
27        .into_iter()
28        .map(|slide| slide.convert_to_md())
29        .collect::<Result<Vec<_>>>()?
30        .join("\n");
31    fs::write(output_path, markdown)?;
32
33    println!("Converted PPTX with the legacy entry point to {output_path}");
34    Ok(())
35}
More examples
Hide additional examples
examples/memory_efficient_streaming.rs (line 29)
11fn main() -> Result<()> {
12    let args: Vec<String> = env::args().collect();
13    let Some(input_path) = args.get(1) else {
14        eprintln!(
15            "Usage: cargo run --example memory_efficient_streaming <presentation.pptx|presentation.odp>"
16        );
17        return Ok(());
18    };
19
20    let mut presentation =
21        PresentationContainer::open(Path::new(input_path), ParserConfig::default())?;
22    let output_dir = "output_streaming";
23    fs::create_dir_all(output_dir)?;
24
25    // Unlike parse_document(), the iterator only retains the current slide.
26    for slide_result in presentation.iter_slides() {
27        let slide = slide_result?;
28        let output_path = format!("{output_dir}/slide_{}.md", slide.slide_number);
29        fs::write(&output_path, slide.convert_to_md()?)?;
30        println!(
31            "Saved slide {} ({} semantic blocks) to {output_path}",
32            slide.slide_number,
33            slide.blocks.len()
34        );
35    }
36
37    Ok(())
38}
examples/manual_image_extraction.rs (line 51)
14fn main() -> Result<()> {
15    let args: Vec<String> = env::args().collect();
16    let Some(input_path) = args.get(1) else {
17        eprintln!(
18            "Usage: cargo run --example manual_image_extraction <presentation.pptx|presentation.odp>"
19        );
20        return Ok(());
21    };
22
23    println!("Processing presentation: {input_path}");
24
25    // Use the config builder to build your config
26    let config = ParserConfig::builder()
27        .extract_images(true)
28        .compress_images(true)
29        .quality(75)
30        .image_handling_mode(ImageHandlingMode::Manually)
31        .build();
32
33    let mut container = PresentationContainer::open(Path::new(input_path), config)?;
34
35    // Parse all slides
36    let slides = container.parse_all()?;
37
38    println!("Found {} slides", slides.len());
39
40    // create a new Markdown file
41    let mut md_file = File::create("output.md")?;
42
43    // Create output directory
44    let output_dir = "extracted_images";
45    fs::create_dir_all(output_dir)?;
46
47    let mut image_count = 1;
48
49    // Convert each slide to Markdown and save
50    for slide in slides {
51        writeln!(md_file, "{}", slide.convert_to_md()?)?;
52
53        // Manually load the base64 encoded image strings from the slide
54        if let Some(images) = slide.load_images_manually() {
55            for image in images {
56                // Decode the base64 strings back to raw image data
57                let image_data = general_purpose::STANDARD
58                    .decode(image.base64_content.clone())
59                    .expect("parser returned invalid base64 image data");
60
61                // Extract image extension if the image is not compressed, otherwise its always `.jpg`
62                let ext = if slide.config.compress_images {
63                    "jpg".to_string()
64                } else {
65                    slide.get_image_extension(&image.img_ref.target.clone())
66                };
67
68                // Construct a unique file name
69                let file_name = format!(
70                    "slide{}_image{}_{}",
71                    slide.slide_number, image_count, &image.img_ref.id
72                );
73
74                // Save the image
75                let output_path = format!("{}/{}.{}", output_dir, &file_name, ext);
76                fs::write(&output_path, image_data)?;
77                println!("Saved image to {}", output_path);
78
79                // Write the image data into the Markdown file
80                writeln!(
81                    md_file,
82                    "![{}](data:image/{};base64,{})",
83                    file_name, ext, image.base64_content
84                )?;
85
86                image_count += 1;
87            }
88        }
89    }
90
91    println!("All slides converted successfully!");
92
93    Ok(())
94}
examples/performance_test.rs (line 117)
70fn main() -> Result<()> {
71    // Get the PPTX file path and optional iteration count from command line arguments
72    let args: Vec<String> = env::args().collect();
73    let pptx_path = if args.len() > 1 {
74        &args[1]
75    } else {
76        eprintln!(
77            "Usage: cargo run --example performance_test <presentation.pptx|presentation.odp> [iterations]"
78        );
79        return Ok(());
80    };
81
82    let iterations = if args.len() > 2 {
83        args[2].parse().unwrap_or(5)
84    } else {
85        10 // Default to 10 iterations
86    };
87
88    println!(
89        "Performance testing with {} iterations on: {}",
90        iterations, pptx_path
91    );
92
93    // =========== Single-threaded Approach ===========
94    let mut single_thread_bench = Benchmark::new("Single-threaded parsing");
95
96    let mut total_slides = 0;
97
98    for i in 0..iterations {
99        println!("\nIteration {} (Single-threaded)", i + 1);
100
101        // Measure container creation
102        let mut container = single_thread_bench.measure(|| {
103            let config = ParserConfig::builder().extract_images(true).build();
104            PresentationContainer::open(Path::new(pptx_path), config)
105                .expect("Failed to open presentation")
106        });
107
108        // Measure parsing
109        let slides =
110            single_thread_bench.measure(|| container.parse_all().expect("Failed to parse slides"));
111        println!("  Found {} slides in the presentation", slides.len());
112
113        // Measure conversion
114        let _md_content = single_thread_bench.measure(|| {
115            slides
116                .iter()
117                .filter_map(|slide| slide.convert_to_md().ok())
118                .collect::<Vec<String>>()
119        });
120
121        total_slides += slides.len();
122    }
123
124    single_thread_bench.report();
125    println!(
126        "Average slides per presentation: {}",
127        total_slides / iterations
128    );
129
130    // =========== Single-threaded Streamed Approach ===========
131    let mut single_thread_streamed_bench = Benchmark::new("Single-threaded streamed parsing");
132
133    total_slides = 0;
134
135    for i in 0..iterations {
136        println!("\nIteration {} (Single-threaded streamed)", i + 1);
137
138        // Measure container creation
139        let mut container = single_thread_streamed_bench.measure(|| {
140            let config = ParserConfig::builder().extract_images(true).build();
141            PresentationContainer::open(Path::new(pptx_path), config)
142                .expect("Failed to open presentation")
143        });
144
145        // Measure slide processing (including parsing and conversion)
146        let slides_processed = single_thread_streamed_bench.measure(|| {
147            let mut processed = 0;
148
149            // Process slides one by one using the iterator
150            for slide_result in container.iter_slides() {
151                match slide_result {
152                    Ok(slide) => {
153                        // Konvertiere den Slide zu Markdown
154                        let _md_content = slide.convert_to_md();
155                        processed += 1;
156                    }
157                    Err(e) => {
158                        eprintln!("Error processing slide: {:?}", e);
159                    }
160                }
161            }
162
163            processed
164        });
165
166        println!("  Processed {} slides", slides_processed);
167        total_slides += slides_processed;
168    }
169
170    single_thread_streamed_bench.report();
171    println!(
172        "Average slides per presentation: {}",
173        total_slides / iterations
174    );
175
176    // =========== Optimized Multi-threaded Approach ===========
177    let mut optimized_multi_thread_bench = Benchmark::new("Optimized Multi-threaded parsing");
178
179    total_slides = 0;
180
181    for i in 0..iterations {
182        println!("\nIteration {} (Optimized Multi-threaded)", i + 1);
183
184        // Container öffnen mit der gewünschten Konfiguration
185        let mut container = optimized_multi_thread_bench.measure(|| {
186            let config = ParserConfig::builder().extract_images(true).build();
187            PresentationContainer::open(Path::new(pptx_path), config)
188                .expect("Failed to open presentation")
189        });
190
191        let slides = optimized_multi_thread_bench.measure(|| {
192            container
193                .parse_all_multi_threaded()
194                .expect("Failed to parse slides")
195        });
196
197        println!("  Successfully processed {} slides", slides.len());
198
199        // Parallel zu Markdown konvertieren (bleibt unverändert)
200        let _md_content = optimized_multi_thread_bench.measure(|| {
201            slides
202                .par_iter()
203                .filter_map(|slide| slide.convert_to_md().ok())
204                .collect::<Vec<String>>()
205        });
206
207        total_slides += slides.len();
208    }
209
210    optimized_multi_thread_bench.report();
211    println!(
212        "Average slides per presentation: {}",
213        total_slides / iterations
214    );
215
216    // =========== Performance Comparison ===========
217    if !single_thread_bench.results.is_empty()
218        && !single_thread_streamed_bench.results.is_empty()
219        && !optimized_multi_thread_bench.results.is_empty()
220    {
221        let single_avg: Duration = single_thread_bench.results.iter().sum::<Duration>()
222            / single_thread_bench.results.len() as u32;
223        let single_streamed_avg: Duration = single_thread_streamed_bench
224            .results
225            .iter()
226            .sum::<Duration>()
227            / single_thread_streamed_bench.results.len() as u32;
228        let optimized_multi_avg: Duration = optimized_multi_thread_bench
229            .results
230            .iter()
231            .sum::<Duration>()
232            / optimized_multi_thread_bench.results.len() as u32;
233
234        println!("\nPerformance Comparison");
235        println!("=====================");
236        println!("Single-threaded average: {:?}", single_avg);
237        println!(
238            "Single-threaded streaming average: {:?}",
239            single_streamed_avg
240        );
241        println!(
242            "Optimized multi-threaded average: {:?}",
243            optimized_multi_avg
244        );
245
246        // Compare single-threaded vs single-threaded streaming
247        if single_avg > single_streamed_avg {
248            let speedup = single_avg.as_secs_f64() / single_streamed_avg.as_secs_f64();
249            println!(
250                "Single-threaded streaming is {:.2}x faster than single-threaded",
251                speedup
252            );
253        } else {
254            let slowdown = single_streamed_avg.as_secs_f64() / single_avg.as_secs_f64();
255            println!(
256                "Single-threaded streaming is {:.2}x slower than single-threaded",
257                slowdown
258            );
259        }
260
261        // Compare single-threaded vs optimized multithreaded
262        if single_avg > optimized_multi_avg {
263            let speedup = single_avg.as_secs_f64() / optimized_multi_avg.as_secs_f64();
264            println!(
265                "Optimized multi-threaded is {:.2}x faster than single-threaded",
266                speedup
267            );
268        } else {
269            let slowdown = optimized_multi_avg.as_secs_f64() / single_avg.as_secs_f64();
270            println!(
271                "Optimized multi-threaded is {:.2}x slower than single-threaded",
272                slowdown
273            );
274        }
275
276        // Compare single-threaded streaming vs optimized multithreaded
277        if single_streamed_avg > optimized_multi_avg {
278            let speedup = single_streamed_avg.as_secs_f64() / optimized_multi_avg.as_secs_f64();
279            println!(
280                "Optimized multi-threaded is {:.2}x faster than single-threaded streaming",
281                speedup
282            );
283        } else {
284            let slowdown = optimized_multi_avg.as_secs_f64() / single_streamed_avg.as_secs_f64();
285            println!(
286                "Optimized multi-threaded is {:.2}x slower than single-threaded streaming",
287                slowdown
288            );
289        }
290
291        // Determine the overall fastest approach
292        let fastest_approach = if single_avg <= single_streamed_avg
293            && single_avg <= optimized_multi_avg
294        {
295            "Single-threaded"
296        } else if single_streamed_avg <= single_avg && single_streamed_avg <= optimized_multi_avg {
297            "Single-threaded streaming"
298        } else {
299            "Optimized multi-threaded"
300        };
301
302        println!(
303            "\nOverall result: {} approach is the fastest for this workload.",
304            fastest_approach
305        );
306    }
307
308    Ok(())
309}
Source

pub fn to_markdown(&self, options: &MarkdownOptions) -> Result<String>

Source

pub fn extract_slide_number(path: &str) -> Option<u32>

Extracts the numeric slide identifier from a slide path.

Helper method to parse slide numbers from internal pptx slide paths (e.g., “ppt/slides/slide1.xml” → 1).

Links slide images references with their corresponding targets.

Ensures that each image referenced by its ID is correctly linked to the actual internal resource paths stored in the slide. This method is typically used internally after parsing a slide

§Notes

Internally those are the values image references are holding

ParameterExample value
idrId2
target../media/image2.png
Source

pub fn get_image_extension(&self, path: &str) -> String

Extracts the file extension from image paths

Examples found in repository?
examples/manual_image_extraction.rs (line 65)
14fn main() -> Result<()> {
15    let args: Vec<String> = env::args().collect();
16    let Some(input_path) = args.get(1) else {
17        eprintln!(
18            "Usage: cargo run --example manual_image_extraction <presentation.pptx|presentation.odp>"
19        );
20        return Ok(());
21    };
22
23    println!("Processing presentation: {input_path}");
24
25    // Use the config builder to build your config
26    let config = ParserConfig::builder()
27        .extract_images(true)
28        .compress_images(true)
29        .quality(75)
30        .image_handling_mode(ImageHandlingMode::Manually)
31        .build();
32
33    let mut container = PresentationContainer::open(Path::new(input_path), config)?;
34
35    // Parse all slides
36    let slides = container.parse_all()?;
37
38    println!("Found {} slides", slides.len());
39
40    // create a new Markdown file
41    let mut md_file = File::create("output.md")?;
42
43    // Create output directory
44    let output_dir = "extracted_images";
45    fs::create_dir_all(output_dir)?;
46
47    let mut image_count = 1;
48
49    // Convert each slide to Markdown and save
50    for slide in slides {
51        writeln!(md_file, "{}", slide.convert_to_md()?)?;
52
53        // Manually load the base64 encoded image strings from the slide
54        if let Some(images) = slide.load_images_manually() {
55            for image in images {
56                // Decode the base64 strings back to raw image data
57                let image_data = general_purpose::STANDARD
58                    .decode(image.base64_content.clone())
59                    .expect("parser returned invalid base64 image data");
60
61                // Extract image extension if the image is not compressed, otherwise its always `.jpg`
62                let ext = if slide.config.compress_images {
63                    "jpg".to_string()
64                } else {
65                    slide.get_image_extension(&image.img_ref.target.clone())
66                };
67
68                // Construct a unique file name
69                let file_name = format!(
70                    "slide{}_image{}_{}",
71                    slide.slide_number, image_count, &image.img_ref.id
72                );
73
74                // Save the image
75                let output_path = format!("{}/{}.{}", output_dir, &file_name, ext);
76                fs::write(&output_path, image_data)?;
77                println!("Saved image to {}", output_path);
78
79                // Write the image data into the Markdown file
80                writeln!(
81                    md_file,
82                    "![{}](data:image/{};base64,{})",
83                    file_name, ext, image.base64_content
84                )?;
85
86                image_count += 1;
87            }
88        }
89    }
90
91    println!("All slides converted successfully!");
92
93    Ok(())
94}
Source

pub fn compress_image(&self, image_data: &[u8]) -> Option<Vec<u8>>

Compresses the image data and returning it as a jpg byte slice

§Parameter
  • image_data: The raw image data as a byte array
§Returns
  • Vec<u8>: Returns the compressed and converted jpg byte array
§Notes

All images will be converted to jpg

Source

pub fn load_images_manually(&self) -> Option<Vec<ManualImage>>

Examples found in repository?
examples/manual_image_extraction.rs (line 54)
14fn main() -> Result<()> {
15    let args: Vec<String> = env::args().collect();
16    let Some(input_path) = args.get(1) else {
17        eprintln!(
18            "Usage: cargo run --example manual_image_extraction <presentation.pptx|presentation.odp>"
19        );
20        return Ok(());
21    };
22
23    println!("Processing presentation: {input_path}");
24
25    // Use the config builder to build your config
26    let config = ParserConfig::builder()
27        .extract_images(true)
28        .compress_images(true)
29        .quality(75)
30        .image_handling_mode(ImageHandlingMode::Manually)
31        .build();
32
33    let mut container = PresentationContainer::open(Path::new(input_path), config)?;
34
35    // Parse all slides
36    let slides = container.parse_all()?;
37
38    println!("Found {} slides", slides.len());
39
40    // create a new Markdown file
41    let mut md_file = File::create("output.md")?;
42
43    // Create output directory
44    let output_dir = "extracted_images";
45    fs::create_dir_all(output_dir)?;
46
47    let mut image_count = 1;
48
49    // Convert each slide to Markdown and save
50    for slide in slides {
51        writeln!(md_file, "{}", slide.convert_to_md()?)?;
52
53        // Manually load the base64 encoded image strings from the slide
54        if let Some(images) = slide.load_images_manually() {
55            for image in images {
56                // Decode the base64 strings back to raw image data
57                let image_data = general_purpose::STANDARD
58                    .decode(image.base64_content.clone())
59                    .expect("parser returned invalid base64 image data");
60
61                // Extract image extension if the image is not compressed, otherwise its always `.jpg`
62                let ext = if slide.config.compress_images {
63                    "jpg".to_string()
64                } else {
65                    slide.get_image_extension(&image.img_ref.target.clone())
66                };
67
68                // Construct a unique file name
69                let file_name = format!(
70                    "slide{}_image{}_{}",
71                    slide.slide_number, image_count, &image.img_ref.id
72                );
73
74                // Save the image
75                let output_path = format!("{}/{}.{}", output_dir, &file_name, ext);
76                fs::write(&output_path, image_data)?;
77                println!("Saved image to {}", output_path);
78
79                // Write the image data into the Markdown file
80                writeln!(
81                    md_file,
82                    "![{}](data:image/{};base64,{})",
83                    file_name, ext, image.base64_content
84                )?;
85
86                image_count += 1;
87            }
88        }
89    }
90
91    println!("All slides converted successfully!");
92
93    Ok(())
94}

Trait Implementations§

Source§

impl Debug for Slide

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl Freeze for Slide

§

impl RefUnwindSafe for Slide

§

impl Send for Slide

§

impl Sync for Slide

§

impl Unpin for Slide

§

impl UnsafeUnpin for Slide

§

impl UnwindSafe for Slide

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.