Commit Graph
1428 Commits
Author SHA1 Message Date
29a5abf3e7 ODT: custom styles with an outline level keep their name when edited (gap 19) (#5770)
* ODT gap 19: custom styles with an outline level keep their name when edited

A paragraph in a custom style with style:default-outline-level was read as
WordCraft's Heading 1 or whichever level it named. Unedited, it was written as
read. Edited, it was saved as Heading_20_1, so LibreOffice showed WordCraft's
blue heading instead of the file's look, and WordCraft drew it that way too.

Read such a style as a custom style that carries the outline level. The
paragraph stays a heading at that level in WordCraft, is numbered with the
file's outline as LibreOffice numbers it, and is saved as
<text:h text:style-name="Head" text:outline-level="1">. The level comes from
the style, not the paragraph kind, because kinds stop at Heading 6.

The writer dropped a custom style's name on any heading, title, quotation or
code paragraph, so custom styles based on those lost their look when edited
too. It now keeps the name for every kind.

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

* ODT: a heading's own outline level gives its number (gap 19)

LibreOffice numbers a heading by its text:outline-level. WordCraft took the
number from the style's level, so a heading stating another showed the wrong
number, or one on a level the outline doesn't number. A heading in a list stays
the list's. Found by CodeRabbit on this PR, as was the Title-based style test.

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

* ODT: a heading in a list of its own keeps it, and an unnumbered level takes no number (gap 19)

Any list whose first level was linked to a style counted as the outline's, so a
heading deeper in one lost its list on save. Now only the number the outline
itself gives counts. A style based on a numbered heading, at a level the outline
doesn't number, took its parent's number. Found by CodeRabbit on this PR.

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

* ODT: a heading past level 6 keeps its list (gap 19)

The written outline has no number past Heading 6, so counting such a heading's
list as the outline's lost its number. Found by CodeRabbit on this PR.

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

* ODT: a style with a heading numbered by its own level stays on the outline (gap 19)

Writing it with no list style took the number from that heading in LibreOffice.
Written so, the numbers match the file's. Found by CodeRabbit on this PR.

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

* ODT: a heading past level six numbered through its style keeps its list (gap 19)

The written outline numbers only six levels, and the list was dropped for a heading numbered only through its style, so at levels 7 to 9 it lost its number once its style was rewritten.

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

* Gap 19: docs status lines are main's, with this change in front

Merging main had kept this branch's older status lines in ROADMAP.md and
docs/file-format-parity.md, which set main's 2026-11-08 date back and dropped
main's newer entries and the parity doc's CLI path note.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-09 16:16:06 +01:00
bd9e726cda ODT: pictures keep the size their file gives (gap 19) (#5721)
* ODT gap 19: pictures keep the size their file gives

Opening a file fitted a picture wider than the text to the text, and shrank any
picture wider than 6.5 in to that, even with its size stated. So a save with no
edit to it took its paragraph off being written as read, and LibreOffice laid
it out differently. Pictures in OpenDocument files keep the size they state.

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

* ODT: a picture keeps a size stated on one side, in proportion (gap 19)

Only a frame stating both sides kept them. On one side alone a picture past a
column's width was still fitted to it, and past 1,584 pt each side was cut
separately, which distorted it. A size past any page can no longer overflow the
sizing either. Found by CodeRabbit on this PR.

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

* ODT: an inline picture with no stated size is fitted to the text (gap 19)

Skipping the fit for OpenDocument files left one wider than the text on a small
page, which `main` fits. A size the file states still stands, and a test now fails
without that. The fit happens when it is read, so its paragraph stays as read.

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

* ODT: an object's picture is fitted too, and one side follows the picture's own shape (gap 19)

An inline object with no stated size kept its picture's width on a small page, which main fitted. A picture stated on one side took the other from the size already capped to the page: 10 by 1000 pixels at 500 pt wide came out 468 by 1584 pt, not 15.84 by 1584.

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

* ODT: a frame stating a size of nothing is fitted like one stating none (gap 19)

svg:width="0pt" counted as a stated size for fitting, though the picture then came at its own size, so it could run past the text on a small page.

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

* ODT: a picture in line that is raised, turned or mirrored is fitted too (gap 19)

Such a picture has a float record that is still in line, which the reader's fit skipped, so with no size stated it stayed 468 pt wide on a small page. main fitted it.

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

* Gap 19: docs status lines are main's, with this change in front

Merging main had kept this branch's older status lines in ROADMAP.md and
docs/file-format-parity.md, which set main's 2026-11-08 date back and dropped
main's newer entries and the parity doc's CLI path note.

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

* Order the parity header's change notes newest first

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-09 16:15:23 +01:00
15b608660f ODT: frames keep the page they are anchored to (gap 19) (#5705)
* ODT gap 19: frames keep the page they are anchored to

A frame or shape anchored to a page was written without its
text:anchor-page-number, so one on page 3 came back on page 1 in LibreOffice
after a save. Keep the number and write it back. WordCraft's own layout still
places such a frame on its anchor paragraph's page.

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

* ODT: a page number only counts on a page anchor, and groups keep theirs (gap 19)

A paragraph anchor with a text:anchor-page-number was saved as a page anchor,
which LibreOffice ignores the number on and so moved the drawing. A group
anchored to a page lost its page. Found by CodeRabbit on this PR.

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

* ODT: a frame moved in WordCraft loses its page number (gap 19)

Nothing cleared it, so the save put the frame back on its old page. One placed by
its paragraph is no page anchor any more, and one dropped on a page takes that page.

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

* ODT tests: put the moved frame's test with the other moves (gap 19)

At the end of the file it collided with other ODT PRs' tests.

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

* ODT: a frame moved onto its paragraph loses its page number (gap 19)

The number was only hidden while the frame was placed by its paragraph, so placing it by the page again (arrange.position) brought back the old page. The merge of main also lost the test attribute on a test from main, put back here.

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

* Gap 19: docs status lines are main's, with this change in front

Merging main had kept this branch's older status lines in ROADMAP.md and
docs/file-format-parity.md, which set main's 2026-11-08 date back and dropped
main's newer entries and the parity doc's CLI path note.

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

* Gap 19: parity row says when a moved frame keeps its page number

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-09 15:57:37 +01:00
52fbc7f068 ODT: a document keeps its language when styles are rewritten (gap 19) (#5704)
* ODT gap 19: a document keeps its language when styles are rewritten

The default language was only ever saved by copying the file's own styles
back. Adding numbering or creating a style writes them from the model, which
never stated it, so LibreOffice showed the document in its own default
language. Import ignored it too, so such a document was proofed as English and
the styles WordCraft adds said en-US.

- Read the language from Standard, else the default paragraph style
- Write it in the model-written default style
- A file stating none has none, rather than en-US from new documents

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

* ODT: a language keeps its script, and a style with no parent the default style's language (gap 19)

sr-Latn-RS was written as fo:country="Latn" with RS dropped, and fo:script
wasn't read, in all three of the language attributes. A style with no parent
took Standard's language when it differed from the default style's. Found by
CodeRabbit on this PR, as were the added assertions and doc comments.

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

* ODT: the language written is Normal's, as WordCraft has it (gap 19)

The default style took its language from the defaults, though WordCraft's document language is Normal's: a Word file with a French Normal over en-US defaults saved as English.

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

* Gap 19: docs status lines are main's, with this change in front

Merging main had kept this branch's older status lines in ROADMAP.md and
docs/file-format-parity.md, which set main's 2026-11-08 date back and dropped
main's newer entries and the parity doc's CLI path note.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-09 15:56:59 +01:00
Aleksander HelgakerandClaude Opus 5.5 d75364067d ODT: table and row backgrounds show behind their cells (gap 19) (#5727)
* ODT gap 19: table and row backgrounds show behind their cells

fo:background-color on a table or row wasn't read, so it was missing on screen
and gone in LibreOffice after a save. WordCraft's tables have their backgrounds
on their cells, so it goes on the cells with none of their own.

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

* ODT: a column's default cell style shows, and transparent clears a background (gap 19)

A cell with no style took the row's or table's colour over its column's default
cell style, and `transparent` on a table or row fell through to its parent style's.
From CodeRabbit on this PR, checked in LibreOffice 26.8.2.2: a row's default cell
style isn't applied there, so it isn't here.

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

* ODT: a column's default background is read once, not its style's name (gap 19)

The name was cloned for every repeat of a column, which a hostile file could make
use to run out of memory, and allocation failure can't be caught.

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

* ODT: a transparent cell style stops its parent's colour (gap 19)

A cell style saying transparent over a coloured parent took the parent's colour and hid the row behind it. LibreOffice 26.8 shows the row, from a column's default cell style or the cell's own.

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

* Gap 19: docs status lines are main's, with this change in front

Merging main had kept this branch's older status lines in ROADMAP.md and
docs/file-format-parity.md, which set main's 2026-11-08 date back and dropped
main's newer entries and the parity doc's CLI path note.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 15:56:09 +01:00
Aleksander HelgakerandClaude Opus 5.5 d102b2f08b Gap 19: ODT parity row says how tab stops are saved (#6094)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 13:41:02 +01:00
FredApps fda8758ec9 Merge upstream text hinting and window scrolling 2026-11-08 17:59:05 +01:00
FredApps c5fe6c6816 Merge pull request #3197 from vangel76/ui-text-hinting
Dark-theme interface text on Linux no longer looks semi-bold
2026-11-08 17:55:10 +01:00
vangel76andClaude Opus 5.5 72a7afd9b5 Merge upstream/main into PR #3195
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 17:37:24 +01:00
FredApps 36ed94a09e Merge upstream control delivery changes 2026-11-08 17:35:46 +01:00
FredApps 0655e651fb Merge upstream control delivery changes 2026-11-08 17:35:06 +01:00
FredApps 0e3f4dd315 Merge pull request #2596 from vangel76/control-mcp-delivery-1326-1331
Control/MCP: no replay of mutations with unknown outcome; queued requests honour timeouts (#1326, #1331)
2026-11-08 17:32:33 +01:00
FredApps ae257ba784 Merge upstream font tests and window scrolling 2026-11-08 17:26:55 +01:00
FredApps 0495fbb789 Merge pull request #2783 from vangel76/test/fixed-layout-fonts-2729
#2729: Layout tests use pinned bundled fonts
2026-11-08 17:24:43 +01:00
vangel76andClaude Opus 5.5 e7e9bc2759 Merge upstream/main into PR #3195
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 17:24:03 +01:00
FredApps be75ee90e3 Merge upstream main and reconcile control documentation 2026-11-08 17:22:59 +01:00
FredApps 480511eaa5 Merge upstream main into control delivery changes 2026-11-08 17:15:38 +01:00
FredApps aa262cf7d8 Merge pull request #2595 from vangel76/mcp-insert-table-2242
MCP: insert_table tool for filled tables (#2242)
2026-11-08 17:11:08 +01:00
FredApps 2d97c8443e Merge pull request #1056 from vangel76/feat/1053-rtf-notes-comments
RTF: footnotes, endnotes and comments are read and written (#1053)
2026-11-08 16:38:59 +01:00
FredApps d5d2b22dac Merge pull request #331 from vangel76/feat/322-view-windows
View › Window: Switch Windows, Arrange All, View Side by Side and Synchronous Scrolling
2026-11-08 16:37:54 +01:00
vangel76andClaude Opus 5.5 5125de6f1f Merge upstream/main into PR #1056
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 16:18:07 +01:00
vangel76andClaude Opus 5.5 59030b1bb5 Merge upstream/main into PR #331
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 16:18:07 +01:00
vangel76andClaude Opus 5.5 4050f1bf8b Merge upstream/main into PR #5115
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 16:18:07 +01:00
02e74a5d9c Add Delete Previous Sentence with Ctrl+Shift+Backspace (#5094)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-08 16:13:33 +01:00
Valentin KleinandClaude Opus 5.5 69d2a2e131 docs/gaps: gap 16 RTL in HTML/ODT/RTF landed (#2696)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 15:57:36 +01:00
David PrietoandMartin Ball 735c2a5382 layout: keep post-break anchors with text across a wrapped row (#5937)
* layout: keep wrapped-row anchors after page breaks

* test: pin wrapped picture drops, history and DOCX

* docs: record wrapped-row anchor coverage

* docs: log wrapped-row picture anchoring fix

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:59:44 +01:00
batistalucasvandMartin Ball fde39ab061 Desktop: constrain newly opened window dimensions to available screen width (#5871) (#5876)
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:44:01 +01:00
d34e341d75 Charts: an automatic series colour follows the series' c:idx (#5963) (#5975)
* Charts: an automatic series colour follows the series' c:idx (#5963)

A series without its own colour took the accent for its place in the
file. Word picks it by the series' c:idx (c:order doesn't matter), so a
chart whose series aren't numbered 0, 1, 2... in file order, such as a
lone series with idx 2 after a deletion, got different colours.

c:idx is read strictly as the unsignedInt the schema says; a missing or
invalid value falls back to the place in the file.

* Charts: the renumbered test helper fails if ser's layout changes (#5963)

---------

Co-authored-by: kiwi4boy <kiwi4boy@gmail.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:41:38 +01:00
535b675e8a DOCX: paragraph indents and spacing beyond 16 bits wrap as in Word (#5655)
## What changed

Word stores paragraph indents (`w:ind` left/start, right/end, hanging, firstLine) in 16 signed bits and paragraph
spacing (`w:spacing` before/after) in 16 unsigned bits. A file that writes a larger plain integer is read by Word
modulo 2^16: `w:left="66256"` is a 720-twip (0.5") indent in Word. We read the full value, so such a paragraph was
indented by about 46 inches and its text was pushed off the page.

- `crates/docx/src/units.rs`: new `tw_i16` / `tw_u16` readers. A plain integer keeps its low 16 bits (two's
  complement for indents, so 66256 -> 720 and 65535 -> -1); values with a unit or a fraction read as before.
- `crates/docx/src/read/props.rs` and `read/styles.rs` use them for `w:ind` and `w:spacing` before/after, in direct
  and style formatting.
- In-range values read exactly as before.

## Testing

- `crates/docx/tests/it/twips_wrap.rs`: synthetic documents with out-of-range indents and spacing in paragraph and
  style properties check the wrapped values; in-range values and values with units are unchanged.
- Unit tests for the two readers; `cargo xtask ci` passes.

Found by a real-file corpus comparison against Word's own PDFs: a file whose indents exceed 16 bits shows its text on
the page in Word and off the page here.

Co-authored-by: tpawley2001 <268951182+tpawley2001@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:39:22 +01:00
a891d861b7 Layout: contextual spacing drops the space after a cell's last Normal paragraph (#5450)
Word treats the end-of-cell mark as a paragraph in the default (Normal)
style, so a cell's last paragraph with contextual spacing in that style
(or in an undefined style, formatted as Normal) loses its space after, as
beside any same-style neighbour; one in another style keeps it.
`layout_box` kept the space after of every last cell paragraph except
automatic spacing, so the row grew by it and the text below the table sat
that much lower (docxide-pdf cell-end-text-context, cell-end-empty-context:
8 pt).

A middle or bottom cell of a row split across pages lines up with the
other cells' kept text, which `align_in_part` counted with its full space
after; it now stops at the cell's content end, so a dropped space after
(contextual or automatic) no longer pushes the aligned cell down.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:27:07 +01:00
Kevin JosephandMartin Ball 85434d2c20 layout: line numbers take the "Line Number" style, not a fixed 10 pt grey (#5788)
* layout: line numbers take the "Line Number" style, not a fixed 10 pt grey

Line numbers were laid out in 10 pt grey in the default font, whatever
the document said. Word formats them with the "Line Number" character
style over the document's defaults: in a manuscript whose defaults are
Aptos 12 pt, Word's PDF shows them in Aptos 12 pt black (measured in
Word for Mac), WordCraft's in 10 pt grey.

Lay the number out with the document's "Line Number" style when it has
one, else its plain defaults, so size, font and colour follow the
document.

Test: line numbers take the document's default size and automatic
colour, and a "Line Number" style's size and colour when there is one.

* docs: progress log and parity entries for line-number-style

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:26:44 +01:00
vangel76andClaude Opus 5.5 833c0154aa Merge upstream/main into control-mcp-delivery-1326-1331
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 06:18:12 +01:00
vangel76andClaude Opus 5.5 0a3fb5828e Merge upstream/main into feat/1053-rtf-notes-comments
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 06:18:12 +01:00
Kevin JosephandMartin Ball 7634553900 Layout: superscript and subscript text at 0.65 of its size rounded to the half point (#5802)
Word for Mac sets raised and lowered text at 0.65 of the run's size
rounded to the half point (measured from digit advances in its PDF
export at base sizes 8 to 36 pt: 11 pt -> 7, 12 -> 8, 24 -> 15.5).
WordCraft used 0.65 exactly, so lines with superscripts were up to
0.5 pt wider and some broke a word early.

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:02:01 +01:00
cd4f755dcf DOCX: nested block-level content controls stay block-level when a bookmark follows them, so Word opens the file (#5811)
## What changed

The DOCX writer's content-control planner (`crates/docx/src/write/sdt.rs`) decides whether a control that
covers a whole paragraph is written block-level (`w:sdt` around `w:p`) or run-level (inside the paragraph).
It only treated the control as covering the whole paragraph when nothing but control-end markers followed
its end. Bookmark, comment and permission range markers after the end now count as "nothing" too, so the
control stays block-level and the markers stay at the end of its paragraph.

## Why

Word writes a body-level `_GoBack` bookmark (`<w:bookmarkStart w:name="_GoBack"/><w:bookmarkEnd/>` directly in
`w:body`) in nearly every file it saves. When the last body-level content control was followed by it, the
reader put the bookmark at the end of the control's paragraph, and the writer then turned the control (and
any controls nested in it) into run-level `w:sdt`s inside that paragraph. For a repeating section
(`w15:repeatingSection` / `w15:repeatingSectionItem`), which must wrap whole paragraphs or rows, Microsoft
365 Word then refuses to open the saved file ("the file appears to be corrupted"). Plain nested block
controls changed what they cover the same way.

Found by the real-file corpus: Word reported our saved copy of a form with a repeating section as corrupted.

## How it was tested

- New tests in `crates/docx/tests/it/nested_block_controls.rs` (synthetic packages): a repeating section around
  one paragraph, plain nested block controls, nested block controls in a table cell, a run-level control that
  must stay in its paragraph, and repeating sections / block controls followed by a body-level bookmark (with
  and without data bindings) all round-trip with the controls at the same level and the text kept.
- `cargo xtask ci` passes.

Co-authored-by: tpawley2001 <268951182+tpawley2001@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 05:59:51 +01:00
Jhong-Ken ChenandMartin Ball f0fb31c0c9 XML: normalise line ends in text and CDATA (XML 1.0 §2.11) (#5897) (#5898)
Text nodes were decoded without line-end normalisation, so a file
saved with Windows line ends kept the CR of each CR LF: a line break
inside w:t read as two spaces. Text and CDATA now go through
quick-xml's xml_content, which reads CR LF and a lone CR as one line
feed; a CR written as &#13; stays a CR. Attribute values were already
normalised.

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:48:20 +01:00
David PrietoandMartin Ball 4cd6ffa564 doc: preserve following formatting when deleting an empty paragraph (#5929)
* doc: preserve following formatting when deleting an empty paragraph (Leaf #152)

* doc: regress empty paragraph merge formatting (Leaf #152)

* engine: cover empty heading deletion, history and DOCX (Leaf #152)

* typing: pin empty heading deletion semantics (Leaf #152)

* docs: record empty paragraph formatting coverage (Leaf #152)

* docs: record the empty paragraph deletion fix (Leaf #152)

* test: retain structured controls across an empty paragraph join

* docs: explain empty paragraph engine regressions

* docs: explain the empty heading keyboard regression

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:48:03 +01:00
Senura DissanayakeandMartin Ball 52934ef110 docbin: brace and bracket shapes are unfilled unless the file fills them (#5943) (#5945)
A .doc bracket or brace AutoShape (MSOSPT 85-88, 185, 186) is drawn as
its box, and with no fFilled property it got the default white fill, an
opaque block over the text behind it. Word's shapetypes for these shapes
are unfilled by default (VML filled="f"), so they now get a fill only
when fFilled is set.

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:45:35 +01:00
3291be0fbf Layout: an empty paragraph after a leading floating table takes no space before (#5440)
Word drops the space before of an empty paragraph that follows a document's
leading floating table: its PDFs of docxide-pdf's leading-empty-before-float-0
and -12 put the text after the empty anchor (beside the table) at the same
place for 0 pt and 12 pt before.

Flow::gap_before (crates/layout/src/flow.rs) kept it: a floating table
takes no room in the flow and clears `pb.prev`, so the paragraph after it
was laid out like a document's first paragraph, with its full space before.
It now drops it for body block 1 when block 0 was placed floating on this
page (the page's float_tables), the page is fresh and the paragraph holds
nothing but markers (bookmarks, comment and permission ranges). It
doesn't check that the paragraph sits beside the table, so below a
full-width leading float an empty paragraph loses its space before too;
that case is untested. Later pages and columns, and paragraphs with text,
keep their space before.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:45:17 +01:00
4259dd1b2e Layout: before Word 2013 layout a floating table moved below another keeps its own left edge (#5442)
A floating table with tblOverlap=never that is moved below another floating
table steps in to its distance from the text's left edge only in Word 2013
layout (compatibility mode 15). clear_of_float_tables read the mode from the
legacy shift, the first cell's left margin before mode 15, being zero, so a
mode 12 table whose first cell has no left margin was taken for Word 2013
layout and moved 9 pt right (docxide displaced-anchor-*-compat12-overlap0-*:
Word 72 pt, WordCraft 81 pt). Pass the mode itself.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:31:43 +01:00
vangel76andClaude Opus 5.5 e344bb4243 Merge upstream/main into PR #3197
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 05:19:14 +01:00
vangel76andClaude Opus 5.5 4a507de3d6 Merge upstream/main into PR #3195
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 05:19:14 +01:00
vangel76andClaude Opus 5.5 87e08051bf Merge upstream/main into PR #2596
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 05:19:14 +01:00
5c0a62a736 Layout: no line break between a full stop and an opening parenthesis (#5437)
UAX #14 allows a break between a full stop (IS) and an opening
parenthesis (OP), so `break_positions` (crates/layout/src/para/paragraph.rs)
let "29.05.2023г.(оформление ЗУ)" and "2023.(Example detail)" wrap after
the full stop. Word keeps such a token whole and wraps it at the space
before, as the docxide-pdf dot-open-token* Word references show. The
break between the two is now dropped (before the kinsoku pass, so
break-anywhere wrapping still applies).

Test: layout::tests::a_full_stop_before_a_parenthesis_is_no_break.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:18:09 +01:00
855a0fe9bd Layout: a nested table's Can't Split row moves on with its outer row (#5456)
A row is split across pages from its flattened items, so a row of a nested
table marked w:cantSplit was cut between its lines like any other text in
the outer cell. Word leaves such a row whole: the outer row moves to the
next page whole when that row comes first in it (docxide-pdf
nested-first-row-prefix300/330-cant1), and otherwise splits above it
(inferred).

table_flow now moves the cut up to just below the top of the highest nested
Can't Split row it runs through (nested_keep_cut): the outer row's bottom
band below that top, so the text and nested rows ending there stay on the
page. A row runs through the cut when it ends below the room left for text,
as in the split itself; one walk over the rows from the lowest top up moves
the cut until none does. A vertically merged cell doesn't stretch its row's
band, and a cell aligned to the middle or bottom is measured from its top, as
the split runs it. This applies to the first part of a row and to the kept-chain and
header-row checks. On a fresh page, where moving on gains nothing, a nested
row taller than the room still splits at the room rather than running into
the bottom margin.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:03:54 +01:00
33fa77a892 Track Changes: resolving away an empty paragraph with a section break keeps the next paragraph's properties (#5667) (#5734)
Rejecting an inserted paragraph (or accepting a deleted one) whose mark carries a section break
left it empty; remove_empty_para refused it for the break, so the next paragraph was joined into
it and took its properties (style, numbering, indents). The empty paragraph now goes whole and
its section joins the following one first, as deleting the break does (remove_section_break).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 04:51:36 +01:00
efd476cbc7 #5197: the ruler shows the spaces between text columns (#5261)
In a section of several text columns the horizontal ruler was one white
strip from margin to margin. The space between each two columns is now
shaded like the margins, so the ruler shows where each column ends and
the next starts.

The edges of a space can be dragged, through layout.columns, one undo
step per drag, to change the spacing. Equal columns stay equal, so every
space changes with it, as in Word for Mac. Between unequal columns only
the column at that edge and the space change. As in Word for Mac, the
middle of a space has no grip of its own. Columns stay at least a
quarter inch wide and spaces 0-300 pt, as the Columns dialog allows,
snapped to 1/16 inch. Right-to-left sections are read from the right
edge.

Outside the main text, outside Print Layout and in a table the ruler is
unchanged. A selection across sections shows no spaces, since a drag
would set the columns of each section it touches; nor does a page the
caret isn't on. Columns from a file that the command wouldn't take back
(over 12, a column under a quarter inch, a space over 300 pt, wider than
the text) are shaded but not dragged. A tab stop's marker keeps its own
drag where it sits on an edge. The indent markers still follow the page
rather than the caret's column; that is #2196 (PR #2215).

Fixes #5197.

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

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 04:51:18 +01:00
d3fcf10bcc Layout: a picture in a paragraph whose hidden mark closes a section keeps its room (#5288) (#5347)
* Layout: a picture in a paragraph whose hidden mark closes a section keeps its room (#5288)

A paragraph with a hidden mark and no plain text was a quiet mark: laid out
with no room, its drawing kept. An in-line picture adds no plain text, so a
hidden-mark paragraph holding only a picture qualified, and the text after it
(the next continuous section in the report) was drawn over the picture. Such
a paragraph, or one holding a note reference mark, now keeps its line; one
with only floating objects stays quiet.

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

* Layout: a legacy check box in a hidden closing mark keeps its room too (#5288)

A FORMCHECKBOX field has an empty result, so a hidden-mark paragraph holding
only a check box still counted as quiet and the next continuous section was
drawn over the box. Fields now count as visible content when the paragraph
layout shows them as something other than their result: their code, a legacy
check box, or a drop-down's entry (the same test as the layout's `field`).

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: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 04:48:48 +01:00
b9936609dc Output counts locked SEQ fields, and not tracked-deleted ones, as Word does (#5594)
* Count locked SEQ fields and not tracked-deleted ones in output

Exporting, Word counts a locked SEQ, its \r too, though the field keeps its result, and a SEQ in a tracked deletion keeps its result and doesn't count, with markup shown or hidden (measured in Word for Windows). Output counted it the other way round: a locked SEQ was skipped and a deleted one counted.

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

* Docs: locked and tracked-deleted SEQ fields in layout parity

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

* count_seq walks the deleted ranges with one cursor

The runs come in order and the objects by offset, so a forward cursor
replaces the per-field scan of every deleted range.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 04:38:32 +01:00
7152143e45 Layout: a line can break after a hyphen before a slash, like Word (#3408) (#5348)
Word wraps "Dataprotection-/Secrecyviolation" as "Dataprotection-" /
"/Secrecyviolation". UAX #14 (LB13) forbids a break before the slash, so
the compound had no break opportunity: lines moved it whole and auto-fit
table columns widened to fit it, taking width from their neighbours.
Add the break after U+002D when a slash and a letter follow, next to the
existing break before a digit (#2712); Word keeps "fitness-/1589" whole,
so a slash before a digit gains no break. Always sort the added breaks.

Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 04:36:10 +01:00