mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 19:27:04 -04:00
Fix/noissue hyphenated print label ranges s20261010 t220019 z f82d4 (#942)
* fix(print): resolve ranges between hyphenated page labels * test(print): resolve ambiguous hyphenated label boundaries
This commit is contained in:
1 parent
dd400d6b23
commit
96538312df
2 files changed
+49
No files matched your search
@@ -24,6 +24,26 @@ fn page_of(tok: &str, count: usize, labels: &[String]) -> Result<usize, PrintErr
|
||||
}
|
||||
}
|
||||
|
||||
/// Split a range whose endpoint is a logical page label containing a hyphen, such
|
||||
/// as "A-1-A-3" or "A-2-". The plain range parser must not mistake the first
|
||||
/// hyphen inside the label for the delimiter between endpoints.
|
||||
fn hyphenated_label_range(part: &str, count: usize, labels: &[String]) -> Option<(usize, usize)> {
|
||||
for (split, _) in part.match_indices('-') {
|
||||
let (Some(first), Some(last)) = (part.get(..split), part.get(split + 1..)) else { continue };
|
||||
let (first, last) = (first.trim(), last.trim());
|
||||
// Avoid changing the meaning of ordinary numeric and open-ended ranges.
|
||||
if !labels.iter().any(|l| l.contains('-') && (l == first || l == last)) {
|
||||
continue;
|
||||
}
|
||||
let from = if first.is_empty() { Some(0) } else { page_of(first, count, labels).ok() };
|
||||
let to = if last.is_empty() { count.checked_sub(1) } else { page_of(last, count, labels).ok() };
|
||||
if let (Some(from), Some(to)) = (from, to) {
|
||||
return Some((from, to));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// The pages to print (0-based, in print order). `range` is `None` for all pages; it may list
|
||||
/// numbers and labels with `-` ranges (open-ended allowed: `5-`, `-3`). The subset counts the
|
||||
/// selected pages (the first selected is "odd"), as Acrobat does.
|
||||
@@ -33,6 +53,18 @@ pub fn select_pages(count: usize, range: Option<&str>, labels: &[String], subset
|
||||
None => pages.extend(0..count),
|
||||
Some(r) => {
|
||||
for part in r.split([',', ';']).map(str::trim).filter(|p| !p.is_empty()) {
|
||||
// A complete label (e.g. "A-1") still wins over interpreting it as a range.
|
||||
// A range of such labels (e.g. "A-1-A-3") needs a later '-' split.
|
||||
if !labels.iter().any(|l| l == part)
|
||||
&& let Some((from, to)) = hyphenated_label_range(part, count, labels)
|
||||
{
|
||||
if from <= to {
|
||||
pages.extend(from..=to);
|
||||
} else {
|
||||
pages.extend((to..=from).rev());
|
||||
}
|
||||
continue;
|
||||
}
|
||||
match part.split_once('-') {
|
||||
// A label may itself contain a dash ("A-1"): try the whole token first.
|
||||
Some(_) if labels.iter().any(|l| l == part) => pages.push(page_of(part, count, labels)?),
|
||||
|
||||
@@ -77,6 +77,23 @@ fn page_selection() {
|
||||
assert_eq!(select_listed(5, &[], Subset::All, false), Err(PrintError::NoPages));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_selection_handles_ranges_between_hyphenated_labels() {
|
||||
let labels: Vec<String> = ["A-1", "A-2", "A-3", "B-1", "B-2"].iter().map(|s| s.to_string()).collect();
|
||||
let pages = |range| select_pages(5, Some(range), &labels, Subset::All, false).unwrap();
|
||||
assert_eq!(pages("A-1"), [0], "the complete label takes precedence over a range");
|
||||
assert_eq!(pages("A-1-A-3"), [0, 1, 2]);
|
||||
assert_eq!(pages("A-3-A-1"), [2, 1, 0], "reverse logical ranges");
|
||||
assert_eq!(pages("A-2-"), [1, 2, 3, 4], "an open-ended range starting at a hyphenated label");
|
||||
assert_eq!(pages("-A-2"), [0, 1], "an open-ended range ending at a hyphenated label");
|
||||
assert_eq!(pages("A-2-5"), [1, 2, 3, 4], "a label followed by a numeric endpoint");
|
||||
assert_eq!(pages("B-1-B-2"), [3, 4]);
|
||||
// If an earlier dash yields one known label but an unknown endpoint, continue
|
||||
// checking later dashes instead of rejecting the whole range.
|
||||
let ambiguous: Vec<String> = ["A-1", "A-1-B", "C"].iter().map(|s| s.to_string()).collect();
|
||||
assert_eq!(select_pages(3, Some("A-1-B-C"), &ambiguous, Subset::All, false).unwrap(), [1, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn size_modes() {
|
||||
let sizes = [(200.0, 300.0), (1000.0, 1500.0)];
|
||||
|
||||
Reference in new issue
Block a user