Commit Graph
342 Commits
Author SHA1 Message Date
mtlcl410andClaude Opus 5.5 36699aeaf0 Merge storytold/pdfcraft main into feat/edit-text-in-forms
Conflicts:
- crates/edit/src/images.rs, crates/engine/src/doc_state.rs, crates/engine/src/edit/labels.rs,
  crates/automation/src/dispatch.rs: main added Transform, Arrange and RemoveCrop next to this
  branch's Ungroup; kept all.
- crates/edit/src/lib.rs: kept `mod ungroup;` with main's export list (ArrangeOrder).
- crates/engine/src/edit/run.rs: main moved EditPageImage into run_edit_page_image; carried
  the Ungroup arm there, and Ungroup goes through change_image on the added-image path too.
- crates/automation/src/tools.rs: image_edit takes main's description and action list, with
  this branch's ungroup added to both.
- crates/ui-egui/src/tools/edit_text_ui/image.rs: main rebuilt the image context menu; took
  it and carried the Ungroup item (Form artwork only) before Replace image.
- crates/automation/tests/it/automation.rs, crates/edit/src/tests.rs,
  crates/ui-egui/tests/it/editing.rs: both sides appended tests; kept both.
- crates/ui-egui/src/i18n/{ca,es,fi,hi,id,ko,ro,sq}.tsv: both sides appended; kept both,
  main's lines first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-11-02 21:39:20 +08:00
d3384340a6 M7: make page-mark replacement transactional (#5657) (#5704)
* M7: make page-mark replacement transactional (#5657)

* ROADMAP: drop the header line (the log entry stays)

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>
2026-11-01 23:49:18 -05:00
FredAppsandCodex GPT-6.1 75e012de9a fix(edit): compose inside-scope group translations in order
Fold a leading transform inside the image scope as cm followed by the local transform; an enclosing transform uses the opposite multiplication order. This preserves the complete requested image and crop displacement under an outer scale. The exact crop-arrange-group-move fixture previously moved only five points for a ten-point request; saved geometry and rasters now match an independent direct Transform control across both axes and signed offsets. Full scoped Linux native, wasm and repository gates pass.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 17:41:13 +01:00
FredAppsandCodex GPT-6.1 e677c009b3 fix(edit): restore inherited text state when arranging Forms
Track the text-state operators with saved drawing state, restoring inherited spacing, scaling, leading, rendering mode, rise and known font before relocated Form artwork. Emit restoration inside BT/ET, and leave a font selected by a replayed ExtGState authoritative over an older direct Tf. The exact inherited 12 Tc fixture failed its saved-PDF raster comparison before this fix; zero-spacing and destination-state controls now preserve rendering. Scoped Linux native, wasm, clippy and formatting checks pass.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 17:41:13 +01:00
FredAppsandCodex GPT-6.1 8669aa48aa fix(edit): preserve graphics state chronology and mask identity
Replay repeated ExtGState names in last-application order. Compare each complete soft-mask value and its establishment matrix instead of only its group reference, and retain cached shared definitions across saved states. Saved-PDF raster regressions cover Alpha versus Luminosity over the same group, repeated aliases at different matrices, and ordinary sequence/single-mask controls. Definition checks also cover backdrop and transfer parameters. Actual baseline and reverted-comparator tests fail; scoped Linux native, wasm, clippy and formatting checks pass.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 17:41:12 +01:00
FredAppsandCodex GPT-6.1 9a1334e04d fix(edit): preserve image scopes and content ownership
Move editor crop scopes with grouped images while retaining fixed author clips and folding repeated transforms. Arrange keeps relocated original artwork in its own stream beside an added item, so later edits or deletion cannot discard it; unusual shared owned streams are refused. Refuse nested marked-content extraction when its tags cannot be preserved. Synthetic regressions and the headless group-move, undo/redo, save/reopen, arrange, delete and render lifecycle fail before these fixes and pass afterward.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 14:47:46 +01:00
FredAppsandCodex GPT-6.1 3a0ea63c33 fix(edit): share saved drawing operands across graphics scopes
Keep dash, color and extended printing operand payloads shared across q snapshots. Copy-on-write color updates preserve saved values without multiplying large operand arrays by nesting depth. A pointer-sharing regression failed before the change; scoped native tests, clippy and wasm check pass on GGEO-AI02.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 14:47:46 +01:00
FredAppsandCodex GPT-6.1 43ad4bf418 feat(edit): expose image crop ownership to frontends
Report active PdfCraft crop markers through PageImage::has_editor_crop and page_images.has_editor_crop so frontends can distinguish removable editor crops from author clipping paths.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 14:47:46 +01:00
FredAppsandCodex GPT-6.1 f62c1cfafe refactor(text): share source font names and selection defaults
Reuse the existing subset-tag parser through the engine facade and share the fallback formatting state.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 12:54:52 +01:00
FredAppsandCodex GPT-6.1 f607d26d14 fix(edit): preserve printing state when arranging images
Refuse relocation into transfer, overprint, black-generation, undercolor-removal or halftone state that cannot be restored. Preserve implicit device color spaces before sc/scn and restore rendering intent from both ri and ExtGState RI, including its initial default. Three synthetic regressions fail against the previous implementation; all 411 edit tests and scoped clippy pass on GGEO-AI02.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 12:41:19 +01:00
FredAppsandCodex GPT-6.1 3c394bb860 fix(edit): remove only editor-owned image crops
Identify crop paths with a marked-content point and preserve ownership through arrange reconstruction. Find the same isolated or outermost image scope as Crop, preserving authored and shared clips. Both regressions fail with the fix reverted; all 410 edit tests, scoped clippy, fmt, layers and assets pass on GGEO-AI02.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 12:37:24 +01:00
FredAppsandCodex GPT-6.1 1a2f7b2a34 fix(edit): refuse arrange across crossed scope boundaries
Require both graphics and marked-content boundaries to balance inside source, destination and removed spans. Regression fails before the guard; all 408 edit tests, scoped clippy, fmt, layers and assets pass on GGEO-AI02.

Co-Authored-By: Codex GPT-6.1 <noreply@openai.com>
2026-11-01 12:33:51 +01:00
FredAppsandClaude Opus 5.5 eb0d5e64c7 refactor(edit): fold duplicated image scope and arrange helpers
PR #2155 review item A5 (part 2), plus the unreachable Ok(()) exits in
arrange_image from B11. No behaviour change for well-formed content:
- image_exclusive_scope and image_transform_scope were one scan; they are
  now image_scopes, and transform/crop take ImageScopes::transform_scope.
  The shared scan resets an open path at Q, as the exclusive one did; the
  two only differed on content that leaves a path open across Q.
- arrange_image and remove_image_crop took page and index and listed the
  page's images again; change_image now passes the page, its streams and
  the image, and neither is public any more.
- content_units' four copied scan loops are one sequence_end.
- DrawingState keeps its line-style, intent and flatness operators in a
  table; the w/J/j/M/d guards in compute_inherited_drawing_ops tested ops
  extract_scope_state never collects.
- clip_path_to_ops: ImageState never builds a quadratic segment and only
  stores finite points, so the QuadTo arm and finite checks were dead; it
  now reports a segment it can't write instead of dropping the clip.
- target_destination_unit and determine_arrange_spans are shorter; the
  Ok(()) exits for lookups that cannot fail are now errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:33:50 +01:00
FredAppsandClaude Opus 5.5 71b39aec5b refactor(edit): drop test-only image API and dead graphics-state code
PR #2155 review item A5 (part 1). No behaviour change for callers:
- crop_image, and the arrange_image/crop_image/remove_image_crop/MaskIdentity
  re-exports, existed only for tests; tests now go through change_image.
- PageImage.smask was read only by tests; page_images no longer tracks
  ExtGState for it. The nested-mask test keeps its rendered-pixel check.
- MaskIdentity's Display, ExtractedScopeState.effective_cm, the
  ExtendedGStateTracker overflow_depth (an error ends the scan, so it was
  never read) and the EffectiveGState one-line getters were unused.
- merge_dict always gets the ExtGState name, so the hash fallback for an
  unnamed inline soft mask was unreachable outside a test.
- cid_to_gid_map_odd_length_is_trimmed_to_even exercised no crate code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:33:50 +01:00
Combiler01 fdc0e6404a fix: address review feedback on arrange, image flips, rotate knob, and format panel
- [P1] Arrange: track and replay inherited non-ExtGState drawing state (colors, line widths, etc.) across q/Q scopes for stencil and form artwork
- [P2] Flips: route ImageEdit::Flip through coordinate-aware transform logic to swap axes when rotated at odd quarter turns (90°/270°)
- [P2] Rotation knob: measure clockwise angle delta from drag start relative to the rotated image center to prevent reapplying existing angle
- [P2] Format panel: connect superscript/subscript controls to line editor and content draft selections, and update parity evidence
2026-10-31 23:18:58 +05:30
Combiler01 d2d28c7728 test(edit): restore regression test rewritten_paragraph_is_drawn_after_backgrounds_between_its_rows (#2377) 2026-10-31 19:40:44 +05:30
Combiler01 e781d31187 fix(ui, test): resolve font-size inspect query and command-key paste tracking 2026-10-31 17:09:50 +05:30
Combiler01 79eed72c08 Merge upstream/main into main preserving custom features
- Resolve all 13 merge conflicts between upstream/main and main
- Preserve full image editing suite: OBB, crop, proportional resize, arrange, and context menu actions
- Preserve text editing formatting tools: bullet/numbered lists, subscript/superscript, and line spacing
- Cleanly integrate upstream's canvas modularization, font updates, and styling changes
- Refactor arrange_image and run_edit_page_image helpers to satisfy clippy cognitive complexity limits
- All engine, edit, and UI integration test suites passing cleanly
2026-10-31 15:50:31 +05:30
Combiler01 87c1e858e3 Coderavbit Fixes 2026-10-31 14:23:19 +05:30
mtlcl410andClaude Opus 5.5 b61661774e M7: Ungroup refuses a transparency group that blends in a colour space of its own (#2964, #3005)
A transparency group composites its contents in its own group colour space
(ISO 32000-2 11.6.6, Table 145 /CS) and the result is then converted to
its parent's; inlined, the same objects composite in the page group's
space (11.4.7). Colour conversion isn't linear, so translucent content and
even opaque colours can change, and Ungroup promises the same pixels.

A group /CS is now accepted only when it matches the page group's /CS, or
is DeviceRGB on a page that names none (the processor chooses that space;
viewers composite on screen in RGB). A group that names no space inherits
the page's and is fine. The tool description and the parity note say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
2026-10-31 08:18:03 +08:00
mtlcl410andClaude Opus 5.5 77fd60b02b M7: restore the UTF-8 characters 00688adf mis-decoded in ungroup.rs doc comments
00688adf read crates/edit/src/ungroup.rs as Windows-1252 and wrote it back
as UTF-8, double-encoding the non-ASCII characters in six doc-comment
lines. Restore them byte for byte (comments only, no code change):

- line 1:  "â–¸" -> "▸" (Edit a PDF ▸ Ungroup)
- line 5:  "…" -> "…" (re W n … Q)
- line 14: "…" -> "…" twice (/OC … BDC … EMC)
- line 17: "§"  -> "§" (ISO 32000-2 §8.7.2)
- line 32: "§"  -> "§" (ISO 32000-2 §7.8.3)
- line 38: "→" -> "→" (form name → name)

The rest of the PR diff has no such damage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
2026-10-31 08:04:09 +08:00
mtlcl410andClaude Opus 5.5 36847a601f M7: Ungroup reads a group's /S and /K, and nested types, through indirect objects (#2964, #3005)
Review follow-up on #3067: the transparency-group checks read /S and /K
raw, so an indirect /K true let a knockout group be ungrouped, and an
indirect /S /Transparency skipped the knockout, translucency and blend
checks. The /Subtype and /PatternType of nested forms, Type 3 fonts and
tiling patterns were read raw too, so blend modes inside such a form and
inherited resources were missed. All are now resolved first (any value
may be an indirect object, ISO 32000-2 7.3.10).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
2026-10-31 08:04:09 +08:00
mtlcl410andClaude Opus 5.5 54b450da0e M7: Ungroup carries the form's placement into the patterns it fills with (#2964, #3005)
A pattern's matrix maps pattern space to the default space of the content
stream that uses it (ISO 32000-2 §8.7.2). Inside a Form XObject that is the
form's space at the Do; drawn inline on the page it became the page's, so a
form moved by its /Matrix or a cm before the Do lost its patterns' place:
tiles out of phase or at the wrong size, a shading outside its box.

Each pattern the form selects with scn/SCN is now drawn from a copy whose
/Matrix is the pattern's own, then the form's /Matrix, then the CTM at the
Do. The form's own pattern object is unchanged for its other placements, a
pattern selected twice is copied once, and a pattern that can't be copied
that way (unreadable matrix, non-finite result, a tiling stream past the
size limit, more than 256 patterns) is refused rather than drawn elsewhere.
Nested forms and patterns inside patterns keep their own parents.

Tests: ungroup_carries_the_placement_into_the_patterns_it_selects (edit) and
ungrouping_keeps_patterns_where_the_forms_placement_put_them (automation,
four synthetic cases rendered before and after save/reopen), both seen
failing with the fix switched off. The same four cases were checked in
Acrobat Pro DC, where the unfixed output drew differently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0175FWj4xFhMhSH3UpMU8DSC
2026-10-31 08:04:09 +08:00
mtlcl410andClaude Opus 5.5 dd773dc15c M7: ungroup reads the state at the Do by its index in the page's streams joined
page_images numbers a Do among the operators of all of the page's content
streams parsed as one (since #3294). translucent_at took that number as an
index into the Do's own stream, so on a page with more than one stream it
read past the Do: with 'q /Mul gs /Art Do Q' in the second stream it saw
the Q and missed the Multiply the group is drawn with, and ungrouped a
transparency group that composites differently. It now walks the joined
operators up to the Do.

Raised by CodeRabbit on #3067.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3niW7kVeT8mi7TpWc8eAJ
2026-10-31 08:03:39 +08:00
mtlcl410andClaude Opus 5.5 30a285ae67 M7: ungroup refuses a nested form, Type 3 font or tiling pattern that inherits renamed resources
A Form XObject, Type 3 font or tiling pattern without /Resources of its own
is drawn by the renderer with the resources of whatever draws it
(vendor/hayro-interpret x_object.rs, font/type3.rs, pattern.rs chain them
with Resources::from_parent). Ungroup moves the form's resources into the
page's under new names, so such an item drawn from the page afterwards
would find the page's resources instead: another font, or none.

Ungroup now refuses when the merge renamed anything and the form's content
draws such an item. A form without resources of its own is unaffected (it
already uses the page's), and so are images and shading patterns.

Raised by @Lcoding15 in #3067 (from #3222).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3niW7kVeT8mi7TpWc8eAJ
2026-10-31 08:03:39 +08:00
mtlcl410andClaude Opus 5.5 363c76db87 M2: ungroup refuses unbalanced content, checks patterns for blending, reads each nested form once
Review follow-ups on #3067: unmatched EMC/BDC/BT/ET are refused; a shading
pattern's ExtGState and a tiling pattern's content count toward blending inside
a transparency group; nested forms with their own resources are cached and the
reads have a ceiling of 1024.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv2WwdgjmBz33GWkwgNn3Q
2026-10-31 08:03:39 +08:00
mtlcl410andClaude Opus 5.5 6eea737e14 M7: Ungroup draws Form artwork in place, so its text can be edited (#2964, #3005)
Text drawn inside a Form XObject was one piece of artwork in Edit text &
images: HTML-to-PDF output that wraps a block with a translucent picture
in a transparency group showed a whole table as a single box with no
editable text (#3005).

Ungroup (artwork right-click menu; image_edit action ungroup) replaces
that placement's Do with the form's content, wrapped as
q <Matrix> cm <BBox> re W n ... Q, its resources merged into a copy of the
page's under fresh names where they clash (fonts, XObjects, graphics
states, colour spaces incl. inline images', patterns, shadings,
properties). The form object and its other placements are untouched; a
form in a layer keeps it as /OC ... BDC ... EMC.

Refused rather than changed: knockout groups, transparency groups that
blend inside (used gs, nested forms) or are drawn translucent, blended or
soft-masked at the Do (followed per parameter across q/Q and streams),
tagged form content, content that can't be decoded strictly or parsed,
and unbalanced Q. cos gains Stream::decoded_strict_within for that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RUNQSPVXx3dDBabRZHAnjz
2026-10-31 08:03:39 +08:00
FredApps d7f3e67141 fix: bound backdrop edits and withhold unsafe integration behavior 2026-10-30 14:45:50 +01:00
FredApps 673178b70c fix(edit): require intact streams when identifying page backdrops 2026-10-30 13:21:04 +01:00
FredApps 25f3d12960 style: format checked soft-wrap budget assignment 2026-10-30 12:54:56 +01:00
FredApps ee71348bc8 fix: bound soft-wrap metadata and preserve unknown backdrop content 2026-10-30 12:47:37 +01:00
FredApps bdf56209ce style: format combined paragraph regression 2026-10-30 11:08:55 +01:00
FredApps 7d3d4830e5 Integrate remaining six PdfCraft parity PRs with conflict resolutions
Includes #4325 #1952 #4180 #2529 #4320 #4805, preserving their original histories.
Keep paragraph alignment and hard/soft wrap metadata together; retain CCITT/downsampling/RGB ownership guards with bounded JBIG2 export and exact-copy symbol encoding.
Combined interaction regressions added. Validation pending.
2026-10-30 11:02:07 +01:00
FredApps b21af3b969 Integrate 21 clean-merging PdfCraft parity PRs
Preserve each original PR head and its authorship as a merge parent.
Includes #4304 #4335 #4384 #4404 #4179 #5300 #4719 #4541 #2815 #4620 #3789 #3779 #2960 #5208 #4596 #3636 #4494 #4013 #5134 #2133 #2635.
Conflicting candidates skipped. Combined CI pending; see docs/integration-20261030.md.
2026-10-30 10:35:42 +01:00
Dylan M. Taylor 3517d05b47 Update prompt shows the app icon and accent color instead of a letter (#5388)
UPDATE_APP (native builds; craft-update is a native-only dependency) carries our 128 px icon and accent (#b70228, its red, from the icon (#5245)). The updater, the self-update
helper and craft-update's startup prompt all get them through crate::UPDATE_APP; the
prompt draws the icon, the accent and "by ArtCraft" (craft-update's default developer).

craft-update is pinned to storytold/craft-libs main at 1065653e7 (#434).
Clippy fixes for Rust 1.99 in untouched code (same logic):
- crates/edit/src/images.rs
- crates/forms/src/appearance.rs
2026-10-30 01:27:58 -04:00
RiceDee 5a86f35210 fix(edit, ui): alignments follow the right rows; Format by keyboard keeps the box
Four holes CodeRabbit found on 648dd428:

- alignment_frame framed a one-row paragraph when any alignment entry
  was set, including one past the draft's rows, and then moved rows
  with no alignment of their own to the column's edge. It now takes the
  draft and frames only when every draft row is aligned, by its own
  alignment or the paragraph's.
- remap could not tell which of two same paragraphs an edit took
  ("A\nA" to "A"), and kept the deleted one's alignment. remap_at takes
  the caret where the edit ended, and the typing path (follow_typing,
  both editors) passes it from the box's state. The caret only chooses
  between readings that fit both texts.
- Both editors judged a focus loss by where a click landed, so focusing
  a Format text control by keyboard or a screen reader (no pointer)
  closed the box before the alignment was chosen. They now also judge
  by where the keyboard went (focus_pos): focus outside the document
  view, or on the added-text box's own buttons, keeps the box open as a
  click there does.

Each change has a test seen failing before it.
2026-10-29 21:30:21 +11:00
ihsan mokhlisse 55a933800b M7: keep a typed space run through a saved width change (#2517)
The wrap's space count has to survive an incremental save and a full rewrite, and a width-only text_edit has to reflow with that count. A recorded editor session narrows the box and widens it again.
2026-10-29 10:42:20 +01:00
ihsan mokhlisse bb7186d87c M7: restore the exact gap a wrap consumed, not a guessed space (#2517)
wrap() already dropped the whole gap at a wrap point (#4199); rejoining it always guessed a single space back, so two or more typed spaces silently became one, and a Chinese/Japanese word split (which has no space of its own) could gain a spurious one. wrap() now records how many separator characters it consumed at each wrap (0 for a CJK split); the /PCSoft marker carries it as /N (a file saved before this, or by another writer, reads as 1); reflow_text and the mixed-style row editor insert exactly that many spaces back instead of assuming one.
2026-10-29 10:13:01 +01:00
ihsan mokhlisse dbeb9fa963 M7: move a paragraph instead of rewriting it to trigger a stale-number regroup
wrap()'s own test for mixed exotic whitespace fed through it directly, bypassing the caller's CR/CRLF normalization, needed its expected value updated for #4199's keep-every-space design, landed on main after this branch's base.
2026-10-29 10:11:19 +01:00
ihsan mokhlisse b282ec26be M7: check a shared text object through the tools (#2517)
Editing either end of one text object, then saving and editing again, has to leave the other paragraph in place. The library tests did not drive text_edit and doc_save.
2026-10-29 10:11:19 +01:00
ihsan mokhlisse 59513aa887 M7: keep a preceding paragraph out of a shared text object's marker (#2517)
A partial rewrite of the last paragraph opened the marker at the shared BT, so the paragraph before it was inside the region.
2026-10-29 10:11:19 +01:00
ihsan mokhlisse 62af4f87ca M7: compile the paragraph fixture on current main (#2517)
The paragraph-key helper has to carry reflow text, which a width change uses. ClosePath on an empty subpath is a match guard so the edit crate still passes clippy.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse 21b911ae42 M7: close a shared text object before the next paragraph (#2517)
A partial rewrite put the paragraph marker around the shared ET, so the neighbour in that text object joined the paragraph and the next edit deleted it.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse cb832b0206 M7: close a partial rewrite before the next marked region (#2517)
An unmarked paragraph was closing its marker at the following line's BT, which is after that line's BDC. The close now follows this paragraph's last ET.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse 2bc2933988 M7: rejoin a wrapped line when the box is widened (#2517)
A newline that was typed stays. A wrap is marked so a later width change can reflow it, and a short unmarked line stays broken.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse a84ae042d1 M7: close a partial rewrite's marker after the paragraph (#2517)
A row left in the source stays in the last paragraph. An extra marker is not placed across two marked-content regions.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse 4ebb575a88 M7: keep a repeated step without TL, and a partial rewrite's neighbour (#2517)
Three or more rows at one repeated step stay one paragraph when the file has no TL, which is how LibreOffice writes each line. A rewrite that leaves a source row still marks the whole paragraph, so the neighbour survives a second edit. Dragging the box width keeps the line breaks.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse 53e6d832af M7: keep an explicit leading, and a grown paragraph's neighbour (#2517)
The join limit follows the text object's TL when it has one, checked against the measured baseline gap. A rewrite of every row is marked so a new row one leading above the next paragraph does not absorb it.
2026-10-29 10:11:18 +01:00
ihsan mokhlisse e228e0edfc M7: keep a blank line as a paragraph break (#2517)
A gap of 2.4 times the font size was still under the 2.5 times join limit, so two paragraphs became one and editing the first deleted the second. Once a paragraph has two rows, a wider gap than its own leading stays the next paragraph.
2026-10-29 10:09:18 +01:00
RiceDee 648dd42810 fix(edit): a column is measured by its whole rows; an isolated row keeps its width
Two holes in alignment_frame:

- A column's right edge came from the runs that start at its left
  edge, so a row of several styles ended at its first run, and the
  column could fail to hold a title wider than that run. It now takes
  the right edge of each complete row (row_rect).
- A row with no column to align in was framed by its own box, which
  the rewrite also used as the width its text wraps at: changing an
  isolated row's text together with an alignment wrapped it at its old
  width, word by word. When the text changes and no column holds the
  row, alignment_frame now returns None, so the row keeps the width it
  has without the alignment. With the text unchanged the own box is
  still the frame, so aligning twice is still a no-op.
2026-10-29 19:12:11 +11:00