diff --git a/src/app/mod.rs b/src/app/mod.rs index 45b482cdc..e1fe02c9a 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -71,6 +71,16 @@ pub(super) struct H7CAD { page_setup_w: String, /// Editable paper height buffer for the Page Setup panel (string while typing). page_setup_h: String, + /// Plot area type: "Layout" | "Extents". + page_setup_plot_area: String, + /// Center the drawing on the page when exporting. + page_setup_center: bool, + /// Plot offset X in mm (applied after optional centering). + page_setup_offset_x: String, + /// Plot offset Y in mm. + page_setup_offset_y: String, + /// Plot rotation in degrees: "0" | "90" | "180" | "270". + page_setup_rotation: String, } #[derive(Debug, Clone)] @@ -259,6 +269,18 @@ pub enum Message { PageSetupWidthEdit(String), /// Live-edit of the paper height field. PageSetupHeightEdit(String), + /// User selected a paper size preset (e.g. "A4 Portrait"). + PageSetupPreset(String), + /// User changed the plot area type ("Layout" or "Extents"). + PageSetupPlotArea(String), + /// Toggle center-on-page. + PageSetupCenterToggle, + /// Live-edit of plot offset X. + PageSetupOffsetXEdit(String), + /// Live-edit of plot offset Y. + PageSetupOffsetYEdit(String), + /// User changed plot rotation. + PageSetupRotation(String), /// Apply the changes entered in Page Setup. PageSetupCommit, // ── Plot / Export ───────────────────────────────────────────────────── @@ -300,6 +322,11 @@ impl H7CAD { page_setup_open: false, page_setup_w: String::new(), page_setup_h: String::new(), + page_setup_plot_area: "Layout".to_string(), + page_setup_center: true, + page_setup_offset_x: "0.0".to_string(), + page_setup_offset_y: "0.0".to_string(), + page_setup_rotation: "0".to_string(), }; app.sync_ribbon_layers(); app diff --git a/src/app/update.rs b/src/app/update.rs index 667f24a83..a1b174b22 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -1779,12 +1779,60 @@ impl H7CAD { self.page_setup_h = s; Task::none() } + Message::PageSetupPreset(name) => { + // Paper size presets defined in view.rs — mirror them here. + let sizes: &[(&str, f64, f64)] = &[ + ("A4 Portrait", 210.0, 297.0), + ("A4 Landscape", 297.0, 210.0), + ("A3 Portrait", 297.0, 420.0), + ("A3 Landscape", 420.0, 297.0), + ("A2 Portrait", 420.0, 594.0), + ("A2 Landscape", 594.0, 420.0), + ("A1 Portrait", 594.0, 841.0), + ("A1 Landscape", 841.0, 594.0), + ("A0 Portrait", 841.0, 1189.0), + ("A0 Landscape", 1189.0, 841.0), + ("Letter Portrait", 215.9, 279.4), + ("Letter Landscape", 279.4, 215.9), + ]; + if let Some(&(_, w, h)) = sizes.iter().find(|(n, _, _)| *n == name) { + self.page_setup_w = format!("{w:.1}"); + self.page_setup_h = format!("{h:.1}"); + } + Task::none() + } + Message::PageSetupPlotArea(s) => { + self.page_setup_plot_area = s; + Task::none() + } + Message::PageSetupCenterToggle => { + self.page_setup_center = !self.page_setup_center; + Task::none() + } + Message::PageSetupOffsetXEdit(s) => { + self.page_setup_offset_x = s; + Task::none() + } + Message::PageSetupOffsetYEdit(s) => { + self.page_setup_offset_y = s; + Task::none() + } + Message::PageSetupRotation(s) => { + self.page_setup_rotation = s; + Task::none() + } Message::PageSetupCommit => { let i = self.active_tab; let layout_name = self.tabs[i].scene.current_layout.clone(); if layout_name != "Model" { let w: f64 = self.page_setup_w.parse::().unwrap_or(297.0).max(1.0); let h: f64 = self.page_setup_h.parse::().unwrap_or(210.0).max(1.0); + let plot_area = self.page_setup_plot_area.clone(); + let center = self.page_setup_center; + let offset_x = self.page_setup_offset_x.parse::().unwrap_or(0.0); + let offset_y = self.page_setup_offset_y.parse::().unwrap_or(0.0); + let rotation: i16 = self.page_setup_rotation.parse().unwrap_or(0); + // Update the Layout object's limits. for obj in self.tabs[i].scene.document.objects.values_mut() { if let acadrust::objects::ObjectType::Layout(l) = obj { @@ -1797,9 +1845,51 @@ impl H7CAD { } } } + + // Find or create the PlotSettings object for this layout. + use acadrust::objects::{ObjectType, PlotSettings, PlotType, PlotRotation, PlotPaperUnits}; + let plot_handle = self.tabs[i].scene.document.objects.iter() + .find_map(|(h, obj)| { + if let ObjectType::PlotSettings(ps) = obj { + if ps.page_name == layout_name { Some(*h) } else { None } + } else { None } + }); + + let ps_entry = if let Some(h) = plot_handle { + self.tabs[i].scene.document.objects.get_mut(&h) + } else { + // Create a new PlotSettings object and insert it. + let mut ps = PlotSettings::new(layout_name.clone()); + ps.handle = acadrust::Handle::new(self.tabs[i].scene.document.next_handle()); + let h = ps.handle; + self.tabs[i].scene.document.objects.insert(h, ObjectType::PlotSettings(ps)); + self.tabs[i].scene.document.objects.get_mut(&h) + }; + + if let Some(ObjectType::PlotSettings(ps)) = ps_entry { + ps.paper_width = w; + ps.paper_height = h; + ps.paper_units = PlotPaperUnits::Millimeters; + ps.plot_type = if plot_area == "Extents" { + PlotType::Extents + } else { + PlotType::Layout + }; + ps.flags.plot_centered = center; + ps.origin_x = offset_x; + ps.origin_y = offset_y; + ps.rotation = match rotation { + 90 => PlotRotation::Degrees90, + 180 => PlotRotation::Degrees180, + 270 => PlotRotation::Degrees270, + _ => PlotRotation::None, + }; + } + self.tabs[i].dirty = true; self.command_line.push_info(&format!( - "Page size set to {w:.1} × {h:.1} mm." + "Page setup: {w:.1}×{h:.1} mm area={plot_area} \ + center={center} rot={rotation}°" )); } self.page_setup_open = false; @@ -1823,24 +1913,97 @@ impl H7CAD { Message::PlotExportPath(None) => Task::none(), Message::PlotExportPath(Some(path)) => { let i = self.active_tab; - let wires = self.tabs[i].scene.entity_wires(); // full scene for PDF - let (paper_w, paper_h, offset_x, offset_y) = - if let Some(((x0, y0), (x1, y1))) = self.tabs[i].scene.paper_limits() { - (x1 - x0, y1 - y0, -x0, -y0) + let scene = &self.tabs[i].scene; + let layout_name = scene.current_layout.clone(); + let wires = scene.entity_wires(); + + // Read PlotSettings for current layout (if available). + use acadrust::objects::{ObjectType, PlotType, PlotRotation}; + let ps_snap = scene.document.objects.values().find_map(|obj| { + if let ObjectType::PlotSettings(ps) = obj { + if ps.page_name == layout_name { Some(ps.clone()) } else { None } + } else { None } + }); + + // Determine paper size and drawing offset. + let (paper_w, paper_h, mut draw_ox, mut draw_oy, rotation_deg) = + if let Some(((x0, y0), (x1, y1))) = scene.paper_limits() { + let (pw, ph) = (x1 - x0, y1 - y0); + + // If PlotSettings says Extents, use model space extents instead. + let use_extents = ps_snap.as_ref() + .map(|ps| matches!(ps.plot_type, PlotType::Extents)) + .unwrap_or(false); + + let (ox, oy) = if use_extents { + if let Some((mn, mx)) = scene.model_space_extents() { + (-mn.x as f64, -mn.y as f64) + } else { + (-x0, -y0) + } + } else { + (-x0, -y0) + }; + + let rot = ps_snap.as_ref() + .map(|ps| ps.rotation.to_degrees() as i32) + .unwrap_or(0); + + (pw, ph, ox, oy, rot) } else { - // Model space: fit wires to computed extents with 5 % margin. + // Model space: fit with 5% margin. let margin = 1.05_f64; - if let Some((mn, mx)) = self.tabs[i].scene.model_space_extents() { + if let Some((mn, mx)) = scene.model_space_extents() { let w = ((mx.x - mn.x) as f64 * margin).max(1.0); let h = ((mx.y - mn.y) as f64 * margin).max(1.0); let pad_x = (w - (mx.x - mn.x) as f64) * 0.5; let pad_y = (h - (mx.y - mn.y) as f64) * 0.5; - (w, h, -(mn.x as f64) + pad_x, -(mn.y as f64) + pad_y) + (w, h, -(mn.x as f64) + pad_x, -(mn.y as f64) + pad_y, 0) } else { - (297.0, 210.0, 0.0, 0.0) + (297.0, 210.0, 0.0, 0.0, 0) } }; - match crate::io::pdf_export::export_pdf(&wires, paper_w, paper_h, offset_x as f32, offset_y as f32, &path) { + + // Apply PlotSettings offset and centering. + if let Some(ref ps) = ps_snap { + if ps.flags.plot_centered { + // Centering: compute wire extents and re-centre. + let all_x: Vec = wires.iter() + .flat_map(|w| w.points.iter().map(|p| p[0])) + .filter(|v| !v.is_nan()) + .collect(); + let all_y: Vec = wires.iter() + .flat_map(|w| w.points.iter().map(|p| p[1])) + .filter(|v| !v.is_nan()) + .collect(); + if let (Some(&min_x), Some(&max_x), Some(&min_y), Some(&max_y)) = ( + all_x.iter().copied().reduce(f32::min).as_ref(), + all_x.iter().copied().reduce(f32::max).as_ref(), + all_y.iter().copied().reduce(f32::min).as_ref(), + all_y.iter().copied().reduce(f32::max).as_ref(), + ) { + let cx = (min_x + max_x) as f64 / 2.0; + let cy = (min_y + max_y) as f64 / 2.0; + draw_ox += paper_w / 2.0 - cx; + draw_oy += paper_h / 2.0 - cy; + } + } else { + draw_ox += ps.origin_x; + draw_oy += ps.origin_y; + } + } + + // For rotation: swap paper dimensions and note angle for export. + let (eff_w, eff_h) = match rotation_deg { + 90 | 270 => (paper_h, paper_w), + _ => (paper_w, paper_h), + }; + + match crate::io::pdf_export::export_pdf( + &wires, eff_w, eff_h, + draw_ox as f32, draw_oy as f32, + rotation_deg, &path, + ) { Ok(()) => self.command_line.push_info(&format!( "Exported: {}", path.file_name().unwrap_or_default().to_string_lossy() diff --git a/src/app/view.rs b/src/app/view.rs index 1fb608b2f..cbd34b184 100644 --- a/src/app/view.rs +++ b/src/app/view.rs @@ -5,7 +5,7 @@ use super::helpers::grid_plane_from_camera; use crate::scene::{VIEWCUBE_DRAW_PX, VIEWCUBE_PAD}; use crate::scene::grip::grips_to_screen; use crate::ui::overlay; -use iced::widget::{button, column, container, mouse_area, row, shader, stack, text, text_input, Row}; +use iced::widget::{button, column, container, mouse_area, row, shader, stack, text, text_input, Row, Space}; use iced::window; use iced::{keyboard, Background, Border, Color, Element, Fill, Subscription, Task, Theme}; @@ -200,7 +200,15 @@ impl H7CAD { }; let page_setup_layer: Element<'_, Message> = if self.page_setup_open { - page_setup_overlay(&self.page_setup_w, &self.page_setup_h) + page_setup_overlay( + &self.page_setup_w, + &self.page_setup_h, + &self.page_setup_plot_area, + self.page_setup_center, + &self.page_setup_offset_x, + &self.page_setup_offset_y, + &self.page_setup_rotation, + ) } else { iced::widget::Space::new().width(0).height(0).into() }; @@ -453,13 +461,41 @@ fn layout_context_menu_overlay(name: &str) -> Element<'_, Message> { // ── Page Setup overlay ────────────────────────────────────────────────────── /// Modal panel for editing paper width / height of the current layout. -fn page_setup_overlay<'a>(w_buf: &'a str, h_buf: &'a str) -> Element<'a, Message> { - const PANEL_BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 }; +// ── Paper size presets ──────────────────────────────────────────────────── + +const PAPER_PRESETS: &[(&str, f64, f64)] = &[ + ("A4 Portrait", 210.0, 297.0), + ("A4 Landscape", 297.0, 210.0), + ("A3 Portrait", 297.0, 420.0), + ("A3 Landscape", 420.0, 297.0), + ("A2 Portrait", 420.0, 594.0), + ("A2 Landscape", 594.0, 420.0), + ("A1 Portrait", 594.0, 841.0), + ("A1 Landscape", 841.0, 594.0), + ("A0 Portrait", 841.0, 1189.0), + ("A0 Landscape", 1189.0, 841.0), + ("Letter Portrait", 215.9, 279.4), + ("Letter Landscape", 279.4, 215.9), + ("Custom", 0.0, 0.0), +]; + +fn page_setup_overlay<'a>( + w_buf: &'a str, + h_buf: &'a str, + plot_area: &'a str, + center: bool, + offset_x: &'a str, + offset_y: &'a str, + rotation: &'a str, +) -> Element<'a, Message> { + const PANEL_BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 }; const BORDER_COL: Color = Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 }; const TEXT_COLOR: Color = Color { r: 0.88, g: 0.88, b: 0.88, a: 1.0 }; - const ACCENT: Color = Color { r: 0.25, g: 0.50, b: 0.85, a: 1.0 }; + const DIM_COLOR: Color = Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 }; + const ACCENT: Color = Color { r: 0.25, g: 0.50, b: 0.85, a: 1.0 }; + const ACTIVE_BG: Color = Color { r: 0.20, g: 0.40, b: 0.70, a: 1.0 }; - let label = |s: &'static str| text(s).size(12).color(TEXT_COLOR); + let lbl = |s: &'static str| text(s).size(11).color(DIM_COLOR).width(110); let field_style = |_: &Theme, _: text_input::Status| text_input::Style { background: Background::Color(Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 }), @@ -489,52 +525,163 @@ fn page_setup_overlay<'a>(w_buf: &'a str, h_buf: &'a str) -> Element<'a, Message } }; + let pill_style = |active: bool| { + move |_: &Theme, status: button::Status| button::Style { + background: Some(Background::Color(match (active, status) { + (true, _) => ACTIVE_BG, + (false, button::Status::Hovered | button::Status::Pressed) => { + Color { r: 0.28, g: 0.28, b: 0.28, a: 1.0 } + } + _ => Color { r: 0.20, g: 0.20, b: 0.20, a: 1.0 }, + })), + text_color: TEXT_COLOR, + border: Border { color: BORDER_COL, width: 1.0, radius: 3.0.into() }, + shadow: iced::Shadow::default(), + snap: false, + } + }; + + let divider = || container(Space::new().width(Fill).height(1)) + .style(|_: &Theme| container::Style { + background: Some(Background::Color(BORDER_COL)), + ..Default::default() + }) + .width(Fill) + .height(1); + + // ── Paper size presets ──────────────────────────────────────────────── + let preset_row1 = row![ + button(text("A4 P").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPreset("A4 Portrait".into())) + .style(pill_style(false)).padding([3, 6]), + button(text("A4 L").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPreset("A4 Landscape".into())) + .style(pill_style(false)).padding([3, 6]), + button(text("A3 P").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPreset("A3 Portrait".into())) + .style(pill_style(false)).padding([3, 6]), + button(text("A3 L").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPreset("A3 Landscape".into())) + .style(pill_style(false)).padding([3, 6]), + ].spacing(4); + + let preset_row2 = row![ + button(text("A2 L").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPreset("A2 Landscape".into())) + .style(pill_style(false)).padding([3, 6]), + button(text("A1 L").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPreset("A1 Landscape".into())) + .style(pill_style(false)).padding([3, 6]), + button(text("A0 L").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPreset("A0 Landscape".into())) + .style(pill_style(false)).padding([3, 6]), + button(text("Letter").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPreset("Letter Landscape".into())) + .style(pill_style(false)).padding([3, 6]), + ].spacing(4); + + // ── Plot area buttons ───────────────────────────────────────────────── + let area_row = row![ + button(text("Layout").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPlotArea("Layout".into())) + .style(pill_style(plot_area == "Layout")).padding([3, 8]), + button(text("Extents").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupPlotArea("Extents".into())) + .style(pill_style(plot_area == "Extents")).padding([3, 8]), + ].spacing(6); + + // ── Center toggle ───────────────────────────────────────────────────── + let center_btn = button( + text(if center { "✓ Center on page" } else { " Center on page" }).size(11).color(TEXT_COLOR) + ) + .on_press(Message::PageSetupCenterToggle) + .style(pill_style(center)) + .padding([4, 10]); + + // ── Rotation buttons ────────────────────────────────────────────────── + let rot_row = row![ + button(text("0°").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupRotation("0".into())) + .style(pill_style(rotation == "0")).padding([3, 8]), + button(text("90°").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupRotation("90".into())) + .style(pill_style(rotation == "90")).padding([3, 8]), + button(text("180°").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupRotation("180".into())) + .style(pill_style(rotation == "180")).padding([3, 8]), + button(text("270°").size(10).color(TEXT_COLOR)) + .on_press(Message::PageSetupRotation("270".into())) + .style(pill_style(rotation == "270")).padding([3, 8]), + ].spacing(4); + let panel = container( column![ text("Page Setup").size(14).color(TEXT_COLOR), - container(iced::widget::Space::new().width(Fill).height(1)) - .style(|_: &Theme| container::Style { - background: Some(Background::Color(BORDER_COL)), - ..Default::default() - }) - .width(Fill), + divider(), + // Paper size + text("Paper Size").size(11).color(DIM_COLOR), + preset_row1, + preset_row2, row![ - label("Width (mm)"), + lbl("Width (mm)"), text_input("297", w_buf) .on_input(Message::PageSetupWidthEdit) .on_submit(Message::PageSetupCommit) .style(field_style) - .width(90) - .size(12), - ] - .spacing(8) - .align_y(iced::Alignment::Center), + .width(80).size(12), + ].spacing(6).align_y(iced::Alignment::Center), row![ - label("Height (mm)"), + lbl("Height (mm)"), text_input("210", h_buf) .on_input(Message::PageSetupHeightEdit) .on_submit(Message::PageSetupCommit) .style(field_style) - .width(90) - .size(12), - ] - .spacing(8) - .align_y(iced::Alignment::Center), + .width(80).size(12), + ].spacing(6).align_y(iced::Alignment::Center), + divider(), + // Plot area + text("Plot Area").size(11).color(DIM_COLOR), + area_row, + divider(), + // Position + text("Position").size(11).color(DIM_COLOR), + center_btn, + row![ + lbl("Offset X (mm)"), + text_input("0", offset_x) + .on_input(Message::PageSetupOffsetXEdit) + .style(field_style) + .width(80).size(12), + ].spacing(6).align_y(iced::Alignment::Center), + row![ + lbl("Offset Y (mm)"), + text_input("0", offset_y) + .on_input(Message::PageSetupOffsetYEdit) + .style(field_style) + .width(80).size(12), + ].spacing(6).align_y(iced::Alignment::Center), + divider(), + // Rotation + text("Rotation").size(11).color(DIM_COLOR), + rot_row, + divider(), + // Buttons row![ button(text("Cancel").size(12).color(TEXT_COLOR)) .on_press(Message::PageSetupClose) .style(btn_style(false)) .padding([5, 14]), + Space::new().width(Fill).height(0), button(text("OK").size(12).color(TEXT_COLOR)) .on_press(Message::PageSetupCommit) .style(btn_style(true)) .padding([5, 20]), ] - .spacing(8), + .align_y(iced::Alignment::Center), ] - .spacing(10) + .spacing(8) .padding(16) - .width(240), + .width(290), ) .style(move |_: &Theme| container::Style { background: Some(Background::Color(PANEL_BG)), @@ -544,7 +691,7 @@ fn page_setup_overlay<'a>(w_buf: &'a str, h_buf: &'a str) -> Element<'a, Message // Click-catcher to close on outside click. let catcher = mouse_area( - container(iced::widget::Space::new().width(Fill).height(Fill)) + container(Space::new().width(Fill).height(Fill)) .width(Fill) .height(Fill), ) diff --git a/src/io/pdf_export.rs b/src/io/pdf_export.rs index f86134b84..b9413cc77 100644 --- a/src/io/pdf_export.rs +++ b/src/io/pdf_export.rs @@ -18,20 +18,20 @@ use std::path::Path; /// Export `wires` to a PDF file. /// -/// - `paper_w` / `paper_h`: page dimensions in mm. +/// - `paper_w` / `paper_h`: page dimensions in mm (already swapped for 90°/270° by caller). /// - `offset_x` / `offset_y`: added to every wire coordinate so the drawing -/// origin maps to the bottom-left corner of the page. Pass 0/0 for paper -/// space (coordinates are already relative to page origin); pass a computed -/// shift for model space to normalise the extents. +/// origin maps to the bottom-left corner of the page. +/// - `rotation_deg`: 0 | 90 | 180 | 270 — rotates the entire drawing on the page. pub fn export_pdf( wires: &[WireModel], paper_w: f64, paper_h: f64, offset_x: f32, offset_y: f32, + rotation_deg: i32, path: &Path, ) -> Result<(), String> { - let bytes = build_pdf(wires, paper_w as f32, paper_h as f32, offset_x, offset_y); + let bytes = build_pdf(wires, paper_w as f32, paper_h as f32, offset_x, offset_y, rotation_deg); let mut file = std::fs::File::create(path).map_err(|e| e.to_string())?; file.write_all(&bytes).map_err(|e| e.to_string()) } @@ -50,7 +50,7 @@ pub async fn pick_pdf_path_owned(stem: String) -> Option { // ── PDF builder ─────────────────────────────────────────────────────────── -fn build_pdf(wires: &[WireModel], paper_w: f32, paper_h: f32, ox: f32, oy: f32) -> Vec { +fn build_pdf(wires: &[WireModel], paper_w: f32, paper_h: f32, ox: f32, oy: f32, rotation_deg: i32) -> Vec { let mut doc = PdfDocument::new("H7CAD Export"); let mut ops: Vec = Vec::new(); @@ -66,6 +66,31 @@ fn build_pdf(wires: &[WireModel], paper_w: f32, paper_h: f32, ox: f32, oy: f32) ops.push(Op::SetLineCapStyle { cap: LineCapStyle::Round }); ops.push(Op::SetLineJoinStyle { join: LineJoinStyle::Round }); + // Apply rotation transform if needed. + // PDF uses mm-based coordinate system with origin at bottom-left. + // We save state, apply a CTM, then restore it after drawing. + let needs_rotation = rotation_deg != 0; + if needs_rotation { + let (cos_a, sin_a, tx, ty) = match rotation_deg { + 90 => ( 0.0_f64, 1.0_f64, 0.0, paper_h as f64), + 180 => (-1.0_f64, 0.0_f64, paper_w as f64, paper_h as f64), + 270 => ( 0.0_f64, -1.0_f64, paper_w as f64, 0.0), + _ => ( 1.0_f64, 0.0_f64, 0.0, 0.0), + }; + // PDF CTM: [a b c d e f] = [cos sin -sin cos tx ty] + ops.push(Op::SaveGraphicsState); + // Convert mm translation to points (1 mm = 2.834645 pt). + let tx_pt = (tx * 2.834645) as f32; + let ty_pt = (ty * 2.834645) as f32; + ops.push(Op::SetTransformationMatrix { + matrix: printpdf::CurTransMat::Raw([ + cos_a as f32, sin_a as f32, + -(sin_a as f32), cos_a as f32, + tx_pt, ty_pt, + ]), + }); + } + let mut last_color: Option<[f32; 3]> = None; let mut last_lw: Option = None; @@ -121,6 +146,10 @@ fn build_pdf(wires: &[WireModel], paper_w: f32, paper_h: f32, ox: f32, oy: f32) flush_line(&mut ops, &segment); } + if needs_rotation { + ops.push(Op::RestoreGraphicsState); + } + let page = PdfPage::new(Mm(paper_w), Mm(paper_h), ops); doc.pages.push(page);