aboutsummaryrefslogtreecommitdiff
path: root/src/core/print/parsing.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/print/parsing.rs')
-rw-r--r--src/core/print/parsing.rs219
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)
+}