Skip to main content

qr_code_styling/core/
qr_code_styling.rs

1//! Main QRCodeStyling struct.
2
3use std::fs::File;
4use std::io::Write;
5use std::path::Path;
6
7use crate::config::{QRCodeStylingBuilder, QRCodeStylingOptions};
8use crate::core::QRMatrix;
9use crate::error::Result;
10use crate::rendering::{PdfRenderer, RasterRenderer, SvgRenderer};
11use crate::types::OutputFormat;
12
13/// Main QR code styling struct.
14///
15/// This is the primary entry point for creating styled QR codes.
16///
17/// # Example
18///
19/// ```rust
20/// use qr_code_styling::{QRCodeStyling, OutputFormat};
21///
22/// let qr = QRCodeStyling::builder()
23///     .data("https://example.com")
24///     .width(300)
25///     .height(300)
26///     .build()
27///     .unwrap();
28///
29/// let svg = qr.render_svg().unwrap();
30/// ```
31pub struct QRCodeStyling {
32    options: QRCodeStylingOptions,
33    matrix: QRMatrix,
34}
35
36impl QRCodeStylingBuilder {
37    /// Build the QRCodeStyling with the configured options.
38    pub fn build(self) -> Result<QRCodeStyling> {
39        let options = self.build_options()?;
40        QRCodeStyling::new(options)
41    }
42}
43
44impl QRCodeStyling {
45    /// Create a new QRCodeStyling builder.
46    pub fn builder() -> QRCodeStylingBuilder {
47        QRCodeStylingBuilder::new()
48    }
49
50    /// Create a new QRCodeStyling with the given options.
51    pub fn new(options: QRCodeStylingOptions) -> Result<Self> {
52        let matrix = QRMatrix::new(&options.data, &options.qr_options)?;
53
54        Ok(Self { options, matrix })
55    }
56
57    /// Update the data and regenerate the QR code.
58    pub fn update(&mut self, data: &str) -> Result<&mut Self> {
59        self.options.data = data.to_string();
60        self.matrix = QRMatrix::new(&self.options.data, &self.options.qr_options)?;
61        Ok(self)
62    }
63
64    /// Render the QR code as an SVG string.
65    pub fn render_svg(&self) -> Result<String> {
66        let renderer = SvgRenderer::new(self.options.clone());
67        renderer.render(&self.matrix)
68    }
69
70    /// Render the QR code in the specified format.
71    pub fn render(&self, format: OutputFormat) -> Result<Vec<u8>> {
72        match format {
73            OutputFormat::Svg => {
74                let svg = self.render_svg()?;
75                Ok(svg.into_bytes())
76            }
77            OutputFormat::Png | OutputFormat::Jpeg | OutputFormat::WebP => {
78                let svg = self.render_svg()?;
79                RasterRenderer::render(&svg, self.options.width, self.options.height, format)
80            }
81            OutputFormat::Pdf => {
82                // Convert SVG directly to PDF (vector quality preserved)
83                let svg = self.render_svg()?;
84                PdfRenderer::render_from_svg(&svg, self.options.width, self.options.height)
85            }
86        }
87    }
88
89    /// Save the QR code to a file.
90    pub fn save<P: AsRef<Path>>(&self, path: P, format: OutputFormat) -> Result<()> {
91        let data = self.render(format)?;
92        let mut file = File::create(path)?;
93        file.write_all(&data)?;
94        Ok(())
95    }
96
97    /// Get the QR code module count.
98    pub fn module_count(&self) -> usize {
99        self.matrix.module_count()
100    }
101
102    /// Get the current options.
103    pub fn options(&self) -> &QRCodeStylingOptions {
104        &self.options
105    }
106
107    /// Get mutable reference to options (requires regeneration after).
108    pub fn options_mut(&mut self) -> &mut QRCodeStylingOptions {
109        &mut self.options
110    }
111
112    /// Regenerate the QR matrix (call after modifying options).
113    pub fn regenerate(&mut self) -> Result<()> {
114        self.matrix = QRMatrix::new(&self.options.data, &self.options.qr_options)?;
115        Ok(())
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::types::DotType;
123    use crate::config::DotsOptions;
124
125    #[test]
126    fn test_basic_creation() {
127        let qr = QRCodeStyling::builder()
128            .data("https://example.com")
129            .build()
130            .unwrap();
131
132        assert!(qr.module_count() >= 21);
133    }
134
135    #[test]
136    fn test_render_svg() {
137        let qr = QRCodeStyling::builder()
138            .data("Test")
139            .width(200)
140            .height(200)
141            .build()
142            .unwrap();
143
144        let svg = qr.render_svg().unwrap();
145        assert!(svg.contains("<?xml"));
146        assert!(svg.contains("<svg"));
147        assert!(svg.contains("</svg>"));
148    }
149
150    #[test]
151    fn test_update() {
152        let mut qr = QRCodeStyling::builder()
153            .data("First")
154            .build()
155            .unwrap();
156
157        let count1 = qr.module_count();
158
159        qr.update("This is a much longer string that should result in a larger QR code")
160            .unwrap();
161
162        let count2 = qr.module_count();
163
164        // Longer data should result in larger QR code
165        assert!(count2 >= count1);
166    }
167
168    #[test]
169    fn test_with_dot_options() {
170        let qr = QRCodeStyling::builder()
171            .data("Test")
172            .dots_options(DotsOptions::new(DotType::Dots))
173            .build()
174            .unwrap();
175
176        let svg = qr.render_svg().unwrap();
177        assert!(svg.contains("circle"));
178    }
179
180    #[test]
181    fn test_render_png() {
182        let qr = QRCodeStyling::builder()
183            .data("Test")
184            .width(100)
185            .height(100)
186            .build()
187            .unwrap();
188
189        let png = qr.render(OutputFormat::Png).unwrap();
190        // PNG magic bytes
191        assert_eq!(&png[0..4], &[0x89, 0x50, 0x4E, 0x47]);
192    }
193}