Skip to content

SmartArt: text is laid out on its text rectangle (dsp:txXfrm), not in columns - #242

Merged
h-a-n-a-s-h-i merged 2 commits into
storytold:mainfrom
pierrevbc:smartart-txxfrm
Oct 14, 2026
Merged

h-a-n-a-s-h-i merged 2 commits into
storytold:mainfrom
pierrevbc:smartart-txxfrm

Conversation

@pierrevbc

Copy link
Copy Markdown
Contributor

Problem

In SmartArt imported from PPTX, the text of a pre-drawn shape is laid out inside the shape's own
box. SmartArt drawings give each shape a separate text rectangle, dsp:txXfrm, which DeckCraft
does not read. In list layouts the shape is a tall, narrow rounded rectangle turned by 90°, with a
text rectangle turned back by -90°: the text should run in wide horizontal lines, but it comes out
as narrow vertical columns of a few words each. The exported PPTX keeps the columns, since the
SmartArt is written back as a group of shapes.

Reproduce on main (0.6.0) with a public file, not for committing:

  1. 2017/OWASP Top 10-2017 (en).pptx from OWASP/Top10 (commit 3a31f35), slides 20 ("What's Next
    for Organizations") and 21 ("What's Next for Application Managers").
  2. deckcraft-cli render "OWASP Top 10-2017 (en).pptx" --slide 19 --scale 1 out.png (the CLI
    index starts at 0).
  3. The bullet text of each row is split into vertical columns. Same after
    deckcraft-cli convert … out.pptx, in DeckCraft and in LibreOffice.

The simple SmartArt of apache/poi (test-data/slideshow/SmartArt.pptx) renders fine: its text
rectangles match its shapes.

Cause: smartart() in crates/pptx/src/read/shapes.rs reads the drawing part's shapes through
sp_tree, and nothing reads dsp:txXfrm. In the OWASP drawing, a row's shape is
<a:xfrm rot="5400000"> with cx="855279" cy="5430307" (narrow, turned), and its text rectangle
is <dsp:txXfrm rot="-5400000"> with cx="5346805" cy="771777" around the same centre (wide,
upright on the slide).

What changes

Two commits: the fix, and the progress docs.

Only the SmartArt import changes (crates/pptx/src/read/shapes.rs, smartart() and three new
functions). After the drawing's shapes are read, each dsp:sp with a dsp:txXfrm gets its text
placed on that rectangle, read in the shape's coordinate space and turned by the shape's rotation
plus its own:

  • Rectangle inside the shape's text area, same orientation (most process and list boxes): the
    shape keeps its text and the margin between the two is added to its insets, so they stay
    positive.
  • Rectangle reaching outside the shape, or turned against it (the OWASP rows): the text moves
    to a new shape with no fill, outline or effects, placed on the rectangle, right after its shape
    in the diagram's group. It keeps the shape's style font reference (text colour) and hyperlinks.

Both export as plain DrawingML: positive insets, or an ordinary rotated shape. No negative insets
and no bodyPr rot, which readers handle unevenly. The text area of the shape is computed the way
the renderer does (preset::build(..).text_rect; the full box for custom geometry).

Progress docs: the SmartArt row of docs/file-format-parity.md (status line, history) and a
ROADMAP.md progress log line. The checklist row "Insert SmartArt" stays P and the gaps.md entry
"SmartArt is pre-drawn shapes only" still holds, so neither changes.

How it was tested

cargo xtask ci passes on this branch (fmt, clippy -D warnings, workspace tests, layers,
assets, wasm32 check).

tests/smartart.rs::smartart_text_follows_tx_xfrm builds the SmartArt in code: a new presentation
exported, plus a diagram frame, data part and drawing part written by the test (no file made by
another application). Three shapes: a text rectangle three times wider than its shape, a shape
turned by 90° whose text rectangle is turned back, and a rectangle inside its shape. Through
render::place_slide, the test checks the slide-space box the text is laid out in, that the
baseline is horizontal and that each text fits on one line, after import and again after a PPTX
export and re-import. It also checks the group structure (the inside case keeps its text in the
roundRect; moved text has no fill or outline). Without the fix it fails: the text is laid out in
the 72 × 144 pt shape instead of the 216 × 72 pt rectangle.

Checked by hand (files not committed):

  • OWASP Top 10 2017, slides 20 and 21: full-width lines in DeckCraft's rendering of the original
    and of its PPTX export; LibreOffice renders the export like the original.
  • Nine public decks (101 slides, including the apache/poi SmartArt sample) rendered before and
    after: only these two slides change.
  • Not verified: PowerPoint, which needs a black-box check on a machine with PowerPoint
    (AGENTS.md §2).

Known limits

  • SmartArt is still a group of pre-drawn shapes (docs/gaps.md, "SmartArt is pre-drawn shapes
    only").
  • When the text moves to its own shape, moving the shape alone leaves its text behind; moving the
    diagram group moves both.
  • Flipped shapes get upright text on the rectangle; no sample with flips was at hand to confirm.

Related PRs

… columns

A SmartArt drawing shape keeps its text rectangle in dsp:txXfrm, apart
from the shape's own box. The importer ignored it and laid the text out
in the shape itself. In list layouts the shape is a tall, narrow box
turned by 90 degrees, so the text came out as narrow vertical columns,
in the editor and in the exported PPTX.

The text rectangle uses the shape's coordinate space and is turned by
the shape's rotation plus its own:

- When it is the shape's text area shrunk by some margin, with the same
  orientation, the shape keeps its text and the margin is added to its
  insets.
- Otherwise (a rectangle reaching outside the shape, or turned against
  it) the text moves to a new shape without fill or outline, placed on
  the rectangle right above its shape in the diagram's group.

Both export as plain DrawingML (positive insets, an ordinary rotated
shape), which other readers lay out the same way.

Test: a SmartArt drawing built in the test, with a text rectangle wider
than its shape, a turned shape with upright text, and a rectangle
inside its shape; the laid-out text box is checked on import and after
a PPTX round trip.
- file-format-parity.md: SmartArt row notes the text rectangle; status line and history.
- ROADMAP.md: progress log line. No checklist row changes score (Insert SmartArt stays P), and
  the gaps.md SmartArt entry (pre-drawn shapes only) still holds.
@echelon

echelon commented Oct 14, 2026

Copy link
Copy Markdown
Member

Thank you for this contribution, @pierrevbc! It has landed on main and will ship in the next DeckCraft release.

If you haven't already, please come join us on Discord: https://discord.gg/artcraft

anishguduru pushed a commit to anishguduru/deckcraft that referenced this pull request Oct 16, 2026
…age coordinates (storytold#129) (storytold#242)

doc_info (and the MCP document-info resource built on it) serialised
annotation and link rectangles straight from the PDF /Rect, in user
space, while every other tool reports and accepts rectangles with the
origin at the top-left of the displayed page. Feeding a doc_info rect
to link_edit therefore moved the link, and on rotated pages the two
conventions disagreed in more than a y flip.

Convert both through the page's user_to_view, the same conversion
comment_list and link_list use, so the three agree. Pages that an
annotation cannot be matched to keep the raw rect.

Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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.

3 participants