Skip to content

Import PPTX shape-level blipFill as ImageElement#284

Merged
hackerwins merged 3 commits into
mainfrom
pptx-shape-blipfill
May 23, 2026
Merged

Import PPTX shape-level blipFill as ImageElement#284
hackerwins merged 3 commits into
mainfrom
pptx-shape-blipfill

Conversation

@hackerwins

@hackerwins hackerwins commented May 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Modern PPTX templates (e.g. the Multicolor Pastel Doodle deck) build full-bleed slide backgrounds as <p:sp> wrapping <a:custGeom> (or <a:prstGeom rect>) + <a:blipFill>, which is semantically equivalent to <p:pic>. The shape parser previously dropped every shape-level blip fill (parseShapeFill only handled <a:solidFill>), so imported decks landed as blank rectangles wherever the artwork should have been.
  • parseSp now detects <a:blipFill> in <p:spPr> before the prstGeom branch and emits an ImageElement via the existing parseBlipFill helper (same uploader / crop / opacity path as <p:pic>). If the same shape also carries visible text, a coincident TextElement is layered on top, matching the existing prstGeom + txBody dual-emission pattern.
  • Genuinely non-rect freeforms lose their clip path under this approach — accepted as a v1 tradeoff because rectangular freeforms dominate real-world exports and the alternative is the prior empty-rectangle behavior. Gradient / pattern fills and master / layout <p:bgRef> resolution remain unhandled and are tracked separately.

Test plan

  • pnpm verify:fast — all 349 test files pass (sheets + docs + slides + frontend + backend)
  • New unit tests cover: custGeom + blipFill, prstGeom rect + blipFill, blipFill + text overlay, upload-failure fallback, solid-fill control
  • Manual smoke: import the Multicolor Pastel Doodle PPTX through /slides/import, confirm slide-1 cream background + doodles render
  • CI green

Task doc

docs/tasks/active/20260523-pptx-shape-blipfill-todo.md

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Image-filled backgrounds on PPTX shapes are now properly imported instead of being dropped during the import process.
    • Shapes that contain both image fills and text content now correctly preserve both elements with proper layering.
  • Tests

    • Added comprehensive test coverage for various image-filled shape import scenarios to ensure reliability.

Review Change Stack

Modern PPTX templates routinely build full-bleed slide backgrounds and
decorations as `<p:sp>` wrapping `<a:custGeom>` (or `<a:prstGeom rect>`)
+ `<a:blipFill>`, which is semantically equivalent to `<p:pic>`. The
shape parser used to drop every shape-level blipFill silently (the
existing `parseShapeFill` only handled `<a:solidFill>`), so imported
doodle / pastel template decks landed as blank rectangles wherever the
artwork should be — the user-reported "background not showing" case
on the Multicolor Pastel Doodle deck.

`parseSp` now detects `<a:blipFill>` in `<p:spPr>` before the
`prstGeom` branch and emits an `ImageElement` via the existing
`parseBlipFill` helper (shared with `<p:pic>` parsing — same uploader,
crop, opacity handling). If the same shape also carries visible text,
a coincident `TextElement` is layered on top, matching the existing
`prstGeom + txBody` dual-emission pattern. When the blip fails to
upload, we fall through so the shape still contributes whatever
geometry or text it has rather than vanishing.

Genuinely non-rect freeforms (e.g. an ellipse-clipped photo) lose
their clip path under this approach — accepted as a v1 tradeoff
because rectangular freeforms dominate real-world exports and the
alternative is an empty rectangle. Gradient / pattern fills and
master / layout `<p:bgRef>` resolution remain unhandled and are
tracked separately as PPTX import gaps.
The previous comment in `parseShapeFill` claimed blip-fill on a shape
was out of v1 scope. The preceding commit moved blip-fill handling
into `parseSp` (it now short-circuits to an `ImageElement` before
reaching `parseShapeFill`), so the comment misled the next reader
into thinking the case was still unhandled. Tighten the wording to
list only gradient/pattern as out of scope and point at the new
branch for blip fills.
@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@hackerwins, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 1 review/hour. Refill in 48 minutes and 24 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 18376ad4-8420-4fb6-b108-2c2f38e8771a

📥 Commits

Reviewing files that changed from the base of the PR and between 3d336b6 and 9a47343.

📒 Files selected for processing (1)
  • packages/slides/test/import/pptx/shape-blipfill.test.ts
📝 Walkthrough

Walkthrough

This PR implements parsing of <a:blipFill> elements on PPTX shapes to emit image elements, with optional text overlays. It includes a task plan, async shape parsing logic with blip detection and image building, and a comprehensive test suite covering image upload, text layering, failures, and control cases.

Changes

PPTX Shape Blip Fill Import Support

Layer / File(s) Summary
Task planning and requirements
docs/tasks/active/20260523-pptx-shape-blipfill-todo.md
Task document specifies the importer gap where <a:blipFill> on shapes is dropped, outlines the planned parseSp helper approach to emit ImageElement (with frame and image data), optional text layering, non-goals (gradient/pattern fills, background rendering), and test/verification steps.
Shape blip fill implementation
packages/slides/src/import/pptx/shape.ts
Imports ImageElement and parseBlipFill; converts parseSp to async; parseChild awaits parseSp. New blip fill detection in parseSp calls buildImageFromBlip to create an ImageElement from the blip; if image exists and visible text is present, both image and text element are returned with coincident frames; otherwise falls back to existing geometry-based parsing. Comments clarify blip fill handling scope.
Blip fill test suite
packages/slides/test/import/pptx/shape-blipfill.test.ts
Test infrastructure with mocked archive, relationships, and context; XML fixtures for doodle template (custGeom + blipFill), rect + blipFill, text + blipFill, and solid-fill control. Tests verify: (1) custGeom + blipFill emits single ImageElement; (2) rect + blipFill emits image; (3) text + blipFill emits layered image and text with matching frames; (4) missing uploadImage skips shape and records skipped image; (5) solid fills remain unchanged as shapes.

Sequence Diagram

sequenceDiagram
  participant parseSpTree
  participant parseChild
  participant parseSp
  participant buildImageFromBlip
  participant parseBlipFill
  participant uploadImage
  parseSpTree->>parseChild: iterate shape elements
  parseChild->>parseSp: await parseSp(sp element)
  parseSp->>parseSp: detect spPr/blipFill?
  alt blipFill exists
    parseSp->>buildImageFromBlip: build image from blip
    buildImageFromBlip->>parseBlipFill: parse blip fill
    parseBlipFill->>uploadImage: upload image bytes
    uploadImage-->>buildImageFromBlip: image URL
    buildImageFromBlip-->>parseSp: ImageElement with frame
    parseSp->>parseSp: visible text present?
    alt text exists
      parseSp-->>parseChild: [ImageElement, TextElement]
    else no text
      parseSp-->>parseChild: [ImageElement]
    end
  else no blipFill
    parseSp->>parseSp: parse prstGeom/custGeom
    parseSp-->>parseChild: geometry-based elements
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A shape once silent, now shows its fill,
Blip renders bright with image thrill!
Text dances atop in layered grace,
Parsing async keeps its place.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Import PPTX shape-level blipFill as ImageElement' clearly and concisely summarizes the main change: enabling import of blipFill elements from PPTX shapes as ImageElement objects, which directly addresses the primary problem described in the PR objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pptx-shape-blipfill

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/slides/test/import/pptx/shape-blipfill.test.ts (1)

182-193: ⚡ Quick win

Add a fallback test for prstGeom + blipFill upload-failure behavior.

Line 182 currently verifies only the custGeom drop case. Please also assert that when blipFill fails on a prstGeom shape, parsing falls through and still emits the underlying shape (not an empty result).

Suggested test addition
+  it('falls through to shape parsing for prstGeom when blip upload fails', async () => {
+    const tree = spTreeFrom(SP_RECT_BLIPFILL);
+    const c = ctx({ rels: rels('../media/image1.png') }); // no uploadImage
+
+    const out = await parseSpTree(tree, c);
+
+    expect(out).toHaveLength(1);
+    expect(out[0].type).toBe('shape');
+    expect(c.report.skippedImages).toBe(1);
+  });

As per coding guidelines, **/*.{test,spec}.{js,ts,jsx,tsx}: Write tests for critical business logic and edge cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/slides/test/import/pptx/shape-blipfill.test.ts` around lines 182 -
193, Add a new test alongside the existing "falls through to existing path when
blip upload fails" that constructs a sp tree representing a prstGeom shape with
blipFill (e.g. the constant used for prst-geom+blip tests such as
SP_PRST_GEOM_BLIPFILL), uses the same ctx({ rels: rels('../media/image1.jpeg')
}) setup so uploadImage is missing, calls parseSpTree(tree, c) and then asserts
that the parser emits the underlying shape (expect(out).toHaveLength(1) or
expect(out.length).toBeGreaterThan(0)) instead of returning an empty array, and
still increments c.report.skippedImages accordingly
(expect(c.report.skippedImages).toBe(1)); use the same helpers spTreeFrom, ctx,
rels and parseSpTree to place the test next to the existing one.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/slides/test/import/pptx/shape-blipfill.test.ts`:
- Around line 182-193: Add a new test alongside the existing "falls through to
existing path when blip upload fails" that constructs a sp tree representing a
prstGeom shape with blipFill (e.g. the constant used for prst-geom+blip tests
such as SP_PRST_GEOM_BLIPFILL), uses the same ctx({ rels:
rels('../media/image1.jpeg') }) setup so uploadImage is missing, calls
parseSpTree(tree, c) and then asserts that the parser emits the underlying shape
(expect(out).toHaveLength(1) or expect(out.length).toBeGreaterThan(0)) instead
of returning an empty array, and still increments c.report.skippedImages
accordingly (expect(c.report.skippedImages).toBe(1)); use the same helpers
spTreeFrom, ctx, rels and parseSpTree to place the test next to the existing
one.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: af91a078-4594-4874-8196-dfc70f80dffd

📥 Commits

Reviewing files that changed from the base of the PR and between 47a44c3 and 3d336b6.

📒 Files selected for processing (3)
  • docs/tasks/active/20260523-pptx-shape-blipfill-todo.md
  • packages/slides/src/import/pptx/shape.ts
  • packages/slides/test/import/pptx/shape-blipfill.test.ts

@github-actions

github-actions Bot commented May 23, 2026

Copy link
Copy Markdown
Contributor

Verification: verify:self

Result: ✅ PASS in 200.4s

Lane Status Duration
sheets:build ✅ pass 12.8s
docs:build ✅ pass 11.6s
slides:build ✅ pass 13.0s
verify:fast ✅ pass 119.8s
frontend:build ✅ pass 16.7s
verify:frontend:chunks ✅ pass 0.3s
backend:build ✅ pass 4.5s
cli:build ✅ pass 2.0s
verify:entropy ✅ pass 19.7s

Verification: verify:integration

Result: ✅ PASS

@codecov

codecov Bot commented May 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The existing fall-through test only exercised the custGeom path
(empty output when no geometry to fall back to). Add a sibling test
for the prstGeom + blipFill case, where the new branch must yield to
the existing prstGeom branch so the underlying rectangle still
renders. This locks in the graceful-degradation contract for image
upload outages — without it a transient uploadImage failure could
silently drop every picture-frame shape from the slide.

Addresses CodeRabbit review feedback on PR #284.
@hackerwins

Copy link
Copy Markdown
Collaborator Author

@coderabbitai Addressed in 9a47343 — added a prstGeom + blipFill upload-failure test that asserts the fall-through emits the underlying shape (rect) and bumps report.skippedImages. This locks in the graceful-degradation contract you flagged: a transient upload outage can't silently drop picture-frame shapes.

@coderabbitai

coderabbitai Bot commented May 23, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@hackerwins That's a great way to lock in the contract — asserting both the emitted shape element and the skippedImages bump together means a future refactor can't accidentally break one side of the fallback without the test catching it. Thanks for the thorough follow-through!

(๑˃ᴗ˂)ﻌ ʕ•ᴥ•ʔ 🐇


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

@hackerwins
hackerwins merged commit 95e5686 into main May 23, 2026
4 checks passed
@hackerwins
hackerwins deleted the pptx-shape-blipfill branch May 23, 2026 04:21
hackerwins added a commit that referenced this pull request May 24, 2026
These tasks' features merged to main (PRs #267 #271 #277 #284 #285)
but their plan checkboxes were never ticked, so the todo files
lingered in tasks/active. Added a status note citing each merge
commit, marked the checkboxes complete, and ran tasks:archive to
move them under archive/2026/05 with regenerated indexes.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
@hackerwins hackerwins mentioned this pull request May 24, 2026
2 tasks
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