Skip to main content

deckcraft_model/
defaults.rs

1//! A new presentation: one master with the standard set of layouts, master text styles, and a
2//! first title slide. Positions and sizes are our own (16:9 at 960 × 540 pt, 4:3 scaled).
3
4use 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
23/// Slide size presets for Design ▸ Slide Size (label, width, height in points).
24pub 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
110/// Master text styles: title, body (bulleted levels) and other.
111pub 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
164/// Presentation default text style for free shapes and text boxes.
165pub 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
189/// The master and its eleven standard layouts for a slide size, with ids starting at `first_id`.
190/// Returns the master and the next free id.
191pub 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    // Master placeholders.
283    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    // Title Slide
321    {
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    // Title and Content
386    {
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    // Section Header
405    {
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    // Two Content
456    {
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    // Comparison
487    {
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    // Title Only
550    {
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    // Blank
556    {
557        let v = footers(&mut b);
558        lay(&mut b, "Blank", LayoutType::Blank, v);
559    }
560    // Content with Caption
561    {
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    // Picture with Caption
625    {
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    // Title and Vertical Text
679    {
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    // Vertical Title and Text
706    {
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
773/// A new presentation with one title slide.
774pub 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
808/// A slide for `layout` with its placeholders instantiated (empty, inheriting position).
809pub 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
840/// Layout for a kind in the first master.
841pub 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}