Commit Graph
966 Commits
Author SHA1 Message Date
batistalucasv bcacf7466f repo: restore LF line endings in the files #6537 rewrote
#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.
2026-11-13 16:35:00 -03:00
FredApps 4216089a23 Tests: restore the ODT frame test #6537 changed; #6536 placed moved body objects by their paragraph again 2026-11-13 20:21:59 +01:00
FredApps 0b2a83a352 Merge pull request #6537 from FredApps/fix-odt-frame-test-5858
Tests: a page-fixed ODT frame keeps its page when moved (main's gate is red)
2026-11-13 20:20:13 +01:00
batistalucasv c37baf764d arrange: place a moved body object by its paragraph (#5858, #5705) (#6536)
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.
2026-11-13 20:17:25 +01:00
FredApps ef3626dbb6 Tests: a page-fixed ODT frame keeps its page when moved; a frame set to move with text drops the page number 2026-11-13 20:03:37 +01:00
batistalucasv 69ea44776d Fix: arrange.bounds on header picture writes page-anchored ODT frame (#5858)
* Fix #5812: arrange.bounds on header picture writes page-anchored ODT frame

* Address review feedback on PR #5858: preserve horizontal anchor in move_with_text
2026-11-13 19:21:30 +01:00
batistalucasv 0294f81d83 Merge branch 'main' into feat/fit-one-page-upstream 2026-11-12 23:49:35 -03:00
batistalucasv 2bda4e5a41 M2.1: Resolve Fit One Page stores by marker, reserve path, and location provenance (#6433)
- 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.
2026-11-12 23:44:54 -03:00
FredApps fab8793ce7 Merge pull request #2703 from vangel76/feat/style-pr-change-2697
Track style property changes (#2697)
2026-11-13 02:29:33 +01:00
FredApps 12ce548607 Merge pull request #6051 from vangel76/fix/6027-textbox-edit
Text boxes inside a group can be edited
2026-11-13 02:17:07 +01:00
batistalucasv d30a8392f9 M2.1: Harden Fit One Page budget provenance and DOCX persistence (#6433)
- 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.
2026-11-12 19:32:10 -03:00
batistalucasv 15c5b68da6 M2.1: Use session deletion visibility in Fit One Page guards (#6433)
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.
2026-11-12 18:23:58 -03:00
batistalucasv 1c3839aa16 M2.1: Fix Fit One Page provenance and Print options (#6433)
- 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.
2026-11-12 18:17:45 -03:00
batistalucasv f392c14f6b Merge upstream/main into feat/fit-one-page-upstream 2026-11-12 16:44:42 -03:00
batistalucasv 48d9c2a46c M2.1: Fix remaining Rust 1.100 Clippy lints (#6433)
Replace indexed row/cell loops with mutable iterators in the layout table
tests and the table eraser test. Test-only; no behaviour change.
2026-11-12 15:57:34 -03:00
Codex d54fb78544 test: avoid range indexing in layout and eraser fixtures (#6225) 2026-11-12 19:43:54 +01:00
batistalucasv b71f257f1b M2.1: Match table region formatting in Fit One Page break scan (#6433)
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.
2026-11-12 15:24:10 -03:00
batistalucasv d87e6f7930 M2.1: Fix remaining Fit One Page review findings (#6433)
- 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.
2026-11-12 15:14:13 -03:00
batistalucasv ed09a2158f M2.1: Fix Fit One Page review findings (#6433)
- 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.
2026-11-12 15:01:22 -03:00
FredApps 11318f9226 Merge main and preserve both UI parity histories (#6225) 2026-11-12 18:13:35 +01:00
10e5bb8bce View: editable Markdown source view for .md files (#5826) (#6004)
* 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>
2026-11-12 17:59:04 +01:00
batistalucasv 98619947d7 M2.1: Layout: add Fit One Page (#6433)
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.
2026-11-12 13:45:53 -03:00
batistalucasv ab90b7142e Clipboard: honor another program's clipboard ownership on paste (#6225)
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.
2026-11-12 12:22:28 -03:00
batistalucasv 02f8022e57 Merge remote-tracking branch 'upstream/main' into fix/6205-image-copy-paste
# Conflicts:
#	docs/gaps.md
#	docs/ui-parity.md
2026-11-12 10:41:04 -03:00
batistalucasv a1d4495cfb Clipboard: account for fragment media and test CF_HTML offsets (#6205) 2026-11-12 09:53:17 -03:00
batistalucasv 4706420e94 tools: chunk LCS alignment past MAX_LCS window to prevent false revisions (#6110) (#6124) 2026-11-11 19:11:33 +01:00
batistalucasv ae7ae89eb2 fix(engine): surface character-level compare fallback on long paragraphs (#6111) (#6120) 2026-11-11 19:09:43 +01:00
batistalucasv ad5e1a65fc #6159: refuse ui.screenshot on secondary document windows and support root window selection (#6169) 2026-11-11 14:22:51 +01:00
batistalucasvandfelipegavilanes-source bf426c1804 Insert > Date & Time: Word-style dialog with regional formats and field support (#5093) (#6022)
* 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>
2026-11-11 14:21:31 +01:00
batistalucasv b67d3b8735 Engine: fall back to fsync on Apple targets when full flush is unsupported (#6155) (#6161) 2026-11-11 14:20:39 +01:00
Mex 060768f1ed Preserve header/footer content when adding page numbers (#2201)
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 -->
2026-11-11 09:06:18 +01:00
batistalucasv 49e777cce5 Clipboard: transfer copied images to system clipboard and preserve media across internal paste (#6205) 2026-11-10 20:59:58 -03:00
Martin BallandClaude Opus 5.5 d40d1b2718 CLI: --strict on convert and render refuses missing glyphs and pictures (#6039)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 20:16:05 +01:00
bd9e726cda ODT: pictures keep the size their file gives (gap 19) (#5721)
* 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>
2026-11-09 16:15:23 +01:00
15b608660f ODT: frames keep the page they are anchored to (gap 19) (#5705)
* 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>
2026-11-09 15:57:37 +01:00
52fbc7f068 ODT: a document keeps its language when styles are rewritten (gap 19) (#5704)
* 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>
2026-11-09 15:56:59 +01:00
vangel76andClaude Opus 5.5 37e891544d Text boxes in a group: a click edits their text
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>
2026-11-08 23:04:59 +01:00
vangel76andClaude Opus 5.5 52d1668507 Resolve only the navigated style change
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>
2026-11-08 18:49:57 +01:00
vangel76andClaude Opus 5.5 dbbc43a89a Merge upstream/main into PR #2703
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 17:18:43 +01:00
FredApps aa262cf7d8 Merge pull request #2595 from vangel76/mcp-insert-table-2242
MCP: insert_table tool for filled tables (#2242)
2026-11-08 17:11:08 +01:00
FredApps d5d2b22dac Merge pull request #331 from vangel76/feat/322-view-windows
View › Window: Switch Windows, Arrange All, View Side by Side and Synchronous Scrolling
2026-11-08 16:37:54 +01:00
vangel76andClaude Opus 5.5 59030b1bb5 Merge upstream/main into PR #331
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 16:18:07 +01:00
vangel76andClaude Opus 5.5 4050f1bf8b Merge upstream/main into PR #5115
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 16:18:07 +01:00
02e74a5d9c Add Delete Previous Sentence with Ctrl+Shift+Backspace (#5094)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-08 16:13:33 +01:00
FredApps d5a1c0e181 Merge pull request #5116 from vangel76/feat/paragraph-tools-5043
Home › Paragraph Tools: nine paragraph conversion and cleanup commands (#5043)
2026-11-08 16:12:57 +01:00
David PrietoandMartin Ball 735c2a5382 layout: keep post-break anchors with text across a wrapped row (#5937)
* layout: keep wrapped-row anchors after page breaks

* test: pin wrapped picture drops, history and DOCX

* docs: record wrapped-row anchor coverage

* docs: log wrapped-row picture anchoring fix

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 06:59:44 +01:00
batistalucasv c4d5f70c1d DOCX: resolve LibreOffice font fallback lists to first installed family (#5843) (#5851)
* #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
2026-11-08 05:55:24 +01:00
David PrietoandMartin Ball 4cd6ffa564 doc: preserve following formatting when deleting an empty paragraph (#5929)
* doc: preserve following formatting when deleting an empty paragraph (Leaf #152)

* doc: regress empty paragraph merge formatting (Leaf #152)

* engine: cover empty heading deletion, history and DOCX (Leaf #152)

* typing: pin empty heading deletion semantics (Leaf #152)

* docs: record empty paragraph formatting coverage (Leaf #152)

* docs: record the empty paragraph deletion fix (Leaf #152)

* test: retain structured controls across an empty paragraph join

* docs: explain empty paragraph engine regressions

* docs: explain the empty heading keyboard regression

---------

Co-authored-by: Martin Ball <martin.ball@ucscaregroup.co.uk>
2026-11-08 05:48:03 +01:00
Kevin JosephandMartin Ball 5dfb3acf15 picture.change: keep the width and take the new picture's aspect ratio (#5761)
* 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>
2026-11-08 05:40:51 +01:00
vangel76andClaude Opus 5.5 5d139d0f0e Merge upstream/main into PR #2703
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 05:19:14 +01:00