Skip to content

feat(pptx): table rows — fills, borders, and cell text at graph coordinates#409

Merged
DemchaAV merged 2 commits into
developfrom
feature/pptx-tables
Jul 17, 2026
Merged

feat(pptx): table rows — fills, borders, and cell text at graph coordinates#409
DemchaAV merged 2 commits into
developfrom
feature/pptx-tables

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

The fixed PPTX backend renders text and vector shapes but throws UnsupportedNodeCapabilityException on table rows — the biggest remaining gap for real documents. Native PowerPoint tables cannot hold the engine's geometry (they re-lay-out their content), so tables need the same positioned-shapes treatment the rest of the backend uses.

What changed

  • PptxTableRowFragmentRenderHandler: each TableResolvedCell renders as a positioned fill rectangle, butt-capped border edge lines, and per-line text frames. Border math mirrors the PDF handler exactly: horizontal lines extend half a stroke width past internal joins (adjacent cells' perpendicular borders otherwise leave a corner notch after rasterization) while outer table edges stay clamped; a fragment that starts a page forces its TOP border; row-spanning cells grow downward through merged rows via their negative yOffset.
  • Two-pass paint discipline: PptxFixedLayoutBackend.renderGraph groups contiguous same-path row fragments and calls renderFills for every row before any renderBordersAndText — the port of PdfFixedLayoutBackend.renderTableRowGroup, so cell fills always land beneath strokes across row-span joins.
  • Cell text mirrors the PDF handler line for line: control-sanitized lines measured with PdfFont.getTextWidth, nine-way anchored inside the padded cell box, drawn through sanitizeForRender into per-line frames seated at baseline − viewer ascent.
  • The frame discipline (wrap off, autofit off, zero insets, LEFT align, kern=0) moved into a shared package helper PptxTextFrames; the paragraph handler now delegates to it — one home for the rules both text paths must agree on, no behavior change (setShapeName additionally learned connector shapes, which previously went unnamed).
  • Capability matrix: table-row cell flips to supported with the implementing classes; CHANGELOG entries regrouped so backend capabilities sit under Public API and the test inventory under Tests.

Verification

./mvnw -B -ntp clean verify (full 10-module reactor gate) → BUILD SUCCESS; render-pptx suite grows 29 → 32 tests.

  • PptxTableGeometryTest — re-derives every fill rectangle, border segment (including caps and the page-start TOP), and text frame from the captured LayoutGraph and matches them against the reopened .pptx across a page break with a repeated header; pins that the row-spanning cell actually resolved (yOffset < 0 asserted), that a page-start fragment exists, and the fills-before-ink paint order per slide. A synthetic-fragment test pins the page-start TOP-border branch at the handler level, independent of what border sides the engine emits.
  • PptxTableParityDemoTest — writes the reviewable pair tables.pdf / tables.pptx + per-page PNGs under render-pptx/target/visual-tests/pptx-parity/tables/; the previews match including the zebra fills, the merged "Fixed layout" cell, and the repeated header on page 2.

Notes

  • TableResolvedCell.fillInsets is unused by both backends today — parity preserved, not a new gap.
  • The demo's table is one table per page; the geometry test's fills-before-ink assertion is scoped per slide, which is stricter than the per-group guarantee and would need loosening if a fixture ever placed two tables on one slide.

Lane: canonical (document.backend.fixed.pptx) — backend surface; engine, layout, and the PDF paint path untouched.

DemchaAV added 2 commits July 17, 2026 11:59
…inates

Table rows render as positioned rectangles, edge lines, and text frames —
never native PowerPoint tables, which re-lay-out their content and cannot
hold the engine's exact row geometry. The backend groups contiguous
same-table row fragments and paints every cell fill before any border or
text, the PDF backend's two-pass discipline, so fills always land beneath
strokes. Border lines reuse the PDF handler's half-stroke-width extension
on internal horizontal joins (outer table edges stay clamped), a fragment
that starts a page forces its TOP border, and row-spanning cells grow
downward through their merged rows via the negative yOffset.

Cell text mirrors the PDF handler line for line: control-sanitized lines
measured with PdfFont.getTextWidth, anchored nine ways inside the padded
cell box, drawn through sanitizeForRender into per-line frames seated at
baseline − viewer ascent. The frame discipline (wrap/autofit off, zero
insets, LEFT align, kern=0) moved into a shared PptxTextFrames helper that
the paragraph handler now delegates to — no behavior change there.

Tests (32 in render-pptx): PptxTableGeometryTest re-derives fills, border
segments, and text frames from the captured LayoutGraph across a page
break with a repeated header, pins the resolved rowSpan (negative
yOffset), the fills-before-ink paint order per slide, and the page-start
TOP-border branch at the handler level with a synthetic fragment;
PptxTableParityDemoTest writes the reviewable PDF/PPTX pair plus per-page
PNGs under target/visual-tests/pptx-parity/tables/. Full 10-module gate
green.
With the standard-14 default the two formats legitimately differ in
letterforms (PDF draws Helvetica, PPTX declares metric-compatible Arial);
a registered binary family embeds the same TTF into both outputs, so the
demo now proves glyph-identical rendering in real viewers as well as
geometry identity.
@DemchaAV
DemchaAV merged commit ebe59ce into develop Jul 17, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the feature/pptx-tables branch July 17, 2026 11:27
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