M4: drop the aggregate page-label cap from document inspection

Document inspection refused every custom label once their total passed 4 MiB, so a valid document with long labels showed physical page numbers and a warning. Labels are now applied to every page. The per-label length limit, the number-tree work, depth and cycle limits and the prefix bounds are unchanged, and labels are still applied only after every range is accepted.

MAX_LABEL_TOTAL_BYTES is removed. The inspector keeps one label per page (PageInfo.label), so its label memory grows with the page count times the label length. The page count is bounded by the objects in the file, and each label by 1,024 bytes.

Co-Authored-By: Claude Haiku 5.5 <[email protected]>
This commit is contained in:
cyfung1031andClaude Haiku 5.5 committed 2026-10-11 01:13:13 +09:00
1 parent 525312475a
commit a096ece161
2 files changed
+6 -17

No files matched your search

-2
View File
@@ -5,8 +5,6 @@
pub const MAX_LABEL_BYTES: usize = 1024;
/// Raw prefixes are bounded before decoding; this admits a full UTF-16BE ASCII label.
pub const MAX_PREFIX_BYTES: usize = 2 * MAX_LABEL_BYTES + 2;
/// Sum of the UTF-8 bytes of custom labels for one document.
pub const MAX_LABEL_TOTAL_BYTES: usize = 4 << 20;
/// The most number-tree entries (nodes, kids and range pairs) read from one `/PageLabels`
/// tree. Scaled for real documents: one range per page on a ten-thousand-page PDF happens
/// (prefix labels; #307's file has 9,156 pages), so the cap is twenty times that while still
+6 -15
View File
@@ -14,7 +14,7 @@ use hayro::hayro_syntax::DecryptionError;
use hayro::hayro_syntax::LoadPdfError;
use hayro::hayro_syntax::Pdf;
use lopdf::{Dictionary, Document, LoadOptions, Object, ObjectId};
use pdfcraft_cos::page_labels::{MAX_LABEL_BYTES, MAX_LABEL_TOTAL_BYTES, MAX_LABEL_TREE_DEPTH, MAX_LABEL_TREE_WORK, MAX_PREFIX_BYTES, alpha, roman};
use pdfcraft_cos::page_labels::{MAX_LABEL_BYTES, MAX_LABEL_TREE_DEPTH, MAX_LABEL_TREE_WORK, MAX_PREFIX_BYTES, alpha, roman};
use crate::OpenError;
use crate::structure::{LazyStructure, Structure};
@@ -761,7 +761,6 @@ impl<'a> Inspector<'a> {
specs.insert(start, (first, prefix, style));
}
let mut staged = Vec::new();
let mut total = 0usize;
for (i, _) in pages.iter().enumerate() {
let Some((&start, (first, prefix, style))) = specs.range(..=i).next_back() else { continue };
let n = if matches!(*style, Some(b"D" | b"R" | b"r" | b"A" | b"a")) {
@@ -797,10 +796,6 @@ impl<'a> Inspector<'a> {
if label.is_empty() {
continue; // Keep the existing physical number for an empty custom label.
}
total = total.checked_add(label.len()).ok_or("the total label size overflows")?;
if total > MAX_LABEL_TOTAL_BYTES {
return Err("custom labels exceed 4 MiB in total");
}
staged.push((i, label));
}
// A malformed later range must not leave a partially applied custom label sequence.
@@ -1453,22 +1448,18 @@ mod tests {
}
#[test]
fn page_label_limits_bound_total_custom_output_atomically() {
fn page_label_limits_apply_labels_past_four_mib_in_total() {
// 4,097 labels of 1,024 bytes: each within the limit, together past 4 MiB.
let doc = Document::with_version("1.7");
let mut tree = Dictionary::new();
tree.set("Nums", vec![Object::Integer(0), label_spec("", vec![b'x'; MAX_LABEL_BYTES], 1)]);
let mut catalog = Dictionary::new();
catalog.set("PageLabels", Object::Dictionary(tree));
let count = MAX_LABEL_TOTAL_BYTES / MAX_LABEL_BYTES + 1;
let mut pages: Vec<_> = (1..=count)
let mut pages: Vec<_> = (1..=4_097)
.map(|n| PageInfo { width: 200.0, height: 300.0, crop: [0.0, 0.0, 200.0, 300.0], rotation: 0, label: n.to_string() })
.collect();
let err = Inspector::new(&doc).page_labels(&catalog, &mut pages).unwrap_err();
assert!(err.contains("4 MiB"), "{err}");
assert!(pages.iter().enumerate().all(|(i, p)| p.label == (i + 1).to_string()));
// Exact aggregate boundary remains supported.
pages.pop();
Inspector::new(&doc).page_labels(&catalog, &mut pages).unwrap();
let notes = Inspector::new(&doc).page_labels(&catalog, &mut pages).unwrap();
assert!(notes.is_empty(), "{notes:?}");
assert!(pages.iter().all(|p| p.label.len() == MAX_LABEL_BYTES));
}