Skip to content

feat(pptx): resolve theme colour, and paint the ground it needs - #747

Merged
andiwand merged 3 commits into
mainfrom
feat/pptx-theme-colour
Aug 26, 2026
Merged

feat(pptx): resolve theme colour, and paint the ground it needs#747
andiwand merged 3 commits into
mainfrom
feat/pptx-theme-colour

Conversation

@andiwand

Copy link
Copy Markdown
Member

🤖 Generated with Claude Code

Follow-up to #746. The colour reading deferred there turned out to be worth
nothing on its own and actively harmful, so this does the version that works.

Why literal colour was not the change to make

a:srgbClr — the literal form — is barely used. Across the four pptx fixtures:

fixture literal run colours scheme (a:schemeClr)
Chapter 4 (the reported deck) none 1066
style-various-1 2
tuesday_d6 331 black, 36 white
2025-09-11 none

Shipping literal-only would have done nothing for the deck that started this and
turned 36 tuesday_d6 runs invisible. Colour in a pptx means theme colour.

What this does

Resolves the theme. A slot (tx1, bg1, accent1, …) resolves along
slide → layout → master → theme: the theme's a:clrScheme holds the values, the
master's p:clrMap says which slot each name stands for, and ColorScheme is
the two folded together. Masters are shared, so one is read once, not once per
slide. This is the first time the module walks past the slide at all.

Paints the ground, because colour is unsafe without it. Text a deck puts on
a coloured master is white; on our white page it vanishes. So p:bg — from the
slide, else its layout, else its master — lands on a new
PageLayout::background_color, and a shape's p:spPr/a:solidFill on the frame.
The reported deck's footers are white on its red master and are now readable.

Along with: a:bodyPr/@anchor, a:lnSpc line height, a:spcBef/a:spcAft
(absolute a:spcPts only — the percent form is of the text size, which css
would resolve against the width), and @baseline sub/superscript.

The odf bug this uncovered

A frame is a plain <div>, so its fill has to be a background-color — the
fill: we were writing only ever reached the <svg> a shape carries, and was
inert on every frame. Painting it revealed that odf's draw:fill-color
cascades independently of draw:fill and outlives it: LibreOffice writes
<style:graphic-properties draw:fill-color="#729fcf" …/> with no draw:fill
anywhere, and boxes the file leaves blank were about to come out blue. Verified
against a LibreOffice render of odp/image-1.odp and odg/sample.odg — neither
paints those fills. The fill state now rides in the resolved colour's alpha.
That is what moves the odf files in the reference output: stale fills correctly
becoming transparent. odg/sample.odg renders pixel-identically before and
after.

What still does not match

Master and layout shape trees are not drawn. tuesday_d6.pptx puts its
white titles on a gradient-filled custGeom banner that lives in its master, so
those 36 runs stay unreadable — neither custom geometry, nor gradients, nor
master shape trees are modelled. This is the honest residual of the trade-off,
called out in presentation/AGENTS.md. Images and bullets are also still
missing.

Test data

Reference output regenerated; the pins move to:

  • reference-output/odr-public → f211f75
  • reference-output/odr-private → eacdb50

Full suite: 1194 passed, 6 skipped. 70 public / 153 private output files change
— pptx from the new styling, odf from the fill fix.

andiwand and others added 3 commits August 26, 2026 09:44
A pptx states most of its colour as `a:schemeClr` — a slot name, not a value —
so reading only the literal `a:srgbClr` sees almost nothing: the deck that
prompted this carries 1066 scheme references and not one literal. A slot now
resolves along slide → layout → master → theme, folding the theme's
`a:clrScheme` together with the master's `p:clrMap` into a `ColorScheme`.

Colour had been held back because a run colour is unsafe until something is
painted behind it: a deck puts white text on a coloured master, and on our white
page it simply vanished. So the ground arrives with it — `p:bg` from the slide,
its layout or its master onto the new `PageLayout::background_color`, and a
shape's own `p:spPr/a:solidFill` onto the frame. `a:bodyPr` anchor, `a:lnSpc`
line height, `a:spcBef`/`a:spcAft` and `@baseline` come along.

Painting a frame's fill exposed an odf bug it had been hiding: `draw:fill` and
`draw:fill-color` cascade independently, and the colour outlives the fill, so
boxes the file leaves blank were about to come out coloured. The fill state now
rides in the resolved colour's alpha.

Master and layout shape trees are still not drawn, so a deck whose only ground
is a shape on its master keeps its white text unreadable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6qVWb1ky168K4FrBpyZeA
`-Wmissing-field-initializers` is an error on the linux builds, and both
`ppt_document` and `iwork_document` spell every member out.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G6qVWb1ky168K4FrBpyZeA
…misses

The style resolution moves out of the document into
`ooxml_presentation_style.{hpp,cpp}`, the way `ooxml/text` already keeps
it, so the layout/master/theme walk and the `a:rPr`/`a:pPr` readers are
drivable from inline xml and an in-memory filesystem.

- every layout, master and theme part is read behind `is_file` and a
  catch, so a missing or non-xml part leaves a slide unstyled instead of
  failing the open
- a relationship target that is absolute, empty or escaping resolves
  rather than throwing
- a `p:bg` we do not model ends the inheritance walk instead of falling
  through to the master's colour
- `p:clrMap` resolves against the theme, not against itself
- xlsx drops the argb alpha byte again: excel ignores it, producers write
  `00`, and `html::color` started honouring it
- a layout is read once, not once per slide, and its master's scheme is
  shared rather than copied per slide
- `PageLayout::background_color` reaches the jni and apple bindings

Reference output is byte-identical across both corpora.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VR6FrcYaNnr5vi9kt9qUD7
@andiwand
andiwand merged commit c2f2852 into main Aug 26, 2026
29 checks passed
@andiwand
andiwand deleted the feat/pptx-theme-colour branch August 26, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant