Files
pdfcraft/crates/print/src/range.rs
a9209c3235 M10: select pages in the Pages panel and print the selection (#296)
* M10: select pages in the Pages panel and print the selection

The Pages panel could only jump to a page. It now picks several:
Ctrl/Cmd-click toggles a page (the first one keeps the current page
selected too), Shift-click takes a range from the last clicked page, a
plain click goes to the page and starts over, Escape clears. Picking does
not turn the page. It is the selection the Organize grid and the page
commands already use; the click rules moved to DocView::click_page and
both places call it.

Print opens on a new "Selected pages" choice when pages are picked and
prints them in page order. They are passed as positions
(print::select_listed), not as a typed range, which would read numbers as
page labels first. ui.state reports active.selected_pages.

Windows still has no printer output, so there the result is a PDF of the
selected pages. No context menu or drag in the panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Restore ROADMAP.md, emptied by accident in this PR

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-08 12:00:35 -04:00

81 lines
3.4 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Pages to Print: all, a range ("1-3, 6, 9-", page labels allowed), odd or even pages, reverse.
use crate::PrintError;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Subset {
#[default]
All,
Odd,
Even,
}
/// Resolve one range token: a page label, or a number (1-based).
fn page_of(tok: &str, count: usize, labels: &[String]) -> Result<usize, PrintError> {
let t = tok.trim();
// Page labels win over numbers (Acrobat's "use logical page numbers").
if let Some(i) = labels.iter().position(|l| l == t) {
return Ok(i);
}
match t.parse::<usize>() {
Ok(n) if n >= 1 && n <= count => Ok(n - 1),
Ok(n) => Err(PrintError::Invalid(format!("page {n} is out of range (1–{count})"))),
Err(_) => Err(PrintError::Invalid(format!("{t:?} is not a page number or label"))),
}
}
/// 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.
pub fn select_pages(count: usize, range: Option<&str>, labels: &[String], subset: Subset, reverse: bool) -> Result<Vec<usize>, PrintError> {
let mut pages = Vec::new();
match range.map(str::trim).filter(|r| !r.is_empty()) {
None => pages.extend(0..count),
Some(r) => {
for part in r.split([',', ';']).map(str::trim).filter(|p| !p.is_empty()) {
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)?),
Some((a, b)) => {
let from = if a.trim().is_empty() { 0 } else { page_of(a, count, labels)? };
let to = if b.trim().is_empty() { count.saturating_sub(1) } else { page_of(b, count, labels)? };
if from <= to {
pages.extend(from..=to);
} else {
pages.extend((to..=from).rev());
}
}
None => pages.push(page_of(part, count, labels)?),
}
}
}
}
narrow(pages, subset, reverse)
}
/// The pages to print from an explicit list (0-based, e.g. the thumbnails selected in the
/// interface), in the order given. Unlike a typed range the entries are page positions, never
/// labels. The subset and `reverse` apply as in [`select_pages`].
pub fn select_listed(count: usize, pages: &[usize], subset: Subset, reverse: bool) -> Result<Vec<usize>, PrintError> {
if let Some(p) = pages.iter().find(|p| **p >= count) {
return Err(PrintError::Invalid(format!("page {} is out of range (1–{count})", p.saturating_add(1))));
}
narrow(pages.to_vec(), subset, reverse)
}
/// Odd or even positions of the chosen pages, then the print order.
fn narrow(pages: Vec<usize>, subset: Subset, reverse: bool) -> Result<Vec<usize>, PrintError> {
let mut out: Vec<usize> = match subset {
Subset::All => pages,
Subset::Odd => pages.into_iter().step_by(2).collect(),
Subset::Even => pages.into_iter().skip(1).step_by(2).collect(),
};
if reverse {
out.reverse();
}
if out.is_empty() {
return Err(PrintError::NoPages);
}
Ok(out)
}