Skip to content

fix(convert): strip auto <p:style> theme refs from PDF→PPTX shapes#43

Merged
nelsonduarte merged 1 commit into
mainfrom
fix/pptx-strip-pstyle
May 7, 2026
Merged

fix(convert): strip auto <p:style> theme refs from PDF→PPTX shapes#43
nelsonduarte merged 1 commit into
mainfrom
fix/pptx-strip-pstyle

Conversation

@nelsonduarte
Copy link
Copy Markdown
Owner

Problem

User reported that slide 1 of his UFCD 1492 deck rendered correctly in PowerPoint (dark dotted background, blue UFCD 1492 badge, title, etc.) but slide 6 looked blank — only the floating text was visible. The PPTX inspect script we wrote confirmed the slide's XML contained 13 valid <p:sp> elements with correct <a:solidFill><a:srgbClr val="..."/></a:solidFill> for each rectangle. So the file was fine; PowerPoint was deciding not to render those specific rectangles.

The user's slide 6 XML showed each shape carrying both:

<p:spPr>
  <a:solidFill><a:srgbClr val="0E172A"/></a:solidFill>   <!-- explicit dark fill -->
  <a:ln><a:noFill/></a:ln>
</p:spPr>
<p:style>
  <a:lnRef idx="1"><a:schemeClr val="accent1"/></a:lnRef>
  <a:fillRef idx="3"><a:schemeClr val="accent1"/></a:fillRef>
  <a:effectRef idx="2"><a:schemeClr val="accent1"/></a:effectRef>
  <a:fontRef idx="minor"><a:schemeClr val="lt1"/></a:fontRef>
</p:style>

Root cause

python-pptx's slide.shapes.add_shape(...) always emits an auto <p:style> block with theme presets (effectRef "moderate effect 2", fillRef "intense fill 3"). Per the OOXML spec, <p:spPr> should win over <p:style>, but PowerPoint's renderer applies the theme effect on top in some shape-mix configurations (apparently triggered on slide 6 by the combination of overlapping rectangles + 21 textboxes), masking the explicit colour entirely. LibreOffice and python-pptx-based inspection both correctly use the explicit fill, which is why our diagnostics looked clean while PowerPoint silently rendered blank.

Fix

Strip the auto-generated <p:style> element after add_shape() returns. Every drawing-extraction shape now emits only <p:spPr> so PowerPoint has to use the explicit colour we set:

style_el = shape._element.find(qn("p:style"))
if style_el is not None:
    shape._element.remove(style_el)

Test plan

  • Smoke test on Linux Py3.14: a 1-page PDF with 3 filled rectangles produces a PPTX with 4 shapes total (incl. white full-slide bg) and 0 <p:style> blocks. Before this commit every shape carried the theme reference.
  • User pulls + re-converts UFCD 1492 PDF and opens slide 6 in PowerPoint: dark navy header / blue separator / dark code block / 4 colored card backgrounds should all be visible now.
  • Slide 1 (which already rendered correctly) should still render correctly — same fill colour, just without the theme override.

🤖 Generated with Claude Code

User reported that slide 1 of the converted UFCD 1492 deck rendered
correctly in PowerPoint (dark dotted background, blue UFCD 1492 badge,
title, etc.) but slide 6 looked blank — only the floating text was
visible, the dark navy header bar / blue separator / dark code block /
4 light-gray cards were all invisible. The PPTX inspect script
confirmed the slide's XML contained 13 valid <p:sp> elements with
correct <a:solidFill><a:srgbClr val="..."/></a:solidFill> for each
rectangle. So the file was fine; PowerPoint was deciding not to render
the rectangles on that specific slide.

Root cause: python-pptx's `slide.shapes.add_shape(...)` always emits
both <p:spPr> (with our explicit fill) AND <p:style> blocks like

  <p:style>
    <a:lnRef idx="1"><a:schemeClr val="accent1"/></a:lnRef>
    <a:fillRef idx="3"><a:schemeClr val="accent1"/></a:fillRef>
    <a:effectRef idx="2"><a:schemeClr val="accent1"/></a:effectRef>
    <a:fontRef idx="minor"><a:schemeClr val="lt1"/></a:fontRef>
  </p:style>

That style references the default theme's preset effects — `effectRef
idx="2"` is "moderate effect" which can apply gradients / soft edges /
opacity overlays. Per the OOXML spec, <p:spPr> should win over
<p:style>, but PowerPoint's renderer applies the theme effect *on top*
in some cases (apparently triggered by the specific shape mix on slide
6 — many overlapping rectangles plus 21 textboxes), masking the
explicit fill colour entirely. LibreOffice and python-pptx-based
inspection both ignore the style block and show the explicit fill,
which is why our diagnostics looked correct but PowerPoint rendered
blank.

Strip the auto-generated <p:style> element after add_shape() — every
drawing-extraction shape now emits with only <p:spPr>, so PowerPoint
has to use our explicit colour.

Verified on Linux Py3.14: a 1-page PDF with 3 filled rectangles (dark
navy header, blue separator, dark code block) now produces a PPTX with
4 shapes total (incl. white full-slide bg) and 0 <p:style> blocks.
Before this commit, every shape carried the theme reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@nelsonduarte nelsonduarte merged commit e447bf2 into main May 7, 2026
3 checks passed
@nelsonduarte nelsonduarte deleted the fix/pptx-strip-pstyle branch May 7, 2026 11:00
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