* 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>
* 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>
* #2108: File › Print scales pages to another paper size
Scale to paper size on File › Print (Word: Pages per sheet › Scale to
paper size): No Scaling or any Layout › Size paper (Letter, Legal, A4, …).
Every page is scaled to the paper and centred, the paper turned to the
page's orientation, in the preview, the print job and Save these pages
as PDF. Native jobs also ask the spooler for that media. The document's
page size is unchanged. New `paper` param on ui.print/ui.printSubmit and
file.printPdf; unknown papers are errors.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* pdf: bookmark destinations from #2364 use the scaled Fit too
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* #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>
* 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>
* 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>
Each picture, and each embedded object or macro part kept for saving, was
copied out of the unzipped package (bytes.to_vec()), so a document with
pictures held them twice while it opened, and the package's copies were
left behind as freed memory. Package now holds its parts as Arc<Vec<u8>>,
the type the document keeps media and passthrough parts in, and hands them
over with get_shared. keep_unmodelled also searches the main part in place
instead of copying it.
A 92 MB document with 20 pictures on macOS 27: the app's footprint once
open 866 -> 772 MB (freed blocks kept 206 -> 118 MB, peak 915-932 ->
821-823 MB); wordcraft-cli info peak 319 -> 231 MB.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* 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>
* 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>
* 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>
* 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>
* Spike: Ctrl+F3 collects cuts, Ctrl+Shift+F3 inserts them and empties it (gap #24)
* Search test: a lone INSERT's witness within the box's 14 results; Table checked past it
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* 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>
* ink-arrows-2: WIP
* ink-arrows-2: Ink to Shape makes two-stroke arrows and closed-triangle heads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ink-arrows-2: review fixes — recognise each stroke once, drawing-order pairing, arrow rect
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>
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>
* Shapes: preserve DrawingML outline dashes
* Shapes: scale dashes to effective path width (#380)
* Keep the outline dash with noFill; cap the raster dashed stroke width at 200 pt
Addresses the two CodeRabbit review notes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use clamp for the dashed stroke width (clippy)
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>
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>
Indonesian spelling reads the installed Hunspell id_ID dictionary at runtime
(Debian/Ubuntu hunspell-id, LibreOffice dict-id, the Dictionaries folder or
WORDCRAFT_DICTIONARIES), like Czech, Polish and Portuguese: nothing is bundled,
and without a dictionary Indonesian text gets no spelling marks.
- ProofLang::Id for id, id-ID, ind and the legacy in tag
- suggestions reuse the dictionary-driven Czech ranking (MAP/REP/TRY/KEY of
the loaded dictionary), nothing Indonesian hard-coded
- hyphenated reduplication accepted whole or by parts (kupu-kupu, anak-anak);
hyphenated clitics after capitals/abbreviations (KTP-nya, se-Indonesia)
- generic grammar only: repeated word (offers anak-anak first), space before
punctuation, double space, with Indonesian messages
- Review > Language, status bar, Set Proofing Language, File > Options >
Proofing list Indonesian; name translated in every catalog
- tests use a hand-written synthetic dictionary; engine end-to-end test
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* 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>
Same pattern as Portuguese (#797) and Polish (#346): the cs_CZ
dictionary is read from the system at runtime (e.g. Debian/Ubuntu
hunspell-cs), nothing is bundled. Suggestions use the dictionary's own
MAP/REP/TRY/KEY; grammar stays generic (repeated word, spacing), with
digit ratios like "3 : 1" exempt. The Czech interface now writes new
documents (and opened .txt/.md) in cs-CZ.
Engine fixes the Czech dictionary needs, also used by pl/pt:
- PFX/SFX header lines may carry a trailing comment (cs_CZ has
"SFX y Y 80 #…"); rule lines are left literal;
- NOSUGGEST stems are accepted when typed but never offered;
- KEY is parsed for neighbour-key suggestions.
Tests use a hand-written synthetic Czech dictionary only; an
end-to-end engine test mirrors the Portuguese one.
Co-authored-by: Losminek <301412321+Losminek@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
* M8: keep long Web and Draft pages sharp with raster tiles
* M8: document Web Layout blur fix with before-and-after screenshots
---------
Co-authored-by: Brandon Thomas <echelon@gmail.com>
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
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>
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>