1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Media operations (images, charts, videos) on a [`Presentation`].
use crate::chart::data::CategoryChartData;
use crate::chart::xmlwriter::ChartXmlWriter;
use crate::enums::chart::XlChartType;
use crate::error::{PartNotFoundExt, PptxResult};
use crate::media::{Image, Video};
use crate::opc::constants::{content_type as CT, relationship_type as RT};
use crate::opc::part::Part;
use crate::shapes::shapetree::ShapeTree;
use crate::slide::SlideRef;
use crate::units::Emu;
use super::Presentation;
impl Presentation {
/// Add an image to the package and return its partname.
///
/// Uses SHA1 deduplication: if an image with the same hash already exists,
/// the existing partname is returned.
/// # Errors
///
/// Returns an error if the image part cannot be created.
pub fn add_image(&mut self, image: &Image) -> PptxResult<String> {
let (partname, _ct) = self.package.or_add_image_part(image)?;
Ok(partname.into_string())
}
/// Add a chart to a slide.
///
/// Generates chart XML from the given `CategoryChartData` and chart type,
/// creates a chart part in the package, and inserts a `<p:graphicFrame>`
/// element into the slide referencing the chart.
///
/// # Example
/// ```no_run
/// use pptx::presentation::Presentation;
/// use pptx::chart::data::CategoryChartData;
/// use pptx::enums::chart::XlChartType;
/// use pptx::units::{Emu, Inches};
///
/// let mut prs = Presentation::new().unwrap();
/// let layouts = prs.slide_layouts().unwrap();
/// let slide_ref = prs.add_slide(&layouts[0]).unwrap();
///
/// let mut chart_data = CategoryChartData::new();
/// chart_data.add_category("Q1");
/// chart_data.add_category("Q2");
/// chart_data.add_series("Sales", &[100.0, 150.0]);
///
/// let left: Emu = Inches(1.0).into();
/// let top: Emu = Inches(1.0).into();
/// let width: Emu = Inches(6.0).into();
/// let height: Emu = Inches(4.0).into();
///
/// prs.add_chart_to_slide(
/// &slide_ref,
/// &chart_data,
/// XlChartType::ColumnClustered,
/// left, top, width, height,
/// ).unwrap();
/// ```
/// # Errors
///
/// Returns an error if the chart cannot be created or inserted.
#[allow(clippy::too_many_arguments)]
pub fn add_chart_to_slide(
&mut self,
slide_ref: &SlideRef,
chart_data: &CategoryChartData,
chart_type: XlChartType,
left: Emu,
top: Emu,
width: Emu,
height: Emu,
) -> PptxResult<()> {
// 1. Generate chart XML
let chart_xml_str = ChartXmlWriter::write_category(chart_data, chart_type)?;
// 2. Create the chart part
let chart_partname = self.package.next_partname("/ppt/charts/chart{}.xml")?;
// 3. Generate and add the embedded xlsx part
let xlsx_bytes = crate::chart::xlsx::generate_category_xlsx(chart_data)?;
let xlsx_partname = self.package.next_partname("/ppt/charts/chart{}.xlsx")?;
// Pre-compute relative refs before consuming partnames
let xlsx_target_ref = xlsx_partname.relative_ref(chart_partname.base_uri());
let chart_target_ref = chart_partname.relative_ref(slide_ref.partname.base_uri());
let chart_partname_clone = chart_partname.clone();
let chart_part = Part::new(chart_partname, CT::DML_CHART, chart_xml_str.into_bytes());
self.package.put_part(chart_part);
let xlsx_part = Part::new(xlsx_partname, CT::SML_SHEET, xlsx_bytes);
self.package.put_part(xlsx_part);
// 4. Add relationship from chart part to xlsx part
let chart_part = self
.package
.part_mut(&chart_partname_clone)
.or_part_not_found(chart_partname_clone.as_str())?;
chart_part
.rels
.add_relationship(RT::PACKAGE, &xlsx_target_ref, false);
let slide_part = self
.package
.part_mut(&slide_ref.partname)
.or_part_not_found(slide_ref.partname.as_str())?;
let r_id = slide_part
.rels
.add_relationship(RT::CHART, &chart_target_ref, false);
// 6. Determine next shape ID from current slide content
let shape_id = {
let shapes = crate::shapes::shapetree::ShapeTree::from_slide_xml(&slide_part.blob)?;
crate::units::ShapeId(shapes.max_shape_id().0 + 1)
};
// 7. Generate the graphicFrame XML
let name = format!("Chart {shape_id}");
let gf_xml = ShapeTree::new_chart_graphic_frame_xml(
shape_id, &name, &r_id, left, top, width, height,
);
// 8. Insert into the slide's spTree
let updated_xml = ShapeTree::insert_shape_xml(&slide_part.blob, &gf_xml)?;
slide_part.blob = updated_xml;
Ok(())
}
/// Add a video to a slide with a poster frame image.
///
/// Adds the video as an external link relationship and the poster image
/// as an embedded relationship, then inserts a `<p:pic>` element with
/// `<a:videoFile>` into the slide.
/// # Errors
///
/// Returns an error if the video cannot be added to the slide.
#[allow(clippy::too_many_arguments)]
pub fn add_video_to_slide(
&mut self,
slide_ref: &SlideRef,
video: &Video,
poster: &Image,
left: Emu,
top: Emu,
width: Emu,
height: Emu,
) -> PptxResult<()> {
// 1. Add the video media part to the package
let (video_partname, _video_ct) = self.package.or_add_media_part(video)?;
// 2. Add the poster image part to the package
let (poster_partname, _poster_ct) = self.package.or_add_image_part(poster)?;
// 3. Add relationship from the slide to the video (external link)
let video_target_ref = video_partname.relative_ref(slide_ref.partname.base_uri());
let slide_part = self
.package
.part_mut(&slide_ref.partname)
.or_part_not_found(slide_ref.partname.as_str())?;
let video_r_id = slide_part
.rels
.add_relationship(RT::VIDEO, &video_target_ref, false);
// 4. Add relationship from the slide to the poster image (embedded)
let poster_target_ref = poster_partname.relative_ref(slide_ref.partname.base_uri());
let poster_r_id = slide_part
.rels
.add_relationship(RT::IMAGE, &poster_target_ref, false);
// 5. Insert the movie shape XML into the slide's spTree
let updated_xml = ShapeTree::add_movie(
&slide_part.blob,
&video_r_id,
&poster_r_id,
left,
top,
width,
height,
)?;
slide_part.blob = updated_xml;
Ok(())
}
}