Commit Graph
495 Commits
Author SHA1 Message Date
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
686f71cabb Save As: fill the file type list (#1399) (#1497)
* #1399: WIP

* 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 13:44:21 -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
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
cyril265andBrandon Thomas 1be06ca323 Fields: PAGEREF in headers and footers shows its bookmark's page (#1477)
* Fields: PAGEREF in headers and footers shows its bookmark's page

Headers and footers are laid out once the body is paginated, so a page
reference there now shows the number of the page its bookmark starts on, in
that page's number format, instead of the result stored in the file. Like the
page totals in headers and footers, it is filled in after pagination and
doesn't make the body lay out again. Mail-merged letters use this for
"Page X of Y" per letter (Y = PAGEREF to a bookmark at the letter's end). Page
references in the body keep their result until fields are updated.

* Contributors: add cyril265

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:42:53 -04:00
dea3e217fa Shapes: rounded rectangles keep their own corner radius (#1476)
* Shapes: rounded rectangles keep their own corner radius

A roundRect's adj (its corner in 100000ths of the shorter side) is read, kept
on the shape, saved back, and used for the screen, the PDF and Tight/Through
wrapping. Without one the corner stays at 16%.

* Render: test rounded rectangle corners through layout and rendering

* Docs: progress log and file-format parity for rounded rectangle corners

* Render: pass the shape's corner to the caret-background hit test (#299 code from main)

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:42:34 -04:00
Saad AbdullahandSaad Abdullah a9afeb4bfe fix(proof): classify each whitespace token once when checking spelling (#1335) (#1490)
Co-authored-by: Saad Abdullah <saadkhantareen@users.noreply.github.com>
2026-10-14 13:26:10 -04:00
b8b8ff6128 Layout: PAGE fields on the later pages of a paragraph show their own page (#1352) (#1481)
* #1352: PAGE fields on the pages a paragraph runs onto show that page's number

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

* #1352: keep the page map local; assert no overflow

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

* Layout: re-laying out a paragraph keeps its equation numbers

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:25:58 -04:00
cyril265andBrandon Thomas 5ff69b8bab PDF: draw horizontal and vertical lines inside groups (#1474)
* PDF: draw horizontal and vertical lines inside groups

A line or connector inside a group keeps its zero height or width (a:ext cy="0"),
and the PDF export skipped every shape without area, so dividers drawn in Word
groups were missing from exported PDFs while the screen showed them.

* PDF: test hostile sizes for flat lines

* Docs: progress log and file-format parity for lines inside groups in PDF export

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:21:21 -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
Bihacsy LászlóandBrandon Thomas 26cdede600 i18n(hu): align two new rows with the catalog terms (#1469)
"Title Bar" was "Címsor", the word the catalog uses for Heading (Címsor 1, Címsor 2); it is now
"Címsáv". "AutoFit Column by Double-Clicking" now uses "automatikus méretezés", as AutoFit
reads elsewhere in the catalog.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 13:20:50 -04:00
45d2546b63 Metafile: WMF object table without scanning, in-place shape ops, cropped DIB decode (#1463) (#1466)
* Metafile: WMF handle table finds its lowest free slot without scanning (#1463)

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

* Metafile: map shape ops in place and decode only the clipped part of a DIB (#1463)

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

* ROADMAP: progress log row for the metafile reader (#1463)

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

* Metafile: write cropped DIB pixels through as_chunks_mut (#1463)

Co-Authored-By: Claude Sonnet 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 13:20:31 -04:00
f2a96a21f9 Close prompt: a failed Save says why and keeps the document open (#1445) (#1465)
* 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 13:20:11 -04:00
17ec132c7c Tables: thick cell borders meet in closed corners (#1461)
A horizontal cell edge ran from cell edge to cell edge, so at a thick
vertical edge it started on that edge's centre line and left a notch half
the border width wide at every corner. Horizontal edges now run on over
half of each visible vertical edge, as Word draws them.

Co-authored-by: dfgodvan <180286157+dfgodvan@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:19:51 -04:00
1059b196c1 i18n: add Vietnamese (vi) interface translation (#1458)
* i18n: add Vietnamese (vi) interface translation (#1434)

* vi: translate the strings main gained since the catalog was drafted

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:19:39 -04:00
533615620e Equations: bound parser tree depth, escape \text in LaTeX, merge runs everywhere (#1456) (#1457)
* Math: bound the depth the equation parsers build (#1456)

x^1^1^1…, a/a/a… and \sum\sum… built left-nested trees thousands of levels deep within the
input cap; clean, Clone, PartialEq, Drop and serde then overflowed small stacks. Wrapping an
existing node (script base, numerator, accent, limit base, n-ary body) is skipped once it is
already 32 levels deep (iterative check); the operator is text (linear) or dropped (LaTeX).

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

* Math: escape \text{…} in LaTeX output so it reads back unchanged (#1456)

Normal-text runs were written verbatim, so a}b\c became \text{a}b\c} and read back as ab\c.
Escape \ { } % $ # & _ ~ (backslash as \backslash{}); \backslash is now a named symbol and
raw_arg ignores the empty braces after a control word. A # inside normal text no longer
numbers the equation.

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

* Math: merge runs inside every structure, not just some (#1456)

merge_runs only recursed into fractions, scripts, radicals, n-ary operators, delimiters and
functions, so the LaTeX parser left \overline{ab} as two runs where the linear parser gives
one. It now recurses through map_args for every node kind.

Co-Authored-By: Claude Sonnet 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 13:19:28 -04:00
Raymond Thurman 4cb013faa1 Stop Space and Enter from resetting the X11 input context (#1179)
Space and Enter count as a click on the focused canvas. request_focus
always interrupts IME, and on X11 winit then destroys the XIM context.
fcitx swallows every later key. Ignore that keyboard click. Pointer
clicks, accessibility clicks, and drags still focus the canvas.
2026-10-14 13:06:38 -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
37193a14ba Layout: a paragraph ending with a page break leaves its space after on the old page (#1168)
A paragraph whose last line holds the page break keeps its mark on the old
page, but its space after was still added after the break, so the next page
started that much too low (and the next paragraph lost its "no space before
after a page-break mark" treatment). Word adds none of it there, in
compatibility modes 12, 14 and 15 alike.

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:05:38 -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
Brandon ThomasandClaude Opus 5.5 abe572e7fb CI: translate Croatian in every interface catalog (#1719)
#1114 added Croatian to the proofing-language menu, and #1143 put the
interface-translation audit back in the test suite; together they left
"Croatian" untranslated, failing untranslated_interface_text and the
catalog-coverage tests.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:01:49 -04:00
156029fe63 Layout: a list label moves with its text when the paragraph is aligned (#1159) (#1163)
* Layout: a list label moves with its text when the paragraph is aligned (#1159)

Alignment shifted the text of a line but not the list label of the first line,
so a centred or end-aligned bullet or number stayed at the margin while its text
moved away. In Word the label stays attached to the start of the text. The
first line's label now moves by the same shift (not after a tab stop, where the
text before the tab stays too). Left-to-right and right-to-left paragraphs are
covered by one test.

* Docs: update the progress docs for this change

* Docs: put the new sentence in the WordCraft / evidence column

Co-Authored-By: Claude Sonnet 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 12:51:19 -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
Marco Arnulfo 3f5d8feebe Print: the preview fills the pane, with a zoom bar and scrolling both ways (#1157) 2026-10-14 12:50:40 -04:00
Marco Arnulfo 48f1b6b842 Dark theme: the ruler text area is lighter than its margins (#1154) 2026-10-14 12:50:29 -04:00
b3132a0b11 Review › Language reaches every proofing language (#860) (#1151)
* #860: Language menu opens Set Proofing Language… and Language Preferences…

Review › Language (and the status bar's language menu) offered only four
languages since #346 replaced the button that opened the Language dialog,
so French and the other 25 languages, and Set As Default, were out of
reach. The menu now ends with Set Proofing Language… (the Language dialog)
and Language Preferences… (Options › Language); a language outside the
short list shows by name and checked, and the status bar names it.

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

* #860: other regions of a language show by name, and no language shows twice

A tag outside the Language dialog's list (fr-CA, en-AU, de-AT) shows as the
language and region (French (Canada)) instead of the raw tag; pt-AO is
Portuguese (Angola), not Brazil. A current language named like a short-list
entry checks that entry rather than showing again above the list.

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:50:16 -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
646349d521 Interface-translation audit green and back in CI (#1128) (#1143)
({name}   {height} × {width} in, {kind}, {columns} × {rows}, …) and use the
  region's decimal separator; the fixed-format paper rows dead since #838 are
  removed from six catalogs.
- 20 strings added to all 18 catalogs: status-bar messages (table split and
  merge, ink replay, citations, SmartArt, Researcher, Design defaults), the
  Reading Mode toolbar, Go To, Search's Suggested, envelope paper names.
- Audit: citation style names stay exempt as proper names; `bad parameters:`
  errors (JSON parameter names for scripts and MCP) stay English like the
  other engine errors; placeholder-only patterns need no entry.
- Flakiness: the Read Aloud player drew the speech worker's error straight
  from shared state, so with no TTS engine an error landing between poll and
  draw was shown without being known to the app (the rare failure under
  load); it now draws the error poll saw. The audit holds Read Aloud (no
  speech thread, no sound) and gathers process-wide names (fonts, custom
  dictionary) every frame, since tests running alongside change them.
  #[ignore] removed.

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:49:18 -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
Brandon ThomasandClaude Opus 5.5 fc41918814 Release 0.7.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.7.0
2026-10-14 09:07:25 -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
ff15dc451d Equation pictures share a few atlas textures instead of hundreds (#1002) (#1149)
* #1002: equation pictures share one texture, keeping theme colours on Windows

The Equation tab's gallery tiles, structure buttons and symbols were a
texture each: hundreds of small textures, created as galleries opened and
all freed at once when the preview cache filled. On Windows (DirectX 12)
tiles then showed other textures' memory (inverted pictures, red and teal
fills, damaged interface letters). They are now packed into one atlas
texture written in place, the way egui keeps its glyphs; it starts over
when full (at most once a frame).

Tests: every equation picture composited in both themes is only the
theme's greys; all pictures share one texture, none freed while browsing,
none overlapping; the atlas packs rows and starts over when full.

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

* #1002: the theme test looks for paper anywhere, not in the corner (Windows fonts reach it)

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

* #1002: equation atlas uses up to four pages and never wipes a page drawn from this frame

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:05:06 -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
e8482fa964 A chosen interface size is absolute; Auto keeps the display scale (#822) (#1142)
* #822: a chosen interface size is absolute; Auto keeps the display scale

On a display the system reports at 200 % (a bare window manager on X11,
where winit guesses the scale from the monitor's DPI), "100%" drew at
200 %. As in PhotoCraft and PdfCraft (pdfcraft#1005), a chosen size now
divides the display scale back out, so 100% is one screen pixel per
point. The new default, Auto, keeps the display scale and its menu entry
shows the size it draws at. Sizes run 75-300 %; ui.json moves to
uiScale and migrates the old interfaceSize (100 -> Auto).

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

* #822: an old interface size becomes the size it drew at; a valid uiScale wins

A pre-#822 interfaceSize was on top of the display scale, so it is kept
until the first frame knows that scale and becomes old x native
(125% on a 2x display stays at 250%). Note that on the web a chosen
size cancels browser zoom.

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

* #822: rustfmt

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:27 -04:00
85a3a53a8e Keep a paragraph's space after clear of the footnote separator (#1116) (#1140)
* #1116: keep a paragraph's space after clear of the footnote separator

On a page with footnotes (already there or brought by the line), a
paragraph's last line only fits if its space after fits above the notes
area too; otherwise it goes to the next page with its note (and, with
widow control, one more line). Contextual spacing and automatic spacing
between list items drop the requirement; pages without notes are
unchanged (space after may still run past the bottom margin).

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

* #1116: space after vs footnote separator uses the spacing code's tests, only on pages with notes

Contextual spacing drops a paragraph's space after only when that paragraph
asks for it, and the next paragraph counts as a list item only when it gets
a label (Numbering::shows_label), as in the spacing between paragraphs.
The kept space after is worked out only for a last line on a page with notes.

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:30 -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
5b92d61a0c Clicks on ribbon and status-bar chrome keep typing in the document (#1093) (#1138)
egui drops the page's keyboard focus on any click elsewhere. Command
buttons handed it back (#686), but gallery tiles, tab names, empty ribbon
space, View check boxes and the status bar's items did not, so the caret
vanished and typing went nowhere. After a click on background chrome that
leaves nothing focused (no text field, dialog, popup, Backstage or comment
balloon), the canvas takes focus back, as in Word. Tabs, style tiles and
status-bar items now report their name and state to screen readers.

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:02:52 -04:00
9fd3490e4c Caret contrasts with the page colour, shading or highlight under it (#299) (#1137)
* #299: caret contrasts with the paper, page colour, shading or highlight under it

The caret was black (white only in Switch Modes' dark page), so it vanished
on a dark page colour, dark shading or a dark highlight. It now takes black
or white by WCAG contrast with the background at the caret
(wordcraft_render::background_at), cached per layout/view/position.

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

* #299: shapes colour the caret's background only inside their outline; look up only when drawn

background_at hit-tests a filled shape's real outline (shape_path), so an ellipse's corner
shows what lies beneath. The caret's colour is worked out only when the caret (or IME text)
is drawn, and the unused Tokens::caret is gone.

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

* #299: rustfmt

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:02:34 -04:00
bf9b84f10b Start screen: no blank document behind File › Home (#1129) (#1134)
* #1129: no document behind the Start screen; Back shows an empty window

Launched with nothing to open, the Start screen no longer has a blank
document behind it. Esc or Back shows an empty window (no page, ribbon
greyed out but for Help, Info/Save/Save As/Print/Export off in File,
title "WordCraft") until Blank document, a template or a file is
opened; closing asks nothing. Commands that need a document say so.

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

* #1129: explicit no-document allow-list; control inspect/render need a document; fonts can be added

works_without_document lists the ui.* commands that make sense with no document instead of
letting every ui.* command through; font.add/list/remove work so a font file dropped on the
empty window is added. The control channel's document.inspect and ui.render answer that no
document is open instead of reading the hidden placeholder.

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:02:15 -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
Marco ArnulfoandBrandon Thomas 2ed77831f9 Canvas: middle-click scrolls the document, like Word (#1121)
A middle click on the canvas starts scrolling toward the pointer, faster the farther it is from the click, with a dead zone around it. Another click, Escape, any key, the wheel or losing focus stops it; held and dragged, the release stops it. Same gesture and speed curve as pdfcraft's autoscroll; on for Windows and Linux/BSD, off on macOS and the web.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 02:01:20 -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
mbaksaandBrandon Thomas 408ae56f49 Add Croatian spelling and grammar support (#1114)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 02:00:58 -04:00
cd5b1a5077 Regression test: DrawingML shapes survive saving a .docx (#886) (#1130)
Reads rectangles, ellipses, triangles, arrows, stars, a connector with an
arrowhead and a text box, saves and reads back: kind, size, fill, outline
and width, position, wrapping, arrowhead and text-box text all match, and
the saved XML holds a wps:wsp per shape with its preset. The data loss in
#886 was fixed on the reading side by #671; this guards it.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:55:41 -04:00