mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-28 12:27:11 -06:00
68402f5b96c99643c803aa516fd2ea76a4e109f3
23
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3d8f8144d9 |
File: recover unsaved work after a crash (#518) (#586)
* M10: retain local crash recovery on current main * i18n: Backstage Recover in every catalog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
37d41150e6 |
Track Changes and content controls: table delete, Change Case and Unlink Fields respect them (#1329) (#1514)
* #1329: WIP * Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
8ca285ca9d |
Tables: select, insert and move with the mouse; double-click a border to AutoFit (#637)
* Tables: select, insert and move with the mouse; double-click a border to AutoFit Like Word, on top of the border resizing: - Selection areas: just above a column the pointer becomes a down arrow, left of a row the row arrow, along a cell's left edge (inside it) the cell arrow. A click selects the column, row or cell; a drag selects every one up to the pointer. Selected cells are shaded as cells, empty ones too. - Insert buttons: left of a row border and above a column border a "+" shows with the border marked; a click adds a row or column there and puts the caret in it. - Move handle past the table's top-left corner: a click selects the table; a drag shows a dashed outline and, on release, the table floats where it is dropped with the text wrapping around it (Word's behaviour). One undo step. Nested tables and tables outside the main text are only selected. - A double-click on a column border fits the column left of it to its contents (the right neighbour gives or takes the room; on the right edge the table's width changes, up to the right margin). - Table Layout > Select > Select Column (catalog 418/449). Whole-cell selection: a column (or a rectangle of cells over several rows) is not one run of the text, so selecting it makes a block of its cells' paragraphs (`CellRange` on the existing `ColumnBlock`). Bold, alignment, Delete, typing, copy and numbering act on those cells only (a numbered column is one list); table commands (shading, merge, delete columns) take the rectangle; Tab moves on without clearing it; Undo brings the selection back. Cells of one row, or whole rows, stay an ordinary stream selection. The format painter no longer clears a block's text (edit.pasteFormat was handled like a paste). The mini toolbar stays hidden over whole-cell selections and during these drags: pinned above the selection it covered the column areas, the "+" buttons and the move handle. Engine: crates/engine/src/cmd/table_mouse.rs - table.selectAt, table.insertAt, table.moveDrag, table.fitColumn (page coordinates, so agents and the control channel can do the same) and table.selectColumn; hit testing reuses table_resize's drawn geometry, and borders keep priority for resizing. 10 engine tests (areas, a column edited cell by cell, numbering, rows/cells/drags, insert buttons, move, fit, format painter, hostile params). UI: crates/ui-egui/src/table_mouse.rs, wired into the existing canvas mouse handler (no extra ui.interact areas, so clicks in a cell's text still place the caret); kittest tests with real pointer events: column click, row drag, "+" button, move-handle drag in one undo step, double-click fit. New labels translated in all eleven catalogs. Progress docs updated: gaps, ui-parity, target-app-parity, ROADMAP, parity checklist. `cargo xtask ci` passes (fmt, clippy, test, assets, layers, wasm). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tables: a selected column's word count is its cells'; merged cells shade once With whole cells selected (a column, or a rectangle that isn't one run of the text) the status bar said "12 of 23 words": `Session::selected_text` took everything between the selection's ends. It now takes each piece's own text, a line each, for a column block too, so the status bar, Word Count and the other callers see what is selected. Since #669 a vertically merged cell is one box as tall as its rows, with a `covered` box for each continuation row; shading selected cells skips those, so a merged cell isn't shaded twice. `selected_paths` follows main's `para_paths_in` (#418): a selection ending at the start of the next paragraph doesn't take it. - Test: a selected column's text is its three cells' and Word Count says three words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tables: double-click AutoFit and the "+" buttons work as in Word Checked against Word (2019, Windows): - A double-click on a column border fits the column left of it to its contents and the columns right of it keep their widths, moving with the border, so the table narrows or widens (up to the right margin). The right neighbour used to give or take the difference. - A "+" insert button selects the new row or column (the caret used to go into it); typing then goes into its first cell. If the table can't take another row or column, nothing changes. - A double-click along a cell's left edge (the cell selection area) selects its row. - Tests: engine (fit keeps the columns on the right; insert selects the new row or column, typing lands in its first cell) and kittest (real double-click on a border; "+" selects the row; double-click by a cell's edge selects the row). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- 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> |
||
|
|
55bfc33418 |
Undo history list beside the Quick Access Toolbar's Undo (#701) (#855)
* #701: Undo history list beside the Quick Access Toolbar's Undo A drop-down arrow next to Undo lists recent steps, newest first; hovering a row highlights it and every newer step, the footer reads "Undo N Actions" (Cancel when none is highlighted), and a click undoes them all at once. Engine: Session::undo_history (newest first, capped at UNDO_LIST_MAX = 100) and Session::undo_steps; edit.undoTo {"count": n} (0, negative and non-number counts rejected, counts past the depth clamped, stops at protection) and the read-only edit.undoHistory for CLI and MCP. Catalog: Undo List. 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> * #701: Bulgarian for the Undo history strings; drop a duplicate Turkish entry 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> |
||
|
|
00ed50515e |
New documents follow the system region: A4, centimetres and decimal comma outside the US (#838) (#857)
* #838: new documents follow the system region (A4/cm/decimal comma outside the US) - geom: Region (from a locale tag; CLDR Letter and US-measurement territories, decimal separator), Unit::format_with; parse_measure takes either separator and a Unicode minus. - engine: Session.region (plain value, en-US headless), Session::units() (explicit Measurement units choice wins, else the region's), blank_document(); file.new blank uses the region's paper; file.options takes "region" and units "auto" and reports unitsFromRegion/region. Prefs.units is now Option and saved as measurementUnits (old saved values were the inch default, not a choice). - ui: region from WORDCRAFT_REGION, LC_PAPER/LC_MEASUREMENT/LC_NUMERIC, the platform locale (browser on the web) and Windows' home location; length fields show and read the region's decimal separator (MeasureExt); Size menu lists sizes in the measurement unit. - Tests: engine new_documents_follow_the_region, geom region/format/parse, ui length_fields_follow_the_region. Docs: localization parity, roadmap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Review fixes for #857: keep a measurement unit chosen before the rename Settings saved before #838 kept the unit under `editing.units`, defaulting to inches. On load, a value there other than inches was the user's own choice, so it now carries over to `measurementUnits` (unless that key is already present); inches, junk and non-strings follow the region. Test: `a_unit_chosen_before_regions_survives_the_rename`. Also merged main (ROADMAP.md, engine lib.rs re-exports: `blank_document` and main's `set_system_locale`). The Windows region code (`GlobalizationPreferences::HomeGeographicRegion`) was cross-checked with clippy -D warnings for x86_64-pc-windows-gnu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #838 review: saved region choice, Unix conventions apart, the browser's locale - File › Options › Advanced › Region: Automatic (the system's) or a chosen region, saved with the preferences (`Prefs::region`; `file.options {"region": "pl-PL" | "auto"}`). The session keeps the platform's region as `system_region`; `Session::region()` is the one in effect. Measurement units gains an Automatic entry (follows the region). - Unix: numbers follow LC_NUMERIC (else LANG), paper LC_PAPER and measurement LC_MEASUREMENT, each on its own (`Region::with_paper_and_measurement`); WORDCRAFT_REGION and LC_ALL still set all three. - Web: sys-locale's `js` feature, so the browser's language reaches the region (and the interface language, as its comment already said). - Runs without preferences (WORDCRAFT_NO_PREFS) stay en-US unless WORDCRAFT_REGION is set. - docs/localization-parity.md: where the region comes from, the Windows home location vs. Word's regional format, and where the decimal separator comes from. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #838 review: rustfmt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #838: rustfmt after merging main 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> |
||
|
|
64319e0039 |
MCP: confine file access with --automation-read-root and --automation-write-root (#769) (#798)
* Engine: check automation read and write roots on every path a command reads or writes (#769) * MCP: confine the headless session to the automation roots (#769) * CLI: add --automation-read-root and --automation-write-root to mcp (#769) * Docs: document --automation-read-root and --automation-write-root for mcp (#769) * Review fixes for #798: confine file.printPdf; guard all of engine/src - print.rs::print_pdf_to_file (file.printPdf, added on main) wrote with std::fs::write directly, so a confined agent could print a PDF anywhere. It now goes through io::write_file, which checks the automation write root. - The guard test now walks every file under crates/engine/src (not only cmd/), except io.rs, file_access.rs and the tests*.rs modules, and also flags OpenOptions. - Tests: an engine test that file.printPdf is refused outside the write root, works inside and is unchanged without roots; the MCP integration test covers it too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Review fixes for #798: list file.printPdf among the confined writes in docs/mcp.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Font files added by path go through io, inside the automation read root (#769) --------- Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2f58d441b6 |
Numeral display modes: Arabic, Hindi, Context and System digits (view.numeral) (#501)
* Numeral display modes: Arabic, Hindi, Context and System digits Display-only digit substitution (Word File > Options > Advanced > Numeral): ASCII and Arabic-Indic digits are substituted before shaping while clusters keep indexing the original text, so find, spelling, copy/paste and saved files see the typed digits. - layout: NumeralMode (arabic|hindi|context|system) with System resolved from LC_ALL/LANG, threaded through LayoutOptions/ParaEnv with cache invalidation; Context follows the surrounding text - engine: view.numeral command + catalog row - tests: layout tests_numeral, engine command test * Options page: Numerals picker in File > Options > Display ComboBox for the view.numeral mode (Arabic/Hindi/Context/System), mirroring Word's File > Options > Advanced > Numeral. Display-only, session-scoped like the other Display options. * fmt: collapse numeral direction branch * #501: translate the Numerals option label in every catalog Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: abdo4312 <abdo4312@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1b96e04b2c |
Add fonts at run time: Add Font…, drop a font file, font.add (#582) (#668)
* Add fonts at run time: Add Font…, drop a font file, font.add (#582) Rebased onto main as one commit (the branch had merge commits from main). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * #582: font test no longer depends on the system's fallback font The FreeBSD runner has no system fonts, so a missing family fell back to Source Serif 4, the font the test compared against. The test now adds a renamed JetBrains Mono under a name without Serif/Sans/Mono (which picks a fallback of that kind), with narrow-letter sample text: monospaced, it measures unlike any proportional fallback. 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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
15f5ad274e |
Column Selection: Alt+drag selects a block of text (#237)
select.column (Mod+Shift+F8) selects the same x range on a run of lines, from two positions, two page points (Alt+drag), or by toggling column mode where the arrow keys extend the block. Copy/Cut give one line per row, Delete/Backspace remove only the block, character formatting and Change Case apply per row, typing/paste replace the block. Any ordinary caret move drops it. F8 keys now reach their shortcuts in the UI. 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> |
||
|
|
5d86dec250 |
Right-to-left and Persian text support (bidi, shaping, RTL paragraphs, DOCX) (#207)
* feat(doc,docx): complex-script run properties and RTL OOXML round-trip Model the formatting Word applies to Persian, Arabic and Hebrew text: cs, font_cs, size_cs, bold_cs, italic_cs and lang_bidi on CharProps, resolved with Word's fallbacks (ResolvedChar::complex). Size, bold and italic pair with their complex-script values so a run's size or bold also covers its Persian characters. Add doc::bidi script helpers, and keep ZWNJ and Arabic marks inside words for word selection. DOCX: read and write w:rFonts/@w:cs, w:szCs, w:bCs, w:iCs, w:cs and w:lang/@w:bidi in schema order; stop using the cs font as the Latin font. Tests round-trip Persian text with ZWNJ, digits and mixed Latin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(fonts): directional shaping and Persian font fallback Add shape_run to shape a run in a known bidi direction (Arabic joining, mirrored brackets). Prefer Persian-capable fonts (Vazirmatn, Noto Naskh/ Sans Arabic…) for right-to-left fallback, pick bold/italic fallback faces, and map common Persian document fonts (B Nazanin, B Lotus, B Titr, IRANSans…) to open substitutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(layout): Unicode Bidirectional Algorithm and right-to-left paragraphs Run UAX #9 per paragraph (unicode-bidi), shape each level run in its direction, keep marks and ZWNJ with their letter and a word in one fallback face. After line breaking, apply rules L1/L2 per line and give each cluster a visual box; right-to-left paragraphs are laid out from their start edge and mirrored, so indents, tabs, alignment, list labels, drop caps, floats and borders sit on the right. Caret, hit testing and selection use visual geometry (split selections, caret at bidi boundaries, DocLayout::visual_step for arrow keys). Display items carry glyph-to-text ranges, which the PDF writer now uses. Plain left-to-right lines are unchanged. No hyphenation of RTL words. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(engine,ui): paragraph direction commands and RTL editing Add para.ltr / para.rtl (Word's text direction buttons) that change only the reading order, and make the alignment buttons visual in right-to-left paragraphs (para.align also takes start/end). Arrow keys move visually in bidirectional text, Ctrl+arrows follow reading order, and Bold/Italic/ Size/Font cover complex-script text. Ribbon buttons with code-drawn icons, a mirrored ruler for RTL paragraphs, translations, catalog, parity and roadmap updates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(contributors): add Saeed Azarehgar to people.toml Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(engine): para.align with an explicit value sets it, without toggling Only the alignment buttons toggle back to the start edge when pressed again. para.align (used by the Paragraph dialog, scripts and agents) now sets the requested alignment, as seen on the page, in each selected paragraph's own direction. Previously pressing OK in the dialog with Center or Right selected reset the paragraph to the start edge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(ui): Direction option in the Paragraph dialog Add Word's Direction (Right-to-left / Left-to-right) to the Paragraph dialog's General section. The dialog now shows alignment and Left/Right indents as seen on the page, mapping them to the paragraph's logical start/end values on OK, so an unchanged OK no longer flips a right-to-left paragraph. OK logic moved to apply_paragraph, with tests; ja/zh translations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(layout): allow hanging trailing spaces in the RTL display test A wrapped right-to-left line's trailing spaces hang past its end edge (the left margin), as left-to-right lines hang them past the right. The test failed whenever the loaded fallback fonts made a line end there; it now checks only visible glyphs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a9eb233a9d |
Track changes: keep other authors' deletions, credit paragraph splits, exact edit snapshots (split from #54) (#125)
* Track changes: a tracked delete keeps other authors' deletions Deleting a range with Track Changes on marked every run in it as deleted by the current author, also text another author had already deleted. That author's deletion was lost: the change list showed it under the wrong name, and rejecting the second author's changes brought back text the first author had deleted. Text that is already deleted now keeps its deletion and its author, like Word. * Track changes: a tracked paragraph split credits the new mark to its author Pressing Enter with Track Changes on inserts a paragraph mark by the current author, but the paragraph after the split took its mark's revisions from the text at the caret. Inside or at the end of another author's insertion, the original paragraph mark then showed as inserted by that author. The paragraph after the split now keeps the original mark's revisions; only the new mark is the splitting author's. * Engine: edit snapshots that take commands back exactly Session::edit_snapshot and Session::restore let a caller run a command and take it back without a trace: the document, the selection, the undo and redo stacks, the open typing group, a pending join_next_undo and the dirty flag come back as they were, and the layout is recomputed. Edit > Undo cannot do this: it leaves a redo step, loses the redo stack the command cleared, and closes the typing group. At the undo limit (500 steps) a command pushes the oldest step out. The snapshot keeps the oldest steps when the stack is near the limit and restore puts them back, so the stack is exact there too (up to four commands between snapshot and restore). Restore is exact for commands that add undo steps; edit.undo and file.new/file.open in between are outside its contract (documented). The snapshot destructures every Session field, so a new field does not build until someone decides whether a snapshot must cover it. Session::undo_depth and the snapshot's doc/dirty/undo_depth accessors tell a caller whether the command changed anything. * Tests: hostile_params_never_panic leaves out review.readAloud Read Aloud hands the document's text to the system's speech synthesizer (say, PowerShell, spd-say or espeak), so running the test made the computer speak the sample document. Like the file commands, it is left out of the sweep. * Undo: evictions at the limit are counted everywhere edit snapshots rely on them Merging main brought two paths #125's eviction counter didn't know: push_undo (typing parity, #204) dropped the oldest step without counting it, and a failed command (#153) put the whole stacks back but left the count raised. Either made restore() bring back a step that had never left. Both now keep the count right; restore_is_exact_at_the_undo_limit covers each (both fail without the fix). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: lpgn <2556951+lpgn@users.noreply.github.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
10eb8798cd |
Equations: Word (OMML) import, layout and save, plus an equation editor like Word's (#191)
* M5.5: Word equations (OMML) import, lay out and save like Word Equations were flattened to plain text on import. Now OMML is read into a math tree (fractions, scripts, radicals, n-ary operators, delimiters, functions, limits, accents, bars, boxes, equation arrays, matrices, phantoms), laid out with the OpenType MATH table (Cambria Math when installed, else STIX Two Math and other open math fonts), drawn to screen and PDF, and written back as the original OMML. Display equations sit on their own centred line. insert.equation parses the linear format into structure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M5.5: equation editor like Word's — Equation tab, in-place editing, # numbers Insert › Equation now opens an equation for in-place editing, with the Equation tab: ready-made equations, Unicode/LaTeX input and conversion (professional ↔ linear), Normal Text, eight symbol sets, and the eleven structure galleries (fraction, script, radical, integral, large operator, bracket, function, accent, limit and log, operator, matrix) with tiles rendered by the math engine. Typing builds up like Word (a/b, x^2, √, ∑… then space), Math AutoCorrect turns \alpha, \sqrt… into symbols, Tab moves between empty slots, arrows/Backspace work through structures, and the right-click menu has structure commands (fraction type, radical degree, limit position, matrix rows/columns, remove brackets…). A trailing #(n) numbers a display equation with the number at the right margin; # alone numbers it automatically. DOCX output keeps numbers Word-compatible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * i18n: equation editor strings in Spanish, Brazilian Portuguese and Ukrainian The catalogs that landed on main after #191 was opened lacked the Equation tab and equation command labels. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Equations: our own names and order for the built-in equations Clean room: the ready-made equation list used another product's gallery entry names and order. Same formulas (and ids), with our own descriptive labels, grouped by subject (geometry, algebra, calculus, trigonometry, probability). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ROADMAP: equations row matches the landed equation editor Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fmt after merge 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> |
||
|
|
e4456efb84 |
Text boxes and floating objects: editing, drag/resize, wrapping, overflow, footnotes (#46)
* fix text boxes: type into a new box, click in and out, Escape leaves it Insert › Text Box left the caret in the body after the box and clicks inside a box never reached its story, so its text could not be edited. - insert.textBox puts the caret in the new box (when anchored in a top-level body paragraph, the only place layout shows box text today). - Layout emits a hit-test-only Placed::TextBox area; story_at prefers a box under the point (body text wins over boxes behind it) and ignores box lines outside their box. - Canvas: click enters a box, clicking outside goes back to the body (not while editing a header/footer); double-click in a box selects a word there. - Escape in a box puts the caret just after it (Document::text_box_anchor). - Editing a box doesn't dim the body or show "Editing header/footer". - Tests: engine (caret in box, typing, undo, table cell unchanged), layout (story_at inside/outside/behind), doc (anchor lookup). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix pasted text boxes sharing the original's text A copied fragment only carried the text box's part id, so a pasted box showed the original's story: editing either changed both, edits made after copying leaked into the paste, and pasting into another document (or Insert › Text from File) pointed at whatever part had that id there, e.g. a header. - Fragment carries copies of the text box stories it shows (nested boxes too). - insert_fragment gives every pasted box a new story of its own; a box whose story can't be found keeps none rather than showing an unrelated part. Nesting is depth-limited and one paste creates at most 4096 stories. - Insert › Text from File uses Document::body_fragment, which brings the file's text boxes along. - Tests: doc (own story, copy-time snapshot, other document with clashing ids, box in a table, fragment without parts, self-nested box), engine (copy/paste/type/undo through commands). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * word count includes text boxes, footnotes and endnotes (Word's default) The status bar and Word Count dialog counted only the body, so words typed into text boxes (and notes) were missing. - Document::counted_stories / word_count_including_notes: the body plus the text boxes, footnotes and endnotes it actually shows (nested too); orphaned stories (deleted or cut boxes), headers, footers and comments don't count. - Session::count_notes (default on) with Session::word_count for the status bar; review.wordCount takes {"includeTextBoxes"?: bool} and reports it. - Word Count dialog: "Include text boxes, footnotes and endnotes" checkbox. - Document::word_count stays body-only (docx Words property, NUMWORDS, stats). - Tests: doc (used vs orphaned stories, nesting, self-nested box), engine (default on, toggle sticks, deleting a box drops its words). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * canvas: select, drag to move and resize pictures, shapes and text boxes Click a picture or shape (or a text box's border) to select it: a frame with eight handles. Drag it to move it, drag a handle to resize it (pictures keep their proportions from a corner, Shift toggles), arrows nudge a floating one (Ctrl/Alt: 1 pt), Escape cancels a drag. While a text box's text is edited its frame shows dashed with handles. Typing or Enter with a text box selected goes into it. Reusable pieces, no per-kind code: - layout: Placed::Object, one hit area for every picture/shape/text box (replaces the text-box-only item); DocLayout::object_at / object / text_box / find_object (page-hinted, no whole-document scans per frame). - engine: arrange.bounds {width,height,x,y,page} (floating objects keep their anchors on the same page; inline ones float where dropped; another page re-anchors to the nearest line) and arrange.nudge; objects::min_size shared with the UI. - ui: frame.rs, a document-agnostic transform frame (handles, hit testing, drag/resize maths, painting); objects.rs wires it to document objects. - geom: Rect::expand. control: ui.press / ui.release for half-way drags. Rendering: nothing is laid out or rasterised while dragging. The preview is the object's own pixels from the cached page texture (moves, picture resizes) or a light outline (text box/shape resizes); the drop is one command, one relayout, one undo step. Tests: frame maths, layout hit areas, engine move/resize/nudge across anchors and pages, hostile values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * moving an object anchors it to the paragraph it's dropped on, so text wraps A floating object only pushes aside text from its anchor paragraph down, and a move kept the old anchor: drag a text box above its paragraph and the text there ran straight under it. - arrange.bounds now anchors the object to the paragraph under its top edge (the last one starting at or above it on that page) and positions it relative to that paragraph's column and top, as Word does: the text it lands on wraps, and it moves with that text. On a page where no paragraph starts it anchors to the nearest line, positioned on the page. - layout: Placed::Object / ObjectHit carry the paragraph origin that column- and paragraph-relative offsets start from. - engine: edit_obj / edit_float change an object at a position (with_obj uses it). - Test: a box dropped over an earlier paragraph anchors there and the lines beside it stop short of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * text boxes hide text that doesn't fit, as in Word Text past a text box's bottom spilled out over the page below it. - layout: fit_box keeps what fits in a box: whole lines that fit (a line that only partly fits is hidden too) and other items starting inside; the first line always stays so even a tiny box shows something. Done in layout, so the canvas, PNG and PDF agree and hidden text can't be clicked; no renderer changes. Reusable for anything laid out into a fixed height. - BOX_INSET_X / BOX_INSET_Y name the text box internal margins (were 7.2/3.6 literals). - Test: overflow hidden, every shown line inside the box, a tall box shows everything, a minimum-size box shows its first line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * square wrap flows text on both sides of a floating object Text only used the wider side of a floating picture, shape or text box; Word's Square wrap fills both sides when there's room. - para: row_spans gives the spans of a row around the objects in its way (each at least 0.5" wide, else the line goes below); a row cut in two is two Lines sharing top and height, the second marked `beside`. Everything per line (alignment and justification per side, hit testing, caret, selection, drawing, PDF) works unchanged; a row is as tall as its tallest part. - layout: a row stays on one page; line numbers count rows, not parts. - Exclusions that don't overlap a line horizontally no longer narrow it. - Test: text both sides of a mid-column box (same row, left then right, hittable), a too-narrow gap stays empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * drop a deleted or cut text box's text from the document Deleting or cutting a text box left its story in Document::parts: never shown or saved to .docx, but listed by document.inspect and kept in memory. - doc: prune_text_boxes removes the text box stories no box shows (nested ones too); free without text boxes, one paragraph pass otherwise. Session runs it after every editing command. Undo snapshots and the clipboard hold their own copies, so undo and cut/paste are unaffected. - doc: reachable() walks the stories objects lead to; counted_stories (word count) and prune_text_boxes share it, and it walks blocks directly instead of building paragraph paths. - Tests: orphaned and nested-orphan boxes go, shown ones (body, nested, in a header) stay; delete then undo, cut then paste. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * word count's include-text-boxes setting survives a restart The Word Count dialog's "Include text boxes, footnotes and endnotes" was a session field, so it reset to on every launch. - engine: Prefs, the editing preferences the front end saves between runs (count_notes moves there; Session::prefs is the one source of truth). - ui: SavedPrefs pairs UiState with the engine Prefs; WordApp::saved_prefs / restore_prefs. UI fields stay at the top level of ui.json, so existing files still load (the new `editing` section defaults). - wordcraft app: saves and restores SavedPrefs instead of UiState alone. - Tests: an old prefs file loads with Word's default; the setting round-trips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * text boxes and floating objects outside the body; footnotes in text boxes Text boxes in table cells, headers, footers, footnotes and other text boxes showed an empty frame (or nothing), and floating pictures and shapes there weren't drawn. Footnotes inserted inside a text box were never numbered or placed. - layout: place_objects, one routine for the pictures, shapes and text boxes in a paragraph's lines (floating ones drawn, every one's area, text boxes' text), used by body paragraphs and by layout_box (cells, headers, footers, notes, box contents). Floats are positioned against an AnchorFrame (column, paragraph, margins, page); headers and footers pass where the page is, so page/margin-anchored objects land right (a footer is laid out again only when it has floating objects); elsewhere they're placed in their container. Nested boxes show MAX_TEXT_BOX_DEPTH (doc) levels deep. - Placed::Object / ObjectHit carry their story; DocLayout::object(&Pos), object lookups include headers and footers, header_footer_text_box_at. A part's lines are looked up in headers and footers too (caret, typing). - Footnotes: numbered in reading order with text boxes where they are (Document::objects_in_reading_order); a box's footnotes go on the page of its line (Document::notes_in_text_box), sharing the body's reservation. - engine: insert.textBox always puts the caret in the new box; disabled inside a text box (as in Word). - canvas: click into a header/footer's text boxes while editing it; leaving a box returns to where it is; body dimming computed once per frame and used for both the render and its cache key. objects: only body objects are selectable, only body paragraphs' objects movable; frames off the body show without handles. - Tests: boxes in header (inline and page-placed), footer, cell and note show text and take the caret; footnotes in a box numbered and placed in order; a box in a table cell takes the caret and its text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * word count: a selected object counts the whole document, in the dialog too With a picture, shape or text box selected the status bar showed the document's total but Review > Word Count counted the selection (0 words). - engine: cmd::objects::object_selection, "the selection is exactly one object"; review.wordCount counts the document then. The UI's objects::selected now uses it (one definition). - Test: an object selection counts the document; a text selection itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * review: bound nested text box expansion; tidy-ups Blocker: laying out a text box lays out the boxes inside it, which could fan out unboundedly (a box whose shapes show itself, or chains of boxes each showing the next many times) - 30 shapes made 1.7M layout items. Footnote numbering's walk fanned out the same way. - doc: BoxBudget, one bound for both: a box isn't expanded inside itself, nesting stops at MAX_TEXT_BOX_DEPTH, an outermost box expands at most 64 boxes inside it (so one hostile box can't starve the next page's header boxes) and a pass at most 20,000. - layout: Ctx carries a BoxBudget for the whole layout; place_objects asks it before laying out a box's text. DocLayout::text_boxes reports how many were. - doc: walk_objects (objects_in_reading_order, notes_in_text_box) uses it too. - Tests (crafted documents): a box showing itself 30 times lays out once; a five-level chain fanning out 30 ways stays within the budget, for layout and for the walk; a box inside a box still shows its text. Weakening the budget makes them fail (20,000 boxes / 600,001 visits). Tidy-ups: - para: row spans in a VecDeque (pop_front, not remove(0)). - fit_box: a text box's text area excludes both the top and bottom margins. - theme: Tokens::handle / handle_shadow for the frame handles (light, dark); the drag preview's opacity is a named constant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * box budget: no overall cap; bound the .docx writer too; tests An audit of the previous commit found: - The 20,000-box overall cap starved ordinary long documents: one budget per layout, spent by body boxes first, then one per page for header boxes (two for footers with floats), so a long document's header boxes went blank on every page. It wasn't needed: only nesting grows exponentially, and MAX_NESTED caps that per outermost box, keeping the work linear. Removed. - The .docx writer still expanded a shape's story only depth-limited (a box showing itself 30 times saved as ~28,000 nested copies, ~7 MB zipped). It now uses the same BoxBudget, and only for text box parts. - Layout expanded a shape's story whatever its part kind (a shape pointing at a header showed the header's text); now text box parts only, as the walk does. The story's blocks are cloned only once the budget agrees. - Translations of "Count words in text boxes and notes" said footnotes only; now footnotes and endnotes, in each catalog's own terms. - Tests: no timing assert or leftovers; a long document with 400 boxes and a header box shows all of them on every page; notes_in_text_box follows nested boxes once; saving a self-showing box stays small (fails at 7 MB without the bound). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Arrange: nudging or dragging an aligned float keeps it in place first main (#136) reads w:align into Float.h_align/v_align, which overrides the offset; the PR's nudge only changed the offset and drag only reset the anchors. Nudge now converts an aligned axis into a column/paragraph offset that keeps the object where it is, and a drag clears the alignment. Test included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fonts: a lookup that races another thread's load of the same family no longer falls back FontDb::face fell back to Source Sans 3 when its load_cataloged added nothing, which happens when another thread loaded the family between the miss and the load. word::resolve memoises that, so the wrong font stuck for the process; it made layout tests on main flaky (justified_lines_shrink_spaces_in_word_2013_mode, space_shrinking_is_only_for_justified_text failed 6 of 15 runs on origin/main). Look again after loading whatever it returns. Regression test races 8 threads on fresh databases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layout tests: text box and two-sided wrap tests measure what they mean square_wrap_flows_text_on_both_sides compared xs.last() (the end of a trailing space, which may hang past the edge) with the edge; compare the last non-space cluster's right edge. text_box_hides_text_that_does_not_fit compared a tall box's lines with another document's line count; compare with its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Weeb Labs <weeblabs@VFIEVOX3.Router> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com> |
||
|
|
fe7e79ad70 |
Read Aloud: player with speed control, sentence skipping, skips citations (#190)
* Zotero 1: range fields and custom document properties round-trip Citation managers' ADDIN fields (Zotero, Mendeley, EndNote) are now kept as range fields: FieldStart/FieldEnd markers with the result as ordinary, formatted content between them, possibly spanning paragraphs (a Zotero bibliography). Before, the result was flattened to plain text and a multi-paragraph field lost its end. - doc: InlineObject::FieldStart/FieldEnd, Document::field_ranges, balance_field_ranges; CustomProp and custom_props with get/set/remove. - docx: read ADDIN fields as ranges (field codes up to 2 MiB), write them back as begin/instr/separate…end; orphan markers are dropped on save so files always hold whole fields. docProps/custom.xml is parsed and regenerated (simple types as text, vectors/blobs verbatim) instead of passed through, so Zotero's ZOTERO_PREF_n settings are editable. - engine: file.properties reads and sets custom properties. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 2: wire-protocol client and document bridge New crate wordcraft-zotero (L5) speaks Zotero's published word-processor protocol (port 23116; 32-bit transaction id + big-endian length frames, JSON payloads, ERR: replies): - client: sends an integration command (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs) and answers Zotero's calls until Document_complete or the connection closes (cancelled dialog). Answers go through a callback so the app can apply them on its UI thread; run_on_session answers directly. - Bridge: implements Zotero's Document_*/Field_* calls on a Session. Fields are stored as Word's plugin stores them (ADDIN ZOTERO_ range fields; prefs in ZOTERO_PREF_n, with Zotero's ReferenceMark field type kept as Word's Field in the file), so documents stay Word-compatible. Rich text (RTF or HTML) keeps citation formatting; bibliographies get a Bibliography style from setBibliographyStyle; note styles create footnotes/endnotes, and deleting a lone note citation removes its note; placeholder links convert to fields. One transaction = one undo step. Field ids stay stable through inserts and deletes. - Tests: a fake Zotero over a real socket, every call, docx round trip, hostile arguments for every method, random frames (proptest). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 3: Zotero in the app and the CLI, tested with the real Zotero - ui.zotero.<command> (addEditCitation, addEditBibliography, addNote, refresh, removeCodes, setDocPrefs), ui.zotero.status and ui.zotero.answer. The socket runs on a background thread; each of Zotero's calls is answered on the UI thread against the session, and Zotero's questions become a dialog the session waits on. Web builds report that Zotero needs the desktop app. - wordcraft-cli zotero <command> <file> [--save] [--trace] [--port]. - Bridge: a selection Zotero makes to show a citation is put back when the session ends. - ui_shot: {"wait": ms} keeps frames running for background work. - docs/zotero.md. Checked against Zotero 10.0.6: refresh of a citation and bibliography (APA), its "modified citation" question answered in the app's dialog, RTF results with italics, Bibliography style, prefs round trip. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 4: switching between in-text and note styles moves citations Document_convert and Field_convert now move a citation between the text, footnotes and endnotes, as Zotero asks when the document's style changes between an in-text and a note style: into a new note at the same place, or back into the text where the note mark was (removing a note that held only the citation; keeping one with other text). Field ids survive the move, formatting is kept, and each Zotero session stays one undo step. Checked against Zotero 10.0.6: unlink citations (removeCodes, with its confirmation question) and a refresh after switching the document to Chicago notes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero 5: Zotero tab in the ribbon A Zotero tab (before Help) with Add/Edit Citation, Add Note, Add/Edit Bibliography, Refresh, Document Preferences and Unlink Citations. The buttons are disabled while a Zotero command is running and in the web build. Three new icons drawn in code (document preferences, unlink, add note); the others reuse the References tab's. Labels and status messages translated (zh-hans, zh-hant, ja). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: Add/Edit Bibliography inserted nothing Zotero reads a newly inserted bibliography field back before filling it, and unlinks a field whose text is empty, taking it for a bibliography the user deleted. Our new fields were empty, so Add/Edit Bibliography ended with Field_removeCode and nothing in the document. New fields now hold "{Citation}" until Zotero writes the result. Found by tracing the session with Zotero 10.0.6; regression test added. wordcraft-cli zotero takes --cmd (run before the Zotero command, e.g. caret.docEnd) to reproduce sessions from a given caret position. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Zotero: log each command's outcome and Zotero's calls to the app log Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read Aloud player: speed, previous/next sentence, skips citations Read Aloud now speaks sentence by sentence with the system speech program (say / espeak / Windows SAPI) on a worker thread, so it can pause, skip and change speed. A floating player window has previous / play-pause / next / stop, a 0.5-3x speed slider and "Skip citations & bibliography" (on by default: Zotero, Mendeley, EndNote and Word citation fields). The spoken sentence is highlighted and the caret follows it. New commands: readAloud.playPause/next/previous/stop/speed/ skipCitations/status, and caret.pastCitation (Zotero > Move Past Citation). Speed and skip preference persist with the UI prefs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cargo.lock: wordcraft-zotero at the workspace version (0.4.0) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read Aloud: no unused BASE_WPM warning on wasm Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Read Aloud: fuzz review.readAloud in hostile_params_never_panic #190 makes the engine's speech backend silent (Hold) under cfg(test), so the #75 skip is no longer needed; assert the backend instead so a test can never start the system synthesiser. 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> |
||
|
|
869ac16110 |
Typing parity with Word: lists, AutoFormat and AutoCorrect as you type (#204)
* Typing parity with Word: lists, AutoFormat and AutoCorrect as you type
Typed side by side with Word (same keystrokes, results read back) and matched it:
- Enter after a list no longer gets stuck: the paragraph that leaves a list goes back to
Normal, and later Enters make blank paragraphs (the `num: 0` marker counted as a list item,
so every Enter re-ran "leave the list" and did nothing; the list indent also stuck to every
following paragraph).
- Enter on an empty nested item moves it up a level before leaving the list.
- Second Backspace at the start of an un-numbered list paragraph returns it to Normal
instead of joining it to the paragraph above.
- "1. " always starts numbering at 1; "a. " and "I. " start lettered/roman lists; "- " makes
a dash bullet.
- Undo right after an AutoFormat/AutoCorrect change undoes only that change.
- AutoCorrect: sentence capitals (with abbreviation/initial exceptions), runs on Enter too,
"word -- word" en dash, "wait..." ellipsis, 1/2 1/4 3/4, ordinal superscripts, web
addresses become links.
- Border lines: Enter after ---, ===, ***, ___, ~~~, ### draws a bottom border.
- Layout: no line break right after a slash ("and/or", URLs), as in Word.
- ui_shot: UI_SHOT_FULL=1 prints whole replies for scripted checks.
Regression tests in crates/engine/src/tests_typing.rs and the layout tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Typing parity: record Word's observed typing rules, two more tests
docs/typing-parity.md lists what Word does for each key sequence (lists, Backspace, Tab,
AutoCorrect, AutoFormat, slash line breaks) with the test that pins it, the known gaps, and how
to compare without taking over the shared machine. CLAUDE.md/AGENTS.md point at it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
d95e09ba25 |
Clippy/fmt clean workspace, ROADMAP with honest parity estimate; xtask ci green
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b8dd46ccb5 |
M0.7: engine — session, undo, 200+ commands, Word feature catalog, samples
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |