diff --git a/crates/cos/src/writer.rs b/crates/cos/src/writer.rs index 627f716495..cd0137f452 100644 --- a/crates/cos/src/writer.rs +++ b/crates/cos/src/writer.rs @@ -245,7 +245,7 @@ pub fn write_incremental(doc: &Document, opts: &SaveOptions) -> Result, if let Some(p) = prev { trailer.set(b"Prev".to_vec(), Object::Int(p as i64)); } - let size = doc.next_num().max(trailer.int(b"Size").unwrap_or(0) as u32); + let size = doc.next_num().max(trailer.int(b"Size").map_or(0, |n| n.clamp(0, i64::from(u32::MAX)) as u32)); if as_stream { let rows = offsets.iter().map(|(n, (o, g, used))| (*n, if *used { Row::InFile(*o, *g) } else { Row::Free(*g) })).collect(); write_xref_stream(&mut out, base, &mut trailer, rows, size); diff --git a/crates/cos/tests/it/writer.rs b/crates/cos/tests/it/writer.rs index 1b98664255..eb03523189 100644 --- a/crates/cos/tests/it/writer.rs +++ b/crates/cos/tests/it/writer.rs @@ -228,6 +228,16 @@ fn incremental_save_keeps_a_trailer_only_edit() { assert_qpdf_clean(&saved); } +#[test] +fn incremental_save_ignores_a_negative_trailer_size() { + let mut doc = Document::open(Arc::new(classic_table())).unwrap(); + doc.trailer_mut().set(b"Size".to_vec(), Object::Int(-1)); + let next = doc.add(Object::Int(5)).num + 1; + let saved = write_incremental(&doc, &SaveOptions::default()).unwrap(); + let back = Document::open(Arc::new(saved)).unwrap(); + assert_eq!(back.trailer().int(b"Size"), Some(i64::from(next))); +} + /// What one edit added and a later one unlinked (a deleted comment, a cleared redaction mark) is /// not written: the file never held it, and writing it would keep what was removed. Additions the /// update still refers to, from the trailer or a changed object and through other additions, are.