mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-13 23:17:49 +00:00
An empty continuous section mark takes no room (#940)
* R21: an empty continuous section mark takes no room An empty paragraph that only carries a section break before a continuous section takes no room in Word: the text after it follows right on, and columns balanced above it don't count it. A break on a paragraph with text keeps its line. Measured in Word for Windows: 10 synthetic cases. The SARP form's fields drifted a line lower at each of its continuous breaks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ROADMAP: keep the progress log separator under its header Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
8c67ef7c28
commit
5383d80f75
4 files changed
+70
-1
No files matched your search
+2
-1
@@ -132,8 +132,8 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
|
||||
## Progress log
|
||||
|
||||
| Date | What landed |
|
||||
| 2026-10-12 | AutoCorrect keys its built-in English replacement list by language (#835): Polish, Croatian, Bosnian and Serbian `i` ("and") is no longer turned into `I`; English and untagged text unchanged |
|
||||
|---|---|
|
||||
| 2026-10-12 | AutoCorrect keys its built-in English replacement list by language (#835): Polish, Croatian, Bosnian and Serbian `i` ("and") is no longer turned into `I`; English and untagged text unchanged |
|
||||
| 2026-10-12 | Ribbon groups fit above their labels: View › Show splits its four check boxes into two columns (Hide White Space, #645, had pushed the fourth onto the "Show" label), Developer › Controls loses the gaps that ran into its label, and Layout › Paragraph (#654) no longer trips a debug assertion and sits 2.5 pt higher; a test draws every tab in every language and fails on any group taller than the ribbon |
|
||||
| 2026-10-12 | Window caption: a non-Latin file name is drawn with a document fallback face (or the CJK UI font), instead of `?` |
|
||||
| 2026-10-12 | 32-bit Windows starts on OpenGL. DirectX 12 graphics pipeline creation returns "The parameter is incorrect" and the app exited before the window appeared |
|
||||
@@ -153,6 +153,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
|
||||
| 2026-10-12 | Text frames (`w:framePr`) in headers, footers, cells and text boxes: placed apart with text wrapping around them, and kept on save (a framed footer page number took a line of its own and was lost on save) |
|
||||
| 2026-10-12 | List indents like Word's when a numbered style has indents of its own: the style giving the list (and styles based on it) beat the level's indents, and a paragraph with its list switched off drops the styles' indents (27 cases measured in Word for Windows); NIH's Heading 3s now sit at their 0.9" |
|
||||
| 2026-10-12 | Keep with next like Word (20 cases measured in Word for Windows): a kept paragraph moves to the next page only when it would fit on this one by itself, otherwise it breaks as usual; chains of kept paragraphs move together |
|
||||
| 2026-10-12 | An empty paragraph that only carries a continuous section break takes no room, as in Word (10 cases measured in Word for Windows): text after it follows right on and balanced columns don't count it |
|
||||
| 2026-10-11 | Hide White Space (#645): `view.hideWhiteSpace` (View › Show checkbox, or double-click the gap between pages) collapses margins, headers and footers between pages to a thin line in Print Layout; screen only, page count unchanged |
|
||||
| 2026-10-11 | Fonts: Palatino Linotype and Book Antiqua fall back to Palatino (macOS) or TeX Gyre Pagella / P052 / URW Palladio (Linux) before generic serif (#670) |
|
||||
|---|---|
|
||||
|
||||
@@ -600,6 +600,9 @@ struct Ctx<'a> {
|
||||
uses_totals: bool,
|
||||
/// Whether to note `uses_totals` now (off while laying out headers and footers).
|
||||
track_totals: bool,
|
||||
/// Body blocks that are empty paragraphs carrying only a section break before a continuous
|
||||
/// section, in order: they take no room (see [`place_para`]).
|
||||
quiet_marks: Vec<usize>,
|
||||
}
|
||||
|
||||
impl Ctx<'_> {
|
||||
@@ -1755,11 +1758,18 @@ fn layout_pass(
|
||||
grid: para::Grid::default(),
|
||||
uses_totals: false,
|
||||
track_totals: true,
|
||||
quiet_marks: Vec::new(),
|
||||
};
|
||||
let sections = doc.sections();
|
||||
let web = matches!(opts.view, ViewMode::Web | ViewMode::Draft | ViewMode::Outline);
|
||||
let read = opts.view == ViewMode::Read;
|
||||
let reflow = web || read;
|
||||
ctx.quiet_marks = sections
|
||||
.windows(2)
|
||||
.filter(|w| w[1].1.start == SectionStart::Continuous)
|
||||
.map(|w| w[0].0)
|
||||
.filter(|end| doc.body.get(*end).and_then(|b| b.as_para()).is_some_and(|p| p.text.is_empty()))
|
||||
.collect();
|
||||
let outline = (opts.view == ViewMode::Outline).then(|| OutlinePass::new(doc, opts.outline.clone().unwrap_or_default()));
|
||||
let default_sect = SectionProps::default();
|
||||
let mut web_sect;
|
||||
@@ -2584,7 +2594,25 @@ fn para_notes(doc: &Document, p: &Paragraph, pl: &ParaLayout) -> Vec<(usize, u32
|
||||
notes
|
||||
}
|
||||
|
||||
/// Place a body paragraph. An empty paragraph that only carries a section break before a
|
||||
/// continuous section takes no room (Word for Windows: the text after it follows right on, and
|
||||
/// columns balanced above it don't count it).
|
||||
fn place_para(ctx: &mut Ctx, pb: &mut PageBuilder, p: &Paragraph, block: usize, body_top: f32) {
|
||||
if !pb.web && ctx.quiet_marks.binary_search(&block).is_ok() {
|
||||
// Its (empty) line stays where the text ended, even at the bottom of a full column.
|
||||
let (y, prev, bottom, content_bottom, at) = (pb.y, pb.prev.clone(), pb.bottom, pb.content_bottom, (pb.pages.len(), pb.col));
|
||||
pb.bottom = f32::MAX / 4.0;
|
||||
place_para_in_flow(ctx, pb, p, block, body_top);
|
||||
pb.bottom = bottom;
|
||||
if (pb.pages.len(), pb.col) == at {
|
||||
(pb.y, pb.prev, pb.content_bottom) = (y, prev, content_bottom);
|
||||
}
|
||||
return;
|
||||
}
|
||||
place_para_in_flow(ctx, pb, p, block, body_top);
|
||||
}
|
||||
|
||||
fn place_para_in_flow(ctx: &mut Ctx, pb: &mut PageBuilder, p: &Paragraph, block: usize, body_top: f32) {
|
||||
let width = pb.col_w();
|
||||
// Floating objects anchored here join the page's wrap areas before the text is laid out; if
|
||||
// the paragraph then starts somewhere else (another column or page, or contextual spacing
|
||||
|
||||
@@ -4387,3 +4387,42 @@ fn keep_with_next_moves_a_paragraph_only_when_it_would_fit() {
|
||||
assert_eq!(pages(fit + 1, kl.clone(), none.clone()), [1, 1, 1, 1, 1, 1], "with keep lines together it moves whole");
|
||||
assert_eq!(pages(fit - 1, kn.clone(), kn.clone()), [1, 1, 1, 1, 1, 1], "a kept chain moves together");
|
||||
}
|
||||
|
||||
/// An empty paragraph that only carries a section break before a continuous section takes no
|
||||
/// room, measured in Word for Windows (`plan/word` R21, 10 cases): the text after it follows right
|
||||
/// on, and columns balanced above it don't count it (two one-line paragraphs in two columns sit
|
||||
/// side by side). A section break on a paragraph with text keeps its line, and so does any break
|
||||
/// before a section on a new page. The SARP form's fields drifted a line lower at each of its
|
||||
/// many continuous breaks.
|
||||
#[test]
|
||||
fn an_empty_continuous_section_mark_takes_no_room() {
|
||||
use wordcraft_doc::section::{SectionProps, SectionStart};
|
||||
// "Pa", the section's last paragraph (`mark`), "Pb", "Pc" in the next section of `cols`
|
||||
// columns, then "Pd" after a second break.
|
||||
let tops = |mark: &str, cols: u32, start: SectionStart| {
|
||||
let mut d = Document::from_text(&format!("Pa\n{mark}\nPb\nPc\n\nPd"));
|
||||
d.para_mut(StoryRef::Body, &Path::top(1)).unwrap().section =
|
||||
Some(Box::new(SectionProps { start: SectionStart::Continuous, ..Default::default() }));
|
||||
let mut s = SectionProps { start, ..Default::default() };
|
||||
s.columns.count = cols;
|
||||
d.para_mut(StoryRef::Body, &Path::top(4)).unwrap().section = Some(Box::new(s));
|
||||
d.last_section.start = SectionStart::Continuous;
|
||||
let l = lay(&d);
|
||||
[0, 2, 3, 5].map(|i| {
|
||||
let c = l.caret(&Pos::body(i, 0)).unwrap();
|
||||
(c.page, (c.top * 10.0).round() / 10.0, c.x)
|
||||
})
|
||||
};
|
||||
let one = tops("", 1, SectionStart::Continuous);
|
||||
let line = one[2].1 - one[1].1;
|
||||
assert!((one[1].1 - one[0].1 - line).abs() < 0.15, "Pb right under Pa: {one:?}");
|
||||
assert!((one[3].1 - one[2].1 - line).abs() < 0.15, "Pd right under Pc: {one:?}");
|
||||
let text = tops("Mark", 1, SectionStart::Continuous);
|
||||
assert!((text[1].1 - text[0].1 - 2.0 * line).abs() < 0.15, "a mark with text keeps its line: {text:?}");
|
||||
let two = tops("", 2, SectionStart::Continuous);
|
||||
assert_eq!(two[1].1, two[2].1, "balanced: Pb and Pc side by side: {two:?}");
|
||||
assert!(two[2].2 > two[1].2 + 100.0, "Pc in the second column: {two:?}");
|
||||
assert!((two[3].1 - two[1].1 - line).abs() < 0.15, "Pd a line below them: {two:?}");
|
||||
let new_page = tops("", 1, SectionStart::NextPage);
|
||||
assert_eq!(new_page[1].0, 1, "a next-page section starts a page: {new_page:?}");
|
||||
}
|
||||
@@ -81,6 +81,7 @@ font can't match Word's metrics.
|
||||
| 2026-10-12 | trivial | Text frames in headers, footers, cells and text boxes are placed like floating objects with the text wrapping around them; body frames still lay out in line |
|
||||
| 2026-10-12 | trivial | List indents and style indents combine like Word's: a numbered style's own indents (and those of styles based on it) beat the list level's, its base styles' don't, the paragraph's own list beats every style; a paragraph that switches its list off (`numId` 0) drops the styles' indents |
|
||||
| 2026-10-12 | trivial | Keep with next: a paragraph that doesn't fit on the page anyway breaks as usual (its last lines go over with the next paragraph) instead of moving whole; a chain of kept paragraphs moves together |
|
||||
| 2026-10-12 | trivial | Continuous section breaks: an empty paragraph carrying only the break takes no room (text after it follows on; balanced columns ignore it) |
|
||||
| 2026-10-11 | trivial | Chinese documents (#534): indents in characters, a page-anchored floating table after a full page moves to the next page, no blank page from a page break at the bottom of a full page |
|
||||
| 2026-10-11 | trivial | East Asian list number formats (一、二、三, 第一章, ①); list labels whose characters come from two fonts draw each part with its own font (the digits of "1、" no longer show as other letters); a list level without indents leaves the label at the paragraph edge (#534) |
|
||||
| 2026-10-11 | trivial | Vertical sections: `w:textDirection` in `w:sectPr`, upright East Asian glyphs (#491) |
|
||||
|
||||
Reference in new issue
Block a user