diff --git a/Sources/Rostrum/Presentation/BackgroundResolver.swift b/Sources/Rostrum/Presentation/BackgroundResolver.swift index 0ba0d11..cf36bf5 100644 --- a/Sources/Rostrum/Presentation/BackgroundResolver.swift +++ b/Sources/Rostrum/Presentation/BackgroundResolver.swift @@ -153,6 +153,45 @@ public extension Slide { } } +public extension SlideLayout { + /// The background this layout paints, following inheritance to its master. + /// + /// The companion to `Slide.effectiveBackground`, and needed for the same + /// reason from the other end: before binding a new slide to a layout, a + /// caller has to know whether that layout is *representative* of the deck. + /// Many decks paint their look on every slide while the layouts and theme + /// stay at the Office defaults, and a slide bound to one of those comes out + /// white in a deck that is emphatically not. + var effectiveBackground: SlideBackground { + BackgroundResolver.resolve(chain: inheritanceParts, theme: resolvedTheme) + } + + var effectiveBackgroundColor: Color? { effectiveBackground.color } + + /// The layout, then its master. + internal var inheritanceParts: [Part] { + var chain = [part] + if let rel = part.rels.first(ofType: RelType.slideMaster), + let master = try? package.part( + at: PackURI.resolve(target: rel.target, relativeTo: part.uri.baseURI)) { + chain.append(master) + } + return chain + } + + internal var resolvedTheme: Theme { + let master = inheritanceParts.count > 1 ? inheritanceParts[1] : nil + let themePart: Part? = { + if let master, let rel = master.rels.first(ofType: RelType.theme) { + return try? package.part( + at: PackURI.resolve(target: rel.target, relativeTo: master.uri.baseURI)) + } + return package.parts[PackURI("/ppt/theme/theme1.xml")] + }() + return Theme(part: themePart ?? part, master: master) + } +} + public extension Presentation { /// The ground this deck mostly paints on. /// diff --git a/Tests/RostrumTests/BackgroundResolverTests.swift b/Tests/RostrumTests/BackgroundResolverTests.swift index d905c27..cb97512 100644 --- a/Tests/RostrumTests/BackgroundResolverTests.swift +++ b/Tests/RostrumTests/BackgroundResolverTests.swift @@ -228,3 +228,53 @@ import Testing #expect(deck.prevailingBackground == nil) } } + +/// What a layout paints, from the other end of the same question. +/// +/// Before binding a new slide to a layout, a caller has to know whether that +/// layout is representative of the deck it came from. Many decks paint their +/// look on every slide while the layouts stay at the Office defaults, and a +/// slide bound to one of those comes out white in a deck that is not. +@Suite struct LayoutBackgroundTests { + @Test func aLayoutReportsTheGroundItPaints() throws { + let deck = try Presentation() + let layout = try #require(deck.layouts.first) + let cSld = try layout.part.dom() + .getOrAddChild("p:cSld", beforeAnyOf: ["p:clrMapOvr", "p:timing"]) + cSld.removeChildren(named: "p:bg") + let bg = XML.Element("p:bg") + let bgPr = XML.Element("p:bgPr") + let fill = XML.Element("a:solidFill") + let clr = XML.Element("a:srgbClr") + clr[attribute: "val"] = "1B1B22" + fill.appendElement(clr) + bgPr.appendElement(fill) + bg.appendElement(bgPr) + cSld.children.insert(.element(bg), at: 0) + + #expect(layout.effectiveBackgroundColor == Color("1B1B22")) + } + + /// And follows through to the master, which is where most templates put it. + @Test func aLayoutInheritsItsMastersGround() throws { + let deck = try Presentation() + let layout = try #require(deck.layouts.first) + guard layout.inheritanceParts.count > 1 else { Issue.record("no master"); return } + + let master = layout.inheritanceParts[1] + let cSld = try master.dom() + .getOrAddChild("p:cSld", beforeAnyOf: ["p:clrMapOvr", "p:timing"]) + cSld.removeChildren(named: "p:bg") + let bg = XML.Element("p:bg") + let bgPr = XML.Element("p:bgPr") + let fill = XML.Element("a:solidFill") + let clr = XML.Element("a:srgbClr") + clr[attribute: "val"] = "2A0E3F" + fill.appendElement(clr) + bgPr.appendElement(fill) + bg.appendElement(bgPr) + cSld.children.insert(.element(bg), at: 0) + + #expect(layout.effectiveBackgroundColor == Color("2A0E3F")) + } +}