* fix(windows): start 32-bit builds on OpenGL so launch no longer exits
DirectX 12 graphics pipeline creation fails on 32-bit Windows, so the app quit before the window appeared.
Co-authored-by: Cursor <cursoragent@cursor.com>
* Add @odedigo to the contributor names list
The About window credits this username; no real name or display name was chosen.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #687, #833: paste keeps other programs' formatting; Wayland clipboard pictures
- Paste (Ctrl+V, Home > Paste) reads the system clipboard's HTML, else RTF,
and keeps its formatting; our own copy still wins while the clipboard holds it.
Windows CF_HTML headers are understood; oversized or empty payloads fall back
to the text.
- Merge Formatting inserts the content in the destination's font and paragraph,
keeping bold, italic, underline and strikethrough (it did nothing before).
- Paste Special lists what the system clipboard holds (HTML, RTF, picture,
text); Mod+Alt+V opens it even with only a picture there.
- Services gain clipboard_html, clipboard_rtf and clipboard_formats hooks; the
host reads RTF on Windows (clipboard-win) and Wayland (wl-clipboard-rs).
- Linux: arboard's wayland-data-control feature, so pictures copied in a
browser or Spectacle reach a native Wayland window (KWin only syncs to
Xwayland while an X11 window has focus); other picture types are read
from Wayland directly. A paste that finds nothing usable names the formats
on the clipboard, or why its picture couldn't be read, instead of "empty".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #847: translate the Paste Special picture strings; Windows/macOS unused import
- `Picture` and `Pastes the clipboard's picture.` translated in all 15 interface catalogs.
- clipboard.rs: `MAX_FILE_BYTES` is used only by the Linux/Wayland module, so it is named
there; the top-level import was unused (a warning, and a clippy -D warnings error) on Windows
and macOS.
- Merged main (ROADMAP.md, docs/gaps.md, docs/file-format-parity.md, main.rs services: keeps
both the clipboard hooks and the default-app hook from #295). Cargo.lock adds only the
wl-clipboard-rs set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: Hungarian and French strings for this change
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M15: Polish interface, and Polish spelling, grammar and hyphenation
Interface
- Polish is the tenth interface language ("Polski", pl.tsv, 1,045 entries);
pl, pl-PL and pl_PL.UTF-8 system locales select it automatically.
- Right-click menu labels now go through translation in every language.
- Review > Language is a menu (English US/UK, Polski, Do not check spelling
or grammar); the status bar shows the language at the caret.
- With a Polish interface, new blank documents and opened .txt/.md files
default to pl-PL.
Proofing
- Each run is checked in its own language (w:lang, resolved through
styles): squiggles, F7, review.issues, suggestions and hyphenation.
Text in languages without proofing tools is no longer flagged as English.
- Polish spelling from the SJP.PL Hunspell dictionary (Apache-2.0 option),
read by a new Rust affix engine (crates/proof/src/affix.rs); 875 KB asset,
~4.5M word forms. Regenerate with the pl_dictionary example.
- Polish suggestions: case, missing diacritics, rz/ż, ó/u, ch/h confusions,
missing spaces (napewno -> na pewno), edit distance.
- Conservative Polish grammar rules: repeated words, spacing, doubled
punctuation, sentence capitals, missing comma before że/który/ponieważ/
aby/żeby/jeśli…, we/ze forms.
- Polish hyphenation with the hyph-utf8 hyph-pl.tex patterns (MIT option);
pattern keys widened to 20 letters, TeX exceptions parsed; the heuristic
no longer treats ł, ż, ś… as vowels.
review.language validates and normalizes tags, can set noProof alone and
the document default; format.state reports lang and noProof.
Tests for every part, docs and ATTRIBUTION.md/NOTICE updated;
cargo xtask ci passes (fmt, clippy, test, assets, layers, wasm).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Layout: rustfmt the test module order left unformatted by #391
cargo fmt --check failed on main after #391 added mod tests_grid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #346: read Polish spelling from the installed Hunspell dictionary
- Polish spelling uses the Hunspell pl_PL dictionary installed on the
system (OS packages, Homebrew, LibreOffice, ~/.local/share/hunspell, or
WORDCRAFT_DICTIONARIES) instead of a bundled 870 KB SJP.PL file;
without one, Polish text gets no spelling marks, like Word without the
language's proofing tools. .aff/.dic are size-capped and decoded from
UTF-8 or ISO 8859-2 (crates/proof/src/hunspell.rs).
- The dictionary can be replaced at runtime (install_dictionary) or
re-searched (reload_dictionary); an empty one accepts every word.
- Tests use small synthetic dictionaries written for them, so they don't
depend on what the machine has installed.
- The bundled pl.dic, its ATTRIBUTION/NOTICE entries and the generator
example are removed; docs describe the installed dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #100: affix — accept FLAG UTF-8 and DEFAULT flag modes
Per-character flag modes are what the reader already parses; only the
multi-character encodings (long, num) stay errors. VERO pt_BR declares
FLAG UTF-8.
* #100: affix — widen flag masks to u128 (128 flag capacity)
VERO pt_BR needs 106 distinct flags; the u64 bitmask capped at 64.
Serialization is unaffected: WCAFF1 stores flag characters and
re-derives the masks on load.
* #100: affix — support FORBIDDENWORD
VERO pt_BR marks spellings it must reject with a forbidden flag (ý).
Stems carrying it are listed but never correct and never inflect.
* #100: affix — continuation classes in check
SFX A 0 able/B: the form derived by A carries flag B, licensing a
further suffix of class B (workable -> workables) while the bare stem
cannot take one. form_has_flag walks the chain depth-bounded
(MAX_CHAIN = 3); with no continuations declared it degenerates to the
stem lookup, so existing dictionaries are unaffected. VERO pt_BR uses
26 continuation rules.
* #100: affix — continuation classes in forms
Enumerate chained suffix forms (workable -> workables) under the same
MAX_CHAIN bound the checker walks, so every form the checker accepts
is listed and vice versa.
* #100: affix — round-trip continuations and FORBIDDENWORD through WCAFF1
The compact format keeps its WCAFF1 magic: continuation classes ride
on the rule's appended field (as in the .aff source) and the forbidden
flag becomes a FORBIDDENWORD line. Old files read as before.
* #100: affix — strip a UTF-8 BOM from .aff/.dic input
VERO's files open with a BOM; glued to the count line it parsed as a
stem (one garbage entry per load) and to SET it hid the directive.
* #782: affix — the module doc still rejected what the code now reads
Continuation classes and FORBIDDENWORD are supported; the header said
Rejected for both.
* #100: pt — Portuguese spelling, following the Polish module
ProofLang::Pt resolves pt/pt-BR/pt-PT/por tags; the pt module reads the
installed Hunspell pt_BR dictionary (VERO) through the affix engine —
FLAG UTF-8 flags, continuation classes and FORBIDDENWORD spellings all
exercised by the synthetic test dictionary. Hyphenated compounds are
checked part by part (guarda-roupa, ex-marido); without a dictionary
installed, Portuguese text gets no marks. Spelling message in
Portuguese; suggestions and grammar land next.
* #100: pt — Portuguese suggestions
Rounds of rising cost, driven by the dictionary's own tables: missing
accents from MAP (coraçao -> coração, saude -> saúde), the REP
confusions (seje -> seja, excessão -> exceção), a missing space after a
short function word (nãoé -> não é, dagua -> da água), one typo over
TRY, and an accent-folding stem scan — the Polish rounds, retuned for
Portuguese.
* #100: pt — Portuguese grammar flags
Conservative rules with Portuguese messages: repeated words (except
the emphatic doubles Portuguese allows), space before punctuation and
double spaces, sentence capitals knowing the Portuguese abbreviations
and dialogue dashes, and doubled punctuation. No comma rule: que opens
a clause only sometimes, so a wrong flag would cost more than a missed
one.
* #100: pt — Portuguese hyphenation
hyph-utf8's hyph-pt.tex bundled unmodified (BSD 3-clause, ATTRIBUTION
row with source and hash): 426 Liang patterns and 2 exception words;
falls back to the generic heuristic outside their reach.
* #100: pt — Review › Language offers Português (Brasil), and a pt-br interface writes pt-BR
The menu's fourth entry sets the run language; the status bar names
Portuguese; editing_lang is now allowed for pt-br (wordcraft-proof
proofs it), so new documents under a Portuguese interface are
Portuguese by default.
* #100: pt — engine test: Portuguese proofing through the review commands
review.language pt-BR normalizes the tag, review.issues reports
Portuguese spelling and grammar with the lang field, F7 and Change
apply a suggestion, Add to Dictionary works for Portuguese.
* #100: pt — Portuguese in the mixed-language proofing tests and docs
check_text proofs a Portuguese span with the Portuguese message; the
never-panic loops and the module docs cover the third language.
* #100: docs — Portuguese proofing in the gap, parity and roadmap docs
Gap 8 narrowed (English, Polish and Portuguese), localization parity
gains a Portuguese row and a revision entry, target-app-parity's
Proofing row moves 40% -> 42%, and the ROADMAP progress log records
what landed.
* #100: hunspell — LibreOffice extension folders follow the language
The search paths hardcoded LibreOffice's dict-pl; a pt_BR user whose
only dictionary is LibreOffice's dict-pt-BR extension — on Windows the
only realistic on-disk source — got silently no spelling. Both the
primary-language folder and the dashed full name are now tried, so
Polish keeps dict-pl and Portuguese finds dict-pt-BR.
* #100: pt — review fixes
Suggestion ties break toward the change nearest the word's start
(saúde before saudê, proven against a fixture that has both); the
Language menu's label is the English source "Portuguese (Brazil)" with
translations in all twelve catalogs (as Polish is); the attribution
counts pt.pat's 2 exception words; the pt fixture drops a duplicate
stem; docs/roadmap.md catches up with the landing; the throwaway spike
is gone.
* Review fixes for #782: two-suffix chain limit, module doc
- MAX_CHAIN = 2 counts suffixes in a row, matching Hunspell's two-suffix limit
(the old bound of 3 allowed up to four); test pins that a third is rejected.
- Module doc: say probes allocate small scratch strings instead of claiming none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #797: NOTICE for pt.pat, no indexing in the LibreOffice folder helper
- NOTICE reproduces hyph-pt.tex's BSD 3-Clause copyright notice, conditions and
disclaimer, which the licence requires for binary redistributions.
- hunspell::lo_ext_dirs builds its list without indexing (never-crash rule).
- Catalogs: main already translates "Portuguese (Brazil)" in every language, so the
duplicate rows the branch added are dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: witkowski01 <konradwitkowski01@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Persist the custom dictionary across sessions (#40)
* Review fixes for #796: session-only Ignore All, desktop-only persistence, capped atomic I/O
- review.ignoreAll no longer adds to (and saves) the custom dictionary: ignored words go to a
separate session-only list, as Ignore All does in Word.
- Only the desktop app wires the dictionary file. Headless MCP and `wordcraft-cli run` keep
words in memory, so an agent never reads or changes the person's dictionary (and nothing
writes outside an automation write root, #769).
- Loading reads through `take(1 MB)`, so a huge file is never read whole; lines that aren't a
single word (spaces, control characters, invalid UTF-8, over 64 characters) are skipped.
- review.addToDictionary applies the same word rule (proof::custom_word), so a word added is
never dropped on the next load.
- Saving merges the words on disk first (another process's additions survive; words just
removed stay out), creates the folder, and replaces the file atomically (temp + rename).
The file I/O lives in the proof crate, so the engine doesn't touch std::fs itself.
- Tests for each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Keep inherited complex-script size, bold and italic independent from Latin
properties and retain explicit values during DOCX round trips. Preserve
ordinary formatting-command behavior and add regression coverage.
Resolve eight existing Clippy warnings without changing behavior so all
six required CI gates pass.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M15: Polish interface, and Polish spelling, grammar and hyphenation
Interface
- Polish is the tenth interface language ("Polski", pl.tsv, 1,045 entries);
pl, pl-PL and pl_PL.UTF-8 system locales select it automatically.
- Right-click menu labels now go through translation in every language.
- Review > Language is a menu (English US/UK, Polski, Do not check spelling
or grammar); the status bar shows the language at the caret.
- With a Polish interface, new blank documents and opened .txt/.md files
default to pl-PL.
Proofing
- Each run is checked in its own language (w:lang, resolved through
styles): squiggles, F7, review.issues, suggestions and hyphenation.
Text in languages without proofing tools is no longer flagged as English.
- Polish spelling from the SJP.PL Hunspell dictionary (Apache-2.0 option),
read by a new Rust affix engine (crates/proof/src/affix.rs); 875 KB asset,
~4.5M word forms. Regenerate with the pl_dictionary example.
- Polish suggestions: case, missing diacritics, rz/ż, ó/u, ch/h confusions,
missing spaces (napewno -> na pewno), edit distance.
- Conservative Polish grammar rules: repeated words, spacing, doubled
punctuation, sentence capitals, missing comma before że/który/ponieważ/
aby/żeby/jeśli…, we/ze forms.
- Polish hyphenation with the hyph-utf8 hyph-pl.tex patterns (MIT option);
pattern keys widened to 20 letters, TeX exceptions parsed; the heuristic
no longer treats ł, ż, ś… as vowels.
review.language validates and normalizes tags, can set noProof alone and
the document default; format.state reports lang and noProof.
Tests for every part, docs and ATTRIBUTION.md/NOTICE updated;
cargo xtask ci passes (fmt, clippy, test, assets, layers, wasm).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Layout: rustfmt the test module order left unformatted by #391
cargo fmt --check failed on main after #391 added mod tests_grid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #346: read Polish spelling from the installed Hunspell dictionary
- Polish spelling uses the Hunspell pl_PL dictionary installed on the
system (OS packages, Homebrew, LibreOffice, ~/.local/share/hunspell, or
WORDCRAFT_DICTIONARIES) instead of a bundled 870 KB SJP.PL file;
without one, Polish text gets no spelling marks, like Word without the
language's proofing tools. .aff/.dic are size-capped and decoded from
UTF-8 or ISO 8859-2 (crates/proof/src/hunspell.rs).
- The dictionary can be replaced at runtime (install_dictionary) or
re-searched (reload_dictionary); an empty one accepts every word.
- Tests use small synthetic dictionaries written for them, so they don't
depend on what the machine has installed.
- The bundled pl.dic, its ATTRIBUTION/NOTICE entries and the generator
example are removed; docs describe the installed dictionary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* M15: Polish catalog covers the interface text merged from main
The merge of main (98456ac) brought ~575 new interface strings (charts,
SmartArt, content controls, Developer tab, AutoCorrect, outline view,
protection, compare, print, accessibility, references and Language/
Hyphenation dialogs, Draw additions). pl.tsv lacked them, so
polish_covers_every_key_of_every_other_catalog and
every_tab_and_command_is_translated failed. All are now translated.
cargo xtask ci: all 6 steps pass.
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>
* Add German (de) interface language
New catalog crates/ui-egui/src/i18n/de.tsv (1,307 entries), key set
byte-identical to zh-hans.tsv; original translations written from the
meaning of the English UI strings in standard German word-processor
vocabulary; MIT OR Apache-2.0.
- LangInfo for de (Deutsch) registered in i18n::LANGUAGES
- Tests: German locale resolution (de, de-DE, de-AT, de-CH,
de_DE.UTF-8@euro), saved-preference/document-content isolation,
catalog key-set parity with zh-hans, count-neutral wording,
bundled-font glyph coverage for ÄäÖöÜüß
- Count strings use count-neutral wording (Wörter: {n}), the
convention the other catalogs' tests pin
- Docs: localization-parity.md, gaps.md, roadmap.md, ROADMAP.md,
README.md, control-protocol.md; ATTRIBUTION.md row for de.tsv
cargo test -p wordcraft-ui-egui i18n: 28 passed; --lib: 162 passed.
* rustfmt after merging main
* German: translate interface strings added on main since this PR
Adds 636 German entries so de.tsv again has the full zh-hans key set
(2,021 entries) and drops the stale "Wildcards (regex)" row; updates the
documented entry count.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fourteen interface languages with German and Italian
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <brandon@storyteller.ai>
* .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>
Like Word: the pointer turns into a resize arrow over a column border or a
row's bottom border, and a small handle shows past the table's bottom-right
corner. Dragging shows a dashed guide; releasing runs the new
`table.resizeDrag` command (page coordinates), so a drag is one undo step
and the selection is kept. Esc cancels a drag.
- Column border: the right neighbour gives or takes the difference, so the
table keeps its width; Shift, or the table's right edge, changes only the
column on the left. Columns stay at least 6 pt wide.
- Row border: sets the row's "at least" height; the margins and border bands
the layout adds are measured and taken off, so the row is drawn at the
dragged height.
- Corner handle: scales every column and the rows on that page.
- Widths come from the drawn layout, so autofit and percentage widths don't
make the result jump; a merged cell hides the border it covers.
- Borders are hit-tested in the existing canvas mouse handler, only within a
few pixels of a border, so clicks in cells still place the caret.
Engine: crates/engine/src/cmd/table_resize.rs (7 tests).
UI: crates/ui-egui/src/table_resize.rs, wired into canvas.rs; kittest tests
drive real pointer events through the whole window (one undo step per drag,
selection kept, clicks in cells still move the caret, Esc cancels).
The command label is translated in all nine interface catalogs.
Progress docs updated: gaps, ui-parity, target-app-parity, ROADMAP.
`cargo xtask ci` passes (fmt, clippy, test, assets, layers, wasm).
Co-authored-by: rabis <340973947+rabis7575@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Options: Animation effects setting that follows the system
The app eased wheel scrolling, Ctrl+wheel zoom and its fades whatever the
system's animation setting was: with Windows' Animation effects off, the
page still smooth-scrolled.
File > Options > Accessibility > Animation effects: Use system setting
(the default), On or Off; `ui.animations` for agents; kept in ui.json.
- Off: a wheel notch scrolls the page its whole distance at once, egui's
own scroll areas get the notch whole instead of eased in, Ctrl+wheel
zoom steps at once, and egui's fades and scroll-to glides are off.
Touchpad momentum and the caret blink are unchanged.
- Use system setting asks the host (`Services::animations_enabled`) at
start-up and when the window regains focus. Windows: the Windows
Runtime's UISettings, a safe API of the `windows` crate already in the
build. Web: `prefers-reduced-motion`. macOS and Linux/BSD aren't read
yet (docs/gaps.md); there it means on.
- The eight new strings are in all eleven interface catalogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Animation effects off: zoom steps land finished in one frame
Follow-up to the Animation effects setting: zooming could still look
animated with the setting off.
- Ctrl+wheel zooms in whole steps, each at once. A mouse-wheel notch is
two steps in its frame, the same zoom as before. Turns that arrive in
small pieces (a touchpad pinch that Windows reports as Ctrl+wheel, a
high-resolution or free-spinning wheel) add up to a step of about 10%
first, where each piece used to zoom a little and the zoom glided.
- A page re-rendered for a zoom step is drawn on the pixel grid in that
frame. It used to be drawn off the grid, slightly soft, and snap sharp
a frame later.
- The frame a zoom changes in asks for the next one at once, so the
status bar's percentage and slider follow in the next frame. They
could lag until the caret's next blink, up to half a second.
The last two apply whatever the setting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Animation effects: follow the system's setting on macOS, Linux and the BSDs
"Use system setting" only read Windows and the browser; elsewhere it
meant on.
- macOS: System Settings > Accessibility > Display > Reduce motion,
through NSWorkspace (a safe call in objc2-app-kit 0.3, already in the
build).
- Linux and the BSDs: the XDG settings portal over D-Bus (zbus, already
in the build through AccessKit). Three settings say whether animations
play, and any one saying no turns them off: the cross-desktop
reduced-motion preference, GNOME's enable-animations and KDE Plasma's
animation speed. A thread reads them at start-up and follows the
portal's change signal. Where no portal answers, or it knows none of
them, KDE's kdeglobals and GTK's settings.ini are read instead.
- The UI now asks the host every frame instead of at start-up and on
focus, so a change in the system's settings is followed while the app
runs. Hosts answer from memory: Windows and macOS are asked again at
most once a second, the web keeps its media query, and Linux/BSD is
kept current by its thread.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Animation setting on Linux: ask every setting again when one changes
KDE's portal announces a new animation speed only as the cross-desktop
reduced-motion preference, not under its own kdeglobals name. Keeping
each setting's last announced value left KDE's own one stale: after
going from instant back to normal speed, animations stayed off.
Every setting is now read afresh on any relevant change, and the
settings files' answer stands whenever the portal has none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Caret glide follows Animation effects; its canvas test turns the setting off, then on
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- 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>
- 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>
- 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>
* 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>
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>
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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
- 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>
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>
- 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>
- 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>
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>
* 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>
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>
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>
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>
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>
* 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>
* 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>
* 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>
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>
* 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>
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>
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>
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>
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>