Conflict in crates/edit/src/text_regions/styled.rs: kept this branch's
`row_step` beside main's `WrappedStyledRows`, and main's marker-aware
`splice_with_marker` splice with the branch's `at` (already computed
before the reach check). main's `wrap` now yields (line, soft, gap), so
the reach's widest line in rewrite.rs reads the line from the tuple.
Gates: fmt, clippy -p pdfcraft-edit -p pdfcraft-engine --all-targets
-D warnings, tests edit 362 / engine 388 / 18, xtask parity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
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.
Some OCR tools write the invisible text layer first and the opaque page
image after it. Edit text rewrote such a phrase in the text's place, so
the cover and the new visible text were painted under the scan: the edit
was saved (text extraction finds it) but nothing showed.
When the rewritten paragraph was invisible and an XObject, inline image
or shading is painted after its place, the cover and the new text now go
on top of the page in their own q/Q (place_tagged, tagged Edited), drawn
from the initial graphics state with the paragraph's CTM. Only the state
the following text expects stays in place. Both rewrite paths (single
style and styled rows) do this. An OCR layer drawn over its scan, the
common case, is rewritten in place as before, so reading order is kept.
The test builds a scan whose OCR word comes before the image and checks
the cover and new text show, for one line and for a typed line break.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqmHYx8FiWM5PcsoiErgp9
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.
Main split and refactored the text-rewrite code this PR changes
(automation dispatch.rs, edit added/ and text/, text_regions/styled.rs).
Conflicts in added/store.rs, text/rewrite.rs and text_regions/styled.rs
take main's structure, with per-row alignment applied in it:
- rewrite.rs: LineWriter keeps the paragraph's alignment and the rows'
own (align, aligns); each line looks up its row's alignment
(align_of) for justify and for its offset. Alignment::offset takes the
row's alignment. Wrapped lines carry their row, as wrap returns it.
- styled.rs: wrap_styled_rows also returns each row's draft line
(row_lines), which the kept-rows check counts aligns by.
- Imports for the moved modules (alignment_frame, BlockStyle,
align_code, align_from, content in dispatch.rs).