Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions Sources/Rostrum/Presentation/BackgroundResolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down
50 changes: 50 additions & 0 deletions Tests/RostrumTests/BackgroundResolverTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
}
Loading