From 076eb68cd0c3c5c2d3edf8d2924ca99dd60f2d53 Mon Sep 17 00:00:00 2001 From: dfgodvan <180286157+dfgodvan@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:52:11 +0300 Subject: [PATCH] Layout: a tab wraps away only from text, not from markers alone A left tab past the right indent, or a right, centre or decimal tab whose text doesn't fit, wrapped whenever any cluster was before it on the line, so a bookmark before the tab was left on a line of its own. It now wraps only when something that takes room is before it. Test: a_bookmark_before_a_left_tab_past_the_right_indent_is_not_a_line. --- crates/layout/src/para.rs | 5 +++-- crates/layout/src/tests.rs | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/crates/layout/src/para.rs b/crates/layout/src/para.rs index ac886878e..98b976880 100644 --- a/crates/layout/src/para.rs +++ b/crates/layout/src/para.rs @@ -2113,7 +2113,8 @@ fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, level: Optio } xs.push(x); if ptab.is_none() && stop.pos > right_edge + 0.01 && stop.align == TabAlign::Left { - if j > c0 { + // Not when only markers (a bookmark, say) are before it: the line is empty. + if (c0..j).any(|k| pl.clusters.get(k).is_some_and(|c| c.kind != ClKind::Marker)) { // Tab past the right indent wraps: like Word, it starts the next line // and goes to the first stop there (a TOC entry's leader and page number). xs.pop(); @@ -2196,7 +2197,7 @@ fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, level: Optio // right margin, after a leader) instead of ending this line. if let Some((tj, ..)) = pending_tab && last_break == Some(tj) - && tj > c0 + && (c0..tj).any(|k| pl.clusters.get(k).is_some_and(|c| c.kind != ClKind::Marker)) { last_break = Some(tj - 1); pending_tab = None; diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index 00252a60f..93585e663 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -1109,6 +1109,31 @@ fn left_tab_past_the_right_indent_does_not_add_a_line() { assert!((page.x - 72.0).abs() < 0.5, "the text after the tab starts at the indent: {}", page.x); } +/// A bookmark before such a tab is not text: the tab does not wrap away from it, and the +/// paragraph breaks as it does without the bookmark (no line holding only the bookmark). +#[test] +fn a_bookmark_before_a_left_tab_past_the_right_indent_is_not_a_line() { + use wordcraft_doc::props::{TabAlign, TabLeader, TabStop}; + let lines = |mark: bool| { + let mut d = Document::from_text("\tPage"); + d.format_paragraphs(&Pos::body(0, 0), &Pos::body(0, 0), &|p| { + p.tabs = Some(vec![TabStop { pos: 500.0, align: TabAlign::Left, leader: TabLeader::None }]) + }) + .unwrap(); + if mark { + d.insert_object(&Pos::body(0, 0), InlineObject::BookmarkStart { name: "top".into() }, &Default::default()).unwrap(); + } + let l = lay(&d); + let Some(para) = l.pages[0].items.iter().find_map(|i| if let Placed::Lines { para, .. } = i { Some(para.clone()) } else { None }) else { + panic!("no lines") + }; + // The lines' clusters, not counting the bookmark's. + let m = usize::from(mark); + para.lines.iter().map(|l| (l.c0.saturating_sub(m), l.c1 - m)).collect::>() + }; + assert_eq!(lines(true), lines(false)); +} + #[test] fn lists_get_labels_and_indent() { let mut d = Document::from_text("one\ntwo\nthree");