mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-25 18:47:22 -06:00
* 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>
81 lines
3.4 KiB
Rust
81 lines
3.4 KiB
Rust
//! 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)
|
||
}
|