parent
90a5495601
commit
6c62daf260
6 changed files with 140 additions and 88 deletions
|
|
@ -2632,6 +2632,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
/// layout's plot settings and the printers found on the system.
|
/// layout's plot settings and the printers found on the system.
|
||||||
pub(super) fn on_plot_dialog_open(&mut self) -> Task<Message> {
|
pub(super) fn on_plot_dialog_open(&mut self) -> Task<Message> {
|
||||||
use crate::io::paper_sizes::Orientation;
|
use crate::io::paper_sizes::Orientation;
|
||||||
|
let previous = self.plot_dialog.clone();
|
||||||
let scales: Vec<(String, f64)> = self.tabs[self.active_tab]
|
let scales: Vec<(String, f64)> = self.tabs[self.active_tab]
|
||||||
.scene
|
.scene
|
||||||
.scale_list()
|
.scale_list()
|
||||||
|
|
@ -2654,7 +2655,7 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
d.scale_lw = false;
|
d.scale_lw = false;
|
||||||
d.scale = one_to_one.clone();
|
d.scale = one_to_one.clone();
|
||||||
} else if !d.scales.iter().any(|(name, _)| name == &d.scale) {
|
} else if !d.scales.iter().any(|(name, _)| name == &d.scale) {
|
||||||
d.scale = one_to_one;
|
d.scale = one_to_one.clone();
|
||||||
}
|
}
|
||||||
d.paper_space = self.tabs[self.active_tab].scene.current_layout != "Model";
|
d.paper_space = self.tabs[self.active_tab].scene.current_layout != "Model";
|
||||||
d.paper = self.plot_format.label().to_string();
|
d.paper = self.plot_format.label().to_string();
|
||||||
|
|
@ -2704,28 +2705,39 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||||
if self.plot_dialog.fit_to_paper {
|
if self.plot_dialog.fit_to_paper {
|
||||||
self.plot_dialog.scale_lw = false;
|
self.plot_dialog.scale_lw = false;
|
||||||
}
|
}
|
||||||
// Refresh the list (<none> / <previous> / layouts / named setups) and
|
// Refresh document/runtime lists, then restore the live choices from
|
||||||
// snapshot the just-loaded settings as the `<previous>` restore point.
|
// the preceding dialog session. Previously the snapshot happened
|
||||||
|
// after the layout values above were reloaded, so opening Plot itself
|
||||||
|
// destroyed the user's last paper, area, scale and output choices.
|
||||||
self.refresh_page_setups();
|
self.refresh_page_setups();
|
||||||
self.plot_prev = Some(self.plot_dialog.clone());
|
self.plot_prev = Some(previous);
|
||||||
// Model space keeps the last-used setup; a paper layout starts from its
|
self.select_page_setup(crate::ui::window::plot::SETUP_PREV);
|
||||||
// embedded setup.
|
// `<previous>` may come from another tab/drawing whose custom scale is
|
||||||
|
// not present in this document. Validate again after restoring it; the
|
||||||
|
// pre-restore validation above only saw the temporarily reseeded state.
|
||||||
|
if self.plot_dialog.scale.eq_ignore_ascii_case("fit") {
|
||||||
|
self.plot_dialog.fit_to_paper = true;
|
||||||
|
self.plot_dialog.scale = one_to_one.clone();
|
||||||
|
} else if !self
|
||||||
|
.plot_dialog
|
||||||
|
.scales
|
||||||
|
.iter()
|
||||||
|
.any(|(name, _)| name == &self.plot_dialog.scale)
|
||||||
|
{
|
||||||
|
self.plot_dialog.scale = one_to_one;
|
||||||
|
}
|
||||||
|
if self.plot_dialog.fit_to_paper {
|
||||||
|
self.plot_dialog.scale_lw = false;
|
||||||
|
}
|
||||||
|
// A model-space plot cannot use the paper-only Layout area. Other
|
||||||
|
// values remain exactly as the user left them; selecting a layout or
|
||||||
|
// named setup explicitly still reloads that setup below.
|
||||||
let cur = self.tabs[self.active_tab].scene.current_layout.clone();
|
let cur = self.tabs[self.active_tab].scene.current_layout.clone();
|
||||||
if cur == "Model" {
|
if cur == "Model" {
|
||||||
self.select_page_setup(crate::ui::window::plot::SETUP_PREV);
|
|
||||||
if self.plot_dialog.area == "Layout" {
|
if self.plot_dialog.area == "Layout" {
|
||||||
self.plot_dialog.area = "Window".into();
|
self.plot_dialog.area = "Window".into();
|
||||||
self.normalize_common_plot_dialog();
|
self.normalize_common_plot_dialog();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
self.select_page_setup(&format!("*{cur}*"));
|
|
||||||
if self.plot_dialog.area != "Layout" {
|
|
||||||
// A layout may persist Window/Extents/Display together with its
|
|
||||||
// sheet-only origin/rotation. Start common area modes with the
|
|
||||||
// same neutral transform as Model even when no Area-change event
|
|
||||||
// fires after opening the dialog.
|
|
||||||
self.normalize_common_plot_dialog();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.active_modal = Some(crate::app::ModalKind::Plot);
|
self.active_modal = Some(crate::app::ModalKind::Plot);
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
|
||||||
|
|
@ -54,13 +54,15 @@ pub async fn pick_pdf_path_owned(_stem: String) -> Option<std::path::PathBuf> {
|
||||||
/// mm to PDF points (1 mm = 2.834645 pt).
|
/// mm to PDF points (1 mm = 2.834645 pt).
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
const MM_TO_PT: f32 = 2.834645;
|
const MM_TO_PT: f32 = 2.834645;
|
||||||
/// `wire.line_weight_px` is the on-screen pixel weight: mm × (96/25.4) × 2.0,
|
/// `wire.line_weight_px` is the on-screen pixel weight. Convert the 96-dpi
|
||||||
/// where the ×2 is a screen-legibility boost (see render.rs). Print wants the
|
/// pixels to points while retaining the viewport's lineweight visibility
|
||||||
/// true physical weight, so undo both the 96-dpi scaling and the boost before
|
/// boost, so "As displayed" output has the same visual hierarchy as the
|
||||||
/// converting to points — otherwise weights export ~2× too heavy in pixels
|
/// canvas instead of making 0.35 mm ByLayer outlines look half as thick.
|
||||||
/// (and the old `× 0.35278` left them inconsistent with the physical mm).
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
const LW_PX_TO_PT: f32 = MM_TO_PT / ((96.0 / 25.4) * 2.0);
|
const LW_PX_TO_PT: f32 = MM_TO_PT / (96.0 / 25.4);
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
const SCREEN_DOT_MM: f32 = 25.4 / 96.0;
|
||||||
|
|
||||||
/// Output controls shared by preview, PDF export, and printer rendering.
|
/// Output controls shared by preview, PDF export, and printer rendering.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -365,14 +367,16 @@ fn build_pdf(
|
||||||
.map(|c| (c[0] - r).abs() > 0.01 || (c[1] - g).abs() > 0.01 || (c[2] - b).abs() > 0.01)
|
.map(|c| (c[0] - r).abs() > 0.01 || (c[1] - g).abs() > 0.01 || (c[2] - b).abs() > 0.01)
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
{
|
{
|
||||||
ops.push(Op::SetOutlineColor {
|
let color = Color::Rgb(Rgb {
|
||||||
col: Color::Rgb(Rgb {
|
r,
|
||||||
r,
|
g,
|
||||||
g,
|
b,
|
||||||
b,
|
icc_profile: None,
|
||||||
icc_profile: None,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
ops.push(Op::SetOutlineColor {
|
||||||
|
col: color.clone(),
|
||||||
|
});
|
||||||
|
ops.push(Op::SetFillColor { col: color });
|
||||||
last_color = Some([r, g, b]);
|
last_color = Some([r, g, b]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -431,9 +435,11 @@ fn build_pdf(
|
||||||
// low-coordinate drawings came out clean. The result is a sheet-mm value
|
// low-coordinate drawings came out clean. The result is a sheet-mm value
|
||||||
// in single digits, so f32 is lossless from here.
|
// in single digits, so f32 is lossless from here.
|
||||||
let mut segment: Vec<LinePoint> = Vec::new();
|
let mut segment: Vec<LinePoint> = Vec::new();
|
||||||
|
let dot_radius = (wire.name == "viewport_hatch_pattern")
|
||||||
|
.then_some(Pt(SCREEN_DOT_MM * MM_TO_PT / (2.0 * scale.max(1e-6))));
|
||||||
for (pi, &[x, y, _z]) in wire.points.iter().enumerate() {
|
for (pi, &[x, y, _z]) in wire.points.iter().enumerate() {
|
||||||
if x.is_nan() || y.is_nan() {
|
if x.is_nan() || y.is_nan() {
|
||||||
flush_line(&mut ops, &segment);
|
flush_line(&mut ops, &segment, dot_radius);
|
||||||
segment.clear();
|
segment.clear();
|
||||||
} else {
|
} else {
|
||||||
let lo = wire.points_low.get(pi).copied().unwrap_or([0.0; 3]);
|
let lo = wire.points_low.get(pi).copied().unwrap_or([0.0; 3]);
|
||||||
|
|
@ -445,7 +451,7 @@ fn build_pdf(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flush_line(&mut ops, &segment);
|
flush_line(&mut ops, &segment, dot_radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text (SDF glyph quads) — re-emitted as vector strokes / fills. Text now
|
// Text (SDF glyph quads) — re-emitted as vector strokes / fills. Text now
|
||||||
|
|
@ -499,10 +505,21 @@ fn dash_array_from_pattern(pattern_length: f32, pattern: &[f32; 8], mm_to_pt: f3
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn flush_line(ops: &mut Vec<Op>, pts: &[LinePoint]) {
|
fn flush_line(ops: &mut Vec<Op>, pts: &[LinePoint], dot_radius: Option<Pt>) {
|
||||||
if pts.len() < 2 {
|
if pts.len() < 2 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if let Some(radius) = dot_radius {
|
||||||
|
let first = pts[0].p;
|
||||||
|
let coincident = pts.iter().skip(1).all(|point| {
|
||||||
|
(point.p.x.0 - first.x.0).abs() <= 1e-6
|
||||||
|
&& (point.p.y.0 - first.y.0).abs() <= 1e-6
|
||||||
|
});
|
||||||
|
if coincident {
|
||||||
|
emit_round_dot(ops, first, radius);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
ops.push(Op::DrawLine {
|
ops.push(Op::DrawLine {
|
||||||
line: Line {
|
line: Line {
|
||||||
points: pts.to_vec(),
|
points: pts.to_vec(),
|
||||||
|
|
@ -511,6 +528,30 @@ fn flush_line(ops: &mut Vec<Op>, pts: &[LinePoint]) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
fn emit_round_dot(ops: &mut Vec<Op>, center: Point, radius: Pt) {
|
||||||
|
const SIDES: usize = 12;
|
||||||
|
let points = (0..SIDES)
|
||||||
|
.map(|index| {
|
||||||
|
let angle = std::f32::consts::TAU * index as f32 / SIDES as f32;
|
||||||
|
LinePoint {
|
||||||
|
p: Point {
|
||||||
|
x: Pt(center.x.0 + radius.0 * angle.cos()),
|
||||||
|
y: Pt(center.y.0 + radius.0 * angle.sin()),
|
||||||
|
},
|
||||||
|
bezier: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
ops.push(Op::DrawPolygon {
|
||||||
|
polygon: Polygon {
|
||||||
|
rings: vec![PolygonRing { points }],
|
||||||
|
mode: PaintMode::Fill,
|
||||||
|
winding_order: WindingOrder::NonZero,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
fn plotted_color(
|
fn plotted_color(
|
||||||
rgb: [f32; 3],
|
rgb: [f32; 3],
|
||||||
|
|
@ -685,7 +726,9 @@ fn emit_hatch(
|
||||||
r = 1.0;
|
r = 1.0;
|
||||||
g = 1.0;
|
g = 1.0;
|
||||||
b = 1.0;
|
b = 1.0;
|
||||||
} else if !color_overridden {
|
} else if !color_overridden
|
||||||
|
&& !(hatch.aci == 7 && matches!(hatch.pattern, HatchPattern::Solid))
|
||||||
|
{
|
||||||
let is_light = r > 0.80 && g > 0.80 && b > 0.80;
|
let is_light = r > 0.80 && g > 0.80 && b > 0.80;
|
||||||
let is_yellow = r > 0.80 && g > 0.70 && b < 0.30;
|
let is_yellow = r > 0.80 && g > 0.70 && b < 0.30;
|
||||||
let is_cyan = r < 0.30 && g > 0.70 && b > 0.70;
|
let is_cyan = r < 0.30 && g > 0.70 && b > 0.70;
|
||||||
|
|
@ -778,21 +821,20 @@ fn emit_hatch(
|
||||||
} else {
|
} else {
|
||||||
scale.max(1e-6)
|
scale.max(1e-6)
|
||||||
};
|
};
|
||||||
let screen_dot_mm = 25.4 / 96.0;
|
let segments = hatch.pattern_segments_for_plot();
|
||||||
let dot_length = (physical / MM_TO_PT / divisor)
|
|
||||||
.max(screen_dot_mm / scale.max(1e-6));
|
|
||||||
let segments = hatch.pattern_segments_for_plot(dot_length as f64);
|
|
||||||
if segments.is_empty() {
|
if segments.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ops.push(Op::SetOutlineColor {
|
let color = Color::Rgb(Rgb {
|
||||||
col: Color::Rgb(Rgb {
|
r,
|
||||||
r,
|
g,
|
||||||
g,
|
b,
|
||||||
b,
|
icc_profile: None,
|
||||||
icc_profile: None,
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
ops.push(Op::SetOutlineColor {
|
||||||
|
col: color.clone(),
|
||||||
|
});
|
||||||
|
ops.push(Op::SetFillColor { col: color });
|
||||||
ops.push(Op::SetOutlineThickness {
|
ops.push(Op::SetOutlineThickness {
|
||||||
pt: Pt(physical / divisor),
|
pt: Pt(physical / divisor),
|
||||||
});
|
});
|
||||||
|
|
@ -806,21 +848,18 @@ fn emit_hatch(
|
||||||
// before narrowing, as everywhere else in this file.
|
// before narrowing, as everywhere else in this file.
|
||||||
let (ax, ay) = ((a[0] + ox) as f32, (a[1] + oy) as f32);
|
let (ax, ay) = ((a[0] + ox) as f32, (a[1] + oy) as f32);
|
||||||
let (bx, by) = ((b_pt[0] + ox) as f32, (b_pt[1] + oy) as f32);
|
let (bx, by) = ((b_pt[0] + ox) as f32, (b_pt[1] + oy) as f32);
|
||||||
ops.push(Op::DrawLine {
|
let points = vec![
|
||||||
line: Line {
|
LinePoint {
|
||||||
points: vec![
|
p: Point::new(Mm(ax), Mm(ay)),
|
||||||
LinePoint {
|
bezier: false,
|
||||||
p: Point::new(Mm(ax), Mm(ay)),
|
|
||||||
bezier: false,
|
|
||||||
},
|
|
||||||
LinePoint {
|
|
||||||
p: Point::new(Mm(bx), Mm(by)),
|
|
||||||
bezier: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
is_closed: false,
|
|
||||||
},
|
},
|
||||||
});
|
LinePoint {
|
||||||
|
p: Point::new(Mm(bx), Mm(by)),
|
||||||
|
bezier: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let dot_radius = Pt(SCREEN_DOT_MM * MM_TO_PT / (2.0 * scale.max(1e-6)));
|
||||||
|
flush_line(ops, &points, Some(dot_radius));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1343,8 +1343,20 @@ impl Scene {
|
||||||
style.4
|
style.4
|
||||||
};
|
};
|
||||||
let color = style.0;
|
let color = style.0;
|
||||||
let color =
|
// Explicit ACI 7 solid fills inside blocks are often
|
||||||
crate::scene::view::render::adapt_to_bg(color, hatch_bg);
|
// white masks. Keep the explicit white on a light
|
||||||
|
// layout; adapting it to paper turns the mask into a
|
||||||
|
// black rectangle unlike the viewport (#618).
|
||||||
|
let preserve_white_mask = dxf.is_solid
|
||||||
|
&& matches!(
|
||||||
|
dxf.common.color,
|
||||||
|
acadrust::types::Color::Index(7)
|
||||||
|
);
|
||||||
|
let color = if preserve_white_mask {
|
||||||
|
color
|
||||||
|
} else {
|
||||||
|
crate::scene::view::render::adapt_to_bg(color, hatch_bg)
|
||||||
|
};
|
||||||
if let Some(mut model) =
|
if let Some(mut model) =
|
||||||
Self::hatch_model_from_dxf(&dxf, color)
|
Self::hatch_model_from_dxf(&dxf, color)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -210,16 +210,12 @@ impl HatchModel {
|
||||||
self.pattern_segments_with_dot_length(None)
|
self.pattern_segments_with_dot_length(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plot variant of [`Self::pattern_segments`] that renders PAT zero-length
|
/// Plot variant of [`Self::pattern_segments`] that preserves PAT
|
||||||
/// dash entries at a caller-provided world length. Screen rendering snaps
|
/// zero-length dash entries as coincident endpoints. The PDF exporter can
|
||||||
/// these entries to one pixel; plot callers know the model-to-paper scale
|
/// then paint a round point; turning them into short segments here makes
|
||||||
/// and can therefore choose the equivalent physical mark without tying its
|
/// point-only patterns such as AR-CONC visibly look like tiny lines.
|
||||||
/// size to the pattern period.
|
pub(crate) fn pattern_segments_for_plot(&self) -> Vec<[[f64; 2]; 2]> {
|
||||||
pub(crate) fn pattern_segments_for_plot(
|
self.pattern_segments_with_dot_length(Some(0.0))
|
||||||
&self,
|
|
||||||
dot_length: f64,
|
|
||||||
) -> Vec<[[f64; 2]; 2]> {
|
|
||||||
self.pattern_segments_with_dot_length(Some(dot_length.max(1e-9)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pattern_segments_with_dot_length(
|
fn pattern_segments_with_dot_length(
|
||||||
|
|
@ -436,13 +432,18 @@ impl HatchModel {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} else if d == 0.0 && seg_t >= t0 - 1e-6 && seg_t <= t1 + 1e-6 {
|
} else if d == 0.0 && seg_t >= t0 - 1e-6 && seg_t <= t1 + 1e-6 {
|
||||||
let a = (seg_t - dot_len * 0.5).max(t0);
|
if plot_dot_length == Some(0.0) {
|
||||||
let b = (seg_t + dot_len * 0.5).min(t1);
|
let point = [lx + seg_t * cos_a, ly + seg_t * sin_a];
|
||||||
if b > a {
|
segments.push([point, point]);
|
||||||
segments.push([
|
} else {
|
||||||
[lx + a * cos_a, ly + a * sin_a],
|
let a = (seg_t - dot_len * 0.5).max(t0);
|
||||||
[lx + b * cos_a, ly + b * sin_a],
|
let b = (seg_t + dot_len * 0.5).min(t1);
|
||||||
]);
|
if b > a {
|
||||||
|
segments.push([
|
||||||
|
[lx + a * cos_a, ly + a * sin_a],
|
||||||
|
[lx + b * cos_a, ly + b * sin_a],
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
seg_t += dl;
|
seg_t += dl;
|
||||||
|
|
|
||||||
|
|
@ -433,13 +433,6 @@ impl Scene {
|
||||||
let hatches = self.plot_hatches_for_block(model_block, Some(&frozen));
|
let hatches = self.plot_hatches_for_block(model_block, Some(&frozen));
|
||||||
for hatch in hatches {
|
for hatch in hatches {
|
||||||
if matches!(&hatch.pattern, HatchPattern::Pattern(_)) {
|
if matches!(&hatch.pattern, HatchPattern::Pattern(_)) {
|
||||||
// The GPU draws PAT zero-length dash entries as one-pixel
|
|
||||||
// dots. Convert that display pixel to model units before
|
|
||||||
// projecting so vector output keeps the same dot size
|
|
||||||
// instead of expanding it from the pattern period.
|
|
||||||
let dot_length = (25.4 / 96.0)
|
|
||||||
* self.paper_space_unit_factor()
|
|
||||||
/ viewport_scale.max(1e-9);
|
|
||||||
let mut points = Vec::new();
|
let mut points = Vec::new();
|
||||||
let mut aabb = [
|
let mut aabb = [
|
||||||
f32::INFINITY,
|
f32::INFINITY,
|
||||||
|
|
@ -447,7 +440,7 @@ impl Scene {
|
||||||
f32::NEG_INFINITY,
|
f32::NEG_INFINITY,
|
||||||
f32::NEG_INFINITY,
|
f32::NEG_INFINITY,
|
||||||
];
|
];
|
||||||
for [a, b] in hatch.pattern_segments_for_plot(dot_length) {
|
for [a, b] in hatch.pattern_segments_for_plot() {
|
||||||
let (Some(a), Some(b)) =
|
let (Some(a), Some(b)) =
|
||||||
(project(a[0], a[1]), project(b[0], b[1]))
|
(project(a[0], a[1]), project(b[0], b[1]))
|
||||||
else {
|
else {
|
||||||
|
|
|
||||||
|
|
@ -99,18 +99,13 @@ pub struct PlotDialogState {
|
||||||
pub printer: Option<String>,
|
pub printer: Option<String>,
|
||||||
/// Output goes to a PDF file instead of a printer.
|
/// Output goes to a PDF file instead of a printer.
|
||||||
pub to_file: bool,
|
pub to_file: bool,
|
||||||
#[serde(skip)]
|
|
||||||
pub paper: String,
|
pub paper: String,
|
||||||
#[serde(skip)]
|
|
||||||
pub orientation: String,
|
pub orientation: String,
|
||||||
pub upside_down: bool,
|
pub upside_down: bool,
|
||||||
pub copies: String,
|
pub copies: String,
|
||||||
pub area: String,
|
pub area: String,
|
||||||
#[serde(skip)]
|
|
||||||
pub center: bool,
|
pub center: bool,
|
||||||
#[serde(skip)]
|
|
||||||
pub offset_x: String,
|
pub offset_x: String,
|
||||||
#[serde(skip)]
|
|
||||||
pub offset_y: String,
|
pub offset_y: String,
|
||||||
pub scale: String,
|
pub scale: String,
|
||||||
#[serde(default = "legacy_fit_to_paper_default")]
|
#[serde(default = "legacy_fit_to_paper_default")]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue