From ff556b537e2bee94aa042fa0a1326ff6263d973a Mon Sep 17 00:00:00 2001 From: Dew Date: Mon, 19 Oct 2026 23:38:29 +0700 Subject: [PATCH] M7: review follow-up, a Thai first word counts the opening spaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A first word that fit a row on its own but not after the spaces opening its paragraph was placed whole and ran past the row's end. Whether the word is too wide now counts the width those spaces already use, so it breaks between clusters on the first row instead. Reported by the CodeRabbit review on #3492. The new case fails without the fix: " ประปา" stayed one row, wider than its 52 pt box. shaped_width is pub(crate) so the test can measure rows. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_018cECVMvRnc9mHNFfVp7nXd --- crates/edit/src/added.rs | 2 ++ crates/edit/src/added/layout.rs | 5 +++-- crates/edit/src/tests.rs | 13 +++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/edit/src/added.rs b/crates/edit/src/added.rs index a603a37060..250f71a533 100644 --- a/crates/edit/src/added.rs +++ b/crates/edit/src/added.rs @@ -29,6 +29,8 @@ use draw::{GLYPHS_PER_FONT, LaidLine, draw_laid_shaped}; use layout::Piece; #[cfg(test)] pub(crate) use layout::WRAP_CALLS; +#[cfg(test)] +pub(crate) use layout::shaped_width; pub use layout::{PASTE_STEP, first_undrawable, lines, paste_place, paste_rect, text_rect, text_width}; pub use store::{add_content, delete_content, isolate_content, list_added, scan_markers, update_content}; pub use types::{Added, AddedImage, AddedText, Align, CHAR_SPACING_LIMIT, Family, MarkerScan}; diff --git a/crates/edit/src/added/layout.rs b/crates/edit/src/added/layout.rs index 62f24c5c4c..c76db6463a 100644 --- a/crates/edit/src/added/layout.rs +++ b/crates/edit/src/added/layout.rs @@ -107,7 +107,8 @@ fn thai_lines(t: &AddedText, para: &str, width: f64) -> Result, Edit end_row(&mut lines, &mut line); used = 0.0; } - let too_wide = blank(&line) && ink > width; + // Too wide for what is left of the row, counting the spaces that open the paragraph. + let too_wide = blank(&line) && used + ink > width; for (text, advance) in word { if too_wide && !blank(&line) && !blank(&text) && used + advance > width { end_row(&mut lines, &mut line); @@ -319,7 +320,7 @@ pub(super) fn shaped_layout(t: &AddedText, line: &str, rtl: bool) -> Result<(Vec /// The width of `s` in a shaped-text item; the item's font's estimate if it can't be shaped (drawing /// then reports why). -fn shaped_width(t: &AddedText, s: &str, rtl: bool) -> f64 { +pub(crate) fn shaped_width(t: &AddedText, s: &str, rtl: bool) -> f64 { shaped_layout(t, s, rtl).map(|(_, w)| w).unwrap_or_else(|_| latin_width(t, s)) } diff --git a/crates/edit/src/tests.rs b/crates/edit/src/tests.rs index d8ba0a8aeb..02378057b7 100644 --- a/crates/edit/src/tests.rs +++ b/crates/edit/src/tests.rs @@ -5826,6 +5826,19 @@ fn added_thai_text_wraps_between_dictionary_words() { assert!(rows[0].starts_with(" "), "the opening spaces are kept at width {width}: {rows:?}"); assert_eq!(rows.concat().trim_start(), "ประปาสะอาด"); } + // A first word that fits a row alone but not after the opening spaces breaks between clusters + // on the first row rather than running past its end. + let word = "ประปา"; + let word_w = super::added::shaped_width(&t, word, false); + let indent_w = super::added::shaped_width(&t, " ", false); + let width = word_w + indent_w / 2.0; + let indented = AddedText { rect: [0.0, 0.0, width, 400.0], text: format!(" {word}"), ..t.clone() }; + let rows = super::added::lines(&indented); + for row in &rows { + assert!(super::added::shaped_width(&t, row, false) <= width + 0.5, "{row:?} overflows {width}: {rows:?}"); + } + assert!(rows.len() > 1 && rows[0].starts_with(" "), "{rows:?}"); + assert_eq!(rows.concat().trim_start(), word); } #[test]