From c9abf15e7c503c5b794f1dfd8a2f245da889040b Mon Sep 17 00:00:00 2001 From: RiceDee <345114586+RiceDee@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:57:41 +1100 Subject: [PATCH] fix(edit): only a row aligned on its own is rewritten A nonempty aligns counted as a style change, so every row of the paragraph was rebuilt from the text state: rows left unselected lost their source operators, and with TJ word gaps their words moved by about a point and a half while the row's outer width stayed the same. aligns no longer makes an edit non-plain; a row whose own line has an alignment override is rewritten, and the others keep their operators. --- crates/edit/src/tests.rs | 20 ++++++++++++++++++++ crates/edit/src/text_regions.rs | 12 +++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/crates/edit/src/tests.rs b/crates/edit/src/tests.rs index f12430561e..afd908f86c 100644 --- a/crates/edit/src/tests.rs +++ b/crates/edit/src/tests.rs @@ -6975,6 +6975,26 @@ fn one_row_of_a_paragraph_takes_its_own_alignment() { assert!((row("eight")[2] - block.rect[2]).abs() < 1.0, "the second row ends at the right edge: {:?} vs {}", row("eight"), block.rect[2]); } +/// Only a row given an alignment of its own is rewritten. The others keep their source operators, +/// `TJ` word gaps included, so their words stay exactly where they were: rebuilt from the text, +/// the first row's words moved by about a point and a half while its outer width stayed the same. +#[test] +fn rows_without_their_own_alignment_keep_their_operators() { + let first = "[(One) -250 (two) -250 (three) -250 (four)] TJ"; + let mut doc = text_page(&format!("BT /F1 10 Tf 12 TL 72 700 Td {first} T* [(five) -250 (six) -250 (seven) -250 (eight)] TJ T* (nine ten) Tj ET")); + let block = text::text_blocks(&doc, 0).unwrap()[0].clone(); + assert_eq!(block.editor_text.matches('\n').count(), 2, "one paragraph of three rows: {:?}", block.editor_text); + let row = |doc: &Document, starts: &str| text::text_lines(doc, 0).unwrap().into_iter().find(|l| l.text.starts_with(starts)).expect("the row"); + let ink = row(&doc, "One").ink; + let style = text::BlockStyle { aligns: vec![None, None, Some(added::Align::Right)], ..Default::default() }; + text::rewrite_block(&mut doc, 0, 0, Some(&block.editor_text), &style).unwrap(); + let doc = reopen(&doc); + assert_eq!(row(&doc, "One").ink, ink, "the first row's words stay where they were"); + let content = String::from_utf8_lossy(&page_content_bytes(&doc, 0)).into_owned(); + assert!(content.contains(first), "the first row keeps its operator: {content}"); + assert!((row(&doc, "nine").rect[2] - block.rect[2]).abs() < 1.0, "the last row is right-aligned: {:?}", row(&doc, "nine").rect); +} + /// The same in a region of several styles: only the short first row is centred. #[test] fn one_row_of_a_styled_region_takes_its_own_alignment() { diff --git a/crates/edit/src/text_regions.rs b/crates/edit/src/text_regions.rs index 5d0a01e97d..965792908b 100644 --- a/crates/edit/src/text_regions.rs +++ b/crates/edit/src/text_regions.rs @@ -874,10 +874,16 @@ pub(super) fn rewrite_styled_block( let pieces = Pieces::join(&streams.iter().map(|(_, d)| d.as_slice()).collect::>()); let ops = pieces.parse().ops; // A row the edit doesn't reach keeps its source operators, so its kerning, TJ word gaps and - // glyph widths stay exactly as they were instead of being rebuilt from the text state. - let plain = BlockStyle { ranges: Vec::new(), ..style.clone() } == BlockStyle::default(); + // glyph widths stay exactly as they were instead of being rebuilt from the text state. A row + // aligned on its own is reached; the rows around it are not. + let plain = BlockStyle { ranges: Vec::new(), aligns: Vec::new(), ..style.clone() } == BlockStyle::default(); let mut kept = if plain { - untouched_rows(block, draft, &row_ranges).into_iter().zip(&rows).map(|(same, row)| same && row.iter().all(|c| c.1 < source_count)).collect() + untouched_rows(block, draft, &row_ranges) + .into_iter() + .zip(&rows) + .zip(&row_lines) + .map(|((same, row), line)| same && row.iter().all(|c| c.1 < source_count) && style.aligns.get(*line).copied().flatten().is_none()) + .collect() } else { vec![false; rows.len()] };