use std::io::{Write, Seek, Cursor};
use zip::ZipWriter;
use zip::write::FileOptions;
use crate::exc::Result;
use super::slide_content::SlideContent;
use super::package_xml::{
create_rels_xml, create_presentation_rels_xml, create_presentation_xml,
create_content_types_xml_with_notes_and_charts,
create_presentation_rels_xml_with_notes,
create_slide_rels_xml_extended,
};
use super::slide_xml::{
create_slide_xml, create_slide_xml_with_content, create_slide_rels_xml,
};
use super::theme_xml::{
create_slide_layout_xml, create_layout_rels_xml,
create_slide_master_xml, create_master_rels_xml, create_theme_xml,
};
use super::props_xml::{create_core_props_xml, create_app_props_xml};
use super::notes_xml::*;
use crate::generator::charts::generate_chart_part_xml;
use crate::generator::slide_content::presentation_settings::PresentationSettings;
pub fn create_pptx(title: &str, slides: usize) -> Result<Vec<u8>> {
let buffer = Vec::new();
let cursor = Cursor::new(buffer);
let mut zip = ZipWriter::new(cursor);
let options = FileOptions::default();
write_package_files(&mut zip, &options, title, slides, None, None)?;
let cursor = zip.finish()?;
Ok(cursor.into_inner())
}
pub fn create_pptx_with_content(
title: &str,
slides: Vec<SlideContent>,
) -> Result<Vec<u8>> {
create_pptx_with_settings(title, slides, None)
}
pub fn create_pptx_with_settings(
title: &str,
slides: Vec<SlideContent>,
settings: Option<PresentationSettings>,
) -> Result<Vec<u8>> {
let buffer = Vec::new();
let cursor = Cursor::new(buffer);
let mut zip = ZipWriter::new(cursor);
let options = FileOptions::default();
write_package_files(&mut zip, &options, title, slides.len(), Some(&slides), settings.as_ref())?;
let cursor = zip.finish()?;
Ok(cursor.into_inner())
}
pub fn create_pptx_to_writer<W: Write + Seek>(
writer: W,
title: &str,
slides: usize,
) -> Result<W> {
let mut zip = ZipWriter::new(writer);
let options = FileOptions::default();
write_package_files(&mut zip, &options, title, slides, None, None)?;
Ok(zip.finish()?)
}
pub fn create_pptx_with_content_to_writer<W: Write + Seek>(
writer: W,
title: &str,
slides: Vec<SlideContent>,
settings: Option<PresentationSettings>,
) -> Result<W> {
let mut zip = ZipWriter::new(writer);
let options = FileOptions::default();
write_package_files(&mut zip, &options, title, slides.len(), Some(&slides), settings.as_ref())?;
Ok(zip.finish()?)
}
pub trait LazySlideSource {
fn slide_count(&self) -> usize;
fn generate_slide(&self, index: usize) -> Option<SlideContent>;
fn slide_has_notes(&self, index: usize) -> bool {
self.generate_slide(index)
.map(|s| s.notes.is_some())
.unwrap_or(false)
}
fn slide_chart_count(&self, index: usize) -> usize {
self.generate_slide(index)
.map(|s| s.charts.len())
.unwrap_or(0)
}
}
pub fn create_pptx_lazy_to_writer<W: Write + Seek>(
writer: W,
title: &str,
slides: Box<dyn LazySlideSource>,
settings: Option<PresentationSettings>,
) -> Result<W> {
let mut zip = ZipWriter::new(writer);
let options = FileOptions::default();
write_package_files_lazy(&mut zip, &options, title, slides.as_ref(), settings.as_ref())?;
Ok(zip.finish()?)
}
struct ChartInfo {
total_charts: usize,
slide_start_indices: Vec<usize>,
}
fn collect_chart_info(slides: Option<&Vec<SlideContent>>) -> ChartInfo {
let mut total_charts = 0;
let mut slide_start_indices = Vec::new();
if let Some(slides) = slides {
for slide in slides {
slide_start_indices.push(total_charts + 1);
total_charts += slide.charts.len();
}
}
ChartInfo {
total_charts,
slide_start_indices,
}
}
fn collect_chart_info_lazy(slides: &dyn LazySlideSource) -> ChartInfo {
let mut total_charts = 0;
let mut slide_start_indices = Vec::new();
for i in 0..slides.slide_count() {
slide_start_indices.push(total_charts + 1);
total_charts += slides.slide_chart_count(i);
}
ChartInfo {
total_charts,
slide_start_indices,
}
}
fn write_content_types<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
slide_count: usize,
custom_slides: Option<&Vec<SlideContent>>,
chart_info: &ChartInfo,
has_pres_props: bool,
) -> Result<()> {
let mut content_types = create_content_types_xml_with_notes_and_charts(
slide_count,
custom_slides,
chart_info.total_charts,
);
if has_pres_props {
let ct_entry = "\n<Override PartName=\"/ppt/presProps.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.presProps+xml\"/>";
if let Some(pos) = content_types.rfind("</Types>") {
content_types.insert_str(pos, ct_entry);
}
}
zip.start_file("[Content_Types].xml", *options)?;
zip.write_all(content_types.as_bytes())?;
Ok(())
}
fn write_presentation_relationships<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
slide_count: usize,
has_notes: bool,
has_pres_props: bool,
) -> Result<()> {
let mut pres_rels = if has_notes {
create_presentation_rels_xml_with_notes(slide_count)
} else {
create_presentation_rels_xml(slide_count)
};
if has_pres_props {
let props_rid = slide_count + 3 + if has_notes { 1 } else { 0 } + 1;
let rel_entry = format!(
"\n <Relationship Id=\"rId{props_rid}\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps\" Target=\"presProps.xml\"/>"
);
if let Some(pos) = pres_rels.rfind("</Relationships>") {
pres_rels.insert_str(pos, &rel_entry);
}
}
zip.start_file("ppt/_rels/presentation.xml.rels", *options)?;
zip.write_all(pres_rels.as_bytes())?;
Ok(())
}
fn write_presentation_properties<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
settings: Option<&PresentationSettings>,
) -> Result<()> {
let has_pres_props = settings.map(|s| s.slide_show.is_some() || s.print.is_some()).unwrap_or(false);
if has_pres_props {
let mut props_xml = String::from(
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><p:presentationPr xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">"#
);
if let Some(s) = settings {
if let Some(ref show) = s.slide_show {
props_xml.push_str(&show.to_xml());
}
if let Some(ref print) = s.print {
props_xml.push_str(&print.to_prnpr_xml());
}
}
props_xml.push_str("</p:presentationPr>");
zip.start_file("ppt/presProps.xml", *options)?;
zip.write_all(props_xml.as_bytes())?;
}
Ok(())
}
fn write_notes_master<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
) -> Result<()> {
let notes_master = create_notes_master_xml();
zip.start_file("ppt/notesMasters/notesMaster1.xml", *options)?;
zip.write_all(notes_master.as_bytes())?;
let notes_master_rels = create_notes_master_rels_xml();
zip.start_file("ppt/notesMasters/_rels/notesMaster1.xml.rels", *options)?;
zip.write_all(notes_master_rels.as_bytes())?;
Ok(())
}
fn write_theme_and_layouts<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
) -> Result<()> {
let slide_layout = create_slide_layout_xml();
zip.start_file("ppt/slideLayouts/slideLayout1.xml", *options)?;
zip.write_all(slide_layout.as_bytes())?;
let layout_rels = create_layout_rels_xml();
zip.start_file("ppt/slideLayouts/_rels/slideLayout1.xml.rels", *options)?;
zip.write_all(layout_rels.as_bytes())?;
let slide_master = create_slide_master_xml();
zip.start_file("ppt/slideMasters/slideMaster1.xml", *options)?;
zip.write_all(slide_master.as_bytes())?;
let master_rels = create_master_rels_xml();
zip.start_file("ppt/slideMasters/_rels/slideMaster1.xml.rels", *options)?;
zip.write_all(master_rels.as_bytes())?;
let theme = create_theme_xml();
zip.start_file("ppt/theme/theme1.xml", *options)?;
zip.write_all(theme.as_bytes())?;
Ok(())
}
fn write_document_properties<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
title: &str,
slide_count: usize,
) -> Result<()> {
let core_props = create_core_props_xml(title);
zip.start_file("docProps/core.xml", *options)?;
zip.write_all(core_props.as_bytes())?;
let app_props = create_app_props_xml(slide_count);
zip.start_file("docProps/app.xml", *options)?;
zip.write_all(app_props.as_bytes())?;
Ok(())
}
fn write_package_files<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
title: &str,
slide_count: usize,
custom_slides: Option<&Vec<SlideContent>>,
settings: Option<&PresentationSettings>,
) -> Result<()> {
let has_notes = custom_slides
.map(|slides| slides.iter().any(|s| s.notes.is_some()))
.unwrap_or(false);
let chart_info = collect_chart_info(custom_slides);
let has_pres_props = settings.map(|s| s.slide_show.is_some() || s.print.is_some()).unwrap_or(false);
write_content_types(zip, options, slide_count, custom_slides, &chart_info, has_pres_props)?;
let rels = create_rels_xml();
zip.start_file("_rels/.rels", *options)?;
zip.write_all(rels.as_bytes())?;
write_presentation_relationships(zip, options, slide_count, has_notes, has_pres_props)?;
let presentation = create_presentation_xml(title, slide_count);
zip.start_file("ppt/presentation.xml", *options)?;
zip.write_all(presentation.as_bytes())?;
write_presentation_properties(zip, options, settings)?;
write_slides(zip, options, slide_count, custom_slides)?;
write_slide_relationships_extended(zip, options, custom_slides, &chart_info.slide_start_indices, slide_count)?;
if has_notes {
write_notes_relationships(zip, options, custom_slides)?;
write_notes_master(zip, options)?;
}
write_theme_and_layouts(zip, options)?;
write_document_properties(zip, options, title, slide_count)?;
if chart_info.total_charts > 0 {
write_charts(zip, options, custom_slides, &chart_info.slide_start_indices)?;
}
write_images(zip, options, custom_slides)?;
Ok(())
}
fn write_package_files_lazy<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
title: &str,
slides: &dyn LazySlideSource,
settings: Option<&PresentationSettings>,
) -> Result<()> {
let slide_count = slides.slide_count();
let has_notes = (0..slide_count).any(|i| slides.slide_has_notes(i));
let chart_info = collect_chart_info_lazy(slides);
let has_pres_props = settings.map(|s| s.slide_show.is_some() || s.print.is_some()).unwrap_or(false);
write_content_types_lazy(zip, options, slide_count, slides, &chart_info, has_pres_props)?;
let rels = create_rels_xml();
zip.start_file("_rels/.rels", *options)?;
zip.write_all(rels.as_bytes())?;
write_presentation_relationships(zip, options, slide_count, has_notes, has_pres_props)?;
let presentation = create_presentation_xml(title, slide_count);
zip.start_file("ppt/presentation.xml", *options)?;
zip.write_all(presentation.as_bytes())?;
write_presentation_properties(zip, options, settings)?;
write_slides_lazy(zip, options, slides)?;
write_slide_relationships_lazy(zip, options, slides, &chart_info.slide_start_indices)?;
if has_notes {
write_notes_relationships_lazy(zip, options, slides)?;
write_notes_master(zip, options)?;
}
write_theme_and_layouts(zip, options)?;
write_document_properties(zip, options, title, slide_count)?;
if chart_info.total_charts > 0 {
write_charts_lazy(zip, options, slides, &chart_info.slide_start_indices)?;
}
Ok(())
}
fn write_content_types_lazy<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
slide_count: usize,
slides: &dyn LazySlideSource,
chart_info: &ChartInfo,
has_pres_props: bool,
) -> Result<()> {
let mut content_types = String::from(
r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>
<Override PartName="/ppt/presProps.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"/>
<Override PartName="/ppt/viewProps.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"/>
<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>
<Override PartName="/ppt/slideLayouts/slideLayout1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>
<Override PartName="/ppt/slideMasters/slideMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>"#
);
for i in 1..=slide_count {
content_types.push_str(&format!(
"\n<Override PartName=\"/ppt/slides/slide{}.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.slide+xml\"/>",
i
));
}
for i in 1..=chart_info.total_charts {
content_types.push_str(&format!(
"\n<Override PartName=\"/ppt/charts/chart{}.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.drawingml.chart+xml\"/>",
i
));
}
for i in 1..slide_count {
if slides.slide_has_notes(i - 1) {
content_types.push_str(&format!(
"\n<Override PartName=\"/ppt/notesSlides/notesSlide{}.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml\"/>",
i
));
}
}
if has_pres_props {
let ct_entry = "\n<Override PartName=\"/ppt/presProps.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.presentationml.presProps+xml\"/>";
if let Some(pos) = content_types.rfind("</Types>") {
content_types.insert_str(pos, ct_entry);
}
}
content_types.push_str("\n</Types>");
zip.start_file("[Content_Types].xml", *options)?;
zip.write_all(content_types.as_bytes())?;
Ok(())
}
fn write_slides_lazy<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
slides: &dyn LazySlideSource,
) -> Result<()> {
for i in 0..slides.slide_count() {
if let Some(slide) = slides.generate_slide(i) {
let slide_num = i + 1;
let mut chart_rids = Vec::new();
let start_rid = if slide.notes.is_some() { 3 } else { 2 };
for j in 0..slide.charts.len() {
chart_rids.push(format!("rId{}", start_rid + j));
}
let slide_xml = create_slide_xml_with_content(slide_num, &slide, &chart_rids);
zip.start_file(format!("ppt/slides/slide{slide_num}.xml"), *options)?;
zip.write_all(slide_xml.as_bytes())?;
if let Some(ref notes) = slide.notes {
let notes_xml = create_notes_xml(slide_num, notes);
zip.start_file(format!("ppt/notesSlides/notesSlide{slide_num}.xml"), *options)?;
zip.write_all(notes_xml.as_bytes())?;
}
}
}
Ok(())
}
fn write_slide_relationships_lazy<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
slides: &dyn LazySlideSource,
slide_chart_start_indices: &[usize],
) -> Result<()> {
for i in 0..slides.slide_count() {
if let Some(slide) = slides.generate_slide(i) {
let slide_num = i + 1;
let mut chart_rels = Vec::new();
let start_chart_idx = slide_chart_start_indices[i];
let start_rid = if slide.notes.is_some() { 3 } else { 2 };
for j in 0..slide.charts.len() {
let rid = format!("rId{}", start_rid + j);
let target = format!("../charts/chart{}.xml", start_chart_idx + j);
chart_rels.push((rid, target));
}
let slide_rels = create_slide_rels_xml_extended(slide_num, slide.notes.is_some(), &chart_rels);
zip.start_file(format!("ppt/slides/_rels/slide{slide_num}.xml.rels"), *options)?;
zip.write_all(slide_rels.as_bytes())?;
}
}
Ok(())
}
fn write_charts_lazy<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
slides: &dyn LazySlideSource,
slide_chart_start_indices: &[usize],
) -> Result<()> {
for i in 0..slides.slide_count() {
if let Some(slide) = slides.generate_slide(i) {
let start_chart_idx = slide_chart_start_indices[i];
for (j, chart) in slide.charts.iter().enumerate() {
let chart_idx = start_chart_idx + j;
let chart_xml = generate_chart_part_xml(chart);
zip.start_file(format!("ppt/charts/chart{}.xml", chart_idx), *options)?;
zip.write_all(chart_xml.as_bytes())?;
}
}
}
Ok(())
}
fn write_notes_relationships_lazy<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
slides: &dyn LazySlideSource,
) -> Result<()> {
for i in 0..slides.slide_count() {
if slides.slide_has_notes(i) {
let slide_num = i + 1;
let notes_rels = create_notes_rels_xml(slide_num);
zip.start_file(format!("ppt/notesSlides/_rels/notesSlide{slide_num}.xml.rels"), *options)?;
zip.write_all(notes_rels.as_bytes())?;
}
}
Ok(())
}
fn write_slides<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
slide_count: usize,
custom_slides: Option<&Vec<SlideContent>>,
) -> Result<()> {
match custom_slides {
Some(slides) => {
for (i, slide) in slides.iter().enumerate() {
let slide_num = i + 1;
let mut chart_rids = Vec::new();
let start_rid = if slide.notes.is_some() { 3 } else { 2 };
for j in 0..slide.charts.len() {
chart_rids.push(format!("rId{}", start_rid + j));
}
let slide_xml = create_slide_xml_with_content(slide_num, slide, &chart_rids);
zip.start_file(format!("ppt/slides/slide{slide_num}.xml"), *options)?;
zip.write_all(slide_xml.as_bytes())?;
if let Some(notes) = &slide.notes {
let notes_xml = create_notes_xml(slide_num, notes);
zip.start_file(format!("ppt/notesSlides/notesSlide{slide_num}.xml"), *options)?;
zip.write_all(notes_xml.as_bytes())?;
}
}
}
None => {
for i in 1..=slide_count {
let slide_xml = create_slide_xml(i, "Presentation");
zip.start_file(format!("ppt/slides/slide{i}.xml"), *options)?;
zip.write_all(slide_xml.as_bytes())?;
}
}
}
Ok(())
}
fn write_slide_relationships_extended<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
custom_slides: Option<&Vec<SlideContent>>,
slide_chart_start_indices: &[usize],
slide_count: usize,
) -> Result<()> {
let mut total_images = 0;
match custom_slides {
Some(slides) => {
for (i, slide) in slides.iter().enumerate() {
let slide_num = i + 1;
let image_count = slide.images.len();
let image_start_num = total_images + 1;
let image_extensions: Vec<String> = slide.images.iter()
.map(|img| img.extension())
.collect();
let mut chart_rels = Vec::new();
let start_chart_idx = slide_chart_start_indices[i];
let start_rid = 2 + image_count + if slide.notes.is_some() { 1 } else { 0 };
for j in 0..slide.charts.len() {
let rid = format!("rId{}", start_rid + j);
let target = format!("../charts/chart{}.xml", start_chart_idx + j);
chart_rels.push((rid, target));
}
let slide_rels = super::package_xml::create_slide_rels_xml_with_images(
slide_num,
slide.notes.is_some(),
&chart_rels,
image_count,
image_start_num,
&image_extensions
);
zip.start_file(format!("ppt/slides/_rels/slide{slide_num}.xml.rels"), *options)?;
zip.write_all(slide_rels.as_bytes())?;
total_images += image_count;
}
}
None => {
for i in 1..=slide_count {
let slide_rels = create_slide_rels_xml();
zip.start_file(format!("ppt/slides/_rels/slide{i}.xml.rels"), *options)?;
zip.write_all(slide_rels.as_bytes())?;
}
}
}
Ok(())
}
fn write_charts<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
custom_slides: Option<&Vec<SlideContent>>,
slide_chart_start_indices: &[usize],
) -> Result<()> {
if let Some(slides) = custom_slides {
for (i, slide) in slides.iter().enumerate() {
let start_chart_idx = slide_chart_start_indices[i];
for (j, chart) in slide.charts.iter().enumerate() {
let chart_idx = start_chart_idx + j;
let chart_xml = generate_chart_part_xml(chart);
zip.start_file(format!("ppt/charts/chart{}.xml", chart_idx), *options)?;
zip.write_all(chart_xml.as_bytes())?;
}
}
}
Ok(())
}
fn write_notes_relationships<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
custom_slides: Option<&Vec<SlideContent>>,
) -> Result<()> {
if let Some(slides) = custom_slides {
for (i, slide) in slides.iter().enumerate() {
if slide.notes.is_some() {
let slide_num = i + 1;
let notes_rels = create_notes_rels_xml(slide_num);
zip.start_file(format!("ppt/notesSlides/_rels/notesSlide{slide_num}.xml.rels"), *options)?;
zip.write_all(notes_rels.as_bytes())?;
}
}
}
Ok(())
}
fn write_images<W: Write + Seek>(
zip: &mut ZipWriter<W>,
options: &FileOptions,
custom_slides: Option<&Vec<SlideContent>>,
) -> Result<()> {
if let Some(slides) = custom_slides {
let mut image_counter = 1;
for slide in slides {
for image in &slide.images {
if let Some(bytes) = image.get_bytes() {
let ext = image.extension();
let filename = format!("ppt/media/image{}.{}", image_counter, ext);
zip.start_file(filename, *options)?;
zip.write_all(&bytes)?;
image_counter += 1;
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
struct TestSlideSource {
count: usize,
with_notes: bool,
}
impl LazySlideSource for TestSlideSource {
fn slide_count(&self) -> usize {
self.count
}
fn generate_slide(&self, index: usize) -> Option<SlideContent> {
if index < self.count {
let mut slide = SlideContent::new(&format!("Slide {}", index + 1))
.add_bullet(&format!("Point {}", index + 1));
if self.with_notes {
slide.notes = Some("Speaker notes here".to_string());
}
Some(slide)
} else {
None
}
}
fn slide_has_notes(&self, index: usize) -> bool {
self.with_notes && index < self.count
}
fn slide_chart_count(&self, _index: usize) -> usize {
0
}
}
#[test]
fn test_create_pptx_to_writer() {
let buffer = Vec::new();
let cursor = Cursor::new(buffer);
let result = create_pptx_to_writer(cursor, "Test Presentation", 3);
assert!(result.is_ok());
}
#[test]
fn test_create_pptx_with_content_to_writer() {
let slides = vec![
SlideContent::new("Title").add_bullet("Point 1"),
SlideContent::new("Slide 2").add_bullet("Point 2"),
];
let buffer = Vec::new();
let cursor = Cursor::new(buffer);
let result = create_pptx_with_content_to_writer(cursor, "Test", slides, None);
assert!(result.is_ok());
}
#[test]
fn test_create_pptx_lazy_to_writer() {
let source = TestSlideSource { count: 10, with_notes: false };
let buffer = Vec::new();
let cursor = Cursor::new(buffer);
let result = create_pptx_lazy_to_writer(cursor, "Lazy Test", Box::new(source), None);
assert!(result.is_ok());
}
#[test]
fn test_create_pptx_lazy_with_notes() {
let source = TestSlideSource { count: 5, with_notes: true };
let buffer = Vec::new();
let cursor = Cursor::new(buffer);
let result = create_pptx_lazy_to_writer(cursor, "Lazy Test with Notes", Box::new(source), None);
assert!(result.is_ok());
}
#[test]
fn test_lazy_slide_source() {
let source = TestSlideSource { count: 3, with_notes: false };
assert_eq!(source.slide_count(), 3);
assert!(!source.slide_has_notes(0));
assert_eq!(source.slide_chart_count(0), 0);
let slide = source.generate_slide(0);
assert!(slide.is_some());
assert_eq!(slide.unwrap().title, "Slide 1");
let out_of_bounds = source.generate_slide(10);
assert!(out_of_bounds.is_none());
}
#[test]
fn test_streaming_api_compatibility() {
let slides = vec![
SlideContent::new("Test").add_bullet("Item 1"),
];
let in_memory = create_pptx_with_content("Test", slides.clone()).unwrap();
let buffer = Vec::new();
let cursor = Cursor::new(buffer);
let streaming = create_pptx_with_content_to_writer(cursor, "Test", slides, None).unwrap().into_inner();
assert!(!in_memory.is_empty());
assert!(!streaming.is_empty());
}
#[test]
fn test_lazy_vs_eager_compatibility() {
let eager_slides = vec![
SlideContent::new("Slide 1").add_bullet("Point 1"),
SlideContent::new("Slide 2").add_bullet("Point 2"),
];
let eager = create_pptx_with_content("Test", eager_slides).unwrap();
let source = TestSlideSource { count: 2, with_notes: false };
let buffer = Vec::new();
let cursor = Cursor::new(buffer);
let lazy = create_pptx_lazy_to_writer(cursor, "Test", Box::new(source), None).unwrap().into_inner();
assert!(!eager.is_empty());
assert!(!lazy.is_empty());
}
}