Commit Graph
157 Commits
Author SHA1 Message Date
b8debb3050 Content controls: typed text takes the control's formatting, in any language (#3116)
* Content controls: typed text takes the control's formatting, in any language

Text typed in place of a content control's placeholder takes the
control's own run properties (w:sdtPr/w:rPr, ECMA-376 §17.5.2.27), as
in Word: in a protected form filled in Word for Mac, all 17 entered
values carry the style their control's rPr names. WordCraft kept that
rPr only as XML to write back and gave typed text the placeholder's
formatting minus the placeholder style.

It also knew the placeholder style only by its English id,
PlaceholderText. German Word writes Platzhaltertext (the name stays
"Placeholder Text"), so in forms from German Word every entry stayed
grey, on screen and in the saved file, and restoring a placeholder
added a second Placeholder Text style. The style is now found by its
built-in name.

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

* Content controls: typing drops a showing placeholder's own style

A control without run properties that shows its placeholder: text typed
over it drops the placeholder run's character style whatever its id
and name. Word localizes only the placeholder style's id, but another
program may localize its name too, and the name lookup then misses it.
Restoring a placeholder and new controls keep the name lookup.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 12:53:44 -04:00
PanyangYandBrandon Thomas de8a81c1e4 References: Word sources file import in Manage Sources (#2640) (#2897)
* References: Word sources file import in Manage Sources (#2640)

Manage Sources gains Import… and references.sources an "import": path
parameter: it reads a b:Sources document (Word's own Sources.xml) into the
document's sources with the same reader as a document's own bibliography
part. A tag already here is left alone, an untagged entry is matched by
title and author or gets a tag like a new source, the whole import is one
undo step, and a file that isn't b:Sources is refused. Desktop only; the
web build hides the button.

The docx reader now shares the b:Sources parser (crates/docx/src/
bibliography.rs) with the import, with the same dedupe and 10,000-source
cap; the hostile-input cap test still passes.

Tests: docx unit tests (names, kinds, accessed/DOI fallbacks, repeated
tags, empty entries, wrong root), engine import test (add, undo, second
import, untagged matching, refused file), ui tests (the dialog button asks
for a "sources" file; finish_pick imports), wordcraft-cli end to end
(import, cite, bibliography, save, reopen; the package's customXml
carries the sources). The button's label is in every interface catalogue.

ROADMAP progress log and docs/roadmap.md, gaps.md, file-format-parity.md
and target-app-parity.md get their rows.

* References: review fixes for the sources import (#2640)

- read_sources validates the root namespace instead of the local name alone:
  an un-namespaced or unrelated <Sources> is refused, and both bibliography
  namespaces read — OOXML's and Word's own 2004/10 (the VBA Source.XML
  format), the latter mapped onto the canonical b prefix with parse_ns.
  Tests: the 2004/10 namespace is accepted; a no-namespace or unrelated
  root is refused.
- The import loop builds the tag and (title, author-or-editor) sets once and
  keeps a per-stem counter, with the stem rule shared with auto_tag
  (auto_tag_base). Importing thousands of untagged sources no longer costs
  O(n^3). Test: untagged sources sharing a stem get Riv21, Riv211, Src, and
  a second import adds none.
- docs/target-app-parity.md and docs/file-format-parity.md no longer claim
  bibliography sources aren't saved in DOCX: #169 and #532 are closed and
  the round trip exists (#790).

* References: doc comments for the sources import helpers (#2640)

Hoist the untagged-source matching helper (by_author) to module level and
document sources, auto_tag_base and sources_ui, so the functions this PR
touches meet CodeRabbit's docstring-coverage pre-merge check.

* References: re-apply the sources import's progress notes (#2640)

The rebase onto current main took upstream's copies of ROADMAP.md,
docs/file-format-parity.md and docs/gaps.md, because the rows this PR
adds conflicted with upstream's own appends. Re-apply them on the current
text: the progress log and revision rows, the bibliography row's
source-format note, and #15's evidence, with each status line's change
note. The other two documents (docs/roadmap.md, docs/target-app-parity.md)
merged cleanly and keep their rows.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:54:47 -04:00
MarekSpodBiedryandBrandon Thomas ca6faa8c1d DOCX: Word 2007 WordArt (VML textpath) in the body is shown as text, not dropped (#2875) (#2903)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:52:01 -04:00
dec24458cf #1273: equations from Word files keep all their content (#2613)
- Math straight in the body or a cell (m:oMathPara/m:oMath without w:p,
  allowed by EG_BlockLevelElts) gets its own paragraph instead of vanishing.
- mc:AlternateContent Requires="m" picks the equation, not its fallback.
- Inside an equation, runs in w:hyperlink, w:fldSimple, w:dir, w:bdo and
  mc:AlternateContent are kept (EG_PContentMath); depth stays bounded.
- w:sym, w:noBreakHyphen and Symbol-font text in math runs become Unicode.
- Layout: n-ary operators, delimiters, accents and group characters the math
  font lacks use a fallback font instead of a missing-glyph box.
- Coverage test reads every ECMA-376 §22.1 element and checks no argument's
  text is dropped; tests for body-level math, Requires="m", symbols, hostile
  nesting and the font fallback. Parity docs and progress log updated.


Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:45:01 -04:00
beedd1b30d Drop Cap Options dialog (#2601) (#2602)
* #2601: Drop Cap Options dialog

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

* gaps: keep the Drop Cap Options note on the dialog-depth row

* i18n: translate the two Drop Cap disabled reasons in the audited catalogs

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:44:30 -04:00
5fdd0a0e89 Watermarks in header table cells drawn behind the text (#1775) (#2580)
A floating picture or VML shape in a header table cell is placed on the page behind the text.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:43:55 -04:00
divyanshu2074andBrandon Thomas c9468a13cb fix(docx): support self-closing w:compat in settings merge (#2131)
* fix(docx): support self-closing w:compat in settings merge

* Drop the duplicate divyanshu2074 entry in people.toml (already on main)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:35:43 -04:00
872b2bc99d DOCX: ink inside groups and drawing canvases survives saving (gap 2) (#2097)
* gap: nested ink WIP

* gap 2: native ink nested in groups and drawing canvases survives DOCX saves

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

* gap 2: review round 1: ungrouping keeps nested ink; no orphan parts

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

* gap 2: Impact cell matches the restated impact

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

* gap 2: only Word ink pins a group against Ungroup

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

* gap 2: alternate ink must hold an a:graphic to be written as standalone

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

* gap: nested ink WIP

* gap 2: native ink nested in groups and drawing canvases survives DOCX saves

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

* gap 2: review round 1: ungrouping keeps nested ink; no orphan parts

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

* gap 2: Impact cell matches the restated impact

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

* gap 2: only Word ink pins a group against Ungroup

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

* gap 2: alternate ink must hold an a:graphic to be written as standalone

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

---------

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-18 11:34:42 -04:00
70fc00c80f Symbols keep their symbol font when the text font changes (#2036) (#2037)
* Symbols: a w:sym character keeps its symbol font when the text font changes (#2036)

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

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

* #2036: review round 3: docx test reads sym_font, paste drops the symbol font, astral symbols stay text

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

* #2036: insert.symbol decides symbol or text per character

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

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 11:33:00 -04:00
492aceb960 Fonts Word embeds as subsets lay out the text set in them (#2240) (#2540)
* #2240: fonts Word embeds as subsets lay out the text set in them

Embedded fonts were registered under the family inside the font file. Word
embeds a subset ("embed only the characters used") under a made-up family
(___WRD_EMBED_SUB_46) and the text asks for it by the font table's name
(OBRBWW+Century Schoolbook), so it still fell back to a substitute. Each
embedded file now comes with its font table name, and its faces go by that
name when they don't already.

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

* #2240: the black-circle fallback test passes its font's table name

main's new `circle_checks_face_coverage` test (#2582) called `add_document_fonts` with bare font
bytes; with this branch each embedded font comes with the name the document's font table gives
it, so the test passes "Times New Roman" alongside the face it renames to it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:28:52 -04:00
Bryan MarkandClaude Opus 5.5 8107581df2 Opening a DOCX no longer copies its pictures (#2441)
Each picture, and each embedded object or macro part kept for saving, was
copied out of the unzipped package (bytes.to_vec()), so a document with
pictures held them twice while it opened, and the package's copies were
left behind as freed memory. Package now holds its parts as Arc<Vec<u8>>,
the type the document keeps media and passthrough parts in, and hands them
over with get_shared. keep_unmodelled also searches the main part in place
instead of copying it.

A 92 MB document with 20 pictures on macOS 27: the app's footprint once
open 866 -> 772 MB (freed blocks kept 206 -> 118 MB, peak 915-932 ->
821-823 MB); wordcraft-cli info peak 319 -> 231 MB.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 10:26:56 -04:00
Fin WarmanandBrandon Thomas 4cb4b2d231 244: inherit omitted cell margins (#2434)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:25:38 -04:00
PanyangYandBrandon Thomas a14948a23d DOCX: strict line heights with a unit are points under auto (#2329) (#2439)
Word 2024 saves Strict Open XML with measures written out with their unit:
the issue's file carries `w:spacing w:after="8pt" w:line="13.90pt"
w:lineRule="auto"` in its paragraph defaults. The reader sent `13.90`
straight into the `auto` branch, where a bare number counts 240ths of a
line (240 = single), so 13.90pt became a 13.9 multiple: 191.8 pt lines,
and the issue's document laid out as 10 pages against Word's 1.

Under `lineRule="auto"` a unit-suffixed height now reads through twips, as
Word renders it (ISO 29500 `ST_SignedTwipsMeasure`: 13.90 pt = 278 twips =
the 1.1583 multiple). A bare number still counts 240ths of a line;
`exact`/`atLeast` already read units as points.

Checked with the issue's file: 1 page with baselines on Word's (15.98 pt
line advance against Word's 16.10/15.96; paragraph advance 23.99 against
24.00; table lines 12.65 against 12.60/12.72), and a save/reopen keeps it.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:25:14 -04:00
Fin WarmanandBrandon Thomas a492189a84 DOCX: preserve zero effect extents on rotated drawings (#2432)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:23:59 -04:00
Marco ArnulfoandBrandon Thomas 9bed0156df OpenType ligatures follow w14:ligatures: model, DOCX read and write, shaping (#2351)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:23:12 -04:00
Marco ArnulfoandBrandon Thomas f7de98a711 DOCX: a w:u without w:val draws no underline (fixes #2362) (#2380)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:22:57 -04:00
86ad3ddb63 DOCX: two lines in one and horizontal in vertical (w:eastAsianLayout) survive a save (#2486)
Read w:eastAsianLayout (ISO/IEC 29500-1 §17.3.2.10) into a new
CharProps::east_asian_layout and write it back after w:lang, in runs and
styles. Values outside the schema are left unset. Layout is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:56:48 -04:00
Marco ArnulfoandBrandon Thomas d2321d05d9 DOCX: positional tabs (w:ptab) keep their alignment, base and leader and go to the margins or indents, as in Word (#2367) (#2387)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:54:01 -04:00
c9f3da2fc5 DOCX: mixed Hebrew/Latin keeps its order in Word (runs split by direction, w:rtl and language) (#1543) (#1950)
* WIP for #1543

* DOCX: split mixed-direction runs and mark right-to-left pieces w:rtl so Word orders them as WordCraft does (#1543)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* DOCX: right-to-left pieces of split runs say their language (w:lang w:bidi) (#1543)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* DOCX: no phantom tracked formatting on split runs; typed text doesn't inherit run direction (#1543)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Engine: text typed or inserted anywhere takes its direction from its own letters, not the caret's run (#1543)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:41:59 -04:00
c5a9c954f7 Track Changes Options: Moves (move tracking, Moved from/to marks and colours) (gap 5) (#1779)
* track-move-options: WIP

* track-move-options: Moves options in Track Changes Options and w:doNotTrackMoves

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* track-move-options: Moved from Hidden leaves the layout; per-revision hiding

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* track-move-options: Word's mark lists, (none) moves are plain, no borders on collapsed paragraphs

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* track-move-options: hr and sv translations for Moved from:/Moved to:

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

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:24:34 -04:00
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
divyanshu2074andBrandon Thomas 963df12a11 fix(docx): trim marker id, bound bookmarks map, and support bookmarkEnd name fallback (#2252)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:22:31 -04:00
divyanshu2074andBrandon Thomas 35cbdb8002 fix(docx): support hex without hash, rgb(), and case-insensitive color names in VML and DrawingML (#2222)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:21:24 -04:00
da11d6634b Shapes: preserve DrawingML outline dashes (#2661)
* Shapes: preserve DrawingML outline dashes

* Shapes: scale dashes to effective path width (#380)

* Keep the outline dash with noFill; cap the raster dashed stroke width at 200 pt

Addresses the two CodeRabbit review notes.

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

* Use clamp for the dashed stroke width (clippy)

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:46:35 -04:00
795cad3326 Automatic text in a styled text box takes the shape style's font colour (#2032)
* Automatic text in a styled text box takes the shape style's font colour

A text box shape whose style has a:fontRef with a colour (Word's
default shape style uses lt1, white on the accent fill) shows its
automatic-colour text in that colour. WordCraft drew it black. Word,
observed with a synthetic document: only text whose resolved colour is
automatic takes the fontRef colour; a run's own colour, a paragraph or
character style colour and a colour from the document defaults all win
over it. A box without a fontRef keeps black.

- wordcraft-doc: TextBody gets `auto_color`.
- wordcraft-docx: read from wps:style/a:fontRef (theme colours resolved
  as for SmartArt).
- wordcraft-layout: lines in a text box story resolve automatic colour
  to it.

Saving does not write a shape style yet, so the colour is not kept
through a save; the reader and layout parts stand on their own.

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

* Remove stray doc-comment lines from the new test

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

---------

Co-authored-by: Daniele Mori <Dinasty@hotmail.it>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:44:45 -04:00
ba8dc7bf1c DOCX: a style ID defined twice is its last definition (#2031)
Some generators write a placeholder Heading 1 and then their own,
under the same w:styleId. The reader kept the first definition and
skipped the rest, so headings came out in the placeholder's font,
size and colour. Word uses the last definition, whole: a synthetic
document with an italic red 20 pt first definition and a bold blue
12 pt second one shows bold blue 12 pt, with nothing of the first
(no italic, no centring), for a custom style and for Heading 1 alike.

The reader now replaces an earlier definition in place instead of
skipping the later one.

Co-authored-by: Daniele Mori <Dinasty@hotmail.it>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:44:34 -04:00
NetterSilverandClaude Haiku 5.5 33f00e8dfe Keep custom footnote/endnote marks as text inside the note on DOCX save (#2647)
Found while testing crates/docx/src/write/story.rs:512-530 (InlineObject::NoteRef, the `current_note == Some(..)` branch)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:43:04 -04:00
4442390213 DOCX: keep the glossary document (building blocks) (#2631)
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-18 03:23:25 -04:00
Fin WarmanandBrandon Thomas 03e713b72f Preserve cached field result formatting (#2605)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:22:06 -04:00
Fin Warman fc15682fb0 Preserve REF field hyperlinks in PDF exports (#2597) 2026-10-18 03:21:29 -04:00
Fin WarmanandBrandon Thomas db0affc0d3 Recognize the Symbol PCL6 font alias (#2521)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:19:40 -04:00
dac8c6d6c7 Table style review follow-ups: No Border on inherited parts, cell Top beats the style (#1288) (#1296)
part that inherits its lines sends it, and the picker only shows No Border checked for it.
- CellProps::valign is Option<VAlign>: a cell's own Top (Align Top, w:vAlign="top") beats a
  table style part's vertical alignment, and round-trips through .docx.
- tableStyle edit rejects Part fields at the top level without "part".
- "font": null or "" makes a part's font inherited; clearing the dialog's font box sends it.
- Document ui.dialog's optional "part" in docs/control-protocol.md.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:26:42 -04:00
5b49d692a5 Table style editor formats every table style part (#714) (#887)
* #714: table style editor formats every table style part

Modify/New Table Style: "Apply formatting to" any of the 13 ECMA-376
§17.7.6 parts (whole table, header/total row, first/last column,
odd/even row and column bands, four corner cells), each with font,
size, bold, italic, underline, colour, fill, borders (line style,
width, colour; all/none/outside/inside/each edge) and the 9-way cell
alignment, over a live preview.

- doc: TablePart, PartFormat, PartSet; TableStyleParts::part/set_part
  (new parts and per-part jc/vAlign in `regions`)
- layout: generic part resolution incl. band2, corner cells, alignment
- docx: every tblStylePr type read/written with pPr jc and tcPr vAlign
- engine: table.newStyle/modifyStyle take ECMA part names, `part`,
  `parts`, per-edge `borders`, `underline`, `align`/`vAlign`/`cellAlign`
- ui: dialog moved to dialogs_table_style.rs; ui.dialog takes `part`

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

* i18n: Hungarian and French strings for this change

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

* Review fixes for #887: Bulgarian strings for the table style parts

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

* #714: Vietnamese strings for the table style parts (main's new vi catalog)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* hr: match the catalog's word for banded

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

---------

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-15 01:13:56 -04:00
109956bb90 Tables: preserve row table border exceptions (#1708)
* Tables: preserve row table border exceptions

* Tests: shorten the row border regression name

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:58:59 -04:00
7af9748ace M1: fields keep w:dirty through saves; computing a result clears it (#877) (#946)
A field begin marker flagged w:dirty by a producer that couldn't compute
its result (pandoc-style TOC placeholders) lost the flag on save, so Word
no longer refreshed the field on open. Mirror w:fldLock end to end:
FieldState, InlineObject::Field/FieldStart, the reader (fldChar and
fldSimple) and the writer (buffered, range and TOC-spanning begins).
Update Field/Update Table clear it on the fields they compute, like
Word's F9; untouched fields keep it. Round-trip and engine regression
tests, incl. a pin for #878 (imported TOC placeholder replaced in place,
fixed earlier by #617).

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:27:25 -04:00
mmazzieriandClaude Opus 5.5 9d4d2ea01c Legacy VML freeforms (header rule lines) are drawn and kept on save (#1659)
* DOCX: read VML freeforms (v:shape with a path) as freeform shapes

Legacy VML shapes with their own `path` (ECMA-376 Part 4), such as rule
lines in headers, were dropped on open and lost on save. They are now
read as freeforms: m/t/l/r/c/v/x/e commands in the coordsize/coordorigin
space, nf/ns, v:fill and v:stroke opacity; arcs drawn straight, formula
references read as 0. Saved as DrawingML custom geometry.

Tests: header rule read and round-tripped; hostile paths and coordinate
spaces stay finite and never panic.

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

* DOCX: VML freeforms whose path uses formulas aren't drawn

Formulas (@n, from v:formulas) aren't evaluated; reading them as 0 drew stray lines. Parity row states the fill-opacity limit (none at 0, otherwise opaque, as for DrawingML fills).

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

* DOCX: VML freeforms reuse the watermark's vml_fraction

The watermark reader (#1328, now on main) brought its own vml_fraction;
the freeform reader had an identical copy, which no longer compiles after
the merge. Keep one.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:08:59 -04:00
13070ca85e Layout: field instructions do not add blank paragraphs (#1665)
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:59:41 -04:00
Fin WarmanandBrandon Thomas 6528f972db Shapes: draw flowchart OR spokes (#1644)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:21:49 -04:00
718eb02b89 DOCX: keep row-level w:tblPrEx when saving (#1637)
Co-authored-by: Larry Hymes <19394645+lhymes@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:21:23 -04:00
87410b4e3f DOCX: keep w:formProt when saving (#1636)
Co-authored-by: Larry Hymes <19394645+lhymes@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:21:11 -04:00
Larry HymesandLarry Hymes ed980e4df7 DOCX: keep an explicit horizontal w:textDirection when saving (#1635)
Co-authored-by: Larry Hymes <19394645+lhymes@users.noreply.github.com>
2026-10-14 21:04:15 -04:00
Larry HymesandLarry Hymes 98cda1ea50 DOCX: keep a bare w:pgNumType when saving (#1634)
Co-authored-by: Larry Hymes <19394645+lhymes@users.noreply.github.com>
2026-10-14 21:04:04 -04:00
63ed4daab2 Fonts: a Bidi theme slot names the theme's complex-script font (#1619)
w:asciiTheme="minorBidi" (and majorBidi, and w:cstheme) read as the
theme's Latin font, so a template's titles came out in Calibri where
Word draws Arial. Measured in Word: the slot names the theme's a:cs
font, and an empty a:cs names nothing, so the font inherited from the
style applies.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 20:35:37 -04:00
74a279d5f2 Fonts: a missing roman font is drawn in Cambria, as in Word (#1613)
WordCraft chose a substitute for a font that isn't installed from its
name alone, so an unknown brand font always came out sans. Word reads the
font table instead: measured with the same missing font, it draws it in
Cambria when w:family is roman and in Calibri when it is swiss or auto.
A report whose font table calls its brand font roman got a sans footer
where Word draws serif.

The font table's roman fonts are read into the settings, and layout looks
a missing one up as Cambria (Caladea where Cambria isn't installed). The
other classes keep the existing substitutes, which already start with
Calibri's metric clone.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 20:31:42 -04:00
fe9d560b49 Layout: paragraph spacing adds up when HTML auto spacing is off (#1612)
space after and the second's space before, as Word does by default. Word
adds them instead when the document's compatibility options include
w:doNotUseHTMLParagraphAutoSpacing. Measured in Word with five paragraphs
of 12 pt before and after: 24 pt between them with the option, 12 pt
without, the same in compatibility modes 14 and 15. A 60-page report with
the option lost 6 pt per paragraph, about 60 pt on its cover.

The option is read, laid out in the body and in boxes, and saved back.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 20:29:43 -04:00
Brandon ThomasandClaude Opus 5.5 3b16face49 CI: the rtTriangle VML fixture draws a right triangle
Follow-up to #1900: the fixture test vml_preset_shapes_are_drawn also
expected the isosceles triangle for VML shape type 6 (#1599).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:00:19 -04:00
Brandon ThomasandClaude Opus 5.5 4570952069 CI: VML shape type 6 is a right triangle in the preset-mapping test
#1599 made VML spt 6 (rtTriangle) a RightTriangle; #1442's mapping test
still expected the isosceles Triangle, so it failed on main.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:54:31 -04:00
90d8844e44 Shapes: a right triangle keeps its right angle (#1599)
DrawingML's rtTriangle was read as an isosceles triangle, so a cover
page's white corner cut-outs pointed up into a peak instead of cutting a
V into the picture above them. It is now its own shape kind, drawn with
the right angle at the bottom left, wrapped and glued along that outline,
and saved back as rtTriangle. The .doc reader maps msosptRightTriangle (6)
to it too.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 19:51:13 -04:00
rabis7575andClaude Opus 5.5 663de991df Flat OPC: reading bounded everywhere in the file, CDATA base64 read (#1353) (#1580)
The Word XML Document (Flat OPC) reader counted only the parts it kept:
a repeated or nameless part, a child of the package that isn't a part
and anything nested inside one were read without being counted, nesting
had no cap, and a part's inline XML was copied before its size was
checked. Every child of the package now counts against the part limit
and every part's data against the part and package size limits, kept or
not; nesting and the elements of each child are capped as the XML tree
reader caps a part's; and data is checked before it is copied.

Base64 in pkg:binaryData was taken from plain text only, so CDATA and
character references were dropped and a picture written that way lost
bytes. Both now read as the same text, and an entity there is an
invalid base64 error.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:37:48 -04:00
9c57b950b9 Page Setup: Justified vertical alignment (#1320) (#1435)
* #1320: justified vertical page alignment, in DOCX, layout.verticalAlign and Page Setup

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

* #1320: a section's last page takes its own vertical alignment; pin space-after and multi-column cases

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

* Docs after rebasing on main

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

---------

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-14 19:31:37 -04:00