Integration: the line breaker measures a tab beside a drop cap from the drop cap

#282 measures tabs in the line breaker from where layout starts the line; next's drop caps (#83) start lines beside the drop cap after it (on the right in RTL). The breaker's tab closure uses the same indent as next's width closure and line placement; measured from the frame edge, a tab looked wider and the line broke a word early.
This commit is contained in:
aiongg
2026-10-10 02:35:37 +00:00
parent 57688aa82e
commit 8a8ecb383a
2 changed files with 31 additions and 1 deletions
+13 -1
View File
@@ -924,7 +924,19 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C
// Where each line starts relative to the tab origin, as `layout_line` places it.
let tab = |j: usize, x: f64, i: usize| {
let x0 = slots.get(j).map_or(col.x0, |s| s.0);
let ind = pp.left_indent + if line_no + j == 0 { pp.first_line_indent } else { 0.0 };
let ind = pp.left_indent
+ match &drop_cap {
// Beside a drop cap: at its start-side indent (the right side in RTL).
Some(dc) if line_no + j < dc.lines && col_first_line == 0 => {
if rtl {
0.0
} else {
dc.indent()
}
}
_ if line_no + j == 0 => pp.first_line_indent,
_ => 0.0,
};
tab_advance(&pp.tabs, x0 + ind + x - col.x0, rest.get(i + 1..).unwrap_or_default()).0
};
breaker::greedy(rest, rest_h, &spacing, &width, &tab)
+18
View File
@@ -1916,6 +1916,24 @@ fn drop_cap_spans_lines_and_indents_them() {
assert_eq!(lines[0].range.start, 0);
}
#[test]
fn tab_beside_a_drop_cap_is_measured_from_the_drop_cap() {
// The line breaker measures a tab from where the line starts, which beside a drop cap is
// after the drop cap: measured from the frame edge, the tab looks wider and the line breaks
// a word early.
let stop = designcraft_doc::TabStop { position: 100.0, align: TabAlign::Left, leader: String::new(), align_on: String::new() };
let para = ParaAttrs { tabs: Some(vec![stop]), hyphenate: Some(false), ..drop_cap(3, 1) };
let text = format!("Wort\t{LOREM}");
let (d, sid, _) = drop_doc(&text, para);
let cs = compose_story(&d, sid, &ComposeOptions::default());
let lines = all_lines(&cs);
let space = |g: &PlacedGlyph| g.len > 0 && text.get(g.byte..).and_then(|s| s.chars().next()).is_some_and(char::is_whitespace);
let right = lines[0].glyphs.iter().filter(|g| g.visible && !space(g)).map(|g| g.x + g.adv).fold(0.0, f64::max);
assert!(right <= lines[0].x1 + 0.01, "line 1 ends at {right}, past {}", lines[0].x1);
let word: f64 = lines[1].glyphs.iter().filter(|g| g.len > 0).take_while(|g| !space(g)).map(|g| g.adv).sum();
assert!(right + word + 4.0 > lines[0].x1, "line 2's first word ({word} pt) fits after line 1's end at {right} (edge {})", lines[0].x1);
}
#[test]
fn drop_cap_of_two_characters_as_a_local_override() {
let (d, sid, _) = drop_doc(LOREM, drop_cap(2, 2));