Text: the line before a page break is a paragraph's last line (#283)

A line ended by a column, frame or page break takes the paragraph's
last-line alignment, so a Left Justify paragraph sets it flush left.
A line ended by a forced line break stays justified.

The break that moves the following text to the next column or frame
is the line's last glyph, so a space after the break no longer keeps
the text in the same column.
This commit is contained in:
aiongg authored and GitHub committed 2026-10-10 01:51:26 -04:00
1 parent b744953d70
commit e6b5a52a85
2 files changed
+37 -2

No files matched your search

+11 -2
View File
@@ -933,8 +933,18 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C
let lx0 = x0 + ind_l;
let lx1 = x1 - pp.right_indent;
let last = k + 1 == breaks.len();
// The break character that ends this line, if any (it is the line's last glyph).
let brk = if b.forced && e > s {
e.checked_sub(1).and_then(|i| glyphs.get(i)).map(|g| g.ch).filter(|&c| breaker::is_forced(c))
} else {
None
};
// A column, frame or page break ends the line as a paragraph's last line (last-line
// alignment); a justified line ended by a forced line break stays justified.
let ends_para = last || matches!(brk, Some(story::COLUMN_BREAK | story::FRAME_BREAK | story::PAGE_BREAK));
let forced_mid = brk == Some(story::FORCED_LINE_BREAK) && !last;
let (mut placed, end_x, ratio) =
layout_line(&glyphs, s, e, b.hyphen, lx0, lx1, col.x0, &pp, &spacing, last, b.forced && !last, f.left_page, &bidi_info);
layout_line(&glyphs, s, e, b.hyphen, lx0, lx1, col.x0, &pp, &spacing, ends_para, forced_mid, f.left_page, &bidi_info);
ruby::annotate(db, &styles_tab, &mut placed, doc.settings.glyph_fallback);
let range_end = if last { prange.end } else { glyphs.get(g0 + b.next).map(|g| g.byte).unwrap_or(prange.end) };
let range_start = glyphs.get(s).map(|g| g.byte).unwrap_or(prange.start).min(range_end);
@@ -983,7 +993,6 @@ fn compose_with_db(doc: &Document, story: &Story, frames: &[FrameSpec], opts: &C
line_no += 1;
// Column / frame / page break characters.
if b.forced && e < glyphs.len() + 1 {
let brk = glyphs.get(g0 + b.next.saturating_sub(1)).map(|g| g.ch);
let jumped = match brk {
Some(story::COLUMN_BREAK) => {
next_column(&mut cur);
+26
View File
@@ -573,6 +573,32 @@ fn column_break_moves_following_text() {
assert!(lines[1].baseline < 20.0, "second column starts at the top");
}
#[test]
fn line_before_a_break_character_is_a_last_line() {
// As in InDesign: column, frame and page breaks end the paragraph's last line (set with the
// last-line alignment), while a forced line break inside a justified paragraph is justified.
use designcraft_doc::story::{COLUMN_BREAK, FORCED_LINE_BREAK, FRAME_BREAK, PAGE_BREAK};
for (brk, align, justified) in [
(COLUMN_BREAK, Align::LeftJustified, false),
(FRAME_BREAK, Align::LeftJustified, false),
(PAGE_BREAK, Align::LeftJustified, false),
(PAGE_BREAK, Align::FullyJustified, true),
(FORCED_LINE_BREAK, Align::LeftJustified, true),
] {
// A space after the break is skipped and does not keep the break from moving the text.
let text = format!("one two three{brk} four five six");
let (mut d, sid, fid) = doc_with(&text, Rect::new(0.0, 0.0, 400.0, 300.0), ParaAttrs { align: Some(align), ..Default::default() });
d.item_mut(fid).unwrap().text_frame_mut().unwrap().options.columns = 2;
let cs = compose_story(&d, sid, &ComposeOptions::default());
let l = &cs.frames[0].lines[0];
let full = (l.end_x - l.x1).abs() < 0.6;
assert_eq!(full, justified, "{brk:?} {align:?}: line ends at {} of {}", l.end_x, l.x1);
if brk == COLUMN_BREAK {
assert_eq!(all_lines(&cs)[1].column, 1, "the text after a column break starts the next column");
}
}
}
#[test]
fn tabs_without_stops_use_default_half_inch() {
let (d, sid, _) = doc_with("A\tB", Rect::new(0.0, 0.0, 300.0, 100.0), ParaAttrs::default());