Commit Graph
100 Commits
Author SHA1 Message Date
ba9bbb48dd Open Word documents with WordCraft by default (file associations + Options button) (#304)
* Open Word documents with WordCraft from the system (#295)

Register WordCraft for Word, RTF and ODT files on Windows and Linux without
taking over anyone's defaults, and add File > Options > General > "Make
WordCraft the default for Word documents..." where the system allows it.

- Windows MSI: a ProgId per type (.docx .docm .dotx .dotm .doc .dot .rtf
  .odt, plus .md and .txt as Open With candidates only) with the app icon
  and `"wordcraft.exe" "%1"`, OpenWithProgids, Applications\wordcraft.exe
  SupportedTypes, and Capabilities + RegisteredApplications so WordCraft
  shows in Settings > Default apps. New component with its own GUID.
- Linux: the desktop entry declares the macro-enabled, template and legacy
  Word types and text/rtf; AppStream lists the media types. deb, rpm,
  AppImage and both Flatpak manifests use this same file.
- macOS: Info.plist also lists legacy .doc.
- The Options button runs `ui.makeDefaultApp` through a new
  Services::make_default_app hook: Windows opens
  ms-settings:defaultapps on WordCraft's page; Linux and BSD run
  `xdg-mime default ai.storyteller.wordcraft.desktop <types>` (refused
  inside Flatpak or without an installed desktop entry); macOS shows the
  Finder Get Info hint; the web shows nothing. The status bar reports the
  outcome. Strings translated in every catalog.

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

* Review fixes for #304: keep Make default out of programmatic dispatch

ui.makeDefaultApp is refused when run programmatically (control channel, MCP, macros,
scripts); only a click on File > Options > General > Make WordCraft the default… reaches
the host's hook, so no agent can change the user's system default apps or open system
settings. Test: programmatic_calls_cannot_change_the_default_apps.

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

* Default-app strings in the newer catalogs; the button's test looks for a General-pane label

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 04:30:57 -04:00
6801d89065 .doc import: inline pictures, floating pictures and basic shapes (#551) (#558)
* .doc import: inline pictures, floating pictures and basic shapes (#551)

Word 97-2003 files kept their text but dropped every picture and drawing.
Pictures and drawings are now read from the OfficeArt records ([MS-DOC],
[MS-ODRAW]):

- Inline pictures: the PICF's OfficeArtInlineSpContainer (MM_SHAPE /
  MM_SHAPEFILE) and its FBSE/BLIP, with crop and alt text from the shape
  properties; the older raw-image path stays as a fallback.
- Floating pictures and shapes: PlcSpaMom/PlcSpaHdr anchors joined with
  the shapes of the OfficeArtContent drawings (fcDggInfo), placed by the
  SPA bounds, bx/by or posrelh/posrelv/posh/posv, wrap type and
  fBelowText; rotation and flips; pictures from the BLIP store.
- Shapes: rectangle, rounded rectangle, ellipse, diamond, triangles, star,
  block arrows, heart, lines/connectors, with fill/line colour and width;
  groups flattened into one group space; text boxes linked to their
  PlcftxbxTxt story parts by spid.
- BLIPs: PNG, JPEG, TIFF as-is, DIB converted to PNG, EMF/WMF inflated
  (the metafile renderer draws them), PICT kept as a sized placeholder.
- Hostile input: record lengths clamped to their parent, nesting depth
  and record counts capped, decompression size-limited, picture bytes
  capped per document as before.

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

* Review fixes for #558: miniz_oxide as a workspace dependency

Restores the change @echelon pushed in 04b59287, which a rebase dropped: miniz_oxide is declared once in the root [workspace.dependencies] and docbin uses it with workspace = true.

Co-Authored-By: Claude Sonnet 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-12 03:42:18 -04:00
d69c94efcf Linux: open/save works without a portal file dialog (#576) (#583)
* Linux: built-in file browser when the portal file dialog is unavailable (#576)

rfd 0.17 on Linux/BSD asks the xdg-desktop-portal FileChooser and falls back to
zenity; with neither (labwc with only xdg-desktop-portal-wlr, no zenity) it
returns None, the same as Cancel, so Open/Save As/Insert Picture silently did
nothing. The dialog thread now checks first (zenity on PATH, or the portal's
FileChooser interface via dbus-send) and answers with an error when neither is
there; the app then finishes the job in its own egui file browser (folder
field, Up/Home, folders and files of the chosen type, file name, type filter,
replace confirmation) with a hint on how to get the system dialog.
File > Open > Browse in WordCraft... (Linux/BSD) and `ui.fileBrowser` reach
it at any time, even while a system dialog never showed up;
WORDCRAFT_FILE_DIALOG=builtin forces it. Native only (not wasm32).

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

* Turkish and Russian strings for the file browser (#576)

Co-Authored-By: Claude Sonnet 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-12 02:19:04 -04:00
8bd3427012 View: Hide White Space between pages in Print Layout (#645) (#684)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-12 00:34:30 -04:00
62b58d1f87 Design tab: style set gallery gets its own box so it can't overlap Colors and Fonts (#638) (#673)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:42:20 -04:00
381c475edc M-print: File > Print preview, page range, odd/even, copies and collate (#641) (#674)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:30:58 -04:00
39e541fc8b Table: a vertical merge lays out and hit-tests as one cell (#640) (#669)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 23:11:40 -04:00
b75bd3c56a Fonts: Palatino Linotype and Book Antiqua prefer Palatino look-alikes (#670) (#677)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 22:42:05 -04:00
baeb8d9c6c Open and save Word XML Document (Flat OPC); open Word 2003 XML (#548) (#560)
- docx: Flat OPC (`pkg:package`) read by unpacking the parts into an
  in-memory package for the DOCX reader; written from the DOCX writer's
  parts (XML inline, binary parts in base64). Part count, part size,
  total size and base64 length are capped.
- docx: Word 2003 XML (`w:wordDocument`) imported by rewriting it as the
  corresponding package (hyphenated names, w:hlink, w:listPr, w:lists,
  inline headers/footers, notes, aml annotations, w:binData pictures,
  o:DocumentProperties) and reading that with the DOCX reader.
- `.xml` told apart by root element; other XML is refused with
  "not a Word XML document". File > Export gets "Word XML Document (*.xml)"
  (all 9 catalogs); .xml saves keep the document like .docx.
- Tests: Flat OPC round trip equals DOCX round trip, hand-written Flat OPC
  package, Word 2003 import, hostile inputs, engine io.

Fixes #548

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:59:00 -04:00
a608332646 Chinese documents: character-unit indents, .doc indents and line spacing, floating tables, blank pages (#534) (#550)
- Indents in characters (w:firstLineChars, w:hangingChars, w:leftChars,
  w:rightChars; sprmPDxc* in .doc) are kept in the model, read and written,
  and laid out in the paragraph mark's font size, winning over the points
  as in Word. Indent commands cancel an inherited character indent.
- .doc: sprmPDxaLeft1 has a two-byte operand (was read as four, so every
  first-line indent was lost); sprmPDxaLeft1_80 is read; LSPD line spacing
  is decoded as signed dyaLine + fMultLinespace (exact 20pt read as 799pt,
  a page per empty paragraph).
- A floating table placed on the page or margin that would cover the text
  already on the page and doesn't fit below it moves to the next page.
- A line holding only a page break stays at the bottom of a full page
  instead of leaving a blank page.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:49:25 -04:00
dab5092e83 Font dialog: separate Asian and Latin text fonts (#505) (#536)
* Font list: installed Chinese fonts are listed, by their Chinese names in a Chinese interface (#505)

The font database read only the English family name of each font, so a
Chinese interface listed SimSun, Microsoft YaHei and WenQuanYi Zen Hei
by their English names and never as 宋体, 微软雅黑 or 文泉驿正黑, and a
document naming a font by its Chinese name fell back to another font.

- fonts: keep each face's localized family names (name IDs 16 and 1,
  every face of a collection, read during the scan without loading the
  file); FontDb::families_in(lang) lists them in place of the English
  names for a matching interface language (zh-hans, zh-hant, ja, ko);
  face, has_family and styles accept either name.
- ui: the ribbon and mini toolbar font boxes show the interface
  language's names and store the English name.
- East Asian font: choosing a Chinese, Japanese or Korean font also sets
  it as the East Asian font (w:eastAsia), read and written in DOCX;
  layout draws CJK text in a run's installed East Asian font.

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

* Font dialog: separate Asian and Latin text fonts (#505)

Word's Font dialog sets the font of Chinese, Japanese and Korean text
apart from the font of Latin text; WordCraft's had one font box.

- Font dialog: an "Asian text font" box (the installed Chinese, Japanese
  and Korean fonts, by their interface-language names, type to find)
  above the "Latin text font" box, shown for a Chinese or Japanese
  interface, East Asian text in the selection or a selection with an
  Asian font. OK sets only the fonts changed, in one undo step.
- format.font takes "eastAsia" (w:eastAsia) besides "name"; either or
  both; format.state reports fontEastAsia.
- fonts: the scan reads each font's OS/2 code pages, so
  FontDb::east_asian_families lists CJK fonts without loading them.
- Tests: dialog sets each font on its own; layout sets CJK text in the
  run's Asian font and Latin text in its Latin font (skipped without an
  installed CJK font); command and undo; the scan finds a CJK font.

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-11 21:42:56 -04:00
60cd1bf568 Numbering: Chinese, Japanese and Korean number formats (一、二、三 …) (#534) (#561)
* Numbering: Chinese, Japanese and Korean number formats (一、二、三 …) (#534)

A list with w:numFmt="chineseCounting" and lvlText "%1、" showed "2、", and the
digit was drawn with glyph ids of the CJK font that supplied the comma, so it
came out as an unrelated letter: the list label was laid out in font runs but
drawn with the style of its last run only.

- NumFormat knows every ST_NumberFormat value (ECMA-376 §17.18.59), so a file
  saves back the format it was opened with; unknown values are decimal.
- New doc::cjk_numbers: Chinese/Taiwanese counting (with 十 below 100, digit by
  digit above) and counting-thousand (百/千/万/亿, 零 for gaps), simplified and
  traditional legal numerals, Japanese counting/legal/digital ten-thousand,
  Korean counting/digital/legal, ideograph digits, heavenly stems, earthly
  branches and the sixty-year cycle, aiueo/iroha (half and full width),
  ganada/chosung, enclosed numbers (①, ⒈, ⑴, ㊀) and full-width digits. Values
  past a sequence's end fall back to decimal.
- Legal numbering (w:isLgl) shows every level, the current one too, in decimal.
- List labels keep their font runs and draw each with its own face.
- A list level without w:ind no longer gets a 36pt indent on read.
- .doc number format codes 0x00-0x27 map to the formats (0x16 decimalZero).
- PAGE/NUMPAGES fields take \* ChineseNum1/2/3.

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

* rustfmt after merging main

* Layout test: end the list range inside the third paragraph (#477 skips a paragraph the range ends at the start of)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:35:41 -04:00
b2c51c6500 Fonts: index system font coverage once, so fallback no longer re-reads every font per character (#605) (#610)
System fallback read and parsed every cataloged font file (<40 MB) again for each distinct
character no loaded font covers; only that character's miss was remembered. Text in a script
no installed font covers (Tangut, CJK Ext G, ...) froze layout for seconds to minutes.

Each file is now read once, when a fallback search first reaches it, and its coverage is kept
as sorted code point ranges; later searches answer from the ranges and only read a file that
maps the character (to load it). Rescans keep the ranges of files still installed. Preferred
fallback families are tried once each, the miss set is capped, and indexing a damaged file is
bounded (faces, mappings, ranges). Search order and the fonts chosen are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:24:48 -04:00
f422d6ccbb Mini toolbar: every button fits, flush font box, proper dropdown arrows, dark-mode font list (#535) (#540)
- Two rows like Word's mini toolbar (font, size, grow, shrink; B I U S, highlight,
  font colour, four alignments), sized from the ribbon widgets' real metrics so no
  control is clipped any more (the one-row bar cut off everything after Align Left).
- Combo lists drop from the box's left edge instead of from the arrow.
- The dropdown triangle fills its box, so it no longer shrinks to a dot beside
  combos and split buttons.
- Font list entries are an ink mask tinted with the theme's text colour, so they no
  longer sit on white strips in dark mode.

Tests: the bar drawn over a real selection contains every control and is exactly as
wide as them; font entries take the theme text colour (dark and light) and paint no
background; the ink mask keeps coverage and drops the paper.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:19:26 -04:00
7484a6bd11 Find and Replace: special characters (^p, ^t, …) and wildcards (#543) (#553)
- Find what understands Word's ^ codes (^p ^t ^? ^# ^$ ^^ ^l ^m ^n ^b ^+ ^= ^s ^~ ^- ^w ^g ^% ^v,
  ^nnn / ^unnnn); Replace with adds ^& (found text) and ^c (clipboard). `special: false` reads
  the text literally.
- Use wildcards: ? * [abc] [a-z] [!x] {n} {n,} {n,m} @ < > ( ), \1-\9 in Replace with; always
  matches case, like Word. Patterns are translated to the linear-time regex engine (no
  catastrophic backtracking), length and counts capped, bad patterns are errors.
- ^p matches paragraph marks between sibling paragraphs; a ^p in the replacement splits the
  paragraph. Replace All stays one undo step.
- Match prefix / suffix, Ignore punctuation / white-space characters.
- Dialog: Use wildcards checkbox, Search Options with the new options and a Special menu that
  inserts codes into the focused field (a shorter list with wildcards); strings in all 9 catalogs.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:15:31 -04:00
850fa0576a AutoFormat: +---+ tables, arrow bullets, smileys inside words, border-line undo (#546) (#547)
- `+---+------+` Enter turns the paragraph into a one-row table across the text
  width, columns proportional to the hyphens, caret in the first cell (body,
  top level, at most 63 columns).
- `> ` `-> ` `=> ` start a bulleted list with an arrow bullet (➢ ➔ ⇨).
- Smileys `:)` `:-)` `:(` `:-(` `:|` are replaced also straight after a word.
- Undo right after a border line or table AutoFormat restores the typed
  characters and keeps the Enter (its own undo step).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:11:22 -04:00
46d5e99b3e Styles: Update to Match Selection takes the selection's formatting (#555) (#562)
styles.updateToMatch copied only the direct run/paragraph formatting at the
caret onto the style, so text whose look came from its own style (Normal,
Title, ...) changed nothing; it then wiped that paragraph's direct formatting
without applying the style, so the selection lost its look too.

Now the style takes the selection's effective formatting (paragraph style
chain, character style chain, direct formatting) as a diff against what the
style currently gives, keeping its outline level and numbering. The selected
paragraphs take the style and drop only the direct formatting it now carries.
The Home styles gallery tiles get the right-click menu (Update to Match
Selection, Modify...).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 21:06:14 -04:00
48b2468f19 Windows: compile DX12 shaders with FXC so startup needs no DXC DLLs (#603) (#609)
wgpu 30's default DX12 compiler (Auto) uses DXC and loads whichever
dxcompiler.dll the DLL search path finds first. WordCraft ships none, so
that copy belongs to another program; without a matching dxil.dll, DXC
warns that the DXIL is unsigned, wgpu treats the warning as a compile
error, its indirect-validation pipeline fails, the device is lost and
eframe exits before the first frame ("Parent device is lost").

The window's wgpu instance (and the DirectX 12 adapter probe) now pins
the DX12 shader compiler to FXC (d3dcompiler_47.dll, part of Windows)
unless WGPU_DX12_COMPILER names another. Same approach as
storytold/lightcraft#484. README troubleshooting note and ROADMAP log.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 20:56:45 -04:00
6a22d7257b Insert › SmartArt: create and edit list, process, cycle, hierarchy, pyramid, radial, matrix and venn graphics (#497) (#503)
- Model (doc::smart_art): layout, items (text + level), colour variant; capped at 100 items,
  255 characters, 8 levels.
- Layout algorithms of our own (docx::smart_art) place rounded rects, circles, bands, arrows and
  connectors; WordCraft draws the graphic from the same dsp:drawing it saves.
- DOCX: data (dgm:dataModel points and connections), our own layout/quick-style/colour
  definitions under the built-in identifiers, the drawing part, relationships and content types.
  A diagram reads back editable only when its data part is exactly what WordCraft writes.
- Commands: insert.smartArt, smartArt.items/addShape/promote/demote/layout/colors/reset/textPane,
  one undo step each; picture.size relays out a SmartArt graphic.
- UI: layout picker with previews drawn in code, SmartArt Design tab, Text Pane; strings in all
  9 catalogs.
- Diagram reader draws `line` presets top-left to bottom-right (was the other diagonal).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 20:09:32 -04:00
900c0f4412 Outline view: Outlining tab, levels, collapse/expand, Show Level (#493) (#499)
* Outline view: Outlining tab, levels, collapse/expand, Show Level (#493)

- View › Outline is its own view mode: the body on one long sheet, each paragraph indented
  by its outline level behind a symbol drawn in code (circled plus for a heading with content,
  circled minus for one without, a dot for body text); paragraph indents and alignment hidden.
- Collapsed headings hide their subtree and get a wavy line; Show Level 1-9 / All; Show First
  Line Only; Show Text Formatting off draws everything in the Normal style. All of it is view
  state (ViewState.outline), not saved; collapsed headings are found again by their text after
  edits, and a caret entering hidden text moves on to the next shown paragraph.
- Commands: outline.promoteToHeading1/promote/demote/demoteToBody/level/moveUp/moveDown,
  outline.expand/collapse/toggle/selectSubtree, outline.showLevel (+ showLevel1-9, showAll),
  outline.firstLineOnly, outline.showFormatting, outline.close, outline.state. In Outline view
  Move Up/Down and Promote/Demote take a collapsed heading's hidden content along.
- Keys: Alt+Shift+Plus/Minus expand/collapse, Alt+Shift+1-9 and Alt+Shift+A Show Level.
- Outlining tab (next to File while in Outline view): level box, promote/demote, move,
  expand/collapse, Show Level, the two check boxes, Close Outline View. Clicking a symbol
  selects the heading with its content; double-clicking expands or collapses it.
- Catalog: Outlining tab rows (421/453, 92.9%); translations in all nine catalogs; docs.

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

* Docs: drop the stale shortcut and dialog rows left by the rebase (#493)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 19:54:56 -04:00
50acc0c5f3 Vertical text for sections (w:textDirection) (#491) (#496)
- Model: SectionProps.text_direction (horizontal, tbRl, btLr); unknown
  values read as horizontal.
- DOCX: w:textDirection in w:sectPr read and written in CT_SectPr order
  (after w:titlePg, before w:bidi), also inside w:sectPrChange.
- Layout: a vertical section is laid out on its page turned on its side
  (margins, page borders and columns turned with it) and each page is
  turned onto the real page: tbRl lines run top to bottom stacking from
  the right, btLr bottom to top from the left. Pictures, shapes and text
  boxes stay upright; headers and footers stay horizontal; a section
  turning the other way starts a new page.
- Display: in top-to-bottom text ideographs, kana, Hangul and full-width
  forms stand upright (ideographic comma/full stop moved to the upper
  right); Latin lies on its side. Raster and PDF draw it unchanged.
- Hit testing: clicks outside turned lines find the nearest turned line.
- Command layout.textDirection (Layout › Page Setup › Text Direction):
  turns the selected cells or the text box the caret is in, else the
  section; ribbon menu and keytip.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 19:45:49 -04:00
e46133817a Templates and Add-ins: attached template, update styles, Organizer (#377) (#382)
* Templates and Add-ins: attached template, update styles, Organizer (#377)

- Model + DOCX: Settings keep the attached template (w:attachedTemplate's external
  relationship of the settings part) and w:linkStyles; both round-trip, written in
  CT_Settings order (after w:mirrorMargins, before w:trackRevisions).
- Engine: tools.templates (state), tools.attachTemplate, tools.detachTemplate,
  tools.linkStyles, tools.updateStyles (copy the template's styles and defaults, one
  undo step; path or base64 data), styles.organizer (list), styles.copyFrom (copies
  missing bases, linked halves and the lists a style numbers with), styles.rename.
  The template path comes from the document, so it is never read automatically (not on
  open either): only an explicit tools.updateStyles / attach with update reads it.
- UI: View > Templates > Templates and Add-ins dialog (path, Attach..., automatically
  update document styles, Organizer...) and a two-list Organizer (Copy, Delete,
  Rename...); strings in every catalog, Estonian included.

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

* Review fixes for #382: never follow a template path to another machine

A document's w:attachedTemplate can name \\host\share\T.dotx, //host/share or
file://host/share. Opening it (Update Styles, Organizer) would make Windows
authenticate to that host over SMB and leak the user's NTLM credentials.
local_path now returns None for network paths; regression test added.

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

* #377: refresh catalog count and parity checklist after rebase

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 19:35:06 -04:00
2e5ecb916b File › Options: Word's panes with working settings (#485) (#492)
File › Options is now a pane list (General, Display, Proofing, Save,
Language, Accessibility, Advanced, Agents), one function per pane in
crates/ui-egui/src/options.rs. Every setting shown is wired:

- General: theme, interface size (#475), mini toolbar on selection, dark page, community
  button, user name and initials (comments use the initials).
- Display: tab characters, spaces, paragraph marks and hidden text shown
  with Show/Hide ¶ off; rulers; print the page colour; update fields
  before printing.
- Proofing: ignore words in capitals / with numbers / web and file
  addresses (proof::Options, passed through layout), check spelling and
  mark grammar as you type, custom dictionary add/remove (kept in ui.json).
- Save: AutoSave. Language: interface language. Accessibility: Read Aloud
  speed and citations. Advanced: typing replaces selection, Insert key
  overtype, overtype (text.overtype), recent documents count, measurement
  units (dialogs, Layout ribbon indents and rulers).

file.options reads and sets the engine-side settings headlessly and only
opens the page when given no setting. ui.json loads leniently: a bad value
falls back to its default instead of dropping every setting.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 19:17:50 -04:00
88a45329b2 Right-click on a picture or shape: Wrap Text submenu (#513) (#521)
Right-clicking a picture, shape, text box or chart selects it (as a
click would) and its context menu offers Wrap Text: In Line with Text,
Square, Tight, Through, Top and Bottom, Behind Text, In Front of Text
(current one checked), then Move with Text / Fix Position on Page.

The latter two are a new engine command, arrange.moveWithText, which
re-anchors a floating object's vertical position to its paragraph or
the page without moving it. The menu items live in one shared function
(ribbon::wrap_text_items, WRAP_STYLES) for the ribbon to reuse.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 19:11:31 -04:00
b0431eadc2 Paragraph dialog: mirror indents, same-style spacing, suppress line numbers, don't hyphenate (#512) (#519)
- Model: `ParaProps::mirror_indents` (`w:mirrorIndents`, ECMA-376 §17.3.1.18), read and
  written in DOCX; resolved `mirror_indents` and `suppress_line_numbers` (so styles count).
- Layout: a paragraph with mirrored indents moves over by its indents' difference on even
  (left-hand) pages, so its left (inside) indent is on the right there.
- Paragraph dialog: Mirror indents (Left/Right become Inside/Outside), Don't add space between
  paragraphs of the same style, and Formatting exceptions (Suppress line numbers, Don't
  hyphenate) on Line and Page Breaks; OK sends only the options that changed, in the same
  `para.set` as the indents and spacing.
- Strings in all nine catalogs; Reveal Formatting names mirrorIndents.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 18:50:28 -04:00
ee04e2b7f8 Font list: installed Chinese fonts are listed, by their Chinese names in a Chinese interface (#505) (#506)
The font database read only the English family name of each font, so a
Chinese interface listed SimSun, Microsoft YaHei and WenQuanYi Zen Hei
by their English names and never as 宋体, 微软雅黑 or 文泉驿正黑, and a
document naming a font by its Chinese name fell back to another font.

- fonts: keep each face's localized family names (name IDs 16 and 1,
  every face of a collection, read during the scan without loading the
  file); FontDb::families_in(lang) lists them in place of the English
  names for a matching interface language (zh-hans, zh-hant, ja, ko);
  face, has_family and styles accept either name.
- ui: the ribbon and mini toolbar font boxes show the interface
  language's names and store the English name.
- East Asian font: choosing a Chinese, Japanese or Korean font also sets
  it as the East Asian font (w:eastAsia), read and written in DOCX;
  layout draws CJK text in a run's installed East Asian font.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 18:27:53 -04:00
91b960ed74 Accessibility: the document canvas exposes its text to screen readers (#488) (#489)
The canvas is now the AccessKit document node: body paragraphs become
paragraph, heading (with level) or list item (with level, grouped into
lists, list numbers as list markers) nodes holding one text run per laid-out
line with character lengths, positions, widths and word starts; tables are
table/row/cell nodes, pictures and charts image nodes named by alt text,
hyperlinks link nodes around their runs. The caret and selection are an
AccessKit text selection on the document node.

Only pages on screen and one either side are described, capped at 6000
nodes; nothing is built unless AccessKit is active, and the tree is rebuilt
only when the layout, selection, described pages or their screen place change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 18:11:05 -04:00
35e22e2cef Fixes: footnotes in table cells, NUMPAGES, bookmark link pages, bookmark around final table, INCLUDEPICTURE results (#450, #451, #455, #464, #465) (#483)
- layout: footnotes referenced in table-cell lines join the page's note area (#450)
- layout: NUMPAGES/SECTIONPAGES in the body use the real totals; the body is laid out
  again with the totals found (at most three extra passes); SECTION in the body counts
  sections (#451)
- pdf: internal link destinations use the page and line of the bookmark's start (#455)
- docx: markers left after a final table go to its last cell's last paragraph (#464)
- docx: a field whose cached result holds a picture is kept as a range field around it,
  so the picture survives save; the linked path is never read (#465)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 18:05:27 -04:00
968c66a13e Fixes: tracked paste marks, plain paste blank lines, ^p in Replace, Delete/Split columns and cells, Font dialog keeps untouched formatting (#417, #422, #423, #431, #432, #466) (#479)
- Paste under Track Changes marks the paragraph marks it adds as inserted, so Reject All
  joins the paragraphs again (#417).
- Keep Text Only / Paste Special text paste treat paragraph separators as text, not Enter:
  an empty list item between them no longer ends the list (#422).
- Untracked Replace All splits paragraphs at CR / U+2029 in the replacement like a single
  Replace does (#423).
- Delete Columns deletes every grid column the selected cells cover (#431).
- Split Cells refines the grid inside the split cell only; other rows' cells widen over the
  new grid columns, keeping the table width (#432).
- Font dialog sends only the properties the user changed and shows small caps, all caps,
  hidden and spacing from the selection (format.state reports them) (#466).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 17:36:30 -04:00
2e9d4a22ce Chinese interface: keep a system CJK fallback after the bundled face; find PingFang on macOS 12 (#486) (#495)
The screenshots in #486 show exactly the characters BIZ UDPGothic (the
Japanese interface face) lacks: 选项规语简户另为导 … in Simplified and
啟內另 … in Traditional, with "?" in the Inter-based medium/semibold
families (no U+25FB replacement glyph) and boxes in the proportional one.
That is the font chain of v0.4.0 and earlier, which embedded no Chinese
face and had no installed-font fallback; v0.5.0 (#248) embeds Noto Sans
CJK SC. This hardens the chain so it can't silently regress:

- The installed CJK font is chosen by coverage: it is added after the
  embedded faces whenever they leave a CJK character of the CJK catalogs
  or the language names without a glyph (not only when no "Hans" face
  exists), and the face picked is the one mapping the most of those
  characters.
- macOS: look inside com_apple_MobileAsset_Font<N>/<hash>.asset/AssetData
  (bounded, one level) where macOS 10.15+ keeps PingFang.ttc, also under
  /System/Library/Assets; PingFangUI.ttc as a last resort.
- Test: every CJK character of the zh-hans, zh-hant and ja catalogs and
  the language names has a glyph in the embedded chain in all three
  interface weights (skips without craft-fonts; fails with a Japanese-only
  craft-fonts, listing exactly the characters from the screenshots).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 17:26:23 -04:00
73bf2a47b5 Layout fixes: mirrored margins, tall headers/footers push the body, endnote continuation, footnote caret on continuation pages (#452, #453, #454, #456, #457) (#490)
- Mirrored margins: even pages put the inside margin and gutter on the right
  (text, notes, header and footer shift; page borders stay).
- A footer taller than the bottom margin moves the body bottom up.
- Headers and footers inherited via link-to-previous reserve their room in
  later sections (shared `linked` helper with the drawing pass).
- Long endnotes split at line boundaries and continue in the next column or
  page (bounded by MAX_NOTE_PAGES).
- Caret/selection lookup in a note collects the paragraph's pieces on the
  neighbouring continuation pages, not only the hinted page.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 17:23:30 -04:00
50505d9e62 Editing fixes: Center range, undo grouping, Change Case runs and offsets, Bold undo at caret (#418, #419, #420, #421, #424) (#477)
- Paragraph formatting skips the paragraph a non-empty selection ends at the start of
  (Select Paragraph's end), via Document::para_paths_in (#418).
- A non-mutating command that moves the selection closes the typing group (#419).
- Change Case converts char by char and replaces only changed stretches inside one run,
  keeping each run's formatting (#421) and mapping the selection to the new offsets (#420).
- Undo/redo steps carry the pending caret formatting (#424).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 17:13:50 -04:00
4b5ed293d7 Compare, Restrict Editing and AutoCorrect Options dialogs (#412) (#476)
* Compare, Restrict Editing and AutoCorrect Options dialogs (#412)

- Compare / Combine Documents dialog: original (this document or a file) and
  revised document, label changes with, More >> comparison settings, word or
  character level, show changes in. review.compare honours case changes,
  white space, formatting (tracked formatting changes), tables (compared and
  kept, no longer dropped) and character level; the LCS no longer drops text
  past its size cap. Unsupported settings are accepted and reported.
- Restrict Editing pane: formatting restrictions (locked styles,
  w:formatting), editing restrictions, exceptions for everyone
  (w:permStart/permEnd), Start Enforcing Protection with an optional password
  (SHA-512, salt, spin count 100000; ECMA-376 §17.15.1.29) and Stop
  Protection. Enforced in Session::run: outside the exceptions nothing may
  change; undo can't lift protection; passwords never land in Repeat/macros.
  DOCX round trip of protection, hash attributes, locked styles and ranges.
- AutoCorrect Options dialog: replacement list (add/replace/delete, built-in
  entries too), capitalization options with exceptions, AutoFormat As You
  Type switches, Math AutoCorrect list. Saved per user in ui.json; damaged
  values read as defaults. Names of days are capitalised.

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

* Restrict Editing: verify Word's legacy protection hash; refuse unsupported forms (#412)

- Stop Protection checks Word's legacy w:documentProtection hash
  (w:cryptAlgorithmSid/w:hash/w:salt/w:cryptSpinCount) like Word: the password
  becomes the legacy 32-bit key (ECMA-376 Part 4 §14.8.1 initial code array and
  encryption matrix), its reversed bytes as an upper-case hex string are hashed
  with the salt prepended and the iteration number appended. A wrong password
  is rejected; the form no longer unlocks with any password.
- Unsupported or damaged hashes (MD2/MD4/MD5 and other SIDs, w:algIdExt,
  unknown algorithmName, spin counts above 10,000,000, bad base64) refuse with
  "This password type isn't supported" and the protection stays on. A
  protection without a hash still stops without a password.
- Known-answer tests: the specification's "Example" key 0x64CEED7E and an
  independently computed SHA-1 hash; pane message in all 9 catalogs.

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-11 17:10:02 -04:00
2ffba93b51 WordArt text effects and transforms, and shape connectors (#416) (#481)
* WordArt text effects and transforms, and shape connectors (#416)

- Model: WordArt text effects on runs (fill solid/gradient/none, outline,
  shadow, glow, reflection), text warps on shapes (all DrawingML presets
  kept, adjust values capped), connector kinds (straight, elbow, curved)
  with glued ends, arrowheads and drawing ids (ShapeExtra).
- DOCX: w14:textFill/textOutline/shadow/glow/reflection in w:rPr,
  a:prstTxWarp in wps:bodyPr, wps:cNvCnPr a:stCxn/a:endCxn,
  a:headEnd/a:tailEnd; glued-to shapes keep their docPr ids.
- Layout/render/PDF: text with effects drawn from glyph outlines as paint
  layers (gradients, shadow/glow bands, faded reflection); warped text
  boxes bent along arch up/down, circle, wave, slant, inflate/deflate,
  chevron and triangle; connectors and arrowheads drawn as paths.
- Engine: insert.wordArt makes a floating WordArt text box with our own
  styles; format.textEffects, wordArt.style/textFill/textOutline/transform,
  shape.connect; glued connectors re-route after object edits.
- UI: WordArt style gallery (Insert, Home › Text Effects, Shape Format ›
  WordArt Styles), Text Fill/Outline/Effects menus, Transform previews
  drawn from the warp geometry, connectors in Insert › Shapes, drag a
  connector end onto a shape to glue it.

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

* Fix tests for ShapeExtra, regenerate parity checklist, fmt (#416)

* Regenerate parity checklist after rebase (#416)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 16:51:01 -04:00
053bb0fd85 Content controls: keep, show and edit w:sdt (#381) (#392)
* Content controls: keep, show and edit w:sdt (#381)

Content controls are now part of the model instead of being unwrapped on open:
ControlStart/ControlEnd markers around their content (inline or around whole
paragraphs), with table, row and cell wraps for controls around tables,
repeating-section rows and cells. All sdtPr properties round-trip (title, tag,
id, lock, placeholder, temporary, rich/plain text, w14 check box, combo box,
drop-down list, date, picture, building block gallery, w15 repeating
sections); unknown children are kept verbatim. The writer plans per block list
which controls are written inline and which around blocks so the XML always
nests, and drops markers that lost their partner.

Editing: deletions keep a marker whose partner is outside the range, typing
replaces a shown placeholder, emptying a control brings it back, sdtLocked and
contentLocked refuse with a status message. Developer tab with insert
commands, Design Mode, Properties dialog; frames and title tabs on the page,
check boxes toggle on click, list and calendar pickers.

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

* #381: refresh catalog count and parity checklist after rebase

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 16:36:53 -04:00
Valentin KleinandClaude Opus 5.5 fd014ea9a1 Insert › Chart: create and edit column, bar, line, pie, area, scatter and doughnut charts (#409) (#415)
* Insert › Chart: create and edit column, bar, line, pie, area, scatter and doughnut charts (#409)

- Chart model (wordcraft_doc::chart::ChartSpec): type (clustered/stacked column and bar, line,
  area, pie, doughnut, scatter), categories, series, title, legend, data labels; sanitized
  (255 series, 4000 points, 100k cells, 255-char text, non-finite values dropped).
- DOCX: charts with a model are written as word/charts/chartN.xml (c:chartSpace, ECMA-376
  §21.2, data as c:strLit/c:numLit, no c:externalData) with relationship and content type.
  On read, a chart part becomes editable only when writing its model again gives the same
  markup; every other chart stays read-only and kept as before (#319).
- Drawing reuses read/chart.rs (the model is drawn from its generated part); data labels
  (c:dLbls showVal) are now drawn for bar, line, area, scatter, pie and doughnut charts.
- Commands: insert.chart, chart.type, chart.editData, chart.title, chart.legend,
  chart.dataLabels (one undo step each); picture.size redraws an editable chart at its size.
- UI: Insert › Chart gallery with previews drawn in code, Chart Design contextual tab, Edit Data
  grid dialog; strings in all 9 catalogs; catalog 408/439.

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

* #409: refresh catalog count and parity checklist after rebase

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:26:44 -04:00
1a0db9ec65 Dialogs: Line Numbers, Hyphenation, Language, Envelopes and Labels (#407) (#414)
* Dialogs: Line Numbers, Hyphenation, Language, Envelopes and Labels (#407)

- Line Numbers (Line Numbering Options…): add numbering, start at, from
  text (auto or a distance), count by, restart each page/section or
  continuous; `layout.lineNumbers` takes start/countBy/distance.
- Hyphenation Options: automatic, words in CAPS, hyphenation zone,
  consecutive-hyphen limit; kept in DOCX (w:hyphenationZone,
  w:doNotHyphenateCaps, w:consecutiveHyphenLimit) and honoured by
  layout. Manual…: `layout.manualHyphenation` finds words at line starts
  that would fit hyphenated on the line above; Yes inserts an optional
  hyphen, No skips.
- Language: localized language list, do not check spelling or grammar,
  detect language automatically (per-user preference), Set As Default
  (document default language).
- Envelopes: delivery/return address, common sizes (#10, #9, Monarch,
  DL, C4, C5, C6, B5); Add to Document inserts an envelope section
  before the letter. Labels: common sheets (Letter and A4), full page or
  a single label at row/column, one per recipient; New Document builds
  the sheet with gap columns.
- Ribbon: Line Numbering Options…, Hyphenation Options…, Manual…; the
  Language, Envelopes and Labels buttons open their dialogs.

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

* Remove duplicate Label: keys in ru and tr (#407)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 16:15:53 -04:00
d5bcb9b490 Accessibility: alt text for every object, decorative flag, more checker rules (#386) (#396)
- Model: Float carries AltText (title, decorative; the description of shapes and groups);
  InlineObject::alt_text/set_alt_text/is_decorative; TableProps.description. Capped input.
- DOCX: wp:docPr / pic:cNvPr / wps:cNvPr descr, title and the [MS-ODRAWXML] adec:decorative
  extension read and written for pictures, shapes, text boxes, groups and members, charts;
  OLE objects get their alt text in the kept XML; w:tblCaption + w:tblDescription.
- Commands: object.altText {text?, title?, decorative?} (picture.altText kept as alias),
  table.altText {title?, text?}, view.accessibilityPane, view.altTextPane.
- Checker (cmd/accessibility.rs): alt text on every object and table (decorative passes),
  heading order, header rows, blank table rows/columns, merged cells, WCAG contrast,
  floating objects, blank paragraphs; issues carry pos/select and fix actions.
- UI: Accessibility pane (grouped, jump-to, fix buttons), Alt Text pane, Picture/Shape
  Format > Accessibility > Alt Text, right-click > View Alt Text...; strings in all catalogs.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 16:01:20 -04:00
56f9456f03 References dialogs: Caption, Index, Table of Contents, Create Source (#402) (#410)
* References dialogs: Caption, Index, Table of Contents, Create Source (#402)

- Caption: labels (built-in plus New/Delete Label, kept with the interface
  settings), above/below the item, leave the label out, Numbering (format,
  chapter number from a heading level with a separator) -> SEQ \* \s and
  STYLEREF n \s fields; Update Field renumbers SEQ with \s restarts, \r, \c, \h.
- Index: indented/run-in, columns, right-aligned page numbers with tab leader,
  letter headings -> INDEX \h \c \e \r; Mark Index Entry (subentry,
  cross-reference, page-range bookmark, bold/italic page numbers, Mark All)
  -> XE \t \r \b \i. The index is rebuilt honouring them; columns become a
  continuous section of their own. Index commands move to cmd/index.rs.
- Custom Table of Contents: page numbers, right align, tab leader,
  hyperlinks, levels, Options (heading styles, other styles with levels,
  outline levels, TC fields), Modify for TOC 1-9 -> TOC \o \h \z \u \t \f
  \l \n \p; updating honours them (hyperlinks to _Toc bookmarks).
- Source Manager and Create/Edit Source: source types with their fields,
  show every bibliography field (new Source fields); Insert Citation menu
  lists the sources and Add New Source.

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

* fmt (#402)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 15:56:41 -04:00
4ca58d67e1 Draw: Lasso Select, Add Pen, Ink to Shape and Ink Replay (#395) (#408)
* Draw: Lasso Select, Add Pen, Ink to Shape and Ink Replay (#395)

- draw.lasso: with no points picks up the lasso; with a loop on a page it
  selects the strokes mostly inside it and drawings whose centre is inside.
  draw.lassoMove / draw.lassoColor / draw.lassoDelete move, recolour and
  delete the selection (one undo step each); it lasts until the document
  changes otherwise. In the app: drag a loop, drag the selection to move it,
  Delete deletes, Esc lets go, the Color menu recolours it.
- draw.addPen / draw.removePen: a pen gallery (kind, colour, width; capped,
  validated) shown after the built-in pens, kept in ui.json; damaged entries
  are dropped on reading. Right-click a gallery pen to remove it.
- draw.inkToShape: toggles Ink to Shape (pen and pencil strokes that look
  like a line, rectangle/square, ellipse/circle, triangle or polygon of up to
  eight corners become that shape, in the same undo step); with ink
  lasso-selected it converts the selection (draw.convertInk). Our own
  recogniser (cmd/ink_shape.rs): chord test for lines, closure test,
  ellipse RMS fit, closed RDP simplification with corner merging.
- draw.replay: Ink Replay plays the strokes back in drawing order with a
  Rewind / Play-Pause / Forward / Close bar; pages are drawn without ink
  meanwhile. Each new stroke records its drawing order (Freeform::order),
  written to DOCX in the drawing name (`Ink Pen 3 #12`) and read back.

Catalog 402/434 -> 406/434 (93.5%). New strings in all nine catalogs.

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

* Regenerate parity checklist after rebase (#395)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 15:53:47 -04:00
6526b134be Document grid: lines per page and characters per line (#391) (#401)
- Model: `SectionProps::doc_grid` (`w:docGrid` type, line pitch, charSpace) and the
  paragraph `w:snapToGrid` flag; DOCX read and write (ECMA-376 §17.6.5).
- Layout: with a line grid, lines of snapping paragraphs take whole line pitches with
  their text centred; with a character grid, East Asian characters take whole cells of
  the character pitch (all text with snapToChars), Latin text stays proportional.
  Hostile pitches and charSpace are clamped. Headers and footers don't snap.
- Command `layout.documentGrid` (type, linesPerPage, charsPerLine, linePitch,
  charSpace), one undo step, section scope; catalog row under Layout › Page Setup.
- Page Setup dialog: Document Grid section; View › Gridlines draws the document
  grid of a section that has one. UI strings in every catalog (incl. et, sr, sr-latn).
- Tests: DOCX round trip, line pitch and lines per page, character pitch placement,
  command with clamping and undo.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 15:29:13 -04:00
30993ffb3f Track Changes: moves (w:moveFrom/w:moveTo) (#400) (#404)
Moved text is tracked as a move instead of a deletion plus an insertion:
- Model: Revision::move_name links the source (a deletion revision) and
  the destination (an insertion revision) of a move; runs and paragraph
  marks keep using ins/del, so everything that hides deletions or exports
  the result treats the moved-away text like a deletion.
- DOCX: w:moveFrom / w:moveTo inside w:moveFromRangeStart/End and
  w:moveToRangeStart/End (ECMA-376 17.13.5.21-28) are read as linked moves,
  paragraph marks included; on save the ranges are written around each
  moved stretch with the move's name, author and date, moved-away text in
  w:t. A move missing an end, or whose range never ends, opens as a plain
  deletion and insertion.
- Recording: with Track Changes on, a cut of a paragraph or at least a
  sentence gets a revision of its own; pasting it right after makes both
  a move (pasted paragraph marks are tracked as inserted too).
- Accept/Reject at either end resolves both; the Reviewing Pane lists
  moveFrom/moveTo with the move name and "Moved from"/"Moved to".
- Markup: green, double strikethrough at the source and double underline
  at the destination; "Moved from"/"Moved to" balloons.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 15:12:59 -04:00
Valentin KleinandClaude Opus 5.5 4e710a920b Footnote and Endnote options per section (#385) (#390)
- Model: NoteProps (pos, numFmt, numStart, numRestart) in the settings and
  each section; Document::note_options resolves section > document > defaults.
- DOCX: w:footnotePr / w:endnotePr read and written in settings and every
  w:sectPr in schema order; hostile numStart clamped (ECMA-376 §17.11).
- Layout: footnotes below the text, endnotes at the end of each section,
  numbering restarted per section or per page (from where notes land, at
  most three passes), per-section number formats.
- Commands: references.noteOptions takes kind/pos/numFmt/numStart/
  numRestart/scope; references.convertNotes (footnote/endnote/swap, one
  undo step); references.footnote/endnote take a custom mark.
- UI: Footnote and Endnote dialog from the Footnotes group launcher;
  strings in every catalog.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:10:17 -04:00
Valentin KleinandClaude Opus 5.5 f8f1d80c38 Shape Format › Text: direction, alignment and linked text boxes (#369) (#380)
- Model: TextBody on text box parts (vert, anchor, next link); chain
  resolution ignores cycles, self-links and second claimants and caps
  chains at 256 boxes; pruning a box clears links to it.
- Layout: text boxes lay out in their own frame and turn like table
  cells (rotate 90°/270°), stacked text one letter to a line, Align Text
  top/middle/bottom; linked boxes split the first box's story by line,
  each at its own line length.
- DOCX: wps:bodyPr vert/anchor, wps:txbx id and wps:linkedTxbx id/seq
  read and written (ECMA-376).
- Commands shape.textDirection, shape.alignText, shape.link,
  shape.breakLink; ribbon Shape Format › Text group with drawn icons;
  Create Link waits for a click on the target box.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:13:07 -04:00
09921dfae8 Right-to-left tables and sections (#362) (#365)
Tables marked right to left (w:bidiVisual) now lay out mirrored: the first
column stands at the right edge, start alignment and indent are measured
from the right margin, and each cell's start margin and border sit on its
right. Merged cells, turned text and nested tables follow; clicks and the
caret follow the drawn geometry while Tab keeps the logical cell order.
Right-to-left sections (w:bidi) run their newspaper columns from the right,
with the line between columns mirrored.

The table direction is read and written in .docx (section direction already
round-tripped). New commands table.rtl and layout.sectionRtl, a "Table
direction" choice in Table Properties ("rtl" in table.properties) and a
"Section direction" choice in Page Setup, translated in every catalog.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 14:00:53 -04:00
c010b73428 Documents keep their own footnote separators and continuation notice (#361)
* Keep and draw a document's own footnote separators and continuation notice (#352)

The docx reader used to drop the special footnotes and endnotes (w:type
separator, continuationSeparator, continuationNotice), so a document's own
separator lines and continuation notice were lost on screen, in PDF and on
save. They are now kept on the document as paragraphs, with the separator
lines themselves as run objects.

Layout draws the document's separator above a page's footnotes and its
continuation separator above notes continued from the page before: the
separator objects become a short line or one across the column, and any
other text or formatting is shown as given. The continuation notice goes
under the part of a note that continues on the next page. The endnote
separator is used above the endnotes. Documents without their own keep the
default lines as before. The separators and notice can't be clicked into.

On save the special notes are written back with Word's ids -1, 0 and 1 (the
notes follow them, starting at 2 when there is a notice) and listed in the
settings' footnotePr/endnotePr. Documents without them keep the default
output.

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

* Merge fix: NoteSeps is Clone (the layout context that holds it is cloned)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:53:08 -04:00
72e59f9f0b Drawing Canvas: insert a canvas and draw shapes, pictures and text boxes inside it (#344) (#356)
Insert > Illustrations > Shapes > New Drawing Canvas (and Draw > Drawing Canvas)
inserts a 6" x 3" canvas in line with text. While it is selected, Pictures,
Shapes and Text Box put the new object inside it. Fit, Expand and Scale Drawing
change its frame, Shape Fill and Shape Outline colour its background and border,
and canvas.member moves, resizes or removes a member.

The canvas is the group object with a canvas style: its frame is independent of
its members, and members outside it are clipped on screen and in PDF (new
Draw::Clip / Placed::Clip). DOCX reads and writes wpc:wpc with wpc:bg,
wpc:whole and wps:wsp / pic:pic members.

A canvas turns and flips as a whole, like a group (#332): its background,
outline and clipped members are drawn inside its turn (Draw::Rotated around
Draw::Clip; Placed::Clip carries the spin, its members sit in the unturned
frame), and hit testing follows the turned frame. The canvas schema has no
transform of its own, so a turned canvas keeps its rot/flipH/flipV as an
a:xfrm in a wpc:extLst extension (urn:wordcraft:canvas-xfrm), which Word skips.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:45:13 -04:00
908a6de2c7 Balance newspaper columns before a continuous section break (#350) (#353)
When a multi-column section is followed by a section that starts
continuous, the columns on its last page are laid out again with the
shortest common height that still holds the text (a binary search on
the column height over at most 12 passes of that page), filled left to
right, and the next section starts right below the tallest column.
Keep-together, keep-with-next, widow/orphan control and rows that can't
split still apply, so the columns may come out slightly uneven. Pages
with an explicit column break, sections ending with a next-page,
even- or odd-page break, and one-column sections are not balanced.
Unequal column widths work. A continuous section now always starts
below the tallest column on the page, not the last one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:34:40 -04:00
589e82e050 Mail merge recipients from Excel and OpenDocument spreadsheets (#334) (#341)
Select Recipients › Use an Existing List… now reads .xlsx, .xlsm and .ods
workbooks besides CSV. A new read-only SpreadsheetML / ODF spreadsheet
reader in wordcraft-formats lists the sheets and turns one sheet into rows
of display strings: shared, inline and rich strings, numbers, booleans,
cached formula results, dates and times as ISO 8601 from the cell's number
format, sparse cells, ODS value types, repeated rows/columns and covered
cells. Zip entries, rows, columns, cells, string lengths and repeat counts
are capped, and nothing recurses.

mailings.recipients takes "data" (base64) and "sheet" and "headers"; a
workbook with several sheets and no sheet answers with the sheet names, and
the app shows a Select Table dialog with "First row of data contains column
headers". The desktop picker, dropped files and the web inbox route
spreadsheets to the recipient list.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:31:25 -04:00
8a2a8bb60d Insert > Icons: an original icon library, a searchable picker and Icon Color (#309) (#318)
Adds wordcraft_render::icon_lib, 61 monochrome icons in ten categories
(Arrows, Communication, Office, People, Nature, Weather, Shapes, Symbols,
Technology, Transport). They are original WordCraft artwork: simple
geometric line drawings designed on a 24x24 grid as path data in code, with
no icon files and nothing copied from another icon set.

insert.icon {"id", "color"?, "size"?} renders the chosen icon with vello_cpu
to a transparent PNG at about 300 ppi and inserts it at the caret through the
ordinary picture path, so it saves to .docx and every other format. Without
an id it lists the library (narrowed by query and category) and asks the
front end for the picker. picture.iconColor repaints a one-colour picture
in a new colour and keeps its transparency.

Insert > Illustrations > Icons opens a dialog with a search box, category
chips and a grid of icons drawn live by the egui painter from the same path
data; Insert or a double click places the selection. Picture Format > Adjust
gains an Icon Color menu. New strings are translated in every catalog.

Catalog parity 393/434 -> 394/434 (90.8%).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:27:42 -04:00
a5b17eb3a4 Enclose Characters: put a character in a circle, square, triangle or diamond (#301)
* Home › Font › Enclose Characters: circle, square, triangle or diamond around a character (#297)

New command format.enclose {"shape": "circle|square|triangle|diamond" | null,
"style"?: "shrink|enlarge", "text"?: string} puts one full-width or up to two
half-width characters in an enclosure, changes its shape, or (null) takes the
text back out. It is stored the way ECMA-376 describes it: an EQ field with the
overstrike switch, eq \o\ac(○,字), with the text as its plain-text result.

Layout now draws EQ \o fields: the arguments overstruck as one cluster
(centred, \al or \ar). An enclosure symbol among them is drawn as a vector
shape around the text in the text's colour, so it never depends on the font
having ○ □ △ ◇; "shrink text" scales the text into a one-em shape, "enlarge
symbol" grows the shape around text at its own size. Screen and PDF both get
it through the display list. Imported Word documents with enclosed characters
used to show nothing (the field has no result); they now render.

.docx: written as Word stores enclosed characters (begin, the code split so the
symbol and the text have their own run sizes, end, no result); the reader
recovers the style from those sizes. The style lives in the model as a trailing
\* enlarge on the code, never written to a file.

Home › Font gets an Enclose Characters button that opens a dialog (Style:
None / Shrink text / Enlarge symbol, Text, Enclosure list, a drawn preview);
scripts with a shape never see it. Strings in every interface catalog.

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

* Merge fix: keep both sides (enclosures and ruby; tests); regenerate parity checklist

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:20:45 -04:00
3042e5251e Phonetic Guide: ruby text (furigana, pinyin) over base text (#296)
* Phonetic Guide: ruby text over base text (#288)

Home › Font › Phonetic Guide (format.phonetic) sets small ruby text
(furigana, pinyin) over the selected text. The ruby is one inline object
anchored at U+FFFC whose plain text is the base, so selection and the
caret treat it as one unit and exports read the base text.

- Model: InlineObject::Ruby with base, ruby, alignment (ECMA-376
  ST_RubyAlign), ruby size, raise, base size, language and the ruby's own
  formatting.
- Layout: the base is drawn in its run's formatting, the ruby above it by
  alignment (centre, distribute letters/space, left, right); the wider of
  the two sets the width and the line grows to fit the ruby. Drawn in the
  shared display list, so screen and PDF both show it.
- DOCX: w:ruby read and written with w:rubyPr (rubyAlign, hps, hpsRaise,
  hpsBaseText, lid), w:rt and w:rubyBase runs with their formatting.
  Previously only the base text was kept.
- Command format.phonetic {"ruby", "align"?, "size"?, "offset"?}: sets,
  edits or (null/"") removes the ruby, undoable; without "ruby" it opens
  the dialog and returns the selection's phonetic guide.
- UI: Phonetic Guide button in Home › Font and a dialog (base text, ruby
  text, alignment, offset, size, Remove), strings in every catalog.

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

* Merge fix: keep both sides' tests

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 13:12:45 -04:00
dba6e66d53 Choose which side of an object text wraps on (#364)
* Tight and Through wrapping follow the object's outline (#354)

Text now wraps around a Tight or Through object's shape instead of its
bounding box. The outline is the object's wrap polygon (wp:wrapPolygon,
in the 21600-unit space of its frame) when it has one; otherwise it is
derived: a shape's geometry (ellipse, triangle, star, heart... flattened),
the convex hull of a picture's opaque pixels (alpha threshold on a coarse
grid, at most 64 points), or the rectangle. The outline turns and flips
with the object, keeps the left/right and top/bottom distances from text,
and is built once per placed object; each line asks it which stretches of
its band it covers. Through also lets text into gaps inside the outline
that are wide enough.

DOCX reads and writes wp:wrapPolygon (with edited) for wrapTight and
wrapThrough; a shape without one gets its outline written, other objects
their rectangle, since the schema requires a polygon. Square, top and
bottom, behind and in front of text are unchanged.

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

* Choose which side of an object text wraps on (#354)

Square, Tight and Through objects now have a wrap side, like Word's
"Wrap text" choice: both sides (the default), left only, right only, or
the largest side. Left keeps the band to the object's right empty on
every line beside it, Right the band to its left, and Largest picks the
wider side line by line. It works with contour wrapping and with turned
objects, and right-to-left paragraphs mirror it.

The setting is Float::wrap_text, read from and written to wrapText on
wp:wrapSquare, wp:wrapTight and wp:wrapThrough (previously always
written as bothSides). The new arrange.wrapText command
{"side": "bothSides|left|right|largest"} sets it on the selected object
as one undo step. The ribbon's Wrap Text button is now a menu with the
wrap styles and a Wrap Text Sides submenu, translated in every
interface language.

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-11 12:39:42 -04:00
860f4ca260 Tight and Through wrapping follow the object's outline (#354) (#360)
Text now wraps around a Tight or Through object's shape instead of its
bounding box. The outline is the object's wrap polygon (wp:wrapPolygon,
in the 21600-unit space of its frame) when it has one; otherwise it is
derived: a shape's geometry (ellipse, triangle, star, heart... flattened),
the convex hull of a picture's opaque pixels (alpha threshold on a coarse
grid, at most 64 points), or the rectangle. The outline turns and flips
with the object, keeps the left/right and top/bottom distances from text,
and is built once per placed object; each line asks it which stretches of
its band it covers. Through also lets text into gaps inside the outline
that are wide enough.

DOCX reads and writes wp:wrapPolygon (with edited) for wrapTight and
wrapThrough; a shape without one gets its outline written, other objects
their rectangle, since the schema requires a polygon. Square, top and
bottom, behind and in front of text are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 12:10:48 -04:00
34511263a7 Customize Ribbon and Quick Access Toolbar (#379) (#384)
* Customize Keyboard dialog (#368)

Tools > Customize Keyboard (tools.customizeKeyboard): pick a category and
a command, see its keys, press a combination to see which command uses it,
then Assign / Remove / Reset All. Also reachable from File > Options
(Keyboard shortcuts: Customize...) and command search.

- engine: KeyMap (assigned key -> command, removed built-in keys), consulted
  before the registry's built-in shortcuts; keys compared in one canonical
  form. Saved maps read leniently: junk entries and unknown commands are
  dropped, never failing the rest of the preferences.
- tools.customizeKeyboard: with params (assign/remove/reset/command/key) it
  only changes or reports the map; without params it opens the dialog.
- ui: key dispatch and menu/tooltip shortcut text use the user map; the map
  persists in ui.json; more keys (Q, F1-F12, 4/6/7, Insert) can be bound.
- i18n strings in every catalog; docs/ui-parity, gaps #24, ROADMAP log.

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

* Customize Ribbon and Quick Access Toolbar (#379)

Tools > Customize Ribbon (tools.customizeRibbon): one dialog with two
pages. Ribbon: commands by category next to the ribbon's tabs; show or
hide and reorder tabs, add custom tabs and custom groups (on built-in or
custom tabs) holding any commands, rename, remove, reset. Quick Access
Toolbar: add, remove and reorder its commands, reset.

- engine: RibbonLayout (tab order and visibility, custom groups, the
  toolbar's commands). Saved layouts read leniently: junk, duplicates,
  reserved names and unknown commands are dropped.
- tools.customizeRibbon: with params (hide/show, newTab, newGroup, add,
  remove, rename, move, qatAdd/qatRemove/qatMove, reset, layout) it only
  changes or reports the layout; without params it opens the dialog.
- ui: the tab strip and ribbon follow the layout (custom groups draw the
  commands' own icons, else a lettered tile); the Quick Access Toolbar
  shows the user's commands; right-click any ribbon button to add it to
  (or remove it from) the toolbar or open the dialog; the toolbar's ...
  button opens a menu of common commands and More Commands... (#376);
  File > Options has a second Customize button; layout persists in
  ui.json under `ribbon`.
- Open and Print icons drawn in code; i18n strings in every catalog;
  docs/ui-parity, gaps #24, ROADMAP log, parity checklist.

Fixes #376.

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-11 12:06:17 -04:00
9cf3b6862f Customize Keyboard dialog (#368) (#378)
Tools > Customize Keyboard (tools.customizeKeyboard): pick a category and
a command, see its keys, press a combination to see which command uses it,
then Assign / Remove / Reset All. Also reachable from File > Options
(Keyboard shortcuts: Customize...) and command search.

- engine: KeyMap (assigned key -> command, removed built-in keys), consulted
  before the registry's built-in shortcuts; keys compared in one canonical
  form. Saved maps read leniently: junk entries and unknown commands are
  dropped, never failing the rest of the preferences.
- tools.customizeKeyboard: with params (assign/remove/reset/command/key) it
  only changes or reports the map; without params it opens the dialog.
- ui: key dispatch and menu/tooltip shortcut text use the user map; the map
  persists in ui.json; more keys (Q, F1-F12, 4/6/7, Insert) can be bound.
- i18n strings in every catalog; docs/ui-parity, gaps #24, ROADMAP log.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 12:00:30 -04:00
adcbf52611 Move paragraphs, promote/demote, Go Back and Alt+X like Word (#343) (#351)
- para.moveUp / para.moveDown (Alt+Shift+Up/Down, Ctrl+Shift+Up/Down on macOS): the
  paragraphs the selection touches swap with their neighbour in the same story or cell,
  keeping style and list membership; selection follows; one undo step; refused at the
  container edges, next to a table and across a section break.
- para.promote / para.demote (Alt+Shift+Left/Right, Ctrl+Shift+Left/Right on macOS):
  heading level up/down (Heading 1..9), promoted body text takes the level of the heading
  before it, list items change level like Shift+Tab/Tab.
- edit.goBack (Shift+F5): cycles the caret through the last four edit places.
- text.toggleUnicode (Alt+X; unbound on macOS): hex code <-> character, undoable. The
  canvas no longer types the key's letter after an Alt shortcut ran.
- Catalog rows, translations in every interface catalog, typing/UI parity docs, tests.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:49:02 -04:00
39ab3b5433 Field codes like Word's field keys (#339) (#349)
Show field codes instead of results with View > Field Codes (Alt+F9) or
per field with Toggle Field Codes (Shift+F9); a field showing its code is
drawn as { CODE } on light grey. Edit a field's code in a small popup
(double-click a field showing its code, right-click > Edit Field..., or
fields.setCode), and Ctrl+F9 asks for a new field's code. F9 updates
fields and shows the selected ones' results again.

New commands: fields.unlink (Ctrl+Shift+F9) replaces fields with their
text, fields.lock / fields.unlock (Ctrl+F11 / Ctrl+Shift+F11) keep a
result through updates (already saved as w:fldLock), fields.next /
fields.previous (F11 / Shift+F11) select the next or previous field, and
fields.selected reports the fields in the selection. Range fields
(citations) can be locked, unlinked, edited and stepped through too.

The right-click menu gains Update Field, Edit Field... and Toggle Field
Codes when the selection holds a field. Strings are translated in every
catalog; parity catalog, ui-parity, gaps and roadmap updated.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:22:25 -04:00
eb068d3823 Add Word's common keyboard shortcuts (#330) (#338)
Bind Word's documented default shortcuts that were missing to existing
commands, plus a few small new commands: Reset Paragraph Formatting
(Ctrl+Q), Reset Character Formatting (Ctrl+Space, which used to clear all
formatting), em dash, copyright, registered, trademark and ellipsis, DATE /
TIME / PAGE fields (Alt+Shift+D/T/P), Print Layout / Outline / Draft /
Split views, Word Count (Ctrl+Shift+G), Bookmark, Alt+F7, Ctrl+Page Up/Down
find browsing, Shift+F12 / Ctrl+F12 / Ctrl+Shift+F12 / Ctrl+F2 / Ctrl+F4,
Mark Index Entry, Mark Citation and the mail merge keys.

Where macOS claims Word's key (Cmd+Q quits, Cmd+Space is Spotlight, Cmd+Shift+G
is Find Previous, Option+letter types symbols), the binding follows Word for
Mac or is left off there. F4 Repeat, Q and F2 now reach the registry from the
keyboard, and an Alt shortcut no longer also types its key's character.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:19:59 -04:00
6129bfeee5 Docs: collapse duplicate rows left by parallel merges (#372)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:18:54 -04:00
1581f30176 Import/export fixes: TXT encodings and list labels, RTF Unicode, ODT styles and cell colours, HTML borders and pre spaces, LaTeX alignment groups (#433–#440) (#482)
- TXT: the engine opens and saves .txt through wordcraft-formats (BOM/unmarked
  UTF-16 sniffing, Windows-1252 fallback; list labels and a tab on export)
  instead of its own narrower decoder and body plain text.
- RTF: \uN and \ucN are handled in document info, bookmark names and field
  instructions, not only in body text.
- ODT: common paragraph styles (and common parents of automatic styles) give
  their text formatting, except the style that sets the paragraph kind;
  table-cell-properties fo:background-color becomes cell shading.
- HTML: tables without style or visible borders export without cell borders;
  white-space:pre paragraphs keep leading/trailing spaces.
- LaTeX: brace groups restore the paragraph alignment set inside them.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:15:08 -04:00
5078d9dea5 Equation fixes: numbering removal, control colour, undo for navigation edits, LaTeX text spaces (#444, #446, #447, #448) (#487)
- equation.number off strips the number from every row; a single row is unwrapped,
  several rows stay an equation array (was: only the first row survived).
- equation.move works out AutoCorrect/build-up on a copy; plain navigation no longer
  rewrites the equation (keeps imported OMML such as m:ctrlPr colours), and a real
  build-up is checkpointed as "Build Up" and marks the document dirty.
- LaTeX tokenizer keeps white space inside \text/\textrm/\mbox/\textnormal/\textbf/\textit
  arguments (collapsed to one space), so \text{for all x} stays "for all x".

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:14:51 -04:00
2666031c35 Spelling: common words like "desktop" are recognised (#520) (#522)
The English speller only knew the 1990s Moby word list plus a small
hard-coded EXTRA array, so everyday modern words (desktop, laptop,
download, upload, spreadsheet, database, screenshot, inbox, offline,
workplace…) were flagged. Inflection, doubled-consonant and prefix rules
also only looked stems up in Moby, so even supplemented words failed in
inflected forms (blogging, uploader).

- New supplement assets/spelling/en-extra.txt: ~220 lowercase base forms,
  our own list (MIT OR Apache-2.0), replacing the EXTRA array.
- All stem lookups (inflections, doubled consonant, prefixes) consult the
  dictionary and the supplement; suggestions include supplement words.
- Regression tests: common modern words and inflections accepted,
  misspellings still flagged, "dekstop" suggests "desktop"; the list
  stays sorted and lowercase.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:14:31 -04:00
5c2fbda5e2 Dialogs: switching tabs keeps the window's top edge in place (#507) (#511)
Every dialog was anchored CENTER_CENTER, which egui re-applies each frame,
so a tab of a different height split the change between top and bottom and
the tab strip jumped. Dialogs are now centred on the frame they open, then
anchored CENTER_TOP at that top edge until they close; only the bottom edge
moves. egui still keeps the window on screen.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:14:13 -04:00
18e472e3de .doc import fixes: piece bold, section kinds, orientation, list overrides, style inheritance, alternate font names (#458–#463) (#484)
Follow [MS-DOC] in the Word 97-2003 reader:
- Prm: bit 0 is fComplex; Prm0 isprm in bits 1-7, Prm1 Prc index in bits 1-15 (#458)
- sprmSBkc operands 0-4 map to continuous/new column/new page/even/odd (#459)
- sprmSBOrientation 1 = portrait, 2 = landscape (#460)
- negative sprmPIlfo (0xF802-0xFFFF) references the negated LFO index (#461)
- StdfBase: istdBase shares the word with stk and is zero-based; the
  whole base chain is applied, bounded and cycle-safe (#462)
- SttbfFfn entries advance by cchData, covering the optional xszAlt (#463)

One regression test per issue, built in code from the spec.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:13:20 -04:00
e5c55c0fc5 CLI: a closed stdout ends quietly instead of panicking (#508) (#510)
`println!` panics when stdout is a closed pipe, so `wordcraft-cli commands
--json | head` aborted with exit 101. Every stdout write now goes through a
locked handle with `writeln!` and propagates `io::Error`; a BrokenPipe becomes
`Failure::StdoutClosed`, which `main` treats as a normal exit 0. The MCP stdio
server's BrokenPipe takes the same path, so a client that hangs up ends the
server cleanly. Real errors still go to stderr with exit 1.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:13:01 -04:00
b20d43707c Backstage: the side menu is fixed width, no dead resize handle (#502) (#504)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 11:12:45 -04:00
1d4f741f23 Citations: eight more bibliography styles (#383) (#388)
Citation and bibliography styles are now data: a CiteStyle table entry
per style (in-text form, author name format, ordering, entry templates
for books and journal articles). Adds Harvard (Anglia), ISO 690
author-date and numerical, Turabian, GB/T 7714, GOST (name sort and
title sort) and SIST02 next to APA, MLA, Chicago and IEEE. Numbered
styles count in order of first citation (GOST: in its sorted order) and
renumber when a citation is inserted. Word's style names and
style-sheet file names select the matching style.

The References > Style button is now a menu listing every style with
the current one checked; its label shows the current style.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-11 01:55:01 -04:00
Valentin KleinandClaude Opus 5.5 51e927028a Options: interface size setting (#475) (#480)
File › Options › General gets an Interface size choice (80–200 %), kept in
ui.json and applied as egui's zoom factor, so it multiplies the display
scale the system reports instead of replacing it. Junk saved values read
as 100 % and out-of-range ones are clamped. Agents use ui.interfaceSize
{"percent"}. Document zoom (view.zoom) is untouched; egui's keyboard zoom
stays off, so Word shortcuts never change the interface size.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:51:56 -04:00
Valentin KleinandClaude Opus 5.5 5528eff2ac Mail merge: SKIPIF, NEXT, IF in preview, header/footer fields (#427, #428, #429, #430) (#478)
Merge fields are now evaluated in document order with a record cursor,
shared by Preview Results and Finish & Merge:
- SKIPIF drops the merged copy when its condition holds (#427).
- NEXT/NEXTIF move later fields to the next record; the next copy
  starts after it, and records past the end are blank (#428).
- IF rules on merge fields preview their true/false text (#429).
- Header and footer stories are previewed and merged; with merge fields
  in them each letter becomes its own section with its own copies (#430).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:51:15 -04:00
Valentin KleinandClaude Opus 5.5 aa7b41d8ef Groups keep rotated members' orientation; group resize; ink erase after rotation (#441, #442, #443, #445) (#474)
- Group keeps each member's own turn (and composes a merged group's turn
  into its members); the group box covers the members' turned bounds and
  the new group starts unturned (#441).
- Size (picture.size) resizes a selected group, scaling its members
  through the group's child space like Move or Resize (#442).
- Ungroup decomposes a turned group: each member's centre is turned about
  the group's centre and its turn becomes its own within the group's (#443).
- The eraser tests the point turned back into the stroke's unturned frame,
  so turned ink is erased where it is drawn (#445).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:51:00 -04:00
Valentin KleinandClaude Opus 5.5 5e84270e60 Hyperlinks cover only their own text (#413) (#449)
Linking a whole paragraph also linked its paragraph mark, and Enter
copied the link at the split into the new paragraph's mark, so everything
typed in the paragraphs Enter made (above or below) became the link, and
hovering anywhere near the end of the text showed the link's tooltip.

- doc: a paragraph mark never takes a hyperlink (format_range, split_off)
- engine: typing at either end of a link isn't linked; inside it is (Word)
- ui: hover tooltip and Ctrl+click only on the linked text itself

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:50:48 -04:00
Valentin KleinandClaude Opus 5.5 972df1e0c7 Track Changes: formatting revisions (w:rPrChange/w:pPrChange) (#41) (#399)
Formatting changes are tracked like insertions and deletions:
- Model: runs and paragraph marks carry the formatting before a tracked
  change (CharProps::fmt_change), paragraphs their old properties
  (ParaProps::fmt_change) and a list numbering change; tables, rows,
  cells and sections their old properties.
- DOCX: w:rPrChange (runs and paragraph marks), w:pPrChange,
  w:numberingChange, w:tblPrChange, w:trPrChange, w:tcPrChange and
  w:sectPrChange are read and written back, each last in its parent
  (ECMA-376 17.13.5). Nested changes are ignored; ids are renumbered.
- Recording: with Track Changes and Track Formatting on, Session::run
  compares the paragraphs a command changed with the document before it,
  so every formatting command records without per-command code. An
  author's later changes keep the formatting before the first one;
  formatting set back to the original stops being a change.
- Accept/Reject (at the caret, selection, all) keep or restore the
  formatting; Next/Previous and the Reviewing Pane list format changes
  with a "Formatted: Bold, Size: 14" description.
- Markup: change bars beside formatted lines and "Formatted: ..." balloons
  in the markup area, per Track Changes Options.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:32:59 -04:00
4615cef878 Rotated OLE objects keep their turn when saving DOCX (#333 follow-up to #332) (#370)
#333 writes OLE objects back from their kept markup, resized to the
object's current size, but #332's rotation and flips weren't carried into
that markup: a turned or flipped OLE object came back unturned (DrawingML)
or with its old turn (VML).

The VML shape's style now gets the object's rotation and flip, and a
DrawingML picture's a:xfrm gets rot/flipH/flipV and its size, with the
drawing's wp:effectExtent covering the rotated bounds like other drawings
(added when missing). Inline VML shapes now read their rotation and flips
too, so the turn survives a WordCraft round trip. Charts and diagrams can't
be turned, so their frame needs nothing new.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 19:13:08 -04:00
a6b23683c1 Font box: type to find a font (#359) (#367)
Typing in the ribbon's or the mini toolbar's font box now lists the
installed families that match, under the field: exact name first, then
names starting with the text, then names containing it, ignoring case
("roman" finds Times New Roman). Up/Down move the highlight, Enter or a
click applies it ("arial" applies Arial), Escape restores the current
font. Text that matches no family is still applied as typed, as Word
allows fonts that aren't installed.

The search is opt-in (widgets::font_combo); the font size box keeps
literal entry. Matches are computed only when the text changes and the
list stays virtualised (#121).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 19:12:01 -04:00
e71e2d1c52 Draw tab: draw with pen, pencil and highlighter, erase ink, and hide ink (#315)
* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303)

Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser.
table.draw takes a pen stroke in page points: a rectangle outside a table
inserts a one-cell table of that size at the paragraph under its start; a
straight vertical or horizontal line inside a table splits the cells it
crosses (new grid column or row; untouched cells span it). table.eraser
merges the two cells beside the border nearest a point. Each stroke is one
undo step. In the app the buttons toggle pen and eraser modes with a live
preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks
don't move the caret while a tool is on. Splitting through part of a
vertically merged cell and erasing beside one are refused with a clear
error. Catalog 384/430 -> 387/430.

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

* Draw tab: pen, pencil and highlighter ink, stroke eraser and Hide Ink (#307)

Ink strokes are floating freeform shapes: a new Freeform geometry (paths in
shape-local coordinates, opacity, and the pen that drew it) on
InlineObject::Shape with ShapeKind::Freeform. draw.stroke inserts one
anchored to the paragraph under the first point (one undo step; points
capped per stroke, strokes capped per page, NaN dropped); draw.erase /
draw.eraser with a point deletes the stroke under it; draw.pen, draw.pencil,
draw.highlighter, draw.eraser and draw.select set the Draw tab's tool in the
session view state; review.hideInk toggles showing ink on screen.

The canvas draws a live stroke while a pen is dragged and commits it on
release; the eraser deletes strokes it passes over (one undo step per drag);
Esc or Select goes back to selecting. Draw tab buttons show the tool in use,
with colour and thickness menus; Review gets an Ink group with Hide Ink.
Ink renders with round caps and joins on screen and in PDF; highlighters are
translucent and behind the text. DOCX writes freeforms as wps:wsp with
a:custGeom (moveTo/lnTo) in a wp:anchor and reads them back, along with
simple custom-geometry freeforms from other files.

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

* #315 review fixes: eraser undo test, sanitized tap dots, custGeom path cap, ink docs

- draw.eraser with a point erases through draw.erase: test that it is one
  undo step (undo/redo) and marks the document dirty.
- Render and PDF: a single-point stroke's dot is drawn at the sanitized
  point, not the raw first one.
- DOCX custGeom reading stops once MAX_PATHS paths (subpaths included) are
  collected, across a:path elements.
- docs/file-format-parity.md: ink is saved as custom geometry, not InkML, so
  Word shows it as a freeform.
- widgets test: theme::apply takes an Appearance (#317).

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

* fmt

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

* Draw tab: pen, pencil and highlighter ink, stroke eraser and Hide Ink (#307)

Ink strokes are floating freeform shapes: a new Freeform geometry (paths in
shape-local coordinates, opacity, and the pen that drew it) on
InlineObject::Shape with ShapeKind::Freeform. draw.stroke inserts one
anchored to the paragraph under the first point (one undo step; points
capped per stroke, strokes capped per page, NaN dropped); draw.erase /
draw.eraser with a point deletes the stroke under it; draw.pen, draw.pencil,
draw.highlighter, draw.eraser and draw.select set the Draw tab's tool in the
session view state; review.hideInk toggles showing ink on screen.

The canvas draws a live stroke while a pen is dragged and commits it on
release; the eraser deletes strokes it passes over (one undo step per drag);
Esc or Select goes back to selecting. Draw tab buttons show the tool in use,
with colour and thickness menus; Review gets an Ink group with Hide Ink.
Ink renders with round caps and joins on screen and in PDF; highlighters are
translucent and behind the text. DOCX writes freeforms as wps:wsp with
a:custGeom (moveTo/lnTo) in a wp:anchor and reads them back, along with
simple custom-geometry freeforms from other files.

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-10 18:50:37 -04:00
0e2e4cde80 Draw tables and split or merge cells with a pen and eraser (#308)
* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303)

Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser.
table.draw takes a pen stroke in page points: a rectangle outside a table
inserts a one-cell table of that size at the paragraph under its start; a
straight vertical or horizontal line inside a table splits the cells it
crosses (new grid column or row; untouched cells span it). table.eraser
merges the two cells beside the border nearest a point. Each stroke is one
undo step. In the app the buttons toggle pen and eraser modes with a live
preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks
don't move the caret while a tool is on. Splitting through part of a
vertically merged cell and erasing beside one are refused with a clear
error. Catalog 384/430 -> 387/430.

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

* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303)

Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser.
table.draw takes a pen stroke in page points: a rectangle outside a table
inserts a one-cell table of that size at the paragraph under its start; a
straight vertical or horizontal line inside a table splits the cells it
crosses (new grid column or row; untouched cells span it). table.eraser
merges the two cells beside the border nearest a point. Each stroke is one
undo step. In the app the buttons toggle pen and eraser modes with a live
preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks
don't move the caret while a tool is on. Splitting through part of a
vertically merged cell and erasing beside one are refused with a clear
error. Catalog 384/430 -> 387/430.

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-10 18:50:20 -04:00
15f2f01475 Tabs and Borders and Shading dialogs (#327)
* Add Tabs and Borders and Shading dialogs (#320)

Tabs: lists the paragraph's tab stops with position, alignment and leader,
Set / Clear / Clear All and the default interval. Opens from the Paragraph
dialog's Tabs… button, by double-clicking a tab marker on the ruler, or
para.tabs without params. OK is one para.tabs call, which now takes
set / clear / clearAll / default (clearing a style's stop records a clear
stop over it).

Borders and Shading: Borders (None / Box / Shadow / 3-D / Custom, style,
colour, width, paragraph or text, preview with clickable sides), Page
Border (same, whole document or this section) and Shading (fill, paragraph
or text) tabs. Opens from Home › Paragraph › Borders › Borders and
Shading… and Design › Page Borders. OK runs only the changed parts as one
undo step. para.borders and design.pageBorders take exact "sides" (and
design.pageBorders a style and applyTo). Shadow and 3-D are drawn as
heavier sides because the model has no border shadow flag; shading
patterns are omitted because the model keeps a solid fill only.

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

* Keep charts, SmartArt and OLE objects when saving DOCX (#319)

Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.

The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.

The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.

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

* Columns, Symbol and Field dialogs (#321)

Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).

Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.

Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.

Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.

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

* #321: Field dialog descriptions in our own words (clean room)

Reword the one-line field descriptions and the empty-selection prompt so
none reads like Word's; translations keep their meaning under the new keys.

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

* #320: test that a default-tab-only para.tabs change undoes and redoes

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

* Keep charts, SmartArt and OLE objects when saving DOCX (#319)

Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.

The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.

The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.

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

* Columns, Symbol and Field dialogs (#321)

Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).

Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.

Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.

Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.

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

* Merge fix: #340's rotation round-trip test builds an Image with #333's ole field

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

* Add Tabs and Borders and Shading dialogs (#320)

Tabs: lists the paragraph's tab stops with position, alignment and leader,
Set / Clear / Clear All and the default interval. Opens from the Paragraph
dialog's Tabs… button, by double-clicking a tab marker on the ruler, or
para.tabs without params. OK is one para.tabs call, which now takes
set / clear / clearAll / default (clearing a style's stop records a clear
stop over it).

Borders and Shading: Borders (None / Box / Shadow / 3-D / Custom, style,
colour, width, paragraph or text, preview with clickable sides), Page
Border (same, whole document or this section) and Shading (fill, paragraph
or text) tabs. Opens from Home › Paragraph › Borders › Borders and
Shading… and Design › Page Borders. OK runs only the changed parts as one
undo step. para.borders and design.pageBorders take exact "sides" (and
design.pageBorders a style and applyTo). Shadow and 3-D are drawn as
heavier sides because the model has no border shadow flag; shading
patterns are omitted because the model keeps a solid fill only.

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

* cargo fmt after merging main

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 17:56:54 -04:00
8204c5cb49 Columns, Symbol and Field dialogs (#335)
* Keep charts, SmartArt and OLE objects when saving DOCX (#319)

Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.

The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.

The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.

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

* Columns, Symbol and Field dialogs (#321)

Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).

Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.

Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.

Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.

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

* #321: Field dialog descriptions in our own words (clean room)

Reword the one-line field descriptions and the empty-selection prompt so
none reads like Word's; translations keep their meaning under the new keys.

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

* Keep charts, SmartArt and OLE objects when saving DOCX (#319)

Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.

The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.

The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.

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

* Columns, Symbol and Field dialogs (#321)

Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).

Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.

Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.

Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.

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

* Merge fix: #340's rotation round-trip test builds an Image with #333's ole field

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-10 17:54:23 -04:00
a7c809e63b Charts, SmartArt and OLE objects are no longer lost when you save a .docx (#333)
* Keep charts, SmartArt and OLE objects when saving DOCX (#319)

Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.

The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.

The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.

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

* Keep charts, SmartArt and OLE objects when saving DOCX (#319)

Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.

The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.

The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.

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

* Merge fix: #340's rotation round-trip test builds an Image with #333's ole field

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-10 17:48:51 -04:00
79b6f33b03 Long footnotes continue onto the next page (#352) (#357)
When the footnotes referenced on a page don't fit below its text, the note
that doesn't fit is split at a line boundary: its first line stays on the
page of its reference (if even that doesn't fit, the reference line moves
to the next page, as before), and the rest continues at the top of the
next page's note area, before that page's own notes, under a full-width
continuation separator. Notes still continuing after the last page get
pages of their own. Endnotes are unchanged.

The docx reader doesn't keep a document's own continuation separator or
continuation notice, so the default line is drawn and no notice.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 17:43:38 -04:00
1d7a54f883 Define New Multilevel List and Track Changes Options dialogs (#342)
* Define New Multilevel List and Track Changes Options dialogs (#328)

Home > Paragraph > Multilevel List > Define New Multilevel List edits all nine
levels with a live preview: number text and style, start, restart after a
level, linked paragraph style, alignment, aligned-at and text indent,
what follows the number with an optional tab stop, and the number's font. OK
runs the new list.define command (list.get reads a definition back). List
levels gain a restart-after level and a tab stop, which the counters and
layout honour and .docx reads and writes (lvlRestart, num tab); number
alignment now places the label.

Review > Tracking's launcher opens Track Changes Options: show comments, ink,
insertions and deletions, formatting; balloon mode; insertion and deletion
marks and colours; changed-line bars and their colour; track formatting. They
go through review.trackingOptions, are saved per user with the interface
preferences, and the page drawing honours the marks, colours, hidden
deletions, change bars and inline mode.

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

* Parity checklist regenerated after merging main into #342

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-10 17:40:39 -04:00
Valentin KleinandClaude Opus 5.5 bd10795306 Free rotation and flips for pictures, shapes and text boxes (#332) (#340)
Drawings carry an angle and horizontal/vertical flips on their placement
(Float::rot, flip_h, flip_v), read and written as DrawingML a:xfrm rot,
flipH and flipV for pictures, shapes, text boxes and groups; the written
effect extent covers the rotated bounds and reading takes that overhang
back out. Layout draws them turned about their centre (screen and PDF),
square wrap and inline lines keep clear of the rotated bounds, and hit
testing follows the turned shape. arrange.rotate now turns the frame
(90 degrees, flips) instead of rewriting pixels; arrange.rotation sets an
exact angle. The canvas frame has a rotation handle (Shift snaps to 15
degrees) and turned frames resize along their own axes; Picture Format
and Shape Format get a Rotation box and a Rotate menu. Text inside a
rotated text box stays upright for now.

Groups turn and flip as a whole (wpg:grpSpPr a:xfrm): members keep their
own turn inside the group's, are carried round its centre, and a group's
text boxes move with it. Shape effects are drawn inside the turn; a
shadow's direction follows DrawingML rotWithShape (kept on the model,
true when absent; Word's presets write 0, so their shadows keep their
page direction).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 17:10:09 -04:00
81bf9187f2 Wrap long display equations across lines like Word (#326) (#329)
A display equation wider than its column ran off the page. It now breaks
at top-level binary operators and relations (never inside a fraction,
radical, script or matrix), filling each line greedily; a term wider
than the line overflows instead of breaking inside.

- Break before the operator by default, or after it, or repeated on both
  lines, as the document's m:brkBin says; m:brkBinSub picks how a minus
  repeats.
- Continuation lines are indented by m:wrapIndent (one inch by default)
  or set flush right with m:wrapRight.
- Manual breaks (m:brk on a run) start a new line; m:alnAt aligns it at
  an operator of the first line.
- DOCX: m:mathPr in settings.xml and m:brk in math runs are read and
  written back. Documents without them save as before.
- Inline equations, numbered equations and m:jc alignment are unchanged.

Tests: layout (a long sum wraps within the column with indented
continuation lines; after/repeat modes; manual breaks and alnAt; short
equations stay on one line; hostile break input) and DOCX round-trip.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 16:37:25 -04:00
9bfdbee0b7 System interface theme follows OS changes again, and Dark page no longer darkens the interface (#317)
* System interface theme keeps following the OS; Dark page leaves the interface alone (#311, #312)

With Interface theme set to System, every frame resolved the OS appearance
to Light or Dark and passed that to egui's set_theme. egui then sent
SetTheme(Light/Dark) to the window, which on macOS sets an explicit
NSAppearance; winit ignores appearance changes while one is set, so later
OS switches never reached the app.

Both palettes are now installed as egui's light and dark styles (with the
same spacing and text styles), and System leaves egui's preference on
System with a light fallback, so egui picks the matching style from the
OS appearance it reports and the window keeps following the OS. Manual
Light and Dark still pin the window.

Dark page (View > Switch Modes, Options > Dark page) also forced the
interface dark; it now only changes how pages are drawn and the interface
follows the Interface theme setting alone.

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

* Theme: update #314's test to the new theme::apply(ctx, Appearance) signature

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-10 16:27:23 -04:00
1800007463 Windows: use OpenGL when DirectX 12 has no GPU driver (#316) (#325)
Since Windows defaults to DirectX 12 only (#50), PCs whose graphics chip has
no DirectX 12 driver (Intel 4th generation on Windows 11) fall back to WARP,
Microsoft's software renderer, and the app lags. Before eframe creates its
instance, WordCraft now probes DirectX 12 with a short-lived instance: a
hardware adapter that meets eframe's device limits keeps DirectX 12 only;
WARP only enables OpenGL next to DirectX 12 (GPU preferred, WARP as last
resort); no adapter, or only GPUs below the limits, uses OpenGL alone.
WGPU_BACKEND still overrides everything. The choice is logged, and the
decision is a pure function with unit tests.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 16:24:16 -04:00
4b450f1b00 Open and save password-protected Word documents (#55) (#281)
* Clipboard pane: collect the last 24 copies and cuts and paste any of them again

Copy and Cut now also keep each item (rich fragment plus plain text) in a
session list of up to 24, newest first. Items over ~4 MB (estimated) are
skipped and the list stays under ~24 MB in total; copying the same thing
twice in a row adds it once. The system clipboard is untouched.

New commands: edit.clipboardPane (toggle the pane, also the Clipboard
group's dialog launcher), edit.clipboardItems (list with previews),
edit.pasteClipboardItem {index}, edit.pasteAllClipboard,
edit.deleteClipboardItem {index} and edit.clearClipboard. Pasting goes
through the normal paste path, so it is one undo step and respects Track
Changes. The pane lists the items with a short preview; clicking one pastes
it at the caret, and each has Delete, plus Paste All and Clear All.

Parity: 378 -> 379 of 428 features.

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

* Layout > Arrange > Group: group pictures, shapes and text boxes into one object

Shift+click (or Ctrl+click) adds pictures, shapes and text boxes to the
selection; Group (arrange.group) combines the selected floating objects
into one group object that moves, wraps and resizes as one, with its
members keeping their places and scaling with the group. Ungroup
(arrange.ungroup) splits it back, where the group showed them, and
leaves them selected so Group puts them back together.

- Model: InlineObject::Group with the members' coordinate space (like
  DrawingML a:chExt), so resizing the group scales its members.
- Layout draws the members inside the group's frame; hit testing
  selects the group; text boxes in a group show their text.
- DOCX writes wpg:wgp with a:chOff/a:chExt and each member's a:xfrm,
  and reads Word's groups (including inside mc:AlternateContent and
  nested groups, flattened).
- select.addObject makes multi-selection scriptable (pos or Selection
  Pane index).

Parity 378 -> 379 of 428 (88.6%); floor raised to 88.5%.

Fixes #265

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

* Paragraph: Asian Typography settings, with kinsoku and mid-word Latin wrapping in layout (#269)

Paragraphs gain the Asian Typography flags (line-breaking rules, wrapping
Latin text in the middle of a word, hanging punctuation, compressing
punctuation at the start of a line, and automatic space between Asian and
Latin text and between Asian text and numbers). They inherit through styles,
round-trip through .docx pPr, are set by the new para.asianTypography
command and appear on a new Asian Typography tab of the Paragraph dialog.

Layout applies the kinsoku lists (no line starts with closing East Asian
punctuation or ends with opening punctuation), breaks between any East Asian
characters when kinsoku is off, and breaks Latin words anywhere when word
wrap is off. Text without East Asian characters breaks as before.

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

* Manage Styles: list, preview, delete and show or hide styles

Home › Styles › Manage Styles (styles.manage) opens a dialog from the
Styles pane listing the document's paragraph, character, linked and table
styles, sorted as recommended or alphabetically, with a preview line and a
description (type, formatting it sets, what it is based on). From there:
Modify… and New Style… reuse the existing dialogs and come back, Delete
removes custom styles, and two checkboxes show the style in the Styles
gallery (w:qFormat) or hide it from style lists (w:semiHidden).

styles.manage returns the list for scripts (name, type, builtIn,
inGallery, hidden, basedOn, priority, own formatting). styles.delete now
moves paragraphs, runs and tables that used the style to its base (in
every story), rebases styles built on it and takes a custom linked
character half with it; built-in styles are refused and it is undoable.
New styles.setVisibility {style, gallery?, hidden?} sets the flags, which
round-trip through .docx.

Fixes #266

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

* Create and modify your own table styles, saved in the .docx (#146)

New Table Style and Modify Table Style (Table Design > Table Styles) make
real table styles in the document's style sheet: name, based on, and
formatting for the whole table, the header row and banded rows (borders,
fill, bold, font colour). The table at the caret takes the new style, it
shows in the gallery, and other tools that read table styles by name
(pandoc's custom-style, for one) see it.

- engine: table.newStyle / table.modifyStyle with documented params
  (regions take borders, fill, bold, italic, colour, size, font; bandSize;
  para); table.style only accepts table styles now.
- doc/layout: table styles gain header-row and row-band cell borders,
  row-band character formatting and rows per band, applied by layout.
- docx: tblStyleRowBandSize and firstRow/band1Horz tcBorders and rPr are
  read and written, so custom styles round-trip.
- ui: a compact dialog from the Styles menu next to the gallery; table
  style tiles redraw when a style changes.

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

* Table styles: every region, a live preview, delete, custom styles first (#146)

Follow-up to the custom table styles work: the New/Modify Table Style
dialog now covers every region a table style formats, shows what the
style will look like, and table styles can be deleted.

- engine: table.newStyle / table.modifyStyle take lastRow (total row),
  firstColumn, lastColumn and bandedColumns regions with the same props
  as the others. New table.deleteStyle removes a custom table style:
  built-in ones are refused, styles based on it are re-based on its own
  base with its formatting folded in (so they look the same), and tables
  using it fall back to Table Grid. Undoable like any command.
- doc/layout: table styles gain fill and cell borders for the first and
  last column and total row, and fill, text and borders for column
  bands; layout applies them in the standard order (column bands, row
  bands, first/last column, header/total row).
- docx: firstCol/lastCol/lastRow/band1Vert shading and cell borders are
  read and written.
- ui: the dialog's "Apply formatting to" lists all seven regions and a
  small live preview table shows the pending changes; Styles menu gets
  Delete Table Style (disabled for built-ins); the gallery shows the
  document's own table styles first.

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

* Paste pictures from the system clipboard (screenshots, Copy Image, copied files) (#45)

egui's paste event only carries text, so a clipboard holding just a picture
sent no paste at all on Ctrl+V, and the ribbon/context-menu Paste only knew
WordCraft's own copy ("the clipboard is empty").

Desktop: the app reads the clipboard's picture through a new
Services::clipboard_picture (arboard, already in the build via egui-winit):
copied files first, else image pixels. Mod+V whose release arrives without a
paste event, and Paste from the ribbon or menus, insert that picture through
insert.picture (one undo step); text naming copied picture files inserts the
pictures. Pixel sizes are hostile: at most 16,000 px a side and 50 Mpx, the
buffer must match, files at most 200 MB.

Web: a document paste listener reads image files from the paste event when
there's no text and delivers them through the inbox like dropped pictures.

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

* Shape Format: Shape Effects — shadow, glow and soft edges (#275)

Shapes and text boxes get an effects model (outer shadow with colour,
transparency, blur, distance and angle; glow with colour, size and
transparency; soft-edge radius), the shape.effects command (presets,
custom values or null per effect, applied to every selected shape,
undoable, hostile numbers clamped), screen and PDF rendering with the
same banded approximation of a blur, DOCX a:effectLst read/write
(outerShdw, glow, softEdge) with a matching wp:effectExtent, and a
contextual Shape Format tab with Shape Fill, Shape Outline and Shape
Effects (Shadow, Glow, Soft Edges submenus) plus Arrange.

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

* Open and save password-protected Word documents (#55)

Opening a password-protected .docx used to fail as a broken zip and showed
a blank document. Encrypted packages (an OLE compound file with
EncryptionInfo + EncryptedPackage) are now detected and decrypted with
ECMA-376 agile encryption, written from the public [MS-OFFCRYPTO] spec:
password key derivation with spinCount, verifier check, data-integrity
HMAC, AES-CBC in 4096-byte segments (AES-128/192/256 with SHA-1/256/384/512
on read; AES-256 + SHA-512, spinCount 100000 on save). Standard (Office
2007) and certificate encryption give a clear "not supported" error.

- docx: crypt module (decrypt, encrypt, is_encrypted, read_with_password),
  PasswordRequired / WrongPassword / Encryption errors; hostile files are
  bounded (stream caps, spinCount <= 10,000,000, checked lengths).
- engine: file.open/file.save take "password"; file.encrypt
  {"password": string|null} sets or removes the save password (pure, so it
  never lands in Repeat or recorded macros); a document opened with a
  password is saved with it again; file.info reports "encrypted".
  Passwords are held in a zeroizing type whose Debug prints ***.
- ui: a password prompt when a user-opened file needs one (scripts get the
  error), File > Info > Protect Document with Encrypt with Password (typed
  twice) and Remove Password; web downloads are encrypted too.
- deps: aes, sha1, sha2, hmac, base64, getrandom (wasm_js in the browser),
  zeroize.

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

* Table styles: Table Grid and Normal Table can't be deleted even when a .docx marks them custom

A .docx can set w:customStyle on any style, which made Word's built-in table styles look custom
and deletable. table.deleteStyle and the ribbon's Delete Table Style now also recognise them by
id and name, so no table is left on a style that's gone.

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

* Manage Styles: deleting a table style runs Table Design's table style deletion

styles.delete on a table style now calls table_style::delete (#263): its tables take Table Grid,
styles based on it keep their look, and Word's built-in table styles are refused even when a
.docx marks them custom (the Manage Styles list reports them built-in too). Document::restyle no
longer retargets tables' own styles, so there is one place that does.

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

* Paste pictures: Mod+Shift+V (Paste Formatting) no longer pastes a clipboard picture

The key-release fallback that pastes the system clipboard's picture treated every Mod+V release as
Paste. It now requires plain Mod+V or the Paste key (no Shift, no Alt), with a unit test.

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

* Asian typography: test that kinsoku keeps a manual line break before a closing bracket

Manual line breaks end the line by cluster kind, not by break opportunity, so kinsoku's filter
can't drop one before 」; pin that with a layout test.

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

* Encrypted saves: the data-integrity HMAC key is hashSize bytes

MS-OFFCRYPTO §2.3.4.14 makes the HMAC salt as long as the hash (64 bytes for SHA-512); it was
16. Reading already accepted both.

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

* i18n: password dialog and File › Info strings in every catalog

Rows for the Password and Encrypt with Password dialogs, the Protect Document section of
File › Info and the password status messages in zh-hans, zh-hant, ja, pt-br, es, uk, sr and
sr-latn.

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

* Open and save password-protected Word documents (#55)

Opening a password-protected .docx used to fail as a broken zip and showed
a blank document. Encrypted packages (an OLE compound file with
EncryptionInfo + EncryptedPackage) are now detected and decrypted with
ECMA-376 agile encryption, written from the public [MS-OFFCRYPTO] spec:
password key derivation with spinCount, verifier check, data-integrity
HMAC, AES-CBC in 4096-byte segments (AES-128/192/256 with SHA-1/256/384/512
on read; AES-256 + SHA-512, spinCount 100000 on save). Standard (Office
2007) and certificate encryption give a clear "not supported" error.

- docx: crypt module (decrypt, encrypt, is_encrypted, read_with_password),
  PasswordRequired / WrongPassword / Encryption errors; hostile files are
  bounded (stream caps, spinCount <= 10,000,000, checked lengths).
- engine: file.open/file.save take "password"; file.encrypt
  {"password": string|null} sets or removes the save password (pure, so it
  never lands in Repeat or recorded macros); a document opened with a
  password is saved with it again; file.info reports "encrypted".
  Passwords are held in a zeroizing type whose Debug prints ***.
- ui: a password prompt when a user-opened file needs one (scripts get the
  error), File > Info > Protect Document with Encrypt with Password (typed
  twice) and Remove Password; web downloads are encrypted too.
- deps: aes, sha1, sha2, hmac, base64, getrandom (wasm_js in the browser),
  zeroize.

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

* Reland: catalog coverage 390/431 (90.5%) in ROADMAP and target-app parity

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

* Manage Styles: mark table styles with ⊞, which the interface fonts have (▦ has no glyph, #305's check)

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-10 16:16:35 -04:00
Valentin KleinandClaude Opus 5.5 4a9078b912 Grey out the AutoSave switch, with the reason, where AutoSave can't save (#196, #176) (#302)
AutoSave writes only a file saved in this session, in a format that keeps
everything (.docx, .docm, .dotx, .dotm, .odt, .rtf, .json), and never in the
browser, where saving is a download. The title-bar switch still showed on for
a saved .md file (#196) and AutoSave looked available in the web app (#176),
so edits stayed unsaved with no explanation.

WordApp::autosave_block() now says why AutoSave can't cover the document
(browser, never saved, a format that can't keep everything, opened but not
saved here). The switch is greyed out with that reason as its tooltip, and
clicking it offers Save As (desktop) so saving as .docx turns AutoSave on.
In the browser, File > Options' AutoSave checkbox is disabled with the same
explanation. file.autosave (scripts, MCP, the switch) now sets the app's
AutoSave and returns the reason as an error when asked to turn it on where it
can't save; turning it off always works. New strings in every catalog.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 15:37:22 -04:00
fc0c11a440 Shape Format: Shape Effects — shadow, glow and soft edges (#275) (#280)
Shapes and text boxes get an effects model (outer shadow with colour,
transparency, blur, distance and angle; glow with colour, size and
transparency; soft-edge radius), the shape.effects command (presets,
custom values or null per effect, applied to every selected shape,
undoable, hostile numbers clamped), screen and PDF rendering with the
same banded approximation of a blur, DOCX a:effectLst read/write
(outerShdw, glow, softEdge) with a matching wp:effectExtent, and a
contextual Shape Format tab with Shape Fill, Shape Outline and Shape
Effects (Shadow, Glow, Soft Edges submenus) plus Arrange.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 14:55:20 -04:00
5034dc517f Layout > Arrange > Group: group pictures, shapes and text boxes into one object (#267)
Shift+click (or Ctrl+click) adds pictures, shapes and text boxes to the
selection; Group (arrange.group) combines the selected floating objects
into one group object that moves, wraps and resizes as one, with its
members keeping their places and scaling with the group. Ungroup
(arrange.ungroup) splits it back, where the group showed them, and
leaves them selected so Group puts them back together.

- Model: InlineObject::Group with the members' coordinate space (like
  DrawingML a:chExt), so resizing the group scales its members.
- Layout draws the members inside the group's frame; hit testing
  selects the group; text boxes in a group show their text.
- DOCX writes wpg:wgp with a:chOff/a:chExt and each member's a:xfrm,
  and reads Word's groups (including inside mc:AlternateContent and
  nested groups, flattened).
- select.addObject makes multi-selection scriptable (pos or Selection
  Pane index).

Parity 378 -> 379 of 428 (88.6%); floor raised to 88.5%.

Fixes #265

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 14:55:00 -04:00
98bd70e117 Paragraph: Asian Typography settings, with kinsoku and mid-word Latin wrapping in layout (#269) (#271)
Paragraphs gain the Asian Typography flags (line-breaking rules, wrapping
Latin text in the middle of a word, hanging punctuation, compressing
punctuation at the start of a line, and automatic space between Asian and
Latin text and between Asian text and numbers). They inherit through styles,
round-trip through .docx pPr, are set by the new para.asianTypography
command and appear on a new Asian Typography tab of the Paragraph dialog.

Layout applies the kinsoku lists (no line starts with closing East Asian
punctuation or ends with opening punctuation), breaks between any East Asian
characters when kinsoku is off, and breaks Latin words anywhere when word
wrap is off. Text without East Asian characters breaks as before.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 14:54:42 -04:00
11a6ad60a5 Paste pictures from the system clipboard (screenshots, Copy Image, copied files) (#45) (#272)
egui's paste event only carries text, so a clipboard holding just a picture
sent no paste at all on Ctrl+V, and the ribbon/context-menu Paste only knew
WordCraft's own copy ("the clipboard is empty").

Desktop: the app reads the clipboard's picture through a new
Services::clipboard_picture (arboard, already in the build via egui-winit):
copied files first, else image pixels. Mod+V whose release arrives without a
paste event, and Paste from the ribbon or menus, insert that picture through
insert.picture (one undo step); text naming copied picture files inserts the
pictures. Pixel sizes are hostile: at most 16,000 px a side and 50 Mpx, the
buffer must match, files at most 200 MB.

Web: a document paste listener reads image files from the paste event when
there's no text and delivers them through the inbox like dropped pictures.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 14:54:28 -04:00
f186dda4d0 Manage Styles: list, preview, delete and show or hide styles (#270)
Home › Styles › Manage Styles (styles.manage) opens a dialog from the
Styles pane listing the document's paragraph, character, linked and table
styles, sorted as recommended or alphabetically, with a preview line and a
description (type, formatting it sets, what it is based on). From there:
Modify… and New Style… reuse the existing dialogs and come back, Delete
removes custom styles, and two checkboxes show the style in the Styles
gallery (w:qFormat) or hide it from style lists (w:semiHidden).

styles.manage returns the list for scripts (name, type, builtIn,
inGallery, hidden, basedOn, priority, own formatting). styles.delete now
moves paragraphs, runs and tables that used the style to its base (in
every story), rebases styles built on it and takes a custom linked
character half with it; built-in styles are refused and it is undoable.
New styles.setVisibility {style, gallery?, hidden?} sets the flags, which
round-trip through .docx.

Fixes #266

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 14:53:42 -04:00
68e6380a98 Table styles: every region, a live preview, delete, custom styles first (#146) (#263)
Follow-up to the custom table styles work: the New/Modify Table Style
dialog now covers every region a table style formats, shows what the
style will look like, and table styles can be deleted.

- engine: table.newStyle / table.modifyStyle take lastRow (total row),
  firstColumn, lastColumn and bandedColumns regions with the same props
  as the others. New table.deleteStyle removes a custom table style:
  built-in ones are refused, styles based on it are re-based on its own
  base with its formatting folded in (so they look the same), and tables
  using it fall back to Table Grid. Undoable like any command.
- doc/layout: table styles gain fill and cell borders for the first and
  last column and total row, and fill, text and borders for column
  bands; layout applies them in the standard order (column bands, row
  bands, first/last column, header/total row).
- docx: firstCol/lastCol/lastRow/band1Vert shading and cell borders are
  read and written.
- ui: the dialog's "Apply formatting to" lists all seven regions and a
  small live preview table shows the pending changes; Styles menu gets
  Delete Table Style (disabled for built-ins); the gallery shows the
  document's own table styles first.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 14:53:22 -04:00
bf64d17a16 Clipboard pane: collect the last 24 copies and cuts and paste any of them again (#264)
Copy and Cut now also keep each item (rich fragment plus plain text) in a
session list of up to 24, newest first. Items over ~4 MB (estimated) are
skipped and the list stays under ~24 MB in total; copying the same thing
twice in a row adds it once. The system clipboard is untouched.

New commands: edit.clipboardPane (toggle the pane, also the Clipboard
group's dialog launcher), edit.clipboardItems (list with previews),
edit.pasteClipboardItem {index}, edit.pasteAllClipboard,
edit.deleteClipboardItem {index} and edit.clearClipboard. Pasting goes
through the normal paste path, so it is one undo step and respects Track
Changes. The pane lists the items with a short preview; clicking one pastes
it at the caret, and each has Delete, plus Paste All and Clear All.

Parity: 378 -> 379 of 428 features.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-10 14:53:02 -04:00
08e8d4ade0 Table sizing: Height/Width boxes, AutoFit to contents and a Table Properties dialog (#287)
* Table sizing: Height/Width boxes, real AutoFit Contents, Table Properties dialog (#44)

- Table Layout > Cell Size gets Height and Width boxes (inches) for the caret cell's row
  and column; each edit runs table.rowHeight / table.columnWidth, a drag is one Undo.
- AutoFit Contents now measures every column's narrowest (longest word) and widest
  (unwrapped text) content with the layout's text shaping, shares the text width the way
  Word does, and writes the widths into the table grid and cell widths.
- Table Properties opens a dialog (table width, alignment, indent; row height and rule,
  break across pages, header row; column width; cell width and vertical alignment). OK
  applies only what changed as one table.properties step; with settings the command still
  works headlessly, and without them it reports the current values.
- table.columnWidth clears the table's preferred width so the column keeps the new size;
  table.rowHeight takes an optional exact rule.

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

* Progress docs: Table Properties dialog and AutoFit Contents landed (#44)

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

* Table sizing: saturating column-span sum in sync_cell_widths

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

* Merge fix: one region_text (main's) after #287 met the landed table-style code

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-10 14:52:08 -04:00
Valentin KleinandClaude Opus 5.5 4a75c57f58 Comment balloons edit in place, and the ruler drags page margins (#167) (#289)
Clicking a comment balloon in the markup area selects it like Word: its text
becomes editable in place, with Reply, Resolve/Reopen and Delete, and replies
are shown (and editable) under the comment they answer. Each edit is written
back through a new review.editComment command, one undo step per editing
session; the Comments pane uses the same path (it used to write the text
without an undo step) and gains a Reply button.

The margin edges on the horizontal ruler (left/right) and the vertical ruler
(top/bottom) can be dragged to set the section's margins through
layout.margins, one undo step per drag, snapped to 1/16 inch and clamped so
at least an inch of text remains. Indent markers keep priority where they
overlap an edge. Setting margins by number no longer switches off mirrored
margins; only a preset decides that.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:38:06 -04:00
Valentin KleinandClaude Opus 5.5 12a4da3be5 Make the floating header text box test independent of installed fonts (#284)
The test put a two-line header 36pt from the top of the page and checked
that the body still starts at the 72pt top margin. How tall those two
lines are depends on the font the default text resolves to: with the
installed Carlito-style fonts they fit, but with only the bundled
fallback fonts (as on the FreeBSD CI VM) they end at 72.6pt, so the
header correctly pushed the body down and the control check failed.

Start the header 18pt from the top instead, which leaves the two lines
plenty of room above the margin with any font. The checks are unchanged:
the control body starts at the top margin, the floating box changes
neither the body start nor the page count, and the same box inline
still pushes the body down.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:36:33 -04:00
Valentin KleinandClaude Opus 5.5 8622ae8cec Desktop File › Print opens the PDF in the system viewer (#15) (#286)
On desktop, the Print button on File › Print now renders the document
to PDF, writes it to a fresh wordcraft-print-*.pdf in the OS temp
folder and opens it without blocking the window: in Preview on macOS,
and in the default PDF app on Linux/BSD (xdg-open, gio, ...) and
Windows (via the `open` crate, already in the tree). The user prints
from there through the system print dialog. If no viewer can be
started, the status bar says so and the file is removed. Print files
older than an hour are cleaned up off the UI thread at start-up and
after each print.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:34:11 -04:00
Valentin KleinandClaude Opus 5.5 a99dddcbdc Show/Hide ¶: paragraph mark no longer overlaps text in mixed-direction paragraphs (#278)
* Show section breaks with Show/Hide ¶ (#42)

With formatting marks on, the last paragraph of a section now ends in a
dotted double rule with the break's name centred in it (Section Break
(Next Page), (Continuous), (Even Page), (Odd Page)) instead of a plain ¶.
The type shown is the start type of the section that follows. The mark
is screen-only: PDF export skips it, and print/PNG never turn marks on.

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

* Show/Hide ¶: put the paragraph mark past mixed-direction text (#274)

The paragraph mark (and the section-break mark) was anchored at the caret
position after the paragraph's logically last character. When that text
runs against the paragraph's direction (Hebrew ending a left-to-right
paragraph, Latin ending a right-to-left one) that position is inside the
line, so the mark was drawn on top of a letter. The mark now sits at the
line's visual end for the paragraph's direction: right of the rightmost
glyph in a left-to-right paragraph, left of the leftmost in a right-to-left
one. Pure left-to-right and pure right-to-left lines are unchanged.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:56:32 -04:00