Skip to content

feat(pptx): import table styles, live charts, and EMF/WMF fallbacks#42

Merged
karthikmudunuri merged 2 commits into
mainfrom
karthikmudunuri/pptx-round-2-impl
May 13, 2026
Merged

feat(pptx): import table styles, live charts, and EMF/WMF fallbacks#42
karthikmudunuri merged 2 commits into
mainfrom
karthikmudunuri/pptx-round-2-impl

Conversation

@karthikmudunuri
Copy link
Copy Markdown
Member

Summary

Implements the round-2 plan from #38. Three deferred items from #36 land together so real-world client decks stop dropping recognisable content on import.

Closes the implementation half of #38.

1. Table styles

  • Parse ppt/tableStyles.xml once per deck (resolved via presentation.xml.rels), honour file-level <a:tblStyleLst def=\"…\"> default.
  • Resolve <a:tblPr><a:tableStyleId> and apply the matching style's parts (wholeTbl / firstRow / lastRow / firstCol / lastCol / band1H / band2H).
  • <a:tblPr> flags (firstRow, firstCol, lastRow, lastCol, bandRow) decide which parts apply; cell-level <a:tcPr><a:solidFill> still wins as an override.
  • TableElement gains rowAltFill, firstColFill, lastColFill, lastRowFill, hasHeader, bandRows, and per-region text-colour overrides; the renderer applies them in PPTX-faithful precedence.

2. Charts — cached image + live rendering

  • <p:graphicFrame> with <c:chart> first tries the chart part's cached raster preview (ppt/charts/_rels/chartN.xml.rels…/image) and emits an ImageElement.
  • When no cached preview ships, the chart XML is parsed into a new ChartElement (bar / column / line / area / pie / doughnut, with standard / stacked / percentStacked grouping, series fills, value labels, number-format codes, optional title) and rendered live via a lazy-loaded Apache ECharts import.
  • Series colours: explicit <c:spPr><a:solidFill> wins; otherwise PowerPoint's default-palette cycle theme.accent{(c:idx % 6) + 1} is used.
  • The source <p:graphicFrame> OOXML is preserved on the element so save round-trips re-emit the source chart part verbatim (including its embedded xlsx workbook).
  • setOption(option, true) + explicit per-series itemStyle.color so palette merging can't drop colours when multiple series share a name.

3. EMF / WMF — raster fallback + Canvas decode

  • When a <p:pic> references EMF/WMF, the importer first looks for a renderable sibling: alt blip in <a:extLst>, extra rels entry on the picture, or same-basename PNG/JPEG/SVG in the slide rels.
  • When no sibling exists, the metafile is decoded in-browser via emf-converter (Canvas-based EMF/WMF replayer) and rendered as PNG. The Dickinson sample wordmark (EMF-only) now appears instead of dropping.
  • Headless environments without Canvas / OffscreenCanvas (SSR, Node) gracefully fall back to the legacy diagnostic-skip — never throws.

Bonus: tint / shade fix

<a:tint val=\"20000\"/> and <a:shade> modifiers now operate per-RGB-channel per ECMA-376 §20.1.2.3 (mix with white / black respectively) instead of as an HSL-luminance shift. This was silently over-saturating the pastel Office accent palette — most visible on table-style band rows like "Medium Style 2 – Accent 1" where accent1 @ tint 20% should be #F2CCCC (light pink), not the saturated red the old code produced.

What's still out of scope

  • Live editing of chart data inside Slidewise (we re-emit the source chart XML verbatim on save).
  • Embedded TTCOMPRESSED fonts, animations, SmartArt — explicit non-goals.

Dependencies

  • emf-converter ^1.1.6 (MIT, 0 deps, ~310 KB unpacked) — Canvas-based EMF/WMF decoder.
  • echarts ^6.0.0 — lazy-loaded only when a deck actually contains a ChartElement.

Test plan

  • pnpm tsc --noEmit -p tsconfig.lib.json clean.
  • pnpm vitest run — 35/35 passing (4 new fixtures in round2.test.ts cover table styles, cached chart, EMF raster sibling, EMF-only skip).
  • Manual: imported Dickinson_Sample_Slides.pptx, eyeballed:
    • slide 2 — Dickinson wordmark (EMF) renders via in-browser decode.
    • slide 4 — stacked column chart renders with the gray + red theme-accent colours.
    • slide 5 — "Medium Style 2 – Accent 1" table renders with the correct header / banded-row pinks.

Bundle impact

  • emf-converter: ~310 KB shipped only when a deck triggers the EMF fallback (dynamic import()).
  • echarts: lazy-loaded on first ChartElement render — zero impact for chart-free decks.

…backs

Implements the round-2 plan from #38. Three deferred items from #36 land
together so real-world decks (e.g. Dickinson sample) stop dropping
recognisable content.

- Table styles: parse ppt/tableStyles.xml, resolve <a:tableStyleId>,
  apply header/banded/firstCol/lastRow fills with cell-level overrides
  winning. TableElement gains rowAltFill / firstColFill / lastColFill /
  lastRowFill / hasHeader / bandRows / per-region text-colour fields.
- Charts: emit ImageElement when the chart part ships a cached preview,
  otherwise parse <c:barChart>/<c:lineChart>/<c:pieChart>/etc. into a
  new ChartElement and render live via a lazy-loaded ECharts import.
  Source <p:graphicFrame> OOXML preserved on the element for round-trip.
- EMF / WMF: prefer raster siblings (alt blip / extra rels / same-basename
  PNG); fall back to decoding the metafile in-browser via emf-converter.
  Headless environments without Canvas keep the legacy diagnostic-skip.
- Bonus fix: tint / shade modifiers now operate per-RGB-channel per
  ECMA-376 §20.1.2.3 instead of as an HSL-luminance shift, so pastel
  Office accents (Medium Style 2 banded rows, etc.) stop coming out
  oversaturated.

Adds emf-converter + echarts as runtime deps. Tests: 35 passing, including
new round-2 fixtures for each item.
…round-2-impl

# Conflicts:
#	packages/slidewise/src/lib/pptx/deckToPptx.ts
#	packages/slidewise/src/lib/pptx/pptxToDeck.ts
@karthikmudunuri karthikmudunuri merged commit f26d777 into main May 13, 2026
1 check passed
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