forked from 1b-agent/opencadstudio-upstream
feat(plot): custom paper sizes with printable margins
AutoCAD lets a plotter configuration carry user-defined sheets; a drawing then names them like any other media (`Roll_24_(609.60_x_1500.00_MM)`). The plot dialog now defines such sheets inline, under the Size row: name, width/height, unit, and the printable margins in that unit. Added sheets are selected at once, offered for every device after the device's own list, remembered in the plot config across sessions, and written to the layout with their own margins (a printer that reports the same size still wins with its hardware margins). A selected user-defined sheet can be removed; the sheet itself stays selectable in the drawing that uses it. `CustomPaper` / `Margins` live in the paper catalogue with serde support; `PlotDevice::margins_mm` consults the user's definitions. The Plot dialog's form helpers (button and field styles, dividers, `label : control` rows) move to `ui::style::form` so the next dialogs share them instead of copying. Tests: draft validation and seeding, catalogue naming of user sheets, device margins with user definitions, and dialog round-trips (add, select, persist, write, replace, remove, refuse an invalid size). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a7c89eca33
commit
5e271e2dbb
28 files changed
+930
-175
No files matched your search
+57
-52
@@ -15,7 +15,8 @@
|
||||
//! a fixed table; a CUPS printer reports its media and margins over IPP
|
||||
//! (`media-col-database`), which is queried once per printer and cached.
|
||||
|
||||
use crate::io::paper_catalog::{self, PaperSize, PaperUnits, PaperVariant};
|
||||
pub use crate::io::paper_catalog::Margins;
|
||||
use crate::io::paper_catalog::{self, CustomPaper, PaperSize, PaperUnits, PaperVariant};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
|
||||
@@ -73,51 +74,23 @@ impl PlotDevice {
|
||||
}
|
||||
|
||||
/// Unprintable margins the device imposes on `paper`, in millimetres.
|
||||
pub fn margins_mm(&self, paper: &PaperSize) -> Margins {
|
||||
/// A sheet the user defined carries its own margins for every device,
|
||||
/// the way a custom size in a plotter configuration does; a printer that
|
||||
/// reports the same size still wins, since it knows its hardware.
|
||||
pub fn margins_mm(&self, paper: &PaperSize, custom: &[CustomPaper]) -> Margins {
|
||||
let user_defined = custom
|
||||
.iter()
|
||||
.find(|c| c.canonical() == paper.canonical)
|
||||
.map(CustomPaper::margins_mm);
|
||||
match self {
|
||||
PlotDevice::Pdf => pdf_margins_mm(paper),
|
||||
PlotDevice::Printer(name) => printer_margins_mm(name, paper),
|
||||
PlotDevice::Pdf => user_defined.unwrap_or_else(|| pdf_margins_mm(paper)),
|
||||
PlotDevice::Printer(name) => cached_printer_capabilities(name)
|
||||
.and_then(|caps| caps.margins_for(paper))
|
||||
.or(user_defined)
|
||||
.unwrap_or(Margins::ZERO),
|
||||
// No device: AutoCAD keeps the margins of the last device or its
|
||||
// defaults; without one we show the full sheet as printable.
|
||||
PlotDevice::None => Margins::ZERO,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Unprintable margins in millimetres, in the order AutoCAD stores them.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Margins {
|
||||
pub left: f64,
|
||||
pub bottom: f64,
|
||||
pub right: f64,
|
||||
pub top: f64,
|
||||
}
|
||||
|
||||
impl Margins {
|
||||
pub const ZERO: Margins = Margins {
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
right: 0.0,
|
||||
top: 0.0,
|
||||
};
|
||||
|
||||
pub const fn uniform(value: f64) -> Self {
|
||||
Margins {
|
||||
left: value,
|
||||
bottom: value,
|
||||
right: value,
|
||||
top: value,
|
||||
}
|
||||
}
|
||||
|
||||
/// The same margins expressed in millimetres when they were given in
|
||||
/// `units`.
|
||||
pub fn to_mm(self, units: PaperUnits) -> Self {
|
||||
Margins {
|
||||
left: units.to_mm(self.left),
|
||||
bottom: units.to_mm(self.bottom),
|
||||
right: units.to_mm(self.right),
|
||||
top: units.to_mm(self.top),
|
||||
PlotDevice::None => user_defined.unwrap_or(Margins::ZERO),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,12 +207,6 @@ pub fn printer_capabilities(printer: &str) -> Option<Arc<PrinterCapabilities>> {
|
||||
Some(caps)
|
||||
}
|
||||
|
||||
fn printer_margins_mm(printer: &str, paper: &PaperSize) -> Margins {
|
||||
cached_printer_capabilities(printer)
|
||||
.and_then(|caps| caps.margins_for(paper))
|
||||
.unwrap_or(Margins::ZERO)
|
||||
}
|
||||
|
||||
/// Name of the system default printer, when the platform reports one.
|
||||
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "windows")))]
|
||||
pub fn default_printer_name() -> Option<String> {
|
||||
@@ -459,7 +426,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn pdf_margins_follow_autocad_per_family_and_variant() {
|
||||
let m = |name: &str| PlotDevice::Pdf.margins_mm(&paper_catalog::resolve(name).unwrap());
|
||||
let m =
|
||||
|name: &str| PlotDevice::Pdf.margins_mm(&paper_catalog::resolve(name).unwrap(), &[]);
|
||||
// ISO sheets share one millimetre set regardless of size.
|
||||
let iso_standard = Margins {
|
||||
left: 5.0,
|
||||
@@ -517,12 +485,49 @@ mod tests {
|
||||
inch(0.03, 0.03, 0.03, 0.03)
|
||||
));
|
||||
assert_eq!(
|
||||
PlotDevice::None
|
||||
.margins_mm(&paper_catalog::resolve("ISO_A4_(210.00_x_297.00_MM)").unwrap()),
|
||||
PlotDevice::None.margins_mm(
|
||||
&paper_catalog::resolve("ISO_A4_(210.00_x_297.00_MM)").unwrap(),
|
||||
&[]
|
||||
),
|
||||
Margins::ZERO
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_defined_sheets_bring_their_own_margins() {
|
||||
let roll = CustomPaper {
|
||||
name: "Roll".into(),
|
||||
width: 900.0,
|
||||
height: 1200.0,
|
||||
units: PaperUnits::Millimeters,
|
||||
margins: Margins::uniform(3.0),
|
||||
};
|
||||
let paper = roll.paper();
|
||||
assert_eq!(
|
||||
PlotDevice::Pdf.margins_mm(&paper, &[roll.clone()]),
|
||||
Margins::uniform(3.0)
|
||||
);
|
||||
assert_eq!(
|
||||
PlotDevice::None.margins_mm(&paper, &[roll.clone()]),
|
||||
Margins::uniform(3.0)
|
||||
);
|
||||
// An unknown printer falls back to the user's margins too.
|
||||
assert_eq!(
|
||||
PlotDevice::Printer("Nowhere".into()).margins_mm(&paper, &[roll]),
|
||||
Margins::uniform(3.0)
|
||||
);
|
||||
// Without a definition a custom-looking sheet gets the family set.
|
||||
assert_eq!(
|
||||
PlotDevice::Pdf.margins_mm(&paper, &[]),
|
||||
Margins {
|
||||
left: 5.0,
|
||||
bottom: 17.0,
|
||||
right: 6.0,
|
||||
top: 18.0
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ipp_media_parses_sizes_margins_borderless_and_default() {
|
||||
let caps = parse_ipp_media(IPP_SAMPLE).expect("media parsed");
|
||||
|
||||
Reference in new issue
Block a user