#6537 declared a test change but committed 50 text files with CRLF, which
`cargo fmt --check` does not catch. Restore the bytes from c37baf76: no text
file carries CRLF, and the tree is identical to that commit again.
Refs #6537, #6539.
The #5858 early return kept page anchoring for any object that does not
move with text, which includes a body frame that came from an .odt with
text:anchor-type="page". Moving it then skipped the paragraph placement
and the save wrote the page anchor and number back, contradicting #5705's
an_odt_frame_moved_from_its_page_loses_the_page_number.
Only header/footer objects (and drops on a page with no anchoring text)
keep page anchoring now, which is what #5812 needed. The body test that
pinned the opposite is updated to the #5705 rule.
- Recognize a marked Fit One Page custom XML store across package and document
relationships, skipping passthrough only after loading successfully.
- Reserve the Fit One Page part path in EmbedWriter taken paths to prevent collisions.
- Track typography provenance per formatting location rather than sorted multisets.
- Validate stepPt parameter and reject non-positive or non-finite values.
- Report "already fits on one page" before an exhausted cumulative budget.
- Record original and reduced font sizes, line spacings and paragraph
spacings as multisets, so restoring neighbouring sizes clears the
exhausted budget, and paragraph spacing keeps the budget when line
spacing rounds back to its original.
- Resolve font and spacing budgets independently, letting the remaining
component be used instead of refusing the whole command.
- Persist FitState in a namespaced DOCX custom XML part and restore it on
read; add a DOCX round-trip regression.
Pass LayoutOptions::hides_deleted per story into the explicit-break scan and
the font floor, so hide_marked, revisions in balloons and moved-from text are
seen exactly as Print layout sees them. This keeps the guards from counting
deletions the page no longer contains.
- Anchor cumulative budget provenance to the original sizes and spacings
recorded before the first fit, instead of matching every current value
against every recorded value. Restoring body and heading sizes now resets
the budget.
- Build the initial and trial layouts from the session's hide_marked,
deletions_in_balloons and resolved numeral settings, so hidden or
ballooned deletions and numeral shaping paginate as in Session::layout.
- Add regressions for mixed body/heading sizes, mixed line spacings, and
Session::layout parity under non-default visibility and numeral settings.
Resolve each cell's conditional table-style character formatting before
checking whether a page break is hidden, matching min_blocks_font_size and
layout's region selection. A first-row or banded hidden override now agrees
with Print layout. Add a regression with a hidden first-row override.
- Round paragraph spacing up to hundredths, so a very small value is never
reduced by more than the requested factor.
- Ignore page-break characters in hidden or tracked-deleted runs, matching
Print layout visibility, so a hidden break cannot force a refusal.
- Scale paragraph spacing without rounding a small value to zero or
compressing past the requested factor.
- Detect resolved page_break_before and nested table paragraphs as explicit
breaks, and refuse before evaluating candidates.
- Count non-text body items in the final-page volume limit.
- Give the ribbon button an accessible label and test it through the
rendered Layout ribbon in both themes.
* View: editable Markdown source view for .md files (#5826)
Co-authored-by: oldgranola <marken.john206@gmail.com>
* Address review feedback on Markdown source view
- Fix view.markdownSourceText purity and input validation
- Single-pass block line mapping in markdown parser
- Reuse parsed documents and block line ranges on apply and leave
- Bound memory usage with MAX_SOURCE_BYTES and clamp caret byte offsets
- Isolate temp directories in tests and test caret validation
- Preserve dirty state while markdown text edits are pending
- Flush pending markdown text edits in swap_doc
* Address CodeRabbit review feedback on Markdown source view
- Exempt view.markdownSourceText in command refusal
- Preserve original source line mapping when filtering link definitions
- Map between egui char indices and byte-offset caret in history and undo
* Remap block line numbers in a single linear pass
* Refuse over-limit Markdown Source edits to prevent document truncation
* Keep a Markdown file's own text when it is opened from handed-over bytes
`file.open` kept a Markdown file's exact text only for path-backed opens, so a
platform that hands the bytes over (data) lost front matter, comments and the
author's line endings on the first save, and the rewritten flag could not say
so. The kept text now comes from the same bytes `read_document` decodes, still
bounded by MAX_SOURCE_BYTES through FileText::from_bytes; the path read stays
for opens without data.
---------
Co-authored-by: oldgranola <marken.john206@gmail.com>
Co-authored-by: CommandCodeBot <noreply@commandcode.ai>
Add layout.fitOnePage, a speculative typography search that removes one
page from the document while preserving relative font hierarchy.
Every size receives the same half-point-rounded reduction; line spacing
never falls below 0.94 of its original and body text never below 8.5 pt.
The command refuses on explicit page breaks, excessive final-page content,
formatting protection, tracked formatting and the cumulative 1 pt budget,
and leaves the document unchanged when it refuses.
Add the Layout ribbon button, KeyTip and a code-drawn icon, cover the
command with 41 tests, and regenerate the parity checklist.
Paste selected WordCraft's own fragment whenever the system clipboard text matched, even when the host reported that another program owned the clipboard, so a plain-text copy made elsewhere could paste the earlier bold fragment back. Read privateClipboard in paste_content and in the Formatted Text source, using the session fragment only when ownership is true or the caller omits the flag (agents and the CLI keep working). Carry the flag through the Paste Special request and dialog so its format list matches.
* feat(engine): format Brazilian date fields and insert localized dates
* feat(ui): persist preferred date and time format
* feat(ui): implement Word-style Insert > Date & Time modal
* feat(ribbon): open Date & Time dialog from Insert tab
* test: Brazilian Word TIME fields respect document defaults after DOCX round-trip
* fix(docx): refresh Word date/time fields using their run language
* ci: add temporary date/time regression validation workflow
* style: apply rustfmt output to insert.rs
* style: apply rustfmt output to date_fields.rs
* style: apply rustfmt output to dialogs_insert.rs
* i18n: reuse translated date-format and language labels
* i18n(pt-BR): translate Update automatically in date/time dialog
* fix(i18n): translate date dialog options and audit generated date previews
* chore: remove temporary date/time regression workflow after focused validation
* fix(date): resolve inherited locale and preserve it in fixed text
* fix(ui): display date insertion errors without closing the dialog and document state
* test: cover inherited language, static date text, and escaped date pictures
* test(ui): exercise date language, picture, checkbox, OK, and invalid-value feedback via egui
* style: apply rustfmt to date insertion and regression tests
* fix(engine): borrow caret position while resolving inherited date language
* fix(date): honor selected locale when the date picture is omitted
* test(ui): target date dialog language combo instead of ribbon menu
* test(date): use raw field instruction literal in locale regression
* test(date): match actual single-backslash DATE field instruction
* test(ui): select actual date dialog language option by accessible label
* fix(i18n): add missing automatic date update translation (Romanian)
* fix(i18n): add missing automatic date update translation (Slovenian)
* fix(ui): open date_time dialog from keytip, normalize pt tags, and document items
* fix(ui): default date_time_update preference to false
---------
Co-authored-by: felipegavilanes-source <felipegavilanes@gmail.com>
Fixes#2201. Top/bottom page numbering preserves neighboring header/footer text, formatting and tables, recognizes existing PAGE fields through nested tables/text boxes, and updates gallery-generated numbering in place.
The latest review follow-up lets refreshed PAGE/NUMPAGES result caches vary during generated-number recognition. It retains those caches and paragraph marks during format/alignment changes; identical requests remain clean and add no undo step. Field instructions, flags and paragraph/run formatting remain conservatively checked. Original history, incoming progress notes and readiness estimates are preserved.
Local validation before the integration refresh:
- Actual RED: 13 existing tests passed; the new reopened cached-PAGE fixture failed because right alignment stayed centered.
- GREEN: all **14 preservation tests** pass, with all 13 originals an exact byte prefix. The fixture checks DOCX-reopened caches 7/12, no-op cleanliness/undo, later changes, content/marks/selection and undo/redo.
- Full engine package: **1,350 passed, zero failed, one existing ignored measurement**, including the focused 14. Engine formatting and strict all-target Clippy pass.
- Synthetic DOCX coverage; native Word interoperability was not exercised. No new whole-workspace local run is claimed.
Current head: `5eb4c8cb8b205ebcfcb5b1a48d55c5e21b1683e6`, a normal preserving merge of upstream `d40d1b27181b61990c84c75f77d5fd23ef81d452`. The new roadmap and format-parity documentation conflicts retain all incoming progress and revision history plus this PR's existing page-number notes. All **1,087 published files** match incoming upstream plus the five reviewed changes; both owned Rust files remain byte-for-byte unchanged from `ca615f3` and `447dec8`, and manifests/lock match incoming upstream. No new local Rust run is claimed for this documentation-only integration. The [preceding ca615f3 full hosted gate](https://github.com/storytold/wordcraft/actions/runs/42679017335/job/129368197175?pr=2251) passed in 13m38s. The [required current-head full gate](https://github.com/storytold/wordcraft/actions/runs/42693258780/job/129418282235?pr=2251) passed in **13m06s**; SonarCloud planning/scanning also passed. Required validation is complete. Maintainer edits are enabled; FredApps re-review remains requested.
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
<!-- coderabbit-ai-generated:v1 -->
## Summary by CodeRabbit
## Summary
* **Bug Fixes**
* Adding page numbers to headers and footers now preserves existing content and formatting, including content in tables and text boxes.
* Existing user-owned page fields remain unchanged, while generated page numbers can be updated without disturbing surrounding content.
* Repeating an identical page-number request no longer changes the document’s state or undo history.
* **Documentation**
* Updated page-numbering guidance to reflect these preservation behaviors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* ODT gap 19: pictures keep the size their file gives
Opening a file fitted a picture wider than the text to the text, and shrank any
picture wider than 6.5 in to that, even with its size stated. So a save with no
edit to it took its paragraph off being written as read, and LibreOffice laid
it out differently. Pictures in OpenDocument files keep the size they state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: a picture keeps a size stated on one side, in proportion (gap 19)
Only a frame stating both sides kept them. On one side alone a picture past a
column's width was still fitted to it, and past 1,584 pt each side was cut
separately, which distorted it. A size past any page can no longer overflow the
sizing either. Found by CodeRabbit on this PR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: an inline picture with no stated size is fitted to the text (gap 19)
Skipping the fit for OpenDocument files left one wider than the text on a small
page, which `main` fits. A size the file states still stands, and a test now fails
without that. The fit happens when it is read, so its paragraph stays as read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: an object's picture is fitted too, and one side follows the picture's own shape (gap 19)
An inline object with no stated size kept its picture's width on a small page, which main fitted. A picture stated on one side took the other from the size already capped to the page: 10 by 1000 pixels at 500 pt wide came out 468 by 1584 pt, not 15.84 by 1584.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: a frame stating a size of nothing is fitted like one stating none (gap 19)
svg:width="0pt" counted as a stated size for fitting, though the picture then came at its own size, so it could run past the text on a small page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: a picture in line that is raised, turned or mirrored is fitted too (gap 19)
Such a picture has a float record that is still in line, which the reader's fit skipped, so with no size stated it stayed 468 pt wide on a small page. main fitted it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Gap 19: docs status lines are main's, with this change in front
Merging main had kept this branch's older status lines in ROADMAP.md and
docs/file-format-parity.md, which set main's 2026-11-08 date back and dropped
main's newer entries and the parity doc's CLI path note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Order the parity header's change notes newest first
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* ODT gap 19: frames keep the page they are anchored to
A frame or shape anchored to a page was written without its
text:anchor-page-number, so one on page 3 came back on page 1 in LibreOffice
after a save. Keep the number and write it back. WordCraft's own layout still
places such a frame on its anchor paragraph's page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: a page number only counts on a page anchor, and groups keep theirs (gap 19)
A paragraph anchor with a text:anchor-page-number was saved as a page anchor,
which LibreOffice ignores the number on and so moved the drawing. A group
anchored to a page lost its page. Found by CodeRabbit on this PR.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: a frame moved in WordCraft loses its page number (gap 19)
Nothing cleared it, so the save put the frame back on its old page. One placed by
its paragraph is no page anchor any more, and one dropped on a page takes that page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT tests: put the moved frame's test with the other moves (gap 19)
At the end of the file it collided with other ODT PRs' tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: a frame moved onto its paragraph loses its page number (gap 19)
The number was only hidden while the frame was placed by its paragraph, so placing it by the page again (arrange.position) brought back the old page. The merge of main also lost the test attribute on a test from main, put back here.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Gap 19: docs status lines are main's, with this change in front
Merging main had kept this branch's older status lines in ROADMAP.md and
docs/file-format-parity.md, which set main's 2026-11-08 date back and dropped
main's newer entries and the parity doc's CLI path note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Gap 19: parity row says when a moved frame keeps its page number
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
* ODT gap 19: a document keeps its language when styles are rewritten
The default language was only ever saved by copying the file's own styles
back. Adding numbering or creating a style writes them from the model, which
never stated it, so LibreOffice showed the document in its own default
language. Import ignored it too, so such a document was proofed as English and
the styles WordCraft adds said en-US.
- Read the language from Standard, else the default paragraph style
- Write it in the model-written default style
- A file stating none has none, rather than en-US from new documents
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: a language keeps its script, and a style with no parent the default style's language (gap 19)
sr-Latn-RS was written as fo:country="Latn" with RS dropped, and fo:script
wasn't read, in all three of the language attributes. A style with no parent
took Standard's language when it differed from the default style's. Found by
CodeRabbit on this PR, as were the added assertions and doc comments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ODT: the language written is Normal's, as WordCraft has it (gap 19)
The default style took its language from the defaults, though WordCraft's document language is Normal's: a Word file with a French Normal over en-US defaults saved as English.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Gap 19: docs status lines are main's, with this change in front
Merging main had kept this branch's older status lines in ROADMAP.md and
docs/file-format-parity.md, which set main's 2026-11-08 date back and dropped
main's newer entries and the parity doc's CLI path note.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
A text box inside a group (Word's cards made of a coloured bar and a text
box) could not be edited: every click selected the whole group, and typing
then replaced the group with the typed text. Layout now keeps each group's
text box areas on the group's object, and hit testing treats them like a
text box on its own: inside is the box's text, the border and the group's
other members still grab the group (#6027).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every style-definition change sits at the same collapsed anchor at the
start of the document, so Accept or Reject there resolved all of them,
across authors, and so did any caret put at the document start. Next and
Previous Change now remember which style change they selected (style id
and revision, for that document and selection), step between style
changes that share the anchor, and Accept/Reject resolve that revision
alone. A caret placed there any other way resolves no style change;
Accept/Reject All and a whole-body selection still resolve them all.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* #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>