A save regenerated word/settings.xml from the model, so every setting the
model doesn't hold was dropped: compatibility options (lost in 18 of 18
real-world Word files), rsids, document variables, proofing state and the
colour scheme mapping.
The reader now keeps the original part when it holds such settings, and
the writer merges into it: the original root and every unowned child stay
byte for byte; owned children follow the model in CT_Settings order; in
w:compat only compatibilityMode is replaced. The privacy options are the
model's since #1062, so they are owned too. Kept children that point at a
relationship (a mail-merge data source) are dropped. Anything unexpected
falls back to the generated part. Files WordCraft wrote itself keep
nothing extra.
Co-authored-by: przemek-arch <263159043+przemek-arch@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
An mc:AlternateContent whose Choice holds a cx:chart is read as a read-only chart drawn from its fallback picture, keeping both branches and every part they reach (chartEx part, style, colours, workbook, picture). Saving writes the AlternateContent back with the chart's current frame on both branches, instead of only the fallback picture.
Co-authored-by: Brandon Thomas <echelon@gmail.com>
- A position wrapped in mc:AlternateContent inside wp:anchor (wp14:pctPosVOffset)
takes the Fallback's wp:posOffset; the shapes were placed at the top of the column.
- A custGeom path that ends where it started without a:close is closed, so it's
filled as in Word; the cover's panel (and its white title) was invisible.
- Credits: add benandez to contributors/people.toml.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Shapes: rounded rectangles keep their own corner radius
A roundRect's adj (its corner in 100000ths of the shorter side) is read, kept
on the shape, saved back, and used for the screen, the PDF and Tight/Through
wrapping. Without one the corner stays at 16%.
* Render: test rounded rectangle corners through layout and rendering
* Docs: progress log and file-format parity for rounded rectangle corners
* Render: pass the shape's corner to the caret-background hit test (#299 code from main)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* #1062: honour Remove personal information / Remove date and time on save
w:removePersonalInformation and w:removeDateAndTime (ECMA-376 §17.15.1.69-70)
are read into Settings, written back in CT_Settings order, and honoured by the
DOCX writer: comment and revision authors saved as "Author" (initials "A"), no
dc:creator / cp:lastModifiedBy, no w:date on comments and tracked changes. The
open document keeps its real values. New file.privacyOptions command sets both.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1062: reading privacy options has no side effects; File › Options › Save check boxes
file.privacy reads the settings (a pure command: no undo step, no unsaved changes);
file.privacyOptions only changes them and refuses an empty call. File › Options › Save gets
a "Privacy for this document" section with the two check boxes, translated in every catalog.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* Text boxes: VML text boxes keep their internal margins, fit to their text and alignment
A VML text box's inset ("l,t,r,b", ECMA-376 Part 4 §19.1.2.22) was ignored,
so its text sat Word's default 7.2 pt in and 3.6 pt down even where the file
says 0,0,0,0, which is how letterheads and form headers place address blocks
exactly. It's now read like DrawingML's lIns/tIns/rIns/bIns: each value left
out keeps the default, a bare number is in EMUs, o:insetmode="auto" leaves
the defaults. mso-fit-shape-to-text (VML's "Resize shape to fit text") is read
as auto-fit, like a:spAutoFit, and the shape's v-text-anchor as the text's
vertical alignment. In a group, a member's margins go with that member's
text.
The reader feeds layout, so the editor, PDF and page images all change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drawings: hidden shapes and pictures are kept but not shown
A drawing marked hidden (VML visibility:hidden, floating or inline;
DrawingML wp:docPr/@hidden, or a group member's cNvPr/@hidden) was drawn,
took room and wrapped text. Word for Windows shows it in no view; it's now
kept and saved hidden, but left out of layout: not placed, no wrap area, no
room in its line, nothing drawn, its text box's notes not placed. Exports to
other formats and copies leave it out, the accessibility checker no longer
asks for its alt text, and the selection pane says it's hidden.
Word for Mac draws hidden VML shapes; Windows follows the markup.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Naxvog <4963183+naxvog@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Reads rectangles, ellipses, triangles, arrows, stars, a connector with an
arrowhead and a text box, saves and reads back: kind, size, fill, outline
and width, position, wrapping, arrowhead and text-box text all match, and
the saved XML holds a wps:wsp per shape with its preset. The data loss in
#886 was fixed on the reading side by #671; this guards it.
Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Write WordprocessingML 2003 by rewriting the DOCX writer's package into the 2003 vocabulary; file.save/saveAs take format wordml2003, File > Export lists the format, CLI convert --format wordml2003, a plain save over a 2003 file keeps it.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Word's symbol fonts are proprietary and never bundled, so text set in them
showed the substitute's letters. New wordcraft_doc::symbol_fonts maps every
code (stored as U+F020-U+F0FF, e.g. w:sym, or as a plain 8-bit code) to its
Unicode equivalent: Symbol from Unicode's Adobe symbol.txt, the Wingdings
and Webdings sets from Unicode L2/12-368 (WG2 N4384), checked against UCD.
Layout shapes the mapped characters (fallback faces draw them); PDF text,
copy as text and TXT export read as the symbols; the stored text and docx
round trips are unchanged.
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>
Found by rendering 12 US government Word documents (NIH, EPA, MCC) in
Word for Windows and WordCraft and comparing the pages side by side,
then pinning each rule down with synthetic documents measured in Word.
- Kerning: the shaper kerned every run, so WordCraft's lines held more
text than Word's and broke differently. Word kerns only a run with
Kerning for fonts (w:kern) at or above that size; no w:kern and
w:kern="0" are unkerned. New documents keep kerning from 1 pt, like
Word's Normal template.
- Automatic paragraph spacing (w:beforeAutospacing/w:afterAutospacing,
common in text pasted from web pages) was ignored. It is 14 pt at
any size, none before the document's or a cell's first paragraph,
none after a cell's last, and the larger of two neighbouring spaces
counts. Read, written back, and settable through para.spacing
(beforeAuto/afterAuto); a number, Ctrl+0 and Remove Space After
replace it as in Word.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found in NIH's monitoring-plan template compared with Word for Windows:
its footer puts the page number in a text frame (w:framePr: centred,
text wrapping around it), and Word draws "NCCIH DSMP Template" ending
just left of the number on the same line.
Before: frames other than drop caps were ignored on import, so the
number took a line of its own above the footer text (making every page
a line shorter), and the frame was lost on save.
After: a paragraph's text frame is read (size, anchors, alignment,
offsets, wrap, distances) and written back. In headers, footers, table
cells and text boxes it is placed like a floating object, sized to its
text unless it has a width, and the text after it wraps around it; the
DSMP footer now matches Word's. Frames in the body text still lay out
in line.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Read and write w:hideMark on table cells, and lay it out like Word: a cell's
empty last paragraph takes no room, spacing and all, so a row of only such
empty cells is just its borders; an ordinary empty cell keeps the row a line
tall. Measured in Word for Windows: 9 synthetic cases. NIH's Table A no
longer shows a blank row.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Found in NIH's monitoring-plan template, compared with Word for Windows.
Before: a w:numPr that gives a level (w:ilvl) but no list (w:numId) was
dropped. Outline-numbered templates do this all the time: Heading 3 is
based on Heading 2 (which names the list) and only says "level 3", so
every Heading 3 numbered as a Heading 2 (6.15 where Word shows 6.4.5).
After: such a level takes the list of the nearest style in its based-on
chain that names one (ECMA-376 17.3.1.19), in styles and in paragraphs;
with no list anywhere in the chain there is no numbering. Saved files
carry an ordinary list reference.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Word 365 cover page lost its look. The missing fills came from
theme-coloured shapes (scheme colours and wps:style fill references),
which the reporter's 0.5.0 build didn't read; #611 fixed that on main.
Two causes were still open on main:
- A freeform (a:custGeom) inside a wpg group was drawn as a rectangle.
read_wsp now reads custom geometry, so group members get it as well.
- Text boxes ignored wps:bodyPr lIns/tIns/rIns/bIns and a:spAutoFit.
Layout always used Word's default margins, so a title box with no
margins that was just tall enough for its text lost its second line.
TextBody now carries the box's own margins and auto_fit. Layout uses
those margins and doesn't cut the text of a box that resizes to fit.
Both are written back on save.
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>
Found comparing Microsoft's own specification documents with Word for
Windows: their table of contents was blue and underlined in WordCraft
and plain in Word.
Measured with nine synthetic documents in Word: a run with the
Hyperlink character style, inside a hyperlink, inside a TOC field's
result, is drawn plain (with or without the TOC's \h switch). A
Hyperlink-styled run outside a hyperlink, another character style,
direct formatting, or a hyperlink in another field keep their look.
The import now drops the Hyperlink style from exactly those runs (the
links stay), so all nine cases render as Word does.
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep inherited complex-script size, bold and italic independent from Latin
properties and retain explicit values during DOCX round trips. Preserve
ordinary formatting-command behavior and add regression coverage.
Resolve eight existing Clippy warnings without changing behavior so all
six required CI gates pass.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* M8.1: Bibliography sources DOCX round-trip
Store bibliography sources in package-root custom XML (customXml/item1.xml) with datastore item properties (customXml/itemProps1.xml), relationships, root customXml relationship (_rels/.rels), and content type overrides per ECMA-376 §15.2.
Map internal source types to Word b:SourceType values (Book, BookSection, JournalArticle, ArticleInAPeriodical, InternetSite, DocumentFromInternetSite, ElectronicSource, Report, ConferenceProceedings, Case, Art, Film, Interview, Patent, Performance, SoundRecording, Standard, Thesis, Misc) and parse them back. Write SelectedStyle and StyleName attributes on b:Sources. Use safe slice indexing for author names parsing. Scan customXml relationships and customXml/item*.xml on read, extracting sources from b:Sources in the bibliography namespace.
Add roundtrip integration tests in crates/docx/tests/roundtrip.rs verifying all parts, relationships, and attributes, update second-generation kitchen sink stability test, add hostile-input tests in crates/docx/tests/malformed.rs, and verify integration with citation styles in engine citations test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Review fixes for #532: bounded source reading, real citation style
- Read at most 10,000 bibliography sources and dedupe them with hash sets, so a hostile
file with ~100k b:Source elements can't make opening quadratic; each custom XML part is
read at most once (64 parts at most). Test: hostile_bibliography_source_count_is_capped.
- Drop the rt::BIBLIOGRAPHY relationship branch (not an OPC relationship type) and the
stray comment in write_as.
- Write the document's citation style (References > Style) in b:Sources/@StyleName and
@SelectedStyle instead of always APA: Settings::bib_style records it, the style
command sets it, opening a file reads it back into the session.
- The b: namespace goes with the other non-body namespaces (it isn't declared on
document.xml), matching #612.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep the custom XML properties relationship apart from customXml
So #612, which adds the same rt::CUSTOM_XML line, merges with this branch without a
conflict.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* clippy: pass parse_persons by value (needless_borrows_for_generic_args)
* rustfmt after merging main
---------
Co-authored-by: yefan911 <yefan911@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Shapes and connectors that take their fill and outline from the theme
(wps:style a:fillRef/a:lnRef) now show; scheme, preset, system, scRGB and
HSL colours resolve. Callouts and flowchart boxes draw as the nearest
shape. Legacy VML lines (with arrowheads), rectangles and ovals are read.
A DrawingML line runs top-left to bottom-right, as the preset does.
Rebuilt on main, which now has connectors, arrowheads and canvases (#416,
#344): those parts of the earlier branch were dropped in favour of main's.
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>
- 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>
ECMA-376 says a theme attribute supersedes the fixed value next to it, and
Word renders it that way. The reader did the opposite:
- w:color read only w:val, so a style written as
<w:color w:val="4472C4" w:themeColor="accent1" w:themeShade="BF"/>
stayed blue under a theme whose accent1 is another colour. w:themeColor
(and w:u's theme colour, and w:shd's w:themeFill) now resolve through the
theme's colour scheme and settings' w:clrSchemeMapping, with
w:themeTint / w:themeShade applied to the HSL luminance. Without a theme
part the fixed colour is kept. The mapping is read before the styles,
which come before settings in the package.
- w:rFonts preferred w:ascii over w:asciiTheme (and w:hAnsi over
w:hAnsiTheme), so a heading with both showed the fixed font instead of
the theme's major font. The theme font now wins in each slot.
Saving writes the resolved colour as w:val.
Co-authored-by: lloyd <lloyd@stackworx.io>
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>
* 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>
- 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>
- 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>
- 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>
* 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>
* 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>
* 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>
* 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>