Commit Graph
143 Commits
Author SHA1 Message Date
aiongg 211daa5862 Merge PR #1847: engine: reject malformed comment IDs before editing or caret fallback 2026-10-22 01:19:42 +00:00
6b01aa967f Shape fills: gradients and transparency (#1500) (#1605)
* #1500: shape fills keep gradients and transparency (model, DOCX read/write)

A shape's, text box's and canvas's fill is now a `Fill` (solid with
transparency, or a gradient kept as the file states it) instead of one
opaque colour, so gradients and transparency survive opening and saving.

- doc: `fill::Fill`/`Gradient` with sanitizing for hostile input; `.json`
  keeps writing opaque solid fills as `[r,g,b]` and reads both forms;
  `ShapeExtra::outline_fill` keeps an outline gradient.
- docx read: `a:solidFill` with `a:alpha`, `a:gradFill` (stops, `a:lin`,
  `a:path`/`fillToRect`, `tileRect`, `rotWithShape`, `flip`), theme fill
  styles through `a:fillRef` 1-999/1001+ with `phClr`, `a:grpFill`,
  `satMod`, outline gradients, VML `v:fill` gradients and opacity, canvas
  backgrounds.
- docx write: `a:solidFill` + `a:alpha`, `a:gradFill` as read.
- engine/docbin/layout: call sites; drawing still uses the main colour
  (next commit draws gradients).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1500: draw shape gradients and transparency (layout, render, PDF)

- layout: WordArt's paint becomes a shared `display::Paint` (solid,
  linear, radial); `paint_for` lays a fill over the shape's frame
  (linear at its angle, `scaled` honoured; path gradients circular from
  their focus to the farthest corner). Shapes, freeforms and canvas
  backgrounds use it.
- render: one `set_paint` for shapes, paths and WordArt; radial
  gradients; soft edges fade the real fill.
- pdf: axial and radial shadings with per-stop opacity.
- Review fixes: nested groups without a fill pass theirs on to
  `a:grpFill`, text-box contents don't take a group's fill, `phClr`
  transparency only on `phClr` colours, VML focus ends consistent and
  opacity clamped, `rotWithShape`/`scaled` written only when set,
  Shape Outline clears an outline gradient, shared stop sanitizing.
- docs: file-format, layout parity, gaps #1, roadmap progress log.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1500: shape.fill sets transparency and gradients on every selected shape

- shape.fill (now cmd/shape_fill.rs) takes {"color", "transparency"?} or {"gradient": {stops,
  type, angle, focus, rotateWithShape}}; a colour alone keeps the transparency; values are
  sanitized; applies to every selected shape, text box and canvas as one undo step.
- Formatting (shape.fill, shape.effects) keeps a Shift+click multi-selection, as in Word.
- view.formatShapePane toggles the Format Shape pane.
- Session::undo_mark/merge_undo_since: a pane drag that applies as it goes undoes in one step.
- Helpers for the pane: params(fill), stop add/remove/move, directions, variations and
  presets (variation and preset colours provisional until compared with Word).
- Catalog: Shape Fill Gradient, Format Shape Pane.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1500: Shape Fill › Gradient and the Format Shape pane

- Shape Fill › Gradient: No Gradient, Light and Dark Variations of the shape's colour, More
  Gradients… (opens the pane).
- Format Shape pane (right): No fill / Solid fill (colour, transparency) / Gradient fill
  (presets, type, direction, angle, stops bar with add/remove/drag and arrow keys, per-stop
  colour, position, transparency, rotate with shape). Drags preview live, one undo step.
- Right-click › Format Shape… on shapes, text boxes and canvases.
- Gradient swatches have accessible names; new labels translated (own wording, clean room).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1500: preset gradients are our own design (clean room)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1500: Gradient variations, starting gradient and directions as Word 365 does them

Observed in Word 365 (behaviour and values only):
- Shape Fill › Gradient: a No Gradient swatch, then 13 Light and 13 Dark Variations in a
  grid five wide, in Word's order; light stops tinted 66/44.5/23.5% with saturation ×1.6,
  dark shaded 30/67.5/100% with ×1.15; linear ones scaled; corners with their tile rectangle.
- Format Shape › Gradient fill starts from the theme's first accent (four stops, 0/74/83/100%).
- Corner directions in the pane get the same tile rectangle; Add/Remove sit by the stops bar.
- shape.fill carries "scaled" and "tile"; docx exports color_modified (lumMod, lumOff, tint,
  shade, satMod) so variations use the reader's own colour maths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1500: review fixes for the shape fill ribbon controls

- Gradient variations are built from the colour the shown variation came from, so picking
  variations in turn, or No Gradient, doesn't drift the colour.
- A drag's undo mark is forgotten while the Format Shape pane is closed (a stale mark could
  merge later undo steps).
- Dragging on the stops bar away from a handle no longer moves the selected stop; the arrow
  keys are taken by the bar (no focus moves or shape nudges).
- Direction and Type changes keep a linear gradient's "scaled" and an imported tile
  rectangle; only a corner direction sets its tile.
- Colour modifiers (lumMod, lumOff, tint, shade, satMod) live on Rgb in the doc crate, shared
  by the DOCX reader and the gradient builders.
- Previews build their stop list once per swatch, not per vertex.
- Translations for the pane's hint, direction and preset names.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1500: adapt to main: caret contrast samples gradient fills; upstream tests use Fill/Paint

- Paint::at gives a paint's colour and opacity at a point; the caret's background sampling
  (#299) uses it for shape fills, now gradients or transparent, and the Format Shape
  previews use it too.
- Upstream tests (#886 shapes survive saving, render) build fills as Fill/Paint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1500: adapt to main after the rebase: VML presets, rounded corners, new catalogs

- vml_fill takes the shape's shapetype: a VML preset shape (#1377) takes its fill, opacity and
  gradient from its shapetype when it states none of its own, as it already took its colour.
- Rounded-rectangle corners (#1476) and the gradient paints meet in render, PDF and the caret's
  background sampling; ShapeExtra keeps both `corner` and `outline_fill`.
- Tests that landed on main since build fills as Fill/Paint (VML presets, cover pages, VML
  freeforms, path arrowheads, PDF and render shapes).
- The 40 Shape Fill / Format Shape strings in the hr, ko, sv and vi catalogs, which main
  gained since.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz

* #1500: docs: shape gradients and transparency, Shape Fill › Gradient and the Format Shape pane

Re-applied on main's docs after the rebase: progress log, file-format, layout and UI parity
rows, gaps #1, status lines and revision history; parity checklist regenerated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz

* #1500: a theme picture fill falls back on the reference colour; VML freeform opacity note

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz

* #1500: adapt to main after the second rebase: VML shapetype tests compare Fill; parity checklist regenerated

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz

* #1500: one VML fraction parser; a preset takes its gradient from its shapetype

Review notes: story.rs's watermark/freeform opacity reuses shape_style's vml_fraction (clamped at the callers) instead of a second copy; a test pins a VML preset whose v:fill gradient comes from its v:shapetype, and the 65536ths form of a fraction.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz

* #1500: adapt to main after the third rebase: the shape duplicate preview test fills with Fill

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013kKPnx8r1vcYumjyL3kcuz

* #1500: review — harness test: right-click a shape, Format Shape… opens the pane and keeps the shape selected

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr

* #1500: review — theme keeps every fill style a fillRef can reach (999 per list), still bounded

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012WEhZXSxzS8orX7f9J3qpr

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: aiongg <aiongtaigi@gmail.com>
2026-10-21 19:12:26 -04:00
Mex bf9571d7e1 Merge branch 'main' into fix/reject-invalid-comment-ids 2026-10-20 22:41:14 +02:00
4e2568e2da RTF import: heading and code formatting compared with the file's own stylesheet (gap 19 follow-up) (#2236)
* gap 19: RTF import compares heading and code run formatting with the file's stylesheet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: review round 1: a style chain with formatting words is complete; unnumbered entries are Normal

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>
2026-10-19 12:57:16 -04:00
b8debb3050 Content controls: typed text takes the control's formatting, in any language (#3116)
* Content controls: typed text takes the control's formatting, in any language

Text typed in place of a content control's placeholder takes the
control's own run properties (w:sdtPr/w:rPr, ECMA-376 §17.5.2.27), as
in Word: in a protected form filled in Word for Mac, all 17 entered
values carry the style their control's rPr names. WordCraft kept that
rPr only as XML to write back and gave typed text the placeholder's
formatting minus the placeholder style.

It also knew the placeholder style only by its English id,
PlaceholderText. German Word writes Platzhaltertext (the name stays
"Placeholder Text"), so in forms from German Word every entry stayed
grey, on screen and in the saved file, and restoring a placeholder
added a second Placeholder Text style. The style is now found by its
built-in name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Content controls: typing drops a showing placeholder's own style

A control without run properties that shows its placeholder: text typed
over it drops the placeholder run's character style whatever its id
and name. Word localizes only the placeholder style's id, but another
program may localize its name too, and the name lookup then misses it.
Restoring a placeholder and new controls keep the name lookup.

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>
2026-10-19 12:53:44 -04:00
f1b4be8a04 Restrict Editing: forms protection keeps a form's content controls (#3112)
* Restrict Editing: forms protection keeps a form's content controls

With Filling in forms enforced (w:edit="forms"), Word greys out the
Developer tab's control buttons and Properties, and a field's shortcut
menu offers no Remove Content Control: only the fields' content can be
filled in. WordCraft refused these only outside the fillable fields, so
with the caret in one it removed the control (its content left as
plain, no longer fillable text), inserted new controls inside it, and
renamed it; only lock changes were refused.

Under forms protection, inserting any content control, Properties
(every setting) and Remove Content Control are now refused before they
run. Typing, Toggle Check Box, Choose List Item and Set Date still fill
the fields. Other protection modes are unchanged.

Observed in Word for Mac 16.113 on a protected real-world form
(72 content controls: rich text, check boxes, date pickers); the same
form filled in WordCraft opens in Word still protected, with its values,
ticks and dates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Restrict Editing: parity row states what forms protection refuses

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Restrict Editing: correct the comment on content-control property edits in forms mode

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>
2026-10-19 12:49:05 -04:00
Diego GueriniandClaude Opus 5.5 2d0169e718 A Word 2003 XML or web page saved as .doc opens by what it holds (#2853) (#2942)
* #2853: Word XML, web pages and MHTML saved as .doc open by what they hold

A Word 2003 XML Document named `.doc` went to the Word 97-2003 reader, which refused it ("Invalid
CFB file"), and the window showed a blank page. Word XML (2003 or Flat OPC), a web page and a
Single File Web Page (MHTML) under a `.doc`, `.docx` or `.rtf` name now open by what they hold,
as in Word. MHTML is read for the first time (`.mht`/`.mhtml` too): the MIME parts are split and
decoded, the page is imported as HTML in its charset and its pictures load from the other parts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #2853: leave MHTML to #1104, keep Word XML and web pages saved as .doc

#1104 adds an MHT reader (`wordcraft_formats::mht`), so this branch drops its own MHTML reader and
the `.mht`/`.mhtml` extensions instead of adding a second one. A Word XML Document or a web page
under a `.doc`, `.docx` or `.rtf` name still opens by what it holds (the #2853 fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #2853: a web page's signature needs its tag name to end (`<htmlish>` is other XML)

CodeRabbit: `starts_with("<html")` took XML with a root like `<htmlish>` under a `.doc` name for a
web page, though XML that isn't a Word document stays an error. `<html` and `<!DOCTYPE html` now
need white space, `>` or `/` after them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #2853: rustfmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:04:33 -04:00
b8b1ec6141 Mail merge review follow-ups: keep the filter on failure, cap spreadsheet text (#1287) (#1295)
* #778: .xls and legacy-encoding recipient lists; filter and sort recipients

- Recipient lists: legacy Excel 97-2003 .xls (BIFF8 reader on the cfb crate already in the
  tree); Excel 5.0/95 and password-protected .xls refused with a clear message; a CSV named
  .xls reads as text, an HTML one gets an error. .xls added to the picker filters (desktop,
  web, built-in browser) and drop handling.
- Text lists (and plain-text import) detect legacy encodings when not UTF-8/UTF-16: GBK/GB18030,
  Big5, Shift_JIS, EUC-KR, Windows code pages (encoding_rs + chardetng, MIT/Apache); an
  `encoding` param overrides.
- mailings.filterRecipients / mailings.sortRecipients: field, comparison (equal, notEqual,
  less, greater, lessOrEqual, greaterOrEqual, blank, notBlank, contains, notContains), value,
  And/Or joins; sort keys. Preview, Finish & Merge and labels use only the matching records
  in sort order. Edit Recipient List edits every record and keeps the filter.
- Recipient List dialog: Filter and Sort section and a merge-order column; catalog rows;
  strings in every catalog; docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: Hungarian and French strings for this change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review fixes for #885: total order for recipient sort, Bulgarian strings

- compare_cells now ranks numbers (compared numerically) before text (compared
  case-insensitively), so it is a total order; a column mixing 9, 10 and 1a
  could make slice::sort_by panic. Regression test sorts mixed columns.
- Bulgarian catalog: the 24 Filter and Sort strings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1287: placeholder (work in progress)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1287: keep the filter on a failed Edit Recipients; cap .xls/.xlsx cell text; dialog drops rules on unknown fields and caches the merge order

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #1287: rustfmt

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>
2026-10-18 11:56:19 -04:00
PanyangYandBrandon Thomas de8a81c1e4 References: Word sources file import in Manage Sources (#2640) (#2897)
* References: Word sources file import in Manage Sources (#2640)

Manage Sources gains Import… and references.sources an "import": path
parameter: it reads a b:Sources document (Word's own Sources.xml) into the
document's sources with the same reader as a document's own bibliography
part. A tag already here is left alone, an untagged entry is matched by
title and author or gets a tag like a new source, the whole import is one
undo step, and a file that isn't b:Sources is refused. Desktop only; the
web build hides the button.

The docx reader now shares the b:Sources parser (crates/docx/src/
bibliography.rs) with the import, with the same dedupe and 10,000-source
cap; the hostile-input cap test still passes.

Tests: docx unit tests (names, kinds, accessed/DOI fallbacks, repeated
tags, empty entries, wrong root), engine import test (add, undo, second
import, untagged matching, refused file), ui tests (the dialog button asks
for a "sources" file; finish_pick imports), wordcraft-cli end to end
(import, cite, bibliography, save, reopen; the package's customXml
carries the sources). The button's label is in every interface catalogue.

ROADMAP progress log and docs/roadmap.md, gaps.md, file-format-parity.md
and target-app-parity.md get their rows.

* References: review fixes for the sources import (#2640)

- read_sources validates the root namespace instead of the local name alone:
  an un-namespaced or unrelated <Sources> is refused, and both bibliography
  namespaces read — OOXML's and Word's own 2004/10 (the VBA Source.XML
  format), the latter mapped onto the canonical b prefix with parse_ns.
  Tests: the 2004/10 namespace is accepted; a no-namespace or unrelated
  root is refused.
- The import loop builds the tag and (title, author-or-editor) sets once and
  keeps a per-stem counter, with the stem rule shared with auto_tag
  (auto_tag_base). Importing thousands of untagged sources no longer costs
  O(n^3). Test: untagged sources sharing a stem get Riv21, Riv211, Src, and
  a second import adds none.
- docs/target-app-parity.md and docs/file-format-parity.md no longer claim
  bibliography sources aren't saved in DOCX: #169 and #532 are closed and
  the round trip exists (#790).

* References: doc comments for the sources import helpers (#2640)

Hoist the untagged-source matching helper (by_author) to module level and
document sources, auto_tag_base and sources_ui, so the functions this PR
touches meet CodeRabbit's docstring-coverage pre-merge check.

* References: re-apply the sources import's progress notes (#2640)

The rebase onto current main took upstream's copies of ROADMAP.md,
docs/file-format-parity.md and docs/gaps.md, because the rows this PR
adds conflicted with upstream's own appends. Re-apply them on the current
text: the progress log and revision rows, the bibliography row's
source-format note, and #15's evidence, with each status line's change
note. The other two documents (docs/roadmap.md, docs/target-app-parity.md)
merged cleanly and keep their rows.

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:54:47 -04:00
MarekSpodBiedryandBrandon Thomas ca6faa8c1d DOCX: Word 2007 WordArt (VML textpath) in the body is shown as text, not dropped (#2875) (#2903)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:52:01 -04:00
MarekSpodBiedryandBrandon Thomas e7cd8679ad Keep the ODT paragraph 'Keep with next' option on import and export (#2813) (#2825)
* Keep the ODT paragraph 'Keep with next' option on import and export (#2813)

* file-format-parity: keep with next on the ODT row

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:46:48 -04:00
dec24458cf #1273: equations from Word files keep all their content (#2613)
- Math straight in the body or a cell (m:oMathPara/m:oMath without w:p,
  allowed by EG_BlockLevelElts) gets its own paragraph instead of vanishing.
- mc:AlternateContent Requires="m" picks the equation, not its fallback.
- Inside an equation, runs in w:hyperlink, w:fldSimple, w:dir, w:bdo and
  mc:AlternateContent are kept (EG_PContentMath); depth stays bounded.
- w:sym, w:noBreakHyphen and Symbol-font text in math runs become Unicode.
- Layout: n-ary operators, delimiters, accents and group characters the math
  font lacks use a fallback font instead of a missing-glyph box.
- Coverage test reads every ECMA-376 §22.1 element and checks no argument's
  text is dropped; tests for body-level math, Requires="m", symbols, hostile
  nesting and the font fallback. Parity docs and progress log updated.


Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:45:01 -04:00
5fdd0a0e89 Watermarks in header table cells drawn behind the text (#1775) (#2580)
A floating picture or VML shape in a header table cell is placed on the page behind the text.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:43:55 -04:00
95b5c71f80 Save writes the chosen file when its folder refuses a temporary file (macOS privacy protection) (#2138) (#2167)
* #2138: save writes the chosen file when its folder refuses a temporary file (macOS privacy protection)

On macOS a Save-panel pick inside a protected folder (Desktop, Documents…) grants
access to that file only, so the sibling .wordcraft-save-*.tmp was likely refused
with EPERM. On PermissionDenied creating the temporary file, write the destination
directly; a remaining permission error on macOS says where to allow access.
Untested on a Mac: the fallback path is pinned on Unix with a read-only folder.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #2138: a failed save in a folder refusing the temporary file keeps the original

Review (FredApps): the in-place fallback truncated the destination before
writing, so a failure partway through lost the previous document.

The fallback now keeps the existing file's bytes in memory (up to 512 MB)
before writing it in place and writes them back if the save fails, or
removes a new file it created. If that write-back fails too, the error
says the file may be incomplete. A file over the limit isn't written in
place; the error suggests Save As.

Tests: the reviewer's partial-write test (read-only folder), a new file
removed on failure, and the size limit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW

* #2138: keep the original untouched when no safe replacement is possible

When the folder refuses the sibling temporary file, an existing destination
is no longer overwritten in place (no in-memory rollback): it stays
byte-identical and the error says to use Save As to another folder or allow
access to the folder (macOS: Privacy & Security > Files and Folders). A
destination that doesn't exist yet is created exclusively and removed again
if the write fails.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:38:04 -04:00
872b2bc99d DOCX: ink inside groups and drawing canvases survives saving (gap 2) (#2097)
* gap: nested ink WIP

* gap 2: native ink nested in groups and drawing canvases survives DOCX saves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 2: review round 1: ungrouping keeps nested ink; no orphan parts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 2: Impact cell matches the restated impact

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 2: only Word ink pins a group against Ungroup

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 2: alternate ink must hold an a:graphic to be written as standalone

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap: nested ink WIP

* gap 2: native ink nested in groups and drawing canvases survives DOCX saves

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 2: review round 1: ungrouping keeps nested ink; no orphan parts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 2: Impact cell matches the restated impact

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 2: only Word ink pins a group against Ungroup

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 2: alternate ink must hold an a:graphic to be written as standalone

Co-Authored-By: Claude Sonnet 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>
2026-10-18 11:34:42 -04:00
e50850d4dd RTF: formatting and paragraph-mark revisions are read and written (gap 19) (#2088)
* gap: RTF formatting revisions WIP

* gap 19: RTF formatting and paragraph-mark revisions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: review round 1: list kept on Reject; old formatting normalised; section marks; hostile borders

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: review round 2: \oldcprops holds only the changed properties, as Word writes it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: old formatting in styled paragraphs uses the style's size and font

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: RTF import keeps main's heading and code normalisation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gap 19: file-format-parity status line keeps main's history

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 19: a paragraph that was a list item gets its old list back on Reject

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 19: a numbered item made bulleted keeps its numbered list for Reject

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* gap 19: format matrix names the tracked section mark

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* rtf test: Writer has the section fields main added; rustfmt

---------

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>
2026-10-18 11:33:46 -04:00
072acce71b Legacy form fields are drawn: check boxes, and drop-downs' selected entry (#2076)
* Legacy form fields are drawn: check boxes, and drop-downs' selected entry

Legacy form fields keep their w:ffData since the last round, but were
not drawn: a FORMCHECKBOX has an empty result, so a form of check
boxes came out with none, and a FORMDROPDOWN showed its saved result
rather than its selected entry.

- wordcraft-doc: `legacy_form` reads what layout needs from the kept
  w:ffData (kind, name, check box state and size, drop-down entries and
  selection, text field length); `InlineObject::legacy_form()`.
- wordcraft-layout: a check box takes the text line's height of room
  and is drawn as Word draws it; a drop-down shows its selected entry
  (w:result, else w:default, else the first).

Measured on Word's PDF export with a synthetic document (Calibri):

  text size | Word square | WordCraft
  8 pt      | 7.44        | 7.45
  10 pt     | 9.96        | 9.89
  12 pt     | 12.36       | 12.33
  20 pt     | 22.08       | 22.09
  w:size 15 | 16.08       | 15.99

The square sits 1.16 pt inside the line height on every side (room on
the line within 0.2 pt of Word's), has a 0.72 pt border and, checked,
an X from corner to corner in 0.48 pt lines; w:checked beats
w:default. docs/file-format-parity.md is updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Draw::Shape takes the dash list main added

---------

Co-authored-by: Daniele Mori <Dinasty@hotmail.it>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:33:15 -04:00
70fc00c80f Symbols keep their symbol font when the text font changes (#2036) (#2037)
* Symbols: a w:sym character keeps its symbol font when the text font changes (#2036)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #2036: review round 3: docx test reads sym_font, paste drops the symbol font, astral symbols stay text

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* #2036: insert.symbol decides symbol or text per character

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-18 11:33:00 -04:00
492aceb960 Fonts Word embeds as subsets lay out the text set in them (#2240) (#2540)
* #2240: fonts Word embeds as subsets lay out the text set in them

Embedded fonts were registered under the family inside the font file. Word
embeds a subset ("embed only the characters used") under a made-up family
(___WRD_EMBED_SUB_46) and the text asks for it by the font table's name
(OBRBWW+Century Schoolbook), so it still fell back to a substitute. Each
embedded file now comes with its font table name, and its faces go by that
name when they don't already.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #2240: the black-circle fallback test passes its font's table name

main's new `circle_checks_face_coverage` test (#2582) called `add_document_fonts` with bare font
bytes; with this branch each embedded font comes with the name the document's font table gives
it, so the test passes "Times New Roman" alongside the face it renames to it.

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>
2026-10-18 10:28:52 -04:00
ReesandBrandon Thomas 9726d6f1cb fix(ui): offer Save As for imported Word files (#2448)
* SCRUM-47: offer Save As for imported Word files

* #2445: reject Save As answers for a replaced document

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:26:16 -04:00
PanyangYandBrandon Thomas a14948a23d DOCX: strict line heights with a unit are points under auto (#2329) (#2439)
Word 2024 saves Strict Open XML with measures written out with their unit:
the issue's file carries `w:spacing w:after="8pt" w:line="13.90pt"
w:lineRule="auto"` in its paragraph defaults. The reader sent `13.90`
straight into the `auto` branch, where a bare number counts 240ths of a
line (240 = single), so 13.90pt became a 13.9 multiple: 191.8 pt lines,
and the issue's document laid out as 10 pages against Word's 1.

Under `lineRule="auto"` a unit-suffixed height now reads through twips, as
Word renders it (ISO 29500 `ST_SignedTwipsMeasure`: 13.90 pt = 278 twips =
the 1.1583 multiple). A bare number still counts 240ths of a line;
`exact`/`atLeast` already read units as points.

Checked with the issue's file: 1 page with baselines on Word's (15.98 pt
line advance against Word's 16.10/15.96; paragraph advance 23.99 against
24.00; table lines 12.65 against 12.60/12.72), and a save/reopen keeps it.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:25:14 -04:00
Marco ArnulfoandBrandon Thomas 9bed0156df OpenType ligatures follow w14:ligatures: model, DOCX read and write, shaping (#2351)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:23:12 -04:00
3348e93140 ASK and SET fields show nothing (#1765) (#1785)
* #1765: WIP

* #1765: ASK and SET fields show nothing

Like Word, ASK and SET only assign text to a bookmark: they draw no
result on the page or in a PDF. The stored result is kept and saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* file-format-parity: note ASK/SET on the existing Fields row instead of a third one

---------

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>
2026-10-18 09:57:31 -04:00
7f4b649f6f RTF export: sections are saved (gap 19) (#1776)
* rtf-sect-export: WIP

* Gap 19: RTF export writes sections

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gap 19: RTF export writes sections — review fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* odt-sect-export: WIP

* Gap 19: ODT export writes sections

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gap 19: ODT export writes sections — review fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* gaps log: RTF and ODT section export both done

* Gap 19: ODT continued lists share the earlier list's style; one RTF and ODT row in the parity table

Co-Authored-By: Claude Sonnet 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>
2026-10-18 09:55:42 -04:00
da607a00ee ODT export: sections are saved (gap 19) (#1777)
* odt-sect-export: WIP

* Gap 19: ODT export writes sections

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Gap 19: ODT export writes sections — review fixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ODT export: a list cut by a section continues with text:continue-list; drop duplicate parity rows

Co-Authored-By: Claude Sonnet 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>
2026-10-18 09:37:06 -04:00
86ad3ddb63 DOCX: two lines in one and horizontal in vertical (w:eastAsianLayout) survive a save (#2486)
Read w:eastAsianLayout (ISO/IEC 29500-1 §17.3.2.10) into a new
CharProps::east_asian_layout and write it back after w:lang, in runs and
styles. Values outside the schema are left unset. Layout is unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:56:48 -04:00
Fin WarmanandBrandon Thomas fc08b0e4f6 Fix PDF outline targets after leading page breaks (#2484)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:56:29 -04:00
Marco ArnulfoandBrandon Thomas d2321d05d9 DOCX: positional tabs (w:ptab) keep their alignment, base and leader and go to the margins or indents, as in Word (#2367) (#2387)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:54:01 -04:00
c9f3da2fc5 DOCX: mixed Hebrew/Latin keeps its order in Word (runs split by direction, w:rtl and language) (#1543) (#1950)
* WIP for #1543

* DOCX: split mixed-direction runs and mark right-to-left pieces w:rtl so Word orders them as WordCraft does (#1543)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* DOCX: right-to-left pieces of split runs say their language (w:lang w:bidi) (#1543)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* DOCX: no phantom tracked formatting on split runs; typed text doesn't inherit run direction (#1543)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* Engine: text typed or inserted anywhere takes its direction from its own letters, not the caret's run (#1543)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:41:59 -04:00
c5a9c954f7 Track Changes Options: Moves (move tracking, Moved from/to marks and colours) (gap 5) (#1779)
* track-move-options: WIP

* track-move-options: Moves options in Track Changes Options and w:doNotTrackMoves

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* track-move-options: Moved from Hidden leaves the layout; per-revision hiding

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* track-move-options: Word's mark lists, (none) moves are plain, no borders on collapsed paragraphs

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* track-move-options: hr and sv translations for Moved from:/Moved to:

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:24:34 -04:00
2e062168f0 Text effects: legacy outline, shadow, emboss and imprint are drawn (#2534) (#2548)
The legacy run properties w:outline, w:shadow, w:emboss and w:imprint were
read, resolved and saved but never drawn. Layout now turns them into text
effects (hollow outlined letters; a sharp grey copy down-right for shadow
and emboss, up-left for imprint, as Word's PDFs show them) and draws them
through the existing WordArt path, so screen and PDF both get them and the
PDF keeps the text. WordArt w14 effects on the same run take precedence.

Text drawn from outlines keeps synthetic bold (a widened outline, or one
in a solid fill's colour and transparency). Colour glyphs (emoji, no
outlines) and top-to-bottom runs with upright characters are drawn as
plain glyphs, and tracked insertions and deletions keep their legacy
effects.

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>
2026-10-18 03:50:15 -04:00
abb36aceb2 #1270: pasted HTML blockquote is an indented paragraph, not Quote (#2737)
LibreOffice puts a paragraph indented on both sides on the clipboard as
<blockquote>, its margins in the page's stylesheet. Paste mapped it to the
centred, italic Quote style. On paste only, it now becomes a Normal
paragraph indented by the blockquote's stylesheet margins (else 0.5 in each
side). Opening .html files still maps blockquote to Quote.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:46:54 -04:00
ce38b0c372 #1297: copied equations go in the MathML clipboard formats Word reads (#2614)
A copy or cut of one equation on its own now also puts its MathML in the
platform's MathML clipboard formats, beside the HTML (with MathML, #816) and
the linear text: "MathML" and "MathML Presentation" (UTF-16LE) on Windows,
public.mathml.presentation and public.mathml on macOS. These are the names
W3C MathML 3 section 6.2.4 registers, and Microsoft's "MathML Support in
Microsoft 365" doc says Office checks them first when it pastes math. No
public clipboard format carries OMML. A copy with text around the equation
doesn't offer them, so Word keeps the text.

Not yet verified in Word: the parity docs say MathML clipboard formats, not
an editable paste.


Claude-Session: https://claude.ai/code/session_01P8veLveuUsJJm6UEcQPEuW

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:46:15 -04:00
Fin WarmanandBrandon Thomas 81098c52f6 CLI: allow explicit field updates during conversion (#2014)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:45:57 -04:00
4442390213 DOCX: keep the glossary document (building blocks) (#2631)
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>
2026-10-18 03:23:25 -04:00
Fin WarmanandBrandon Thomas 03e713b72f Preserve cached field result formatting (#2605)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 03:22:06 -04:00
Fin Warman fc15682fb0 Preserve REF field hyperlinks in PDF exports (#2597) 2026-10-18 03:21:29 -04:00
Mex 62bb20187d review (#1690): reject malformed explicit comment IDs 2026-10-15 21:48:27 +02:00
dac8c6d6c7 Table style review follow-ups: No Border on inherited parts, cell Top beats the style (#1288) (#1296)
part that inherits its lines sends it, and the picker only shows No Border checked for it.
- CellProps::valign is Option<VAlign>: a cell's own Top (Align Top, w:vAlign="top") beats a
  table style part's vertical alignment, and round-trips through .docx.
- tableStyle edit rejects Part fields at the top level without "part".
- "font": null or "" makes a part's font inherited; clearing the dialog's font box sends it.
- Document ui.dialog's optional "part" in docs/control-protocol.md.

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:26:42 -04:00
5b49d692a5 Table style editor formats every table style part (#714) (#887)
* #714: table style editor formats every table style part

Modify/New Table Style: "Apply formatting to" any of the 13 ECMA-376
§17.7.6 parts (whole table, header/total row, first/last column,
odd/even row and column bands, four corner cells), each with font,
size, bold, italic, underline, colour, fill, borders (line style,
width, colour; all/none/outside/inside/each edge) and the 9-way cell
alignment, over a live preview.

- doc: TablePart, PartFormat, PartSet; TableStyleParts::part/set_part
  (new parts and per-part jc/vAlign in `regions`)
- layout: generic part resolution incl. band2, corner cells, alignment
- docx: every tblStylePr type read/written with pPr jc and tcPr vAlign
- engine: table.newStyle/modifyStyle take ECMA part names, `part`,
  `parts`, per-edge `borders`, `underline`, `align`/`vAlign`/`cellAlign`
- ui: dialog moved to dialogs_table_style.rs; ui.dialog takes `part`

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* i18n: Hungarian and French strings for this change

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Review fixes for #887: Bulgarian strings for the table style parts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* #714: Vietnamese strings for the table style parts (main's new vi catalog)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* hr: match the catalog's word for banded

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>
2026-10-15 01:13:56 -04:00
109956bb90 Tables: preserve row table border exceptions (#1708)
* Tables: preserve row table border exceptions

* Tests: shorten the row border regression name

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:58:59 -04:00
7af9748ace M1: fields keep w:dirty through saves; computing a result clears it (#877) (#946)
A field begin marker flagged w:dirty by a producer that couldn't compute
its result (pandoc-style TOC placeholders) lost the flag on save, so Word
no longer refreshed the field on open. Mirror w:fldLock end to end:
FieldState, InlineObject::Field/FieldStart, the reader (fldChar and
fldSimple) and the writer (buffered, range and TOC-spanning begins).
Update Field/Update Table clear it on the fields they compute, like
Word's F9; untouched fields keep it. Round-trip and engine regression
tests, incl. a pin for #878 (imported TOC placeholder replaced in place,
fixed earlier by #617).

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 00:27:25 -04:00
mmazzieriandClaude Opus 5.5 9d4d2ea01c Legacy VML freeforms (header rule lines) are drawn and kept on save (#1659)
* 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

The watermark reader (#1328, now on main) brought its own vml_fraction;
the freeform reader had an identical copy, which no longer compiles after
the merge. Keep one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:08:59 -04:00
Brandon ThomasandClaude Opus 5.5 850bbb5dbc Docs: merge table rows that conflict resolutions left duplicated
While landing community PRs, a few rows in gaps.md, layout-parity.md and
file-format-parity.md ended up twice (an older and a newer version).
Each pair is now one row: the newer text, plus what only the older one
said (section note options #385; allowSpaceOfSameStyleInTable).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:02:53 -04:00
13070ca85e Layout: field instructions do not add blank paragraphs (#1665)
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:59:41 -04:00
29320804f2 RTF import: Mac OS Roman and DOS code pages (\mac, \pc, \pca) (#1653) (#1661)
* RTF import: Mac OS Roman text (`\mac`, `\fcharset77`) (#1653)

A Macintosh RTF document (`{\rtf1\mac …}`) had its `\'xx` bytes read as
Windows-1252: LibreOffice's fdo85889-mac.rtf showed "ñòó" instead of
"ÒÚÛ". Mac OS Roman (code page 10000, with the euro sign at 0xDB) joins
the single-byte code pages, and the reader uses it for `\mac` documents,
fonts with the Mac charset (`\fcharset77`) and `\cpg` / `\ansicpg` 10000.

Tests: the code page table (accents, euro, quotes) and two RTF cases, the
issue's file and a Mac-charset font in a Windows document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* RTF import: DOS code pages (`\pc`, `\pca`, OEM `\cpg`) (#1653)

With the OEM tables from #1146 now on main, the RTF reader decodes DOS
text too: `\pc` documents as code page 437, `\pca` as 850, a font's
`\cpg` 437/737/850/852/857/866 and `\fcharset254` (PC 437).

`encoding::code_page_byte` decodes a byte of document text in any code
page RTF names (Windows, OEM or Mac OS Roman; ASCII below 0x80 stays
ASCII), and `is_byte_code_page` says which those are.
`SINGLE_BYTE_CODE_PAGES` stays Windows-only, so `text.typeCode`'s `ansi`
pages are unchanged.

A `\cpg` / `\ansicpg` number out of the u16 range is now ignored; before,
`pv as u16` truncated it to another code page (65973 read as 437).

Tests: document text in DOS, Mac and Windows pages; `\pc`, `\pca` with a
`\cpg437` font, and an out-of-range `\ansicpg`.

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>
2026-10-14 21:29:21 -04:00
Fin WarmanandBrandon Thomas 6528f972db Shapes: draw flowchart OR spokes (#1644)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:21:49 -04:00
8b0194bc23 M12: preserve document properties when importing legacy DOC and DOT (#1640)
* M12: preserve document properties when importing legacy DOC and DOT

M12: preserve document properties when importing legacy DOC and DOT


Integrate current upstream while retaining import behavior and embedded-font loading.

* docbin: as_chunks for fixed-size records (clippy::chunks_exact_to_as_chunks on Rust 1.99); rustfmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: LabDennis <341377186+LabDennis@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:21:35 -04:00
Fin WarmanandBrandon Thomas 2c26fa58a8 Draw arrowheads on open freeform paths (#1645)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 21:04:33 -04:00
63ed4daab2 Fonts: a Bidi theme slot names the theme's complex-script font (#1619)
w:asciiTheme="minorBidi" (and majorBidi, and w:cstheme) read as the
theme's Latin font, so a template's titles came out in Calibri where
Word draws Arial. Measured in Word: the slot names the theme's a:cs
font, and an empty a:cs names nothing, so the font inherited from the
style applies.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 20:35:37 -04:00