- docx: Flat OPC (`pkg:package`) read by unpacking the parts into an
in-memory package for the DOCX reader; written from the DOCX writer's
parts (XML inline, binary parts in base64). Part count, part size,
total size and base64 length are capped.
- docx: Word 2003 XML (`w:wordDocument`) imported by rewriting it as the
corresponding package (hyphenated names, w:hlink, w:listPr, w:lists,
inline headers/footers, notes, aml annotations, w:binData pictures,
o:DocumentProperties) and reading that with the DOCX reader.
- `.xml` told apart by root element; other XML is refused with
"not a Word XML document". File > Export gets "Word XML Document (*.xml)"
(all 9 catalogs); .xml saves keep the document like .docx.
- Tests: Flat OPC round trip equals DOCX round trip, hand-written Flat OPC
package, Word 2003 import, hostile inputs, engine io.
Fixes#548
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Indents in characters (w:firstLineChars, w:hangingChars, w:leftChars,
w:rightChars; sprmPDxc* in .doc) are kept in the model, read and written,
and laid out in the paragraph mark's font size, winning over the points
as in Word. Indent commands cancel an inherited character indent.
- .doc: sprmPDxaLeft1 has a two-byte operand (was read as four, so every
first-line indent was lost); sprmPDxaLeft1_80 is read; LSPD line spacing
is decoded as signed dyaLine + fMultLinespace (exact 20pt read as 799pt,
a page per empty paragraph).
- A floating table placed on the page or margin that would cover the text
already on the page and doesn't fit below it moves to the next page.
- A line holding only a page break stays at the bottom of a full page
instead of leaving a blank page.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Font list: installed Chinese fonts are listed, by their Chinese names in a Chinese interface (#505)
The font database read only the English family name of each font, so a
Chinese interface listed SimSun, Microsoft YaHei and WenQuanYi Zen Hei
by their English names and never as 宋体, 微软雅黑 or 文泉驿正黑, and a
document naming a font by its Chinese name fell back to another font.
- fonts: keep each face's localized family names (name IDs 16 and 1,
every face of a collection, read during the scan without loading the
file); FontDb::families_in(lang) lists them in place of the English
names for a matching interface language (zh-hans, zh-hant, ja, ko);
face, has_family and styles accept either name.
- ui: the ribbon and mini toolbar font boxes show the interface
language's names and store the English name.
- East Asian font: choosing a Chinese, Japanese or Korean font also sets
it as the East Asian font (w:eastAsia), read and written in DOCX;
layout draws CJK text in a run's installed East Asian font.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Font dialog: separate Asian and Latin text fonts (#505)
Word's Font dialog sets the font of Chinese, Japanese and Korean text
apart from the font of Latin text; WordCraft's had one font box.
- Font dialog: an "Asian text font" box (the installed Chinese, Japanese
and Korean fonts, by their interface-language names, type to find)
above the "Latin text font" box, shown for a Chinese or Japanese
interface, East Asian text in the selection or a selection with an
Asian font. OK sets only the fonts changed, in one undo step.
- format.font takes "eastAsia" (w:eastAsia) besides "name"; either or
both; format.state reports fontEastAsia.
- fonts: the scan reads each font's OS/2 code pages, so
FontDb::east_asian_families lists CJK fonts without loading them.
- Tests: dialog sets each font on its own; layout sets CJK text in the
run's Asian font and Latin text in its Latin font (skipped without an
installed CJK font); command and undo; the scan finds a CJK font.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Numbering: Chinese, Japanese and Korean number formats (一、二、三 …) (#534)
A list with w:numFmt="chineseCounting" and lvlText "%1、" showed "2、", and the
digit was drawn with glyph ids of the CJK font that supplied the comma, so it
came out as an unrelated letter: the list label was laid out in font runs but
drawn with the style of its last run only.
- NumFormat knows every ST_NumberFormat value (ECMA-376 §17.18.59), so a file
saves back the format it was opened with; unknown values are decimal.
- New doc::cjk_numbers: Chinese/Taiwanese counting (with 十 below 100, digit by
digit above) and counting-thousand (百/千/万/亿, 零 for gaps), simplified and
traditional legal numerals, Japanese counting/legal/digital ten-thousand,
Korean counting/digital/legal, ideograph digits, heavenly stems, earthly
branches and the sixty-year cycle, aiueo/iroha (half and full width),
ganada/chosung, enclosed numbers (①, ⒈, ⑴, ㊀) and full-width digits. Values
past a sequence's end fall back to decimal.
- Legal numbering (w:isLgl) shows every level, the current one too, in decimal.
- List labels keep their font runs and draw each with its own face.
- A list level without w:ind no longer gets a 36pt indent on read.
- .doc number format codes 0x00-0x27 map to the formats (0x16 decimalZero).
- PAGE/NUMPAGES fields take \* ChineseNum1/2/3.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* rustfmt after merging main
* Layout test: end the list range inside the third paragraph (#477 skips a paragraph the range ends at the start of)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Two rows like Word's mini toolbar (font, size, grow, shrink; B I U S, highlight,
font colour, four alignments), sized from the ribbon widgets' real metrics so no
control is clipped any more (the one-row bar cut off everything after Align Left).
- Combo lists drop from the box's left edge instead of from the arrow.
- The dropdown triangle fills its box, so it no longer shrinks to a dot beside
combos and split buttons.
- Font list entries are an ink mask tinted with the theme's text colour, so they no
longer sit on white strips in dark mode.
Tests: the bar drawn over a real selection contains every control and is exactly as
wide as them; font entries take the theme text colour (dark and light) and paint no
background; the ink mask keeps coverage and drops the paper.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Find what understands Word's ^ codes (^p ^t ^? ^# ^$ ^^ ^l ^m ^n ^b ^+ ^= ^s ^~ ^- ^w ^g ^% ^v,
^nnn / ^unnnn); Replace with adds ^& (found text) and ^c (clipboard). `special: false` reads
the text literally.
- Use wildcards: ? * [abc] [a-z] [!x] {n} {n,} {n,m} @ < > ( ), \1-\9 in Replace with; always
matches case, like Word. Patterns are translated to the linear-time regex engine (no
catastrophic backtracking), length and counts capped, bad patterns are errors.
- ^p matches paragraph marks between sibling paragraphs; a ^p in the replacement splits the
paragraph. Replace All stays one undo step.
- Match prefix / suffix, Ignore punctuation / white-space characters.
- Dialog: Use wildcards checkbox, Search Options with the new options and a Special menu that
inserts codes into the focused field (a shorter list with wildcards); strings in all 9 catalogs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- `+---+------+` Enter turns the paragraph into a one-row table across the text
width, columns proportional to the hyphens, caret in the first cell (body,
top level, at most 63 columns).
- `> ` `-> ` `=> ` start a bulleted list with an arrow bullet (➢ ➔ ⇨).
- Smileys `:)` `:-)` `:(` `:-(` `:|` are replaced also straight after a word.
- Undo right after a border line or table AutoFormat restores the typed
characters and keeps the Enter (its own undo step).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
styles.updateToMatch copied only the direct run/paragraph formatting at the
caret onto the style, so text whose look came from its own style (Normal,
Title, ...) changed nothing; it then wiped that paragraph's direct formatting
without applying the style, so the selection lost its look too.
Now the style takes the selection's effective formatting (paragraph style
chain, character style chain, direct formatting) as a diff against what the
style currently gives, keeping its outline level and numbering. The selected
paragraphs take the style and drop only the direct formatting it now carries.
The Home styles gallery tiles get the right-click menu (Update to Match
Selection, Modify...).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Model (doc::smart_art): layout, items (text + level), colour variant; capped at 100 items,
255 characters, 8 levels.
- Layout algorithms of our own (docx::smart_art) place rounded rects, circles, bands, arrows and
connectors; WordCraft draws the graphic from the same dsp:drawing it saves.
- DOCX: data (dgm:dataModel points and connections), our own layout/quick-style/colour
definitions under the built-in identifiers, the drawing part, relationships and content types.
A diagram reads back editable only when its data part is exactly what WordCraft writes.
- Commands: insert.smartArt, smartArt.items/addShape/promote/demote/layout/colors/reset/textPane,
one undo step each; picture.size relays out a SmartArt graphic.
- UI: layout picker with previews drawn in code, SmartArt Design tab, Text Pane; strings in all
9 catalogs.
- Diagram reader draws `line` presets top-left to bottom-right (was the other diagonal).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Outline view: Outlining tab, levels, collapse/expand, Show Level (#493)
- View › Outline is its own view mode: the body on one long sheet, each paragraph indented
by its outline level behind a symbol drawn in code (circled plus for a heading with content,
circled minus for one without, a dot for body text); paragraph indents and alignment hidden.
- Collapsed headings hide their subtree and get a wavy line; Show Level 1-9 / All; Show First
Line Only; Show Text Formatting off draws everything in the Normal style. All of it is view
state (ViewState.outline), not saved; collapsed headings are found again by their text after
edits, and a caret entering hidden text moves on to the next shown paragraph.
- Commands: outline.promoteToHeading1/promote/demote/demoteToBody/level/moveUp/moveDown,
outline.expand/collapse/toggle/selectSubtree, outline.showLevel (+ showLevel1-9, showAll),
outline.firstLineOnly, outline.showFormatting, outline.close, outline.state. In Outline view
Move Up/Down and Promote/Demote take a collapsed heading's hidden content along.
- Keys: Alt+Shift+Plus/Minus expand/collapse, Alt+Shift+1-9 and Alt+Shift+A Show Level.
- Outlining tab (next to File while in Outline view): level box, promote/demote, move,
expand/collapse, Show Level, the two check boxes, Close Outline View. Clicking a symbol
selects the heading with its content; double-clicking expands or collapses it.
- Catalog: Outlining tab rows (421/453, 92.9%); translations in all nine catalogs; docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: drop the stale shortcut and dialog rows left by the rebase (#493)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Model: SectionProps.text_direction (horizontal, tbRl, btLr); unknown
values read as horizontal.
- DOCX: w:textDirection in w:sectPr read and written in CT_SectPr order
(after w:titlePg, before w:bidi), also inside w:sectPrChange.
- Layout: a vertical section is laid out on its page turned on its side
(margins, page borders and columns turned with it) and each page is
turned onto the real page: tbRl lines run top to bottom stacking from
the right, btLr bottom to top from the left. Pictures, shapes and text
boxes stay upright; headers and footers stay horizontal; a section
turning the other way starts a new page.
- Display: in top-to-bottom text ideographs, kana, Hangul and full-width
forms stand upright (ideographic comma/full stop moved to the upper
right); Latin lies on its side. Raster and PDF draw it unchanged.
- Hit testing: clicks outside turned lines find the nearest turned line.
- Command layout.textDirection (Layout › Page Setup › Text Direction):
turns the selected cells or the text box the caret is in, else the
section; ribbon menu and keytip.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Templates and Add-ins: attached template, update styles, Organizer (#377)
- Model + DOCX: Settings keep the attached template (w:attachedTemplate's external
relationship of the settings part) and w:linkStyles; both round-trip, written in
CT_Settings order (after w:mirrorMargins, before w:trackRevisions).
- Engine: tools.templates (state), tools.attachTemplate, tools.detachTemplate,
tools.linkStyles, tools.updateStyles (copy the template's styles and defaults, one
undo step; path or base64 data), styles.organizer (list), styles.copyFrom (copies
missing bases, linked halves and the lists a style numbers with), styles.rename.
The template path comes from the document, so it is never read automatically (not on
open either): only an explicit tools.updateStyles / attach with update reads it.
- UI: View > Templates > Templates and Add-ins dialog (path, Attach..., automatically
update document styles, Organizer...) and a two-list Organizer (Copy, Delete,
Rename...); strings in every catalog, Estonian included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review fixes for #382: never follow a template path to another machine
A document's w:attachedTemplate can name \\host\share\T.dotx, //host/share or
file://host/share. Opening it (Update Styles, Organizer) would make Windows
authenticate to that host over SMB and leak the user's NTLM credentials.
local_path now returns None for network paths; regression test added.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #377: refresh catalog count and parity checklist after rebase
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
File › Options is now a pane list (General, Display, Proofing, Save,
Language, Accessibility, Advanced, Agents), one function per pane in
crates/ui-egui/src/options.rs. Every setting shown is wired:
- General: theme, interface size (#475), mini toolbar on selection, dark page, community
button, user name and initials (comments use the initials).
- Display: tab characters, spaces, paragraph marks and hidden text shown
with Show/Hide ¶ off; rulers; print the page colour; update fields
before printing.
- Proofing: ignore words in capitals / with numbers / web and file
addresses (proof::Options, passed through layout), check spelling and
mark grammar as you type, custom dictionary add/remove (kept in ui.json).
- Save: AutoSave. Language: interface language. Accessibility: Read Aloud
speed and citations. Advanced: typing replaces selection, Insert key
overtype, overtype (text.overtype), recent documents count, measurement
units (dialogs, Layout ribbon indents and rulers).
file.options reads and sets the engine-side settings headlessly and only
opens the page when given no setting. ui.json loads leniently: a bad value
falls back to its default instead of dropping every setting.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Right-clicking a picture, shape, text box or chart selects it (as a
click would) and its context menu offers Wrap Text: In Line with Text,
Square, Tight, Through, Top and Bottom, Behind Text, In Front of Text
(current one checked), then Move with Text / Fix Position on Page.
The latter two are a new engine command, arrange.moveWithText, which
re-anchors a floating object's vertical position to its paragraph or
the page without moving it. The menu items live in one shared function
(ribbon::wrap_text_items, WRAP_STYLES) for the ribbon to reuse.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Model: `ParaProps::mirror_indents` (`w:mirrorIndents`, ECMA-376 §17.3.1.18), read and
written in DOCX; resolved `mirror_indents` and `suppress_line_numbers` (so styles count).
- Layout: a paragraph with mirrored indents moves over by its indents' difference on even
(left-hand) pages, so its left (inside) indent is on the right there.
- Paragraph dialog: Mirror indents (Left/Right become Inside/Outside), Don't add space between
paragraphs of the same style, and Formatting exceptions (Suppress line numbers, Don't
hyphenate) on Line and Page Breaks; OK sends only the options that changed, in the same
`para.set` as the indents and spacing.
- Strings in all nine catalogs; Reveal Formatting names mirrorIndents.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The font database read only the English family name of each font, so a
Chinese interface listed SimSun, Microsoft YaHei and WenQuanYi Zen Hei
by their English names and never as 宋体, 微软雅黑 or 文泉驿正黑, and a
document naming a font by its Chinese name fell back to another font.
- fonts: keep each face's localized family names (name IDs 16 and 1,
every face of a collection, read during the scan without loading the
file); FontDb::families_in(lang) lists them in place of the English
names for a matching interface language (zh-hans, zh-hant, ja, ko);
face, has_family and styles accept either name.
- ui: the ribbon and mini toolbar font boxes show the interface
language's names and store the English name.
- East Asian font: choosing a Chinese, Japanese or Korean font also sets
it as the East Asian font (w:eastAsia), read and written in DOCX;
layout draws CJK text in a run's installed East Asian font.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The canvas is now the AccessKit document node: body paragraphs become
paragraph, heading (with level) or list item (with level, grouped into
lists, list numbers as list markers) nodes holding one text run per laid-out
line with character lengths, positions, widths and word starts; tables are
table/row/cell nodes, pictures and charts image nodes named by alt text,
hyperlinks link nodes around their runs. The caret and selection are an
AccessKit text selection on the document node.
Only pages on screen and one either side are described, capped at 6000
nodes; nothing is built unless AccessKit is active, and the tree is rebuilt
only when the layout, selection, described pages or their screen place change.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- layout: footnotes referenced in table-cell lines join the page's note area (#450)
- layout: NUMPAGES/SECTIONPAGES in the body use the real totals; the body is laid out
again with the totals found (at most three extra passes); SECTION in the body counts
sections (#451)
- pdf: internal link destinations use the page and line of the bookmark's start (#455)
- docx: markers left after a final table go to its last cell's last paragraph (#464)
- docx: a field whose cached result holds a picture is kept as a range field around it,
so the picture survives save; the linked path is never read (#465)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Paste under Track Changes marks the paragraph marks it adds as inserted, so Reject All
joins the paragraphs again (#417).
- Keep Text Only / Paste Special text paste treat paragraph separators as text, not Enter:
an empty list item between them no longer ends the list (#422).
- Untracked Replace All splits paragraphs at CR / U+2029 in the replacement like a single
Replace does (#423).
- Delete Columns deletes every grid column the selected cells cover (#431).
- Split Cells refines the grid inside the split cell only; other rows' cells widen over the
new grid columns, keeping the table width (#432).
- Font dialog sends only the properties the user changed and shows small caps, all caps,
hidden and spacing from the selection (format.state reports them) (#466).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The screenshots in #486 show exactly the characters BIZ UDPGothic (the
Japanese interface face) lacks: 选项规语简户另为导 … in Simplified and
啟內另 … in Traditional, with "?" in the Inter-based medium/semibold
families (no U+25FB replacement glyph) and boxes in the proportional one.
That is the font chain of v0.4.0 and earlier, which embedded no Chinese
face and had no installed-font fallback; v0.5.0 (#248) embeds Noto Sans
CJK SC. This hardens the chain so it can't silently regress:
- The installed CJK font is chosen by coverage: it is added after the
embedded faces whenever they leave a CJK character of the CJK catalogs
or the language names without a glyph (not only when no "Hans" face
exists), and the face picked is the one mapping the most of those
characters.
- macOS: look inside com_apple_MobileAsset_Font<N>/<hash>.asset/AssetData
(bounded, one level) where macOS 10.15+ keeps PingFang.ttc, also under
/System/Library/Assets; PingFangUI.ttc as a last resort.
- Test: every CJK character of the zh-hans, zh-hant and ja catalogs and
the language names has a glyph in the embedded chain in all three
interface weights (skips without craft-fonts; fails with a Japanese-only
craft-fonts, listing exactly the characters from the screenshots).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Mirrored margins: even pages put the inside margin and gutter on the right
(text, notes, header and footer shift; page borders stay).
- A footer taller than the bottom margin moves the body bottom up.
- Headers and footers inherited via link-to-previous reserve their room in
later sections (shared `linked` helper with the drawing pass).
- Long endnotes split at line boundaries and continue in the next column or
page (bounded by MAX_NOTE_PAGES).
- Caret/selection lookup in a note collects the paragraph's pieces on the
neighbouring continuation pages, not only the hinted page.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Paragraph formatting skips the paragraph a non-empty selection ends at the start of
(Select Paragraph's end), via Document::para_paths_in (#418).
- A non-mutating command that moves the selection closes the typing group (#419).
- Change Case converts char by char and replaces only changed stretches inside one run,
keeping each run's formatting (#421) and mapping the selection to the new offsets (#420).
- Undo/redo steps carry the pending caret formatting (#424).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Compare, Restrict Editing and AutoCorrect Options dialogs (#412)
- Compare / Combine Documents dialog: original (this document or a file) and
revised document, label changes with, More >> comparison settings, word or
character level, show changes in. review.compare honours case changes,
white space, formatting (tracked formatting changes), tables (compared and
kept, no longer dropped) and character level; the LCS no longer drops text
past its size cap. Unsupported settings are accepted and reported.
- Restrict Editing pane: formatting restrictions (locked styles,
w:formatting), editing restrictions, exceptions for everyone
(w:permStart/permEnd), Start Enforcing Protection with an optional password
(SHA-512, salt, spin count 100000; ECMA-376 §17.15.1.29) and Stop
Protection. Enforced in Session::run: outside the exceptions nothing may
change; undo can't lift protection; passwords never land in Repeat/macros.
DOCX round trip of protection, hash attributes, locked styles and ranges.
- AutoCorrect Options dialog: replacement list (add/replace/delete, built-in
entries too), capitalization options with exceptions, AutoFormat As You
Type switches, Math AutoCorrect list. Saved per user in ui.json; damaged
values read as defaults. Names of days are capitalised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Restrict Editing: verify Word's legacy protection hash; refuse unsupported forms (#412)
- Stop Protection checks Word's legacy w:documentProtection hash
(w:cryptAlgorithmSid/w:hash/w:salt/w:cryptSpinCount) like Word: the password
becomes the legacy 32-bit key (ECMA-376 Part 4 §14.8.1 initial code array and
encryption matrix), its reversed bytes as an upper-case hex string are hashed
with the salt prepended and the iteration number appended. A wrong password
is rejected; the form no longer unlocks with any password.
- Unsupported or damaged hashes (MD2/MD4/MD5 and other SIDs, w:algIdExt,
unknown algorithmName, spin counts above 10,000,000, bad base64) refuse with
"This password type isn't supported" and the protection stays on. A
protection without a hash still stops without a password.
- Known-answer tests: the specification's "Example" key 0x64CEED7E and an
independently computed SHA-1 hash; pane message in all 9 catalogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* WordArt text effects and transforms, and shape connectors (#416)
- Model: WordArt text effects on runs (fill solid/gradient/none, outline,
shadow, glow, reflection), text warps on shapes (all DrawingML presets
kept, adjust values capped), connector kinds (straight, elbow, curved)
with glued ends, arrowheads and drawing ids (ShapeExtra).
- DOCX: w14:textFill/textOutline/shadow/glow/reflection in w:rPr,
a:prstTxWarp in wps:bodyPr, wps:cNvCnPr a:stCxn/a:endCxn,
a:headEnd/a:tailEnd; glued-to shapes keep their docPr ids.
- Layout/render/PDF: text with effects drawn from glyph outlines as paint
layers (gradients, shadow/glow bands, faded reflection); warped text
boxes bent along arch up/down, circle, wave, slant, inflate/deflate,
chevron and triangle; connectors and arrowheads drawn as paths.
- Engine: insert.wordArt makes a floating WordArt text box with our own
styles; format.textEffects, wordArt.style/textFill/textOutline/transform,
shape.connect; glued connectors re-route after object edits.
- UI: WordArt style gallery (Insert, Home › Text Effects, Shape Format ›
WordArt Styles), Text Fill/Outline/Effects menus, Transform previews
drawn from the warp geometry, connectors in Insert › Shapes, drag a
connector end onto a shape to glue it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix tests for ShapeExtra, regenerate parity checklist, fmt (#416)
* Regenerate parity checklist after rebase (#416)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Content controls: keep, show and edit w:sdt (#381)
Content controls are now part of the model instead of being unwrapped on open:
ControlStart/ControlEnd markers around their content (inline or around whole
paragraphs), with table, row and cell wraps for controls around tables,
repeating-section rows and cells. All sdtPr properties round-trip (title, tag,
id, lock, placeholder, temporary, rich/plain text, w14 check box, combo box,
drop-down list, date, picture, building block gallery, w15 repeating
sections); unknown children are kept verbatim. The writer plans per block list
which controls are written inline and which around blocks so the XML always
nests, and drops markers that lost their partner.
Editing: deletions keep a marker whose partner is outside the range, typing
replaces a shown placeholder, emptying a control brings it back, sdtLocked and
contentLocked refuse with a status message. Developer tab with insert
commands, Design Mode, Properties dialog; frames and title tabs on the page,
check boxes toggle on click, list and calendar pickers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #381: refresh catalog count and parity checklist after rebase
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Insert › Chart: create and edit column, bar, line, pie, area, scatter and doughnut charts (#409)
- Chart model (wordcraft_doc:💹:ChartSpec): type (clustered/stacked column and bar, line,
area, pie, doughnut, scatter), categories, series, title, legend, data labels; sanitized
(255 series, 4000 points, 100k cells, 255-char text, non-finite values dropped).
- DOCX: charts with a model are written as word/charts/chartN.xml (c:chartSpace, ECMA-376
§21.2, data as c:strLit/c:numLit, no c:externalData) with relationship and content type.
On read, a chart part becomes editable only when writing its model again gives the same
markup; every other chart stays read-only and kept as before (#319).
- Drawing reuses read/chart.rs (the model is drawn from its generated part); data labels
(c:dLbls showVal) are now drawn for bar, line, area, scatter, pie and doughnut charts.
- Commands: insert.chart, chart.type, chart.editData, chart.title, chart.legend,
chart.dataLabels (one undo step each); picture.size redraws an editable chart at its size.
- UI: Insert › Chart gallery with previews drawn in code, Chart Design contextual tab, Edit Data
grid dialog; strings in all 9 catalogs; catalog 408/439.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #409: refresh catalog count and parity checklist after rebase
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: Line Numbers, Hyphenation, Language, Envelopes and Labels (#407)
- Line Numbers (Line Numbering Options…): add numbering, start at, from
text (auto or a distance), count by, restart each page/section or
continuous; `layout.lineNumbers` takes start/countBy/distance.
- Hyphenation Options: automatic, words in CAPS, hyphenation zone,
consecutive-hyphen limit; kept in DOCX (w:hyphenationZone,
w:doNotHyphenateCaps, w:consecutiveHyphenLimit) and honoured by
layout. Manual…: `layout.manualHyphenation` finds words at line starts
that would fit hyphenated on the line above; Yes inserts an optional
hyphen, No skips.
- Language: localized language list, do not check spelling or grammar,
detect language automatically (per-user preference), Set As Default
(document default language).
- Envelopes: delivery/return address, common sizes (#10, #9, Monarch,
DL, C4, C5, C6, B5); Add to Document inserts an envelope section
before the letter. Labels: common sheets (Letter and A4), full page or
a single label at row/column, one per recipient; New Document builds
the sheet with gap columns.
- Ribbon: Line Numbering Options…, Hyphenation Options…, Manual…; the
Language, Envelopes and Labels buttons open their dialogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Remove duplicate Label: keys in ru and tr (#407)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Model: Float carries AltText (title, decorative; the description of shapes and groups);
InlineObject::alt_text/set_alt_text/is_decorative; TableProps.description. Capped input.
- DOCX: wp:docPr / pic:cNvPr / wps:cNvPr descr, title and the [MS-ODRAWXML] adec:decorative
extension read and written for pictures, shapes, text boxes, groups and members, charts;
OLE objects get their alt text in the kept XML; w:tblCaption + w:tblDescription.
- Commands: object.altText {text?, title?, decorative?} (picture.altText kept as alias),
table.altText {title?, text?}, view.accessibilityPane, view.altTextPane.
- Checker (cmd/accessibility.rs): alt text on every object and table (decorative passes),
heading order, header rows, blank table rows/columns, merged cells, WCAG contrast,
floating objects, blank paragraphs; issues carry pos/select and fix actions.
- UI: Accessibility pane (grouped, jump-to, fix buttons), Alt Text pane, Picture/Shape
Format > Accessibility > Alt Text, right-click > View Alt Text...; strings in all catalogs.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* References dialogs: Caption, Index, Table of Contents, Create Source (#402)
- Caption: labels (built-in plus New/Delete Label, kept with the interface
settings), above/below the item, leave the label out, Numbering (format,
chapter number from a heading level with a separator) -> SEQ \* \s and
STYLEREF n \s fields; Update Field renumbers SEQ with \s restarts, \r, \c, \h.
- Index: indented/run-in, columns, right-aligned page numbers with tab leader,
letter headings -> INDEX \h \c \e \r; Mark Index Entry (subentry,
cross-reference, page-range bookmark, bold/italic page numbers, Mark All)
-> XE \t \r \b \i. The index is rebuilt honouring them; columns become a
continuous section of their own. Index commands move to cmd/index.rs.
- Custom Table of Contents: page numbers, right align, tab leader,
hyperlinks, levels, Options (heading styles, other styles with levels,
outline levels, TC fields), Modify for TOC 1-9 -> TOC \o \h \z \u \t \f
\l \n \p; updating honours them (hyperlinks to _Toc bookmarks).
- Source Manager and Create/Edit Source: source types with their fields,
show every bibliography field (new Source fields); Insert Citation menu
lists the sources and Add New Source.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fmt (#402)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Draw: Lasso Select, Add Pen, Ink to Shape and Ink Replay (#395)
- draw.lasso: with no points picks up the lasso; with a loop on a page it
selects the strokes mostly inside it and drawings whose centre is inside.
draw.lassoMove / draw.lassoColor / draw.lassoDelete move, recolour and
delete the selection (one undo step each); it lasts until the document
changes otherwise. In the app: drag a loop, drag the selection to move it,
Delete deletes, Esc lets go, the Color menu recolours it.
- draw.addPen / draw.removePen: a pen gallery (kind, colour, width; capped,
validated) shown after the built-in pens, kept in ui.json; damaged entries
are dropped on reading. Right-click a gallery pen to remove it.
- draw.inkToShape: toggles Ink to Shape (pen and pencil strokes that look
like a line, rectangle/square, ellipse/circle, triangle or polygon of up to
eight corners become that shape, in the same undo step); with ink
lasso-selected it converts the selection (draw.convertInk). Our own
recogniser (cmd/ink_shape.rs): chord test for lines, closure test,
ellipse RMS fit, closed RDP simplification with corner merging.
- draw.replay: Ink Replay plays the strokes back in drawing order with a
Rewind / Play-Pause / Forward / Close bar; pages are drawn without ink
meanwhile. Each new stroke records its drawing order (Freeform::order),
written to DOCX in the drawing name (`Ink Pen 3 #12`) and read back.
Catalog 402/434 -> 406/434 (93.5%). New strings in all nine catalogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Regenerate parity checklist after rebase (#395)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Model: `SectionProps::doc_grid` (`w:docGrid` type, line pitch, charSpace) and the
paragraph `w:snapToGrid` flag; DOCX read and write (ECMA-376 §17.6.5).
- Layout: with a line grid, lines of snapping paragraphs take whole line pitches with
their text centred; with a character grid, East Asian characters take whole cells of
the character pitch (all text with snapToChars), Latin text stays proportional.
Hostile pitches and charSpace are clamped. Headers and footers don't snap.
- Command `layout.documentGrid` (type, linesPerPage, charsPerLine, linePitch,
charSpace), one undo step, section scope; catalog row under Layout › Page Setup.
- Page Setup dialog: Document Grid section; View › Gridlines draws the document
grid of a section that has one. UI strings in every catalog (incl. et, sr, sr-latn).
- Tests: DOCX round trip, line pitch and lines per page, character pitch placement,
command with clamping and undo.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Moved text is tracked as a move instead of a deletion plus an insertion:
- Model: Revision::move_name links the source (a deletion revision) and
the destination (an insertion revision) of a move; runs and paragraph
marks keep using ins/del, so everything that hides deletions or exports
the result treats the moved-away text like a deletion.
- DOCX: w:moveFrom / w:moveTo inside w:moveFromRangeStart/End and
w:moveToRangeStart/End (ECMA-376 17.13.5.21-28) are read as linked moves,
paragraph marks included; on save the ranges are written around each
moved stretch with the move's name, author and date, moved-away text in
w:t. A move missing an end, or whose range never ends, opens as a plain
deletion and insertion.
- Recording: with Track Changes on, a cut of a paragraph or at least a
sentence gets a revision of its own; pasting it right after makes both
a move (pasted paragraph marks are tracked as inserted too).
- Accept/Reject at either end resolves both; the Reviewing Pane lists
moveFrom/moveTo with the move name and "Moved from"/"Moved to".
- Markup: green, double strikethrough at the source and double underline
at the destination; "Moved from"/"Moved to" balloons.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- Model: NoteProps (pos, numFmt, numStart, numRestart) in the settings and
each section; Document::note_options resolves section > document > defaults.
- DOCX: w:footnotePr / w:endnotePr read and written in settings and every
w:sectPr in schema order; hostile numStart clamped (ECMA-376 §17.11).
- Layout: footnotes below the text, endnotes at the end of each section,
numbering restarted per section or per page (from where notes land, at
most three passes), per-section number formats.
- Commands: references.noteOptions takes kind/pos/numFmt/numStart/
numRestart/scope; references.convertNotes (footnote/endnote/swap, one
undo step); references.footnote/endnote take a custom mark.
- UI: Footnote and Endnote dialog from the Footnotes group launcher;
strings in every catalog.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- Model: TextBody on text box parts (vert, anchor, next link); chain
resolution ignores cycles, self-links and second claimants and caps
chains at 256 boxes; pruning a box clears links to it.
- Layout: text boxes lay out in their own frame and turn like table
cells (rotate 90°/270°), stacked text one letter to a line, Align Text
top/middle/bottom; linked boxes split the first box's story by line,
each at its own line length.
- DOCX: wps:bodyPr vert/anchor, wps:txbx id and wps:linkedTxbx id/seq
read and written (ECMA-376).
- Commands shape.textDirection, shape.alignText, shape.link,
shape.breakLink; ribbon Shape Format › Text group with drawn icons;
Create Link waits for a click on the target box.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Tables marked right to left (w:bidiVisual) now lay out mirrored: the first
column stands at the right edge, start alignment and indent are measured
from the right margin, and each cell's start margin and border sit on its
right. Merged cells, turned text and nested tables follow; clicks and the
caret follow the drawn geometry while Tab keeps the logical cell order.
Right-to-left sections (w:bidi) run their newspaper columns from the right,
with the line between columns mirrored.
The table direction is read and written in .docx (section direction already
round-tripped). New commands table.rtl and layout.sectionRtl, a "Table
direction" choice in Table Properties ("rtl" in table.properties) and a
"Section direction" choice in Page Setup, translated in every catalog.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Keep and draw a document's own footnote separators and continuation notice (#352)
The docx reader used to drop the special footnotes and endnotes (w:type
separator, continuationSeparator, continuationNotice), so a document's own
separator lines and continuation notice were lost on screen, in PDF and on
save. They are now kept on the document as paragraphs, with the separator
lines themselves as run objects.
Layout draws the document's separator above a page's footnotes and its
continuation separator above notes continued from the page before: the
separator objects become a short line or one across the column, and any
other text or formatting is shown as given. The continuation notice goes
under the part of a note that continues on the next page. The endnote
separator is used above the endnotes. Documents without their own keep the
default lines as before. The separators and notice can't be clicked into.
On save the special notes are written back with Word's ids -1, 0 and 1 (the
notes follow them, starting at 2 when there is a notice) and listed in the
settings' footnotePr/endnotePr. Documents without them keep the default
output.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge fix: NoteSeps is Clone (the layout context that holds it is cloned)
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Insert > Illustrations > Shapes > New Drawing Canvas (and Draw > Drawing Canvas)
inserts a 6" x 3" canvas in line with text. While it is selected, Pictures,
Shapes and Text Box put the new object inside it. Fit, Expand and Scale Drawing
change its frame, Shape Fill and Shape Outline colour its background and border,
and canvas.member moves, resizes or removes a member.
The canvas is the group object with a canvas style: its frame is independent of
its members, and members outside it are clipped on screen and in PDF (new
Draw::Clip / Placed::Clip). DOCX reads and writes wpc:wpc with wpc:bg,
wpc:whole and wps:wsp / pic:pic members.
A canvas turns and flips as a whole, like a group (#332): its background,
outline and clipped members are drawn inside its turn (Draw::Rotated around
Draw::Clip; Placed::Clip carries the spin, its members sit in the unturned
frame), and hit testing follows the turned frame. The canvas schema has no
transform of its own, so a turned canvas keeps its rot/flipH/flipV as an
a:xfrm in a wpc:extLst extension (urn:wordcraft:canvas-xfrm), which Word skips.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
When a multi-column section is followed by a section that starts
continuous, the columns on its last page are laid out again with the
shortest common height that still holds the text (a binary search on
the column height over at most 12 passes of that page), filled left to
right, and the next section starts right below the tallest column.
Keep-together, keep-with-next, widow/orphan control and rows that can't
split still apply, so the columns may come out slightly uneven. Pages
with an explicit column break, sections ending with a next-page,
even- or odd-page break, and one-column sections are not balanced.
Unequal column widths work. A continuous section now always starts
below the tallest column on the page, not the last one.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Select Recipients › Use an Existing List… now reads .xlsx, .xlsm and .ods
workbooks besides CSV. A new read-only SpreadsheetML / ODF spreadsheet
reader in wordcraft-formats lists the sheets and turns one sheet into rows
of display strings: shared, inline and rich strings, numbers, booleans,
cached formula results, dates and times as ISO 8601 from the cell's number
format, sparse cells, ODS value types, repeated rows/columns and covered
cells. Zip entries, rows, columns, cells, string lengths and repeat counts
are capped, and nothing recurses.
mailings.recipients takes "data" (base64) and "sheet" and "headers"; a
workbook with several sheets and no sheet answers with the sheet names, and
the app shows a Select Table dialog with "First row of data contains column
headers". The desktop picker, dropped files and the web inbox route
spreadsheets to the recipient list.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Adds wordcraft_render::icon_lib, 61 monochrome icons in ten categories
(Arrows, Communication, Office, People, Nature, Weather, Shapes, Symbols,
Technology, Transport). They are original WordCraft artwork: simple
geometric line drawings designed on a 24x24 grid as path data in code, with
no icon files and nothing copied from another icon set.
insert.icon {"id", "color"?, "size"?} renders the chosen icon with vello_cpu
to a transparent PNG at about 300 ppi and inserts it at the caret through the
ordinary picture path, so it saves to .docx and every other format. Without
an id it lists the library (narrowed by query and category) and asks the
front end for the picker. picture.iconColor repaints a one-colour picture
in a new colour and keeps its transparency.
Insert > Illustrations > Icons opens a dialog with a search box, category
chips and a grid of icons drawn live by the egui painter from the same path
data; Insert or a double click places the selection. Picture Format > Adjust
gains an Icon Color menu. New strings are translated in every catalog.
Catalog parity 393/434 -> 394/434 (90.8%).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Home › Font › Enclose Characters: circle, square, triangle or diamond around a character (#297)
New command format.enclose {"shape": "circle|square|triangle|diamond" | null,
"style"?: "shrink|enlarge", "text"?: string} puts one full-width or up to two
half-width characters in an enclosure, changes its shape, or (null) takes the
text back out. It is stored the way ECMA-376 describes it: an EQ field with the
overstrike switch, eq \o\ac(○,字), with the text as its plain-text result.
Layout now draws EQ \o fields: the arguments overstruck as one cluster
(centred, \al or \ar). An enclosure symbol among them is drawn as a vector
shape around the text in the text's colour, so it never depends on the font
having ○ □ △ ◇; "shrink text" scales the text into a one-em shape, "enlarge
symbol" grows the shape around text at its own size. Screen and PDF both get
it through the display list. Imported Word documents with enclosed characters
used to show nothing (the field has no result); they now render.
.docx: written as Word stores enclosed characters (begin, the code split so the
symbol and the text have their own run sizes, end, no result); the reader
recovers the style from those sizes. The style lives in the model as a trailing
\* enlarge on the code, never written to a file.
Home › Font gets an Enclose Characters button that opens a dialog (Style:
None / Shrink text / Enlarge symbol, Text, Enclosure list, a drawn preview);
scripts with a shape never see it. Strings in every interface catalog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge fix: keep both sides (enclosures and ruby; tests); regenerate parity checklist
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Phonetic Guide: ruby text over base text (#288)
Home › Font › Phonetic Guide (format.phonetic) sets small ruby text
(furigana, pinyin) over the selected text. The ruby is one inline object
anchored at U+FFFC whose plain text is the base, so selection and the
caret treat it as one unit and exports read the base text.
- Model: InlineObject::Ruby with base, ruby, alignment (ECMA-376
ST_RubyAlign), ruby size, raise, base size, language and the ruby's own
formatting.
- Layout: the base is drawn in its run's formatting, the ruby above it by
alignment (centre, distribute letters/space, left, right); the wider of
the two sets the width and the line grows to fit the ruby. Drawn in the
shared display list, so screen and PDF both show it.
- DOCX: w:ruby read and written with w:rubyPr (rubyAlign, hps, hpsRaise,
hpsBaseText, lid), w:rt and w:rubyBase runs with their formatting.
Previously only the base text was kept.
- Command format.phonetic {"ruby", "align"?, "size"?, "offset"?}: sets,
edits or (null/"") removes the ruby, undoable; without "ruby" it opens
the dialog and returns the selection's phonetic guide.
- UI: Phonetic Guide button in Home › Font and a dialog (base text, ruby
text, alignment, offset, size, Remove), strings in every catalog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Merge fix: keep both sides' tests
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Tight and Through wrapping follow the object's outline (#354)
Text now wraps around a Tight or Through object's shape instead of its
bounding box. The outline is the object's wrap polygon (wp:wrapPolygon,
in the 21600-unit space of its frame) when it has one; otherwise it is
derived: a shape's geometry (ellipse, triangle, star, heart... flattened),
the convex hull of a picture's opaque pixels (alpha threshold on a coarse
grid, at most 64 points), or the rectangle. The outline turns and flips
with the object, keeps the left/right and top/bottom distances from text,
and is built once per placed object; each line asks it which stretches of
its band it covers. Through also lets text into gaps inside the outline
that are wide enough.
DOCX reads and writes wp:wrapPolygon (with edited) for wrapTight and
wrapThrough; a shape without one gets its outline written, other objects
their rectangle, since the schema requires a polygon. Square, top and
bottom, behind and in front of text are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Choose which side of an object text wraps on (#354)
Square, Tight and Through objects now have a wrap side, like Word's
"Wrap text" choice: both sides (the default), left only, right only, or
the largest side. Left keeps the band to the object's right empty on
every line beside it, Right the band to its left, and Largest picks the
wider side line by line. It works with contour wrapping and with turned
objects, and right-to-left paragraphs mirror it.
The setting is Float::wrap_text, read from and written to wrapText on
wp:wrapSquare, wp:wrapTight and wp:wrapThrough (previously always
written as bothSides). The new arrange.wrapText command
{"side": "bothSides|left|right|largest"} sets it on the selected object
as one undo step. The ribbon's Wrap Text button is now a menu with the
wrap styles and a Wrap Text Sides submenu, translated in every
interface language.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Text now wraps around a Tight or Through object's shape instead of its
bounding box. The outline is the object's wrap polygon (wp:wrapPolygon,
in the 21600-unit space of its frame) when it has one; otherwise it is
derived: a shape's geometry (ellipse, triangle, star, heart... flattened),
the convex hull of a picture's opaque pixels (alpha threshold on a coarse
grid, at most 64 points), or the rectangle. The outline turns and flips
with the object, keeps the left/right and top/bottom distances from text,
and is built once per placed object; each line asks it which stretches of
its band it covers. Through also lets text into gaps inside the outline
that are wide enough.
DOCX reads and writes wp:wrapPolygon (with edited) for wrapTight and
wrapThrough; a shape without one gets its outline written, other objects
their rectangle, since the schema requires a polygon. Square, top and
bottom, behind and in front of text are unchanged.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Customize Keyboard dialog (#368)
Tools > Customize Keyboard (tools.customizeKeyboard): pick a category and
a command, see its keys, press a combination to see which command uses it,
then Assign / Remove / Reset All. Also reachable from File > Options
(Keyboard shortcuts: Customize...) and command search.
- engine: KeyMap (assigned key -> command, removed built-in keys), consulted
before the registry's built-in shortcuts; keys compared in one canonical
form. Saved maps read leniently: junk entries and unknown commands are
dropped, never failing the rest of the preferences.
- tools.customizeKeyboard: with params (assign/remove/reset/command/key) it
only changes or reports the map; without params it opens the dialog.
- ui: key dispatch and menu/tooltip shortcut text use the user map; the map
persists in ui.json; more keys (Q, F1-F12, 4/6/7, Insert) can be bound.
- i18n strings in every catalog; docs/ui-parity, gaps #24, ROADMAP log.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Customize Ribbon and Quick Access Toolbar (#379)
Tools > Customize Ribbon (tools.customizeRibbon): one dialog with two
pages. Ribbon: commands by category next to the ribbon's tabs; show or
hide and reorder tabs, add custom tabs and custom groups (on built-in or
custom tabs) holding any commands, rename, remove, reset. Quick Access
Toolbar: add, remove and reorder its commands, reset.
- engine: RibbonLayout (tab order and visibility, custom groups, the
toolbar's commands). Saved layouts read leniently: junk, duplicates,
reserved names and unknown commands are dropped.
- tools.customizeRibbon: with params (hide/show, newTab, newGroup, add,
remove, rename, move, qatAdd/qatRemove/qatMove, reset, layout) it only
changes or reports the layout; without params it opens the dialog.
- ui: the tab strip and ribbon follow the layout (custom groups draw the
commands' own icons, else a lettered tile); the Quick Access Toolbar
shows the user's commands; right-click any ribbon button to add it to
(or remove it from) the toolbar or open the dialog; the toolbar's ...
button opens a menu of common commands and More Commands... (#376);
File > Options has a second Customize button; layout persists in
ui.json under `ribbon`.
- Open and Print icons drawn in code; i18n strings in every catalog;
docs/ui-parity, gaps #24, ROADMAP log, parity checklist.
Fixes#376.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Tools > Customize Keyboard (tools.customizeKeyboard): pick a category and
a command, see its keys, press a combination to see which command uses it,
then Assign / Remove / Reset All. Also reachable from File > Options
(Keyboard shortcuts: Customize...) and command search.
- engine: KeyMap (assigned key -> command, removed built-in keys), consulted
before the registry's built-in shortcuts; keys compared in one canonical
form. Saved maps read leniently: junk entries and unknown commands are
dropped, never failing the rest of the preferences.
- tools.customizeKeyboard: with params (assign/remove/reset/command/key) it
only changes or reports the map; without params it opens the dialog.
- ui: key dispatch and menu/tooltip shortcut text use the user map; the map
persists in ui.json; more keys (Q, F1-F12, 4/6/7, Insert) can be bound.
- i18n strings in every catalog; docs/ui-parity, gaps #24, ROADMAP log.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* 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>