Commit Graph
5 Commits
Author SHA1 Message Date
Hamza Naveed c2b97f1180 DOCX: read every VML shape in a w:pict, and a v:group's members (#1662)
* DOCX: read every VML shape in a `w:pict`, and a `v:group`'s members (#1643)

`read_vml` took `pict.els().find(..)`, the first `v:*` child that is not a
`v:shapetype`, and read only that one. So a `w:pict` holding several shapes
kept the first and dropped the rest, and a `v:group` was read as a single
shape -- or, with no text box in it, as nothing at all -- losing every member.
Both are gone after a save, since saving writes the model back as DrawingML.

Read all of them instead:

- `read_vml_all` walks every `v:*` child of a `w:pict` and emits one object
  each; `read_vml` keeps returning the first for the two places that show a
  single drawing (an OLE object's picture, a `mc:Fallback` preview).
- a `v:group` becomes an `InlineObject::Group`, its members mapped from the
  space `coordsize`/`coordorigin` give it onto the group's own box, nested
  groups flattened into it -- what `group_members` already does for a
  DrawingML `wpg:wgp`, so the model, the renderer and the writer need no
  change. Nesting is bounded at `MAX_VML_GROUP_DEPTH`, members at
  `MAX_GROUP_CHILDREN`.
- `read_vml_shape` is the old per-shape body, taking the group's scale so a
  member's `width`/`height` (and a `v:line`'s `from`/`to`) come through in
  the group's coordinates.

`boxes()` in the text-box tests now asks for `text_boxes()` rather than
`text_box()`, which only reports an object that is itself a box and so could
not see one inside a group; its assertions are unchanged.

* DOCX: cover a `v:group` holding several text boxes (#1643)

@KNUFFIXX reported the same loss from the other end: an inline `v:group` with
two `_x0000_t202` boxes side by side came through a convert as box A only, in
both a tall-B and a too-short-B variant, against Word reading both.

A text box is the one member a group used to keep -- `vml_textbox` recurses to
the first box that has content -- so this case looked half-working rather than
broken, and it is worth pinning separately from the shape cases. On `main` the
group collapses to a single `Shape { kind: TextBox, w: 400, h: 100 }` wearing
the group's own size and holding box A's story; here both boxes read, keep
their own stories and positions, and survive a save.

Built in code from the reported fixture rather than committing the .docx.

* DOCX: cover the shapetype lookup for a group's member (#1643)

Word writes each `v:shapetype` once, at its first use, as a child of the
`w:pict` -- so a group's member refers to one that is its parent's sibling,
not its own. Reading all shapes means `read_vml_shape` is now called for a
member as well as for a top-level shape, and both need the picture to find it,
which is why `pict` is threaded through the group walk.

Two tests: a member with no geometry and no colours of its own, taking its
diamond from the shapetype's `o:spt` and its fill from its `fillcolor`, and the
same for a shape outside a group, which is the case the lookup was written for.
2026-10-18 04:23:34 -04:00
d6723153cc Drawings: VML text boxes keep their margins, fit and alignment; hidden drawings are kept but not shown (#1118)
* Text boxes: VML text boxes keep their internal margins, fit to their text and alignment

A VML text box's inset ("l,t,r,b", ECMA-376 Part 4 §19.1.2.22) was ignored,
so its text sat Word's default 7.2 pt in and 3.6 pt down even where the file
says 0,0,0,0, which is how letterheads and form headers place address blocks
exactly. It's now read like DrawingML's lIns/tIns/rIns/bIns: each value left
out keeps the default, a bare number is in EMUs, o:insetmode="auto" leaves
the defaults. mso-fit-shape-to-text (VML's "Resize shape to fit text") is read
as auto-fit, like a:spAutoFit, and the shape's v-text-anchor as the text's
vertical alignment. In a group, a member's margins go with that member's
text.

The reader feeds layout, so the editor, PDF and page images all change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Drawings: hidden shapes and pictures are kept but not shown

A drawing marked hidden (VML visibility:hidden, floating or inline;
DrawingML wp:docPr/@hidden, or a group member's cNvPr/@hidden) was drawn,
took room and wrapped text. Word for Windows shows it in no view; it's now
kept and saved hidden, but left out of layout: not placed, no wrap area, no
room in its line, nothing drawn, its text box's notes not placed. Exports to
other formats and copies leave it out, the accessibility checker no longer
asks for its alt text, and the selection pane says it's hidden.

Word for Mac draws hidden VML shapes; Windows follows the markup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 02:01:09 -04:00
45abb5ded3 #743: freeforms in groups keep their shape; text boxes keep their margins and Resize to fit (#843)
A Word 365 cover page lost its look. The missing fills came from
theme-coloured shapes (scheme colours and wps:style fill references),
which the reporter's 0.5.0 build didn't read; #611 fixed that on main.
Two causes were still open on main:

- A freeform (a:custGeom) inside a wpg group was drawn as a rectangle.
  read_wsp now reads custom geometry, so group members get it as well.
- Text boxes ignored wps:bodyPr lIns/tIns/rIns/bIns and a:spAutoFit.
  Layout always used Word's default margins, so a title box with no
  margins that was just tall enough for its text lost its second line.
  TextBody now carries the box's own margins and auto_fit. Layout uses
  those margins and doesn't cut the text of a box that resizes to fit.
  Both are written back on save.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 09:37:13 -04:00
2ffba93b51 WordArt text effects and transforms, and shape connectors (#416) (#481)
* WordArt text effects and transforms, and shape connectors (#416)

- Model: WordArt text effects on runs (fill solid/gradient/none, outline,
  shadow, glow, reflection), text warps on shapes (all DrawingML presets
  kept, adjust values capped), connector kinds (straight, elbow, curved)
  with glued ends, arrowheads and drawing ids (ShapeExtra).
- DOCX: w14:textFill/textOutline/shadow/glow/reflection in w:rPr,
  a:prstTxWarp in wps:bodyPr, wps:cNvCnPr a:stCxn/a:endCxn,
  a:headEnd/a:tailEnd; glued-to shapes keep their docPr ids.
- Layout/render/PDF: text with effects drawn from glyph outlines as paint
  layers (gradients, shadow/glow bands, faded reflection); warped text
  boxes bent along arch up/down, circle, wave, slant, inflate/deflate,
  chevron and triangle; connectors and arrowheads drawn as paths.
- Engine: insert.wordArt makes a floating WordArt text box with our own
  styles; format.textEffects, wordArt.style/textFill/textOutline/transform,
  shape.connect; glued connectors re-route after object edits.
- UI: WordArt style gallery (Insert, Home › Text Effects, Shape Format ›
  WordArt Styles), Text Fill/Outline/Effects menus, Transform previews
  drawn from the warp geometry, connectors in Insert › Shapes, drag a
  connector end onto a shape to glue it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix tests for ShapeExtra, regenerate parity checklist, fmt (#416)

* Regenerate parity checklist after rebase (#416)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 16:51:01 -04:00
Valentin KleinandClaude Opus 5.5 f8f1d80c38 Shape Format › Text: direction, alignment and linked text boxes (#369) (#380)
- Model: TextBody on text box parts (vert, anchor, next link); chain
  resolution ignores cycles, self-links and second claimants and caps
  chains at 256 boxes; pruning a box clears links to it.
- Layout: text boxes lay out in their own frame and turn like table
  cells (rotate 90°/270°), stacked text one letter to a line, Align Text
  top/middle/bottom; linked boxes split the first box's story by line,
  each at its own line length.
- DOCX: wps:bodyPr vert/anchor, wps:txbx id and wps:linkedTxbx id/seq
  read and written (ECMA-376).
- Commands shape.textDirection, shape.alignText, shape.link,
  shape.breakLink; ribbon Shape Format › Text group with drawn icons;
  Create Link waits for a click on the target box.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:13:07 -04:00