1use std::io::{Seek, Write};
15
16use drawing::{ShapeProperties, TextAnchor};
17use opc::{Package, Part, Relationship, Relationships, TargetMode};
18use xml_core::{BytesDecl, BytesEnd, BytesStart, Event, Writer};
19
20use crate::error::Result;
21use crate::model::{
22 AutoShape, ColorScheme, Connector, CustomPropertyValue, DEFAULT_NOTES_HEIGHT_EMU,
23 DEFAULT_NOTES_WIDTH_EMU, DocumentProperties, FontScheme, Placeholder, PlaceholderKind,
24 Presentation, Shape, ShapeGroup, Slide, SlideComment, SlideHyperlinkTarget, SlideMedia,
25 SlideTable, SlideTableStyle, TableCell, TableCellProperties, TableRow, TableStylePart, Theme,
26};
27
28const P_NAMESPACE: &str = "http://schemas.openxmlformats.org/presentationml/2006/main";
29const A_NAMESPACE: &str = "http://schemas.openxmlformats.org/drawingml/2006/main";
30const R_NAMESPACE: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
31const CHART_NAMESPACE: &str = "http://schemas.openxmlformats.org/drawingml/2006/chart";
32const TABLE_NAMESPACE: &str = "http://schemas.openxmlformats.org/drawingml/2006/table";
33
34fn validate_table_style_id(id: &str) -> Result<()> {
41 let bytes = id.as_bytes();
42 let is_hex_run = |s: &[u8], len: usize| s.len() == len && s.iter().all(u8::is_ascii_hexdigit);
43 let valid = bytes.len() == 38
44 && bytes[0] == b'{'
45 && bytes[37] == b'}'
46 && bytes[9] == b'-'
47 && bytes[14] == b'-'
48 && bytes[19] == b'-'
49 && bytes[24] == b'-'
50 && is_hex_run(&bytes[1..9], 8)
51 && is_hex_run(&bytes[10..14], 4)
52 && is_hex_run(&bytes[15..19], 4)
53 && is_hex_run(&bytes[20..24], 4)
54 && is_hex_run(&bytes[25..37], 12)
55 && id.chars().all(|c| !c.is_ascii_hexdigit() || c.is_ascii_digit() || c.is_ascii_uppercase());
59 if valid {
60 Ok(())
61 } else {
62 Err(crate::error::Error::InvalidTableStyleId(id.to_string()))
63 }
64}
65
66const OFFICE_DOCUMENT_RELATIONSHIP_TYPE: &str =
67 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
68const SLIDE_MASTER_RELATIONSHIP_TYPE: &str =
69 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster";
70const SLIDE_LAYOUT_RELATIONSHIP_TYPE: &str =
71 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout";
72const SLIDE_RELATIONSHIP_TYPE: &str =
73 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide";
74const THEME_RELATIONSHIP_TYPE: &str =
75 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme";
76const PRES_PROPS_RELATIONSHIP_TYPE: &str =
77 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps";
78const VIEW_PROPS_RELATIONSHIP_TYPE: &str =
79 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps";
80const TABLE_STYLES_RELATIONSHIP_TYPE: &str =
81 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles";
82const CORE_PROPERTIES_RELATIONSHIP_TYPE: &str =
83 "http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties";
84const EXTENDED_PROPERTIES_RELATIONSHIP_TYPE: &str =
85 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties";
86const IMAGE_RELATIONSHIP_TYPE: &str =
87 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
88const CHART_RELATIONSHIP_TYPE: &str =
89 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart";
90const NOTES_MASTER_RELATIONSHIP_TYPE: &str =
91 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesMaster";
92const NOTES_SLIDE_RELATIONSHIP_TYPE: &str =
93 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide";
94const COMMENT_AUTHORS_RELATIONSHIP_TYPE: &str =
97 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/commentAuthors";
98const COMMENT_RELATIONSHIP_TYPE: &str =
99 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
100const HYPERLINK_RELATIONSHIP_TYPE: &str =
101 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
102const VIDEO_RELATIONSHIP_TYPE: &str =
103 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/video";
104const AUDIO_RELATIONSHIP_TYPE: &str =
105 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/audio";
106const FONT_RELATIONSHIP_TYPE: &str =
107 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
108const CUSTOM_PROPERTIES_RELATIONSHIP_TYPE: &str =
109 "http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties";
110
111const PRESENTATION_CONTENT_TYPE: &str =
112 "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml";
113const SLIDE_MASTER_CONTENT_TYPE: &str =
114 "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml";
115const SLIDE_LAYOUT_CONTENT_TYPE: &str =
116 "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml";
117const SLIDE_CONTENT_TYPE: &str =
118 "application/vnd.openxmlformats-officedocument.presentationml.slide+xml";
119const THEME_CONTENT_TYPE: &str = "application/vnd.openxmlformats-officedocument.theme+xml";
120const PRES_PROPS_CONTENT_TYPE: &str =
121 "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml";
122const VIEW_PROPS_CONTENT_TYPE: &str =
123 "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml";
124const TABLE_STYLES_CONTENT_TYPE: &str =
125 "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml";
126const CORE_PROPERTIES_CONTENT_TYPE: &str =
127 "application/vnd.openxmlformats-package.core-properties+xml";
128const EXTENDED_PROPERTIES_CONTENT_TYPE: &str =
129 "application/vnd.openxmlformats-officedocument.extended-properties+xml";
130const CHART_CONTENT_TYPE: &str =
131 "application/vnd.openxmlformats-officedocument.drawingml.chart+xml";
132const NOTES_MASTER_CONTENT_TYPE: &str =
133 "application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml";
134const NOTES_SLIDE_CONTENT_TYPE: &str =
135 "application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml";
136const COMMENT_AUTHORS_CONTENT_TYPE: &str =
137 "application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml";
138const COMMENT_CONTENT_TYPE: &str =
139 "application/vnd.openxmlformats-officedocument.presentationml.comments+xml";
140const FONT_CONTENT_TYPE: &str = "application/x-fontdata";
141const CUSTOM_PROPERTIES_CONTENT_TYPE: &str =
142 "application/vnd.openxmlformats-officedocument.custom-properties+xml";
143const CUSTOM_PROPERTIES_NAMESPACE: &str =
144 "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties";
145const CUSTOM_PROPERTIES_VT_NAMESPACE: &str =
146 "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes";
147const CUSTOM_PROPERTY_FMTID: &str = "{D5CDD505-2E9C-101B-9397-08002B2CF9AE}";
151const FIRST_CUSTOM_PROPERTY_PID: i32 = 2;
152const TABLE_STYLE_GUID: &str = "{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}";
157
158const PRESENTATION_PART_NAME: &str = "/ppt/presentation.xml";
159const PRESENTATION_ENTRY_NAME: &str = "ppt/presentation.xml";
160const SLIDE_MASTER_PART_NAME: &str = "/ppt/slideMasters/slideMaster1.xml";
161const SLIDE_MASTER_ENTRY_NAME: &str = "slideMasters/slideMaster1.xml";
162const SLIDE_LAYOUT_PART_NAME: &str = "/ppt/slideLayouts/slideLayout1.xml";
163const THEME_PART_NAME: &str = "/ppt/theme/theme1.xml";
164const THEME_ENTRY_NAME: &str = "theme/theme1.xml";
165const NOTES_MASTER_THEME_PART_NAME: &str = "/ppt/theme/theme2.xml";
174const NOTES_MASTER_THEME_ENTRY_NAME: &str = "../theme/theme2.xml";
175const PRES_PROPS_PART_NAME: &str = "/ppt/presProps.xml";
176const PRES_PROPS_ENTRY_NAME: &str = "presProps.xml";
177const VIEW_PROPS_PART_NAME: &str = "/ppt/viewProps.xml";
178const VIEW_PROPS_ENTRY_NAME: &str = "viewProps.xml";
179const TABLE_STYLES_PART_NAME: &str = "/ppt/tableStyles.xml";
180const TABLE_STYLES_ENTRY_NAME: &str = "tableStyles.xml";
181const CORE_PROPERTIES_PART_NAME: &str = "/docProps/core.xml";
182const CORE_PROPERTIES_ENTRY_NAME: &str = "docProps/core.xml";
183const EXTENDED_PROPERTIES_PART_NAME: &str = "/docProps/app.xml";
184const EXTENDED_PROPERTIES_ENTRY_NAME: &str = "docProps/app.xml";
185const NOTES_MASTER_PART_NAME: &str = "/ppt/notesMasters/notesMaster1.xml";
186const NOTES_MASTER_ENTRY_NAME: &str = "notesMasters/notesMaster1.xml";
187const COMMENT_AUTHORS_PART_NAME: &str = "/ppt/commentAuthors.xml";
188const COMMENT_AUTHORS_ENTRY_NAME: &str = "commentAuthors.xml";
189const CUSTOM_PROPERTIES_PART_NAME: &str = "/docProps/custom.xml";
190const CUSTOM_PROPERTIES_ENTRY_NAME: &str = "docProps/custom.xml";
191
192const SLIDE_MASTER_ID: u32 = 2_147_483_648;
195const FIRST_SLIDE_ID: u32 = 256;
197
198impl Presentation {
199 pub fn write_to<W: Write + Seek>(&self, writer: W) -> Result<W> {
212 let mut package = Package::new();
213
214 package.add_relationship(Relationship {
215 id: "rId1".to_string(),
216 rel_type: OFFICE_DOCUMENT_RELATIONSHIP_TYPE.to_string(),
217 target: PRESENTATION_ENTRY_NAME.to_string(),
218 target_mode: TargetMode::Internal,
219 });
220 package.add_relationship(Relationship {
221 id: "rId2".to_string(),
222 rel_type: CORE_PROPERTIES_RELATIONSHIP_TYPE.to_string(),
223 target: CORE_PROPERTIES_ENTRY_NAME.to_string(),
224 target_mode: TargetMode::Internal,
225 });
226 package.add_relationship(Relationship {
227 id: "rId3".to_string(),
228 rel_type: EXTENDED_PROPERTIES_RELATIONSHIP_TYPE.to_string(),
229 target: EXTENDED_PROPERTIES_ENTRY_NAME.to_string(),
230 target_mode: TargetMode::Internal,
231 });
232 if !self.properties.custom_properties.is_empty() {
236 package.add_relationship(Relationship {
237 id: "rId4".to_string(),
238 rel_type: CUSTOM_PROPERTIES_RELATIONSHIP_TYPE.to_string(),
239 target: CUSTOM_PROPERTIES_ENTRY_NAME.to_string(),
240 target_mode: TargetMode::Internal,
241 });
242 }
243
244 let mut presentation_relationships = Relationships::new();
249 presentation_relationships.add(Relationship {
250 id: "rId1".to_string(),
251 rel_type: SLIDE_MASTER_RELATIONSHIP_TYPE.to_string(),
252 target: SLIDE_MASTER_ENTRY_NAME.to_string(),
253 target_mode: TargetMode::Internal,
254 });
255
256 let mut state = PackageState::new();
260 let has_notes = self.slides.iter().any(|slide| slide.notes.is_some());
261
262 let mut next_relationship_id = 2u32;
263 let mut slide_ids: Vec<(u32, String)> = Vec::new();
264 for (index, slide) in self.slides.iter().enumerate() {
265 let slide_number = index + 1;
266 let relationship_id = format!("rId{next_relationship_id}");
267 next_relationship_id += 1;
268
269 presentation_relationships.add(Relationship {
270 id: relationship_id.clone(),
271 rel_type: SLIDE_RELATIONSHIP_TYPE.to_string(),
272 target: format!("slides/slide{slide_number}.xml"),
273 target_mode: TargetMode::Internal,
274 });
275 slide_ids.push((FIRST_SLIDE_ID + index as u32, relationship_id));
276
277 let mut slide_relationships = PartRelationships::new();
281 slide_relationships.add(
282 SLIDE_LAYOUT_RELATIONSHIP_TYPE,
283 "../slideLayouts/slideLayout1.xml".to_string(),
284 );
285
286 let slide_xml = to_slide_xml(slide, &mut state, &mut slide_relationships)?;
287
288 if let Some(notes) = &slide.notes {
289 let notes_entry_name = format!("notesSlides/notesSlide{slide_number}.xml");
290 slide_relationships.add(
291 NOTES_SLIDE_RELATIONSHIP_TYPE,
292 format!("../{notes_entry_name}"),
293 );
294
295 let mut notes_part_relationships = Relationships::new();
296 notes_part_relationships.add(Relationship {
297 id: "rId1".to_string(),
298 rel_type: NOTES_MASTER_RELATIONSHIP_TYPE.to_string(),
299 target: "../notesMasters/notesMaster1.xml".to_string(),
300 target_mode: TargetMode::Internal,
301 });
302 notes_part_relationships.add(Relationship {
303 id: "rId2".to_string(),
304 rel_type: SLIDE_RELATIONSHIP_TYPE.to_string(),
305 target: format!("../slides/slide{slide_number}.xml"),
306 target_mode: TargetMode::Internal,
307 });
308 let mut notes_part = Part::new(
309 format!("/ppt/{notes_entry_name}"),
310 NOTES_SLIDE_CONTENT_TYPE,
311 to_notes_slide_xml(notes)?,
312 );
313 notes_part.relationships = notes_part_relationships;
314 package.add_part(notes_part);
315 }
316
317 if !slide.comments.is_empty() {
322 let comments_entry_name = format!("comments/comment{slide_number}.xml");
323 slide_relationships.add(
324 COMMENT_RELATIONSHIP_TYPE,
325 format!("../{comments_entry_name}"),
326 );
327 let comments_xml = to_comments_xml(&slide.comments, &mut state.comment_authors);
328 package.add_part(Part::new(
329 format!("/ppt/{comments_entry_name}"),
330 COMMENT_CONTENT_TYPE,
331 comments_xml,
332 ));
333 }
334
335 let mut slide_part = Part::new(
336 format!("/ppt/slides/slide{slide_number}.xml"),
337 SLIDE_CONTENT_TYPE,
338 slide_xml,
339 );
340 slide_part.relationships = slide_relationships.relationships;
341 package.add_part(slide_part);
342 }
343
344 if !state.comment_authors.authors.is_empty() {
350 let relationship_id = format!("rId{next_relationship_id}");
351 next_relationship_id += 1;
352 presentation_relationships.add(Relationship {
353 id: relationship_id,
354 rel_type: COMMENT_AUTHORS_RELATIONSHIP_TYPE.to_string(),
355 target: COMMENT_AUTHORS_ENTRY_NAME.to_string(),
356 target_mode: TargetMode::Internal,
357 });
358 package.add_part(Part::new(
359 COMMENT_AUTHORS_PART_NAME,
360 COMMENT_AUTHORS_CONTENT_TYPE,
361 to_comment_authors_xml(&state.comment_authors),
362 ));
363 }
364
365 let notes_master_relationship_id = if has_notes {
366 let relationship_id = format!("rId{next_relationship_id}");
367 next_relationship_id += 1;
368 presentation_relationships.add(Relationship {
369 id: relationship_id.clone(),
370 rel_type: NOTES_MASTER_RELATIONSHIP_TYPE.to_string(),
371 target: NOTES_MASTER_ENTRY_NAME.to_string(),
372 target_mode: TargetMode::Internal,
373 });
374 Some(relationship_id)
375 } else {
376 None
377 };
378
379 let theme_relationship_id = format!("rId{next_relationship_id}");
380 next_relationship_id += 1;
381 presentation_relationships.add(Relationship {
382 id: theme_relationship_id,
383 rel_type: THEME_RELATIONSHIP_TYPE.to_string(),
384 target: THEME_ENTRY_NAME.to_string(),
385 target_mode: TargetMode::Internal,
386 });
387 let pres_props_relationship_id = format!("rId{next_relationship_id}");
388 next_relationship_id += 1;
389 presentation_relationships.add(Relationship {
390 id: pres_props_relationship_id,
391 rel_type: PRES_PROPS_RELATIONSHIP_TYPE.to_string(),
392 target: PRES_PROPS_ENTRY_NAME.to_string(),
393 target_mode: TargetMode::Internal,
394 });
395 let view_props_relationship_id = format!("rId{next_relationship_id}");
396 next_relationship_id += 1;
397 presentation_relationships.add(Relationship {
398 id: view_props_relationship_id,
399 rel_type: VIEW_PROPS_RELATIONSHIP_TYPE.to_string(),
400 target: VIEW_PROPS_ENTRY_NAME.to_string(),
401 target_mode: TargetMode::Internal,
402 });
403 let table_styles_relationship_id = format!("rId{next_relationship_id}");
404 next_relationship_id += 1;
405 presentation_relationships.add(Relationship {
406 id: table_styles_relationship_id,
407 rel_type: TABLE_STYLES_RELATIONSHIP_TYPE.to_string(),
408 target: TABLE_STYLES_ENTRY_NAME.to_string(),
409 target_mode: TargetMode::Internal,
410 });
411
412 let mut next_font_index = 1u32;
418 let mut resolved_fonts: Vec<ResolvedEmbeddedFont> = Vec::new();
419 for font in &self.embedded_fonts {
420 resolved_fonts.push(ResolvedEmbeddedFont {
421 typeface: font.typeface.clone(),
422 regular: register_embedded_font_variant(
423 &mut package,
424 &mut presentation_relationships,
425 &mut next_relationship_id,
426 &mut next_font_index,
427 &font.regular,
428 ),
429 bold: register_embedded_font_variant(
430 &mut package,
431 &mut presentation_relationships,
432 &mut next_relationship_id,
433 &mut next_font_index,
434 &font.bold,
435 ),
436 italic: register_embedded_font_variant(
437 &mut package,
438 &mut presentation_relationships,
439 &mut next_relationship_id,
440 &mut next_font_index,
441 &font.italic,
442 ),
443 bold_italic: register_embedded_font_variant(
444 &mut package,
445 &mut presentation_relationships,
446 &mut next_relationship_id,
447 &mut next_font_index,
448 &font.bold_italic,
449 ),
450 });
451 }
452
453 let mut presentation_part = Part::new(
454 PRESENTATION_PART_NAME,
455 PRESENTATION_CONTENT_TYPE,
456 to_presentation_xml(
457 self,
458 &slide_ids,
459 notes_master_relationship_id.as_deref(),
460 &resolved_fonts,
461 )?,
462 );
463 presentation_part.relationships = presentation_relationships;
464 package.add_part(presentation_part);
465
466 let mut slide_master_part = Part::new(
467 SLIDE_MASTER_PART_NAME,
468 SLIDE_MASTER_CONTENT_TYPE,
469 to_slide_master_xml()?,
470 );
471 slide_master_part.relationships.add(Relationship {
472 id: "rId1".to_string(),
473 rel_type: SLIDE_LAYOUT_RELATIONSHIP_TYPE.to_string(),
474 target: "../slideLayouts/slideLayout1.xml".to_string(),
475 target_mode: TargetMode::Internal,
476 });
477 slide_master_part.relationships.add(Relationship {
478 id: "rId2".to_string(),
479 rel_type: THEME_RELATIONSHIP_TYPE.to_string(),
480 target: "../theme/theme1.xml".to_string(),
481 target_mode: TargetMode::Internal,
482 });
483 package.add_part(slide_master_part);
484
485 let mut slide_layout_part = Part::new(
486 SLIDE_LAYOUT_PART_NAME,
487 SLIDE_LAYOUT_CONTENT_TYPE,
488 to_slide_layout_xml()?,
489 );
490 slide_layout_part.relationships.add(Relationship {
491 id: "rId1".to_string(),
492 rel_type: SLIDE_MASTER_RELATIONSHIP_TYPE.to_string(),
493 target: "../slideMasters/slideMaster1.xml".to_string(),
494 target_mode: TargetMode::Internal,
495 });
496 package.add_part(slide_layout_part);
497
498 package.add_part(Part::new(
499 THEME_PART_NAME,
500 THEME_CONTENT_TYPE,
501 to_theme_xml(self.theme.as_ref()),
502 ));
503 package.add_part(Part::new(
504 PRES_PROPS_PART_NAME,
505 PRES_PROPS_CONTENT_TYPE,
506 to_pres_props_xml(),
507 ));
508 package.add_part(Part::new(
509 VIEW_PROPS_PART_NAME,
510 VIEW_PROPS_CONTENT_TYPE,
511 to_view_props_xml(),
512 ));
513 package.add_part(Part::new(
514 TABLE_STYLES_PART_NAME,
515 TABLE_STYLES_CONTENT_TYPE,
516 to_table_styles_xml(&self.table_styles)?,
517 ));
518
519 if has_notes {
520 let mut notes_master_part = Part::new(
521 NOTES_MASTER_PART_NAME,
522 NOTES_MASTER_CONTENT_TYPE,
523 to_notes_master_xml()?,
524 );
525 notes_master_part.relationships.add(Relationship {
526 id: "rId1".to_string(),
527 rel_type: THEME_RELATIONSHIP_TYPE.to_string(),
528 target: NOTES_MASTER_THEME_ENTRY_NAME.to_string(),
529 target_mode: TargetMode::Internal,
530 });
531 package.add_part(notes_master_part);
532 package.add_part(Part::new(
536 NOTES_MASTER_THEME_PART_NAME,
537 THEME_CONTENT_TYPE,
538 to_theme_xml(None),
539 ));
540 }
541
542 for media_part in state.media.parts {
543 package.add_part(Part::new(
544 format!("/ppt/{}", media_part.entry_name),
545 media_part.content_type,
546 media_part.data,
547 ));
548 }
549 for chart_part in state.charts.parts {
550 package.add_part(Part::new(
551 format!("/ppt/{}", chart_part.entry_name),
552 CHART_CONTENT_TYPE,
553 chart_part.data,
554 ));
555 }
556
557 package.add_part(Part::new(
558 CORE_PROPERTIES_PART_NAME,
559 CORE_PROPERTIES_CONTENT_TYPE,
560 to_core_properties_xml(&self.properties)?,
561 ));
562 package.add_part(Part::new(
563 EXTENDED_PROPERTIES_PART_NAME,
564 EXTENDED_PROPERTIES_CONTENT_TYPE,
565 to_extended_properties_xml(self.slides.len(), &self.properties)?,
566 ));
567 if !self.properties.custom_properties.is_empty() {
570 package.add_part(Part::new(
571 CUSTOM_PROPERTIES_PART_NAME,
572 CUSTOM_PROPERTIES_CONTENT_TYPE,
573 to_custom_properties_xml(&self.properties.custom_properties)?,
574 ));
575 }
576
577 Ok(package.write_to(writer)?)
578 }
579}
580
581struct ResolvedEmbeddedFont {
585 typeface: String,
586 regular: Option<String>,
587 bold: Option<String>,
588 italic: Option<String>,
589 bold_italic: Option<String>,
590}
591
592fn register_embedded_font_variant(
598 package: &mut Package,
599 presentation_relationships: &mut Relationships,
600 next_relationship_id: &mut u32,
601 next_font_index: &mut u32,
602 data: &Option<Vec<u8>>,
603) -> Option<String> {
604 let data = data.as_ref()?;
605 let entry_name = format!("fonts/font{next_font_index}.fntdata");
606 *next_font_index += 1;
607 let relationship_id = format!("rId{next_relationship_id}");
608 *next_relationship_id += 1;
609 presentation_relationships.add(Relationship {
610 id: relationship_id.clone(),
611 rel_type: FONT_RELATIONSHIP_TYPE.to_string(),
612 target: entry_name.clone(),
613 target_mode: TargetMode::Internal,
614 });
615 package.add_part(Part::new(
616 format!("/ppt/{entry_name}"),
617 FONT_CONTENT_TYPE,
618 data.clone(),
619 ));
620 Some(relationship_id)
621}
622
623struct PackageState {
628 media: MediaRegistry,
629 charts: ChartRegistry,
630 comment_authors: CommentAuthorRegistry,
631}
632
633impl PackageState {
634 fn new() -> Self {
635 Self {
636 media: MediaRegistry::new(),
637 charts: ChartRegistry::new(),
638 comment_authors: CommentAuthorRegistry::new(),
639 }
640 }
641}
642
643struct CommentAuthorRegistry {
649 authors: Vec<(String, String)>,
650}
651
652impl CommentAuthorRegistry {
653 fn new() -> Self {
654 Self {
655 authors: Vec::new(),
656 }
657 }
658
659 fn id_for(&mut self, name: &str, initials: &str) -> u32 {
662 if let Some(index) = self
663 .authors
664 .iter()
665 .position(|(existing_name, _)| existing_name == name)
666 {
667 return index as u32;
668 }
669 let id = self.authors.len() as u32;
670 self.authors.push((name.to_string(), initials.to_string()));
671 id
672 }
673}
674
675struct MediaRegistry {
676 next_index: usize,
677 parts: Vec<MediaPart>,
678}
679
680struct MediaPart {
681 entry_name: String,
683 content_type: &'static str,
684 data: Vec<u8>,
685}
686
687impl MediaRegistry {
688 fn new() -> Self {
689 Self {
690 next_index: 1,
691 parts: Vec::new(),
692 }
693 }
694
695 fn register(&mut self, picture: &crate::model::Picture) -> String {
696 self.register_bytes(
697 "image",
698 &picture.data,
699 picture.format.extension(),
700 picture.format.content_type(),
701 )
702 }
703
704 fn register_bytes(
714 &mut self,
715 base_name: &str,
716 data: &[u8],
717 extension: &str,
718 content_type: &'static str,
719 ) -> String {
720 let index = self.next_index;
721 self.next_index += 1;
722 let entry_name = format!("media/{base_name}{index}.{extension}");
723 self.parts.push(MediaPart {
724 entry_name: entry_name.clone(),
725 content_type,
726 data: data.to_vec(),
727 });
728 entry_name
729 }
730}
731
732struct ChartRegistry {
733 next_index: usize,
734 parts: Vec<ChartPart>,
735}
736
737struct ChartPart {
738 entry_name: String,
740 data: Vec<u8>,
741}
742
743impl ChartRegistry {
744 fn new() -> Self {
745 Self {
746 next_index: 1,
747 parts: Vec::new(),
748 }
749 }
750
751 fn register(&mut self, chart_space: &chart::ChartSpace) -> Result<String> {
752 let index = self.next_index;
753 self.next_index += 1;
754 let entry_name = format!("charts/chart{index}.xml");
755 let mut chart_writer = Writer::new(Vec::new());
756 chart::write_chart_space(&mut chart_writer, chart_space)?;
757 self.parts.push(ChartPart {
758 entry_name: entry_name.clone(),
759 data: chart_writer.into_inner(),
760 });
761 Ok(entry_name)
762 }
763}
764
765struct PartRelationships {
769 next_id: usize,
770 relationships: Relationships,
771}
772
773impl PartRelationships {
774 fn new() -> Self {
775 Self {
776 next_id: 1,
777 relationships: Relationships::new(),
778 }
779 }
780
781 fn add(&mut self, rel_type: &str, target: impl Into<String>) -> String {
782 let id = format!("rId{}", self.next_id);
783 self.next_id += 1;
784 self.relationships.add(Relationship {
785 id: id.clone(),
786 rel_type: rel_type.to_string(),
787 target: target.into(),
788 target_mode: TargetMode::Internal,
789 });
790 id
791 }
792
793 fn add_external(&mut self, rel_type: &str, target: impl Into<String>) -> String {
796 let id = format!("rId{}", self.next_id);
797 self.next_id += 1;
798 self.relationships.add(Relationship {
799 id: id.clone(),
800 rel_type: rel_type.to_string(),
801 target: target.into(),
802 target_mode: TargetMode::External,
803 });
804 id
805 }
806}
807
808fn to_presentation_xml(
810 presentation: &Presentation,
811 slide_ids: &[(u32, String)],
812 notes_master_relationship_id: Option<&str>,
813 embedded_fonts: &[ResolvedEmbeddedFont],
814) -> Result<Vec<u8>> {
815 let mut writer = Writer::new(Vec::new());
816 writer.write_event(Event::Decl(BytesDecl::new(
817 "1.0",
818 Some("UTF-8"),
819 Some("yes"),
820 )))?;
821
822 let mut root = BytesStart::new("p:presentation");
823 root.push_attribute(("xmlns:a", A_NAMESPACE));
824 root.push_attribute(("xmlns:r", R_NAMESPACE));
825 root.push_attribute(("xmlns:p", P_NAMESPACE));
826 if !embedded_fonts.is_empty() {
830 root.push_attribute(("embedTrueTypeFonts", "1"));
831 }
832 writer.write_event(Event::Start(root))?;
833
834 writer.write_event(Event::Start(BytesStart::new("p:sldMasterIdLst")))?;
835 let mut master_id = BytesStart::new("p:sldMasterId");
836 master_id.push_attribute(("id", SLIDE_MASTER_ID.to_string().as_str()));
837 master_id.push_attribute(("r:id", "rId1"));
838 writer.write_event(Event::Empty(master_id))?;
839 writer.write_event(Event::End(BytesEnd::new("p:sldMasterIdLst")))?;
840
841 if let Some(relationship_id) = notes_master_relationship_id {
846 writer.write_event(Event::Start(BytesStart::new("p:notesMasterIdLst")))?;
847 let mut notes_master_id = BytesStart::new("p:notesMasterId");
848 notes_master_id.push_attribute(("r:id", relationship_id));
849 writer.write_event(Event::Empty(notes_master_id))?;
850 writer.write_event(Event::End(BytesEnd::new("p:notesMasterIdLst")))?;
851 }
852
853 writer.write_event(Event::Start(BytesStart::new("p:sldIdLst")))?;
854 for (numeric_id, relationship_id) in slide_ids {
855 let mut slide_id = BytesStart::new("p:sldId");
856 slide_id.push_attribute(("id", numeric_id.to_string().as_str()));
857 slide_id.push_attribute(("r:id", relationship_id.as_str()));
858 writer.write_event(Event::Empty(slide_id))?;
859 }
860 writer.write_event(Event::End(BytesEnd::new("p:sldIdLst")))?;
861
862 let mut slide_size = BytesStart::new("p:sldSz");
863 slide_size.push_attribute(("cx", presentation.slide_width_emu.to_string().as_str()));
864 slide_size.push_attribute(("cy", presentation.slide_height_emu.to_string().as_str()));
865 writer.write_event(Event::Empty(slide_size))?;
866
867 let mut notes_size = BytesStart::new("p:notesSz");
868 notes_size.push_attribute(("cx", DEFAULT_NOTES_WIDTH_EMU.to_string().as_str()));
869 notes_size.push_attribute(("cy", DEFAULT_NOTES_HEIGHT_EMU.to_string().as_str()));
870 writer.write_event(Event::Empty(notes_size))?;
871
872 if !embedded_fonts.is_empty() {
878 writer.write_event(Event::Start(BytesStart::new("p:embeddedFontLst")))?;
879 for font in embedded_fonts {
880 writer.write_event(Event::Start(BytesStart::new("p:embeddedFont")))?;
881
882 let mut font_element = BytesStart::new("p:font");
883 font_element.push_attribute(("typeface", font.typeface.as_str()));
884 writer.write_event(Event::Empty(font_element))?;
885
886 let variants: [(&str, &Option<String>); 4] = [
887 ("p:regular", &font.regular),
888 ("p:bold", &font.bold),
889 ("p:italic", &font.italic),
890 ("p:boldItalic", &font.bold_italic),
891 ];
892 for (element_name, relationship_id) in variants {
893 if let Some(relationship_id) = relationship_id {
894 let mut variant = BytesStart::new(element_name);
895 variant.push_attribute(("r:id", relationship_id.as_str()));
896 writer.write_event(Event::Empty(variant))?;
897 }
898 }
899
900 writer.write_event(Event::End(BytesEnd::new("p:embeddedFont")))?;
901 }
902 writer.write_event(Event::End(BytesEnd::new("p:embeddedFontLst")))?;
903 }
904
905 writer.write_event(Event::End(BytesEnd::new("p:presentation")))?;
906 Ok(writer.into_inner())
907}
908
909fn to_slide_xml(
911 slide: &Slide,
912 state: &mut PackageState,
913 relationships: &mut PartRelationships,
914) -> Result<Vec<u8>> {
915 let mut writer = Writer::new(Vec::new());
916 writer.write_event(Event::Decl(BytesDecl::new(
917 "1.0",
918 Some("UTF-8"),
919 Some("yes"),
920 )))?;
921
922 let mut root = BytesStart::new("p:sld");
923 root.push_attribute(("xmlns:a", A_NAMESPACE));
924 root.push_attribute(("xmlns:r", R_NAMESPACE));
925 root.push_attribute(("xmlns:p", P_NAMESPACE));
926 if slide.hidden {
927 root.push_attribute(("show", "0"));
928 }
929 if slide.hide_master_graphics {
930 root.push_attribute(("showMasterSp", "0"));
931 }
932 writer.write_event(Event::Start(root))?;
933
934 let mut c_sld = BytesStart::new("p:cSld");
935 if let Some(name) = &slide.name {
936 c_sld.push_attribute(("name", name.as_str()));
937 }
938 writer.write_event(Event::Start(c_sld))?;
939 if let Some(background) = &slide.background {
940 write_slide_background(&mut writer, background)?;
941 }
942 write_shape_tree(&mut writer, &slide.shapes, state, relationships)?;
943 writer.write_event(Event::End(BytesEnd::new("p:cSld")))?;
944
945 write_color_map_override(&mut writer)?;
946
947 writer.write_event(Event::End(BytesEnd::new("p:sld")))?;
948 Ok(writer.into_inner())
949}
950
951fn write_slide_background<W: Write>(
957 writer: &mut Writer<W>,
958 background: &drawing::Fill,
959) -> Result<()> {
960 writer.write_event(Event::Start(BytesStart::new("p:bg")))?;
961 writer.write_event(Event::Start(BytesStart::new("p:bgPr")))?;
962 drawing::write_fill(writer, background)?;
963 writer.write_event(Event::End(BytesEnd::new("p:bgPr")))?;
964 writer.write_event(Event::End(BytesEnd::new("p:bg")))?;
965 Ok(())
966}
967
968fn write_color_map_override<W: Write>(writer: &mut Writer<W>) -> Result<()> {
972 writer.write_event(Event::Start(BytesStart::new("p:clrMapOvr")))?;
973 writer.write_event(Event::Empty(BytesStart::new("a:masterClrMapping")))?;
974 writer.write_event(Event::End(BytesEnd::new("p:clrMapOvr")))?;
975 Ok(())
976}
977
978fn write_shape_tree<W: Write>(
982 writer: &mut Writer<W>,
983 shapes: &[Shape],
984 state: &mut PackageState,
985 relationships: &mut PartRelationships,
986) -> Result<()> {
987 writer.write_event(Event::Start(BytesStart::new("p:spTree")))?;
988
989 writer.write_event(Event::Start(BytesStart::new("p:nvGrpSpPr")))?;
990 let mut group_cnv_pr = BytesStart::new("p:cNvPr");
991 group_cnv_pr.push_attribute(("id", "1"));
992 group_cnv_pr.push_attribute(("name", ""));
993 writer.write_event(Event::Empty(group_cnv_pr))?;
994 writer.write_event(Event::Empty(BytesStart::new("p:cNvGrpSpPr")))?;
995 writer.write_event(Event::Empty(BytesStart::new("p:nvPr")))?;
996 writer.write_event(Event::End(BytesEnd::new("p:nvGrpSpPr")))?;
997
998 writer.write_event(Event::Start(BytesStart::new("p:grpSpPr")))?;
999 writer.write_event(Event::Start(BytesStart::new("a:xfrm")))?;
1000 write_zero_point(writer, "a:off", "x", "y")?;
1001 write_zero_point(writer, "a:ext", "cx", "cy")?;
1002 write_zero_point(writer, "a:chOff", "x", "y")?;
1003 write_zero_point(writer, "a:chExt", "cx", "cy")?;
1004 writer.write_event(Event::End(BytesEnd::new("a:xfrm")))?;
1005 writer.write_event(Event::End(BytesEnd::new("p:grpSpPr")))?;
1006
1007 write_shapes(writer, shapes, state, relationships)?;
1008
1009 writer.write_event(Event::End(BytesEnd::new("p:spTree")))?;
1010 Ok(())
1011}
1012
1013fn write_shapes<W: Write>(
1018 writer: &mut Writer<W>,
1019 shapes: &[Shape],
1020 state: &mut PackageState,
1021 relationships: &mut PartRelationships,
1022) -> Result<()> {
1023 for shape in shapes {
1024 match shape {
1025 Shape::AutoShape(auto_shape) => write_auto_shape(writer, auto_shape, relationships)?,
1026 Shape::Picture(picture) => write_picture(writer, picture, state, relationships)?,
1027 Shape::Chart(slide_chart) => {
1028 write_slide_chart(writer, slide_chart, state, relationships)?
1029 }
1030 Shape::Group(group) => write_group_shape(writer, group, state, relationships)?,
1031 Shape::Connector(connector) => write_connector(writer, connector)?,
1032 Shape::Table(table) => write_table(writer, table)?,
1033 Shape::Media(media) => write_media_shape(writer, media, state, relationships)?,
1034 }
1035 }
1036 Ok(())
1037}
1038
1039fn write_zero_point<W: Write>(
1042 writer: &mut Writer<W>,
1043 element_name: &str,
1044 first_attribute: &str,
1045 second_attribute: &str,
1046) -> Result<()> {
1047 let mut start = BytesStart::new(element_name);
1048 start.push_attribute((first_attribute, "0"));
1049 start.push_attribute((second_attribute, "0"));
1050 writer.write_event(Event::Empty(start))?;
1051 Ok(())
1052}
1053
1054fn write_auto_shape<W: Write>(
1058 writer: &mut Writer<W>,
1059 shape: &AutoShape,
1060 relationships: &mut PartRelationships,
1061) -> Result<()> {
1062 writer.write_event(Event::Start(BytesStart::new("p:sp")))?;
1063
1064 writer.write_event(Event::Start(BytesStart::new("p:nvSpPr")))?;
1065 let mut cnv_pr = BytesStart::new("p:cNvPr");
1066 cnv_pr.push_attribute(("id", shape.id.to_string().as_str()));
1067 cnv_pr.push_attribute(("name", shape.name.as_str()));
1068 match &shape.hyperlink {
1069 None => {
1070 writer.write_event(Event::Empty(cnv_pr))?;
1071 }
1072 Some(target) => {
1073 writer.write_event(Event::Start(cnv_pr))?;
1074 write_hyperlink_click(writer, target, relationships)?;
1075 writer.write_event(Event::End(BytesEnd::new("p:cNvPr")))?;
1076 }
1077 }
1078
1079 let mut cnv_sp_pr = BytesStart::new("p:cNvSpPr");
1080 if shape.is_text_box {
1081 cnv_sp_pr.push_attribute(("txBox", "1"));
1082 }
1083 writer.write_event(Event::Empty(cnv_sp_pr))?;
1084 write_placeholder(writer, shape.placeholder.as_ref())?;
1085 writer.write_event(Event::End(BytesEnd::new("p:nvSpPr")))?;
1086
1087 write_shape_properties(writer, &shape.properties)?;
1088
1089 if let Some(text_body) = &shape.text_body {
1090 writer.write_event(Event::Start(BytesStart::new("p:txBody")))?;
1091 drawing::write_text_body(writer, text_body)?;
1092 writer.write_event(Event::End(BytesEnd::new("p:txBody")))?;
1093 }
1094
1095 writer.write_event(Event::End(BytesEnd::new("p:sp")))?;
1096 Ok(())
1097}
1098
1099fn write_hyperlink_click<W: Write>(
1109 writer: &mut Writer<W>,
1110 target: &SlideHyperlinkTarget,
1111 relationships: &mut PartRelationships,
1112) -> Result<()> {
1113 let mut hlink = BytesStart::new("a:hlinkClick");
1114 match target {
1115 SlideHyperlinkTarget::External(url) => {
1116 let relationship_id =
1117 relationships.add_external(HYPERLINK_RELATIONSHIP_TYPE, url.as_str());
1118 hlink.push_attribute(("r:id", relationship_id.as_str()));
1119 }
1120 SlideHyperlinkTarget::Slide(index) => {
1121 let relationship_id = relationships.add(
1122 SLIDE_RELATIONSHIP_TYPE,
1123 format!("../slides/slide{}.xml", index + 1),
1124 );
1125 hlink.push_attribute(("r:id", relationship_id.as_str()));
1126 hlink.push_attribute(("action", "ppaction://hlinksldjump"));
1127 }
1128 SlideHyperlinkTarget::NextSlide => {
1129 hlink.push_attribute(("action", "ppaction://hlinkshowjump?jump=nextslide"));
1130 }
1131 SlideHyperlinkTarget::PreviousSlide => {
1132 hlink.push_attribute(("action", "ppaction://hlinkshowjump?jump=previousslide"));
1133 }
1134 SlideHyperlinkTarget::FirstSlide => {
1135 hlink.push_attribute(("action", "ppaction://hlinkshowjump?jump=firstslide"));
1136 }
1137 SlideHyperlinkTarget::LastSlide => {
1138 hlink.push_attribute(("action", "ppaction://hlinkshowjump?jump=lastslide"));
1139 }
1140 }
1141 writer.write_event(Event::Empty(hlink))?;
1142 Ok(())
1143}
1144
1145fn write_placeholder<W: Write>(
1148 writer: &mut Writer<W>,
1149 placeholder: Option<&Placeholder>,
1150) -> Result<()> {
1151 match placeholder {
1152 None => {
1153 writer.write_event(Event::Empty(BytesStart::new("p:nvPr")))?;
1154 }
1155 Some(placeholder) => {
1156 writer.write_event(Event::Start(BytesStart::new("p:nvPr")))?;
1157 let mut placeholder_start = BytesStart::new("p:ph");
1158 placeholder_start.push_attribute(("type", placeholder.kind.xml_token()));
1159 if let Some(index) = placeholder.index {
1160 placeholder_start.push_attribute(("idx", index.to_string().as_str()));
1161 }
1162 writer.write_event(Event::Empty(placeholder_start))?;
1163 writer.write_event(Event::End(BytesEnd::new("p:nvPr")))?;
1164 }
1165 }
1166 Ok(())
1167}
1168
1169fn write_shape_properties<W: Write>(
1184 writer: &mut Writer<W>,
1185 properties: &ShapeProperties,
1186) -> Result<()> {
1187 writer.write_event(Event::Start(BytesStart::new("p:spPr")))?;
1188
1189 match &properties.transform {
1190 Some(transform) => drawing::write_transform(writer, transform)?,
1191 None => {
1192 writer.write_event(Event::Start(BytesStart::new("a:xfrm")))?;
1193 write_zero_point(writer, "a:off", "x", "y")?;
1194 write_zero_point(writer, "a:ext", "cx", "cy")?;
1195 writer.write_event(Event::End(BytesEnd::new("a:xfrm")))?;
1196 }
1197 }
1198
1199 drawing::write_geometry_fill_line_effects(writer, properties, "rect")?;
1200
1201 writer.write_event(Event::End(BytesEnd::new("p:spPr")))?;
1202 Ok(())
1203}
1204
1205fn write_picture<W: Write>(
1212 writer: &mut Writer<W>,
1213 picture: &crate::model::Picture,
1214 state: &mut PackageState,
1215 relationships: &mut PartRelationships,
1216) -> Result<()> {
1217 let entry_name = state.media.register(picture);
1218 let relationship_id = relationships.add(IMAGE_RELATIONSHIP_TYPE, format!("../{entry_name}"));
1219
1220 writer.write_event(Event::Start(BytesStart::new("p:pic")))?;
1221
1222 writer.write_event(Event::Start(BytesStart::new("p:nvPicPr")))?;
1223 let mut cnv_pr = BytesStart::new("p:cNvPr");
1224 cnv_pr.push_attribute(("id", picture.id.to_string().as_str()));
1225 cnv_pr.push_attribute(("name", picture.name.as_str()));
1226 cnv_pr.push_attribute(("descr", picture.description.as_str()));
1227 writer.write_event(Event::Empty(cnv_pr))?;
1228 writer.write_event(Event::Start(BytesStart::new("p:cNvPicPr")))?;
1229 let mut pic_locks = BytesStart::new("a:picLocks");
1230 pic_locks.push_attribute(("noChangeAspect", "1"));
1231 writer.write_event(Event::Empty(pic_locks))?;
1232 writer.write_event(Event::End(BytesEnd::new("p:cNvPicPr")))?;
1233 writer.write_event(Event::Empty(BytesStart::new("p:nvPr")))?;
1234 writer.write_event(Event::End(BytesEnd::new("p:nvPicPr")))?;
1235
1236 writer.write_event(Event::Start(BytesStart::new("p:blipFill")))?;
1237 let mut blip = BytesStart::new("a:blip");
1238 blip.push_attribute(("r:embed", relationship_id.as_str()));
1239 if let Some(external_link) = &picture.external_link {
1245 let link_relationship_id =
1246 relationships.add_external(IMAGE_RELATIONSHIP_TYPE, external_link.as_str());
1247 blip.push_attribute(("r:link", link_relationship_id.as_str()));
1248 }
1249 writer.write_event(Event::Empty(blip))?;
1250 writer.write_event(Event::Start(BytesStart::new("a:stretch")))?;
1251 writer.write_event(Event::Empty(BytesStart::new("a:fillRect")))?;
1252 writer.write_event(Event::End(BytesEnd::new("a:stretch")))?;
1253 writer.write_event(Event::End(BytesEnd::new("p:blipFill")))?;
1254
1255 writer.write_event(Event::Start(BytesStart::new("p:spPr")))?;
1256 writer.write_event(Event::Start(BytesStart::new("a:xfrm")))?;
1257 let mut off = BytesStart::new("a:off");
1258 off.push_attribute(("x", picture.offset_emu.0.to_string().as_str()));
1259 off.push_attribute(("y", picture.offset_emu.1.to_string().as_str()));
1260 writer.write_event(Event::Empty(off))?;
1261 let mut ext = BytesStart::new("a:ext");
1262 ext.push_attribute(("cx", picture.extent_emu.0.to_string().as_str()));
1263 ext.push_attribute(("cy", picture.extent_emu.1.to_string().as_str()));
1264 writer.write_event(Event::Empty(ext))?;
1265 writer.write_event(Event::End(BytesEnd::new("a:xfrm")))?;
1266
1267 let empty_properties = drawing::ShapeProperties::default();
1268 let shape_properties = picture
1269 .shape_properties
1270 .as_ref()
1271 .unwrap_or(&empty_properties);
1272 drawing::write_geometry_fill_line_effects(writer, shape_properties, "rect")?;
1273 writer.write_event(Event::End(BytesEnd::new("p:spPr")))?;
1274
1275 writer.write_event(Event::End(BytesEnd::new("p:pic")))?;
1276 Ok(())
1277}
1278
1279fn write_media_shape<W: Write>(
1287 writer: &mut Writer<W>,
1288 media: &SlideMedia,
1289 state: &mut PackageState,
1290 relationships: &mut PartRelationships,
1291) -> Result<()> {
1292 let media_entry_name = state.media.register_bytes(
1293 "media",
1294 &media.data,
1295 media.format.extension(),
1296 media.format.content_type(),
1297 );
1298 let media_relationship_type = if media.format.is_video() {
1299 VIDEO_RELATIONSHIP_TYPE
1300 } else {
1301 AUDIO_RELATIONSHIP_TYPE
1302 };
1303 let media_relationship_id =
1304 relationships.add(media_relationship_type, format!("../{media_entry_name}"));
1305
1306 writer.write_event(Event::Start(BytesStart::new("p:pic")))?;
1307
1308 writer.write_event(Event::Start(BytesStart::new("p:nvPicPr")))?;
1309 let mut cnv_pr = BytesStart::new("p:cNvPr");
1310 cnv_pr.push_attribute(("id", media.id.to_string().as_str()));
1311 cnv_pr.push_attribute(("name", media.name.as_str()));
1312 writer.write_event(Event::Empty(cnv_pr))?;
1313 writer.write_event(Event::Start(BytesStart::new("p:cNvPicPr")))?;
1314 let mut pic_locks = BytesStart::new("a:picLocks");
1315 pic_locks.push_attribute(("noChangeAspect", "1"));
1316 writer.write_event(Event::Empty(pic_locks))?;
1317 writer.write_event(Event::End(BytesEnd::new("p:cNvPicPr")))?;
1318 writer.write_event(Event::Start(BytesStart::new("p:nvPr")))?;
1319 let media_element_name = if media.format.is_video() {
1320 "a:videoFile"
1321 } else {
1322 "a:audioFile"
1323 };
1324 let mut media_start = BytesStart::new(media_element_name);
1325 media_start.push_attribute(("r:link", media_relationship_id.as_str()));
1326 writer.write_event(Event::Empty(media_start))?;
1327 writer.write_event(Event::End(BytesEnd::new("p:nvPr")))?;
1328 writer.write_event(Event::End(BytesEnd::new("p:nvPicPr")))?;
1329
1330 writer.write_event(Event::Start(BytesStart::new("p:blipFill")))?;
1331 if let Some((poster_data, poster_format)) = &media.poster_image {
1332 let poster_entry_name = state.media.register_bytes(
1333 "image",
1334 poster_data,
1335 poster_format.extension(),
1336 poster_format.content_type(),
1337 );
1338 let poster_relationship_id =
1339 relationships.add(IMAGE_RELATIONSHIP_TYPE, format!("../{poster_entry_name}"));
1340 let mut blip = BytesStart::new("a:blip");
1341 blip.push_attribute(("r:embed", poster_relationship_id.as_str()));
1342 writer.write_event(Event::Empty(blip))?;
1343 }
1344 writer.write_event(Event::Start(BytesStart::new("a:stretch")))?;
1345 writer.write_event(Event::Empty(BytesStart::new("a:fillRect")))?;
1346 writer.write_event(Event::End(BytesEnd::new("a:stretch")))?;
1347 writer.write_event(Event::End(BytesEnd::new("p:blipFill")))?;
1348
1349 writer.write_event(Event::Start(BytesStart::new("p:spPr")))?;
1350 writer.write_event(Event::Start(BytesStart::new("a:xfrm")))?;
1351 write_point(writer, "a:off", "x", "y", media.offset_emu)?;
1352 write_point(writer, "a:ext", "cx", "cy", media.extent_emu)?;
1353 writer.write_event(Event::End(BytesEnd::new("a:xfrm")))?;
1354 drawing::write_geometry_fill_line_effects(
1355 writer,
1356 &drawing::ShapeProperties::default(),
1357 "rect",
1358 )?;
1359 writer.write_event(Event::End(BytesEnd::new("p:spPr")))?;
1360
1361 writer.write_event(Event::End(BytesEnd::new("p:pic")))?;
1362 Ok(())
1363}
1364
1365fn write_slide_chart<W: Write>(
1374 writer: &mut Writer<W>,
1375 slide_chart: &crate::model::SlideChart,
1376 state: &mut PackageState,
1377 relationships: &mut PartRelationships,
1378) -> Result<()> {
1379 let entry_name = state.charts.register(&slide_chart.chart_space)?;
1380 let relationship_id = relationships.add(CHART_RELATIONSHIP_TYPE, format!("../{entry_name}"));
1381
1382 writer.write_event(Event::Start(BytesStart::new("p:graphicFrame")))?;
1383
1384 writer.write_event(Event::Start(BytesStart::new("p:nvGraphicFramePr")))?;
1385 let mut cnv_pr = BytesStart::new("p:cNvPr");
1386 cnv_pr.push_attribute(("id", slide_chart.id.to_string().as_str()));
1387 cnv_pr.push_attribute(("name", slide_chart.name.as_str()));
1388 writer.write_event(Event::Empty(cnv_pr))?;
1389 writer.write_event(Event::Empty(BytesStart::new("p:cNvGraphicFramePr")))?;
1390 writer.write_event(Event::Empty(BytesStart::new("p:nvPr")))?;
1391 writer.write_event(Event::End(BytesEnd::new("p:nvGraphicFramePr")))?;
1392
1393 writer.write_event(Event::Start(BytesStart::new("p:xfrm")))?;
1394 let mut off = BytesStart::new("a:off");
1395 off.push_attribute(("x", slide_chart.offset_emu.0.to_string().as_str()));
1396 off.push_attribute(("y", slide_chart.offset_emu.1.to_string().as_str()));
1397 writer.write_event(Event::Empty(off))?;
1398 let mut ext = BytesStart::new("a:ext");
1399 ext.push_attribute(("cx", slide_chart.extent_emu.0.to_string().as_str()));
1400 ext.push_attribute(("cy", slide_chart.extent_emu.1.to_string().as_str()));
1401 writer.write_event(Event::Empty(ext))?;
1402 writer.write_event(Event::End(BytesEnd::new("p:xfrm")))?;
1403
1404 writer.write_event(Event::Start(BytesStart::new("a:graphic")))?;
1405 let mut graphic_data = BytesStart::new("a:graphicData");
1406 graphic_data.push_attribute(("uri", CHART_NAMESPACE));
1407 writer.write_event(Event::Start(graphic_data))?;
1408 let mut c_chart = BytesStart::new("c:chart");
1409 c_chart.push_attribute(("xmlns:c", CHART_NAMESPACE));
1410 c_chart.push_attribute(("r:id", relationship_id.as_str()));
1411 writer.write_event(Event::Empty(c_chart))?;
1412 writer.write_event(Event::End(BytesEnd::new("a:graphicData")))?;
1413 writer.write_event(Event::End(BytesEnd::new("a:graphic")))?;
1414
1415 writer.write_event(Event::End(BytesEnd::new("p:graphicFrame")))?;
1416 Ok(())
1417}
1418
1419fn write_group_shape<W: Write>(
1424 writer: &mut Writer<W>,
1425 group: &ShapeGroup,
1426 state: &mut PackageState,
1427 relationships: &mut PartRelationships,
1428) -> Result<()> {
1429 writer.write_event(Event::Start(BytesStart::new("p:grpSp")))?;
1430
1431 writer.write_event(Event::Start(BytesStart::new("p:nvGrpSpPr")))?;
1432 let mut cnv_pr = BytesStart::new("p:cNvPr");
1433 cnv_pr.push_attribute(("id", group.id.to_string().as_str()));
1434 cnv_pr.push_attribute(("name", group.name.as_str()));
1435 writer.write_event(Event::Empty(cnv_pr))?;
1436 writer.write_event(Event::Empty(BytesStart::new("p:cNvGrpSpPr")))?;
1437 writer.write_event(Event::Empty(BytesStart::new("p:nvPr")))?;
1438 writer.write_event(Event::End(BytesEnd::new("p:nvGrpSpPr")))?;
1439
1440 writer.write_event(Event::Start(BytesStart::new("p:grpSpPr")))?;
1441 let mut xfrm = BytesStart::new("a:xfrm");
1442 if group.rotation_60000ths != 0 {
1443 xfrm.push_attribute(("rot", group.rotation_60000ths.to_string().as_str()));
1444 }
1445 if group.flip_horizontal {
1446 xfrm.push_attribute(("flipH", "1"));
1447 }
1448 if group.flip_vertical {
1449 xfrm.push_attribute(("flipV", "1"));
1450 }
1451 writer.write_event(Event::Start(xfrm))?;
1452 write_point(writer, "a:off", "x", "y", group.offset_emu)?;
1453 write_point(writer, "a:ext", "cx", "cy", group.extent_emu)?;
1454 write_point(writer, "a:chOff", "x", "y", group.child_offset_emu)?;
1455 write_point(writer, "a:chExt", "cx", "cy", group.child_extent_emu)?;
1456 writer.write_event(Event::End(BytesEnd::new("a:xfrm")))?;
1457 writer.write_event(Event::End(BytesEnd::new("p:grpSpPr")))?;
1458
1459 write_shapes(writer, &group.shapes, state, relationships)?;
1460
1461 writer.write_event(Event::End(BytesEnd::new("p:grpSp")))?;
1462 Ok(())
1463}
1464
1465fn write_point<W: Write>(
1469 writer: &mut Writer<W>,
1470 element_name: &str,
1471 first_attribute: &str,
1472 second_attribute: &str,
1473 (first, second): (i64, i64),
1474) -> Result<()> {
1475 let mut start = BytesStart::new(element_name);
1476 start.push_attribute((first_attribute, first.to_string().as_str()));
1477 start.push_attribute((second_attribute, second.to_string().as_str()));
1478 writer.write_event(Event::Empty(start))?;
1479 Ok(())
1480}
1481
1482fn write_connector<W: Write>(writer: &mut Writer<W>, connector: &Connector) -> Result<()> {
1485 writer.write_event(Event::Start(BytesStart::new("p:cxnSp")))?;
1486
1487 writer.write_event(Event::Start(BytesStart::new("p:nvCxnSpPr")))?;
1488 let mut cnv_pr = BytesStart::new("p:cNvPr");
1489 cnv_pr.push_attribute(("id", connector.id.to_string().as_str()));
1490 cnv_pr.push_attribute(("name", connector.name.as_str()));
1491 writer.write_event(Event::Empty(cnv_pr))?;
1492
1493 match (&connector.start_connection, &connector.end_connection) {
1494 (None, None) => {
1495 writer.write_event(Event::Empty(BytesStart::new("p:cNvCxnSpPr")))?;
1496 }
1497 _ => {
1498 writer.write_event(Event::Start(BytesStart::new("p:cNvCxnSpPr")))?;
1499 write_shape_connection(writer, "a:stCxn", connector.start_connection.as_ref())?;
1500 write_shape_connection(writer, "a:endCxn", connector.end_connection.as_ref())?;
1501 writer.write_event(Event::End(BytesEnd::new("p:cNvCxnSpPr")))?;
1502 }
1503 }
1504 writer.write_event(Event::Empty(BytesStart::new("p:nvPr")))?;
1505 writer.write_event(Event::End(BytesEnd::new("p:nvCxnSpPr")))?;
1506
1507 write_connector_properties(writer, &connector.properties)?;
1508
1509 writer.write_event(Event::End(BytesEnd::new("p:cxnSp")))?;
1510 Ok(())
1511}
1512
1513fn write_shape_connection<W: Write>(
1517 writer: &mut Writer<W>,
1518 element_name: &str,
1519 connection: Option<&crate::model::ShapeConnection>,
1520) -> Result<()> {
1521 if let Some(connection) = connection {
1522 let mut start = BytesStart::new(element_name);
1523 start.push_attribute(("id", connection.shape_id.to_string().as_str()));
1524 start.push_attribute(("idx", connection.index.to_string().as_str()));
1525 writer.write_event(Event::Empty(start))?;
1526 }
1527 Ok(())
1528}
1529
1530fn write_connector_properties<W: Write>(
1535 writer: &mut Writer<W>,
1536 properties: &ShapeProperties,
1537) -> Result<()> {
1538 writer.write_event(Event::Start(BytesStart::new("p:spPr")))?;
1539
1540 match &properties.transform {
1541 Some(transform) => drawing::write_transform(writer, transform)?,
1542 None => {
1543 writer.write_event(Event::Start(BytesStart::new("a:xfrm")))?;
1544 write_zero_point(writer, "a:off", "x", "y")?;
1545 write_zero_point(writer, "a:ext", "cx", "cy")?;
1546 writer.write_event(Event::End(BytesEnd::new("a:xfrm")))?;
1547 }
1548 }
1549
1550 drawing::write_geometry_fill_line_effects(writer, properties, "line")?;
1551
1552 writer.write_event(Event::End(BytesEnd::new("p:spPr")))?;
1553 Ok(())
1554}
1555
1556fn write_table<W: Write>(writer: &mut Writer<W>, table: &SlideTable) -> Result<()> {
1566 writer.write_event(Event::Start(BytesStart::new("p:graphicFrame")))?;
1567
1568 writer.write_event(Event::Start(BytesStart::new("p:nvGraphicFramePr")))?;
1569 let mut cnv_pr = BytesStart::new("p:cNvPr");
1570 cnv_pr.push_attribute(("id", table.id.to_string().as_str()));
1571 cnv_pr.push_attribute(("name", table.name.as_str()));
1572 writer.write_event(Event::Empty(cnv_pr))?;
1573 writer.write_event(Event::Start(BytesStart::new("p:cNvGraphicFramePr")))?;
1574 let mut locks = BytesStart::new("a:graphicFrameLocks");
1575 locks.push_attribute(("noGrp", "1"));
1576 writer.write_event(Event::Empty(locks))?;
1577 writer.write_event(Event::End(BytesEnd::new("p:cNvGraphicFramePr")))?;
1578 writer.write_event(Event::Empty(BytesStart::new("p:nvPr")))?;
1579 writer.write_event(Event::End(BytesEnd::new("p:nvGraphicFramePr")))?;
1580
1581 writer.write_event(Event::Start(BytesStart::new("p:xfrm")))?;
1582 write_point(writer, "a:off", "x", "y", table.offset_emu)?;
1583 write_point(writer, "a:ext", "cx", "cy", table.extent_emu)?;
1584 writer.write_event(Event::End(BytesEnd::new("p:xfrm")))?;
1585
1586 writer.write_event(Event::Start(BytesStart::new("a:graphic")))?;
1587 let mut graphic_data = BytesStart::new("a:graphicData");
1588 graphic_data.push_attribute(("uri", TABLE_NAMESPACE));
1589 writer.write_event(Event::Start(graphic_data))?;
1590
1591 writer.write_event(Event::Start(BytesStart::new("a:tbl")))?;
1592 let mut tbl_pr = BytesStart::new("a:tblPr");
1593 if table.style_first_row {
1594 tbl_pr.push_attribute(("firstRow", "1"));
1595 }
1596 if table.style_first_column {
1597 tbl_pr.push_attribute(("firstCol", "1"));
1598 }
1599 if table.style_last_row {
1600 tbl_pr.push_attribute(("lastRow", "1"));
1601 }
1602 if table.style_last_column {
1603 tbl_pr.push_attribute(("lastCol", "1"));
1604 }
1605 if table.style_band_rows {
1606 tbl_pr.push_attribute(("bandRow", "1"));
1607 }
1608 if table.style_band_columns {
1609 tbl_pr.push_attribute(("bandCol", "1"));
1610 }
1611 writer.write_event(Event::Start(tbl_pr))?;
1612 let table_style_id = table.style_id.as_deref().unwrap_or(TABLE_STYLE_GUID);
1613 validate_table_style_id(table_style_id)?;
1614 writer.write_event(Event::Start(BytesStart::new("a:tableStyleId")))?;
1615 writer.write_event(Event::Text(xml_core::BytesText::new(table_style_id)))?;
1616 writer.write_event(Event::End(BytesEnd::new("a:tableStyleId")))?;
1617 writer.write_event(Event::End(BytesEnd::new("a:tblPr")))?;
1618
1619 let column_count = table.column_widths_emu.len();
1620 writer.write_event(Event::Start(BytesStart::new("a:tblGrid")))?;
1621 for width_emu in &table.column_widths_emu {
1622 let mut grid_col = BytesStart::new("a:gridCol");
1623 grid_col.push_attribute(("w", width_emu.to_string().as_str()));
1624 writer.write_event(Event::Empty(grid_col))?;
1625 }
1626 writer.write_event(Event::End(BytesEnd::new("a:tblGrid")))?;
1627
1628 for row in &table.rows {
1629 write_table_row(writer, row, column_count)?;
1630 }
1631
1632 writer.write_event(Event::End(BytesEnd::new("a:tbl")))?;
1633 writer.write_event(Event::End(BytesEnd::new("a:graphicData")))?;
1634 writer.write_event(Event::End(BytesEnd::new("a:graphic")))?;
1635
1636 writer.write_event(Event::End(BytesEnd::new("p:graphicFrame")))?;
1637 Ok(())
1638}
1639
1640fn write_table_row<W: Write>(
1643 writer: &mut Writer<W>,
1644 row: &TableRow,
1645 column_count: usize,
1646) -> Result<()> {
1647 let mut tr = BytesStart::new("a:tr");
1648 tr.push_attribute(("h", row.height_emu.to_string().as_str()));
1649 writer.write_event(Event::Start(tr))?;
1650
1651 for index in 0..column_count {
1652 match row.cells.get(index) {
1653 Some(cell) => write_table_cell(writer, cell)?,
1654 None => write_table_cell(writer, &TableCell::new())?,
1655 }
1656 }
1657
1658 writer.write_event(Event::End(BytesEnd::new("a:tr")))?;
1659 Ok(())
1660}
1661
1662fn write_table_cell<W: Write>(writer: &mut Writer<W>, cell: &TableCell) -> Result<()> {
1667 let mut tc = BytesStart::new("a:tc");
1668 if let Some(span) = cell.horizontal_span {
1669 tc.push_attribute(("gridSpan", span.to_string().as_str()));
1670 }
1671 if let Some(span) = cell.vertical_span {
1672 tc.push_attribute(("rowSpan", span.to_string().as_str()));
1673 }
1674 if cell.horizontal_merge {
1675 tc.push_attribute(("hMerge", "1"));
1676 }
1677 if cell.vertical_merge {
1678 tc.push_attribute(("vMerge", "1"));
1679 }
1680 writer.write_event(Event::Start(tc))?;
1681
1682 writer.write_event(Event::Start(BytesStart::new("a:txBody")))?;
1683 match &cell.text_body {
1684 Some(text_body) => drawing::write_text_body(writer, text_body)?,
1685 None => {
1686 drawing::write_text_body(
1690 writer,
1691 &drawing::TextBody::new().with_paragraph(drawing::TextParagraph::new()),
1692 )?;
1693 }
1694 }
1695 writer.write_event(Event::End(BytesEnd::new("a:txBody")))?;
1696 write_table_cell_properties(writer, cell.properties.as_ref())?;
1697
1698 writer.write_event(Event::End(BytesEnd::new("a:tc")))?;
1699 Ok(())
1700}
1701
1702fn write_table_cell_properties<W: Write>(
1708 writer: &mut Writer<W>,
1709 properties: Option<&TableCellProperties>,
1710) -> Result<()> {
1711 let Some(properties) = properties else {
1712 writer.write_event(Event::Empty(BytesStart::new("a:tcPr")))?;
1713 return Ok(());
1714 };
1715
1716 let mut start = BytesStart::new("a:tcPr");
1717 if let Some(margin) = properties.margin_left_emu {
1718 start.push_attribute(("marL", margin.to_string().as_str()));
1719 }
1720 if let Some(margin) = properties.margin_top_emu {
1721 start.push_attribute(("marT", margin.to_string().as_str()));
1722 }
1723 if let Some(margin) = properties.margin_right_emu {
1724 start.push_attribute(("marR", margin.to_string().as_str()));
1725 }
1726 if let Some(margin) = properties.margin_bottom_emu {
1727 start.push_attribute(("marB", margin.to_string().as_str()));
1728 }
1729 if let Some(anchor) = &properties.anchor {
1730 start.push_attribute(("anchor", table_cell_anchor_token(anchor)));
1731 }
1732
1733 let has_children = properties.border_left.is_some()
1734 || properties.border_right.is_some()
1735 || properties.border_top.is_some()
1736 || properties.border_bottom.is_some()
1737 || properties.fill.is_some();
1738 if !has_children {
1739 writer.write_event(Event::Empty(start))?;
1740 return Ok(());
1741 }
1742
1743 writer.write_event(Event::Start(start))?;
1744 if let Some(border) = &properties.border_left {
1745 drawing::write_line_named(writer, "a:lnL", border)?;
1746 }
1747 if let Some(border) = &properties.border_right {
1748 drawing::write_line_named(writer, "a:lnR", border)?;
1749 }
1750 if let Some(border) = &properties.border_top {
1751 drawing::write_line_named(writer, "a:lnT", border)?;
1752 }
1753 if let Some(border) = &properties.border_bottom {
1754 drawing::write_line_named(writer, "a:lnB", border)?;
1755 }
1756 if let Some(fill) = &properties.fill {
1757 drawing::write_fill(writer, fill)?;
1758 }
1759 writer.write_event(Event::End(BytesEnd::new("a:tcPr")))?;
1760 Ok(())
1761}
1762
1763fn table_cell_anchor_token(anchor: &TextAnchor) -> &'static str {
1769 match anchor {
1770 TextAnchor::Top => "t",
1771 TextAnchor::Center => "ctr",
1772 TextAnchor::Bottom => "b",
1773 TextAnchor::Justified => "just",
1774 TextAnchor::Distributed => "dist",
1775 }
1776}
1777
1778fn to_slide_master_xml() -> Result<Vec<u8>> {
1783 let mut writer = Writer::new(Vec::new());
1784 writer.write_event(Event::Decl(BytesDecl::new(
1785 "1.0",
1786 Some("UTF-8"),
1787 Some("yes"),
1788 )))?;
1789
1790 let mut root = BytesStart::new("p:sldMaster");
1791 root.push_attribute(("xmlns:a", A_NAMESPACE));
1792 root.push_attribute(("xmlns:r", R_NAMESPACE));
1793 root.push_attribute(("xmlns:p", P_NAMESPACE));
1794 writer.write_event(Event::Start(root))?;
1795
1796 writer.write_event(Event::Start(BytesStart::new("p:cSld")))?;
1797 write_shape_tree(
1798 &mut writer,
1799 &[],
1800 &mut PackageState::new(),
1801 &mut PartRelationships::new(),
1802 )?;
1803 writer.write_event(Event::End(BytesEnd::new("p:cSld")))?;
1804
1805 write_default_color_map(&mut writer)?;
1806
1807 writer.write_event(Event::Start(BytesStart::new("p:sldLayoutIdLst")))?;
1808 let mut layout_id = BytesStart::new("p:sldLayoutId");
1809 layout_id.push_attribute(("id", (SLIDE_MASTER_ID + 1).to_string().as_str()));
1810 layout_id.push_attribute(("r:id", "rId1"));
1811 writer.write_event(Event::Empty(layout_id))?;
1812 writer.write_event(Event::End(BytesEnd::new("p:sldLayoutIdLst")))?;
1813
1814 writer.write_event(Event::End(BytesEnd::new("p:sldMaster")))?;
1815 Ok(writer.into_inner())
1816}
1817
1818fn write_default_color_map<W: Write>(writer: &mut Writer<W>) -> Result<()> {
1822 let mut clr_map = BytesStart::new("p:clrMap");
1823 clr_map.push_attribute(("bg1", "lt1"));
1824 clr_map.push_attribute(("tx1", "dk1"));
1825 clr_map.push_attribute(("bg2", "lt2"));
1826 clr_map.push_attribute(("tx2", "dk2"));
1827 clr_map.push_attribute(("accent1", "accent1"));
1828 clr_map.push_attribute(("accent2", "accent2"));
1829 clr_map.push_attribute(("accent3", "accent3"));
1830 clr_map.push_attribute(("accent4", "accent4"));
1831 clr_map.push_attribute(("accent5", "accent5"));
1832 clr_map.push_attribute(("accent6", "accent6"));
1833 clr_map.push_attribute(("hlink", "hlink"));
1834 clr_map.push_attribute(("folHlink", "folHlink"));
1835 writer.write_event(Event::Empty(clr_map))?;
1836 Ok(())
1837}
1838
1839fn to_slide_layout_xml() -> Result<Vec<u8>> {
1843 let mut writer = Writer::new(Vec::new());
1844 writer.write_event(Event::Decl(BytesDecl::new(
1845 "1.0",
1846 Some("UTF-8"),
1847 Some("yes"),
1848 )))?;
1849
1850 let mut root = BytesStart::new("p:sldLayout");
1851 root.push_attribute(("xmlns:a", A_NAMESPACE));
1852 root.push_attribute(("xmlns:r", R_NAMESPACE));
1853 root.push_attribute(("xmlns:p", P_NAMESPACE));
1854 writer.write_event(Event::Start(root))?;
1855
1856 writer.write_event(Event::Start(BytesStart::new("p:cSld")))?;
1857 write_shape_tree(
1858 &mut writer,
1859 &[],
1860 &mut PackageState::new(),
1861 &mut PartRelationships::new(),
1862 )?;
1863 writer.write_event(Event::End(BytesEnd::new("p:cSld")))?;
1864
1865 write_color_map_override(&mut writer)?;
1866
1867 writer.write_event(Event::End(BytesEnd::new("p:sldLayout")))?;
1868 Ok(writer.into_inner())
1869}
1870
1871fn to_theme_xml(theme: Option<&Theme>) -> Vec<u8> {
1881 let owned_default;
1882 let theme = match theme {
1883 Some(theme) => theme,
1884 None => {
1885 owned_default = Theme::office_default();
1886 &owned_default
1887 }
1888 };
1889
1890 let mut xml = String::new();
1891 xml.push_str(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#);
1892 xml.push_str(&format!(
1893 r#"<a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="{}">"#,
1894 xml_escape(&theme.name)
1895 ));
1896 xml.push_str("<a:themeElements>");
1897 xml.push_str(&to_color_scheme_xml(&theme.name, &theme.colors));
1898 xml.push_str(&to_font_scheme_xml(&theme.name, &theme.fonts));
1899 xml.push_str(FIXED_FMT_SCHEME_XML);
1900 xml.push_str("</a:themeElements>");
1901 xml.push_str("</a:theme>");
1902 xml.into_bytes()
1903}
1904
1905fn to_color_scheme_xml(name: &str, colors: &ColorScheme) -> String {
1909 format!(
1910 concat!(
1911 r#"<a:clrScheme name="{name}">"#,
1912 r#"<a:dk1><a:srgbClr val="{dk1}"/></a:dk1>"#,
1913 r#"<a:lt1><a:srgbClr val="{lt1}"/></a:lt1>"#,
1914 r#"<a:dk2><a:srgbClr val="{dk2}"/></a:dk2>"#,
1915 r#"<a:lt2><a:srgbClr val="{lt2}"/></a:lt2>"#,
1916 r#"<a:accent1><a:srgbClr val="{accent1}"/></a:accent1>"#,
1917 r#"<a:accent2><a:srgbClr val="{accent2}"/></a:accent2>"#,
1918 r#"<a:accent3><a:srgbClr val="{accent3}"/></a:accent3>"#,
1919 r#"<a:accent4><a:srgbClr val="{accent4}"/></a:accent4>"#,
1920 r#"<a:accent5><a:srgbClr val="{accent5}"/></a:accent5>"#,
1921 r#"<a:accent6><a:srgbClr val="{accent6}"/></a:accent6>"#,
1922 r#"<a:hlink><a:srgbClr val="{hlink}"/></a:hlink>"#,
1923 r#"<a:folHlink><a:srgbClr val="{fol_hlink}"/></a:folHlink>"#,
1924 "</a:clrScheme>",
1925 ),
1926 name = xml_escape(name),
1927 dk1 = colors.dark1,
1928 lt1 = colors.light1,
1929 dk2 = colors.dark2,
1930 lt2 = colors.light2,
1931 accent1 = colors.accent1,
1932 accent2 = colors.accent2,
1933 accent3 = colors.accent3,
1934 accent4 = colors.accent4,
1935 accent5 = colors.accent5,
1936 accent6 = colors.accent6,
1937 hlink = colors.hyperlink,
1938 fol_hlink = colors.followed_hyperlink,
1939 )
1940}
1941
1942fn to_font_scheme_xml(name: &str, fonts: &FontScheme) -> String {
1945 format!(
1946 concat!(
1947 r#"<a:fontScheme name="{name}">"#,
1948 r#"<a:majorFont><a:latin typeface="{major}"/><a:ea typeface=""/><a:cs typeface=""/></a:majorFont>"#,
1949 r#"<a:minorFont><a:latin typeface="{minor}"/><a:ea typeface=""/><a:cs typeface=""/></a:minorFont>"#,
1950 "</a:fontScheme>",
1951 ),
1952 name = xml_escape(name),
1953 major = xml_escape(&fonts.major_latin),
1954 minor = xml_escape(&fonts.minor_latin),
1955 )
1956}
1957
1958fn xml_escape(value: &str) -> String {
1962 value
1963 .replace('&', "&")
1964 .replace('<', "<")
1965 .replace('>', ">")
1966 .replace('"', """)
1967}
1968
1969const FIXED_FMT_SCHEME_XML: &str = concat!(
1972 r#"<a:fmtScheme name="Office">"#,
1973 "<a:fillStyleLst>",
1974 r#"<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>"#,
1975 r#"<a:gradFill rotWithShape="1"><a:gsLst>"#,
1976 r#"<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="50000"/><a:satMod val="300000"/></a:schemeClr></a:gs>"#,
1977 r#"<a:gs pos="35000"><a:schemeClr val="phClr"><a:tint val="37000"/><a:satMod val="300000"/></a:schemeClr></a:gs>"#,
1978 r#"<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="15000"/><a:satMod val="350000"/></a:schemeClr></a:gs>"#,
1979 r#"</a:gsLst><a:lin ang="16200000" scaled="1"/></a:gradFill>"#,
1980 r#"<a:gradFill rotWithShape="1"><a:gsLst>"#,
1981 r#"<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="100000"/><a:shade val="100000"/><a:satMod val="130000"/></a:schemeClr></a:gs>"#,
1982 r#"<a:gs pos="100000"><a:schemeClr val="phClr"><a:tint val="50000"/><a:shade val="100000"/><a:satMod val="350000"/></a:schemeClr></a:gs>"#,
1983 r#"</a:gsLst><a:lin ang="16200000" scaled="0"/></a:gradFill>"#,
1984 "</a:fillStyleLst>",
1985 "<a:lnStyleLst>",
1986 r#"<a:ln w="9525" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"><a:shade val="95000"/><a:satMod val="105000"/></a:schemeClr></a:solidFill><a:prstDash val="solid"/></a:ln>"#,
1987 r#"<a:ln w="25400" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>"#,
1988 r#"<a:ln w="38100" cap="flat" cmpd="sng" algn="ctr"><a:solidFill><a:schemeClr val="phClr"/></a:solidFill><a:prstDash val="solid"/></a:ln>"#,
1989 "</a:lnStyleLst>",
1990 "<a:effectStyleLst>",
1991 r#"<a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="38000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle>"#,
1992 r#"<a:effectStyle><a:effectLst><a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst></a:effectStyle>"#,
1993 "<a:effectStyle>",
1994 r#"<a:effectLst><a:outerShdw blurRad="40000" dist="23000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="35000"/></a:srgbClr></a:outerShdw></a:effectLst>"#,
1995 r#"<a:scene3d><a:camera prst="orthographicFront"><a:rot lat="0" lon="0" rev="0"/></a:camera><a:lightRig rig="threePt" dir="t"><a:rot lat="0" lon="0" rev="1200000"/></a:lightRig></a:scene3d>"#,
1996 r#"<a:sp3d><a:bevelT w="63500" h="25400"/></a:sp3d>"#,
1997 "</a:effectStyle>",
1998 "</a:effectStyleLst>",
1999 "<a:bgFillStyleLst>",
2000 r#"<a:solidFill><a:schemeClr val="phClr"/></a:solidFill>"#,
2001 r#"<a:gradFill rotWithShape="1"><a:gsLst>"#,
2002 r#"<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="40000"/><a:satMod val="350000"/></a:schemeClr></a:gs>"#,
2003 r#"<a:gs pos="40000"><a:schemeClr val="phClr"><a:tint val="45000"/><a:shade val="99000"/><a:satMod val="350000"/></a:schemeClr></a:gs>"#,
2004 r#"<a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="20000"/><a:satMod val="255000"/></a:schemeClr></a:gs>"#,
2005 r#"</a:gsLst><a:path path="circle"><a:fillToRect l="50000" t="-80000" r="50000" b="180000"/></a:path></a:gradFill>"#,
2006 r#"<a:gradFill rotWithShape="1"><a:gsLst>"#,
2007 r#"<a:gs pos="0"><a:schemeClr val="phClr"><a:tint val="80000"/><a:satMod val="300000"/></a:schemeClr></a:gs>"#,
2008 r#"<a:gs pos="100000"><a:schemeClr val="phClr"><a:shade val="30000"/><a:satMod val="200000"/></a:schemeClr></a:gs>"#,
2009 r#"</a:gsLst><a:path path="circle"><a:fillToRect l="50000" t="50000" r="50000" b="50000"/></a:path></a:gradFill>"#,
2010 "</a:bgFillStyleLst>",
2011 "</a:fmtScheme>",
2012);
2013
2014fn to_pres_props_xml() -> Vec<u8> {
2017 const XML: &str = concat!(
2018 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#,
2019 r#"<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"/>"#,
2020 );
2021 XML.as_bytes().to_vec()
2022}
2023
2024fn to_view_props_xml() -> Vec<u8> {
2027 const XML: &str = concat!(
2028 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#,
2029 r#"<p:viewPr 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"/>"#,
2030 );
2031 XML.as_bytes().to_vec()
2032}
2033
2034fn to_table_styles_xml(table_styles: &[SlideTableStyle]) -> Result<Vec<u8>> {
2042 let mut writer = Writer::new(Vec::new());
2043 writer.write_event(Event::Decl(BytesDecl::new(
2044 "1.0",
2045 Some("UTF-8"),
2046 Some("yes"),
2047 )))?;
2048
2049 let mut root = BytesStart::new("a:tblStyleLst");
2050 root.push_attribute(("xmlns:a", A_NAMESPACE));
2051 root.push_attribute(("def", TABLE_STYLE_GUID));
2052
2053 if table_styles.is_empty() {
2054 writer.write_event(Event::Empty(root))?;
2055 return Ok(writer.into_inner());
2056 }
2057
2058 writer.write_event(Event::Start(root))?;
2059 for style in table_styles {
2060 write_table_style(&mut writer, style)?;
2061 }
2062 writer.write_event(Event::End(BytesEnd::new("a:tblStyleLst")))?;
2063 Ok(writer.into_inner())
2064}
2065
2066fn write_table_style<W: Write>(writer: &mut Writer<W>, style: &SlideTableStyle) -> Result<()> {
2070 validate_table_style_id(&style.id)?;
2071 let mut start = BytesStart::new("a:tblStyle");
2072 start.push_attribute(("styleId", style.id.as_str()));
2073 start.push_attribute(("styleName", style.name.as_str()));
2074 writer.write_event(Event::Start(start))?;
2075
2076 let parts: [(&str, &Option<TableStylePart>); 9] = [
2077 ("a:wholeTbl", &style.whole_table),
2078 ("a:band1H", &style.band1_horizontal),
2079 ("a:band2H", &style.band2_horizontal),
2080 ("a:band1V", &style.band1_vertical),
2081 ("a:band2V", &style.band2_vertical),
2082 ("a:firstRow", &style.first_row),
2083 ("a:lastRow", &style.last_row),
2084 ("a:firstCol", &style.first_column),
2085 ("a:lastCol", &style.last_column),
2086 ];
2087 for (element_name, part) in parts {
2088 if let Some(part) = part {
2089 write_table_style_part(writer, element_name, part)?;
2090 }
2091 }
2092
2093 writer.write_event(Event::End(BytesEnd::new("a:tblStyle")))?;
2094 Ok(())
2095}
2096
2097fn write_table_style_part<W: Write>(
2101 writer: &mut Writer<W>,
2102 element_name: &str,
2103 part: &TableStylePart,
2104) -> Result<()> {
2105 writer.write_event(Event::Start(BytesStart::new(element_name)))?;
2106
2107 let mut tc_tx_style = BytesStart::new("a:tcTxStyle");
2108 if part.bold {
2109 tc_tx_style.push_attribute(("b", "on"));
2110 }
2111 if part.italic {
2112 tc_tx_style.push_attribute(("i", "on"));
2113 }
2114 if let Some(color) = &part.text_color {
2115 writer.write_event(Event::Start(tc_tx_style))?;
2116 drawing::write_color(writer, color)?;
2117 writer.write_event(Event::End(BytesEnd::new("a:tcTxStyle")))?;
2118 } else {
2119 writer.write_event(Event::Empty(tc_tx_style))?;
2120 }
2121
2122 if let Some(fill) = &part.fill {
2123 writer.write_event(Event::Start(BytesStart::new("a:tcStyle")))?;
2124 writer.write_event(Event::Start(BytesStart::new("a:fill")))?;
2125 drawing::write_fill(writer, fill)?;
2126 writer.write_event(Event::End(BytesEnd::new("a:fill")))?;
2127 writer.write_event(Event::End(BytesEnd::new("a:tcStyle")))?;
2128 } else {
2129 writer.write_event(Event::Empty(BytesStart::new("a:tcStyle")))?;
2130 }
2131
2132 writer.write_event(Event::End(BytesEnd::new(element_name)))?;
2133 Ok(())
2134}
2135
2136fn to_core_properties_xml(properties: &DocumentProperties) -> Result<Vec<u8>> {
2141 let mut writer = Writer::new(Vec::new());
2142 writer.write_event(Event::Decl(BytesDecl::new(
2143 "1.0",
2144 Some("UTF-8"),
2145 Some("yes"),
2146 )))?;
2147
2148 let mut root = BytesStart::new("cp:coreProperties");
2149 root.push_attribute((
2150 "xmlns:cp",
2151 "http://schemas.openxmlformats.org/package/2006/metadata/core-properties",
2152 ));
2153 root.push_attribute(("xmlns:dc", "http://purl.org/dc/elements/1.1/"));
2154 root.push_attribute(("xmlns:dcterms", "http://purl.org/dc/terms/"));
2155 root.push_attribute(("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"));
2156
2157 let has_any = properties.title.is_some()
2158 || properties.author.is_some()
2159 || properties.subject.is_some()
2160 || properties.keywords.is_some();
2161 if !has_any {
2162 writer.write_event(Event::Empty(root))?;
2163 return Ok(writer.into_inner());
2164 }
2165 writer.write_event(Event::Start(root))?;
2166
2167 if let Some(title) = &properties.title {
2168 write_text_element(&mut writer, "dc:title", title)?;
2169 }
2170 if let Some(author) = &properties.author {
2171 write_text_element(&mut writer, "dc:creator", author)?;
2172 }
2173 if let Some(subject) = &properties.subject {
2174 write_text_element(&mut writer, "dc:subject", subject)?;
2175 }
2176 if let Some(keywords) = &properties.keywords {
2177 write_text_element(&mut writer, "cp:keywords", keywords)?;
2178 }
2179
2180 writer.write_event(Event::End(BytesEnd::new("cp:coreProperties")))?;
2181 Ok(writer.into_inner())
2182}
2183
2184fn to_extended_properties_xml(
2190 slide_count: usize,
2191 properties: &DocumentProperties,
2192) -> Result<Vec<u8>> {
2193 let mut writer = Writer::new(Vec::new());
2194 writer.write_event(Event::Decl(BytesDecl::new(
2195 "1.0",
2196 Some("UTF-8"),
2197 Some("yes"),
2198 )))?;
2199
2200 let mut root = BytesStart::new("Properties");
2201 root.push_attribute((
2202 "xmlns",
2203 "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties",
2204 ));
2205 writer.write_event(Event::Start(root))?;
2206
2207 write_text_element(&mut writer, "Application", "office-toolkit")?;
2208 write_text_element(&mut writer, "Slides", slide_count.to_string().as_str())?;
2209 if let Some(company) = &properties.company {
2210 write_text_element(&mut writer, "Company", company)?;
2211 }
2212 if let Some(manager) = &properties.manager {
2213 write_text_element(&mut writer, "Manager", manager)?;
2214 }
2215 if let Some(hyperlink_base) = &properties.hyperlink_base {
2216 write_text_element(&mut writer, "HyperlinkBase", hyperlink_base)?;
2217 }
2218
2219 writer.write_event(Event::End(BytesEnd::new("Properties")))?;
2220 Ok(writer.into_inner())
2221}
2222
2223fn to_custom_properties_xml(
2228 custom_properties: &[(String, CustomPropertyValue)],
2229) -> Result<Vec<u8>> {
2230 let mut writer = Writer::new(Vec::new());
2231 writer.write_event(Event::Decl(BytesDecl::new(
2232 "1.0",
2233 Some("UTF-8"),
2234 Some("yes"),
2235 )))?;
2236
2237 let mut root = BytesStart::new("Properties");
2238 root.push_attribute(("xmlns", CUSTOM_PROPERTIES_NAMESPACE));
2239 root.push_attribute(("xmlns:vt", CUSTOM_PROPERTIES_VT_NAMESPACE));
2240 writer.write_event(Event::Start(root))?;
2241
2242 for (index, (name, value)) in custom_properties.iter().enumerate() {
2243 let pid = FIRST_CUSTOM_PROPERTY_PID + index as i32;
2244
2245 let mut property = BytesStart::new("property");
2246 property.push_attribute(("fmtid", CUSTOM_PROPERTY_FMTID));
2247 property.push_attribute(("pid", pid.to_string().as_str()));
2248 property.push_attribute(("name", name.as_str()));
2249 writer.write_event(Event::Start(property))?;
2250
2251 let (variant_element, text) = match value {
2252 CustomPropertyValue::Text(text) => ("vt:lpwstr", text.clone()),
2253 CustomPropertyValue::Bool(value) => (
2254 "vt:bool",
2255 if *value {
2256 "true".to_string()
2257 } else {
2258 "false".to_string()
2259 },
2260 ),
2261 CustomPropertyValue::Int(value) => ("vt:i4", value.to_string()),
2262 CustomPropertyValue::Number(value) => ("vt:r8", value.to_string()),
2263 };
2264 write_text_element(&mut writer, variant_element, &text)?;
2265
2266 writer.write_event(Event::End(BytesEnd::new("property")))?;
2267 }
2268
2269 writer.write_event(Event::End(BytesEnd::new("Properties")))?;
2270 Ok(writer.into_inner())
2271}
2272
2273fn write_text_element<W: Write>(
2276 writer: &mut Writer<W>,
2277 element_name: &str,
2278 text: &str,
2279) -> Result<()> {
2280 writer.write_event(Event::Start(BytesStart::new(element_name)))?;
2281 writer.write_event(Event::Text(xml_core::BytesText::new(text)))?;
2282 writer.write_event(Event::End(BytesEnd::new(element_name)))?;
2283 Ok(())
2284}
2285
2286fn to_notes_master_xml() -> Result<Vec<u8>> {
2292 let mut writer = Writer::new(Vec::new());
2293 writer.write_event(Event::Decl(BytesDecl::new(
2294 "1.0",
2295 Some("UTF-8"),
2296 Some("yes"),
2297 )))?;
2298
2299 let mut root = BytesStart::new("p:notesMaster");
2300 root.push_attribute(("xmlns:a", A_NAMESPACE));
2301 root.push_attribute(("xmlns:r", R_NAMESPACE));
2302 root.push_attribute(("xmlns:p", P_NAMESPACE));
2303 writer.write_event(Event::Start(root))?;
2304
2305 writer.write_event(Event::Start(BytesStart::new("p:cSld")))?;
2306 write_shape_tree(
2307 &mut writer,
2308 &[],
2309 &mut PackageState::new(),
2310 &mut PartRelationships::new(),
2311 )?;
2312 writer.write_event(Event::End(BytesEnd::new("p:cSld")))?;
2313
2314 write_default_color_map(&mut writer)?;
2315
2316 writer.write_event(Event::End(BytesEnd::new("p:notesMaster")))?;
2317 Ok(writer.into_inner())
2318}
2319
2320fn to_notes_slide_xml(notes: &drawing::TextBody) -> Result<Vec<u8>> {
2330 let mut writer = Writer::new(Vec::new());
2331 writer.write_event(Event::Decl(BytesDecl::new(
2332 "1.0",
2333 Some("UTF-8"),
2334 Some("yes"),
2335 )))?;
2336
2337 let mut root = BytesStart::new("p:notes");
2338 root.push_attribute(("xmlns:a", A_NAMESPACE));
2339 root.push_attribute(("xmlns:r", R_NAMESPACE));
2340 root.push_attribute(("xmlns:p", P_NAMESPACE));
2341 writer.write_event(Event::Start(root))?;
2342
2343 let slide_image_shape = Shape::AutoShape(
2344 AutoShape::new(2, "Image de la diapositive")
2345 .with_placeholder(Placeholder::new(PlaceholderKind::SlideImage)),
2346 );
2347 let body_shape = Shape::AutoShape(
2348 AutoShape::new(3, "Texte de la note")
2349 .with_placeholder(Placeholder::new(PlaceholderKind::Body).with_index(1))
2350 .with_text_body(notes.clone()),
2351 );
2352
2353 writer.write_event(Event::Start(BytesStart::new("p:cSld")))?;
2354 write_shape_tree(
2355 &mut writer,
2356 &[slide_image_shape, body_shape],
2357 &mut PackageState::new(),
2358 &mut PartRelationships::new(),
2359 )?;
2360 writer.write_event(Event::End(BytesEnd::new("p:cSld")))?;
2361
2362 write_color_map_override(&mut writer)?;
2363
2364 writer.write_event(Event::End(BytesEnd::new("p:notes")))?;
2365 Ok(writer.into_inner())
2366}
2367
2368fn to_comments_xml(comments: &[SlideComment], authors: &mut CommentAuthorRegistry) -> Vec<u8> {
2373 let mut xml = String::new();
2374 xml.push_str(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#);
2375 xml.push_str(r#"<p:cmLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">"#);
2376 for comment in comments {
2377 let author_id = authors.id_for(&comment.author, &comment.initials);
2378 xml.push_str(&format!(
2379 r#"<p:cm authorId="{author_id}" dt="{dt}" idx="1"><p:pos x="{x}" y="{y}"/><p:text>{text}</p:text></p:cm>"#,
2380 author_id = author_id,
2381 dt = xml_escape(&comment.date),
2382 x = comment.position_emu.0,
2383 y = comment.position_emu.1,
2384 text = xml_escape(&comment.text),
2385 ));
2386 }
2387 xml.push_str("</p:cmLst>");
2388 xml.into_bytes()
2389}
2390
2391fn to_comment_authors_xml(authors: &CommentAuthorRegistry) -> Vec<u8> {
2399 let mut xml = String::new();
2400 xml.push_str(r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"#);
2401 xml.push_str(
2402 r#"<p:cmAuthorLst xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">"#,
2403 );
2404 for (id, (name, initials)) in authors.authors.iter().enumerate() {
2405 xml.push_str(&format!(
2406 r#"<p:cmAuthor id="{id}" name="{name}" initials="{initials}" lastIdx="1" clrIdx="{clr_idx}"/>"#,
2407 id = id,
2408 name = xml_escape(name),
2409 initials = xml_escape(initials),
2410 clr_idx = id % 4,
2411 ));
2412 }
2413 xml.push_str("</p:cmAuthorLst>");
2414 xml.into_bytes()
2415}