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
impl Slide
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
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
Sourcepub fn convert_to_md(&self) -> Result<String>
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?
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
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}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 "",
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}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}pub fn to_markdown(&self, options: &MarkdownOptions) -> Result<String>
Sourcepub fn extract_slide_number(path: &str) -> Option<u32>
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).
Sourcepub fn link_images(&mut self)
pub fn link_images(&mut self)
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
| Parameter | Example value |
|---|---|
id | rId2 |
target | ../media/image2.png |
Sourcepub fn get_image_extension(&self, path: &str) -> String
pub fn get_image_extension(&self, path: &str) -> String
Extracts the file extension from image paths
Examples found in repository?
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 "",
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}Sourcepub fn load_images_manually(&self) -> Option<Vec<ManualImage>>
pub fn load_images_manually(&self) -> Option<Vec<ManualImage>>
Examples found in repository?
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 "",
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§
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> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
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 moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
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