Layout: page numbers can restart at 0 (#1601)

w:pgNumType w:start="0" was read but laid out as 1: the page counter
was set to one less than the restart and could not go below zero. A
report numbering its cover 0 read "Page 2" on the page Word calls 1.
The restart is now the next page's number itself.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
Fin Warman
2026-10-14 19:38:36 -04:00
committed by GitHub
co-authored by Claude Opus 5.5 Brandon Thomas
parent e9e8af517c
commit ba1e28382d
3 changed files with 18 additions and 4 deletions
+7 -3
View File
@@ -1400,6 +1400,9 @@ struct PageBuilder<'a> {
/// The previous paragraph ended with a page break (its mark stayed with the break): the next
/// paragraph, first on the new page, gets no space before (Word).
after_break_mark: bool,
/// The number the next page takes instead of counting on: a section restarting its
/// numbering, possibly at 0 (a cover page numbered 0).
restart_at: Option<u32>,
/// Wrap areas of floating objects on this page (see [`wrap_area`]).
excl: Vec<WrapArea>,
/// Wrap areas of the floating tables on this page, which others may not overlap.
@@ -1815,7 +1818,7 @@ impl PageBuilder<'_> {
self.line_no = 0;
}
let s = self.sect;
self.number = self.number.saturating_add(1);
self.number = self.restart_at.take().unwrap_or_else(|| self.number.saturating_add(1));
let (w, h) = if self.web { (s.page_w, f32::MAX / 4.0) } else { (s.page_w, s.page_h) };
// A footer taller than the bottom margin pushes the body up (Word: no gap), leaving it at
// least a little room.
@@ -2045,6 +2048,7 @@ fn layout_pass(
orig_bottom: 0.0,
prev: None,
after_break_mark: false,
restart_at: None,
excl: Vec::new(),
float_tables: Vec::new(),
line_no: 0,
@@ -2137,8 +2141,8 @@ fn layout_pass(
pb.sect = sect;
pb.sect_idx = si;
}
if let Some(n) = restart {
pb.number = n.saturating_sub(1);
if restart.is_some() {
pb.restart_at = restart;
}
pb.foot_top = first_foot;
pb.new_page(block, first_top);
+9
View File
@@ -688,6 +688,15 @@ fn page_break_char_starts_new_page() {
assert_eq!(l.pages.len(), 2);
}
#[test]
fn page_numbers_can_restart_at_zero() {
// `w:pgNumType w:start="0"`: a cover page numbered 0, so the page after it reads 1.
let mut d = Document::from_text("Cover\u{000C}Contents");
d.last_section.page_num_start = Some(0);
let numbers: Vec<u32> = lay(&d).pages.iter().map(|p| p.number).collect();
assert_eq!(numbers, [0, 1]);
}
#[test]
fn center_and_right_alignment() {
let mut d = Document::from_text("abc\nabc\nabc");
+2 -1
View File
@@ -1,6 +1,6 @@
# Layout and pagination parity with Microsoft Word
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (justified vertical page alignment, #1320; previously trivial: a table's percentage width past 100% is kept; 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-14 · **Change:** 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,
@@ -165,6 +165,7 @@ No overall parity estimate changes from these probes.
| 2026-10-14 | trivial | A word too wide for the strip beside a square-wrapped object moves past it whole instead of being split across the strip (#1376) |
| 2026-10-14 | trivial | Russian letter list formats (`russianLower`, `russianUpper`) draw а) б) в) … аа) бб) as Word does, instead of digits |
| 2026-10-14 | trivial | A table's percentage width past 100% is kept, so a table pulled into the margins spans them as in Word |
| 2026-10-14 | trivial | A section can restart its page numbers at 0 (`w:pgNumType w:start="0"`), as a cover page numbered 0 does; the restart was read but laid out from 1 |
| 2026-10-13 | trivial | A shape or picture dragged into another column anchors there, so the text in that column wraps around it (#884) |
| 2026-10-13 | trivial | On a page with footnotes, a paragraph that ends there keeps its space after above the footnote separator; if it doesn't fit, its last line (and, with widow control, one more) goes to the next page with the note. Pages without notes are unchanged (#1116) |
| 2026-10-12 | trivial | STYLEREF fields in headers and footers show each page's chapter (or other styled text); Insert › Quick Parts › Field offers STYLEREF (#775) |