namespace AIStudio.Tools.PowerPoint; /// /// What a PowerPoint template offers to build slides from: its layouts, grouped into families. /// /// /// Read once from the template the user chose. The model gets the families as a catalog and picks /// one for every slide; the presentation is then written from the layouts of the same template. The /// corporate design therefore comes from the template alone, and nothing about any particular /// template is written into the code. /// internal sealed partial class PresentationTemplate { /// /// A picture slot covering at least this share of the slide is a backdrop, which a picture fills. /// /// /// A backdrop, such as the picture of a chapter slide, looks broken with a margin, so a picture is /// cropped to fill it. A smaller slot often gets a diagram or a screenshot, which must not lose its /// edges, so a picture is scaled to fit inside and keeps all of its content. An icon is never /// cropped and never goes into a backdrop: it would fill the slide as a giant. /// public const double BACKDROP_AREA_SHARE = 0.5; private PresentationTemplate(long slideWidth, long slideHeight, IReadOnlyList families, IReadOnlyList variantLabels, IReadOnlyList slides) { this.SlideWidth = slideWidth; this.SlideHeight = slideHeight; this.Families = families; this.VariantLabels = variantLabels; this.Slides = slides; } /// /// The width of a slide, in EMU. /// public long SlideWidth { get; } /// /// The height of a slide, in EMU. /// public long SlideHeight { get; } /// /// The layout families which have room for content, in the order of the template. /// public IReadOnlyList Families { get; } /// /// The labels which tell the variants of the families apart, e.g., "A", "B" and "C"; empty when /// the template carries every layout once. /// public IReadOnlyList VariantLabels { get; } /// /// The slides the template carries, in the order of the file; empty for a template without slides. /// public IReadOnlyList Slides { get; } /// /// Finds a family by the ID the model used for it. /// /// The ID, e.g., "L3". /// The family, or null when there is none with this ID. public LayoutFamily? FindFamily(string id) => this.Families.FirstOrDefault(family => string.Equals(family.Id, id.Trim(), StringComparison.OrdinalIgnoreCase)); /// /// Finds a family by the name the user chose it by. /// /// /// The name is compared exactly, as the template's author wrote it. A template may give two /// layouts the same name, e.g., in two slide masters; the first one in the order of the template /// counts then. /// /// The name, e.g., "Inhalt/Content 1". /// The family, or null when there is none with this name. public LayoutFamily? FindFamilyByName(string name) => name.Length == 0 ? null : this.Families.FirstOrDefault(family => string.Equals(family.Name, name, StringComparison.Ordinal)); /// /// The names of the families, each one once, in the order of the template, e.g., for the user to choose a layout by. /// public IEnumerable FamilyNames => this.Families.Select(family => family.Name).Distinct(StringComparer.Ordinal); /// /// Whether a picture slot in these bounds is a backdrop, see BACKDROP_AREA_SHARE. /// public bool IsBackdrop(SlotBounds bounds) => bounds.Area >= (double)this.SlideWidth * this.SlideHeight * BACKDROP_AREA_SHARE; /// /// Finds a slide of the template by its number. /// /// The number, starting at 1. /// The slide, or null when the template has no slide with this number. public TemplateSlide? FindSlide(int number) => number >= 1 && number <= this.Slides.Count ? this.Slides[number - 1] : null; /// /// Finds the family a slide of a deck is written on: that of the template slide it refers to, or the one it names. /// /// The slide of the deck. /// The family, or null when there is none, e.g., because the template slide is on a layout without room for content. public LayoutFamily? FamilyOf(SlideSpec slide) => slide.TemplateSlide is { } number ? this.FindSlide(number)?.FamilyId is { } familyId ? this.FindFamily(familyId) : null : this.FindFamily(slide.Layout); }