Commit Graph
226 Commits
Author SHA1 Message Date
dbebb2eca7 #2617: centre equation-array rows without & (#2690)
* #2617: centre equation-array rows without &

A row with no alignment mark sat in the first column and was right-aligned
to it, widening that column and pushing the aligned rows right (Word's
"First Property of Radicals" case equation). Word centres such a row in the
array; it no longer widens the aligned columns.

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

* #2617: review — test a lone eq-array row wider than the aligned columns

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 20:04:57 -04: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
ff3c7d8d25 Layout: text above a float's anchor wraps around it (#2048) (#2237)
* #2048: text above a float's anchor wraps around it

A floating object joined the page's wrap areas only when layout reached
its anchor paragraph, so paragraphs above the anchor ran behind a picture
placed higher up the page (fixed on the page, or moved up from its
paragraph). Layout now notes such floats and lays the pass out again
(at most 3 times) with their wrap areas known from the page's start.

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

* #2048: early-float wrap settles when the anchor moves to the next page

Review follow-up. Rewrap passes add to the wrap areas pages start with
instead of replacing them, and a pass is done when every float found
above its anchor sits inside the room its page started with. A float
whose wrap pushes its own anchor onto the next page has no fixed point;
the first page keeps the room and the float goes with its anchor, so
the passes no longer swap between the two pages. The last allowed pass
moves any float still not covered into its seeded room, or down to its
paragraph's top, so the returned layout never has text under it.

Tests: the reviewer's page-boundary case, and a last pass with nothing
seeded keeping floats clear of earlier text.

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

* #2048: review — the early-float page-boundary case is pinned for 4 to 18 paragraphs

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

* #2048: review — a column break into another width keeps the floats where the first piece drew them

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 18:48:29 -04:00
c02ceef673 Borders: triple borders draw as three lines on screen and in PDF (#1740) (#2210)
* #1740: triple borders draw as three lines on screen and in PDF

Triple borders (tables, paragraphs, the ### AutoFormat line) were drawn as
double lines. Layout now emits a Triple stroke; the renderer and the PDF
exporter draw three thin lines across the line's direction, so vertical
table borders get three side-by-side lines too.

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

* #1740: rebase onto main — triple border strokes use the line's f64 ends

Main now converts the line ends to f64 once and draws through a shared
offset-line closure; the triple stroke uses both instead of dereferencing
the shadowed values.

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

* #1740: review — PDF triple-border test checks the three strokes are distinct and 3 widths apart

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 18:32:33 -04:00
e6c5a701dd The caret and clicks land between the letters of a ligature (#1974) (#2006)
* Fix #1974: the caret and clicks land between the letters of a ligature

The glyph's advance is shared evenly among its letters (mirrored in
right-to-left text); ligature letters keep their character spacing.

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

* Fix #1974: a long word never wraps inside a ligature

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

* Fix #1974: a hyphenation point never splits a ligature

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

* #1974: ligature tests use a bundled font that draws fi and ffi as ligatures

They assumed the installed default-font stand-in draws ligatures; Liberation Sans (Linux) draws
none, so they failed there.

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 Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-19 13:09:22 -04:00
MarekSpodBiedry dbc5c3407b Size the text cursor by the font, not the line spacing (#2920) (#3011) 2026-10-19 03:05:17 -04:00
Fin Warman d588e0b873 Limit table row deferral to genuine widow splits (#2925) 2026-10-18 17:11:52 -04:00
dfgodvananddfgodvan 99ff497180 Layout: a roman page number in the footer stays roman when the body shows the page count (#2966)
A paragraph with a PAGE field is cached by the page number, the totals, the
section and the bookmarks, but not by the number format the field shows the
page number in. Paginating lays the footer out to know where the body ends,
before the section's number format is set in the field context, so as
decimal. When the body shows NUMPAGES or SECTIONPAGES, the pass that has the
right totals made that layout's key equal to the page's own footer's, and the
cached decimal one was drawn: page 2 of a section numbered in lower-case
roman showed "2", not "ii". The number format is now part of the key.

Co-authored-by: dfgodvan <180286157+dfgodvan@users.noreply.github.com>
2026-10-18 16:55:08 -04:00
100dfb37c8 Layout: a word after a slash wraps whole, like Word (#2963) (#2985)
* Layout: a word after a slash wraps whole, like Word (#2963)

UAX #14 (LB13) forbids a break between a space and a slash, and the break
after a slash is dropped on purpose (Word keeps "and/or" whole). Together
"caption /word" had no break opportunity at all, so a line too short for
both filled letter by letter and split the word. Add the break before a
slash that follows whitespace and starts a word.

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

* Layout: no break before a slash after a no-break space (#2963)

The break before a slash word took any whitespace, U+00A0, U+2007 and
U+202F included, so "Nr. /12" could wrap after the no-break space.
Only a breakable space counts now. The parity checklist lists the rule
on its own row as untested against Word.

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

* Layout: test the slash word in a fixed-width table cell (#2963)

The report came from a table cell, where the cell's own width is the
line width.

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

* Docs: gaps and typing parity for the slash-word break (#2963)

Doc wording taken from #2994 (maxm11), with the test names of this PR.

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

---------

Co-authored-by: Claude (ThinkSystem) <claude@thinksystem.local>
Co-authored-by: Claude <noreply@anthropic.com>
2026-10-18 16:41:44 -04:00
Fin WarmanandBrandon Thomas 8b789b87d1 Keep fitting exact-height rows together (#1641)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:57:23 -04:00
9b9eeb8c61 Fit inline text boxes with Resize shape to fit text to their text (#2912)
Fixes #2791

Co-authored-by: Claude Haiku 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:52:32 -04:00
Fin WarmanandBrandon Thomas fad83fd876 419: count shared row border clearance (#2789)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:50:03 -04:00
GavinQiErandBrandon Thomas fe4faca3f0 fix: do not leave a dragged object on its page (#2720)
While a floating picture, shape or text box is dragged, the page raster is
not repainted (the texture cache key does not include the drag state) and a
translucent preview is pasted at the pointer. The user therefore sees the
original copy at full opacity plus the preview, i.e. two copies of the same
object.

Exclude only the dragged object from that page's display pass, and repaint
that page once when the drag starts and once when it ends; the preview keeps
using the previous texture. The rest of the page is left untouched (no
full-page clear, no per-frame repaint).

Adds three tests: a dragged inline object and a dragged floating shape are
left out of their page, and the view key changes when a drag takes an object
off the page.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:49:30 -04:00
Fin WarmanandBrandon Thomas 596176c794 371: clear header tables for floating body tables (#2660)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:49:00 -04:00
Fin WarmanandBrandon Thomas 5909ab642a T372: wrap body around page-relative footer tables (#2659)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:47:30 -04:00
Fin WarmanandBrandon Thomas 60617d9a71 371: count shared section pages in fields (#2654)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:47:03 -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
Fin WarmanandBrandon Thomas 473df388f3 Keep the line in an empty section starting a page (#2609)
* 371: retain the line in an empty page section

* 371: resolve hidden section marks

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:44:45 -04:00
beedd1b30d Drop Cap Options dialog (#2601) (#2602)
* #2601: Drop Cap Options dialog

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

* gaps: keep the Drop Cap Options note on the dialog-depth row

* i18n: translate the two Drop Cap disabled reasons in the audited catalogs

---------

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:44:30 -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
Fin WarmanandBrandon Thomas a7abd346e7 layout: preserve subpoint table grid widths (#2589)
* 356: preserve subpoint table grid widths

* 356: normalize tiny positive grid totals

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:43:27 -04:00
Fin WarmanandBrandon Thomas b5759bcd99 351: reserve footer flow-table border clearance (#2585)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:42:54 -04:00
Fin WarmanandBrandon Thomas 5b211d6dc5 345: ignore explicit page breaks in table cells (#2574)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:42:39 -04:00
Antoine BoxhoandBrandon Thomas 61a626820e #1552: no spelling squiggle under the word being typed (#2183)
Word draws no red squiggle under the word being typed. It appears when the word ends or the
caret leaves it by a key or a click. The session records the word after a text command that
edits the caret's paragraph, with apostrophes and hyphens counted as the spelling check counts
them, and drops it once the caret is anywhere else. The layout leaves that one range out of the
spelling squiggles, and a caret move without an edit lays out only that paragraph again. F7 and
the Spelling pane still report the word, and grammar marks are unchanged.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:39:21 -04:00
divyanshu2074andBrandon Thomas 3031033790 fix(layout): support quoted bookmark names with spaces in PAGEREF fields (#2169)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:38:19 -04:00
divyanshu2074andBrandon Thomas 720f8acd2c fix(layout): support field format switches without whitespace after \* (#2147)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 11:36:58 -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
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
20188e4610 A floating object's anchor takes no room on a line (#2033)
A line's first item stays on it even when it is wider than the line;
markers before it don't count. A floating object's anchor (a zero-size
object cluster) did count, so a picture a little wider than the line,
after an anchor, went to a second line and left the anchor an empty
line of its own above it. Word keeps the picture on the first line.
Anchors are now ignored like markers when deciding whether the line
already has content.

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:32:22 -04:00
Fin WarmanandBrandon Thomas ca45f97ace Layout: exclude floating anchors from line font metrics (task 256) (#2462)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:26:32 -04:00
Fin WarmanandBrandon Thomas 57dfacfc07 Layout: preserve equation baselines under exact line spacing (#2435)
* Layout: preserve equation baselines under exact line spacing (task 243)

* layout-parity: keep the cell-margin inheritance note on the Tables row

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:26:01 -04:00
Fin WarmanandBrandon Thomas 4cb4b2d231 244: inherit omitted cell margins (#2434)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:25:38 -04:00
Fin WarmanandBrandon Thomas 4deb654862 Layout: keep narrow cell line widths (task 245) (#2436)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:24:58 -04:00
Fin WarmanandBrandon Thomas d7bd5bcd3c Layout: preserve additive spacing after section breaks (task 243) (#2433)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:24:41 -04:00
Marco ArnulfoandBrandon Thomas 8f0000de63 Text boxes: framed paragraphs run with the box's text, not drawn over each other (part of #2357) (#2383)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 10:23:27 -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
d6f20f93fd Tables: at-least rows taller than a page start on a fresh page; header rows follow (#1742) (#2004)
* WIP for #1742

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

* #1742: parity doc separates the Word-observed case from synthetic-test behaviours

---------

Co-authored-by: Dmitrii Khlapov <19991457+dkhlapov@users.noreply.github.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 09:57:47 -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
11f1d0aee6 Lowercase list items no longer get a grammar underline (#1741) (#1783)
* #1741: WIP

* #1741: list items no longer get the sentence-capital grammar squiggle

The English "Capitalize the first word of a sentence" rule skips the first
word of a list paragraph (own or style numbering); later sentences are still
checked. check_text_with and paragraph_issues take a list_item flag.

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

* #1741: review fixes

Thread the capital-first flag into the Polish, Croatian, Italian, Turkish and
Portuguese grammar rules; test the drawn squiggles through layout() for own,
style and num 0 numbering; tidy the proof test.

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

* #1741: main's Vietnamese proofing test passes the new list-item argument

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

* #1741: the third call in main's Vietnamese proofing test

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 <noreply@anthropic.com>
2026-10-18 09:57:15 -04:00
Fin WarmanandBrandon Thomas 4dcd4749ca Layout: align behind-text cell pictures (#1871) (#1871)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 09:56:19 -04:00
Fin WarmanandBrandon Thomas c92d120098 Honor table row keep-next during pagination (#2494)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:57:07 -04:00
Fin WarmanandBrandon Thomas 56d0a162b3 Share AutoFit donor reductions equally (#2464)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:55:37 -04:00
Marco ArnulfoandBrandon Thomas 61ad110e9f Layout: automatic space between Asian text and Latin text or digits (autoSpaceDE, autoSpaceDN) (#2358) (#2397)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:54:39 -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
0254f3afc7 Hebrew: field results like STYLEREF draw right to left; smart quotes match Word (#1670) (#2007)
* Field results (STYLEREF, REF, PAGE text, note marks) shown as one atomic
cluster now place their inner clusters in UAX #9 visual order, in the
direction of the text around the field, so "פרק א" in a header no longer
draws reversed. Smart quotes already matched Word's logical-order result
in Hebrew (” in acronyms, not gershayim); pinned with a typing test.

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

* #1670: fix round 1: field results shaped per bidi run, so brackets mirror correctly

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 04:42:54 -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
ab57696ba7 Draw undrawable private-use list bullets as the level's default bullet, not an icon-font glyph (#1683) (#2206)
A bullet stored as a private-use code (U+F0xx) in a font that isn't
installed, and that isn't a mapped Symbol/Wingdings code, fell back to
any installed face with a glyph at that code - an icon font such as
Font Awesome / Nerd Fonts, where U+F081 is the Twitter logo. Such a
bullet now shows the level's default bullet (• ○ ▪), so level 2 is a
hollow circle.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:03:35 -04:00
Antoine BoxhoandBrandon Thomas 1c8c352cf5 #1549: one Thai face for a run in a missing Thai font (#2175)
A paragraph in a Thai font that isn't installed (TH Sarabun New, Angsana
New, Cordia New, the UPC families…) drew its Thai letters in one fallback
face and the Latin letters, digits and baht sign in a Latin substitute at
a visibly different size. Word substitutes one font (Cordia New) for the
whole run.

Thai families now have their own substitute lists (Cordia New and
Leelawadee UI on Windows, Thonburi on macOS, Noto Sans Thai and the TLWG
fonts on Linux; serif families prefer Angsana New, Sathu or Noto Serif
Thai), and the first listed face with Latin letters and digits as well
as Thai wins. When the substitute still lacks some of the run's Thai or
Latin text, the run moves to one face covering it through
FontDb::fallback_for_run, the generalised East Asian run fallback. Only
Thai fonts get this: a missing Latin font keeps Latin text in its own
substitute, a run with its own complex-script font keeps it for the Thai
letters, and a missing roman-classed Thai font is no longer drawn in
Cambria.

Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:02:18 -04:00
Marco ArnulfoandBrandon Thomas 986d7e2a5a Layout: the caret after spaces hanging past the margin stays at the margin (#2152)
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-18 04:01:38 -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