1use std::sync::Arc;
5
6use deckcraft_color::SchemeSlot;
7use deckcraft_geom::{Size, Xfrm};
8
9use crate::style::ColorRef;
10use crate::text::{
11 Anchor, AutoFit, BodyProps, Bullet, LevelStyle, ListStyle, ParaProps, RunProps, Spacing,
12 TextBody, TextDir,
13};
14use crate::theme::Theme;
15use crate::{
16 Background, Fill, HeaderFooter, Layout, LayoutId, LayoutType, Master, MasterId, PhType,
17 Placeholder, Presentation, Shape, ShapeId, ShapeKind, Slide, SlideId,
18};
19
20pub const WIDE: Size = Size::new(960.0, 540.0);
21pub const STANDARD: Size = Size::new(720.0, 540.0);
22
23pub const SLIDE_SIZES: &[(&str, f64, f64)] = &[
25 ("Widescreen (16:9)", 960.0, 540.0),
26 ("Standard (4:3)", 720.0, 540.0),
27 ("On-screen Show (16:10)", 720.0, 450.0),
28 ("Letter Paper (8.5x11 in)", 720.0, 540.0),
29 ("Ledger Paper (11x17 in)", 960.0, 720.0),
30 ("A3 Paper (297x420 mm)", 1008.0, 756.0),
31 ("A4 Paper (210x297 mm)", 780.0, 540.0),
32 ("B4 (ISO) Paper (250x353 mm)", 852.0, 639.0),
33 ("B5 (ISO) Paper (176x250 mm)", 598.0, 448.5),
34 ("35mm Slides", 810.0, 540.0),
35 ("Overhead", 720.0, 540.0),
36 ("Banner", 576.0, 72.0),
37 ("Widescreen (13.333x7.5 in)", 960.0, 540.0),
38 ("Square (1:1)", 540.0, 540.0),
39 ("Portrait (9:16)", 540.0, 960.0),
40];
41
42fn ph(kind: PhType, idx: u32) -> Placeholder {
43 Placeholder {
44 kind,
45 idx,
46 ..Default::default()
47 }
48}
49
50fn body_with(anchor: Option<Anchor>, autofit: Option<AutoFit>) -> TextBody {
51 TextBody {
52 body: BodyProps {
53 anchor,
54 autofit,
55 ..Default::default()
56 },
57 ..Default::default()
58 }
59}
60
61struct B {
62 next: u32,
63}
64
65impl B {
66 fn shape(
67 &mut self,
68 name: &str,
69 kind: PhType,
70 idx: u32,
71 x: f64,
72 y: f64,
73 w: f64,
74 h: f64,
75 ) -> Shape {
76 self.next += 1;
77 Shape {
78 id: ShapeId(self.next),
79 name: name.into(),
80 xfrm: Some(Xfrm::new(x, y, w, h)),
81 ph: Some(ph(kind, idx)),
82 text: Some(TextBody::default()),
83 ..Default::default()
84 }
85 }
86}
87
88fn level(size: f64, margin: f64, indent: f64, before: f64) -> LevelStyle {
89 LevelStyle {
90 para: ParaProps {
91 align: Some(crate::text::Align::Left),
92 margin_left: Some(margin),
93 indent: Some(indent),
94 line_spacing: Some(Spacing::Pct(0.9)),
95 space_before: Some(Spacing::Pts(before)),
96 bullet: Some(Bullet::Char { char: "•".into() }),
97 bullet_font: Some("Arial".into()),
98 ..Default::default()
99 },
100 run: RunProps {
101 size: Some(size),
102 font: Some("+mn-lt".into()),
103 fill: Some(Fill::solid(ColorRef::scheme(SchemeSlot::Tx1))),
104 kern: Some(12.0),
105 ..Default::default()
106 },
107 }
108}
109
110pub fn master_styles() -> (ListStyle, ListStyle, ListStyle) {
112 let mut title = ListStyle::default();
113 title.set(
114 0,
115 LevelStyle {
116 para: ParaProps {
117 align: Some(crate::text::Align::Left),
118 line_spacing: Some(Spacing::Pct(0.9)),
119 space_before: Some(Spacing::Pts(0.0)),
120 bullet: Some(Bullet::None),
121 ..Default::default()
122 },
123 run: RunProps {
124 size: Some(44.0),
125 font: Some("+mj-lt".into()),
126 fill: Some(Fill::solid(ColorRef::scheme(SchemeSlot::Tx1))),
127 kern: Some(12.0),
128 ..Default::default()
129 },
130 },
131 );
132 let mut body = ListStyle::default();
133 let sizes = [28.0, 24.0, 20.0, 18.0, 18.0, 18.0, 18.0, 18.0, 18.0];
134 for (i, sz) in sizes.iter().enumerate() {
135 let margin = 18.0 + 36.0 * i as f64;
136 body.set(
137 i as u8,
138 level(*sz, margin, -18.0, if i == 0 { 10.0 } else { 5.0 }),
139 );
140 }
141 let mut other = ListStyle::default();
142 for i in 0..9u8 {
143 other.set(
144 i,
145 LevelStyle {
146 para: ParaProps {
147 margin_left: Some(36.0 * i as f64),
148 align: Some(crate::text::Align::Left),
149 ..Default::default()
150 },
151 run: RunProps {
152 size: Some(18.0),
153 font: Some("+mn-lt".into()),
154 fill: Some(Fill::solid(ColorRef::scheme(SchemeSlot::Tx1))),
155 kern: Some(12.0),
156 ..Default::default()
157 },
158 },
159 );
160 }
161 (title, body, other)
162}
163
164pub fn default_text_style() -> ListStyle {
166 let mut s = ListStyle::default();
167 for i in 0..9u8 {
168 s.set(
169 i,
170 LevelStyle {
171 para: ParaProps {
172 margin_left: Some(36.0 * i as f64),
173 align: Some(crate::text::Align::Left),
174 ..Default::default()
175 },
176 run: RunProps {
177 size: Some(18.0),
178 font: Some("+mn-lt".into()),
179 fill: Some(Fill::solid(ColorRef::scheme(SchemeSlot::Tx1))),
180 kern: Some(12.0),
181 ..Default::default()
182 },
183 },
184 );
185 }
186 s
187}
188
189pub fn build_master(size: Size, theme: Theme, first_id: u32) -> (Master, u32) {
192 let (w, h) = (size.width, size.height);
193 let sx = w / 960.0;
194 let sy = h / 540.0;
195 let r = |x: f64, y: f64, ww: f64, hh: f64| (x * sx, y * sy, ww * sx, hh * sy);
196 let mut b = B { next: first_id };
197 let footers = |b: &mut B| {
198 let (x1, y1, w1, h1) = r(60.0, 500.0, 216.0, 28.0);
199 let (x2, _, w2, _) = r(318.0, 500.0, 324.0, 28.0);
200 let (x3, _, w3, _) = r(684.0, 500.0, 216.0, 28.0);
201 let mut d = b.shape("Date Placeholder", PhType::Date, 10, x1, y1, w1, h1);
202 let mut f = b.shape("Footer Placeholder", PhType::Footer, 11, x2, y1, w2, h1);
203 let mut n = b.shape(
204 "Slide Number Placeholder",
205 PhType::SlideNum,
206 12,
207 x3,
208 y1,
209 w3,
210 h1,
211 );
212 let small = |align| {
213 let mut ls = ListStyle::default();
214 ls.set(
215 0,
216 LevelStyle {
217 para: ParaProps {
218 align: Some(align),
219 ..Default::default()
220 },
221 run: RunProps {
222 size: Some(12.0),
223 fill: Some(Fill::solid(
224 ColorRef::scheme(SchemeSlot::Tx1)
225 .with(deckcraft_color::ColorTransform::Tint(75000)),
226 )),
227 ..Default::default()
228 },
229 },
230 );
231 ls
232 };
233 for (s, a) in [
234 (&mut d, crate::text::Align::Left),
235 (&mut f, crate::text::Align::Center),
236 (&mut n, crate::text::Align::Right),
237 ] {
238 if let Some(t) = s.text.as_mut() {
239 t.body.anchor = Some(Anchor::Middle);
240 t.list_style = small(a);
241 }
242 }
243 if let Some(t) = n.text.as_mut() {
244 t.paragraphs = vec![crate::text::Paragraph {
245 runs: vec![crate::text::Run {
246 text: "‹#›".into(),
247 props: RunProps::default(),
248 kind: crate::text::RunKind::Field {
249 field: "slidenum".into(),
250 },
251 }],
252 ..Default::default()
253 }];
254 }
255 vec![d, f, n]
256 };
257 let title_box = |b: &mut B| {
258 let (x, y, ww, hh) = r(60.0, 29.0, 840.0, 104.0);
259 let mut s = b.shape("Title 1", PhType::Title, 0, x, y, ww, hh);
260 s.text = Some(body_with(
261 Some(Anchor::Middle),
262 Some(AutoFit::Shrink {
263 font_scale: 1.0,
264 line_reduction: 0.0,
265 }),
266 ));
267 s
268 };
269 let body_box = |b: &mut B, name: &str, idx: u32, x: f64, y: f64, ww: f64, hh: f64| {
270 let (x, y, ww, hh) = r(x, y, ww, hh);
271 let mut s = b.shape(name, PhType::Body, idx, x, y, ww, hh);
272 s.text = Some(body_with(
273 Some(Anchor::Top),
274 Some(AutoFit::Shrink {
275 font_scale: 1.0,
276 line_reduction: 0.0,
277 }),
278 ));
279 s
280 };
281
282 let mut mshapes = vec![
284 title_box(&mut b),
285 body_box(&mut b, "Text Placeholder 2", 1, 60.0, 144.0, 840.0, 343.0),
286 ];
287 mshapes.extend(footers(&mut b));
288 for s in &mut mshapes {
289 let kind = s.ph_type();
290 if let Some(t) = s.text.as_mut()
291 && t.paragraphs.is_empty()
292 {
293 let prompt = match kind {
294 Some(PhType::Title) => "Click to edit Master title style",
295 Some(PhType::Body) => "Click to edit Master text styles",
296 _ => "",
297 };
298 if !prompt.is_empty() {
299 t.paragraphs = vec![crate::text::Paragraph::new(prompt)];
300 }
301 }
302 }
303
304 let mut layouts = vec![];
305 let mut lay = |b: &mut B, name: &str, kind: LayoutType, shapes: Vec<Shape>| {
306 b.next += 1;
307 layouts.push(Layout {
308 id: LayoutId(b.next),
309 name: name.into(),
310 kind,
311 shapes,
312 background: None,
313 show_master_shapes: true,
314 preserve: true,
315 color_map: None,
316 raw_ext: None,
317 });
318 };
319
320 {
322 let (x, y, ww, hh) = r(120.0, 88.0, 720.0, 188.0);
323 let mut t = b.shape("Title 1", PhType::CtrTitle, 0, x, y, ww, hh);
324 let mut ls = ListStyle::default();
325 ls.set(
326 0,
327 LevelStyle {
328 para: ParaProps {
329 align: Some(crate::text::Align::Center),
330 ..Default::default()
331 },
332 run: RunProps {
333 size: Some(60.0),
334 ..Default::default()
335 },
336 },
337 );
338 t.text = Some(TextBody {
339 body: BodyProps {
340 anchor: Some(Anchor::Bottom),
341 autofit: Some(AutoFit::Shrink {
342 font_scale: 1.0,
343 line_reduction: 0.0,
344 }),
345 ..Default::default()
346 },
347 list_style: ls,
348 paragraphs: vec![],
349 });
350 let (x, y, ww, hh) = r(120.0, 284.0, 720.0, 130.0);
351 let mut st = b.shape("Subtitle 2", PhType::SubTitle, 1, x, y, ww, hh);
352 let mut ls = ListStyle::default();
353 ls.set(
354 0,
355 LevelStyle {
356 para: ParaProps {
357 align: Some(crate::text::Align::Center),
358 margin_left: Some(0.0),
359 indent: Some(0.0),
360 bullet: Some(Bullet::None),
361 ..Default::default()
362 },
363 run: RunProps {
364 size: Some(24.0),
365 ..Default::default()
366 },
367 },
368 );
369 st.text = Some(TextBody {
370 body: BodyProps {
371 anchor: Some(Anchor::Top),
372 autofit: Some(AutoFit::Shrink {
373 font_scale: 1.0,
374 line_reduction: 0.0,
375 }),
376 ..Default::default()
377 },
378 list_style: ls,
379 paragraphs: vec![],
380 });
381 let mut v = vec![t, st];
382 v.extend(footers(&mut b));
383 lay(&mut b, "Title Slide", LayoutType::Title, v);
384 }
385 {
387 let t = title_box(&mut b);
388 let c = body_with_ph(
389 body_box(
390 &mut b,
391 "Content Placeholder 2",
392 1,
393 60.0,
394 144.0,
395 840.0,
396 343.0,
397 ),
398 PhType::Obj,
399 );
400 let mut v = vec![t, c];
401 v.extend(footers(&mut b));
402 lay(&mut b, "Title and Content", LayoutType::TitleAndContent, v);
403 }
404 {
406 let (x, y, ww, hh) = r(65.0, 135.0, 830.0, 225.0);
407 let mut t = b.shape("Title 1", PhType::Title, 0, x, y, ww, hh);
408 let mut ls = ListStyle::default();
409 ls.set(
410 0,
411 LevelStyle {
412 run: RunProps {
413 size: Some(60.0),
414 ..Default::default()
415 },
416 ..Default::default()
417 },
418 );
419 t.text = Some(TextBody {
420 body: BodyProps {
421 anchor: Some(Anchor::Bottom),
422 ..Default::default()
423 },
424 list_style: ls,
425 paragraphs: vec![],
426 });
427 let mut s = body_box(&mut b, "Text Placeholder 2", 1, 65.0, 362.0, 830.0, 118.0);
428 let mut ls = ListStyle::default();
429 ls.set(
430 0,
431 LevelStyle {
432 para: ParaProps {
433 margin_left: Some(0.0),
434 indent: Some(0.0),
435 bullet: Some(Bullet::None),
436 ..Default::default()
437 },
438 run: RunProps {
439 size: Some(24.0),
440 fill: Some(Fill::solid(
441 ColorRef::scheme(SchemeSlot::Tx1)
442 .with(deckcraft_color::ColorTransform::Tint(82000)),
443 )),
444 ..Default::default()
445 },
446 },
447 );
448 if let Some(tb) = s.text.as_mut() {
449 tb.list_style = ls;
450 }
451 let mut v = vec![t, s];
452 v.extend(footers(&mut b));
453 lay(&mut b, "Section Header", LayoutType::SectionHeader, v);
454 }
455 {
457 let t = title_box(&mut b);
458 let l = body_with_ph(
459 body_box(
460 &mut b,
461 "Content Placeholder 2",
462 1,
463 60.0,
464 144.0,
465 412.0,
466 343.0,
467 ),
468 PhType::Obj,
469 );
470 let rr = body_with_ph(
471 body_box(
472 &mut b,
473 "Content Placeholder 3",
474 2,
475 488.0,
476 144.0,
477 412.0,
478 343.0,
479 ),
480 PhType::Obj,
481 );
482 let mut v = vec![t, l, rr];
483 v.extend(footers(&mut b));
484 lay(&mut b, "Two Content", LayoutType::TwoContent, v);
485 }
486 {
488 let t = title_box(&mut b);
489 let head = |b: &mut B, name: &str, idx: u32, x: f64| {
490 let (x, y, ww, hh) = r(x, 133.0, 412.0, 62.0);
491 let mut s = b.shape(name, PhType::Body, idx, x, y, ww, hh);
492 let mut ls = ListStyle::default();
493 ls.set(
494 0,
495 LevelStyle {
496 para: ParaProps {
497 margin_left: Some(0.0),
498 indent: Some(0.0),
499 bullet: Some(Bullet::None),
500 ..Default::default()
501 },
502 run: RunProps {
503 size: Some(24.0),
504 bold: Some(true),
505 ..Default::default()
506 },
507 },
508 );
509 s.text = Some(TextBody {
510 body: BodyProps {
511 anchor: Some(Anchor::Bottom),
512 ..Default::default()
513 },
514 list_style: ls,
515 paragraphs: vec![],
516 });
517 s
518 };
519 let h1 = head(&mut b, "Text Placeholder 2", 1, 60.0);
520 let c1 = body_with_ph(
521 body_box(
522 &mut b,
523 "Content Placeholder 3",
524 2,
525 60.0,
526 198.0,
527 412.0,
528 289.0,
529 ),
530 PhType::Obj,
531 );
532 let h2 = head(&mut b, "Text Placeholder 4", 3, 488.0);
533 let c2 = body_with_ph(
534 body_box(
535 &mut b,
536 "Content Placeholder 5",
537 4,
538 488.0,
539 198.0,
540 412.0,
541 289.0,
542 ),
543 PhType::Obj,
544 );
545 let mut v = vec![t, h1, c1, h2, c2];
546 v.extend(footers(&mut b));
547 lay(&mut b, "Comparison", LayoutType::Comparison, v);
548 }
549 {
551 let mut v = vec![title_box(&mut b)];
552 v.extend(footers(&mut b));
553 lay(&mut b, "Title Only", LayoutType::TitleOnly, v);
554 }
555 {
557 let v = footers(&mut b);
558 lay(&mut b, "Blank", LayoutType::Blank, v);
559 }
560 {
562 let (x, y, ww, hh) = r(60.0, 36.0, 322.0, 126.0);
563 let mut t = b.shape("Title 1", PhType::Title, 0, x, y, ww, hh);
564 let mut ls = ListStyle::default();
565 ls.set(
566 0,
567 LevelStyle {
568 run: RunProps {
569 size: Some(32.0),
570 ..Default::default()
571 },
572 ..Default::default()
573 },
574 );
575 t.text = Some(TextBody {
576 body: BodyProps {
577 anchor: Some(Anchor::Bottom),
578 ..Default::default()
579 },
580 list_style: ls,
581 paragraphs: vec![],
582 });
583 let c = body_with_ph(
584 body_box(
585 &mut b,
586 "Content Placeholder 2",
587 1,
588 412.0,
589 78.0,
590 488.0,
591 384.0,
592 ),
593 PhType::Obj,
594 );
595 let mut cap = body_box(&mut b, "Text Placeholder 3", 2, 60.0, 162.0, 322.0, 300.0);
596 let mut ls = ListStyle::default();
597 ls.set(
598 0,
599 LevelStyle {
600 para: ParaProps {
601 margin_left: Some(0.0),
602 indent: Some(0.0),
603 bullet: Some(Bullet::None),
604 ..Default::default()
605 },
606 run: RunProps {
607 size: Some(16.0),
608 ..Default::default()
609 },
610 },
611 );
612 if let Some(tb) = cap.text.as_mut() {
613 tb.list_style = ls;
614 }
615 let mut v = vec![t, c, cap];
616 v.extend(footers(&mut b));
617 lay(
618 &mut b,
619 "Content with Caption",
620 LayoutType::ContentWithCaption,
621 v,
622 );
623 }
624 {
626 let (x, y, ww, hh) = r(60.0, 36.0, 322.0, 126.0);
627 let mut t = b.shape("Title 1", PhType::Title, 0, x, y, ww, hh);
628 let mut ls = ListStyle::default();
629 ls.set(
630 0,
631 LevelStyle {
632 run: RunProps {
633 size: Some(32.0),
634 ..Default::default()
635 },
636 ..Default::default()
637 },
638 );
639 t.text = Some(TextBody {
640 body: BodyProps {
641 anchor: Some(Anchor::Bottom),
642 ..Default::default()
643 },
644 list_style: ls,
645 paragraphs: vec![],
646 });
647 let (x, y, ww, hh) = r(412.0, 78.0, 488.0, 384.0);
648 let p = b.shape("Picture Placeholder 2", PhType::Picture, 1, x, y, ww, hh);
649 let mut cap = body_box(&mut b, "Text Placeholder 3", 2, 60.0, 162.0, 322.0, 300.0);
650 let mut ls = ListStyle::default();
651 ls.set(
652 0,
653 LevelStyle {
654 para: ParaProps {
655 margin_left: Some(0.0),
656 indent: Some(0.0),
657 bullet: Some(Bullet::None),
658 ..Default::default()
659 },
660 run: RunProps {
661 size: Some(16.0),
662 ..Default::default()
663 },
664 },
665 );
666 if let Some(tb) = cap.text.as_mut() {
667 tb.list_style = ls;
668 }
669 let mut v = vec![t, p, cap];
670 v.extend(footers(&mut b));
671 lay(
672 &mut b,
673 "Picture with Caption",
674 LayoutType::PictureWithCaption,
675 v,
676 );
677 }
678 {
680 let t = title_box(&mut b);
681 let mut c = body_box(
682 &mut b,
683 "Vertical Text Placeholder 2",
684 1,
685 60.0,
686 144.0,
687 840.0,
688 343.0,
689 );
690 if let Some(tb) = c.text.as_mut() {
691 tb.body.vert = Some(TextDir::EaVertical);
692 }
693 if let Some(p) = c.ph.as_mut() {
694 p.vertical = true;
695 }
696 let mut v = vec![t, c];
697 v.extend(footers(&mut b));
698 lay(
699 &mut b,
700 "Title and Vertical Text",
701 LayoutType::TitleAndVerticalText,
702 v,
703 );
704 }
705 {
707 let (x, y, ww, hh) = r(687.0, 29.0, 213.0, 458.0);
708 let mut t = b.shape("Vertical Title 1", PhType::Title, 0, x, y, ww, hh);
709 t.text = Some(TextBody {
710 body: BodyProps {
711 vert: Some(TextDir::EaVertical),
712 ..Default::default()
713 },
714 ..Default::default()
715 });
716 if let Some(p) = t.ph.as_mut() {
717 p.vertical = true;
718 }
719 let mut c = body_box(
720 &mut b,
721 "Vertical Text Placeholder 2",
722 1,
723 60.0,
724 29.0,
725 612.0,
726 458.0,
727 );
728 if let Some(tb) = c.text.as_mut() {
729 tb.body.vert = Some(TextDir::EaVertical);
730 }
731 if let Some(p) = c.ph.as_mut() {
732 p.vertical = true;
733 }
734 let mut v = vec![t, c];
735 v.extend(footers(&mut b));
736 lay(
737 &mut b,
738 "Vertical Title and Text",
739 LayoutType::VerticalTitleAndText,
740 v,
741 );
742 }
743
744 let (title_style, body_style, other_style) = master_styles();
745 b.next += 1;
746 let master = Master {
747 id: MasterId(b.next),
748 name: theme.name.clone(),
749 theme,
750 shapes: mshapes,
751 background: Some(Background::Ref {
752 idx: 1001,
753 color: ColorRef::scheme(SchemeSlot::Bg1),
754 }),
755 title_style,
756 body_style,
757 other_style,
758 layouts,
759 color_map: vec![],
760 preserve: false,
761 raw_ext: None,
762 };
763 (master, b.next + 1)
764}
765
766fn body_with_ph(mut s: Shape, kind: PhType) -> Shape {
767 if let Some(p) = s.ph.as_mut() {
768 p.kind = kind;
769 }
770 s
771}
772
773pub fn new_presentation(theme: Option<Theme>) -> Presentation {
775 blank_presentation(WIDE, theme.unwrap_or_default(), true)
776}
777
778pub fn blank_presentation(size: Size, theme: Theme, title_slide: bool) -> Presentation {
779 let (master, next) = build_master(size, theme, 1);
780 let first_layout = master.layouts.first().map(|l| l.id).unwrap_or_default();
781 let mut p = Presentation {
782 slide_size: size,
783 notes_size: Size::new(540.0, 720.0),
784 slides: vec![],
785 masters: vec![Arc::new(master)],
786 notes_master: None,
787 handout_master: None,
788 sections: vec![],
789 custom_shows: vec![],
790 show: Default::default(),
791 header_footer: HeaderFooter::default(),
792 props: Default::default(),
793 default_text_style: default_text_style(),
794 media: vec![],
795 first_slide_number: 1,
796 next_id: next.max(256),
797 embedded_fonts: vec![],
798 table_styles: vec![],
799 raw_ext: None,
800 };
801 if title_slide {
802 let s = new_slide(&mut p, first_layout);
803 p.slides.push(Arc::new(s));
804 }
805 p
806}
807
808pub fn new_slide(p: &mut Presentation, layout: LayoutId) -> Slide {
810 let id = SlideId(p.alloc_id());
811 let mut shapes = vec![];
812 let lay_shapes: Vec<Shape> = p
813 .layout(layout)
814 .map(|(_, l)| l.shapes.clone())
815 .unwrap_or_default();
816 for ls in lay_shapes {
817 let Some(ph) = ls.ph.clone() else { continue };
818 if ph.kind.is_footer_kind() {
819 continue;
820 }
821 let sid = ShapeId(p.alloc_id());
822 shapes.push(Shape {
823 id: sid,
824 name: ls.name.clone(),
825 xfrm: None,
826 kind: ShapeKind::Shape,
827 ph: Some(ph),
828 text: Some(TextBody::default()),
829 ..Default::default()
830 });
831 }
832 Slide {
833 id,
834 layout,
835 shapes,
836 ..Default::default()
837 }
838}
839
840pub fn layout_of_kind(p: &Presentation, kind: LayoutType) -> Option<LayoutId> {
842 p.masters
843 .first()
844 .and_then(|m| m.layouts.iter().find(|l| l.kind == kind))
845 .map(|l| l.id)
846}