Files
ElyOffice/crates/ely-platform/src/print.rs
T
ZacharyZhang-NYandClaude Opus 5.5 13546ea899 R7.6: Windows and Linux print (#3)
Windows prints through the system dialog at the document's paper;
Linux through the desktop portal, the dialog's choices laid out by Ely.
Round 3 findings fixed without a further review, as the rules say.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 16:59:46 -04:00

68 lines
2.5 KiB
Rust

//! Prints a PDF through the system print dialog.
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod mac;
#[cfg(windows)]
mod win;
#[cfg(target_os = "linux")]
pub use linux::print_pdf;
#[cfg(target_os = "macos")]
pub use mac::print_pdf;
#[cfg(windows)]
pub use win::print_pdf;
/// Paper sizes round to whole points at worst.
#[cfg(not(target_os = "macos"))]
const ROUNDING: f64 = 0.5;
/// Whether a named paper, short side first, holds a page.
#[cfg(not(target_os = "macos"))]
fn holds((across, down): (f64, f64), (short, long): (f64, f64)) -> bool {
// A named paper may run 2 mm over, never under.
let suits = |paper: f64, page: f64| paper >= page - ROUNDING && paper - page <= 2.0 * 72.0 / 25.4;
suits(across, short) && suits(down, long)
}
/// One paper holding every page, turned as needed.
fn paper_for(sizes: &[(f64, f64)]) -> Option<(f64, f64)> {
let long = sizes.iter().map(|(w, h)| w.max(*h)).fold(None, |most: Option<f64>, side| Some(most.map_or(side, |m| m.max(side))))?;
let short = sizes.iter().map(|(w, h)| w.min(*h)).fold(0.0, f64::max);
// The largest page sets the paper's way up.
let (w, h) = sizes.iter().copied().fold((0.0, 0.0), |big, size| if size.0 * size.1 > big.0 * big.1 { size } else { big });
// A square page leaves it to the others.
let wide = match w == h {
true => sizes.iter().any(|(w, h)| w > h),
false => w > h,
};
Some(if wide { (long, short) } else { (short, long) })
}
/// Runs `work` on its own thread, off the app's.
#[cfg(not(target_os = "macos"))]
async fn off<T: Send + 'static>(work: impl FnOnce() -> anyhow::Result<T> + Send + 'static) -> anyhow::Result<T> {
let (sender, answer) = futures::channel::oneshot::channel();
std::thread::Builder::new().name("ely-print".into()).spawn(move || {
if sender.send(work()).is_err() {
log::warn!("print: the job ended after its caller left");
}
})?;
answer.await.map_err(|_| anyhow::anyhow!("the print thread ended without an answer"))?
}
#[cfg(test)]
mod tests {
use super::paper_for;
#[test]
fn the_paper_holds_every_page() {
assert_eq!(paper_for(&[(595.0, 420.0)]), Some((595.0, 420.0)));
assert_eq!(paper_for(&[(420.0, 595.0), (842.0, 1191.0)]), Some((842.0, 1191.0)));
// Wide and square pages: neither side may be cut.
assert_eq!(paper_for(&[(1000.0, 200.0), (600.0, 600.0)]), Some((1000.0, 600.0)));
assert_eq!(paper_for(&[]), None);
}
}