mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-25 18:47:22 -06:00
This commit is contained in:
@@ -396,6 +396,31 @@ pub fn set_page_label_ranges(doc: &mut Document, ranges: &[LabelRange]) -> Resul
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// After `deleted` pages (0-based, as they were numbered before the delete) are gone, move the label
|
||||
/// ranges with the pages they label: a range starts as many pages earlier as were deleted before
|
||||
/// it, and keeps its first number, so numbering continues over the gap as in Acrobat. A range whose
|
||||
/// first page went starts on the next page left; one left with no pages gives way to the next.
|
||||
/// A tree that can't be read is left as it is: the delete still goes ahead.
|
||||
pub(crate) fn drop_deleted_pages(doc: &mut Document, deleted: &[usize]) -> Result<(), OrganizeError> {
|
||||
let Ok(old) = checked_ranges(doc) else { return Ok(()) };
|
||||
let mut deleted = deleted.to_vec();
|
||||
deleted.sort_unstable();
|
||||
deleted.dedup();
|
||||
let mut ranges: Vec<LabelRange> = Vec::with_capacity(old.len());
|
||||
for r in &old {
|
||||
let start = r.start.saturating_sub(deleted.partition_point(|d| *d < r.start));
|
||||
match ranges.last_mut() {
|
||||
// The later range is the one on the first page left; ranges stay sorted by start.
|
||||
Some(last) if last.start == start => *last = LabelRange { start, ..r.clone() },
|
||||
_ => ranges.push(LabelRange { start, ..r.clone() }),
|
||||
}
|
||||
}
|
||||
if ranges == old {
|
||||
return Ok(());
|
||||
}
|
||||
set_page_label_ranges(doc, &ranges)
|
||||
}
|
||||
|
||||
/// Acrobat's "Number pages": label pages `from..=to` (0-based) with `style`, `prefix` and
|
||||
/// starting number `first`; pages after `to` keep the labels they had.
|
||||
pub fn number_pages(doc: &mut Document, from: usize, to: usize, style: LabelStyle, prefix: &str, first: u32) -> Result<(), OrganizeError> {
|
||||
|
||||
@@ -181,6 +181,7 @@ pub fn delete_pages(doc: &mut Document, indices: &[usize]) -> Result<(), Organiz
|
||||
return Err(OrganizeError::WouldRemoveAllPages);
|
||||
}
|
||||
rebuild(doc, &keep)?;
|
||||
labels::drop_deleted_pages(doc, indices)?;
|
||||
// The deleted pages go for good: bookmarks, links and widgets that still point at one would
|
||||
// otherwise keep it, with its content, in the saved file. Their form widgets leave the form,
|
||||
// and bookmarks and links that went to them lose that destination.
|
||||
|
||||
@@ -1472,6 +1472,38 @@ fn number_pages_like_acrobat() {
|
||||
assert!(d.get(d.root().unwrap()).as_dict().unwrap().get(b"PageLabels").is_none());
|
||||
}
|
||||
|
||||
/// Deleting pages moves the label ranges with the pages they label (#2091): later ranges start that
|
||||
/// many pages earlier and keep numbering on, a range whose first page went starts on the next page
|
||||
/// left, and a range left with no pages gives way to the one after it.
|
||||
#[test]
|
||||
fn deleting_pages_keeps_the_label_ranges_on_their_pages() {
|
||||
use crate::{LabelRange, LabelStyle::*};
|
||||
let ten = || {
|
||||
let mut d = doc_a();
|
||||
for at in 3..10 {
|
||||
crate::insert_blank_page(&mut d, at, 300.0, 400.0).unwrap();
|
||||
}
|
||||
let ranges = [
|
||||
LabelRange { start: 0, style: LowerRoman, prefix: String::new(), first: 1 },
|
||||
LabelRange { start: 3, style: Decimal, prefix: String::new(), first: 1 },
|
||||
LabelRange { start: 7, style: Decimal, prefix: "A-".into(), first: 1 },
|
||||
];
|
||||
crate::set_page_label_ranges(&mut d, &ranges).unwrap();
|
||||
assert_eq!(crate::page_labels(&d).unwrap(), ["i", "ii", "iii", "1", "2", "3", "4", "A-1", "A-2", "A-3"]);
|
||||
d
|
||||
};
|
||||
for (deleted, expected) in [
|
||||
(&[4, 5][..], &["i", "ii", "iii", "1", "2", "A-1", "A-2", "A-3"][..]),
|
||||
(&[3], &["i", "ii", "iii", "1", "2", "3", "A-1", "A-2", "A-3"]),
|
||||
(&[3, 4, 5, 6], &["i", "ii", "iii", "A-1", "A-2", "A-3"]),
|
||||
] {
|
||||
let mut d = ten();
|
||||
crate::delete_pages(&mut d, deleted).unwrap();
|
||||
assert_eq!(crate::page_labels(&d).unwrap(), expected, "after deleting {deleted:?}");
|
||||
assert_eq!(crate::page_labels(&full_roundtrip(&d)).unwrap(), expected, "reopened after deleting {deleted:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_label_limits_reject_unsupported_edits_before_mutating() {
|
||||
use crate::LabelStyle::*;
|
||||
|
||||
Reference in New Issue
Block a user