WordCraft chose a substitute for a font that isn't installed from its
name alone, so an unknown brand font always came out sans. Word reads the
font table instead: measured with the same missing font, it draws it in
Cambria when w:family is roman and in Calibri when it is swiss or auto.
A report whose font table calls its brand font roman got a sans footer
where Word draws serif.
The font table's roman fonts are read into the settings, and layout looks
a missing one up as Cambria (Caladea where Cambria isn't installed). The
other classes keep the existing substitutes, which already start with
Calibri's metric clone.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
space after and the second's space before, as Word does by default. Word
adds them instead when the document's compatibility options include
w:doNotUseHTMLParagraphAutoSpacing. Measured in Word with five paragraphs
of 12 pt before and after: 24 pt between them with the option, 12 pt
without, the same in compatibility modes 14 and 15. A 60-page report with
the option lost 6 pt per paragraph, about 60 pt on its cover.
The option is read, laid out in the body and in boxes, and saved back.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Follow-up to #1900: the fixture test vml_preset_shapes_are_drawn also
expected the isosceles triangle for VML shape type 6 (#1599).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
DrawingML's rtTriangle was read as an isosceles triangle, so a cover
page's white corner cut-outs pointed up into a peak instead of cutting a
V into the picture above them. It is now its own shape kind, drawn with
the right angle at the bottom left, wrapped and glued along that outline,
and saved back as rtTriangle. The .doc reader maps msosptRightTriangle (6)
to it too.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
The Word XML Document (Flat OPC) reader counted only the parts it kept:
a repeated or nameless part, a child of the package that isn't a part
and anything nested inside one were read without being counted, nesting
had no cap, and a part's inline XML was copied before its size was
checked. Every child of the package now counts against the part limit
and every part's data against the part and package size limits, kept or
not; nesting and the elements of each child are capped as the XML tree
reader caps a part's; and data is checked before it is copied.
Base64 in pkg:binaryData was taken from plain text only, so CDATA and
character references were dropped and a picture written that way lost
bytes. Both now read as the same text, and an entity there is an
invalid base64 error.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: draw VML preset shapes such as Word's right arrow (#1377)
A VML v:shape that takes its geometry from a preset shapetype
(type="#_x0000_t13", o:spt) was dropped on import. Its preset number now
comes from the shape's o:spt, its v:shapetype, or Word's _x0000_tNN id,
and the presets whose geometry matches a DrawingML preset WordCraft draws
are read with their fill, outline and size (paint left out by the shape
comes from its shapetype): rectangle, rounded rectangle, ellipse,
diamond, isosceles triangle, five-point star, right arrow, line and
straight connector, heart, text box, and the flowchart process, decision
and connector. Saving writes them as DrawingML presets. Text in a preset
keeps the shape around it. MSOSPT numbers from [MS-ODRAW] 2.4.24.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* DOCX: draw unmatched VML presets as their nearest shape (#1377)
VML preset numbers now map to the DrawingML preset of the same shape and
go through preset_kind, so a preset WordCraft has no shape for (left, up
and down arrows, right triangle, callouts, four-point star...) is drawn
as the nearest one, as DrawingML presets are, instead of being left out.
The exact mappings are unchanged. WordArt text shapes, picture frames
without a picture and non-primitive shapes are still not drawn.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #1290: read Word's VML text watermark from DOCX headers
Word's Design › Watermark is a VML shape with a v:textpath in the headers.
The reader dropped it, so screen and PDF showed no watermark. It now maps
it to the document's watermark model (text, font, fill colour,
semitransparent from v:fill opacity, diagonal when turned), which the
renderers already draw. Only Word's watermark shape (PowerPlusWaterMarkObject
id) or text centred on the page both ways counts; other header WordArt,
body WordArt and shapes inside text boxes are left alone. Writing it back
to DOCX is still missing (it was already lost on save).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1290: write the watermark to DOCX as a VML text shape in the headers
The DOCX writer dropped settings.watermark, so a watermark read from Word
(or set in WordCraft) was lost on save. Every header part now starts its
first paragraph with a VML shape (ECMA-376 Part 4) whose v:textpath carries
the text, font, fill colour, semitransparency (v:fill opacity) and diagonal
turn, centred on the margins behind the text, with the reader's
PowerPlusWaterMarkObject id so it reads back as the watermark, not a shape.
The shape type is our own: a straight path the text follows, fitted to the
box. Slots the pages show with no header of their own or linked from an
earlier section (default; first page with a title page; even pages with
even/odd headers) get a header holding only the watermark; linked sections
stay linked. A blank watermark isn't written. Check Compatibility no longer
warns about watermarks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1290: give the watermark's text path a top and a bottom edge so Word draws it
With a single line as the shape type's path, Word loaded the watermark as
WordArt (right text, size and turn) but drew nothing. Checked black-box in
Word: diagonal, horizontal and header-less documents now show it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1290: test the watermark through a Word-shaped file, table-only and even-page headers
Read → write → read of a file with Word's watermark keeps the watermark, the
headers' text and paragraphs and one shape per header; header WordArt that
isn't a watermark doesn't become one. A header holding only a table gains no
line; with odd and even pages an even header is added and later sections
stay linked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: read VML freeforms (v:shape with a path) as freeform shapes
Legacy VML shapes with their own `path` (ECMA-376 Part 4), such as rule
lines in headers, were dropped on open and lost on save. They are now
read as freeforms: m/t/l/r/c/v/x/e commands in the coordsize/coordorigin
space, nf/ns, v:fill and v:stroke opacity; arcs drawn straight, formula
references read as 0. Saved as DrawingML custom geometry.
Tests: header rule read and round-tripped; hostile paths and coordinate
spaces stay finite and never panic.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: VML freeforms whose path uses formulas aren't drawn
Formulas (@n, from v:formulas) aren't evaluated; reading them as 0 drew stray lines. Parity row states the fill-opacity limit (none at 0, otherwise opaque, as for DrawingML fills).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: VML freeforms reuse the watermark's vml_fraction
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #1290: move the VML freeform reader out of this PR
The three VML freeform commits (b7a6cc7, d8fa665, 945cbb4) aren't part of
#1290; they go to their own pull request so this one stays on the
watermark.
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>
* Word forms keep their legacy form fields and rows' skipped grid columns when saved (part of #1174)
* Tests: field initializers name the new form field
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>
* DOCX: merge settings.xml into the original instead of regenerating it (#1034)
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: Claude Opus 5.5 <noreply@anthropic.com>
* DOCX: carry unmodelled parts and keep comment ids, style defaults, latent styles, list ids and app props (#1034)
Builds on the settings.xml merge.
- Parts the package or main document relate to that WordCraft neither
models nor writes (webSettings, stylesWithEffects, people, add-in task
panes, the thumbnail, future parts) are carried verbatim with their
relationships and content types; relationship types the writer
produces, and any the body refers to by id, are not (HANDLED_RELS).
- commentsIds / commentsExtensible: durable ids and UTC dates are read
into the model and regenerated against the written paragraph ids.
- w:default on localized default styles, w:latentStyles, lists'
nsid/tmpl/multiLevelType, and app.xml Template/Company/Manager/
HyperlinkBase round-trip; counts are not carried, and Company/Manager
are dropped when the file asks to remove personal information.
- Comment reference marks use the document's "annotation reference"
style id instead of a hard-coded CommentReference.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
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>
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>