mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-29 21:07:24 -06:00
244: preserve list metrics in keep-next lookahead
This commit is contained in:
@@ -3176,7 +3176,7 @@ fn place_para_in_flow(ctx: &mut Ctx, pb: &mut PageBuilder, p: &Paragraph, block:
|
||||
// next alone moves a paragraph only when it would fit here by itself (Word for Windows): one
|
||||
// that doesn't breaks as usual, its last lines going over with the next paragraph.
|
||||
if pl.rp.keep_lines || (pl.rp.keep_next && pb.y + pl.height <= pb.bottom + 0.01) {
|
||||
let need = pl.height + if pl.rp.keep_next { next_first_line(ctx, block, width, 0) } else { 0.0 };
|
||||
let need = pl.height + if pl.rp.keep_next { next_first_line(ctx, block, width, 0, &mut ctx.counters.clone()) } else { 0.0 };
|
||||
// (Against the whole column: balanced columns grow to hold it.)
|
||||
if pb.y + need > pb.bottom && !pb.at_top() && need <= pb.uncapped_bottom() - pb.top {
|
||||
pb.advance(block, body_top);
|
||||
@@ -3601,15 +3601,17 @@ fn space_after_kept(ctx: &Ctx, block: usize, rp: &wordcraft_doc::resolve::Resolv
|
||||
/// of it under keep lines together — or all of it and what it keeps with in turn when it is
|
||||
/// kept with the next too (a chain moves together in Word). Word carries a kept heading over
|
||||
/// when the paragraph after it cannot start on the page (#966).
|
||||
fn next_first_line(ctx: &mut Ctx, block: usize, width: f32, depth: usize) -> f32 {
|
||||
fn next_first_line(ctx: &mut Ctx, block: usize, width: f32, depth: usize, counters: &mut Counters) -> f32 {
|
||||
match ctx.doc.body.get(block + 1).map(|b| &**b) {
|
||||
Some(Block::Para(p)) => {
|
||||
// Don't advance list counters for a lookahead: lay out without the label.
|
||||
// Keep list indents and label metrics without consuming the document counters.
|
||||
let rp = ctx.doc.styles.resolve_para(&p.props);
|
||||
let label =
|
||||
p.props.numbering.or(rp.numbering).filter(|n| n.num != 0).and_then(|n| counters.next_label(&ctx.doc.numbering, n.num, n.level));
|
||||
let env = para::ParaEnv {
|
||||
doc: ctx.doc,
|
||||
width,
|
||||
label: None,
|
||||
label,
|
||||
fields: &ctx.fields,
|
||||
show_hidden: ctx.opts.show_hidden,
|
||||
field_codes: ctx.opts.field_codes,
|
||||
@@ -3623,10 +3625,9 @@ fn next_first_line(ctx: &mut Ctx, block: usize, width: f32, depth: usize) -> f32
|
||||
grid: ctx.grid,
|
||||
fit_objects: false,
|
||||
};
|
||||
let _ = rp;
|
||||
let pl = para::layout_para(p, &env);
|
||||
if pl.rp.keep_next && depth < 8 {
|
||||
pl.rp.space_before + pl.height + pl.rp.space_after + next_first_line(ctx, block + 1, width, depth + 1)
|
||||
pl.rp.space_before + pl.height + pl.rp.space_after + next_first_line(ctx, block + 1, width, depth + 1, counters)
|
||||
} else {
|
||||
// The room this one needs to start on the page with the kept paragraph before
|
||||
// it (#966).
|
||||
|
||||
@@ -7263,3 +7263,50 @@ fn row_nil_overrides_style() {
|
||||
d.insert_block(StoryRef::Body, &Path::top(1), wordcraft_doc::Block::Table(t)).unwrap();
|
||||
assert_eq!(rules_on_page0(&lay(&d)), vec![2.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keep_next_counts_list_indent() {
|
||||
use wordcraft_doc::props::{LineSpacing, NumRef};
|
||||
let mut doc = Document::new();
|
||||
doc.last_section.page_w = 240.0;
|
||||
doc.last_section.page_h = 200.0;
|
||||
doc.last_section.margin_top = 20.0;
|
||||
doc.last_section.margin_bottom = 20.0;
|
||||
doc.last_section.margin_left = 20.0;
|
||||
doc.last_section.margin_right = 20.0;
|
||||
let id = doc.numbering.add_list(wordcraft_doc::ListKind::Numbered);
|
||||
let level = &mut doc.numbering.abstracts[0].levels[0];
|
||||
level.indent = 100.0;
|
||||
level.hanging = 0.0;
|
||||
let props = ParaProps {
|
||||
space_before: Some(0.0),
|
||||
space_after: Some(0.0),
|
||||
line_spacing: Some(LineSpacing::Exactly(12.0)),
|
||||
widow_control: Some(true),
|
||||
..Default::default()
|
||||
};
|
||||
let para = |text: &str, props: ParaProps| {
|
||||
let mut p =
|
||||
Paragraph::with_text(text, wordcraft_doc::CharProps { font: Some("Source Sans 3".into()), size: Some(12.0), ..Default::default() });
|
||||
p.props = props;
|
||||
wordcraft_doc::para_block(p)
|
||||
};
|
||||
doc.body = vec![
|
||||
para("Filler", ParaProps { line_spacing: Some(LineSpacing::Exactly(136.0)), ..props.clone() }),
|
||||
para("Heading", ParaProps { keep_next: Some(true), keep_lines: Some(true), ..props.clone() }),
|
||||
para("Alpha beta gamma delta", ParaProps { numbering: Some(NumRef { num: id, level: 0 }), ..props }),
|
||||
];
|
||||
let layout = lay(&doc);
|
||||
assert_eq!(layout.caret(&Pos::body(2, 0)).unwrap().page, 1, "the indented list cannot start below the heading");
|
||||
assert_eq!(layout.caret(&Pos::body(1, 0)).unwrap().page, 1, "the kept heading must follow it");
|
||||
let labels: Vec<_> = layout
|
||||
.pages
|
||||
.iter()
|
||||
.flat_map(|p| &p.items)
|
||||
.filter_map(|item| match item {
|
||||
Placed::Lines { para, .. } => para.label.as_ref().map(|label| label.text.as_str()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(labels, ["1."], "lookahead must not consume a list counter");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Layout and pagination parity with Microsoft Word
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-15 · **Change:** trivial (keep-with-next lookahead retains list indents and label metrics without advancing counters; previously trivial: consistent cell widths override a stale table grid; previously trivial: multi-paragraph field instructions no longer add blank lines; previously trivial: page-break-only list paragraphs hide their labels; previously trivial: floating-table overlap ignores empty line-break lines; previously trivial: paragraph spacing adds up when the document turns HTML auto spacing off; previously trivial: an inherited footer sizes its percentage tables by its own section; previously trivial: an empty section mark stays on a full page; previously trivial: a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
|
||||
A document only "opens correctly" if its pages break where Word breaks them: the same lines, the
|
||||
same page count, footnotes and pictures on the same pages. This checklist covers line breaking,
|
||||
@@ -159,6 +159,7 @@ No overall parity estimate changes from these probes.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-15 | trivial | Keep-with-next lookahead uses copied list counters so list indents and label metrics match placement. An original regression keeps a heading with an indented list that needs two lines. |
|
||||
| 2026-10-14 | trivial | Row table border exceptions draw without leaking into other rows |
|
||||
| 2026-10-14 | trivial | Consistent cell widths override a stale table grid |
|
||||
| 2026-10-14 | trivial | Paragraphs inside complex-field instructions take no flow height; empty result paragraphs keep their line. |
|
||||
|
||||
Reference in New Issue
Block a user