diff --git a/crates/edit/src/lib.rs b/crates/edit/src/lib.rs index 532b32487..7c7816d5a 100644 --- a/crates/edit/src/lib.rs +++ b/crates/edit/src/lib.rs @@ -414,6 +414,20 @@ pub fn add_header_footer(doc: &mut Document, pages: &[usize], hf: &HeaderFooter, if !(hf.font_size.is_finite() && hf.font_size > 0.0 && hf.font_size <= 200.0 && hf.margins.iter().all(|m| m.is_finite() && *m >= 0.0)) { return Err(EditError::Invalid("invalid font size or margins".into())); } + // Page numbers count up from start_number, so the last selected page needs the most room. + // Refuse the whole run before drawing anything: headers applied to some pages and then a + // failed page number would leave the document half changed. + let last_number = u64::from(hf.start_number) + .checked_add(pages.len().saturating_sub(1) as u64) + .ok_or_else(|| EditError::Invalid("the header page numbers are too large".into()))?; + if last_number > u64::from(u32::MAX) { + return Err(EditError::Invalid(format!( + "the header start number {} leaves no room for {} pages (page numbers stop at {})", + hf.start_number, + pages.len(), + u32::MAX + ))); + } if replace { remove_marks(doc, pages, MarkKind::HeaderFooter)?; } @@ -423,7 +437,8 @@ pub fn add_header_footer(doc: &mut Document, pages: &[usize], hf: &HeaderFooter, for (k, &i) in pages.iter().enumerate() { let page = &all[i]; let (w, h) = page.display_size(doc); - let number = hf.start_number + k as u32; + let number = + u32::try_from(u64::from(hf.start_number) + k as u64).map_err(|_| EditError::Invalid("the header page numbers are too large".into()))?; let bates = bates0 + k as u64; let size = hf.font_size; let [top, bottom, left, right] = hf.margins; diff --git a/crates/edit/src/tests.rs b/crates/edit/src/tests.rs index 03baeac09..10750dcc3 100644 --- a/crates/edit/src/tests.rs +++ b/crates/edit/src/tests.rs @@ -96,6 +96,25 @@ fn header_and_footer_are_drawn_in_display_space_and_wrap_the_original_content() assert_eq!(marks_present(&doc), [MarkKind::HeaderFooter]); } +#[test] +fn header_start_number_that_would_wrap_is_refused_without_drawing() { + // u32::MAX is a valid start_number on its own, but there is no room for a second page + // number after it. The old code wrapped the second page to zero; now the whole run is + // refused and the document keeps no header at all. + let mut doc = fixture(); + let hf = HeaderFooter { + text: [String::new(), String::new(), "<<1>>".into(), String::new(), String::new(), String::new()], + start_number: u32::MAX, + ..HeaderFooter::default() + }; + let err = add_header_footer(&mut doc, &[0, 1], &hf, false, &cx()).unwrap_err(); + assert!(err.to_string().contains("leaves no room"), "{err}"); + assert!(marks_present(&doc).is_empty(), "nothing is drawn when the run is refused"); + // A single page still fits the start number itself. + add_header_footer(&mut doc, &[0], &hf, false, &cx()).unwrap(); + assert_eq!(marks_present(&doc).len(), 1); +} + #[test] fn replace_and_remove_restore_the_original_content() { let mut doc = fixture();