M7: review follow-up, a Thai first word counts the opening spaces

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018cECVMvRnc9mHNFfVp7nXd
This commit is contained in:
DewandClaude Opus 5.5 authored and Rutthawit Chuennuawapan committed 2026-11-03 20:24:05 +07:00
1 parent fb6830c4dd
commit ff556b537e
3 files changed
+18 -2

No files matched your search

+2
View File
@@ -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};
+3 -2
View File
@@ -107,7 +107,8 @@ fn thai_lines(t: &AddedText, para: &str, width: f64) -> Result<Vec<String>, 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))
}
+13
View File
@@ -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]