diff options
Diffstat (limited to 'src/core/print/parsing.rs')
| -rw-r--r-- | src/core/print/parsing.rs | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/src/core/print/parsing.rs b/src/core/print/parsing.rs new file mode 100644 index 0000000..01edf37 --- /dev/null +++ b/src/core/print/parsing.rs @@ -0,0 +1,219 @@ +use anyhow::{Context, Result}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +// This file now centralizes parsing logic that was previously in system_print.rs +// It helps decouple the UI and plugins from the direct implementation of parsing. + +/// Represents the layout of a label, deserialized from JSON. +// NOTE: This assumes LabelLayout is defined in your renderer module. +// If not, you might need to move or publicly export it. +use super::renderer::LabelLayout; + +/// Represents printer-specific settings. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PrinterSettings { + #[serde(default = "default_paper_size")] + pub paper_size: String, + #[serde(default = "default_orientation")] + pub orientation: String, + #[serde(default)] + pub margins: PrinterMargins, + #[serde(default = "default_color")] + pub color: bool, + #[serde(default = "default_quality")] + pub quality: String, + #[serde(default = "default_copies")] + pub copies: u32, + #[serde(default)] + pub duplex: bool, + #[serde(default)] + pub center: Option<CenterMode>, + #[serde(default)] + pub center_disabled: bool, + #[serde(default = "default_scale_mode")] + pub scale_mode: ScaleMode, + #[serde(default = "default_scale_factor")] + pub scale_factor: f32, + #[serde(default)] + pub custom_width_mm: Option<f32>, + #[serde(default)] + pub custom_height_mm: Option<f32>, + // New optional direct-print fields + #[serde(default)] + pub printer_name: Option<String>, + #[serde(default)] + pub show_dialog_if_unfound: Option<bool>, + #[serde(default)] + pub compatibility_mode: bool, +} + +impl Default for PrinterSettings { + fn default() -> Self { + Self { + paper_size: default_paper_size(), + orientation: default_orientation(), + margins: PrinterMargins::default(), + color: default_color(), + quality: default_quality(), + copies: default_copies(), + duplex: false, + center: None, + center_disabled: false, + scale_mode: default_scale_mode(), + scale_factor: default_scale_factor(), + custom_width_mm: None, + custom_height_mm: None, + printer_name: None, + show_dialog_if_unfound: None, + compatibility_mode: false, + } + } +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum CenterMode { + None, + Horizontal, + Vertical, + Both, +} + +impl CenterMode { + pub fn includes_horizontal(self) -> bool { + matches!(self, CenterMode::Horizontal | CenterMode::Both) + } + + pub fn includes_vertical(self) -> bool { + matches!(self, CenterMode::Vertical | CenterMode::Both) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct PrinterMargins { + pub top: f32, + pub right: f32, + pub bottom: f32, + pub left: f32, +} + +// Default value functions for PrinterSettings +fn default_paper_size() -> String { + "A4".to_string() +} +fn default_orientation() -> String { + "portrait".to_string() +} +#[allow(dead_code)] +fn default_scale() -> f32 { + 1.0 +} +fn default_color() -> bool { + false +} +fn default_quality() -> String { + "high".to_string() +} +fn default_copies() -> u32 { + 1 +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "kebab-case")] +pub enum ScaleMode { + Fit, + FitX, + FitY, + MaxBoth, + MaxX, + MaxY, + Manual, +} + +fn default_scale_mode() -> ScaleMode { + ScaleMode::Fit +} + +fn default_scale_factor() -> f32 { + 1.0 +} + +impl PrinterSettings { + pub fn canonicalize_dimensions(&mut self) { + // No-op: dimensions are used as specified + } + + pub fn get_dimensions_mm(&self) -> (f32, f32) { + if let (Some(w), Some(h)) = (self.custom_width_mm, self.custom_height_mm) { + // For custom dimensions, swap if landscape to create rotated PDF + let orientation = self.orientation.to_ascii_lowercase(); + + let result = if orientation == "landscape" { + // Landscape: swap dimensions for PDF (rotate 90°) + (h, w) + } else { + // Portrait: use as-is + (w, h) + }; + + log::info!( + "get_dimensions_mm: custom {}×{} mm, orientation='{}' → PDF {}×{} mm", + w, + h, + self.orientation, + result.0, + result.1 + ); + + result + } else { + // Standard paper sizes + let (width, height) = match self.paper_size.as_str() { + "A4" => (210.0, 297.0), + "A5" => (148.0, 210.0), + "Letter" => (215.9, 279.4), + _ => (100.0, 150.0), // Default + }; + if self.orientation == "landscape" { + (height, width) + } else { + (width, height) + } + } + } +} + +/// Utility function to parse a JSON value that might be a raw string, +/// a base64-encoded string, or a direct JSON object. +fn parse_flexible_json<T>(value: &Value) -> Result<T> +where + T: for<'de> Deserialize<'de>, +{ + match value { + Value::String(s) => { + if let Ok(parsed) = serde_json::from_str(s) { + return Ok(parsed); + } + match base64::Engine::decode(&base64::engine::general_purpose::STANDARD, s) { + Ok(decoded_bytes) => { + let decoded_str = String::from_utf8(decoded_bytes) + .context("Decoded base64 is not valid UTF-8")?; + serde_json::from_str(&decoded_str) + .context("Failed to parse base64-decoded JSON") + } + Err(_) => anyhow::bail!("Value is not valid JSON or base64-encoded JSON"), + } + } + json_obj => serde_json::from_value(json_obj.clone()) + .context("Failed to parse value as a direct JSON object"), + } +} + +pub fn parse_layout_json(layout_json_value: &Value) -> Result<LabelLayout> { + parse_flexible_json(layout_json_value) +} + +pub fn parse_printer_settings(settings_value: &Value) -> Result<PrinterSettings> { + parse_flexible_json(settings_value) +} |
