Commit Graph
121 Commits
Author SHA1 Message Date
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
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
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
Brandon Thomasandheogun e5d9b3e7cf i18n: add Korean UI catalog and font fallback (#1216)
Korean interface catalog and Hangul font fallback by @heogun (#1216), merged with main.

Co-authored-by: heogun <mumal@MP.local>
2026-10-14 23:44:26 -04:00
Raj Patel 4062db0ac2 Shortcuts: more of Word's default keys (Insert-key clipboard, F2 file keys, field and outline keys) (gap #24) (#1166) 2026-10-14 23:01:17 -04:00
aa9c31fc69 Fix blurred text in Web and Draft Layout on long documents (#1152)
* M8: keep long Web and Draft pages sharp with raster tiles

* M8: document Web Layout blur fix with before-and-after screenshots

---------

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

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

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

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

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

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

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

* DOCX: VML freeforms reuse the watermark's vml_fraction

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

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:08:59 -04:00
Brandon ThomasandClaude Opus 5.5 850bbb5dbc Docs: merge table rows that conflict resolutions left duplicated
While landing community PRs, a few rows in gaps.md, layout-parity.md and
file-format-parity.md ended up twice (an older and a newer version).
Each pair is now one row: the newer text, plus what only the older one
said (section note options #385; allowSpaceOfSameStyleInTable).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:02:53 -04:00
Fin WarmanandBrandon Thomas 63d5faa9b2 Tables: honor consistent preferred cell widths (#1709)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 22:01:19 -04:00
35e24aa436 Layout: hide labels on page-break-only list paragraphs (#1673)
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:59:59 -04:00
13070ca85e Layout: field instructions do not add blank paragraphs (#1665)
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:59:41 -04:00
MarekSpodBiedryandBrandon Thomas 1c814e0269 Copy puts the formatted text on the system clipboard as HTML, not only copies with equations (#1523) (#1667)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:29:56 -04:00
Fin WarmanandBrandon Thomas faa621104c Layout: empty line breaks do not move floating tables (#1658)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:22:19 -04:00
Fin WarmanandBrandon Thomas 6528f972db Shapes: draw flowchart OR spokes (#1644)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:21:49 -04:00
Fin WarmanandBrandon Thomas 2c26fa58a8 Draw arrowheads on open freeform paths (#1645)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:04:33 -04:00
52190491ff Picture Format › Crop: crop handles on the canvas (#1447)
* Crop handles: Picture Format › Crop crop mode on the canvas, picture.crop takes the new size

ui.crop toggles crop mode for the selected (unturned) picture: black crop
handles trim a side or two, keeping the scale and the visible part in place;
the cropped-away part shows dimmed; Esc, Enter, a click outside or Crop again
keep the crop. A drop is one picture.crop with width/height, one undo step;
a floating picture moves by what a left/top crop trims.

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

* Crop handles: flipped pictures trim the side under the handle, stale drags drop unapplied

Review round 1: the crop maths, the dimmed preview and the floating
position shift map the on-screen side to the source side (flip H swaps
left/right, flip V top/bottom). A drag remembers its picture and is
dropped unapplied if that picture is no longer the one in crop mode.
Sizes stay within 1-4000 pt at the same scale. Pointer-path tests drive
a real handle drag and a click outside. The dim is darker, and the
canvas keeps following a crop drag's pointer.

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

* Docs after rebasing on main

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

* Crop handles: ui-parity row merged with main; #1242's Cell Size note moved to the Tables row

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

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 19:08:44 -04:00
2c33a66ca9 Word's text watermark in DOCX: drawn on screen and in the PDF, and kept on save (#1290) (#1328)
* #1290: read Word's VML text watermark from DOCX headers

Word's Design › Watermark is a VML shape with a v:textpath in the headers.
The reader dropped it, so screen and PDF showed no watermark. It now maps
it to the document's watermark model (text, font, fill colour,
semitransparent from v:fill opacity, diagonal when turned), which the
renderers already draw. Only Word's watermark shape (PowerPlusWaterMarkObject
id) or text centred on the page both ways counts; other header WordArt,
body WordArt and shapes inside text boxes are left alone. Writing it back
to DOCX is still missing (it was already lost on save).

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

* #1290: write the watermark to DOCX as a VML text shape in the headers

The DOCX writer dropped settings.watermark, so a watermark read from Word
(or set in WordCraft) was lost on save. Every header part now starts its
first paragraph with a VML shape (ECMA-376 Part 4) whose v:textpath carries
the text, font, fill colour, semitransparency (v:fill opacity) and diagonal
turn, centred on the margins behind the text, with the reader's
PowerPlusWaterMarkObject id so it reads back as the watermark, not a shape.
The shape type is our own: a straight path the text follows, fitted to the
box. Slots the pages show with no header of their own or linked from an
earlier section (default; first page with a title page; even pages with
even/odd headers) get a header holding only the watermark; linked sections
stay linked. A blank watermark isn't written. Check Compatibility no longer
warns about watermarks.

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

* #1290: give the watermark's text path a top and a bottom edge so Word draws it

With a single line as the shape type's path, Word loaded the watermark as
WordArt (right text, size and turn) but drew nothing. Checked black-box in
Word: diagonal, horizontal and header-less documents now show it.

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

* #1290: test the watermark through a Word-shaped file, table-only and even-page headers

Read → write → read of a file with Word's watermark keeps the watermark, the
headers' text and paragraphs and one shape per header; header WordArt that
isn't a watermark doesn't become one. A header holding only a table gains no
line; with odd and even pages an even header is added and later sections
stay linked.

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

* DOCX: read VML freeforms (v:shape with a path) as freeform shapes

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

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

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

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

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

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

* DOCX: VML freeforms reuse the watermark's vml_fraction

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

* #1290: move the VML freeform reader out of this PR

The three VML freeform commits (b7a6cc7, d8fa665, 945cbb4) aren't part of
#1290; they go to their own pull request so this one stays on the
watermark.

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 18:59:01 -04:00
a21b069390 fix(ui): drag a Behind Text picture or shape over the text it overlaps (#1313)
A picture with Wrap Text > Behind Text could not be dragged where it overlapped text, in the body or in a header/footer. Pressing it did nothing; once it was selected some other way (Selection Pane, arrow keys) the arrow keys moved it, but the mouse never started a drag. Reported from the field (#82).

Root cause: the hit test deliberately lets body text win over an object behind it (crates/layout/src/hit.rs: 'objects behind the text only where there's no text'), so objects::pointer never began a frame drag.

Fix: grab_at/pointer now treat a Behind Text picture, shape or group as a drag candidate even where text covers it, in body and header/footer stories. The press does not take the gesture at once: it becomes an object drag only after the pointer passes the drag threshold (pending_behind), so a plain click still places the text caret in the overlapping text. Text boxes are unchanged (their inside is their text; only their border grabs them).

Verified with three egui_kittest UI tests driving real pointer events, plus a headless ui_shot run against the real app:
- floating_picture_can_be_dragged_in_front_of_or_behind_text
- floating_header_picture_can_be_dragged_in_front_of_or_behind_text
- behind_picture_dragged_over_text_without_being_selected_first
Full wordcraft-ui-egui suite: 365 passed.

Docs: ROADMAP progress log and revision, docs/ui-parity.md, docs/gaps.md (#3).

Co-authored-by: Muthiullah Hib <thye911@mail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 18:58:44 -04:00
Eduardo SoaresandBrandon Thomas 41cf3c7563 Restrict Editing: filling in forms opens fillable content controls (#1077) (#1309)
* #1077: forms protection fills content controls (w:edit="forms")

* #1077: forms walk stays conservative for unclosed control markers

* #1077: forms protection edges — nested locks, orphan markers, tables, empty interiors

* #1077: forms protection — formFields state and Find Next Region over fillable controls

* #1077: forms protection round-trip, password guard, docs

* #1077: final review fixes — lock-param gate, per-list pairing, docs, pane perf

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 18:58:25 -04:00
e5780698f2 Accessibility: Windows Magnifier follows the caret onto a new line (#1119) (#1247)
Windows Magnifier keeps the text cursor in view through UI Automation: the
caret's own box has no width, so it takes the box of the character after
the caret, and when that has none either, of the two characters before it.
The canvas described a line's end mark (the paragraph mark, a line break)
with no width, so on the empty line Enter or Shift+Enter makes Magnifier
looked at the end of the line above and stayed there until two more
letters were typed.

A line's end mark now takes the room a selection shows a paragraph mark
with: PARA_MARK_WIDTH (5 pt, now shared with selection_rects) at the line's
visual end (Line::mark_box). Checked live with Windows Magnifier, its view
read with MagGetFullscreenTransform: after Enter and Shift+Enter it moves
to the new line at once.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 18:15:46 -04:00
ReesandBrandon Thomas bfacaf3627 fix(layout): preserve legacy table paragraph spacing (#577)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 17:22:17 -04:00
3d8f8144d9 File: recover unsaved work after a crash (#518) (#586)
* M10: retain local crash recovery on current main

* i18n: Backstage Recover in every catalog

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 17:21:57 -04:00
92c0036d72 Russian proofing: spelling, suggestions, grammar and word division (#894)
* Russian proofing: spelling, suggestions, grammar and word division

Runs tagged ru get Russian tools instead of none:
- spelling against an installed ru_RU Hunspell dictionary (not bundled;
  LibreOffice dict-ru folders searched; KOI8-R and Windows-1251 decoded),
  with ё optional, hyphenated words and solid compounds understood, не with
  adjectives, participles and verbal nouns, prefixed verbs, abbreviations
  with endings and camel case left alone, plus our supplement of modern
  words with their declensions (assets/spelling/ru-extra.txt);
- suggestions tuned for Russian: е/ё, letter groups, common confusions,
  missing spaces, typos and a stem scan;
- grammar rules with Russian messages (repeated words, spaces, sentence
  capitals with Russian abbreviations, likely missing commas before
  clause openers, doubled punctuation);
- Russian word division from the spelling rules, written as code.

* Options: Russian in the dictionaries list (ProofLang::Ru arm)

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

---------

Co-authored-by: Магда М.Г. <mail@mgmagda.ru>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:21:39 -04:00
ReesandBrandon Thomas 248dae4bd1 fix(layout): prevent list labels from inflating line height (#601)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 17:21:20 -04:00
575c5f98df .doc: open password-protected Word 97–2003 files (RC4, RC4 CryptoAPI, XOR) (#1079) (#1087)
docbin decrypts XOR-obfuscated, RC4 and RC4 CryptoAPI files after verifying the
password against the stored verifier; the engine reuses the .docx password
prompt errors. Written from [MS-DOC] and [MS-OFFCRYPTO]; synthetic tests only.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 17:21:08 -04:00
eb54d88ec0 Mail merge: .xls recipients, legacy encodings, Filter and Sort (#778) (#885)
* #778: .xls and legacy-encoding recipient lists; filter and sort recipients

- Recipient lists: legacy Excel 97-2003 .xls (BIFF8 reader on the cfb crate already in the
  tree); Excel 5.0/95 and password-protected .xls refused with a clear message; a CSV named
  .xls reads as text, an HTML one gets an error. .xls added to the picker filters (desktop,
  web, built-in browser) and drop handling.
- Text lists (and plain-text import) detect legacy encodings when not UTF-8/UTF-16: GBK/GB18030,
  Big5, Shift_JIS, EUC-KR, Windows code pages (encoding_rs + chardetng, MIT/Apache); an
  `encoding` param overrides.
- mailings.filterRecipients / mailings.sortRecipients: field, comparison (equal, notEqual,
  less, greater, lessOrEqual, greaterOrEqual, blank, notBlank, contains, notContains), value,
  And/Or joins; sort keys. Preview, Finish & Merge and labels use only the matching records
  in sort order. Edit Recipient List edits every record and keeps the filter.
- Recipient List dialog: Filter and Sort section and a merge-order column; catalog rows;
  strings in every catalog; docs.

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 #885: total order for recipient sort, Bulgarian strings

- compare_cells now ranks numbers (compared numerically) before text (compared
  case-insensitively), so it is a total order; a column mixing 9, 10 and 1a
  could make slice::sort_by panic. Regression test sorts mixed columns.
- Bulgarian catalog: the 24 Filter and Sort strings.

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

* vi: mail merge Filter and Sort 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:57:06 -04:00
ReesandBrandon Thomas 8e0f42e4bc fix(layout): match Word's contextual paragraph spacing (#568)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:56:29 -04:00
2686e20330 Docs: gaps.md re-audited against main (#1071)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 16:55:40 -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
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
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
5e8c5b3c4e PDF export: Hebrew and Arabic text extracts in reading order (#1383) (#1508)
* #1383: 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:08 -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
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
93175f979f Word 365 cover pages: page-relative positions and freeforms drawn back to their start (#743) (#1487)
- A position wrapped in mc:AlternateContent inside wp:anchor (wp14:pctPosVOffset)
  takes the Fallback's wp:posOffset; the shapes were placed at the top of the column.
- A custGeom path that ends where it started without a:close is closed, so it's
  filled as in Word; the cover's panel (and its white title) was invisible.
- Credits: add benandez to contributors/people.toml.

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

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:43:24 -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
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
0837c8b7e0 Charts of undrawn types (radar, stock...) show a placeholder (#1092)
Fixes #998

Co-authored-by: Fabio Lussi <198260595+itrycodinganditsbad@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 01:54:03 -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
Raj Patel d8cfe59c14 DOCX: keep Word ink placed directly in a run when saving (gap #2) (#1098) 2026-10-14 01:50:31 -04:00
skylar taylor-barrick a381c93e8d Merge pull request #783 from storytold/codex/craft-print-onboarding
Print: select printers and submit jobs through craft-print
2026-10-12 09:46:52 -06:00
1350cb959b Font list previews no longer keep every family in memory (#848) (#874)
* #848: font list previews no longer load every family they show

The Font box drew each entry in its own face through the full layout
pipeline, which loaded the whole family into the font database for good
(faces are never unloaded). Every trip through the list kept tens of
megabytes. Entries are now drawn with FontDb::preview_face: a loaded
family's face is shared, an installed one is read into a face the
database doesn't keep and is freed when dropped, and render_text_line
draws it without the shared outline cache.

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

* Font preview test tolerates other tests loading fallbacks; gaps rows 1–3 de-duplicated, #848 noted in row 3

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 11:14:56 -04:00
skylar taylor-barrick f0d47b6090 PRINT: integrate current main and expose native print commands 2026-10-12 17:02:36 +02:00
691b136811 #811: smooth scrolling through a very large table (#891)
Every frame fingerprinted every item of every page for the page texture
cache (thousands of cell borders a page, formatted to strings), and the
markup-area check walked every paragraph twice a frame. Page content keys
are now taken once per layout and only for pages on screen or cached; the
document facts behind the markup area are cached per revision; textures of
pages far from the view are dropped. Layout and rendering are unchanged.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 10:49:03 -04:00
8c8d5611a4 Rulers: table column and row markers; indents in a cell measured from it (#710)
* Rulers: table column and row markers; indents in a cell measured from it (#700)

With the caret in a table, the rulers show the table, as Word's do:

- The horizontal ruler marks the column borders of the caret's row (past
  the table's left edge); the vertical ruler marks the bottom of each of
  the table's rows on the page. Each marker has the resize pointer, a
  tooltip and a name for screen readers.
- Dragging a marker drags that border exactly like a drag on the page
  (#600): a dashed guide, Escape cancels, one undo step on release
  through `table.resizeDrag`. On the horizontal ruler only the column
  left of the border changes and the columns right of it move along, so
  the table's width changes (Word's ruler); a row marker sets the row's
  height. The markers follow any drag of the table's borders, on the
  ruler or on the page.
- In a cell, the indent markers and tabs are measured from the cell's
  text area (its box less its margins); they were placed as if the
  paragraph started at the page's margin.

- Layout: `cell_margins` (a cell's own margins, else the table's, else
  Word's), so the ruler uses what the layout does.
- UI: crates/ui-egui/src/table_ruler.rs; table_resize.rs starts a drag
  from a ruler marker (`grab`) and tells the ruler where a dragged border
  is (`moved`), sharing that computation with the dashed guide.
- Tests: layout (cell_margins matches where each cell's text starts);
  kittest with real pointer events: a column marker drag (the marker
  follows, one undo step, the columns right of it keep their widths), a
  row marker drag, and an 18 pt drag of the left-indent marker in a cell
  indenting the paragraph 18 pt.

Progress docs updated: ui-parity (rulers 70% -> 85%), gaps (the ruler's
column markers are off the list), target-app-parity, ROADMAP. No new
commands or labels (the tooltips reuse "Table Column Width" and "Table
Row Height"). `cargo xtask ci` passes.

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

* Rulers: Shift works the other way round on the ruler; row markers beside a vertical merge

Checked against Word (2019, Windows): on the ruler a column marker drag
changes only the column on its left and the columns right of it move
along (as before), and with Shift the neighbour makes up the difference,
so the table keeps its width; on the page Shift does the opposite. The
ruler now follows that: `ResizeDrag::plan` takes the Shift key and flips
it for a drag started on a ruler marker.

Since #669 a vertically merged cell's box spans all its rows, so a row
marker took the merge's bottom when the merge was the row's leftmost cell.
The marker now sits on the row's shortest box. (`Placed::Cell` patterns
take `..` for #669's new field.)

- Test (kittest, real pointer events): a Shift drag of a column marker
  on the ruler gives the neighbour's width to the column on the left and
  keeps the table's width.

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

---------

Co-authored-by: rabis <340973947+rabis7575@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 10:38:17 -04:00
f24a5b22b2 Tables: drags and Shift+arrows over cells select whole cells (#722)
* Tables: select, insert and move with the mouse; double-click a border to AutoFit

Like Word, on top of the border resizing:

- Selection areas: just above a column the pointer becomes a down arrow,
  left of a row the row arrow, along a cell's left edge (inside it) the
  cell arrow. A click selects the column, row or cell; a drag selects every
  one up to the pointer. Selected cells are shaded as cells, empty ones too.
- Insert buttons: left of a row border and above a column border a "+"
  shows with the border marked; a click adds a row or column there and puts
  the caret in it.
- Move handle past the table's top-left corner: a click selects the table;
  a drag shows a dashed outline and, on release, the table floats where it
  is dropped with the text wrapping around it (Word's behaviour). One undo
  step. Nested tables and tables outside the main text are only selected.
- A double-click on a column border fits the column left of it to its
  contents (the right neighbour gives or takes the room; on the right edge
  the table's width changes, up to the right margin).
- Table Layout > Select > Select Column (catalog 418/449).

Whole-cell selection: a column (or a rectangle of cells over several rows)
is not one run of the text, so selecting it makes a block of its cells'
paragraphs (`CellRange` on the existing `ColumnBlock`). Bold, alignment,
Delete, typing, copy and numbering act on those cells only (a numbered
column is one list); table commands (shading, merge, delete columns) take
the rectangle; Tab moves on without clearing it; Undo brings the selection
back. Cells of one row, or whole rows, stay an ordinary stream selection.
The format painter no longer clears a block's text (edit.pasteFormat was
handled like a paste). The mini toolbar stays hidden over whole-cell
selections and during these drags: pinned above the selection it covered
the column areas, the "+" buttons and the move handle.

Engine: crates/engine/src/cmd/table_mouse.rs - table.selectAt,
table.insertAt, table.moveDrag, table.fitColumn (page coordinates, so
agents and the control channel can do the same) and table.selectColumn;
hit testing reuses table_resize's drawn geometry, and borders keep priority
for resizing. 10 engine tests (areas, a column edited cell by cell,
numbering, rows/cells/drags, insert buttons, move, fit, format painter,
hostile params).
UI: crates/ui-egui/src/table_mouse.rs, wired into the existing canvas mouse
handler (no extra ui.interact areas, so clicks in a cell's text still place
the caret); kittest tests with real pointer events: column click, row drag,
"+" button, move-handle drag in one undo step, double-click fit.
New labels translated in all eleven catalogs. Progress docs updated: gaps,
ui-parity, target-app-parity, ROADMAP, parity checklist.
`cargo xtask ci` passes (fmt, clippy, test, assets, layers, wasm).

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

* Tables: a selected column's word count is its cells'; merged cells shade once

With whole cells selected (a column, or a rectangle that isn't one run of
the text) the status bar said "12 of 23 words": `Session::selected_text`
took everything between the selection's ends. It now takes each piece's
own text, a line each, for a column block too, so the status bar, Word
Count and the other callers see what is selected.

Since #669 a vertically merged cell is one box as tall as its rows, with a
`covered` box for each continuation row; shading selected cells skips
those, so a merged cell isn't shaded twice. `selected_paths` follows
main's `para_paths_in` (#418): a selection ending at the start of the next
paragraph doesn't take it.

- Test: a selected column's text is its three cells' and Word Count says
  three words.

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

* Tables: double-click AutoFit and the "+" buttons work as in Word

Checked against Word (2019, Windows):

- A double-click on a column border fits the column left of it to its
  contents and the columns right of it keep their widths, moving with the
  border, so the table narrows or widens (up to the right margin). The
  right neighbour used to give or take the difference.
- A "+" insert button selects the new row or column (the caret used to
  go into it); typing then goes into its first cell. If the table can't
  take another row or column, nothing changes.
- A double-click along a cell's left edge (the cell selection area)
  selects its row.

- Tests: engine (fit keeps the columns on the right; insert selects the
  new row or column, typing lands in its first cell) and kittest (real
  double-click on a border; "+" selects the row; double-click by a cell's
  edge selects the row).

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

* Tables: drags and Shift+arrows over cells select whole cells

An ordinary drag, Shift+click or Shift+arrow from one table cell into
another now selects the rectangle of whole cells between them, as Word
does, instead of the run of text between the two points: bold, alignment
and Delete leave the cells outside the rectangle alone (they already did
for Select Column and the table's selection areas). Between a cell and
text outside its table the selection takes whole rows, from the cell's
row out to the table's edge, and the text beyond. Extending again goes on
from where the selection really started and ends: dragging back into the
first cell selects its text again, Shift+Up after Shift+Down shrinks the
rectangle, and from a nested table's cell into another cell of the table
around it the outer table's cells are taken.

- Engine: `table::extend_selection` (the rule, down through nested
  tables) and `Session::sel_ends` (where a widened selection really
  runs); every caret command run with `extend` goes through it in
  `column::dispatch`, so agents get the same with `caret.*`.
- UI: a text drag extends through `caret.set` (still straight to the
  session, so nothing scrolls on every frame); the view follows the real
  caret.
- Tests: engine (drag across cells and back, nested tables, Shift+Down
  and Shift+Up through and past a table, Shift+click, from text into a
  table) and a kittest with a real mouse drag.

Progress docs updated: gaps (the known gap from #637 is gone),
ui-parity, target-app-parity, ROADMAP. `cargo xtask ci` passes.

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

---------

Co-authored-by: rabis <340973947+rabis7575@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 10:34:10 -04:00