mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-05 18:19:57 +00:00
Text: Indent to Here beside a drop cap isn't indented by the drop cap again (#2080)
The lines after an Indent to Here start at the character, which already sits past the drop cap, but the lines beside the drop cap still added its width on top. They no longer do. Adds tests for the drop cap, for Indent to Here after a tab (the usual hanging number) and for a right-to-left paragraph, and documents how the indent behaves beside a text wrap.
This commit is contained in:
1 parent
7e3d0fe2f5
commit
d0271a562f
2 files changed
+64
-3
No files matched your search
@@ -1817,6 +1817,8 @@ fn place_sub_objects(
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets one paragraph's lines, column after column and frame after frame. It stops when the
|
||||
/// paragraph ends, when the frames run out, or when an earlier paragraph has to be set again.
|
||||
fn place_paragraph_lines(shared: PlacementShared<'_, '_>, paragraph: ParagraphLines<'_>) -> LinePlacement {
|
||||
let PlacementShared {
|
||||
doc,
|
||||
@@ -1916,6 +1918,9 @@ fn place_paragraph_lines(shared: PlacementShared<'_, '_>, paragraph: ParagraphLi
|
||||
});
|
||||
}
|
||||
let pp = indented.as_ref().unwrap_or(para_props);
|
||||
// The Indent to Here character sits past the drop cap, so the lines that start at it
|
||||
// aren't indented by the drop cap again.
|
||||
let beside_cap = drop_cap.filter(|_| indented.is_none());
|
||||
let f = &frames[cur.fi];
|
||||
let base = cols[cur.fi][cur.col.min(cols[cur.fi].len() - 1)];
|
||||
let col = cur.clip(base);
|
||||
@@ -1941,7 +1946,7 @@ fn place_paragraph_lines(shared: PlacementShared<'_, '_>, paragraph: ParagraphLi
|
||||
props: pp,
|
||||
slots: &slots,
|
||||
column: col,
|
||||
drop_cap,
|
||||
drop_cap: beside_cap,
|
||||
line_no,
|
||||
col_first_line,
|
||||
rtl,
|
||||
@@ -2066,7 +2071,7 @@ fn place_paragraph_lines(shared: PlacementShared<'_, '_>, paragraph: ParagraphLi
|
||||
let col_w = notes.area(&cols[cur.fi], cur.col).map_or(0.0, |r| r.width());
|
||||
let note_column = NoteColumn { frame_index: cur.fi, column_index: cur.col, width: col_w, frame: f };
|
||||
// A drop cap sits at the start-side indent; the first-line indent doesn't move it.
|
||||
let beside = drop_cap.filter(|dc| line_no < dc.lines && col_first_line == 0).map(|dc| dc.indent());
|
||||
let beside = beside_cap.filter(|dc| line_no < dc.lines && col_first_line == 0).map(|dc| dc.indent());
|
||||
let first_line = if line_no == 0 { pp.first_line_indent } else { 0.0 };
|
||||
let (ind_l, ind_r) = match beside {
|
||||
Some(w) if rtl => (pp.left_indent, pp.right_indent + w),
|
||||
@@ -4963,7 +4968,9 @@ fn distribute(line: &[Glyph], spaces: &[usize], extra: f64, sp: &Spacing, add: &
|
||||
|
||||
/// The paragraph settings for the lines after `line` when it holds an Indent to Here character
|
||||
/// (the last one, if several): the start-side indent reaches the character, measured from the
|
||||
/// start edge of the line's segment `x0..x1`. `None` when there is none or the indent is unchanged.
|
||||
/// start edge of the line's segment `x0..x1`. Like the paragraph's own indents it then applies
|
||||
/// from each later line's segment, so beside a text wrap it starts at the wrap's edge. `None`
|
||||
/// when there is none or the indent is unchanged.
|
||||
fn indent_here(
|
||||
line: &[Glyph],
|
||||
placed: &[PlacedGlyph],
|
||||
|
||||
@@ -1934,6 +1934,60 @@ fn indent_to_here_starts_the_paragraphs_later_lines_at_the_character() {
|
||||
assert!((start - 78.0).abs() < 0.01, "plain second line starts at {start}");
|
||||
}
|
||||
|
||||
/// Where each line after the first starts, and the Indent to Here character's x on the first.
|
||||
fn indent_to_here_starts(text: &str, para: ParaAttrs) -> (f64, Vec<f64>) {
|
||||
let (d, sid, _) = doc_with(text, Rect::new(72.0, 36.0, 272.0, 600.0), para);
|
||||
let cs = compose_story(&d, sid, &Default::default());
|
||||
let lines = all_lines(&cs);
|
||||
assert!(lines.len() > 4, "the text wraps");
|
||||
let byte = text.find('\u{e005}').unwrap();
|
||||
let here = lines[0].glyphs.iter().find(|g| g.byte == byte && g.len > 0).unwrap().x;
|
||||
let starts = lines.iter().skip(1).map(|l| l.glyphs.iter().find(|g| g.visible).unwrap().x).collect();
|
||||
(here, starts)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent_to_here_after_a_tab_hangs_the_text_after_the_number() {
|
||||
// The usual hanging number: "1.", a tab to the next stop, then Indent to Here.
|
||||
let tabs = vec![designcraft_doc::TabStop { position: 24.0, align: TabAlign::Left, leader: String::new(), align_on: String::new() }];
|
||||
let (here, starts) = indent_to_here_starts(&format!("1.\t\u{e005}{LOREM}"), ParaAttrs { tabs: Some(tabs), ..Default::default() });
|
||||
assert!((here - (72.0 + 24.0)).abs() < 0.01, "the character sits at the tab stop: {here}");
|
||||
for (i, start) in starts.iter().enumerate() {
|
||||
assert!((start - here).abs() < 0.01, "line {} starts at {start}, not at {here}", i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent_to_here_beside_a_drop_cap_is_not_indented_by_the_drop_cap_again() {
|
||||
// A three-line drop cap, then Indent to Here on the first line: the lines beside the drop
|
||||
// cap and the lines below it all start at the character, none further in.
|
||||
let (here, starts) = indent_to_here_starts(&format!("Drop \u{e005}{LOREM}"), drop_cap(3, 1));
|
||||
for (i, start) in starts.iter().enumerate() {
|
||||
assert!((start - here).abs() < 0.01, "line {} starts at {start}, not at {here}", i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn indent_to_here_in_a_right_to_left_paragraph_indents_from_the_right() {
|
||||
// Hebrew words (the bidi classes decide the order; no Hebrew font has to be present): the
|
||||
// lines after the first end where the character sits, measured from the right edge.
|
||||
let words = "שלום עולם ".repeat(40);
|
||||
let text = format!("שלום \u{e005}{words}");
|
||||
let para = ParaAttrs { direction: Some(designcraft_doc::TextDirection::RightToLeft), align: Some(Align::Right), ..Default::default() };
|
||||
let (d, sid, _) = doc_with(&text, Rect::new(72.0, 36.0, 272.0, 600.0), para);
|
||||
let cs = compose_story(&d, sid, &Default::default());
|
||||
let lines = all_lines(&cs);
|
||||
assert!(lines.len() > 3, "the text wraps");
|
||||
let byte = text.find('\u{e005}').unwrap();
|
||||
let here = lines[0].glyphs.iter().find(|g| g.byte == byte && g.len > 0).unwrap().x;
|
||||
assert!(here < 272.0 - 1.0, "the character follows the first word: {here}");
|
||||
let right_edge = |l: &Line| l.glyphs.iter().filter(|g| g.visible).map(|g| g.x + g.adv).fold(f64::MIN, f64::max);
|
||||
for (i, line) in lines.iter().enumerate().skip(1) {
|
||||
let end = right_edge(line);
|
||||
assert!((end - here).abs() < 0.01, "line {i} ends at {end}, not at {here}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn right_indent_tab_leaders_follow_stop_precedence_without_moving_text() {
|
||||
let text = "Intro\u{e006}12";
|
||||
|
||||
Reference in new issue
Block a user