Commit Graph
223 Commits
Author SHA1 Message Date
cf9a4ff7fa STYLEREF fields show each page's result in headers and footers (#775) (#882)
* #775: STYLEREF fields in headers and footers show each page's result

Running headers/footers with STYLEREF (e.g. the current chapter title) are
evaluated per page at layout time (ECMA-376 §17.16.5.59): the page's first
paragraph of the style (\l: last), else the nearest before, else after.
Style names/ids, heading levels (STYLEREF 1), character styles, and the
\n \r \w \s \t \p switches. Body STYLEREF fields update with the other
fields. STYLEREF added to Insert › Quick Parts › Field with its options.

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

* #775: STYLEREF error text in our own words

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

* Review fixes for #882: translate the STYLEREF Field dialog strings

Adds the style picker label, the six STYLEREF switch labels and the
STYLEREF description to all 18 interface catalogs.

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

* STYLEREF: reuse the style index while the body and styles are unchanged

Headers with STYLEREF rebuilt and hashed the body's style index on every
layout pass (echelon's follow-up note on #882). The layout cache now keeps
the index with the body blocks it came from (shared, so an edited block is
a different pointer) and the styles/numbering hash, and rebuilds it only
when either changed.

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

* vi: STYLEREF field strings

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 16:56:47 -04:00
3e20488c15 Comments: an option to show only the selected text's comment (#1055) (#1066)
* test(engine): drop a needless drop() that clippy 1.97 rejects (drop_non_drop)

* Comments: a click inside a selected balloon moves the caret in its text (#1013)

- The canvas under the markup area also "contains" the pointer, so a press on the balloon counted as a click on the page and left it
- Presses over a balloon card now belong to the card, in both the deselect check and the document click in mouse()
- Test: press, drag and release inside the selected balloon; a click on the page still leaves it

* Comments: an option to show only the selected text's comment (#1055)

- Track Changes Options › Comments: "In the margin" (default, as in Word) or "Only for the selected text"
- The second mode takes comments out of the margin column; the comment under the caret, or the clicked one, floats beside the page, over its edge in a narrow window
- The floating card has its own foreground layer, so presses on it never reach the canvas
- review.trackingOptions takes commentDisplay: margin|selected
- comment_range_at_caret: the comment whose range holds the caret
- Strings in all catalogs; tests for the option, the helper and the card's clicks

* vi: comment display options

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-14 16:55:27 -04:00
77da439d0b DOCX: carry unmodelled parts; keep comment ids, style defaults, latent styles, list ids and app props (#1034) (#1060)
* DOCX: merge settings.xml into the original instead of regenerating it (#1034)

A save regenerated word/settings.xml from the model, so every setting the
model doesn't hold was dropped: compatibility options (lost in 18 of 18
real-world Word files), rsids, document variables, proofing state and the
colour scheme mapping.

The reader now keeps the original part when it holds such settings, and
the writer merges into it: the original root and every unowned child stay
byte for byte; owned children follow the model in CT_Settings order; in
w:compat only compatibilityMode is replaced. The privacy options are the
model's since #1062, so they are owned too. Kept children that point at a
relationship (a mail-merge data source) are dropped. Anything unexpected
falls back to the generated part. Files WordCraft wrote itself keep
nothing extra.

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

* DOCX: carry unmodelled parts and keep comment ids, style defaults, latent styles, list ids and app props (#1034)

Builds on the settings.xml merge.

- Parts the package or main document relate to that WordCraft neither
  models nor writes (webSettings, stylesWithEffects, people, add-in task
  panes, the thumbnail, future parts) are carried verbatim with their
  relationships and content types; relationship types the writer
  produces, and any the body refers to by id, are not (HANDLED_RELS).
- commentsIds / commentsExtensible: durable ids and UTC dates are read
  into the model and regenerated against the written paragraph ids.
- w:default on localized default styles, w:latentStyles, lists'
  nsid/tmpl/multiLevelType, and app.xml Template/Company/Manager/
  HyperlinkBase round-trip; counts are not carried, and Company/Manager
  are dropped when the file asks to remove personal information.
- Comment reference marks use the document's "annotation reference"
  style id instead of a hard-coded CommentReference.

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

---------

Co-authored-by: przemek-arch <263159043+przemek-arch@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 16:55:15 -04:00
e1f34a90d6 Proofing: a Dictionaries folder setting, and the dictionary each language found (#1063) (#1084)
File › Options › Proofing gets a Dictionaries folder: a folder of Hunspell
.aff/.dic files searched before the system locations, saved with the settings
and applied at start-up. An app started from Finder or the Dock doesn't see
WORDCRAFT_DICTIONARIES, so this is how it finds a dictionary kept in a folder
of the user's own. Changing it reloads the Polish and Portuguese dictionaries
and checks the text again. The pane lists the dictionary file each language
found, or that none was found, so missing spelling marks are explained.

Agents set it with file.options {"dictionariesFolder": path | "auto"}; a
missing folder or a relative path is refused and changes nothing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:25:00 -04:00
a04356933c UI: mini toolbar steps aside for the ribbon (#1069)
* Clippy 1.96: simplify boolean guards, drop a needless drop()

Rust 1.96's clippy flags nonminimal_bool in find_pattern, table_mouse and
table_resize, and drop_non_drop in a ribbon test; `cargo xtask ci` failed at
clippy on main. Same logic, rewritten as clippy suggests.

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

* UI: mini toolbar steps aside for the ribbon

The mini toolbar covered the ribbon's drop-downs (the font size list opened
under it). A press outside the document and the bar (the ribbon, a pane, the
rulers, the status bar) now puts the bar away until the selection changes or
the next press in the document; presses on popups (the bar's own lists, an
open ribbon list over the page) neither hide nor restore it. The bar also
moves from the Tooltip layer to Middle, so every popup and menu draws over
it, as its comment always said.

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

* UI: mini toolbar scrolls under the ribbon, not over it

With text selected, scrolling the selection up toward the ribbon carried the
mini toolbar along on top of the rulers and ribbon. The bar is now clipped to
the document area: it slides under the rulers and ribbon (or the status bar)
with the text and goes once none of it shows. Its area is constrained to the
document for hit testing too, so the hidden part no longer swallows clicks
and drags meant for the ruler or the ribbon underneath.

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-14 16:23:50 -04:00
f9f8b67c54 feat(ui): implement Ctrl+drag to copy objects on canvas (#1046)
* feat(ui): implement Ctrl+drag to copy objects on canvas

* feat(ui): implement Ctrl+drag to copy objects on canvas and update translations for "Copy to"

* vi: Copy to

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

* rustfmt

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-14 16:04:34 -04:00
1f01a13dfc Track Changes: deletions inside a field result are tracked, accepted and saved (#1037) (#1043)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:04:13 -04:00
e29fbdb0bf Page background: picture and texture fills are drawn and kept on save (#999) (#1036)
* Page background: picture and texture fills are drawn and kept on save (#999)

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

* rustfmt

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:03:32 -04:00
85dd93a2ba Table eraser: borders beside vertically merged cells merge as in Word (#1039) (#1044)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:48:19 -04:00
Marco ArnulfoandBrandon Thomas 68501de224 One Page and the zoom are saved with the document and restored when it is reopened (#1023)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:47:46 -04:00
8196624e83 Caret: Down and Up from a line start beside a table go into it, not past it (#1021)
Down from the start of the line above a table (or Up from the start of the
paragraph after it) skipped the whole table: `DocLayout::vertical` took the
nearest line covering the caret's x, and a cell's lines start a cell margin in
from the table's edge, so the paragraph beyond the table won. Lines between
the caret's paragraph and that line in document order now come first: the
caret goes into the nearest such line's table row, to the cell under it or the
horizontally nearest. Another cell of the caret's own row never counts, so Down
in a table still goes to the cell below. Pinned in `tests_typing.rs` and
`docs/typing-parity.md`.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:31:22 -04:00
e9d5da31d4 Track Changes: rejecting a whole inserted paragraph keeps the next paragraph's style and numbering (#1020)
Reject All (and Accept All of a whole deleted paragraph) joined the next
paragraph into the emptied one, so the next paragraph took the removed
paragraph's properties: a § heading after a rejected list item became a
list item and later § numbers shifted. An emptied paragraph is now removed
whole, as Word does. Found while round-tripping a Word-saved contract.

Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:31:04 -04:00
aedbbc9325 feat(engine): rename documents from the title bar and open account settings from the badge (#1016)
* M10: enable document renaming and account settings

* no-mistakes(review): Allow case-only document renames without overwriting files

* no-mistakes(review): Keep unsaved rename as session file name, not Title

* no-mistakes(review): Keep AutoSave and Recent across renames; Compare clears name

* no-mistakes(document): Document file.rename write-root rule; dedupe rename gap note

* vi: Rename Document strings

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-14 15:30:52 -04:00
5597351213 #916: Cross-reference opens a dialog (#1003)
* #916: Cross-reference opens a dialog

The ribbon's Cross-reference button ran insert.crossReference with no
target, which only returns the target list, so nothing happened. It now
opens a Cross-reference dialog: reference type (heading, bookmark,
equation, figure, table), what to insert (text, number, page,
above/below) and the item, ending in insert.crossReference. Scripts and
agents still get the target list.

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

* i18n: Cross-reference dialog strings in the audited catalogs (ru, hu, tr, bg)

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

* i18n: Cross-reference dialog strings in pl, it and vi

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

---------

Co-authored-by: justinjohn0306 <babyjjbabyjj549@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 15:30:32 -04:00
Marco Arnulfo ee33d71173 Focus hides the ribbon, status bar and panes; Escape brings them back (#1017) 2026-10-14 15:09:32 -04:00
987c41364d Tables: AutoFit in a nested table, whole cells shaded for a selection through a table, Shift+Right at a cell's end (#1004)
* Tables: AutoFit in a nested table fits within its cell (#971)

A double-click on a nested table's column border let the table widen up to
the page's right margin, far past the cell around it. It now widens only up
to the right edge of that cell's text (a right-to-left table's start margin
on the right); the columns right of the border still keep their widths.

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

* Tables: a selection through a table shades its rows' whole cells (#971)

A selection from text into or across a table takes whole rows (#722), but
only the letters in the cells were highlighted. Every row it takes from start
to end now shades all its cells (an empty one too), as selected cells do, and
the letters in them aren't shaded twice (`table_mouse::selection_rects`, used
by the canvas). A selection that starts and ends in one table keeps its text
highlight, so selecting all the text of a cell doesn't look like selecting the
cell; a nested table in a shaded row shades with it.

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

* Typing: Shift+Right at a cell's end selects the cell first (#971)

Shift+Right out of the cell the selection is in selected that cell and the
next one at once. As in Word, the first step out of the cell now takes the
cell alone (its end-of-cell mark) and the next press goes on into the next
cell; in an empty cell the first press selects it. Shift+Left in
right-to-left text, the forward step there, does the same. Pinned in
`tests_typing.rs` and `docs/typing-parity.md`; progress docs for all three
table fixes of #971.

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-14 15:08:57 -04:00
Brandon ThomasandClaude Opus 5.5 6b107250a5 CI: Turkish review-command test skips without a Turkish dictionary
#1527's engine test needs a system Turkish Hunspell dictionary; on
machines without one it failed the workspace tests.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:03:01 -04:00
3fc4ef2cf4 proof: Turkish spelling, suggestions, grammar and hyphenation (#1527)
* proof: Turkish spelling, suggestions, grammar and hyphenation

* Turkish: dictionary tests skip when no Turkish Hunspell dictionary is installed

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-14 14:45:30 -04:00
e7e4522d0d ODT: read and write tracked changes (text:tracked-changes) (#1517)
* ODT: read and write tracked changes (gap 19)

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

# Conflicts:
#	ROADMAP.md
#	crates/engine/src/tests.rs
#	crates/formats/src/model.rs
#	crates/formats/src/odt.rs
#	docs/file-format-parity.md
#	docs/gaps.md

* ODT tracked changes: deleted paragraph marks don't take the body's insertion; one region per deletion

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>
2026-10-14 14:45:07 -04:00
mjohnson7cd 4d79e18ae8 fix(math): preserve equation text color on save/load (#1467) (#1525)
Lift equation color from run-level OMML to CharProps during read,
push it back to runs during write. Keep source OMML until the color
changes to preserve control properties the model can't hold.
2026-10-14 14:29:11 -04:00
666b9afc5a Draw: Ink to Shape recognises arrows (#1520)
* feat-ink-to-shape-arrows: WIP

* Draw: Ink to Shape recognises arrows (gap 12)

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

* Ink to Shape arrows: hand-drawn shafts, more tests, docs

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

* Ink to Shape arrows: the tip is the first near-farthest point

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

* Ink to Shape arrows: step to the real tip on dense strokes

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 14:28:59 -04:00
60c4a372d5 RTF: read and write tracked changes (insertions, deletions, authors, times) (#1516)
* feat-rtf-revisions: WIP

* RTF: read and write tracked changes (gap 19)

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

* RTF tracked changes: Unicode authors, \pard keeps revision marks, pasted deletions stay deleted

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 14:28:20 -04:00
b10c97b765 Document Inspector: find hidden text outside the body and from styles (#1344) (#1515)
* #1344: WIP

* #1344: Inspect Document finds and removes hidden text in every story and from styles

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

* #1344: hidden paragraphs, note references and table-style text handled; faster removal

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

* #1344: a hidden paragraph between tables stays; note echo capped

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 14:28:00 -04:00
37d41150e6 Track Changes and content controls: table delete, Change Case and Unlink Fields respect them (#1329) (#1514)
* #1329: WIP

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

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

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

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 14:07:47 -04:00
b1e8ae07e7 ODT import: keep each section’s page properties (#1379) (#1506)
* #1379: WIP

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

* #1379: master pages on table styles start sections too; spec tidy-ups

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 14:06:49 -04:00
0c7b4a72f2 Layout: long tables keep one layout per cell and relayout without re-measuring (#1492)
- Table autofit measured every cell paragraph at an unbounded width through
  the paragraph cache, so a long table held two layouts per cell. The cache
  now keeps only the measured narrowest/widest widths (`LayoutCache::measures`,
  same key and eviction as the layouts).
- The cache key formatted each cell's table-style text formatting with Debug
  for every cell paragraph on every pass; the hash is now memoised per pass.
- Update Fields added the hidden ListEntry style even without a generated
  list, which changed the layout environment and dropped the whole cache.
  The style is now added only when a list is rebuilt.

Co-authored-by: dfgodvan <180286157+dfgodvan@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:06:17 -04:00
42c1f71b52 Custom page sizes: Size › More Paper Sizes and Page Setup width/height (#1433) (#1496)
* #1433: WIP

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

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

* #1433: one OK is one undo step; errors stay in the dialog

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 13:58:33 -04:00
bf2bd69470 HTML import: bold header cells, cell alignment and the region paper (#1381) (#1494)
* #1381: WIP

* #1381: HTML import: bold <th>, cell text-align, region paper for HTML/MD/TXT

Header cells are bold unless their own CSS says otherwise. text-align on a
td/th, or inherited from its tr/row group/table, aligns the cell's
paragraphs; a paragraph's own alignment wins, and a table's align attribute
(which places the table) no longer counts. Files that give no page size
(HTML, Markdown, plain text) open on the region's paper, as a new blank
document does: file.open uses the session's region, wordcraft-cli reads the
system region the way the app does (Region::from_locale_vars, shared with
ui-egui). Tables size to that page.

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

* #1381: the CLI uses the system region everywhere; help mentions WORDCRAFT_REGION

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 13:58:14 -04:00
1f2b2287b6 RTF import: keep section breaks and their page settings (#1378) (#1495)
* #1378: WIP

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

* #1378: pasted sections link their headers; section orientation wins over the document's

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

* #1378: Text from File clears inserted sections' headers too

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 13:44:02 -04:00
MexandBrandon Thomas 4cf13cd178 Fix Replace All bypassing content-control locks (#1488)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:43:43 -04:00
ff4e560e7d Ctrl+A stays on this page, and in a table grows cell, then row, then table, then the document (#1424) (#1478)
* Ctrl+A stays on this page, and in a table grows cell, then row, then table, then the document (#1424)

Fixes #1424

* Table: Option::map for the outer-cell selection (clippy::manual_map on Rust 1.99)

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-14 13:43:06 -04:00
47ae2d495d References: TOC entries carry their headings' list numbers (#1470)
Update Table took an entry's text from the heading's plain text, which has
no list label, so headings numbered through a multilevel list (1, 1.1)
lost their numbers in the TOC. The entries now walk the body's list
counters and put the label and the level's separator (tab, space or
nothing) before the text, as Word writes it.

Co-authored-by: dfgodvan <180286157+dfgodvan@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:21:03 -04:00
0128e117b3 Layout: table rows share their cell margins and split across pages like Word (#1177)
- Every cell in a row gets the row's largest top and bottom cell margin, so
  a cell without its own margins lines up with its neighbours.
- An exact row height covers the top border, top margin and text; the
  bottom margin comes on top. Dragging a row border, or drawing a line
  through an exact row, accounts for it.
- A row split across pages must keep its cells' bottom margin, border and
  last paragraph's space after on the first page, and widow/orphan control
  applies to the paragraphs in its cells. A row whose text fits but whose
  margin doesn't no longer moves to the next page whole.
- Each cell of a split row goes on at the top of the continuation (one
  cell's widow line no longer pushes its neighbours down), and the
  continuation is as tall as the cell needing the most room, and at least
  the rest of the row (a minimum row height holds over the pages).
- A nested table's cell the page bottom cuts through goes on from the cut
  with what is left of it, and the text after it stays below it.
- A row of an exact height never splits: it moves to the next page whole.

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 13:06:24 -04:00
Marco Arnulfo 6e6cfba765 Fonts embedded in a .docx lay out and export its text (#1176)
* Fonts embedded in a .docx lay out and export its text (fixes #1171)

* Fonts: forget lookups made while an embedded family was missing (#1171)
2026-10-14 13:06:05 -04:00
DevCoderMax 5b9010f6ad Picture transparency/corrections/color are absolute from the original + transparency slider (#1175) 2026-10-14 13:05:50 -04:00
25ca4b0752 Full screen on F11 and View › Window › Full Screen (#980) (#1153)
* #980: full screen on F11 and View › Window › Full Screen

view.fullScreen toggles OS-level full screen (ViewportCommand::Fullscreen),
on F11 and ⌃⌘F (macOS); Escape leaves it when nothing else takes Escape.
The window's own full screen changes (WM, macOS button) keep the flag in
step. Disabled on the web (the browser's own F11). Next Field moves from
F11 to Word's other keys, Alt+F1 / Alt+Shift+F1 (Shift+F11 kept).
Ribbon button with a drawn icon, keytip, translations in all catalogs.

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

* #980: Escape collapses a selection before leaving full screen; F11 only off macOS

In full screen, Escape left it even when it had a selection to collapse
or a drag to cancel; it now leaves only when nothing else takes Escape.
The shortcut read Ctrl+Ctrl+F on Windows and Linux: macOS gets ⌃⌘F and
F11, others F11. Docs: Alt+F1 may be taken by GNOME/KDE; keyboard
counts re-measured from the registry (152 commands, 172 combinations).

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 13:05:18 -04:00
Marco ArnulfoandBrandon Thomas 7d3767ba99 Zotero: text typed just after a citation goes outside its field (#1162)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 12:51:00 -04:00
086d3d0384 Alt+numpad character codes (#961) (#1146)
* #961: Alt+numpad decimal character codes (ANSI, OEM 437/850, Unicode)

Hold Alt, type a decimal code, release Alt: the character is typed at the
caret through the new text.typeCode command. A leading 0 means
Windows-1252, no leading 0 the OEM code page (437, or 850 for Western
European locales), above 255 the Unicode character. Not on macOS.

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

* #961: Alt codes follow the system code pages; ignore auto-repeat, drop on blur

text.typeCode takes optional ansi (874, 1250-1258) and oem (437, 737, 850,
852, 857, 866) code pages; OEM 852/866/737/857 tables added from the public
mapping files. On Windows the shell passes the system locale's pages and, for
codes up to 255, lets the character Windows composes replace ours (above 255
ours stays and Windows' byte-reduced text isn't typed). Auto-repeat of a held
digit adds nothing, and losing focus drops a half-typed code.

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 12:49:57 -04:00
44086d08c8 Double-click the margin to create and edit a header or footer (#960) (#1144)
* #960: double-click the margin to create and edit a header or footer

Double-clicking the top or bottom margin in Print Layout now edits the
header/footer that page shows, creating it (first page, even page or
default slot) when the page has none. insert.editHeader/editFooter take
{"page"}; insert.closeHeader takes away a created one left empty.

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

* #960: making a header by double-click is an undoable, protected edit

insert.editHeader/editFooter stay pure and hand creation to the new
mutating insert.createHeader/createFooter; closeHeader removes empty new
ones through insert.removeEmptyHeaders. An empty new header is never
saved, one an undo brings back goes on the next close, and a new header
goes in the first section (linked to later ones, as in Word).

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 12:49:37 -04:00
68cf67a6c6 CI: main passes again (Word 2003 check reads through io; first keystroke after a chrome click isn't dropped) (#1578)
* Engine: the Word 2003 format check reads through io, so commands still never touch files themselves

is_word_2003_file (#1081) opened the file with std::fs, which the file-access guard test
(commands_do_not_touch_the_file_system_themselves) forbids outside io.rs; main failed it.
A new io::read_file_head reads the first bytes inside the read root.

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

* UI: a chrome click counts the page as focused at once, so the next keystroke isn't dropped; the ribbon-focus test passes again

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>
2026-10-14 12:42:37 -04:00
04f6f53b8a Read Aloud starts PowerShell without a console window (#1122) (#1150)
* #1122: Read Aloud starts PowerShell without a console window

On Windows the speech program is PowerShell, a console program: each sentence it spoke flashed a console window over the document. It now starts with CREATE_NO_WINDOW.

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

* #1122: the console-window test checks the flags Read Aloud passes

windows_speech builds the PowerShell program, arguments and creation flags
that speak() turns into its Command; std can't read creation flags back from
a Command, so the test checks that value (CREATE_NO_WINDOW) and the arguments.

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>
2026-10-14 02:05:18 -04:00
aa113f0801 Copied equations reach other programs as MathML (#816) (#1147)
* #816: copied equations reach other programs as MathML

A copy or cut whose selection holds an equation now also puts HTML on the
system clipboard, with each equation as Presentation MathML (wrapped in
<semantics> with the linear format as an application/x-unicodemath
annotation), so Word, LibreOffice, Google Docs and browsers paste a
formatted equation instead of `y=2x_s`.

- formats: new mathml module, a writer from the equation tree (runs,
  fractions, scripts and pre-scripts, radicals, n-ary operators, delimiters,
  functions, limits, accents, bars, border boxes, group characters, equation
  arrays, matrices, phantoms) and a bounded reader (our annotation, else the
  presentation elements, else a TeX annotation). HTML export writes
  equations as MathML; HTML import (open and paste) reads <math> back.
- engine: edit.copy/edit.cut (and column copy) return `html` and keep
  Session::clipboard_html when the fragment holds an equation.
- ui-egui: Services::set_clipboard_html; copy sites put HTML with the text
  through it, falling back to egui's text copy.
- wordcraft: arboard set().html() with the text alternative; on Wayland
  without data-control the text-only path is kept.

OMML on the clipboard (Word's "Office Open XML") needs custom clipboard
formats arboard can't set; left as a follow-up.

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

* #816: honest clipboard docs; MathML reader fixes (token cap, function names, column vectors)

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 02:04:46 -04:00
2dcde62220 A dragged shape anchors in the column it lands in, so that column's text wraps around it (#884) (#1139)
* #884: a dragged shape anchors in the column it lands in, so that column's text wraps around it

arrange.bounds (the canvas drag) re-anchored a moved object to the paragraph
under its top edge but ignored x: on a multi-column page it took the last
paragraph starting above that y in any column, usually one in the last column.
Layout places a float when it lays out its anchor, so text in an earlier column
(already laid out) ran through a shape dropped there. anchor_paragraph now picks
the paragraph in the column under the object's centre (the ink placement uses
the first stroke point).

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

* #884: a dropped object finds its column from the section's column geometry

Columns came from the distinct left edges of the page's text, so a
paragraph shifted by mirrored indents on an even page made a column of its
own and the drop anchored to the wrong paragraph. The columns now come from
the section's column boxes on the page. Notes in the layout parity doc that
lines a paragraph continues into the next column don't wrap yet.

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 02:03:12 -04:00
c3d45adeab Comment balloon buttons work, so an empty comment can be deleted (#1085) (#1132)
A press on a selected balloon's Delete, Reply or Resolve button counted
as a click on the page and closed the balloon before the button saw its
click. Presses inside a balloon no longer leave it.

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 02:01:56 -04:00
99fa307cf2 Honour Remove personal information / date and time on save (#1062) (#1131)
* #1062: honour Remove personal information / Remove date and time on save

w:removePersonalInformation and w:removeDateAndTime (ECMA-376 §17.15.1.69-70)
are read into Settings, written back in CT_Settings order, and honoured by the
DOCX writer: comment and revision authors saved as "Author" (initials "A"), no
dc:creator / cp:lastModifiedBy, no w:date on comments and tracked changes. The
open document keeps its real values. New file.privacyOptions command sets both.

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

* #1062: reading privacy options has no side effects; File › Options › Save check boxes

file.privacy reads the settings (a pure command: no undo step, no unsaved changes);
file.privacyOptions only changes them and refuses an empty call. File › Options › Save gets
a "Privacy for this document" section with the two check boxes, translated in every catalog.

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 02:01:37 -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
Dante Souza e SouzaandBrandon Thomas 196f828f3f Expand sample-text formulas on Enter (#1067) (#1101)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 01:55:30 -04:00
65b66c6216 Export: page images draw headers and footers at full strength (#1106)
file.exportPng (and `wordcraft-cli render`) rendered with the on-screen
display options, which dim headers and footers while the body is edited,
so exported pages showed grey headers; the PDF export and Word's exports
don't. Page images now render with dimming off.

Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:54:13 -04:00
RegalDragoon200andBrandon Thomas 2689f3c6a3 Italian proofing: it_IT and it_CH spelling, suggestions, grammar and hyphenation (#951) (#1090)
* Italian proofing: it-IT spelling, suggestions and grammar (#951)

* Italian proofing: Review Language menu and editing language

* Italian proofing: interface test for editing language

* Italian proofing: find the Swiss it_CH dictionary too

* Italian proofing: TeX hyphenation patterns (MIT option)

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 01:52:47 -04:00
09b8b436cc Save as Word 2003 XML Document (#1081) (#1088)
Write WordprocessingML 2003 by rewriting the DOCX writer's package into the 2003 vocabulary; file.save/saveAs take format wordml2003, File > Export lists the format, CLI convert --format wordml2003, a plain save over a 2003 file keeps it.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 01:52:42 -04:00