What
Build a chart rendering pipeline that produces native-quality chart visuals in all five output formats (DOCX / PPTX / XLSX / HTML / PDF) from the same template tag, by routing every chart through the existing image-substitution path.
Revised architecture (post-spike findings, 2026-05-19)
Original plan was a 4-serializer matrix (OOXML c:chartSpace + SVG fragment + HTML+CSS bars + PNG external). Four rounds of empirical spike on portwood-staging proved:
Blob.toPdf (Flying Saucer) silently drops inline SVG and SVG-via-CV-URL. Salesforce's hosted Flying Saucer instance lacks SVGSalamander / Batik on the classpath; per Microsoft's own docs the SVG ReplacedElementFactory is host-application plumbing not bundled with the engine.
- Word's "SVG support" is preservation-only. Saving an Insert > Picture > SVG in Word produces a dual-blip (PNG fallback +
<asvg:svgBlip> extension). When the docx is opened, Word renders the PNG. Removing the PNG from the package and re-opening shows a placeholder — Word does NOT render the SVG for display. PowerPoint and Excel share the same OOXML image stack and behave identically.
- Word's local SVG rasterizer runs at Insert/Edit time only, not at file-open time. We can't access it from our pipeline.
Conclusion: PNG is the only image format every output renders. SVG buys nothing the existing path doesn't already provide.
The pipeline
chart tag at render time (record-page Generate, Bulk runner, etc.)
↓
Apex emits SVG (deterministic — pure data → SVG string)
↓
LWC rasterizes via <canvas>.drawImage + toDataURL → PNG bytes
↓
PNG saved as ContentVersion linked to the source record (cached, see below)
↓
Existing image-substitution pipeline embeds the CV reference
↓
PNG renders natively in:
DOCX (Word renders PNG)
PPTX (PPT renders PNG)
XLSX (Excel renders PNG)
HTML (browser renders PNG)
PDF (Flying Saucer renders PNG)
Every output gets the same PNG via the same {%ImageField}-style substitution that DocGen already does. No new image-embed plumbing per format.
Three free wins from PNG-as-CV-on-the-record
- Universal output support. One PNG per chart, five outputs. The chart tag is just "generate-and-attach-an-image-then-substitute-it."
- Cacheability. Re-rendering the same template against the same record reuses the existing chart CV. The resolver checks for a fresh CV before generating a new one.
- Author flexibility. If a template author wants more layout control than
{#ChartBucket} gives them, they can reference the cached chart CV via {%FieldName} for arbitrary positioning/sizing/wrapping — same flexibility as any image field today.
v1 scope (sub-issue #117)
Browser-only LWC rasterization. Three flows are browser-driven and get charts:
docGenRunner (record-page Generate button) ✅
docGenBulkRunner ✅
- Future Experience Cloud LWCs if any return ✅
Three flows are server-side and do NOT get charts in v1:
DocGenFlowAction (Flow invocable) — render text-only placeholder where chart would go
DocGenBatch (Apex Batchable) — same
- Signature pipeline — same
Documentation captures the limitation. Customers who file requests for server-side chart support trigger v2.
v2 scope (separate sub-issue, future)
External SVG→PNG rendering service (Heroku/Lambda) so Flow / batch / async paths can produce charts. The Apex side just becomes an HTTP callout returning PNG bytes; everything downstream is unchanged.
v3 scope (separate sub-issue, future)
OOXML native <c:chartSpace> serializer for customers wanting PowerPoint's "Edit Data" UI on top of the chart. Separate from this epic's "static visual fidelity" goal — that one's about editable native chart objects.
Sizing
~9 days total for v1:
- Apex SVG emitter (2 days)
- LWC rasterization helper (1 day)
- Chart tag wiring + CV upload (1 day)
- Caching layer (1 day)
- e2e + apex tests + docs (4 days)
Customer story
"Author the report once. Render to PDF, Word, PowerPoint, Excel, or HTML — same Salesforce data, no manual chart updates. Browser-initiated generation rasterizes the chart to a high-quality PNG attached to the record; every output format embeds the same image. The chart is cached on the record, so re-renders are instant."
Out of scope
- Server-side chart rendering (v2 — separate sub-issue)
- Native editable OOXML charts with PowerPoint "Edit Data" UI (v3 — separate sub-issue)
- 3D / sankey / mekko / treemap (could be added to the SVG emitter incrementally; not v1)
Spike artifacts
The spike scripts under scripts/spike-117-*.apex document the empirical findings. Re-run when Salesforce updates their hosted Flying Saucer with an SVG ReplacedElementFactory (which would change the matrix again).
What
Build a chart rendering pipeline that produces native-quality chart visuals in all five output formats (DOCX / PPTX / XLSX / HTML / PDF) from the same template tag, by routing every chart through the existing image-substitution path.
Revised architecture (post-spike findings, 2026-05-19)
Original plan was a 4-serializer matrix (OOXML c:chartSpace + SVG fragment + HTML+CSS bars + PNG external). Four rounds of empirical spike on portwood-staging proved:
Blob.toPdf(Flying Saucer) silently drops inline SVG and SVG-via-CV-URL. Salesforce's hosted Flying Saucer instance lacks SVGSalamander / Batik on the classpath; per Microsoft's own docs the SVG ReplacedElementFactory is host-application plumbing not bundled with the engine.<asvg:svgBlip>extension). When the docx is opened, Word renders the PNG. Removing the PNG from the package and re-opening shows a placeholder — Word does NOT render the SVG for display. PowerPoint and Excel share the same OOXML image stack and behave identically.Conclusion: PNG is the only image format every output renders. SVG buys nothing the existing path doesn't already provide.
The pipeline
Every output gets the same PNG via the same
{%ImageField}-style substitution that DocGen already does. No new image-embed plumbing per format.Three free wins from PNG-as-CV-on-the-record
{#ChartBucket}gives them, they can reference the cached chart CV via{%FieldName}for arbitrary positioning/sizing/wrapping — same flexibility as any image field today.v1 scope (sub-issue #117)
Browser-only LWC rasterization. Three flows are browser-driven and get charts:
docGenRunner(record-page Generate button) ✅docGenBulkRunner✅Three flows are server-side and do NOT get charts in v1:
DocGenFlowAction(Flow invocable) — render text-only placeholder where chart would goDocGenBatch(Apex Batchable) — sameDocumentation captures the limitation. Customers who file requests for server-side chart support trigger v2.
v2 scope (separate sub-issue, future)
External SVG→PNG rendering service (Heroku/Lambda) so Flow / batch / async paths can produce charts. The Apex side just becomes an HTTP callout returning PNG bytes; everything downstream is unchanged.
v3 scope (separate sub-issue, future)
OOXML native
<c:chartSpace>serializer for customers wanting PowerPoint's "Edit Data" UI on top of the chart. Separate from this epic's "static visual fidelity" goal — that one's about editable native chart objects.Sizing
~9 days total for v1:
Customer story
Out of scope
Spike artifacts
The spike scripts under
scripts/spike-117-*.apexdocument the empirical findings. Re-run when Salesforce updates their hosted Flying Saucer with an SVG ReplacedElementFactory (which would change the matrix again).