A nested altChunk DOCX adds its records past its 256-entry list to the outer
total. file.strictReport's params doc lists source_part, the snake_case codes
and the totals. The human line says "300 characters cannot be drawn (first 256
listed)" when a list is cut. README gains the picture stderr line and the
possible source_part values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
JSON reasons are snake_case codes (missing_part, external_target, link_only,
undecodable, unsupported_format); "in" becomes "source_part" (full story part);
total_missing_glyphs and total_missing_pictures count past the 256 caps (code
point bitset, saturating picture counter). Human lines unchanged. README
documents the schema. Layout missing_glyphs tests call test_fonts().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
render --strict passes strict to file.exportPng and, on refusal, prints the
report for the same page (human lines, then the JSON line) on stderr, exits 3,
and prints nothing on stdout. convert and render share one refusal helper.
Adds the engine guard that file.exportPng with strict off is byte-identical.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
Page background picture and watermark text are checked like the display list
(watermark shaped by one shared helper used by the PDF export, the page raster and
the check). Picture side limit is per output (PDF 30000, PNG 65535). Separate
truncation flags per list. Glossary pictures name the glossary part.
MissingPictures::push dedupes in O(1). Hostile params cover strict, README note,
ToUnicode regression test for chart and equation runs.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
Engine: StrictReport (missing glyphs + missing pictures, schema-1 JSON), pure
file.strictReport, and strict: true on file.save/saveAs/exportPdf/exportPng that
refuses with "strict: ..." before any write. PDF strict lays out once through
wordcraft_pdf::export_layout; PdfOptions now owns the display/layout options the
exporter and the check share. CLI: --strict, exit 3, report plus JSON line on
stderr; a non-pdf/png output is rejected before the input is read.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
doc: Document.missing_pictures (MissingPicture, MissingPictureReason), capped at 256,
never saved, ignored by ==.
docx reader: records a missing part, an external target, and r:link / VML r:href
only pictures with their story part; nothing is fetched.
geom: decode_for_drawing / check_for_drawing, the one decision pdf and render now
take (output unchanged, pinned by golden hash tests); layout: media_check and
undecodable_pictures walk a layout's drawn pictures per story.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FypgCX8G9hbs6XYZFsgjbL
* #5843: resolve LibreOffice semicolon-separated font fallback lists to first installed family
* #5843: address review comments on font fallback list resolution
* #5843: verify fallback list text on cache hit and accept Times New Roman in tests
* #5843: bound the fallback lists read from a DOCX and reuse their classification
A document could name thousands of fallback families in `w:ascii`/`w:hAnsi`.
Nothing bounded that value, and the name-based rules (roman classification,
Thai family, named substitutes) then scanned every candidate for each shaped
piece, so opening the document took seconds.
`w:ascii` and `w:hAnsi` now keep the leading families within MAX_FONT_NAME
bytes, cut at a `;` so no name is split, as `w:cs`/`w:eastAsia` already bound
their names. The family a real list would pick is kept: a LibreOffice list
names a few families, well under the bound.
The Thai-family and named-substitute answers are worked out once per list and
kept in the fallback-list cache, keyed by the list text, so shaping no longer
rescans candidates for every piece. Resolution still reads every candidate.
A 500-paragraph document whose font names 20,000 candidates laid out in 10.8 s;
bounded on read it takes 17 ms.
Tests:
- a hostile fallback list keeps its leading families, cut on a `;`
- a real LibreOffice list is read whole
- a later Thai or named fallback still classifies, and lists are cached
* picture.change: keep the width and take the new picture's aspect ratio
Change Picture swapped only the media key, so a replacement of another
shape was stretched into the old frame (a revised figure came out ~2%
taller in a real manuscript). Keep the display width, set the height
from the new picture's pixel size, and drop the old crop, which
described the old picture. Regression test included.
* docs: progress log and parity entries for picture-change-aspect
---------
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
* Proofing: Check spelling as you type is one setting for every open document (#5740)
Each open document's view kept its own copy of the setting. Turned off with one document active, it stayed on in the others, and the settings saved for the next run took the value of whichever document was active then, so it came back on. A document made active now takes the saved preference, as Read Mode's page colour already does.
* Contributors: credit ashokram-si as Ashok Ram
* Proofing: every view made active follows the app-wide settings, a window's view put back too (#5740)
put_view restored a window's view with the Check spelling as you type value it had when it was taken, after switch_to had applied the shared preference, so the settings saved for the next run could take the old value again. One ViewState::follow_prefs now applies the app-wide settings a view keeps a copy of (Read Mode's page colour and Check spelling as you type) wherever a view is made active: switch_to, a new document's view, set_document and put_view.
* test(ui): Check spelling off in a second window stays off in every window (#5740)
The whole sequence through the app: View › New Window, spelling turned off in the second window, the first window's own view put back (put_view), a new document's window, back to the second window, then the saved settings read by a new app. Fails if put_view doesn't follow the setting.
---------
Co-authored-by: ashokram-si <347905169+ashokram-si@users.noreply.github.com>
Rejecting an inserted paragraph (or accepting a deleted one) whose mark carries a section break
left it empty; remove_empty_para refused it for the break, so the next paragraph was joined into
it and took its properties (style, numbering, indents). The empty paragraph now goes whole and
its section joins the following one first, as deleting the break does (remove_section_break).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
* Count locked SEQ fields and not tracked-deleted ones in output
Exporting, Word counts a locked SEQ, its \r too, though the field keeps its result, and a SEQ in a tracked deletion keeps its result and doesn't count, with markup shown or hidden (measured in Word for Windows). Output counted it the other way round: a locked SEQ was skipped and a deleted one counted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: locked and tracked-deleted SEQ fields in layout parity
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* count_seq walks the deleted ranges with one cursor
The runs come in order and the objects by offset, so a forward cursor
replaces the per-field scan of every deleted range.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
* M2.1: find smart-quoted words with a keyboard apostrophe (#5388)
* M2.1: align Find parity status and regression docs (#5388)
---------
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
* #5469: Size keeps the side it isn't asked to change
Typing only the width of a straight line (height 0) gave it a 1 pt
height: picture.size fitted both sides into 1-4000 pt, also the one the
call never set. A side the call leaves as it was now keeps its size,
a line's 0 and a side past 4000 pt from a file too.
Sides set in proportion (the original size, or a scale or one side
typed with the proportions locked while both sides have a length) still
fit the limits together, even when one of them comes out as it was;
any other side the call sets fits on its own.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #5469: the table test checks exact sizes
Where both sides follow each other the test only checked that they
stay within 1-4000 pt and keep their proportions. The rule gives one
size there (the one factor nearest 1 that fits both), so each case now
checks that size, worked out by hand.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
Pin that FILENAME and FILENAME \p in a header show the new name and path after Save As, that a Word-style FILENAME \* MERGEFORMAT field drops its cached name, and that Rename is picked up by Update Field. No production code changes.
Refs #3796
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
A Word template (.dotx, .dotm, .dot) the platform hands the app, from a
double-click in the file manager, the command line or the Finder, opened
as itself: the window called it saved, AutoSave was on, and Ctrl+S wrote
over the template. Word's default verb for a template makes a new,
untitled document based on it.
WordApp::open_from_platform sends a Word template to file.newFromTemplate
and opens anything else as File > Open does; main.rs (also for a name that
isn't valid Unicode) and the Apple Events handler use it.
file.newFromTemplate reads the template as file.open reads a file
(password, data, an empty file) and, with replaceBlank, takes an untouched
blank document's place, so a launch shows one window. The Password box
retries the command that asked for it, so a protected template still
starts a new document. File > Open and drops still open a template itself.
Claude-Session: https://claude.ai/code/session_01LvG6E1XTfHtZDbfYwfREhR
Co-authored-by: Filip Strajnar <filip.strajnar@proton.me>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
* #4985: a numFmt none list level shows its literal text
Word labels a paragraph whose level has numFmt none and lvlText RESPONSE:
with RESPONSE:. Counters::next_label returned no label for any none level;
now only a none level of placeholders shows nothing. Its own %n stays
empty, and it still doesn't count. Numbering::shows_label agrees.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4985: rustfmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4985: a "none" list level counts, and only its own number is empty
Taken from MeHail-02's numbering-literal-none branch (46405e66), checked in Word
for Windows: a none level is an ordinary level whose own number reads as
nothing. It counts and restarts the levels below it, stays empty in legal
numbering, and an empty label still tabs the text to the indent unless it
already ends there (the Chapter 1 list's Heading 2 stays at the margin).
Co-Authored-By: MeHail <mihailsmetanin15@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4985: STYLEREF in full context keeps a none-level heading's parent
Review: a none level now has a label, so its headings reach the style index,
which leaves the none level out of the levels it keeps; \w and \r took the last
kept level for the heading's own and dropped the parent. Levels now carry their
index, and the levels above are picked by it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4985: STYLEREF number switches on a none-level heading
Review: in full context an empty label of its own added a trailing period (3.),
and the suppress switch gave a none level's label (Note), which holds no number,
back whole. An empty own part is left out, and a none level's label with no
number is suppressed to nothing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4985: STYLEREF \s and \t on none-level labels
Review: \s took a none level's label (Note) for a chapter number; the heading's
count stands in, as for an unnumbered heading. \t matched any ancestor's number
inside a word (the a of Chapter); only the numbers the label's template shows
count now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4985: STYLEREF takes a label's numbers by their spans
Review: \s took the last word of a label (Response of "3 Response") and fell
back on the heading count for a none level showing its parent's number (%1);
\t kept text attached to a number (Section3). A label's numbers are now the
spans of the values its template shows, with their delimiters (a letter number
only standing apart from other letters); \s takes the last of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4985: tests edit the numbering through Arc::make_mut after merging main
Main shares the numbering between undo steps (#2694), so `Document::numbering` is an
`Arc<Numbering>`; the tests this PR adds wrote to it directly and no longer built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: MeHail <mihailsmetanin15@gmail.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
* #4685: Shading on text selected inside a paragraph shades the text
Home › Paragraph › Shading always set the paragraph's shading. Word shades the selected
text when the selection is inside one paragraph (w:shd in the run properties), and the
paragraph for a caret or a whole paragraph. So an orange text shading from a .docx (which
Text Highlight Color › No Color leaves, in Word too) couldn't be taken off from the ribbon.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4685: review — a paragraph's whole text is text unless it's the last; a selected object isn't text
A selection with a paragraph's mark ends at the next paragraph's start, so 0..len of a
paragraph that isn't the last of its story, cell or box is text only and No Color clears its
shading. A selected text box or picture (its object character) shades the paragraph as before,
instead of every line of the box.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4685: review — a selected field is text; Apply to: Paragraph shades the paragraph
Only a selected picture, shape or text box (`object_selection`) keeps Shading on the
paragraph; a field's object character is text, so No Color clears its shading. The Borders
and Shading dialog's Apply to: Paragraph passes `paragraph: true`, so it shades the
paragraphs even for text selected inside one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4685: ui-parity wording (CodeRabbit): text shading applies to part of one paragraph
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4685: ui-parity wording (CodeRabbit): removes text shading imported from a file
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* UI parity: status line after merging main
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #4685: a test of Shading in a cell's last paragraph
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
* fix(fields): quoted-argument backslash escapes and body PAGE in a cardinal-text section
A backslash inside a quoted field argument makes the next character literal, so TITLE "x \* Upper" shows x * Upper as Word does. A body PAGE field now uses the section's page number format (cardinal text shows words), as headers and footers already did.
* Fields: PAGE in a body table follows the section number format; parity docs
* fmt
* docs: clarify quoted field escape rule
* Fix page fields in fitted text boxes
* Move fitted cache test after layout helpers
---------
Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
* Ctrl+K on a selection shows the changed Text to display
With text selected, the Link dialog's Text to display was ignored:
insert.link only linked the selection. A changed text now retypes the
selection (keeping its formatting, tracked with Track Changes on) and
links it. The same text, text with a line break, or a selection over
several paragraphs or holding a picture or field keeps its text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Links: keep selections a changed Text to display can't replace
Review of the Ctrl+K retype: a selection holding a manual line break,
page or column break, or sitting in a field's result is only linked, so
neither both lines nor the field's result are lost. `select.text` clears
the caret formatting as `select.word` does, so a second retyped link
keeps its selection's bold instead of the first link's default props.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Selections: select.all, select.range and select.word drop pending formatting
A link leaves plain formatting pending for what is typed after it. These
commands kept it, so retyping the next selection took that instead of the
selection's own formatting, as select.text and select.word's caret moves
already avoid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* #3719: run the file commands through the hostile-parameter test
hostile_params_never_panic skips file.*, insert.picture and
insert.textFromFile, the commands that take a path and parse what is
there. hostile_file_params_never_panic runs them with the same junk
params, and with paths to an empty file, a folder, a missing name and
noise under each extension they open or save. Automation roots hold
them to a temporary folder.
It found no panic, so this adds only the test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #5688: check the whole working directory, on the sample document
The working-directory check looked only for hostile-relative names, but
an empty path comes out as .pdf or .dotx there. It now compares the
whole folder before and after the runs.
The runs now use the sample document, like hostile_params_never_panic:
on an empty document the export commands had almost nothing to write.
The docs and comments name the three groups of commands, because other
commands also read files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #5688: a failing file check names the files, not their bytes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A list level, page number or note mark in Word's thaiNumbers format was
read and saved as such but drawn as decimal (1, 2, 3). It now shows the
decimal number in Thai digits (U+0E50..U+0E59): ๑, ๒ … ๑๐, with the
level's text around it (๑., (๒)). Thai letters and Thai counting still
show decimal until they are drawn too.
Part of #5757 (Thai numerals in automatic numbering).
The CSV fallback is now only for an absent values field. A null values argument is rejected
before anything is inserted. Null cells inside a valid row still work. Engine and MCP
regressions check that the document and the undo history stay unchanged.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>