Commit Graph
698 Commits
Author SHA1 Message Date
2e062168f0 Text effects: legacy outline, shadow, emboss and imprint are drawn (#2534) (#2548)
The legacy run properties w:outline, w:shadow, w:emboss and w:imprint were
read, resolved and saved but never drawn. Layout now turns them into text
effects (hollow outlined letters; a sharp grey copy down-right for shadow
and emboss, up-left for imprint, as Word's PDFs show them) and draws them
through the existing WordArt path, so screen and PDF both get them and the
PDF keeps the text. WordArt w14 effects on the same run take precedence.

Text drawn from outlines keeps synthetic bold (a widened outline, or one
in a solid fill's colour and transparency). Colour glyphs (emoji, no
outlines) and top-to-bottom runs with upright characters are drawn as
plain glyphs, and tracked insertions and deletions keep their legacy
effects.

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-18 03:50:15 -04:00
8fb4d44d2c Different First Page: page 1's header can be edited and inserted (#2826) (#2833)
With Different First Page on, page 1 shows the first-page header, but
Insert > Header/Footer/Page Number always wrote the default one (which
page 1 no longer shows), Edit Header without a page went to the default
too, and a double-click in page 1's header area was ignored while
another header was being edited (the usual case: edit the header, tick
Different First Page, double-click page 1). Now hf_part and edit_hf use
the slot of the caret's page, and a double-click in a header/footer
zone switches to the one that page shows even from another header.

Fixes #2826
Fixes #2811


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

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:47:13 -04:00
abb36aceb2 #1270: pasted HTML blockquote is an indented paragraph, not Quote (#2737)
LibreOffice puts a paragraph indented on both sides on the clipboard as
<blockquote>, its margins in the page's stylesheet. Paste mapped it to the
centred, italic Quote style. On paste only, it now becomes a Normal
paragraph indented by the blockquote's stylesheet margins (else 0.5 in each
side). Opening .html files still maps blockquote to Quote.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:46:54 -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
ce38b0c372 #1297: copied equations go in the MathML clipboard formats Word reads (#2614)
A copy or cut of one equation on its own now also puts its MathML in the
platform's MathML clipboard formats, beside the HTML (with MathML, #816) and
the linear text: "MathML" and "MathML Presentation" (UTF-16LE) on Windows,
public.mathml.presentation and public.mathml on macOS. These are the names
W3C MathML 3 section 6.2.4 registers, and Microsoft's "MathML Support in
Microsoft 365" doc says Office checks them first when it pastes math. No
public clipboard format carries OMML. A copy with text around the equation
doesn't offer them, so Word keeps the text.

Not yet verified in Word: the parity docs say MathML clipboard formats, not
an editable paste.


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 03:46:15 -04:00
Fin WarmanandBrandon Thomas a3fcb9ed47 Fonts: keep Arial Unicode MS fallbacks proportional (#2657)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:45:37 -04:00
Alik ff61b32177 Tests: the Alt+0233 test expects the system code page's character (#2040)
alt_numpad_code_types_its_character_without_keytips always expected é, but on
Windows the app (rightly) reads 0233 in the system locale's ANSI code page, so
a Russian system (1251) types й and the test failed. The test now works out the
expected character the same way, as its Alt+233 half already did.
2026-10-18 03:45:07 -04:00
da84f48040 A cell emptied by widow control takes its row to the next page (#2035)
split_row already keeps widow/orphan control in each cell paragraph.
When that moves all of a cell's lines on although its first line would
fit, the other cells stayed on the page and the row was split beside
an empty cell. Word moves the whole row on instead. Observed with
synthetic documents (exact 14 pt lines, a one-line cell next to a
longer one near the bottom of the page):

  long cell | room for | Word      | before          | now
  4 lines   | 2        | 2 + 2     | 2 + 2           | 2 + 2
  3 lines   | 2        | row moves | short cell kept | row moves
  3 lines   | 1        | row moves | short cell kept | row moves
  4 lines   | 1        | row moves | short cell kept | row moves
  5 lines   | 3        | 3 + 2     | 3 + 2           | 3 + 2
  3, control off | 2   | 2 + 1     | 2 + 1           | 2 + 1

Co-authored-by: Daniele Mori <Dinasty@hotmail.it>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 03:44:56 -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
MarekSpodBiedry ace1adc624 Format Painter: double-click keeps it on, armed and sticky look different (#2785) (#2822) 2026-10-18 03:44:13 -04:00
4d8204c2f2 #2634: a table inserted in right-to-left text is right to left, so Tab moves left (#2688)
Tab already follows reading order (next logical cell) and right-to-left
tables (w:bidiVisual) already lay the first cell out at the right. The
bug was that insert.table ignored the caret paragraph's direction, so a
table made while writing RTL text was a left-to-right table and Tab went
rightwards. Like Word, insert.table now makes a right-to-left table when
the caret's paragraph reads right to left.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:43:47 -04:00
Charan Rathore 5b322e1530 fix(commands): Export PDF and PNG without a path ask where to save (#2665)
Signed-off-by: Charan Rathore <180254320+charan-rathore@users.noreply.github.com>
2026-10-18 03:43:27 -04:00
NetterSilverandClaude Haiku 5.5 70c5c44f01 Promote the cell below when deleting a vertical merge's first row (#2649)
Found while testing crates/doc/src/table.rs:129 (Table::delete_row), called from crates/engine/src/cmd/table.rs:526 (del_row)

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:43:16 -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
4950690479 #498: Indonesian (id-ID) spelling proofing (#2576)
Indonesian spelling reads the installed Hunspell id_ID dictionary at runtime
(Debian/Ubuntu hunspell-id, LibreOffice dict-id, the Dictionaries folder or
WORDCRAFT_DICTIONARIES), like Czech, Polish and Portuguese: nothing is bundled,
and without a dictionary Indonesian text gets no spelling marks.

- ProofLang::Id for id, id-ID, ind and the legacy in tag
- suggestions reuse the dictionary-driven Czech ranking (MAP/REP/TRY/KEY of
  the loaded dictionary), nothing Indonesian hard-coded
- hyphenated reduplication accepted whole or by parts (kupu-kupu, anak-anak);
  hyphenated clitics after capitals/abbreviations (KTP-nya, se-Indonesia)
- generic grammar only: repeated word (offers anak-anak first), space before
  punctuation, double space, with Indonesian messages
- Review > Language, status bar, Set Proofing Language, File > Options >
  Proofing list Indonesian; name translated in every catalog
- tests use a hand-written synthetic dictionary; engine end-to-end test

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:42:52 -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 bd0521d476 Prefer a covering text face for missing black-circle glyphs (#2582)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:23:05 -04:00
55bef52d38 Comments readable in the dark theme (#2045) (#2563)
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 03:22:46 -04:00
df9c29f673 Read Mode keeps the keyboard and shows whole, unobscured columns (#1772) (#2591)
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-18 03:22:26 -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
b2b8ad8f52 Font list: entries of tall-ascent fonts are no longer cut off (#2570) (#2598)
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:21:48 -04:00
Fin Warman fc15682fb0 Preserve REF field hyperlinks in PDF exports (#2597) 2026-10-18 03:21:29 -04:00
aa665b16a9 #2592: Split Cells and Convert Text to Table dialogs (#2593)
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 03:21:14 -04:00
8c1a39ba95 A new source's Tag follows Word's format; editing keeps the Tag (#2094) (#2571)
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 03:20:56 -04:00
16622f0853 #1876: Web Layout text fills the window width (#2539)
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 03:20:37 -04:00
NetterSilverandClaude Haiku 5.5 75122c1178 Keep leading U+3000/NBSP in Markdown import and round trip (#2538)
Found while testing crates/formats/src/markdown.rs:253 (join_lines); also the trim_start() calls at lines 229, 311, 426 and 432, against escape_line_start at line 1077

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-18 03:20:26 -04:00
dhruvLearner8 ad820dce6c Remember Navigation Pane visibility across launches (#2526) (#2528) 2026-10-18 03:20:14 -04:00
Fin WarmanandBrandon Thomas 3507a34430 Retain cell top insets across table row page breaks (#2524)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:19:59 -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
Fin Warman 74c1f26c4e Resolve exact OpenType full font names (#2516) 2026-10-18 03:19:21 -04:00
Rees 2f6311cc37 M15: keep inherited proofing choices in Language dialog (#2717) (#2736)
Read effective character formatting so unchanged OK preserves language and no-proof choices inherited from defaults and styles.
2026-10-17 17:00:10 -06:00
NetterSilverandClaude Haiku 5.5 5834dc5a19 Shorten Russian "Accessibility" label so it fits the options sidebar (#2672)
Fixes #2668

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
2026-10-17 16:59:29 -06:00
skylar taylor-barrick df28b78de9 refactor(pdf): reconcile shared writer with current main (PDFW)
Refresh #1558 onto fd0bb2fb while preserving the shared writer migration
and current upstream layout, DOCX, field and table behavior.
2026-10-15 11:47:00 -06:00
fd0bb2fb1e #954: macOS ⌘←/⌘→ go to the line's start and end (#972)
On macOS, Cmd+Left/Right were bound to Word Left/Right (same as Option+arrows).
Bind them to Line Start/End there, as in Word for Mac and every Mac text field;
Option+arrows still move by word. Windows/Linux keys are unchanged.

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-15 01:35:42 -04:00
b3b6c451e3 fix(ui): draw Arabic and other non-Latin comment text in balloons (#950)
* fix(ui): draw Arabic and other non-Latin comment text in balloons

Comment balloons and the Comments pane draw comment text and author
names with the interface faces (Inter, Source Sans, CJK), which have no
Arabic, Hebrew, Thai and so on: every such letter was a box. The app now
borrows a document fallback face that covers the comments, as the window
caption does (#840), checked once per document revision.

egui also lays bidi runs and words out left to right, so a right-to-left
comment read backwards. Read-only balloons now wrap the text by word
widths, put each row in display order (Unicode bidi runs, words of a
right-to-left run reversed, brackets mirrored) and set right-to-left
paragraphs against the right edge. Text without right-to-left letters is
laid out exactly as before. The editable fields (the selected balloon and
the Comments pane) keep logical order.

Also adds my contributor entry (sync_github_name).

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

* One contributor entry each

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

---------

Co-authored-by: Mohamed Youssef <ms.rightsteps@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 01:34:11 -04:00
6a2bad8afa Tables: formulas with cell references, functions, number formats and F9 (#907)
* Tables: formulas with cell references, functions, number formats and F9

Table Layout › Data › Formula was a fixed set of string checks: any
unknown function was silently summed, cell references weren't read, and
Update Field never recalculated a formula field.

Formulas are now parsed and evaluated (crates/engine/src/cmd/table_formula.rs):
- cell references and ranges by grid column (=B2+B3, =SUM(B2:C3)),
  + - * / ^, parentheses and the comparisons = <> < <= > >=
- SUM, AVERAGE, COUNT, MAX, MIN, PRODUCT, ABS, INT, SIGN, ROUND, MOD,
  IF (only the branch taken is worked out), AND, OR, NOT, TRUE, FALSE,
  DEFINED
- ABOVE/LEFT/RIGHT/BELOW sum the first run of numbers next to the cell,
  as observed in Word; `=SUM()` is a syntax error, as in Word; a bare
  `=SUM` takes ABOVE or LEFT from the cell
- the \# numeric picture (`#,##0.00`, `0%`, `;` sections)
- an unknown function or a syntax error is an error and inserts nothing;
  input is bounded (formula length, nesting depth)

Update Field (F9) re-evaluates formula fields from the cells they read.

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

* Review fixes for #907: clamp formula ranges to the table, cell budget, depth for ^ and unary +

- A range reads only the rows and grid columns the table has, so SUM(A1:BK32767) over a small
  table no longer walks two million grid positions.
- One evaluation reads at most 100,000 cells; past it the formula fails with "Formula too
  complex" (F9 shows !Formula too complex) instead of hanging on a hostile document.
- Each ^ in a chain and each unary + counts toward the nesting limit of 32, like unary -.
- Regression tests for all three.

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

* M2.1: locale-aware number parsing and formatting in table formulas

* contributors: credit rbr48

* Merge main; rustfmt; one contributor entry for rbr48 (#906 had added one)

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

* Drop a stray merge marker from ROADMAP

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

---------

Co-authored-by: rbr48 <rajbrur48@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 01:31:18 -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
Brandon ThomasandClaude Opus 5.5 18b680c9aa Recovery: a record lock held for a moment is waited out, not skipped
new_draft_restarts_restores_unsaved_and_explicitly_discards failed in a
workspace run: the reopened session found no recovery record, because a
process another test was starting held the record's lock for a moment (a
forked child shares it until exec) and scan() skips locked records. The
same can happen to a user when the app starts a helper process. lock() now
tries again up to ten times, 10 ms apart, before treating the record as
owned by a live instance.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:26:06 -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
Hamza NaveedandBrandon Thomas ffacc4c08c Fields: refresh unlocked DATE and TIME when a document is opened (#1679)
A `DATE` or `TIME` field showed whatever was cached the last time the file was
saved, so a letter written from a template went out carrying the date the
template was saved. Word refreshes those two whenever it shows, prints or
exports a document; WordCraft only did so on an explicit Update Field(s), on
insert, or when printing with "Update fields before printing" on. Headless
`convert` to PDF was the worst of it: nothing hints the date is old.

Refresh them where a document is read instead, in a new `io::opened` beside
`ensure_nonempty` -- the two things a freshly-read document needs -- so `text`,
PDF, rendering and the window all show the same, current value. The model's
stored result is the one source they share; patching each output path would
have needed a clock in two places and still missed the next one.

Narrow on purpose. Only `DATE` and `TIME`: `CREATEDATE`, `SAVEDATE` and
`PRINTDATE` record when something happened and are not "now", and `REF`, `TOC`
and citations stay on explicit update, as in Word. A field carrying `w:fldLock`
keeps its stored result, which is what the lock is for. Headers, footers and
text boxes are covered too, not only the body.

The field code is untouched, so saving writes the field back as it was, with a
current cached result -- as Word writes one.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 01:07:07 -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
add4851443 layout.pageSetup validates its section values (#1582) (#1584)
* #1582: WIP

* #1582: layout.pageSetup {section} validates like Size, Margins and Columns

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

* #1582: cargo fmt

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

* #1582: shared page limits; notes, borders and columns validated too

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

* #1582: page limits are pub, as in #1496

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

* #1582: drop page constants #1496 now defines on main

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

* #1582: a page smaller than 36 pt passes Page Setup with zero margins

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 00:55:10 -04:00
Brandon ThomasandClaude Opus 5.5 9a19e09d97 CI: rustfmt the proofing language list
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:53:07 -04:00
6e25d482c4 proof: Czech (cs-CZ) spelling from the installed Hunspell dictionary (#1048)
Same pattern as Portuguese (#797) and Polish (#346): the cs_CZ
dictionary is read from the system at runtime (e.g. Debian/Ubuntu
hunspell-cs), nothing is bundled. Suggestions use the dictionary's own
MAP/REP/TRY/KEY; grammar stays generic (repeated word, spacing), with
digit ratios like "3 : 1" exempt. The Czech interface now writes new
documents (and opened .txt/.md) in cs-CZ.

Engine fixes the Czech dictionary needs, also used by pl/pt:
- PFX/SFX header lines may carry a trailing comment (cs_CZ has
  "SFX y Y 80 #…"); rule lines are left literal;
- NOSUGGEST stems are accepted when typed but never offered;
- KEY is parsed for neighbour-key suggestions.

Tests use a hand-written synthetic Czech dictionary only; an
end-to-end engine test mirrors the Portuguese one.

Co-authored-by: Losminek <301412321+Losminek@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 00:52:43 -04:00
94bfc29b0b Tab stops: edit on the ruler (corner selector, click, drag, drag off) and an inline-edit table in the Tabs dialog (#976)
* Ruler tab stops: corner selector, click to set, drag to move, drag off to clear

The ruler's markers were read-only (double-click opened the Tabs dialog,
click sets (Left, Center, Right, Decimal, Bar; right-click cycles back),
clicking the ruler sets one on the half-division grid (Alt places
freely), dragging moves it with a dashed guide down the page and a
position tooltip, and dragging it off the ruler clears it. Markers draw
per kind, mirrored for right-to-left paragraphs. Each gesture runs
para.tabs once on release, so it is one undo step and inherited-stop
clearing keeps working. Double-click still opens the dialog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Progress docs: ruler tab stops (ui-parity Rulers row and revision history, ROADMAP progress log)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Tabs dialog: OK applies what the fields show; double-click a stop to edit its position

Selecting a stop, changing its alignment or leader and pressing OK now
applies the change without Set, and a new position typed while a stop
is selected (the dialog preselects the first) is set on OK, so adding a
stop never requires leaving the dialog for the ruler. Double-clicking a
stop in the list edits its position in place: Enter or clicking away
commits (the interface unit by default, any unit spelled out, either
decimal separator), Escape cancels, and the commit moves the stop in
one clear + set, keeping its alignment and leader. Clear still wins
over a re-typed cleared position, and junk input changes nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Tabs dialog: New button, delete from the list, and the list fills its frame

New stages a fresh stop - deselects, proposes the next free multiple of
the default interval after the last stop (skipping cleared positions),
resets alignment and leader, and focuses the position field - and Set
or OK adds it. Every row gets a small x that clears its stop, and the
Delete key clears the selected one when nothing is being typed; the
selection and an in-progress inline edit follow when a row above them
goes. The list is a fixed 150 pt wide and the rows and scrollbar fill
it, so labels stay left-aligned, the x sits at the frame's edge clear
of the scrollbar, and the scrollbar docks at the edge instead of
floating mid-list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Tabs dialog: the stop list is an inline-edit table

Each row edits its stop in place: a unit-aware position field (rows
resort into position order once the edit ends, never mid-typing; two
stops landing on one position merge, the edited row winning), an
alignment dropdown and a leader dropdown (the closed box shows just the
fill), and the x that clears the row. The double-click editor and
move_stop go: the table subsumes them. The selected row keeps an accent
outline, its edits sync the fields below, and the frame widens to fit
the three columns.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Tabs dialog: the table is the whole editor

New adds a row straight into the table at the next free multiple of
the default interval, selected and ready to edit in place, and the
position field, the Alignment and Leader radio groups and the Set and
Clear buttons go: every one of their jobs is a table cell or the row's
x. What stays is New, Clear All, the default interval, the cleared
list and OK/Cancel, with OK applying the staged diff as one para.tabs
call. The staged-field methods (set, clear, on_ok) go with them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Review fixes: safe ruler gestures, one tab tolerance, dialog cleanups

Ruler gestures are primary-button only, a click on an indent marker
places nothing (the markers absorb their clicks), Escape cancels a drag
like a table-border drag (#217), and a drag is stamped with its
paragraph: the caret leaving it mid-drag, or a release the ruler never
saw, abandons the gesture instead of applying it elsewhere or late. The
dragged marker no longer double-draws on its first frame. One
TAB_POS_EPS in the doc layer replaces four hardcoded 0.5s across the
engine, the dialog and the ruler. The dialog's normalize is a no-op
unless a position edit happened, its combos share the ruler's KINDS and
one LEADERS table, dead code and a stale doc comment go, and the
ui-parity revision row moves to the table's top. Regression tests cover
each gesture rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 00:40:35 -04:00
cb6418dc6c Tables: one selected cell's border drags in its row only (#970)
Selecting one cell (Table Layout > Select > Select Cell, or a click along
its left edge) and dragging its left or right border moved the border in
the whole column: one selected cell wasn't told apart from text selected
inside it. `ResizeTarget::limit_to_selection` now takes the selected cells
from `Session::cell_range` (#637) when they're in the dragged table, so one
selected cell's border moves in its row only, as in Word, like the border
of cells selected across (#720). Text selected within a cell still moves
the whole column.

Tests: engine `a_selected_cells_border_moves_only_in_its_row`; kittest
`a_selected_cells_border_drags_in_its_row_only` (a click along the cell's
edge, then a real drag). Progress docs: ui-parity, ROADMAP.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-15 00:35:32 -04:00
5c8dba229f fix(references): clear imported TOC field result on update (#878) (#953)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:32:19 -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
5f83a4727a get_text and CLI text omit tracked deletions (#866) (#944)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:21:06 -04:00