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>
* I18N.et: add Estonian interface translation
* I18N.et: record the verified headless fork push step
* i18n: send interface text that skipped translation through the catalog
A test-only pseudo-language wraps every catalog lookup in ⟦…⟧ marks; a new
audit test lays the app out in it (every tab, File page, dialog and pane,
the right-click menu, and a hover and a click on every ribbon control,
including the Table and Equation tabs) and fails on any visible text
outside the marks. The document is drawn as an image, so only interface
text is checked; document text, font and style names, shortcuts, icon
lettering, contributor handles and command error messages are allowed.
What it found, now translated in every language that has the strings:
- Right-click menu items (Cut, Paste, Font…, Paragraph…, Link…, New Comment).
- Paragraph dialog: the alignment choices showed code values (`left`);
the label now sits before the dropdown.
- Table Design › Table Style Options checkboxes.
- Table Layout › Alignment tooltips showed code values (`topLeft`).
- Design › Fonts: the theme's name is translated, its font names are not.
- `pt` units, the Insert › Table grid label, the default watermark text,
the font dialog's preview sample, and the untitled document's name.
- Contributors and Models dialogs: headings, sort and name choices, summary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n: add Russian (ru) interface translation
- ru.tsv: 1,068 entries covering every interface string, written clean-room
from the English labels and reviewed by a native speaker. `ru`, `ru-RU`,
`ru_RU.UTF-8` and other regions resolve to it; File > Options picks it by hand.
- Built-in style names (Normal, Heading 2, TOC 3, Strong…) show in the
interface language in the Styles gallery, the Styles pane and the Style
Inspector. Display only: documents, commands and saved files keep the
English names, and styles the author made keep theirs.
- Rendered previews use the interface language: the Styles gallery samples
(`АаБбВвГг`) and the Design tab's style-set tiles.
- Catalogs gain a `preview` context for sample text that must fit a small
tile (`Title` is «Заголовок» as a style but «Пример» in the tiles); it falls
back to the plain entry, and unknown contexts are still rejected.
- Tests: locale detection and saved preference, full coverage, Cyrillic glyphs
in the bundled interface fonts, style-name display, catalog contexts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(ru): translate everything the interface audit finds
Adds 356 Russian entries: the equation gallery (categories, templates and
built-in equations), equation options, themes, watermark presets, paper
sizes, table styles, greetings, outline levels, the strings #313 routes
through the catalog, and the new AutoSave messages. The two rows #305 made
obsolete (📂 Browse…, ➕ New comment) are gone.
The interface audit now also fails when a string it sees translated has no
Russian entry, so Russian stays complete as the interface grows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303)
Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser.
table.draw takes a pen stroke in page points: a rectangle outside a table
inserts a one-cell table of that size at the paragraph under its start; a
straight vertical or horizontal line inside a table splits the cells it
crosses (new grid column or row; untouched cells span it). table.eraser
merges the two cells beside the border nearest a point. Each stroke is one
undo step. In the app the buttons toggle pen and eraser modes with a live
preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks
don't move the caret while a tool is on. Splitting through part of a
vertically merged cell and erasing beside one are refused with a clear
error. Catalog 384/430 -> 387/430.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Draw tab: pen, pencil and highlighter ink, stroke eraser and Hide Ink (#307)
Ink strokes are floating freeform shapes: a new Freeform geometry (paths in
shape-local coordinates, opacity, and the pen that drew it) on
InlineObject::Shape with ShapeKind::Freeform. draw.stroke inserts one
anchored to the paragraph under the first point (one undo step; points
capped per stroke, strokes capped per page, NaN dropped); draw.erase /
draw.eraser with a point deletes the stroke under it; draw.pen, draw.pencil,
draw.highlighter, draw.eraser and draw.select set the Draw tab's tool in the
session view state; review.hideInk toggles showing ink on screen.
The canvas draws a live stroke while a pen is dragged and commits it on
release; the eraser deletes strokes it passes over (one undo step per drag);
Esc or Select goes back to selecting. Draw tab buttons show the tool in use,
with colour and thickness menus; Review gets an Ink group with Hide Ink.
Ink renders with round caps and joins on screen and in PDF; highlighters are
translucent and behind the text. DOCX writes freeforms as wps:wsp with
a:custGeom (moveTo/lnTo) in a wp:anchor and reads them back, along with
simple custom-geometry freeforms from other files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #315 review fixes: eraser undo test, sanitized tap dots, custGeom path cap, ink docs
- draw.eraser with a point erases through draw.erase: test that it is one
undo step (undo/redo) and marks the document dirty.
- Render and PDF: a single-point stroke's dot is drawn at the sanitized
point, not the raw first one.
- DOCX custGeom reading stops once MAX_PATHS paths (subpaths included) are
collected, across a:path elements.
- docs/file-format-parity.md: ink is saved as custom geometry, not InkML, so
Word shows it as a freeform.
- widgets test: theme::apply takes an Appearance (#317).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: send interface text that skipped translation through the catalog
A test-only pseudo-language wraps every catalog lookup in ⟦…⟧ marks; a new
audit test lays the app out in it (every tab, File page, dialog and pane,
the right-click menu, and a hover and a click on every ribbon control,
including the Table and Equation tabs) and fails on any visible text
outside the marks. The document is drawn as an image, so only interface
text is checked; document text, font and style names, shortcuts, icon
lettering, contributor handles and command error messages are allowed.
What it found, now translated in every language that has the strings:
- Right-click menu items (Cut, Paste, Font…, Paragraph…, Link…, New Comment).
- Paragraph dialog: the alignment choices showed code values (`left`);
the label now sits before the dropdown.
- Table Design › Table Style Options checkboxes.
- Table Layout › Alignment tooltips showed code values (`topLeft`).
- Design › Fonts: the theme's name is translated, its font names are not.
- `pt` units, the Insert › Table grid label, the default watermark text,
the font dialog's preview sample, and the untitled document's name.
- Contributors and Models dialogs: headings, sort and name choices, summary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n audit: start every ribbon click from a fresh app; translate the table style preview
The sweep pressed Escape after each click, and in an equation Escape leaves it,
so the Equation tab vanished after its first control and the rest of its menus
(Convert, Justification, …) were never opened. Each control now gets a fresh app
set up the same way. The narrower allowance for short text (only icon lettering
and initials) also catches short words.
It found the sample table in the Table Style dialog (Mon, Tue, North, Total, …),
now translated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* #313: catalog rows for the newly translated interface text
Rows in all eight catalogs for the strings #313 now sends through the
catalog (cell alignment tooltips, credits columns and summary, theme names,
context-menu items, table picker, pt suffix, Document1, the Font dialog
sample) and for the Table Style preview's sample table, which the
untranslated-text audit flagged after main's table style editor landed.
The audit runs headless (no host services: no file pickers or speech) in
about 20 s, so it stays enabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n audit: click the ribbon's bottom row too
The sweep only clicked controls centred between y = 52 and 125, which took in
the tab row and left out each group's bottom row (Equation › Convert and
Justification among them). The band is now taken from the layout: below the
tab row and above the document area.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n audit: lay the app out wide enough that no ribbon tab overflows
At 1440 pt the Equation tab ran past the window, so its last controls
(Justification among them) sat behind the ribbon's scroll arrow and were never
clicked. The audit window is now 2560 pt wide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n: add Russian (ru) interface translation
- ru.tsv: 1,068 entries covering every interface string, written clean-room
from the English labels and reviewed by a native speaker. `ru`, `ru-RU`,
`ru_RU.UTF-8` and other regions resolve to it; File > Options picks it by hand.
- Built-in style names (Normal, Heading 2, TOC 3, Strong…) show in the
interface language in the Styles gallery, the Styles pane and the Style
Inspector. Display only: documents, commands and saved files keep the
English names, and styles the author made keep theirs.
- Rendered previews use the interface language: the Styles gallery samples
(`АаБбВвГг`) and the Design tab's style-set tiles.
- Catalogs gain a `preview` context for sample text that must fit a small
tile (`Title` is «Заголовок» as a style but «Пример» in the tiles); it falls
back to the plain entry, and unknown contexts are still rejected.
- Tests: locale detection and saved preference, full coverage, Cyrillic glyphs
in the bundled interface fonts, style-name display, catalog contexts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(ru): translate everything the interface audit finds
Adds 356 Russian entries: the equation gallery (categories, templates and
built-in equations), equation options, themes, watermark presets, paper
sizes, table styles, greetings, outline levels, the strings #313 routes
through the catalog, and the new AutoSave messages. The two rows #305 made
obsolete (📂 Browse…, ➕ New comment) are gone.
The interface audit now also fails when a string it sees translated has no
Russian entry, so Russian stays complete as the interface grows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n(ru): native-speaker review corrections
Add Note, Move Past Citation, equation display/inline, accents, stacks,
operators, Column Selection, Reading Highlight, ASAP and Tools, as reviewed;
the Accent gallery button wraps onto two lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n(ru): translate what the improved audit and today's main add
The audit now opens every ribbon menu (bottom rows and the Equation tab's last
controls included), which surfaced Equation › Convert and Justification, and
the Table Style dialog's sample table. Also covers password protection, Manage
Styles, the clipboard pane, Shape Effects and Asian typography from main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n: translate the Table Layout 'Draw Table' ribbon label in every catalog
#308's two-line ribbon label had no catalog rows; #300's Russian audit
(ru covers everything the interface looks up) found it. Rows in all ten
catalogs keep et == es and ru covering zh-hans.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: Borders and Shading line widths through the catalog ({n} pt)
#313's untranslated-text audit flagged main's new dialog (#327).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* land-294: Estonian rows for main's newer dialogs
Define New Multilevel List and Track Changes Options (#342), rotation (#332),
Tabs and Borders and Shading (#327), Columns, Symbol and Field (#335): et
keys equal es again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n audit: run on demand (#[ignore]) until its rare failure under load is understood
It failed once in ~46 runs on a heavily loaded machine and passed 35 straight reruns,
so CI shouldn't depend on it yet. Run it with --ignored.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(ru): restore the review corrections a merge reverted; narrow and hide labels
A merge of older copies of this branch's commits put back the pre-review
wording of 20 entries (Add Note, Tools, the equation accents and operators,
Column Selection, Reading Highlight, ASAP, …) and dropped six that are still in
use (Equation › Convert, Centered as Group, the hyphenation menu's ✓ None).
They're back as reviewed. Also: 1/4 Em Space → «Узкий пробел», and the Draw
tab's Hide Ink button reads «Скрыть».
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n(ru): restore the reviewed wording a merge rewrote
The same merge reworded 24 reviewed entries (password protection, Shape
Effects, Asian typography, the clipboard pane, the table style sample);
they're back as a native speaker reviewed them. Clipboard Pane keeps
«Область буфера обмена», like the other panes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
---------
Co-authored-by: Henno Täht <henno.taht@gmail.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: vangel76 <vangel1976@googlemail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- para.moveUp / para.moveDown (Alt+Shift+Up/Down, Ctrl+Shift+Up/Down on macOS): the
paragraphs the selection touches swap with their neighbour in the same story or cell,
keeping style and list membership; selection follows; one undo step; refused at the
container edges, next to a table and across a section break.
- para.promote / para.demote (Alt+Shift+Left/Right, Ctrl+Shift+Left/Right on macOS):
heading level up/down (Heading 1..9), promoted body text takes the level of the heading
before it, list items change level like Shift+Tab/Tab.
- edit.goBack (Shift+F5): cycles the caret through the last four edit places.
- text.toggleUnicode (Alt+X; unbound on macOS): hex code <-> character, undoable. The
canvas no longer types the key's letter after an Alt shortcut ran.
- Catalog rows, translations in every interface catalog, typing/UI parity docs, tests.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
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>
* i18n audit: start every ribbon click from a fresh app; translate the table style preview
The sweep pressed Escape after each click, and in an equation Escape leaves it,
so the Equation tab vanished after its first control and the rest of its menus
(Convert, Justification, …) were never opened. Each control now gets a fresh app
set up the same way. The narrower allowance for short text (only icon lettering
and initials) also catches short words.
It found the sample table in the Table Style dialog (Mon, Tue, North, Total, …),
now translated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n audit: click the ribbon's bottom row too
The sweep only clicked controls centred between y = 52 and 125, which took in
the tab row and left out each group's bottom row (Equation › Convert and
Justification among them). The band is now taken from the layout: below the
tab row and above the document area.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n audit: lay the app out wide enough that no ribbon tab overflows
At 1440 pt the Equation tab ran past the window, so its last controls
(Justification among them) sat behind the ribbon's scroll arrow and were never
clicked. The audit window is now 2560 pt wide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
---------
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>
* chore(lints): satisfy nonminimal_bool on Rust 1.96 Clippy
Four boolean expressions rewritten into their equivalent minimal forms
(De Morgan / is_none_or) so `cargo xtask ci` passes `clippy -D warnings`
on current stable. No behaviour change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(tr): Turkish interface (Türkçe)
- crates/ui-egui/src/i18n/tr.tsv: complete catalog, all 1,307 keys of
es.tsv, written clean-room from the plain meaning of the English
labels; placeholders, escapes and "…" kept. A noun after a number
stays singular in Turkish, so the context-free format needs no
plural forms.
- Registry entry `tr` / "Türkçe": tr-TR, tr-CY and tr_*.UTF-8 locales
resolve by the generic prefix match; saved preference and English
fallback use the existing machinery.
- Command search folds case the Turkish way (İ→i, I→ı) for translated
labels and locations, while English labels and ASCII command ids keep
their existing Unicode case-insensitive matching ("italik" finds
"İtalik", "INSERT.TABLE" still finds insert.table).
- The Font dialog's preview sample is "AaBbYyZz ĞğİıŞş": it has no
lowercase i, so the All Caps preview (to_uppercase) gets the Turkish
capitals right.
- Tests: catalog key and placeholder parity, locale resolution, saved
preference, Turkish folding, the sample's capitals, a real egui
command-dialog regression, and ÇçĞğİıÖöŞşÜü in the bundled UI fonts.
- Docs: README, ROADMAP, localization-parity, control-protocol, new
localization-tr.md (terminology, scope); ATTRIBUTION row for tr.tsv.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(paste): an absolute picture path that is absolute on Windows too
`/tmp/b.JPG` has no drive, so `Path::is_absolute` is false on Windows and
`paste_inserts_clipboard_pixels` failed there; the test uses `C:\tmp\b.JPG`
on Windows. Test-only.
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>
- Every instruction now uses tú (the catalog mixed usted and tú), and
«añadir» replaces the remaining «agregar».
- Page Up / Page Down labels were swapped (Av Pág / Re Pág).
- Mistranslations: Block Authors, Grab bag, Do Not Show in TOC,
Capitalize Each Word, Set Caret, Switch Modes (and its mention in the
dark-page hint), Space before/after, At least/Exactly.
- Spanish quotation marks in the command box hint.
Translated from the English labels in this repository only; no other
product's localisation consulted.
Claude-Session: https://claude.ai/code/session_012JC2QZ1Qc5KQMBswS38GKy
Co-authored-by: Diego Ospina <diegozpy@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- TXT: the engine opens and saves .txt through wordcraft-formats (BOM/unmarked
UTF-16 sniffing, Windows-1252 fallback; list labels and a tab on export)
instead of its own narrower decoder and body plain text.
- RTF: \uN and \ucN are handled in document info, bookmark names and field
instructions, not only in body text.
- ODT: common paragraph styles (and common parents of automatic styles) give
their text formatting, except the style that sets the paragraph kind;
table-cell-properties fo:background-color becomes cell shading.
- HTML: tables without style or visible borders export without cell borders;
white-space:pre paragraphs keep leading/trailing spaces.
- LaTeX: brace groups restore the paragraph alignment set inside them.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- equation.number off strips the number from every row; a single row is unwrapped,
several rows stay an equation array (was: only the first row survived).
- equation.move works out AutoCorrect/build-up on a copy; plain navigation no longer
rewrites the equation (keeps imported OMML such as m:ctrlPr colours), and a real
build-up is checkpointed as "Build Up" and marks the document dirty.
- LaTeX tokenizer keeps white space inside \text/\textrm/\mbox/\textnormal/\textbf/\textit
arguments (collapsed to one space), so \text{for all x} stays "for all x".
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The English speller only knew the 1990s Moby word list plus a small
hard-coded EXTRA array, so everyday modern words (desktop, laptop,
download, upload, spreadsheet, database, screenshot, inbox, offline,
workplace…) were flagged. Inflection, doubled-consonant and prefix rules
also only looked stems up in Moby, so even supplemented words failed in
inflected forms (blogging, uploader).
- New supplement assets/spelling/en-extra.txt: ~220 lowercase base forms,
our own list (MIT OR Apache-2.0), replacing the EXTRA array.
- All stem lookups (inflections, doubled consonant, prefixes) consult the
dictionary and the supplement; suggestions include supplement words.
- Regression tests: common modern words and inflections accepted,
misspellings still flagged, "dekstop" suggests "desktop"; the list
stays sorted and lowercase.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Every dialog was anchored CENTER_CENTER, which egui re-applies each frame,
so a tab of a different height split the change between top and bottom and
the tab strip jumped. Dialogs are now centred on the frame they open, then
anchored CENTER_TOP at that top edge until they close; only the bottom edge
moves. egui still keeps the window on screen.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Follow [MS-DOC] in the Word 97-2003 reader:
- Prm: bit 0 is fComplex; Prm0 isprm in bits 1-7, Prm1 Prc index in bits 1-15 (#458)
- sprmSBkc operands 0-4 map to continuous/new column/new page/even/odd (#459)
- sprmSBOrientation 1 = portrait, 2 = landscape (#460)
- negative sprmPIlfo (0xF802-0xFFFF) references the negated LFO index (#461)
- StdfBase: istdBase shares the word with stk and is zero-based; the
whole base chain is applied, bounded and cycle-safe (#462)
- SttbfFfn entries advance by cchData, covering the optional xszAlt (#463)
One regression test per issue, built in code from the spec.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
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>
File › Options › General gets an Interface size choice (80–200 %), kept in
ui.json and applied as egui's zoom factor, so it multiplies the display
scale the system reports instead of replacing it. Junk saved values read
as 100 % and out-of-range ones are clamped. Agents use ui.interfaceSize
{"percent"}. Document zoom (view.zoom) is untouched; egui's keyboard zoom
stays off, so Word shortcuts never change the interface size.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Merge fields are now evaluated in document order with a record cursor,
shared by Preview Results and Finish & Merge:
- SKIPIF drops the merged copy when its condition holds (#427).
- NEXT/NEXTIF move later fields to the next record; the next copy
starts after it, and records past the end are blank (#428).
- IF rules on merge fields preview their true/false text (#429).
- Header and footer stories are previewed and merged; with merge fields
in them each letter becomes its own section with its own copies (#430).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Group keeps each member's own turn (and composes a merged group's turn
into its members); the group box covers the members' turned bounds and
the new group starts unturned (#441).
- Size (picture.size) resizes a selected group, scaling its members
through the group's child space like Move or Resize (#442).
- Ungroup decomposes a turned group: each member's centre is turned about
the group's centre and its turn becomes its own within the group's (#443).
- The eraser tests the point turned back into the stroke's unturned frame,
so turned ink is erased where it is drawn (#445).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Linking a whole paragraph also linked its paragraph mark, and Enter
copied the link at the split into the new paragraph's mark, so everything
typed in the paragraphs Enter made (above or below) became the link, and
hovering anywhere near the end of the text showed the link's tooltip.
- doc: a paragraph mark never takes a hyperlink (format_range, split_off)
- engine: typing at either end of a link isn't linked; inside it is (Word)
- ui: hover tooltip and Ctrl+click only on the linked text itself
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Formatting changes are tracked like insertions and deletions:
- Model: runs and paragraph marks carry the formatting before a tracked
change (CharProps::fmt_change), paragraphs their old properties
(ParaProps::fmt_change) and a list numbering change; tables, rows,
cells and sections their old properties.
- DOCX: w:rPrChange (runs and paragraph marks), w:pPrChange,
w:numberingChange, w:tblPrChange, w:trPrChange, w:tcPrChange and
w:sectPrChange are read and written back, each last in its parent
(ECMA-376 17.13.5). Nested changes are ignored; ids are renumbered.
- Recording: with Track Changes and Track Formatting on, Session::run
compares the paragraphs a command changed with the document before it,
so every formatting command records without per-command code. An
author's later changes keep the formatting before the first one;
formatting set back to the original stops being a change.
- Accept/Reject (at the caret, selection, all) keep or restore the
formatting; Next/Previous and the Reviewing Pane list format changes
with a "Formatted: Bold, Size: 14" description.
- Markup: change bars beside formatted lines and "Formatted: ..." balloons
in the markup area, per Track Changes Options.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Delete Cells removes a cell without shrinking the grid, so a row can be shorter than the
grid. Delete Columns then removed that row's only cell, dropped every row left without
cells, and kept the now empty table block (`rows: []`), an invalid state that anything
indexing the first row would turn into a panic. When no rows are left, remove the table, as
deleting the only column already does.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
- A page-number start near u32::MAX (.docx accepts up to 4294967295) or a line-number start
near u32::MAX (open through .json and commands) overflowed when the next page or line was
counted: a panic whenever overflow checks are on, silently wrapped numbers otherwise. The
three additions now saturate.
- The wave stroke used by wave borders and underlines looped forever, pushing curves until
memory ran out, once its x coordinate passed about 1e16 and `x + step == x` (a wave page
border with a 1e20 `space`, reachable through .json, the control channel and MCP). The
path is built in `wave_path`, which stops as soon as a step makes no progress.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
The drop-cap letter's size was `clamp(rc.size, 1000.0)`, and `f32::clamp` panics when the
minimum is above the maximum, which happens as soon as the paragraph's text is larger than
1000 pt (Word allows up to 1638). Layout, PDF export and saving run outside `Session::run`'s
panic guard, so the desktop app panicked on every frame, and `wordcraft-cli convert` failed.
Use `.min(1000.0).max(rc.size)`, which never panics and keeps the letter at least as large as
the text.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
#333 writes OLE objects back from their kept markup, resized to the
object's current size, but #332's rotation and flips weren't carried into
that markup: a turned or flipped OLE object came back unturned (DrawingML)
or with its old turn (VML).
The VML shape's style now gets the object's rotation and flip, and a
DrawingML picture's a:xfrm gets rot/flipH/flipV and its size, with the
drawing's wp:effectExtent covering the rotated bounds like other drawings
(added when missing). Inline VML shapes now read their rotation and flips
too, so the turn survives a WordCraft round trip. Charts and diagrams can't
be turned, so their frame needs nothing new.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Typing in the ribbon's or the mini toolbar's font box now lists the
installed families that match, under the field: exact name first, then
names starting with the text, then names containing it, ignoring case
("roman" finds Times New Roman). Up/Down move the highlight, Enter or a
click applies it ("arial" applies Arial), Escape restores the current
font. Text that matches no family is still applied as typed, as Word
allows fonts that aren't installed.
The search is opt-in (widgets::font_combo); the font size box keeps
literal entry. Matches are computed only when the text changes and the
list stays virtualised (#121).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* I18N.et: add Estonian interface translation
* I18N.et: record the verified headless fork push step
* i18n: send interface text that skipped translation through the catalog
A test-only pseudo-language wraps every catalog lookup in ⟦…⟧ marks; a new
audit test lays the app out in it (every tab, File page, dialog and pane,
the right-click menu, and a hover and a click on every ribbon control,
including the Table and Equation tabs) and fails on any visible text
outside the marks. The document is drawn as an image, so only interface
text is checked; document text, font and style names, shortcuts, icon
lettering, contributor handles and command error messages are allowed.
What it found, now translated in every language that has the strings:
- Right-click menu items (Cut, Paste, Font…, Paragraph…, Link…, New Comment).
- Paragraph dialog: the alignment choices showed code values (`left`);
the label now sits before the dropdown.
- Table Design › Table Style Options checkboxes.
- Table Layout › Alignment tooltips showed code values (`topLeft`).
- Design › Fonts: the theme's name is translated, its font names are not.
- `pt` units, the Insert › Table grid label, the default watermark text,
the font dialog's preview sample, and the untitled document's name.
- Contributors and Models dialogs: headings, sort and name choices, summary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303)
Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser.
table.draw takes a pen stroke in page points: a rectangle outside a table
inserts a one-cell table of that size at the paragraph under its start; a
straight vertical or horizontal line inside a table splits the cells it
crosses (new grid column or row; untouched cells span it). table.eraser
merges the two cells beside the border nearest a point. Each stroke is one
undo step. In the app the buttons toggle pen and eraser modes with a live
preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks
don't move the caret while a tool is on. Splitting through part of a
vertically merged cell and erasing beside one are refused with a clear
error. Catalog 384/430 -> 387/430.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Draw tab: pen, pencil and highlighter ink, stroke eraser and Hide Ink (#307)
Ink strokes are floating freeform shapes: a new Freeform geometry (paths in
shape-local coordinates, opacity, and the pen that drew it) on
InlineObject::Shape with ShapeKind::Freeform. draw.stroke inserts one
anchored to the paragraph under the first point (one undo step; points
capped per stroke, strokes capped per page, NaN dropped); draw.erase /
draw.eraser with a point deletes the stroke under it; draw.pen, draw.pencil,
draw.highlighter, draw.eraser and draw.select set the Draw tab's tool in the
session view state; review.hideInk toggles showing ink on screen.
The canvas draws a live stroke while a pen is dragged and commits it on
release; the eraser deletes strokes it passes over (one undo step per drag);
Esc or Select goes back to selecting. Draw tab buttons show the tool in use,
with colour and thickness menus; Review gets an Ink group with Hide Ink.
Ink renders with round caps and joins on screen and in PDF; highlighters are
translucent and behind the text. DOCX writes freeforms as wps:wsp with
a:custGeom (moveTo/lnTo) in a wp:anchor and reads them back, along with
simple custom-geometry freeforms from other files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #315 review fixes: eraser undo test, sanitized tap dots, custGeom path cap, ink docs
- draw.eraser with a point erases through draw.erase: test that it is one
undo step (undo/redo) and marks the document dirty.
- Render and PDF: a single-point stroke's dot is drawn at the sanitized
point, not the raw first one.
- DOCX custGeom reading stops once MAX_PATHS paths (subpaths included) are
collected, across a:path elements.
- docs/file-format-parity.md: ink is saved as custom geometry, not InkML, so
Word shows it as a freeform.
- widgets test: theme::apply takes an Appearance (#317).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #313: catalog rows for the newly translated interface text
Rows in all eight catalogs for the strings #313 now sends through the
catalog (cell alignment tooltips, credits columns and summary, theme names,
context-menu items, table picker, pt suffix, Document1, the Font dialog
sample) and for the Table Style preview's sample table, which the
untranslated-text audit flagged after main's table style editor landed.
The audit runs headless (no host services: no file pickers or speech) in
about 20 s, so it stays enabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: Borders and Shading line widths through the catalog ({n} pt)
#313's untranslated-text audit flagged main's new dialog (#327).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* land-294: Estonian rows for main's newer dialogs
Define New Multilevel List and Track Changes Options (#342), rotation (#332),
Tabs and Borders and Shading (#327), Columns, Symbol and Field (#335): et
keys equal es again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n audit: run on demand (#[ignore]) until its rare failure under load is understood
It failed once in ~46 runs on a heavily loaded machine and passed 35 straight reruns,
so CI shouldn't depend on it yet. Run it with --ignored.
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: vangel76 <vangel1976@googlemail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* i18n: send interface text that skipped translation through the catalog
A test-only pseudo-language wraps every catalog lookup in ⟦…⟧ marks; a new
audit test lays the app out in it (every tab, File page, dialog and pane,
the right-click menu, and a hover and a click on every ribbon control,
including the Table and Equation tabs) and fails on any visible text
outside the marks. The document is drawn as an image, so only interface
text is checked; document text, font and style names, shortcuts, icon
lettering, contributor handles and command error messages are allowed.
What it found, now translated in every language that has the strings:
- Right-click menu items (Cut, Paste, Font…, Paragraph…, Link…, New Comment).
- Paragraph dialog: the alignment choices showed code values (`left`);
the label now sits before the dropdown.
- Table Design › Table Style Options checkboxes.
- Table Layout › Alignment tooltips showed code values (`topLeft`).
- Design › Fonts: the theme's name is translated, its font names are not.
- `pt` units, the Insert › Table grid label, the default watermark text,
the font dialog's preview sample, and the untitled document's name.
- Contributors and Models dialogs: headings, sort and name choices, summary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303)
Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser.
table.draw takes a pen stroke in page points: a rectangle outside a table
inserts a one-cell table of that size at the paragraph under its start; a
straight vertical or horizontal line inside a table splits the cells it
crosses (new grid column or row; untouched cells span it). table.eraser
merges the two cells beside the border nearest a point. Each stroke is one
undo step. In the app the buttons toggle pen and eraser modes with a live
preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks
don't move the caret while a tool is on. Splitting through part of a
vertically merged cell and erasing beside one are refused with a clear
error. Catalog 384/430 -> 387/430.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Draw tab: pen, pencil and highlighter ink, stroke eraser and Hide Ink (#307)
Ink strokes are floating freeform shapes: a new Freeform geometry (paths in
shape-local coordinates, opacity, and the pen that drew it) on
InlineObject::Shape with ShapeKind::Freeform. draw.stroke inserts one
anchored to the paragraph under the first point (one undo step; points
capped per stroke, strokes capped per page, NaN dropped); draw.erase /
draw.eraser with a point deletes the stroke under it; draw.pen, draw.pencil,
draw.highlighter, draw.eraser and draw.select set the Draw tab's tool in the
session view state; review.hideInk toggles showing ink on screen.
The canvas draws a live stroke while a pen is dragged and commits it on
release; the eraser deletes strokes it passes over (one undo step per drag);
Esc or Select goes back to selecting. Draw tab buttons show the tool in use,
with colour and thickness menus; Review gets an Ink group with Hide Ink.
Ink renders with round caps and joins on screen and in PDF; highlighters are
translucent and behind the text. DOCX writes freeforms as wps:wsp with
a:custGeom (moveTo/lnTo) in a wp:anchor and reads them back, along with
simple custom-geometry freeforms from other files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #315 review fixes: eraser undo test, sanitized tap dots, custGeom path cap, ink docs
- draw.eraser with a point erases through draw.erase: test that it is one
undo step (undo/redo) and marks the document dirty.
- Render and PDF: a single-point stroke's dot is drawn at the sanitized
point, not the raw first one.
- DOCX custGeom reading stops once MAX_PATHS paths (subpaths included) are
collected, across a:path elements.
- docs/file-format-parity.md: ink is saved as custom geometry, not InkML, so
Word shows it as a freeform.
- widgets test: theme::apply takes an Appearance (#317).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: send interface text that skipped translation through the catalog
A test-only pseudo-language wraps every catalog lookup in ⟦…⟧ marks; a new
audit test lays the app out in it (every tab, File page, dialog and pane,
the right-click menu, and a hover and a click on every ribbon control,
including the Table and Equation tabs) and fails on any visible text
outside the marks. The document is drawn as an image, so only interface
text is checked; document text, font and style names, shortcuts, icon
lettering, contributor handles and command error messages are allowed.
What it found, now translated in every language that has the strings:
- Right-click menu items (Cut, Paste, Font…, Paragraph…, Link…, New Comment).
- Paragraph dialog: the alignment choices showed code values (`left`);
the label now sits before the dropdown.
- Table Design › Table Style Options checkboxes.
- Table Layout › Alignment tooltips showed code values (`topLeft`).
- Design › Fonts: the theme's name is translated, its font names are not.
- `pt` units, the Insert › Table grid label, the default watermark text,
the font dialog's preview sample, and the untitled document's name.
- Contributors and Models dialogs: headings, sort and name choices, summary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n audit: start every ribbon click from a fresh app; translate the table style preview
The sweep pressed Escape after each click, and in an equation Escape leaves it,
so the Equation tab vanished after its first control and the rest of its menus
(Convert, Justification, …) were never opened. Each control now gets a fresh app
set up the same way. The narrower allowance for short text (only icon lettering
and initials) also catches short words.
It found the sample table in the Table Style dialog (Mon, Tue, North, Total, …),
now translated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* #313: catalog rows for the newly translated interface text
Rows in all eight catalogs for the strings #313 now sends through the
catalog (cell alignment tooltips, credits columns and summary, theme names,
context-menu items, table picker, pt suffix, Document1, the Font dialog
sample) and for the Table Style preview's sample table, which the
untranslated-text audit flagged after main's table style editor landed.
The audit runs headless (no host services: no file pickers or speech) in
about 20 s, so it stays enabled.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n audit: click the ribbon's bottom row too
The sweep only clicked controls centred between y = 52 and 125, which took in
the tab row and left out each group's bottom row (Equation › Convert and
Justification among them). The band is now taken from the layout: below the
tab row and above the document area.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* i18n audit: lay the app out wide enough that no ribbon tab overflows
At 1440 pt the Equation tab ran past the window, so its last controls
(Justification among them) sat behind the ribbon's scroll arrow and were never
clicked. The audit window is now 2560 pt wide.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
* fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: Borders and Shading line widths through the catalog ({n} pt)
#313's untranslated-text audit flagged main's new dialog (#327).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* i18n audit: run on demand (#[ignore]) until its rare failure under load is understood
It failed once in ~46 runs on a heavily loaded machine and passed 35 straight reruns,
so CI shouldn't depend on it yet. Run it with --ignored.
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: vangel76 <vangel1976@googlemail.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303)
Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser.
table.draw takes a pen stroke in page points: a rectangle outside a table
inserts a one-cell table of that size at the paragraph under its start; a
straight vertical or horizontal line inside a table splits the cells it
crosses (new grid column or row; untouched cells span it). table.eraser
merges the two cells beside the border nearest a point. Each stroke is one
undo step. In the app the buttons toggle pen and eraser modes with a live
preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks
don't move the caret while a tool is on. Splitting through part of a
vertically merged cell and erasing beside one are refused with a clear
error. Catalog 384/430 -> 387/430.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Draw tab: pen, pencil and highlighter ink, stroke eraser and Hide Ink (#307)
Ink strokes are floating freeform shapes: a new Freeform geometry (paths in
shape-local coordinates, opacity, and the pen that drew it) on
InlineObject::Shape with ShapeKind::Freeform. draw.stroke inserts one
anchored to the paragraph under the first point (one undo step; points
capped per stroke, strokes capped per page, NaN dropped); draw.erase /
draw.eraser with a point deletes the stroke under it; draw.pen, draw.pencil,
draw.highlighter, draw.eraser and draw.select set the Draw tab's tool in the
session view state; review.hideInk toggles showing ink on screen.
The canvas draws a live stroke while a pen is dragged and commits it on
release; the eraser deletes strokes it passes over (one undo step per drag);
Esc or Select goes back to selecting. Draw tab buttons show the tool in use,
with colour and thickness menus; Review gets an Ink group with Hide Ink.
Ink renders with round caps and joins on screen and in PDF; highlighters are
translucent and behind the text. DOCX writes freeforms as wps:wsp with
a:custGeom (moveTo/lnTo) in a wp:anchor and reads them back, along with
simple custom-geometry freeforms from other files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #315 review fixes: eraser undo test, sanitized tap dots, custGeom path cap, ink docs
- draw.eraser with a point erases through draw.erase: test that it is one
undo step (undo/redo) and marks the document dirty.
- Render and PDF: a single-point stroke's dot is drawn at the sanitized
point, not the raw first one.
- DOCX custGeom reading stops once MAX_PATHS paths (subpaths included) are
collected, across a:path elements.
- docs/file-format-parity.md: ink is saved as custom geometry, not InkML, so
Word shows it as a freeform.
- widgets test: theme::apply takes an Appearance (#317).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Draw tab: pen, pencil and highlighter ink, stroke eraser and Hide Ink (#307)
Ink strokes are floating freeform shapes: a new Freeform geometry (paths in
shape-local coordinates, opacity, and the pen that drew it) on
InlineObject::Shape with ShapeKind::Freeform. draw.stroke inserts one
anchored to the paragraph under the first point (one undo step; points
capped per stroke, strokes capped per page, NaN dropped); draw.erase /
draw.eraser with a point deletes the stroke under it; draw.pen, draw.pencil,
draw.highlighter, draw.eraser and draw.select set the Draw tab's tool in the
session view state; review.hideInk toggles showing ink on screen.
The canvas draws a live stroke while a pen is dragged and commits it on
release; the eraser deletes strokes it passes over (one undo step per drag);
Esc or Select goes back to selecting. Draw tab buttons show the tool in use,
with colour and thickness menus; Review gets an Ink group with Hide Ink.
Ink renders with round caps and joins on screen and in PDF; highlighters are
translucent and behind the text. DOCX writes freeforms as wps:wsp with
a:custGeom (moveTo/lnTo) in a wp:anchor and reads them back, along with
simple custom-geometry freeforms from other files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303)
Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser.
table.draw takes a pen stroke in page points: a rectangle outside a table
inserts a one-cell table of that size at the paragraph under its start; a
straight vertical or horizontal line inside a table splits the cells it
crosses (new grid column or row; untouched cells span it). table.eraser
merges the two cells beside the border nearest a point. Each stroke is one
undo step. In the app the buttons toggle pen and eraser modes with a live
preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks
don't move the caret while a tool is on. Splitting through part of a
vertically merged cell and erasing beside one are refused with a clear
error. Catalog 384/430 -> 387/430.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Draw Table and Eraser: draw tables and split or merge cells with a pen (#303)
Insert › Tables › Draw Table and Table Layout › Draw › Draw Table / Eraser.
table.draw takes a pen stroke in page points: a rectangle outside a table
inserts a one-cell table of that size at the paragraph under its start; a
straight vertical or horizontal line inside a table splits the cells it
crosses (new grid column or row; untouched cells span it). table.eraser
merges the two cells beside the border nearest a point. Each stroke is one
undo step. In the app the buttons toggle pen and eraser modes with a live
preview, a crosshair with the tool's icon, and Esc to stop; canvas clicks
don't move the caret while a tool is on. Splitting through part of a
vertically merged cell and erasing beside one are refused with a clear
error. Catalog 384/430 -> 387/430.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
`crates/docx/src/xml.rs` parses untrusted document XML through
`quick_xml::reader::NsReader`. In 0.38.4 that is RUSTSEC-2026-0195 (CVSS 7.5): the
namespace resolver pushes a binding for every `xmlns` / `xmlns:*` declaration on a
start tag into an unbounded `Vec`, with no limit on the number of declarations.
None of the reader's existing caps bound that path - MAX_PART (256 MiB), MAX_TOTAL
(512 MiB), MAX_ENTRIES, MAX_DEPTH and MAX_ELEMENTS bound part size, entry count,
nesting and element count, not declarations per tag.
Measured before this change, `wordcraft-cli convert` on a crafted .docx whose
`word/document.xml` is one start tag with 21,845,333 `xmlns:a="u"` declarations:
508 KB on disk -> 262 MB decompressed (under MAX_PART) -> 1.19 GB peak RSS,
exit 0: the document opened and converted without any warning.
At the MAX_PART/MAX_TOTAL ceiling that is roughly 2.4 GB of resident memory from
about a megabyte of download, i.e. an OOM kill with unsaved work open. Nothing
panics, so the `Session::run` guard does not catch it.
0.41.0 caps namespace declarations per start tag at 256
(`DEFAULT_MAX_DECLARATIONS_PER_ELEMENT`, raisable through
`NamespaceResolver::set_max_declarations_per_element`) and returns
`NamespaceError::TooManyDeclarations` beyond it. The same fixture now fails in
about a second with a clear error and no namespace allocation:
wordcraft-cli: malformed XML: start tag declares more than 256 namespace
bindings; raise the limit with NamespaceResolver::set_max_declarations_per_element
Two API adaptations for 0.41 (0.x bumps are allowed to break):
- `resolve_attribute` moved off `NsReader` on to the resolver:
`r.resolve_attribute(key)` -> `r.resolver_mut().resolve_attribute(key)`.
- `Attribute::unescape_value` is deprecated in favour of `normalized_value`, which
also applies XML attribute-value normalization as the spec requires; attribute
values in a Word part are UTF-8 here (UTF-16 parts are transcoded first).
Regression test: `crates/docx/src/xml.rs::too_many_namespace_declarations_is_error`
asserts a tag with 10,000 declarations is an error. It fails on 0.38.4 and passes
on 0.41.0.
This also clears the second quick-xml advisory, RUSTSEC-2026-0194
(quadratic duplicate-attribute scan). That one was not reachable here - the reader
already passes `with_checks(false)` at xml.rs:257, and a 60,000-attribute tag parses
in the same 65 ms as a 20,000-attribute one - but `cargo audit` is clean afterwards.
Verified: `cargo xtask ci`; `cargo audit` (no vulnerabilities left, the two
unmaintained-crate warnings are `krilla`'s `rustybuzz`/`ttf-parser` and are
unrelated); 32 docx tests plus the malformed-input suite; and 21 crafted hostile
documents through `wordcraft-cli convert` with zero panics before and after.
One residual, unchanged by this commit: the fixture above still costs about 509 MB,
because MAX_PART lets one part decompress to 256 MiB and every part is held in memory
while the package is read. That is the existing, intended bound; what this commit
removes is the namespace allocation on top of it.
* Add Tabs and Borders and Shading dialogs (#320)
Tabs: lists the paragraph's tab stops with position, alignment and leader,
Set / Clear / Clear All and the default interval. Opens from the Paragraph
dialog's Tabs… button, by double-clicking a tab marker on the ruler, or
para.tabs without params. OK is one para.tabs call, which now takes
set / clear / clearAll / default (clearing a style's stop records a clear
stop over it).
Borders and Shading: Borders (None / Box / Shadow / 3-D / Custom, style,
colour, width, paragraph or text, preview with clickable sides), Page
Border (same, whole document or this section) and Shading (fill, paragraph
or text) tabs. Opens from Home › Paragraph › Borders › Borders and
Shading… and Design › Page Borders. OK runs only the changed parts as one
undo step. para.borders and design.pageBorders take exact "sides" (and
design.pageBorders a style and applyTo). Shadow and 3-D are drawn as
heavier sides because the model has no border shadow flag; shading
patterns are omitted because the model keeps a solid fill only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep charts, SmartArt and OLE objects when saving DOCX (#319)
Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.
The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.
The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Columns, Symbol and Field dialogs (#321)
Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).
Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.
Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.
Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #321: Field dialog descriptions in our own words (clean room)
Reword the one-line field descriptions and the empty-selection prompt so
none reads like Word's; translations keep their meaning under the new keys.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #320: test that a default-tab-only para.tabs change undoes and redoes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep charts, SmartArt and OLE objects when saving DOCX (#319)
Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.
The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.
The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Columns, Symbol and Field dialogs (#321)
Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).
Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.
Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.
Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge fix: #340's rotation round-trip test builds an Image with #333's ole field
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add Tabs and Borders and Shading dialogs (#320)
Tabs: lists the paragraph's tab stops with position, alignment and leader,
Set / Clear / Clear All and the default interval. Opens from the Paragraph
dialog's Tabs… button, by double-clicking a tab marker on the ruler, or
para.tabs without params. OK is one para.tabs call, which now takes
set / clear / clearAll / default (clearing a style's stop records a clear
stop over it).
Borders and Shading: Borders (None / Box / Shadow / 3-D / Custom, style,
colour, width, paragraph or text, preview with clickable sides), Page
Border (same, whole document or this section) and Shading (fill, paragraph
or text) tabs. Opens from Home › Paragraph › Borders › Borders and
Shading… and Design › Page Borders. OK runs only the changed parts as one
undo step. para.borders and design.pageBorders take exact "sides" (and
design.pageBorders a style and applyTo). Shadow and 3-D are drawn as
heavier sides because the model has no border shadow flag; shading
patterns are omitted because the model keeps a solid fill only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* cargo fmt after merging main
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Keep charts, SmartArt and OLE objects when saving DOCX (#319)
Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.
The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.
The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Columns, Symbol and Field dialogs (#321)
Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).
Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.
Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.
Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #321: Field dialog descriptions in our own words (clean room)
Reword the one-line field descriptions and the empty-selection prompt so
none reads like Word's; translations keep their meaning under the new keys.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep charts, SmartArt and OLE objects when saving DOCX (#319)
Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.
The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.
The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Columns, Symbol and Field dialogs (#321)
Layout > Columns > More Columns... opens a Columns dialog: One/Two/Three/
Left/Right presets, number of columns, per-column width and spacing,
Equal column width, Line between, a page preview and Apply to (whole
document, this section, this point forward). OK is one layout.columns
call, which now takes unequal widths and an apply target (forward adds a
continuous section break first).
Insert > Symbol > More Symbols... opens a Symbol dialog: font picker,
Unicode subset list that jumps through a grid of only the characters the
font has (drawn in that font), recently used symbols, a hex character
code, and a Special Characters tab. insert.symbol takes a font for the
symbol alone; typing after it goes on in the text's font. The ribbon's
Symbol gallery shows recently used symbols first.
Insert > Quick Parts > Field... opens a Field dialog: categories and the
fields WordCraft computes, date pictures, number formats, bookmark /
sequence / merge field names, preserve formatting, a live field code and
a Field Codes view to type the code directly; OK runs insert.field.
Also: date pictures read correctly when switches follow them, and am/pm
and seconds work in date and time pictures. Strings in every catalog;
gaps #7 and ui-parity updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge fix: #340's rotation round-trip test builds an Image with #333's ole field
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Keep charts, SmartArt and OLE objects when saving DOCX (#319)
Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.
The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.
The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep charts, SmartArt and OLE objects when saving DOCX (#319)
Charts and SmartArt diagrams were drawn since #292 but the writer skipped
them, and OLE objects kept only their picture, so opening and saving a
document silently dropped them.
The reader now keeps each object's own markup (the a:graphic of a chart or
diagram, the whole w:object of an OLE object) on the model object, and every
part it reaches through relationships (chart part, embedded workbook, colour
and style parts; diagram data, layout, quick style, colours and drawing; OLE
embedding and picture) as opaque bytes in Document::passthrough, with their
content types and relationships in a manifest, as the VBA project already is.
The writer emits the object with fresh relationship ids pointing at the
re-added parts, keeping each part's own relationship ids and content types.
A chart or diagram gets its frame from the object's current size and
position; an OLE object gets its new size written into its VML shape. A part
whose name the writer uses itself is renamed, and an object pasted more than
once gets its own copies of every part (sharing past a size budget). Part
counts, relationship counts, markup size and copy bytes are all capped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge fix: #340's rotation round-trip test builds an Image with #333's ole field
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
When the footnotes referenced on a page don't fit below its text, the note
that doesn't fit is split at a line boundary: its first line stays on the
page of its reference (if even that doesn't fit, the reference line moves
to the next page, as before), and the rest continues at the top of the
next page's note area, before that page's own notes, under a full-width
continuation separator. Notes still continuing after the last page get
pages of their own. Endnotes are unchanged.
The docx reader doesn't keep a document's own continuation separator or
continuation notice, so the default line is drawn and no notice.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
A few hundred KB of `*[`, `, `<` (for `>`) and `&` (for `;`). Bracket and destination scans now
share a per-paragraph look-ahead budget, tags are searched within 4 KB and entity names
within 33 bytes.
- HTML `unescape` searched the whole remaining text for `;` after every stray `&`; it now
looks only at the first 33 bytes.
- The Markdown/HTML-to-document step appended every inline through `insert_text`, which
rescans all earlier runs, so a paragraph with thousands of runs took quadratic time on
its own. It now appends runs directly and normalises once.
Ordinary links, images, tags and entities read as before.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
A script or above/below operand takes one letter unless it is grouped, as in
Word, so `⟶┴yields` (Yields) put only "y" above the arrow and the n×n Matrix's
`a_1n`, `a_m1`, `a_mn` subscripted only "1" and "m". Grouped as `⟶┴(yields)`
and `a_(1n)`, `a_(m1)`, `a_(mn)`. A new test fails when any template or
built-in equation leaves letters right after a script or limit.
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Define New Multilevel List and Track Changes Options dialogs (#328)
Home > Paragraph > Multilevel List > Define New Multilevel List edits all nine
levels with a live preview: number text and style, start, restart after a
level, linked paragraph style, alignment, aligned-at and text indent,
what follows the number with an optional tab stop, and the number's font. OK
runs the new list.define command (list.get reads a definition back). List
levels gain a restart-after level and a tab stop, which the counters and
layout honour and .docx reads and writes (lvlRestart, num tab); number
alignment now places the label.
Review > Tracking's launcher opens Track Changes Options: show comments, ink,
insertions and deletions, formatting; balloon mode; insertion and deletion
marks and colours; changed-line bars and their colour; track formatting. They
go through review.trackingOptions, are saved per user with the interface
preferences, and the page drawing honours the marks, colours, hidden
deletions, change bars and inline mode.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Parity checklist regenerated after merging main into #342
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Drawings carry an angle and horizontal/vertical flips on their placement
(Float::rot, flip_h, flip_v), read and written as DrawingML a:xfrm rot,
flipH and flipV for pictures, shapes, text boxes and groups; the written
effect extent covers the rotated bounds and reading takes that overhang
back out. Layout draws them turned about their centre (screen and PDF),
square wrap and inline lines keep clear of the rotated bounds, and hit
testing follows the turned shape. arrange.rotate now turns the frame
(90 degrees, flips) instead of rewriting pixels; arrange.rotation sets an
exact angle. The canvas frame has a rotation handle (Shift snaps to 15
degrees) and turned frames resize along their own axes; Picture Format
and Shape Format get a Rotation box and a Rotate menu. Text inside a
rotated text box stays upright for now.
Groups turn and flip as a whole (wpg:grpSpPr a:xfrm): members keep their
own turn inside the group's, are carried round its centre, and a group's
text boxes move with it. Shape effects are drawn inside the turn; a
shadow's direction follows DrawingML rotWithShape (kept on the model,
true when absent; Word's presets write 0, so their shadows keep their
page direction).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The first glyph is math italic 𝑦 only when a font has it; FreeBSD CI has none, so
it shapes as plain y. The test is about line breaks and indents, not the glyph.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A display equation wider than its column ran off the page. It now breaks
at top-level binary operators and relations (never inside a fraction,
radical, script or matrix), filling each line greedily; a term wider
than the line overflows instead of breaking inside.
- Break before the operator by default, or after it, or repeated on both
lines, as the document's m:brkBin says; m:brkBinSub picks how a minus
repeats.
- Continuation lines are indented by m:wrapIndent (one inch by default)
or set flush right with m:wrapRight.
- Manual breaks (m:brk on a run) start a new line; m:alnAt aligns it at
an operator of the first line.
- DOCX: m:mathPr in settings.xml and m:brk in math runs are read and
written back. Documents without them save as before.
- Inline equations, numbered equations and m:jc alignment are unchanged.
Tests: layout (a long sum wraps within the column with indented
continuation lines; after/repeat modes; manual breaks and alnAt; short
equations stay on one line; hostile break input) and DOCX round-trip.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* System interface theme keeps following the OS; Dark page leaves the interface alone (#311, #312)
With Interface theme set to System, every frame resolved the OS appearance
to Light or Dark and passed that to egui's set_theme. egui then sent
SetTheme(Light/Dark) to the window, which on macOS sets an explicit
NSAppearance; winit ignores appearance changes while one is set, so later
OS switches never reached the app.
Both palettes are now installed as egui's light and dark styles (with the
same spacing and text styles), and System leaves egui's preference on
System with a light fallback, so egui picks the matching style from the
OS appearance it reports and the window keeps following the OS. Manual
Light and Dark still pin the window.
Dark page (View > Switch Modes, Options > Dark page) also forced the
interface dark; it now only changes how pages are drawn and the interface
follows the Interface theme setting alone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Theme: update #314's test to the new theme::apply(ctx, Appearance) signature
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* 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>
The original squash merges of these six PRs were empty. Integrated and tested together on current main.
Co-authored-by: Valentin Klein <vangel1976@googlemail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
#305 replaced symbols the interface fonts lack with drawn icons, but the
new buttons switched state instantly and the pane close button had no
pressed state at all.
- widgets::drawn_state: the shared look for drawn buttons. Hover fades in
over egui's animation time and the pressed state blends in while the
pointer is down. Framed buttons take egui's widget visuals (fill,
stroke, text colour, expansion) so they match ui.button; frameless
ones take the ribbon's hover/pressed tokens, like the ribbon buttons.
- widgets::icon_button: a frameless icon button built on it; the panes'
close button uses it.
- icon_text_button (Editing/Reviewing, Comments, New Comment) uses it too.
widgets::tests::drawn_buttons_fade_in_on_hover_and_darken_while_pressed
drives both kinds with real pointer events at 60 fps and checks the
idle, mid-fade, hovered and pressed fills.
Claude-Session: https://claude.ai/code/session_01WtHKQ7wrZm3uBuaG1Ye8po
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>