Skip to main content

qrcode_rs/
lib.rs

1//! QRCode encoder
2//!
3//! This crate provides a QR code and Micro QR code encoder for binary data.
4//!
5#![cfg_attr(feature = "image", doc = "```rust")]
6#![cfg_attr(not(feature = "image"), doc = "```ignore")]
7//! use qrcode_rs::QrCode;
8//! use image::Luma;
9//!
10//! // Encode some data into bits.
11//! let code = QrCode::new(b"01234567").unwrap();
12//!
13//! // Render the bits into an image.
14//! let image = code.render::<Luma<u8>>().build();
15//!
16//! // Save the image.
17//! # if cfg!(unix) {
18//! image.save("/tmp/qrcode.png").unwrap();
19//! # }
20//!
21//! // You can also render it into a string.
22//! let string = code.render()
23//!     .light_color(' ')
24//!     .dark_color('#')
25//!     .build();
26//! println!("{}", string);
27//! ```
28
29#![cfg_attr(docsrs, feature(doc_cfg))]
30#![cfg_attr(not(feature = "std"), no_std)]
31#![deny(missing_docs)]
32#![deny(clippy::uninlined_format_args, clippy::manual_range_contains, clippy::semicolon_if_nothing_returned)]
33#![allow(
34    clippy::must_use_candidate, // This is just annoying.
35)]
36
37extern crate alloc;
38
39#[cfg(feature = "std")]
40#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
41pub mod batch;
42pub mod render;
43pub mod structured_append;
44
45// The encoding primitive layer lives in `qrcode-core` and is re-exported here
46// so the public API (`qrcode_rs::bits::Bits`, `qrcode_rs::Version`, …) is unchanged.
47pub use qrcode_core::ConstVersion;
48pub use qrcode_core::{
49    AlphanumericMode, ByteMode, DynEncoder, DynRenderer, EncodeConfig, EncodedOutput, EncoderFactory, EncodingMode,
50    EncodingModes, KanjiMode, ModuleGrid, NumericMode, PluginError, PluginRegistry, PostProcessor, QrPlugin,
51    RenderConfig, RenderOutput, RendererFactory, ResourceLimits, StaticVersion,
52};
53pub use qrcode_core::{bits, canvas, ec, optimize, plugin, traits, types};
54pub use qrcode_decode as decode;
55pub use qrcode_parse as parse;
56// `cast` stays crate-private (not part of the public API); re-import it so
57// `crate::cast::As` keeps resolving across the facade and render modules.
58pub use crate::types::{Color, EcLevel, Mode, QrError, QrResult, Version};
59pub use qrcode_core::QrCodeRef;
60use qrcode_core::cast;
61pub use qrcode_core::traits::{
62    Builder, Encoder, ModuleSource, ModuleStorage, ModuleView, QrSymbol, Renderer as CoreRenderer,
63};
64
65#[cfg(not(feature = "std"))]
66#[allow(unused_imports)]
67use alloc::{
68    borrow::ToOwned,
69    format,
70    string::{String, ToString},
71    vec,
72    vec::Vec,
73};
74
75use crate::cast::As;
76use crate::render::{Pixel, Renderer};
77use core::iter::FusedIterator;
78use core::ops::Index;
79
80/// The encoded QR code symbol.
81///
82/// `QrCode` is `Send + Sync`, so it can be shared or moved across threads
83/// (e.g. for parallel rendering of many codes). This is verified at compile
84/// time below.
85#[derive(Clone)]
86pub struct QrCode {
87    content: Vec<Color>,
88    version: Version,
89    ec_level: EcLevel,
90    width: usize,
91    mask_pattern: Option<canvas::MaskPattern>,
92    mask_penalty_score: Option<u16>,
93    encoding_modes: EncodingModes,
94    remaining_capacity_bits: Option<usize>,
95}
96
97/// Borrowed plugin view for a QR code.
98///
99/// This is a convenience wrapper around [`QrCode::render_with`] for code that
100/// wants to bind a symbol and registry once, then render through one or more
101/// named plugin renderers.
102pub struct QrCodePlugins<'a> {
103    code: &'a QrCode,
104    registry: &'a PluginRegistry,
105}
106
107impl QrCodePlugins<'_> {
108    /// Returns the QR code bound to this plugin view.
109    #[must_use]
110    pub fn code(&self) -> &QrCode {
111        self.code
112    }
113
114    /// Returns the plugin registry bound to this plugin view.
115    #[must_use]
116    pub fn registry(&self) -> &PluginRegistry {
117        self.registry
118    }
119
120    /// Renders the bound QR code through a named plugin renderer.
121    ///
122    /// # Errors
123    ///
124    /// Returns [`PluginError::RendererNotFound`] when no renderer is registered
125    /// with `renderer_name`, or another [`PluginError`] from grid construction,
126    /// postprocessing, or rendering.
127    pub fn render(&self, renderer_name: &str, config: &RenderConfig) -> Result<RenderOutput, PluginError> {
128        self.code.render_with(self.registry, renderer_name, config)
129    }
130}
131
132// Compile-time guarantee that QrCode stays Send + Sync as fields evolve.
133const _: () = {
134    const fn assert_send_sync<T: Send + Sync>() {}
135    assert_send_sync::<QrCode>();
136};
137
138impl QrCode {
139    /// Constructs a new QR code which automatically encodes the given data.
140    ///
141    /// This method uses the "medium" error correction level and automatically
142    /// chooses the smallest QR code.
143    ///
144    ///     use qrcode_rs::QrCode;
145    ///
146    ///     let code = QrCode::new(b"Some data").unwrap();
147    ///
148    /// # Errors
149    ///
150    /// Returns error if the QR code cannot be constructed, e.g. when the data
151    /// is too long.
152    pub fn new<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
153        AutoEncoder::default().encode(data.as_ref())
154    }
155
156    /// Constructs a new QR code through the explicit deterministic API.
157    ///
158    /// The encoder is deterministic by design: it uses no random seed or
159    /// process-global state when selecting versions, modes, masks, or rendered
160    /// modules. This feature-gated helper gives audit-sensitive callers a
161    /// stable, named entry point for that contract.
162    ///
163    /// # Errors
164    ///
165    /// Returns error if the QR code cannot be constructed, e.g. when the data
166    /// is too long.
167    #[cfg(feature = "deterministic")]
168    pub fn new_deterministic<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
169        Self::new(data)
170    }
171
172    /// Constructs a new QR code which automatically encodes the given data at a
173    /// specific error correction level.
174    ///
175    /// This method automatically chooses the smallest QR code.
176    ///
177    ///     use qrcode_rs::{QrCode, EcLevel};
178    ///
179    ///     let code = QrCode::with_error_correction_level(b"Some data", EcLevel::H).unwrap();
180    ///
181    /// # Errors
182    ///
183    /// Returns error if the QR code cannot be constructed, e.g. when the data
184    /// is too long.
185    pub fn with_error_correction_level<D: AsRef<[u8]>>(data: D, ec_level: EcLevel) -> QrResult<Self> {
186        AutoEncoder::new(ec_level).encode(data.as_ref())
187    }
188
189    /// Constructs a QR code with an explicit input, version, and symbol-size
190    /// budget.
191    ///
192    /// The default [`QrCode::new`] constructor remains source-compatible and
193    /// uses the library's bounded defaults. This method is useful at trust
194    /// boundaries where an application needs a stricter per-request budget.
195    /// Input length is checked before parser allocation; the selected version
196    /// and module dimensions are checked before returning the symbol.
197    ///
198    /// # Errors
199    ///
200    /// Returns [`QrError::InvalidResourceLimits`] for a malformed budget,
201    /// [`QrError::DataTooLong`] when the input cannot fit within the data or
202    /// version budget, and [`QrError::RenderSizeExceeded`] when the resulting
203    /// module dimensions exceed `max_render_size`.
204    pub fn with_limits<D: AsRef<[u8]>>(data: D, limits: ResourceLimits) -> QrResult<Self> {
205        limits.validate()?;
206        #[cfg(feature = "std")]
207        let started_at = std::time::Instant::now();
208        let data = data.as_ref();
209        if data.len() > limits.max_data_length {
210            return Err(QrError::DataTooLong);
211        }
212        let max_version = match limits.max_version {
213            Version::Normal(version) => version,
214            // `validate` above rejects Micro versions. Keeping this branch
215            // explicit makes the invariant obvious if validation changes.
216            Version::Micro(_) => return Err(QrError::InvalidResourceLimits),
217        };
218        let bits = bits::encode_auto_with_max_version(data, EcLevel::M, max_version)?;
219        #[cfg(feature = "std")]
220        if limits
221            .encoding_timeout
222            .is_some_and(|timeout_ms| started_at.elapsed() > std::time::Duration::from_millis(timeout_ms))
223        {
224            return Err(QrError::EncodingTimeout);
225        }
226        let code = Self::with_bits(bits, EcLevel::M)?;
227        #[cfg(feature = "std")]
228        if limits
229            .encoding_timeout
230            .is_some_and(|timeout_ms| started_at.elapsed() > std::time::Duration::from_millis(timeout_ms))
231        {
232            return Err(QrError::EncodingTimeout);
233        }
234        let width = u32::try_from(code.width).map_err(|_| QrError::RenderSizeExceeded {
235            width: u32::MAX,
236            height: u32::MAX,
237            max_width: limits.max_render_size.0,
238            max_height: limits.max_render_size.1,
239        })?;
240        if width > limits.max_render_size.0 || width > limits.max_render_size.1 {
241            return Err(QrError::RenderSizeExceeded {
242                width,
243                height: width,
244                max_width: limits.max_render_size.0,
245                max_height: limits.max_render_size.1,
246            });
247        }
248        Ok(code)
249    }
250
251    /// Constructs a new Micro QR code which automatically encodes the given
252    /// data.
253    ///
254    /// This method uses the "medium" error correction level and automatically
255    /// chooses the smallest Micro QR code.
256    ///
257    ///     use qrcode_rs::QrCode;
258    ///
259    ///     let code = QrCode::new_micro(b"123").unwrap();
260    ///
261    /// # Errors
262    ///
263    /// Returns error if the data cannot be encoded as a Micro QR code, e.g.
264    /// when the data is too long.
265    pub fn new_micro<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
266        MicroEncoder::default().encode(data.as_ref())
267    }
268
269    /// Constructs a new Micro QR code which automatically encodes the given
270    /// data at a specific error correction level.
271    ///
272    /// This method automatically chooses the smallest Micro QR code.
273    ///
274    ///     use qrcode_rs::{QrCode, EcLevel};
275    ///
276    ///     let code = QrCode::micro_with_error_correction_level(b"123", EcLevel::L).unwrap();
277    ///
278    /// # Errors
279    ///
280    /// Returns error if the data cannot be encoded as a Micro QR code, e.g.
281    /// when the data is too long, or when the error correction level is not
282    /// supported by any Micro QR version.
283    pub fn micro_with_error_correction_level<D: AsRef<[u8]>>(data: D, ec_level: EcLevel) -> QrResult<Self> {
284        MicroEncoder::new(ec_level).encode(data.as_ref())
285    }
286
287    /// Constructs a new QR code for the given version and error correction
288    /// level.
289    ///
290    ///     use qrcode_rs::{QrCode, Version, EcLevel};
291    ///
292    ///     let code = QrCode::with_version(b"Some data", Version::Normal(5), EcLevel::M).unwrap();
293    ///
294    /// This method can also be used to generate Micro QR code.
295    ///
296    ///     use qrcode_rs::{QrCode, Version, EcLevel};
297    ///
298    ///     let micro_code = QrCode::with_version(b"123", Version::Micro(1), EcLevel::L).unwrap();
299    ///
300    /// # Errors
301    ///
302    /// Returns error if the QR code cannot be constructed, e.g. when the data
303    /// is too long, or when the version and error correction level are
304    /// incompatible.
305    pub fn with_version<D: AsRef<[u8]>>(data: D, version: Version, ec_level: EcLevel) -> QrResult<Self> {
306        VersionEncoder::new(version, ec_level).encode(data.as_ref())
307    }
308
309    /// Constructs a new QR code with a compile-time checked normal QR version.
310    ///
311    /// `N` must be in `1..=40`. Invalid values fail during const evaluation
312    /// when this fixed-version path is monomorphized.
313    ///
314    /// ```rust
315    /// use qrcode_rs::{EcLevel, QrCode, Version};
316    ///
317    /// let code = QrCode::with_const_version::<5, _>(b"Some data", EcLevel::M).unwrap();
318    /// assert_eq!(code.version(), Version::Normal(5));
319    /// ```
320    ///
321    /// ```compile_fail
322    /// use qrcode_rs::{EcLevel, QrCode};
323    ///
324    /// let _ = QrCode::with_const_version::<41, _>(b"Some data", EcLevel::M);
325    /// ```
326    ///
327    /// # Errors
328    ///
329    /// Returns error if the QR code cannot be constructed for version `N`, e.g.
330    /// when the data is too long for that version and error correction level.
331    pub fn with_const_version<const N: i16, D: AsRef<[u8]>>(data: D, ec_level: EcLevel) -> QrResult<Self> {
332        ConstVersionEncoder::<N>::new(ec_level).encode(data.as_ref())
333    }
334
335    fn encode_auto(data: &[u8], ec_level: EcLevel) -> QrResult<Self> {
336        let bits = bits::encode_auto(data, ec_level)?;
337        Self::with_bits(bits, ec_level)
338    }
339
340    fn encode_auto_micro(data: &[u8], ec_level: EcLevel) -> QrResult<Self> {
341        let bits = bits::encode_auto_micro(data, ec_level)?;
342        Self::with_bits(bits, ec_level)
343    }
344
345    fn encode_with_version(data: &[u8], version: Version, ec_level: EcLevel) -> QrResult<Self> {
346        Self::validate_input_length(data)?;
347        let mut bits = bits::Bits::new(version);
348        bits.push_optimal_data(data)?;
349        bits.push_terminator(ec_level)?;
350        Self::with_bits(bits, ec_level)
351    }
352
353    /// Constructs a new QR code with encoded bits.
354    ///
355    /// Use this method only if there are very special need to manipulate the
356    /// raw bits before encoding. Some examples are:
357    ///
358    /// * Encode data using specific character set with ECI
359    /// * Use the FNC1 modes
360    /// * Avoid the optimal segmentation algorithm
361    ///
362    /// See the `Bits` structure for detail.
363    ///
364    ///     #![allow(unused_must_use)]
365    ///
366    ///     use qrcode_rs::{QrCode, Version, EcLevel};
367    ///     use qrcode_rs::bits::Bits;
368    ///
369    ///     let mut bits = Bits::new(Version::Normal(1));
370    ///     bits.push_eci_designator(9);
371    ///     bits.push_byte_data(b"\xca\xfe\xe4\xe9\xea\xe1\xf2 QR");
372    ///     bits.push_terminator(EcLevel::L);
373    ///     let qrcode = QrCode::with_bits(bits, EcLevel::L);
374    ///
375    /// # Errors
376    ///
377    /// Returns error if the QR code cannot be constructed, e.g. when the bits
378    /// are too long, or when the version and error correction level are
379    /// incompatible.
380    pub fn with_bits(bits: bits::Bits, ec_level: EcLevel) -> QrResult<Self> {
381        let version = bits.version();
382        let encoding_modes = bits.encoding_modes();
383        let remaining_capacity_bits = bits.remaining_capacity_bits(ec_level)?;
384        #[cfg(feature = "log")]
385        log::debug!("qrcode_rs: encoding at version {version:?}, ec {ec_level:?}");
386        let data = bits.into_bytes();
387        let (encoded_data, ec_data) = ec::construct_codewords(&data, version, ec_level)?;
388        let mut canvas = canvas::Canvas::new(version, ec_level);
389        canvas.draw_all_functional_patterns();
390        canvas.draw_data(&encoded_data, &ec_data);
391        let (canvas, mask_pattern, mask_penalty_score) = canvas.apply_best_mask_with_score();
392        let width = version.width().as_usize();
393        #[cfg(feature = "log")]
394        log::info!("qrcode_rs: encoded version {version:?} ec {ec_level:?} ({} modules)", width * width);
395        Ok(Self {
396            content: canvas.into_colors(),
397            version,
398            ec_level,
399            width,
400            mask_pattern: Some(mask_pattern),
401            mask_penalty_score: Some(mask_penalty_score),
402            encoding_modes,
403            remaining_capacity_bits: Some(remaining_capacity_bits),
404        })
405    }
406
407    /// Encodes many inputs at once at the given error-correction level, stopping
408    /// at the first input that fails to encode.
409    ///
410    /// # Examples
411    ///
412    /// ```rust
413    /// use qrcode_rs::{QrCode, EcLevel};
414    ///
415    /// let codes = QrCode::batch(&["alpha", "beta", "gamma"], EcLevel::M).unwrap();
416    /// assert_eq!(codes.len(), 3);
417    /// ```
418    pub fn batch<I, D>(inputs: I, ec_level: EcLevel) -> QrResult<Vec<Self>>
419    where
420        I: IntoIterator<Item = D>,
421        D: AsRef<[u8]>,
422    {
423        Self::stream_with_error_correction_level(inputs, ec_level).collect()
424    }
425
426    /// Creates a library-level batch builder for encoding, rendering, and
427    /// packaging named outputs.
428    ///
429    /// This convenience entry point is available with the `std` feature. It is
430    /// useful when callers want stable output names plus helpers such as ZIP
431    /// archives or PNG contact sheets without invoking the CLI.
432    ///
433    /// # Examples
434    ///
435    /// ```rust
436    /// use qrcode_rs::QrCode;
437    ///
438    /// let rendered = QrCode::batch_builder(["alpha", "beta"])
439    ///     .file_extension("txt")
440    ///     .render::<char>()
441    ///     .unwrap();
442    ///
443    /// assert_eq!(rendered.entries()[0].name(), "qr-0001.txt");
444    /// ```
445    #[cfg(feature = "std")]
446    #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
447    pub fn batch_builder<I>(inputs: I) -> batch::QrBatchBuilder<I> {
448        batch::QrBatchBuilder::new(inputs)
449    }
450
451    /// Creates a builder for rendering an already encoded batch into stable,
452    /// named in-memory outputs.
453    ///
454    /// This keeps existing encoded symbols reusable when callers need multiple
455    /// render styles or want to decide packaging separately with the
456    /// [`batch`](crate::batch) module.
457    ///
458    /// ```
459    /// use qrcode_rs::{EcLevel, QrCode};
460    ///
461    /// let codes = QrCode::batch(["alpha", "beta"], EcLevel::M)?;
462    /// let rendered = QrCode::batch_render(&codes)
463    ///     .extension("txt")
464    ///     .build::<char>();
465    ///
466    /// assert_eq!(rendered[0].name(), "qr-0001.txt");
467    /// # Ok::<(), qrcode_rs::QrError>(())
468    /// ```
469    #[must_use]
470    pub fn batch_render(codes: &[Self]) -> BatchRender<'_> {
471        BatchRender::new(codes)
472    }
473
474    /// Encodes many inputs in parallel at the given error-correction level.
475    ///
476    /// This method is available with the `parallel` feature. It preserves input
477    /// order in the returned vector and returns the first encoding error in
478    /// that same order. Small batches should continue to use
479    /// [`batch`](Self::batch); this is intended for larger, CPU-bound batches.
480    ///
481    /// # Examples
482    ///
483    /// ```rust
484    /// # #[cfg(feature = "parallel")]
485    /// # {
486    /// use qrcode_rs::{EcLevel, QrCode};
487    ///
488    /// let codes = QrCode::par_batch(vec!["alpha", "beta", "gamma"], EcLevel::M).unwrap();
489    /// assert_eq!(codes.len(), 3);
490    /// # }
491    /// ```
492    #[cfg(feature = "parallel")]
493    pub fn par_batch<I, D>(inputs: I, ec_level: EcLevel) -> QrResult<Vec<Self>>
494    where
495        I: rayon::iter::IntoParallelIterator<Item = D>,
496        I::Iter: rayon::iter::IndexedParallelIterator,
497        D: AsRef<[u8]> + Send,
498    {
499        use rayon::prelude::*;
500
501        inputs
502            .into_par_iter()
503            .map(|data| Self::with_error_correction_level(data, ec_level))
504            .collect::<Vec<_>>()
505            .into_iter()
506            .collect()
507    }
508
509    /// Lazily encodes inputs into QR codes with the default error-correction level.
510    ///
511    /// Unlike [`batch`](Self::batch), this returns an iterator and does not
512    /// collect every generated code into memory.
513    ///
514    /// # Examples
515    ///
516    /// ```rust
517    /// use qrcode_rs::{EcLevel, QrCode};
518    ///
519    /// let widths = QrCode::stream(["alpha", "beta"])
520    ///     .map(|code| code.map(|code| (code.error_correction_level(), code.width())))
521    ///     .collect::<Result<Vec<_>, _>>()
522    ///     .unwrap();
523    ///
524    /// assert_eq!(widths, vec![(EcLevel::M, 21), (EcLevel::M, 21)]);
525    /// ```
526    pub fn stream<I, D>(inputs: I) -> QrCodeStream<I::IntoIter>
527    where
528        I: IntoIterator<Item = D>,
529        D: AsRef<[u8]>,
530    {
531        Self::stream_with_error_correction_level(inputs, EcLevel::M)
532    }
533
534    /// Lazily encodes inputs into QR codes at `ec_level`.
535    ///
536    /// The iterator yields one [`QrResult<QrCode>`] per input, so callers can
537    /// stop at the first error with `collect::<Result<Vec<_>, _>>()` or handle
538    /// errors per item while keeping memory usage bounded by the active item.
539    pub fn stream_with_error_correction_level<I, D>(inputs: I, ec_level: EcLevel) -> QrCodeStream<I::IntoIter>
540    where
541        I: IntoIterator<Item = D>,
542        D: AsRef<[u8]>,
543    {
544        QrCodeStream { inputs: inputs.into_iter(), ec_level }
545    }
546
547    /// Gets the version of this QR code.
548    pub const fn version(&self) -> Version {
549        self.version
550    }
551
552    /// Gets the error correction level of this QR code.
553    pub const fn error_correction_level(&self) -> EcLevel {
554        self.ec_level
555    }
556
557    /// Gets the number of modules per side, i.e. the width of this QR code.
558    ///
559    /// The width here does not contain the quiet zone paddings.
560    pub const fn width(&self) -> usize {
561        self.width
562    }
563
564    /// Gets the maximum number of allowed erratic modules can be introduced
565    /// before the data becomes corrupted. Note that errors should not be
566    /// introduced to functional modules.
567    pub fn max_allowed_errors(&self) -> usize {
568        ec::max_allowed_errors(self.version, self.ec_level).expect("invalid version or ec_level")
569    }
570
571    /// Returns metadata about this QR code (version, error-correction level,
572    /// dimensions, module count, error tolerance, and data capacity).
573    ///
574    /// # Examples
575    ///
576    /// ```rust
577    /// use qrcode_rs::QrCode;
578    ///
579    /// let code = QrCode::new(b"hello").unwrap();
580    /// let info = code.info();
581    /// assert_eq!(info.width(), code.width());
582    /// assert_eq!(info.module_count(), code.width() * code.width());
583    /// assert!(info.data_capacity_bytes() > 0);
584    /// ```
585    #[must_use]
586    pub fn info(&self) -> Info {
587        Info {
588            version: self.version,
589            ec_level: self.ec_level,
590            width: self.width,
591            module_count: self.width * self.width,
592            max_allowed_errors: self.max_allowed_errors(),
593            data_capacity_bytes: bits::data_capacity_bits(self.version, self.ec_level).map(|b| b / 8).unwrap_or(0),
594            mask_pattern: self.mask_pattern,
595            mask_penalty_score: self.mask_penalty_score,
596            encoding_modes: self.encoding_modes,
597            remaining_capacity_bits: self.remaining_capacity_bits,
598        }
599    }
600
601    /// Returns diagnostic stats for this code: dark-module ratio and the split
602    /// between functional and data modules. Combine with [`QrCode::info`] for
603    /// version / capacity. Computed on demand (scans the grid).
604    ///
605    /// # Examples
606    ///
607    /// ```rust
608    /// use qrcode_rs::QrCode;
609    ///
610    /// let code = QrCode::new(b"hello").unwrap();
611    /// let a = code.analyze();
612    /// assert!(a.dark_ratio() > 0.0 && a.dark_ratio() < 1.0);
613    /// assert_eq!(a.functional_modules() + a.data_modules(), code.width() * code.width());
614    /// ```
615    #[must_use]
616    pub fn analyze(&self) -> Analysis {
617        let total = self.width * self.width;
618        let dark = self.content.iter().filter(|c| **c == Color::Dark).count();
619        let functional =
620            (0..self.width).map(|y| (0..self.width).filter(|x| self.is_functional(*x, y)).count()).sum::<usize>();
621        Analysis {
622            dark_ratio: if total == 0 { 0.0 } else { dark as f64 / total as f64 },
623            functional_modules: functional,
624            data_modules: total - functional,
625        }
626    }
627
628    /// Checks whether a module at coordinate (x, y) is a functional module or
629    /// not.
630    pub fn is_functional(&self, x: usize, y: usize) -> bool {
631        let x = x.try_into().expect("coordinate is too large for QR code");
632        let y = y.try_into().expect("coordinate is too large for QR code");
633        canvas::is_functional(self.version, self.version.width(), x, y)
634    }
635
636    /// Converts the QR code into a human-readable string. This is mainly for
637    /// debugging only.
638    pub fn to_debug_str(&self, on_char: char, off_char: char) -> String {
639        self.render().quiet_zone(false).dark_color(on_char).light_color(off_char).build()
640    }
641
642    /// Returns the module colors as a borrowed slice — no allocation. Use this
643    /// in preference to [`to_colors`](Self::to_colors) when you only need to
644    /// read the modules.
645    ///
646    /// The slice is row-major, with `width() * width()` entries and no quiet
647    /// zone.
648    ///
649    /// # Examples
650    ///
651    /// ```rust
652    /// use qrcode_rs::QrCode;
653    ///
654    /// let code = QrCode::new(b"hi").unwrap();
655    /// let colors = code.colors();
656    /// assert_eq!(colors.len(), code.width() * code.width());
657    /// ```
658    pub fn colors(&self) -> &[Color] {
659        &self.content
660    }
661
662    /// Returns a borrowed, read-only module-grid view.
663    ///
664    /// This is the facade-friendly [`ModuleSource`] adapter for APIs that accept
665    /// a borrowed QR module source without needing ownership of the full
666    /// [`QrCode`].
667    ///
668    /// # Examples
669    ///
670    /// ```rust
671    /// use qrcode_rs::{ModuleSource, QrCode};
672    ///
673    /// let code = QrCode::new(b"hi").unwrap();
674    /// let view = code.module_view();
675    /// assert_eq!(view.width(), code.width());
676    /// assert_eq!(view.modules(), code.colors());
677    /// ```
678    #[must_use]
679    pub fn module_view(&self) -> ModuleView<'_> {
680        ModuleView::new(&self.content, self.width).expect("QrCode stores a non-empty square module grid")
681    }
682
683    /// Returns a borrowed QR symbol view with module data and metadata.
684    ///
685    /// Unlike [`to_colors`](Self::to_colors), this does not allocate or clone
686    /// the module grid. It is useful for renderers and analyzers that accept a
687    /// [`QrSymbol`] and need version/error-correction metadata in addition to
688    /// read-only module access.
689    ///
690    /// # Examples
691    ///
692    /// ```rust
693    /// use qrcode_rs::{ModuleSource, QrCode, QrSymbol};
694    ///
695    /// let code = QrCode::new(b"hi").unwrap();
696    /// let borrowed = code.as_ref();
697    /// assert_eq!(borrowed.version(), code.version());
698    /// assert_eq!(borrowed.modules(), code.colors());
699    /// ```
700    #[must_use]
701    pub fn as_ref(&self) -> QrCodeRef<'_> {
702        QrCodeRef::new(&self.content, self.width, self.version, self.ec_level)
703            .expect("QrCode stores a non-empty square module grid")
704    }
705
706    /// Converts the QR code to a vector of colors.
707    pub fn to_colors(&self) -> Vec<Color> {
708        self.content.clone()
709    }
710
711    /// Converts the QR code to a vector of colors.
712    pub fn into_colors(self) -> Vec<Color> {
713        self.content
714    }
715
716    /// Renders the QR code into an image. The result is an image builder, which
717    /// you may do some additional configuration before copying it into a
718    /// concrete image.
719    ///  Note: the`image` crate itself also provides method to rotate the image,
720    /// or overlay a logo on top of the QR code.
721    /// # Examples
722    ///
723    #[cfg_attr(feature = "image", doc = " ```rust")]
724    #[cfg_attr(not(feature = "image"), doc = " ```ignore")]
725    /// # use qrcode_rs::QrCode;
726    /// # use image::Rgb;
727    ///
728    /// let image = QrCode::new(b"hello").unwrap()
729    ///                     .render()
730    ///                     .dark_color(Rgb([0, 0, 128]))
731    ///                     .light_color(Rgb([224, 224, 224])) // adjust colors
732    ///                     .quiet_zone(false)          // disable quiet zone (white border)
733    ///                     .min_dimensions(300, 300)   // sets minimum image size
734    ///                     .build();
735    /// ```
736    ///
737    pub fn render<P: Pixel>(&self) -> Renderer<'_, P> {
738        Renderer::from_symbol(self)
739    }
740
741    /// Returns the render builder for this QR code.
742    ///
743    /// This is an explicit builder-style alias for [`render`](Self::render),
744    /// useful when code wants the construction and rendering paths to read the
745    /// same way (`QrCode::builder(...).build()?.render_builder::<P>()...`).
746    ///
747    /// # Examples
748    ///
749    /// ```rust
750    /// use qrcode_rs::QrCode;
751    ///
752    /// let text = QrCode::new(b"hello").unwrap()
753    ///     .render_builder::<char>()
754    ///     .dark_color('#')
755    ///     .quiet_zone(false)
756    ///     .module_dimensions(1, 1)
757    ///     .build();
758    ///
759    /// assert!(text.contains('#'));
760    /// ```
761    pub fn render_builder<P: Pixel>(&self) -> Renderer<'_, P> {
762        self.render()
763    }
764
765    /// Renders the QR code on Tokio's blocking thread pool.
766    ///
767    /// This opt-in async helper is available with the `async` feature. It clones
768    /// the compact module grid and performs the synchronous render work inside
769    /// [`tokio::task::spawn_blocking`], so callers running inside a Tokio
770    /// runtime do not execute CPU-heavy rendering on the async worker thread.
771    ///
772    /// # Errors
773    ///
774    /// Returns [`tokio::task::JoinError`] if the blocking task is cancelled or
775    /// panics.
776    ///
777    /// # Examples
778    ///
779    /// ```rust
780    /// use qrcode_rs::QrCode;
781    ///
782    /// let code = QrCode::new(b"hello").unwrap();
783    /// let runtime = tokio::runtime::Builder::new_current_thread().build().unwrap();
784    /// let text = runtime.block_on(code.render_async::<char>()).unwrap();
785    /// assert!(!text.is_empty());
786    /// ```
787    #[cfg(feature = "async")]
788    #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
789    pub async fn render_async<P>(&self) -> Result<P::Image, tokio::task::JoinError>
790    where
791        P: Pixel + Send + 'static,
792        P::Image: Send + 'static,
793    {
794        let code = self.clone();
795        tokio::task::spawn_blocking(move || code.render::<P>().build()).await
796    }
797
798    /// Encodes raw input through a named plugin encoder.
799    ///
800    /// This is the encoder-side counterpart to [`QrCode::render_with`]: it
801    /// looks up `encoder_name` in `registry`, builds the dynamic encoder with
802    /// `config`, and returns the encoder's type-erased output.
803    ///
804    /// # Errors
805    ///
806    /// Returns [`PluginError::EncoderNotFound`] when no encoder is registered
807    /// with `encoder_name`, or another [`PluginError`] returned by the encoder.
808    pub fn encode_with(
809        registry: &PluginRegistry,
810        encoder_name: &str,
811        input: &[u8],
812        config: &EncodeConfig,
813    ) -> Result<EncodedOutput, PluginError> {
814        registry.build_encoder(encoder_name, config)?.encode(input)
815    }
816
817    /// Renders the QR code through a named plugin renderer.
818    ///
819    /// The method looks up `renderer_name` in `registry`, copies this QR code's
820    /// module grid into a mutable [`ModuleGrid`], applies all registered
821    /// [`PostProcessor`] values in registration order, then renders the
822    /// transformed grid through the selected dynamic renderer.
823    ///
824    /// # Errors
825    ///
826    /// Returns [`PluginError::RendererNotFound`] when no renderer is registered
827    /// with `renderer_name`, or another [`PluginError`] from grid construction,
828    /// postprocessing, or rendering.
829    pub fn render_with(
830        &self,
831        registry: &PluginRegistry,
832        renderer_name: &str,
833        config: &RenderConfig,
834    ) -> Result<RenderOutput, PluginError> {
835        let mut modules = ModuleGrid::new(self.content.clone(), self.width, self.width)?;
836        registry.process_modules(&mut modules)?;
837        registry.build_renderer(renderer_name, config)?.render(&modules)
838    }
839
840    /// Binds this QR code to a plugin registry for fluent plugin rendering.
841    ///
842    /// The returned view borrows both values and delegates to
843    /// [`QrCode::render_with`], so it has the same deterministic, explicit
844    /// registry behavior without introducing global plugin state.
845    pub fn with_plugins<'a>(&'a self, registry: &'a PluginRegistry) -> QrCodePlugins<'a> {
846        QrCodePlugins { code: self, registry }
847    }
848}
849
850impl QrCode {
851    /// Creates a [`QrCodeBuilder`] for configuring and constructing a QR code.
852    ///
853    /// This is an ergonomic alternative to the `with_*` constructors. The
854    /// builder uses the same encoding paths, so its output is identical to the
855    /// equivalent constructor.
856    ///
857    /// # Examples
858    ///
859    /// ```rust
860    /// use qrcode_rs::{QrCode, EcLevel};
861    ///
862    /// let code = QrCode::builder(b"https://example.com")
863    ///     .ec_level(EcLevel::H)
864    ///     .build()
865    ///     .unwrap();
866    /// # let _ = code;
867    /// ```
868    pub fn builder<D: AsRef<[u8]>>(data: D) -> QrCodeBuilder<D> {
869        QrCodeBuilder::new(data)
870    }
871
872    /// Returns an iterator yielding one [`Row`] of modules at a time.
873    ///
874    /// Each row iterates over the module [`Color`]s from left to right. The
875    /// quiet zone is *not* included.
876    ///
877    /// # Examples
878    ///
879    /// ```rust
880    /// use qrcode_rs::QrCode;
881    ///
882    /// let code = QrCode::new(b"hi").unwrap();
883    /// for row in code.rows() {
884    ///     for color in row {
885    ///         # let _ = color;
886    ///     }
887    /// }
888    /// ```
889    pub fn rows(&self) -> Rows<'_> {
890        Rows { code: self, y: 0 }
891    }
892
893    /// Returns an iterator over the `(x, y)` coordinates of every dark module,
894    /// convenient for custom rendering. The quiet zone is *not* included.
895    ///
896    /// # Examples
897    ///
898    /// ```rust
899    /// use qrcode_rs::QrCode;
900    ///
901    /// let code = QrCode::new(b"hi").unwrap();
902    /// let dark_count = code.dark_modules().count();
903    /// # let _ = dark_count;
904    /// ```
905    pub fn dark_modules(&self) -> DarkModules<'_> {
906        DarkModules { code: self, idx: 0 }
907    }
908
909    /// Encodes a URL, using high error correction (robust to print damage).
910    ///
911    /// # Errors
912    ///
913    /// Returns an error only if the URL is too long to encode.
914    pub fn for_url<D: AsRef<[u8]>>(url: D) -> QrResult<Self> {
915        Self::with_error_correction_level(url, EcLevel::H)
916    }
917
918    /// Encodes plain text at the default (medium) error correction level.
919    ///
920    /// # Errors
921    ///
922    /// Returns an error only if the text is too long to encode.
923    pub fn for_text<D: AsRef<[u8]>>(text: D) -> QrResult<Self> {
924        Self::new(text)
925    }
926
927    /// Encodes a WiFi configuration that most phone cameras will offer to join.
928    ///
929    /// `auth` is one of `WPA`, `WEP` or `nopass`. Special characters in the
930    /// SSID/password are backslash-escaped per the WiFi QR specification.
931    ///
932    /// # Errors
933    ///
934    /// Returns an error if the resulting payload is too long to encode.
935    ///
936    /// # Examples
937    ///
938    /// ```rust
939    /// use qrcode_rs::QrCode;
940    ///
941    /// let code = QrCode::for_wifi("MyNetwork", "p\\a;ss", "WPA").unwrap();
942    /// # let _ = code;
943    /// ```
944    pub fn for_wifi(ssid: &str, password: &str, auth: &str) -> QrResult<Self> {
945        Self::new(parse::wifi::encode_wifi(ssid, password, auth))
946    }
947
948    /// Encodes a minimal vCard 3.0 contact card.
949    ///
950    /// # Errors
951    ///
952    /// Returns an error if the resulting payload is too long to encode.
953    ///
954    /// # Examples
955    ///
956    /// ```rust
957    /// use qrcode_rs::QrCode;
958    ///
959    /// let code = QrCode::for_vcard("John Doe", "+1234567890", "john@example.com").unwrap();
960    /// # let _ = code;
961    /// ```
962    pub fn for_vcard(name: &str, phone: &str, email: &str) -> QrResult<Self> {
963        Self::new(parse::vcard::encode_vcard(name, phone, email))
964    }
965
966    /// Encodes a GS1 data carrier (FNC1 in first position), e.g. a GTIN /
967    /// application-identifier payload such as
968    /// `"010491234512345915970331301234561842"`. Uses medium error correction
969    /// and the smallest fitting version.
970    ///
971    /// # Errors
972    ///
973    /// Returns an error if the data is too long to encode.
974    ///
975    /// # Examples
976    ///
977    /// ```rust
978    /// use qrcode_rs::QrCode;
979    ///
980    /// let code = QrCode::for_gs1("010491234512345915970331301234561842").unwrap();
981    /// # let _ = code;
982    /// ```
983    pub fn for_gs1<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
984        let data = data.as_ref();
985        Self::validate_input_length(data)?;
986        for v in 1..=40 {
987            let version = Version::Normal(v);
988            let mut bits = bits::Bits::new(version);
989            if bits.push_fnc1_first_position().is_err()
990                || bits.push_optimal_data(data).is_err()
991                || bits.push_terminator(EcLevel::M).is_err()
992            {
993                continue;
994            }
995            return Self::with_bits(bits, EcLevel::M);
996        }
997        Err(QrError::DataTooLong)
998    }
999
1000    /// Splits `payload` across `symbols` QR codes (2..=16) using Structured
1001    /// Append (ISO/IEC 18004 §7.4), each at error-correction level `ec`. Every
1002    /// symbol is the smallest version that fits its chunk plus the 20-bit
1003    /// Structured Append header.
1004    ///
1005    /// This is a thin convenience over
1006    /// [`crate::structured_append::StructuredAppend`]; see that type for the
1007    /// split and parity details, and [`crate::structured_append::reassemble`]
1008    /// for recombining decoded symbols.
1009    ///
1010    /// # Errors
1011    ///
1012    /// Returns [`QrError::InvalidStructuredAppend`] if `symbols` is not in
1013    /// `2..=16`, or [`QrError::DataTooLong`] if a chunk cannot fit even version
1014    /// 40 at `ec`.
1015    ///
1016    /// # Examples
1017    ///
1018    /// ```rust
1019    /// use qrcode_rs::{EcLevel, QrCode};
1020    ///
1021    /// let codes = QrCode::structured_append(b"split across multiple symbols", 3, EcLevel::M)?;
1022    /// assert_eq!(codes.len(), 3);
1023    /// # Ok::<(), qrcode_rs::QrError>(())
1024    /// ```
1025    pub fn structured_append<D: AsRef<[u8]>>(payload: D, symbols: u8, ec: EcLevel) -> QrResult<Vec<Self>> {
1026        let sa = structured_append::StructuredAppend::new(symbols, payload.as_ref())?;
1027        sa.encode(ec)
1028    }
1029
1030    /// Generates accessible alt text describing a QR code that encodes `data`.
1031    ///
1032    /// URLs are described as "linking to …"; other payloads as "containing: …".
1033    /// Use the result as the `alt` of an `<img>` or the `aria-label` of an inline
1034    /// SVG so assistive technology can describe the code without decoding it.
1035    ///
1036    /// This is an associated function (it does not require a constructed
1037    /// [`QrCode`]), so the input data does not need to be retained on the code.
1038    ///
1039    /// # Examples
1040    ///
1041    /// ```rust
1042    /// use qrcode_rs::QrCode;
1043    ///
1044    /// assert_eq!(QrCode::alt_text("https://example.com"), "QR code linking to https://example.com");
1045    /// assert_eq!(QrCode::alt_text("hello"), "QR code containing: hello");
1046    /// ```
1047    #[must_use]
1048    pub fn alt_text<D: AsRef<[u8]>>(data: D) -> String {
1049        let text = String::from_utf8_lossy(data.as_ref());
1050        if text.starts_with("http://") || text.starts_with("https://") {
1051            format!("QR code linking to {text}")
1052        } else {
1053            format!("QR code containing: {text}")
1054        }
1055    }
1056
1057    /// Generates alt text with a custom formatter that receives the raw bytes.
1058    ///
1059    /// # Examples
1060    ///
1061    /// ```rust
1062    /// use qrcode_rs::QrCode;
1063    ///
1064    /// let alt = QrCode::alt_text_custom("hello", |data| {
1065    ///     format!("A QR code with {} bytes", data.len())
1066    /// });
1067    /// assert_eq!(alt, "A QR code with 5 bytes");
1068    /// ```
1069    #[must_use]
1070    pub fn alt_text_custom<D: AsRef<[u8]>, F: FnOnce(&[u8]) -> String>(data: D, f: F) -> String {
1071        f(data.as_ref())
1072    }
1073
1074    /// Encodes `data` forced into a single `mode` at a pinned version. Used by
1075    /// [`QrCodeBuilder::build`] when both a version and an encoding-mode hint
1076    /// are set.
1077    fn with_mode<D: AsRef<[u8]>>(data: D, version: Version, ec_level: EcLevel, mode: Mode) -> QrResult<Self> {
1078        let data = data.as_ref();
1079        Self::validate_input_length(data)?;
1080        let mut bits = bits::Bits::new(version);
1081        match mode {
1082            Mode::Numeric => bits.push_numeric_data(data)?,
1083            Mode::Alphanumeric => bits.push_alphanumeric_data(data)?,
1084            Mode::Byte => bits.push_byte_data(data)?,
1085            Mode::Kanji => bits.push_kanji_data(data)?,
1086        }
1087        bits.push_terminator(ec_level)?;
1088        Self::with_bits(bits, ec_level)
1089    }
1090
1091    /// Encodes `data` forced into a single `mode`, auto-selecting the smallest
1092    /// fitting version. Used by [`QrCodeBuilder::build`] when an encoding-mode
1093    /// hint is set without a pinned version. Returns the underlying error
1094    /// (e.g. [`QrError::InvalidCharacter`]) if the data is incompatible with the
1095    /// forced mode.
1096    fn with_mode_auto<D: AsRef<[u8]>>(data: D, ec_level: EcLevel, mode: Mode) -> QrResult<Self> {
1097        let data = data.as_ref();
1098        Self::validate_input_length(data)?;
1099        let mut last_err = QrError::DataTooLong;
1100        for v in 1..=40 {
1101            let version = Version::Normal(v);
1102            let mut bits = bits::Bits::new(version);
1103            let pushed = match mode {
1104                Mode::Numeric => bits.push_numeric_data(data),
1105                Mode::Alphanumeric => bits.push_alphanumeric_data(data),
1106                Mode::Byte => bits.push_byte_data(data),
1107                Mode::Kanji => bits.push_kanji_data(data),
1108            };
1109            if let Err(e) = pushed {
1110                last_err = e;
1111                continue;
1112            }
1113            if let Err(e) = bits.push_terminator(ec_level) {
1114                last_err = e;
1115                continue;
1116            }
1117            return Self::with_bits(bits, ec_level);
1118        }
1119        Err(last_err)
1120    }
1121
1122    fn validate_input_length(data: &[u8]) -> QrResult<()> {
1123        if data.len() > qrcode_core::DEFAULT_MAX_DATA_LENGTH { Err(QrError::DataTooLong) } else { Ok(()) }
1124    }
1125}
1126
1127impl Index<(usize, usize)> for QrCode {
1128    type Output = Color;
1129
1130    fn index(&self, (x, y): (usize, usize)) -> &Color {
1131        let index = y * self.width + x;
1132        &self.content[index]
1133    }
1134}
1135
1136impl ModuleStorage for QrCode {
1137    fn get(&self, x: usize, y: usize) -> Color {
1138        self[(x, y)]
1139    }
1140
1141    fn set(&mut self, x: usize, y: usize, color: Color) {
1142        let index = y * self.width + x;
1143        self.content[index] = color;
1144    }
1145
1146    fn width(&self) -> usize {
1147        self.width
1148    }
1149
1150    fn height(&self) -> usize {
1151        self.width
1152    }
1153
1154    fn modules(&self) -> &[Color] {
1155        self.colors()
1156    }
1157}
1158
1159impl QrSymbol for QrCode {
1160    fn version(&self) -> Version {
1161        self.version
1162    }
1163
1164    fn error_correction_level(&self) -> EcLevel {
1165        self.ec_level
1166    }
1167}
1168
1169//------------------------------------------------------------------------------
1170//{{{ Encoder adapters
1171
1172/// Encoder adapter for automatically sized normal QR codes.
1173///
1174/// This is the trait-friendly counterpart to
1175/// [`QrCode::with_error_correction_level`].
1176#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1177pub struct AutoEncoder {
1178    ec_level: EcLevel,
1179}
1180
1181impl AutoEncoder {
1182    /// Creates an encoder with the given error-correction level.
1183    #[must_use]
1184    pub const fn new(ec_level: EcLevel) -> Self {
1185        Self { ec_level }
1186    }
1187
1188    /// Returns the configured error-correction level.
1189    #[must_use]
1190    pub const fn ec_level(&self) -> EcLevel {
1191        self.ec_level
1192    }
1193}
1194
1195impl Default for AutoEncoder {
1196    fn default() -> Self {
1197        Self { ec_level: EcLevel::M }
1198    }
1199}
1200
1201impl Encoder for AutoEncoder {
1202    type Output = QrCode;
1203    type Error = QrError;
1204
1205    fn encode(&self, input: &[u8]) -> QrResult<QrCode> {
1206        QrCode::encode_auto(input, self.ec_level)
1207    }
1208}
1209
1210/// Encoder adapter for automatically sized Micro QR codes.
1211///
1212/// This is the trait-friendly counterpart to
1213/// [`QrCode::micro_with_error_correction_level`].
1214#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1215pub struct MicroEncoder {
1216    ec_level: EcLevel,
1217}
1218
1219impl MicroEncoder {
1220    /// Creates a Micro QR encoder with the given error-correction level.
1221    #[must_use]
1222    pub const fn new(ec_level: EcLevel) -> Self {
1223        Self { ec_level }
1224    }
1225
1226    /// Returns the configured error-correction level.
1227    #[must_use]
1228    pub const fn ec_level(&self) -> EcLevel {
1229        self.ec_level
1230    }
1231}
1232
1233impl Default for MicroEncoder {
1234    fn default() -> Self {
1235        Self { ec_level: EcLevel::M }
1236    }
1237}
1238
1239impl Encoder for MicroEncoder {
1240    type Output = QrCode;
1241    type Error = QrError;
1242
1243    fn encode(&self, input: &[u8]) -> QrResult<QrCode> {
1244        QrCode::encode_auto_micro(input, self.ec_level)
1245    }
1246}
1247
1248/// Encoder adapter for a pinned [`Version`] and [`EcLevel`].
1249///
1250/// This is the trait-friendly counterpart to [`QrCode::with_version`].
1251#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1252pub struct VersionEncoder {
1253    version: Version,
1254    ec_level: EcLevel,
1255}
1256
1257impl VersionEncoder {
1258    /// Creates an encoder for a specific version and error-correction level.
1259    #[must_use]
1260    pub const fn new(version: Version, ec_level: EcLevel) -> Self {
1261        Self { version, ec_level }
1262    }
1263
1264    /// Returns the configured version.
1265    #[must_use]
1266    pub const fn version(&self) -> Version {
1267        self.version
1268    }
1269
1270    /// Returns the configured error-correction level.
1271    #[must_use]
1272    pub const fn ec_level(&self) -> EcLevel {
1273        self.ec_level
1274    }
1275}
1276
1277impl Encoder for VersionEncoder {
1278    type Output = QrCode;
1279    type Error = QrError;
1280
1281    fn encode(&self, input: &[u8]) -> QrResult<QrCode> {
1282        QrCode::encode_with_version(input, self.version, self.ec_level)
1283    }
1284}
1285
1286/// Encoder adapter for a compile-time checked normal QR version and [`EcLevel`].
1287///
1288/// This is the trait-friendly counterpart to
1289/// [`QrCode::with_const_version`]. `N` must be in `1..=40`.
1290///
1291/// ```rust
1292/// use qrcode_rs::{ConstVersionEncoder, EcLevel, Encoder, Version};
1293///
1294/// let code = ConstVersionEncoder::<5>::new(EcLevel::M).encode(b"Some data").unwrap();
1295/// assert_eq!(code.version(), Version::Normal(5));
1296/// ```
1297///
1298/// ```compile_fail
1299/// use qrcode_rs::{ConstVersionEncoder, EcLevel};
1300///
1301/// const INVALID: ConstVersionEncoder<0> = ConstVersionEncoder::<0>::new(EcLevel::M);
1302/// ```
1303#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1304pub struct ConstVersionEncoder<const N: i16> {
1305    ec_level: EcLevel,
1306}
1307
1308impl<const N: i16> ConstVersionEncoder<N> {
1309    /// Creates an encoder for fixed normal QR version `N`.
1310    #[must_use]
1311    pub const fn new(ec_level: EcLevel) -> Self {
1312        let _ = ConstVersion::<N>::VALUE;
1313        Self { ec_level }
1314    }
1315
1316    /// Returns the compile-time checked dynamic version value.
1317    #[must_use]
1318    pub const fn version(&self) -> Version {
1319        ConstVersion::<N>::VALUE
1320    }
1321
1322    /// Returns the configured error-correction level.
1323    #[must_use]
1324    pub const fn ec_level(&self) -> EcLevel {
1325        self.ec_level
1326    }
1327}
1328
1329impl<const N: i16> Encoder for ConstVersionEncoder<N> {
1330    type Output = QrCode;
1331    type Error = QrError;
1332
1333    fn encode(&self, input: &[u8]) -> QrResult<QrCode> {
1334        QrCode::encode_with_version(input, ConstVersion::<N>::VALUE, self.ec_level)
1335    }
1336}
1337
1338//}}}
1339
1340//------------------------------------------------------------------------------
1341//{{{ QrCodeBuilder
1342
1343/// A builder for [`QrCode`], offering ergonomic, chainable configuration.
1344///
1345/// Construct one with [`QrCode::builder`]. The builder delegates to the
1346/// existing constructors, so its output is identical to calling them directly.
1347#[derive(Clone, Debug)]
1348pub struct QrCodeBuilder<D: AsRef<[u8]>> {
1349    data: D,
1350    ec_level: EcLevel,
1351    version: Option<Version>,
1352    micro: bool,
1353    mode_hint: Option<Mode>,
1354}
1355
1356impl<D: AsRef<[u8]>> QrCodeBuilder<D> {
1357    fn new(data: D) -> Self {
1358        Self { data, ec_level: EcLevel::M, version: None, micro: false, mode_hint: None }
1359    }
1360
1361    /// Sets the error correction level (default [`EcLevel::M`]).
1362    #[must_use]
1363    pub fn ec_level(mut self, ec_level: EcLevel) -> Self {
1364        self.ec_level = ec_level;
1365        self
1366    }
1367
1368    /// Pins a specific QR [`Version`]. When set, `build()` behaves like
1369    /// [`QrCode::with_version`]. If [`micro`](Self::micro) is also set, the
1370    /// explicit version takes precedence.
1371    #[must_use]
1372    pub fn version(mut self, version: Version) -> Self {
1373        self.version = Some(version);
1374        self
1375    }
1376
1377    /// Requests a Micro QR code (the smallest fitting Micro version), behaving
1378    /// like [`QrCode::micro_with_error_correction_level`] when no explicit
1379    /// [`version`](Self::version) is set.
1380    #[must_use]
1381    pub fn micro(mut self, yes: bool) -> Self {
1382        self.micro = yes;
1383        self
1384    }
1385
1386    /// Hints the encoding [`Mode`] (e.g. [`Mode::Byte`]), bypassing automatic
1387    /// mode optimization. When a [`version`](Self::version) is also set it is
1388    /// used directly; otherwise the smallest fitting version for that mode is
1389    /// auto-selected.
1390    ///
1391    /// The data must be encodable in the chosen mode: [`Mode::Kanji`] validates
1392    /// its Shift-JIS pairs and [`Mode::Byte`] accepts anything, but
1393    /// [`Mode::Numeric`] / [`Mode::Alphanumeric`] assume their input already
1394    /// matches (as automatic optimization would never select them otherwise).
1395    #[must_use]
1396    pub fn encoding_mode(mut self, mode: Mode) -> Self {
1397        self.mode_hint = Some(mode);
1398        self
1399    }
1400
1401    /// Hints the encoding mode with a type-level [`EncodingMode`] marker.
1402    ///
1403    /// Unlike [`encoding_mode`](Self::encoding_mode), this validates `data`
1404    /// immediately and returns [`QrError::InvalidCharacter`] when the selected
1405    /// mode cannot represent it.
1406    ///
1407    /// ```rust
1408    /// use qrcode_rs::{NumericMode, QrCode, QrError, Version};
1409    ///
1410    /// let code = QrCode::builder(b"01234567")
1411    ///     .version(Version::Normal(1))
1412    ///     .encoding_mode_typed::<NumericMode>()
1413    ///     .unwrap()
1414    ///     .build()
1415    ///     .unwrap();
1416    /// assert_eq!(code.version(), Version::Normal(1));
1417    ///
1418    /// let err = QrCode::builder(b"12a")
1419    ///     .encoding_mode_typed::<NumericMode>()
1420    ///     .unwrap_err();
1421    /// assert_eq!(err, QrError::InvalidCharacter { position: 2, byte: b'a' });
1422    /// ```
1423    ///
1424    /// # Errors
1425    ///
1426    /// Returns [`QrError::InvalidCharacter`] with the first invalid byte when
1427    /// `data` is not valid for `M`.
1428    pub fn encoding_mode_typed<M: EncodingMode>(mut self) -> QrResult<Self> {
1429        if let Some((position, byte)) = M::invalid_character(self.data.as_ref()) {
1430            return Err(QrError::InvalidCharacter { position, byte });
1431        }
1432        self.mode_hint = Some(M::MODE);
1433        Ok(self)
1434    }
1435
1436    /// Forces a specific encoding [`Mode`], bypassing automatic optimization.
1437    /// This is an alias for [`encoding_mode`](Self::encoding_mode), provided for
1438    /// familiarity with the QR-code vocabulary.
1439    #[must_use]
1440    pub fn force_mode(self, mode: Mode) -> Self {
1441        self.encoding_mode(mode)
1442    }
1443
1444    /// Forces a type-level encoding mode after validating the input.
1445    ///
1446    /// This is an alias for [`encoding_mode_typed`](Self::encoding_mode_typed).
1447    ///
1448    /// # Errors
1449    ///
1450    /// Returns [`QrError::InvalidCharacter`] with the first invalid byte when
1451    /// `data` is not valid for `M`.
1452    pub fn force_mode_typed<M: EncodingMode>(self) -> QrResult<Self> {
1453        self.encoding_mode_typed::<M>()
1454    }
1455
1456    /// Builds the [`QrCode`].
1457    ///
1458    /// # Errors
1459    ///
1460    /// Propagates any [`QrError`] from the underlying encoder
1461    /// (e.g. data too long, or an incompatible version / error-correction
1462    /// combination).
1463    pub fn build(self) -> QrResult<QrCode> {
1464        if let Some(version) = self.version {
1465            if let Some(mode) = self.mode_hint {
1466                return QrCode::with_mode(self.data, version, self.ec_level, mode);
1467            }
1468            return QrCode::with_version(self.data, version, self.ec_level);
1469        }
1470        if let Some(mode) = self.mode_hint {
1471            return QrCode::with_mode_auto(self.data, self.ec_level, mode);
1472        }
1473        if self.micro {
1474            return QrCode::micro_with_error_correction_level(self.data, self.ec_level);
1475        }
1476        QrCode::with_error_correction_level(self.data, self.ec_level)
1477    }
1478}
1479
1480impl<D: AsRef<[u8]>> Builder for QrCodeBuilder<D> {
1481    type Output = QrCode;
1482    type Error = QrError;
1483
1484    fn build(self) -> QrResult<QrCode> {
1485        QrCodeBuilder::build(self)
1486    }
1487}
1488
1489//}}}
1490//------------------------------------------------------------------------------
1491//{{{ QrCodeStream
1492
1493/// Lazy iterator returned by [`QrCode::stream`] and
1494/// [`QrCode::stream_with_error_correction_level`].
1495#[derive(Clone, Debug)]
1496pub struct QrCodeStream<I> {
1497    inputs: I,
1498    ec_level: EcLevel,
1499}
1500
1501impl<I> Iterator for QrCodeStream<I>
1502where
1503    I: Iterator,
1504    I::Item: AsRef<[u8]>,
1505{
1506    type Item = QrResult<QrCode>;
1507
1508    fn next(&mut self) -> Option<Self::Item> {
1509        self.inputs.next().map(|input| QrCode::with_error_correction_level(input, self.ec_level))
1510    }
1511
1512    fn size_hint(&self) -> (usize, Option<usize>) {
1513        self.inputs.size_hint()
1514    }
1515}
1516
1517impl<I> FusedIterator for QrCodeStream<I>
1518where
1519    I: FusedIterator,
1520    I::Item: AsRef<[u8]>,
1521{
1522}
1523
1524impl<I> ExactSizeIterator for QrCodeStream<I>
1525where
1526    I: ExactSizeIterator,
1527    I::Item: AsRef<[u8]>,
1528{
1529    fn len(&self) -> usize {
1530        self.inputs.len()
1531    }
1532}
1533
1534//}}}
1535//------------------------------------------------------------------------------
1536//{{{ Batch render
1537
1538/// A named output produced by [`BatchRender`].
1539#[derive(Clone, Debug, PartialEq, Eq)]
1540pub struct BatchRendered<T> {
1541    name: String,
1542    image: T,
1543}
1544
1545impl<T> BatchRendered<T> {
1546    /// Creates a named batch-rendered output.
1547    #[must_use]
1548    pub fn new(name: impl Into<String>, image: T) -> Self {
1549        Self { name: name.into(), image }
1550    }
1551
1552    /// Stable output name for this rendered item.
1553    #[must_use]
1554    pub fn name(&self) -> &str {
1555        &self.name
1556    }
1557
1558    /// Borrow the rendered image payload.
1559    #[must_use]
1560    pub const fn image(&self) -> &T {
1561        &self.image
1562    }
1563
1564    /// Consume this item and return only the rendered image payload.
1565    #[must_use]
1566    pub fn into_image(self) -> T {
1567        self.image
1568    }
1569
1570    /// Consume this item into `(name, image)`.
1571    #[must_use]
1572    pub fn into_parts(self) -> (String, T) {
1573        (self.name, self.image)
1574    }
1575}
1576
1577/// Builder for rendering a slice of [`QrCode`] values into stable, named
1578/// in-memory outputs.
1579#[derive(Clone)]
1580pub struct BatchRender<'a> {
1581    codes: &'a [QrCode],
1582    prefix: String,
1583    extension: String,
1584    start_index: usize,
1585    index_width: usize,
1586    quiet_zone: bool,
1587    module_dimensions: Option<(u32, u32)>,
1588}
1589
1590impl<'a> BatchRender<'a> {
1591    /// Creates a batch-render builder for `codes`.
1592    #[must_use]
1593    pub fn new(codes: &'a [QrCode]) -> Self {
1594        Self {
1595            codes,
1596            prefix: "qr-".into(),
1597            extension: "bin".into(),
1598            start_index: 1,
1599            index_width: 4,
1600            quiet_zone: true,
1601            module_dimensions: None,
1602        }
1603    }
1604
1605    /// Sets the output name prefix.
1606    #[must_use]
1607    pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
1608        self.prefix = prefix.into();
1609        self
1610    }
1611
1612    /// Sets the output extension. A leading dot is accepted and stripped.
1613    ///
1614    /// Pass an empty string to omit the extension.
1615    #[must_use]
1616    pub fn extension(mut self, extension: impl Into<String>) -> Self {
1617        self.extension = extension.into();
1618        self
1619    }
1620
1621    /// Sets the first numeric index used in generated names.
1622    #[must_use]
1623    pub const fn start_index(mut self, start_index: usize) -> Self {
1624        self.start_index = start_index;
1625        self
1626    }
1627
1628    /// Sets the minimum zero-padded index width.
1629    #[must_use]
1630    pub const fn index_width(mut self, index_width: usize) -> Self {
1631        self.index_width = index_width;
1632        self
1633    }
1634
1635    /// Sets whether rendered symbols include their quiet zone.
1636    #[must_use]
1637    pub const fn quiet_zone(mut self, quiet_zone: bool) -> Self {
1638        self.quiet_zone = quiet_zone;
1639        self
1640    }
1641
1642    /// Sets fixed module dimensions for every rendered output.
1643    #[must_use]
1644    pub const fn module_dimensions(mut self, width: u32, height: u32) -> Self {
1645        self.module_dimensions = Some((width, height));
1646        self
1647    }
1648
1649    /// Clears fixed module dimensions, returning to the renderer default.
1650    #[must_use]
1651    pub const fn default_module_dimensions(mut self) -> Self {
1652        self.module_dimensions = None;
1653        self
1654    }
1655
1656    /// Render every code in order.
1657    #[must_use]
1658    pub fn build<P: Pixel>(&self) -> Vec<BatchRendered<P::Image>> {
1659        self.codes
1660            .iter()
1661            .enumerate()
1662            .map(|(offset, code)| {
1663                let mut renderer = code.render::<P>();
1664                renderer.quiet_zone(self.quiet_zone);
1665                if let Some((width, height)) = self.module_dimensions {
1666                    renderer.module_dimensions(width, height);
1667                }
1668                BatchRendered::new(self.name_for(offset), renderer.build())
1669            })
1670            .collect()
1671    }
1672
1673    fn name_for(&self, offset: usize) -> String {
1674        let index = self.start_index.saturating_add(offset);
1675        let mut name = format!("{}{:0width$}", self.prefix, index, width = self.index_width);
1676        let extension = self.extension.trim_start_matches('.');
1677        if !extension.is_empty() {
1678            name.push('.');
1679            name.push_str(extension);
1680        }
1681        name
1682    }
1683}
1684
1685//}}}
1686//------------------------------------------------------------------------------
1687//{{{ Info
1688
1689/// Metadata about a constructed [`QrCode`], returned by [`QrCode::info`].
1690///
1691/// `encoding_modes` records the set of data modes used by the encoder. It does
1692/// not retain input bytes or expose every segment boundary.
1693#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1694#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1695#[non_exhaustive]
1696pub struct Info {
1697    version: Version,
1698    ec_level: EcLevel,
1699    width: usize,
1700    module_count: usize,
1701    max_allowed_errors: usize,
1702    data_capacity_bytes: usize,
1703    mask_pattern: Option<canvas::MaskPattern>,
1704    mask_penalty_score: Option<u16>,
1705    encoding_modes: EncodingModes,
1706    remaining_capacity_bits: Option<usize>,
1707}
1708
1709impl Info {
1710    /// The QR [`Version`].
1711    #[must_use]
1712    pub const fn version(&self) -> Version {
1713        self.version
1714    }
1715
1716    /// The error correction level.
1717    #[must_use]
1718    pub const fn ec_level(&self) -> EcLevel {
1719        self.ec_level
1720    }
1721
1722    /// Modules per side (excluding the quiet zone).
1723    #[must_use]
1724    pub const fn width(&self) -> usize {
1725        self.width
1726    }
1727
1728    /// Total number of modules (`width * width`).
1729    #[must_use]
1730    pub const fn module_count(&self) -> usize {
1731        self.module_count
1732    }
1733
1734    /// Maximum number of erroneous modules that can still be recovered.
1735    #[must_use]
1736    pub const fn max_allowed_errors(&self) -> usize {
1737        self.max_allowed_errors
1738    }
1739
1740    /// Data capacity of this symbol in bytes.
1741    #[must_use]
1742    pub const fn data_capacity_bytes(&self) -> usize {
1743        self.data_capacity_bytes
1744    }
1745
1746    /// Distinct data modes used by the encoded payload.
1747    ///
1748    /// Codes rebuilt from legacy [`QrCodeData`] do not contain encoding-time
1749    /// metadata and return an empty set.
1750    #[must_use]
1751    pub const fn encoding_modes(&self) -> EncodingModes {
1752        self.encoding_modes
1753    }
1754
1755    /// Remaining payload capacity in bits before terminator and padding bits.
1756    ///
1757    /// Codes rebuilt from legacy [`QrCodeData`] do not contain encoding-time
1758    /// metadata and return `None`.
1759    #[must_use]
1760    pub const fn remaining_capacity(&self) -> Option<usize> {
1761        self.remaining_capacity_bits
1762    }
1763
1764    /// Alias for [`remaining_capacity`](Self::remaining_capacity), making the
1765    /// unit explicit at call sites.
1766    #[must_use]
1767    pub const fn remaining_capacity_bits(&self) -> Option<usize> {
1768        self.remaining_capacity_bits
1769    }
1770
1771    /// The selected mask pattern, when this code was produced by an encoder
1772    /// path that records it.
1773    ///
1774    /// Serializable legacy payloads do not contain this metadata, so codes
1775    /// rebuilt from [`QrCodeData`] return `None`.
1776    #[must_use]
1777    pub const fn mask_pattern(&self) -> Option<canvas::MaskPattern> {
1778        self.mask_pattern
1779    }
1780
1781    /// The penalty score of the selected mask, when this code was produced by
1782    /// an encoder path that records it.
1783    ///
1784    /// Lower scores indicate a visually better matrix according to the QR mask
1785    /// evaluation rules. Serializable legacy payloads do not contain this
1786    /// metadata, so codes rebuilt from [`QrCodeData`] return `None`.
1787    #[must_use]
1788    pub const fn mask_penalty_score(&self) -> Option<u16> {
1789        self.mask_penalty_score
1790    }
1791}
1792
1793//}}}
1794//------------------------------------------------------------------------------
1795//{{{ Serde (QrCodeData)
1796
1797/// A serializable view of a [`QrCode`] (matrix + metadata), enabled by the
1798/// `serde` feature. Round-trips via [`QrCode::to_serializable`] and
1799/// [`QrCode::from_serializable`].
1800#[cfg(feature = "serde")]
1801#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1802pub struct QrCodeData {
1803    /// The [`Version`].
1804    pub version: Version,
1805    /// The error-correction level.
1806    pub ec_level: EcLevel,
1807    /// Modules per side (excluding the quiet zone).
1808    pub width: usize,
1809    /// Module colors, row-major (`width * width` entries).
1810    pub content: Vec<Color>,
1811}
1812
1813#[cfg(feature = "serde")]
1814impl QrCode {
1815    /// Serializes this QR code into a [`QrCodeData`] (requires the `serde` feature).
1816    #[must_use]
1817    pub fn to_serializable(&self) -> QrCodeData {
1818        QrCodeData { version: self.version, ec_level: self.ec_level, width: self.width, content: self.content.clone() }
1819    }
1820
1821    /// Reconstructs a [`QrCode`] from [`QrCodeData`] (requires the `serde` feature).
1822    ///
1823    /// `data` is trusted: `content.len()` must equal `width * width` (checked in
1824    /// debug builds). Pair with [`QrCode::to_serializable`].
1825    #[must_use]
1826    pub fn from_serializable(data: QrCodeData) -> Self {
1827        debug_assert_eq!(data.content.len(), data.width * data.width, "malformed QrCodeData");
1828        Self {
1829            content: data.content,
1830            version: data.version,
1831            ec_level: data.ec_level,
1832            width: data.width,
1833            mask_pattern: None,
1834            mask_penalty_score: None,
1835            encoding_modes: EncodingModes::empty(),
1836            remaining_capacity_bits: None,
1837        }
1838    }
1839}
1840
1841//}}}
1842//------------------------------------------------------------------------------
1843//{{{ Analysis
1844
1845/// Diagnostic stats for a constructed [`QrCode`], returned by [`QrCode::analyze`].
1846#[derive(Clone, Copy, Debug, PartialEq)]
1847#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1848#[non_exhaustive]
1849pub struct Analysis {
1850    dark_ratio: f64,
1851    functional_modules: usize,
1852    data_modules: usize,
1853}
1854
1855impl Analysis {
1856    /// Fraction of modules that are dark, in `0.0..=1.0`.
1857    #[must_use]
1858    pub const fn dark_ratio(&self) -> f64 {
1859        self.dark_ratio
1860    }
1861
1862    /// Number of functional modules (finder / alignment / timing / format / version).
1863    #[must_use]
1864    pub const fn functional_modules(&self) -> usize {
1865        self.functional_modules
1866    }
1867
1868    /// Number of data + error-correction modules (`width² − functional`).
1869    #[must_use]
1870    pub const fn data_modules(&self) -> usize {
1871        self.data_modules
1872    }
1873}
1874
1875//}}}
1876//------------------------------------------------------------------------------
1877//{{{ QrTemplate
1878
1879/// A reusable render-time style: dark/light hex colors, module size, and quiet
1880/// zone. Apply to a [`Renderer`] with [`Renderer::template`] when the pixel type
1881/// is a [`StyledPixel`](crate::render::StyledPixel).
1882#[derive(Clone, Debug, PartialEq, Eq)]
1883#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1884pub struct QrTemplate {
1885    /// Dark module color as a CSS hex string (e.g. `"#1a1a2e"`).
1886    pub dark_color: String,
1887    /// Light module color as a CSS hex string (e.g. `"#e0e0e0"`).
1888    pub light_color: String,
1889    /// Optional module dimensions `(width, height)` in output units/pixels.
1890    pub module_size: Option<(u32, u32)>,
1891    /// Whether to include the quiet zone.
1892    pub quiet_zone: bool,
1893}
1894
1895/// Optional overrides applied to a parent [`QrTemplate`].
1896///
1897/// `None` means the value is inherited from the parent template. Module size
1898/// uses a nested option so a patch can either inherit, set, or explicitly clear
1899/// the parent's size.
1900#[derive(Clone, Debug, Default, PartialEq, Eq)]
1901#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1902pub struct QrTemplatePatch {
1903    /// Optional dark module color override.
1904    pub dark_color: Option<String>,
1905    /// Optional light module color override.
1906    pub light_color: Option<String>,
1907    /// Optional module-size override; `Some(None)` clears the inherited size.
1908    pub module_size: Option<Option<(u32, u32)>>,
1909    /// Optional quiet-zone override.
1910    pub quiet_zone: Option<bool>,
1911}
1912
1913impl QrTemplatePatch {
1914    /// Creates an empty patch that inherits every value.
1915    #[must_use]
1916    pub const fn new() -> Self {
1917        Self { dark_color: None, light_color: None, module_size: None, quiet_zone: None }
1918    }
1919
1920    /// Sets the dark module color override.
1921    #[must_use]
1922    pub fn dark_color(mut self, color: impl Into<String>) -> Self {
1923        self.dark_color = Some(color.into());
1924        self
1925    }
1926
1927    /// Sets the light module color override.
1928    #[must_use]
1929    pub fn light_color(mut self, color: impl Into<String>) -> Self {
1930        self.light_color = Some(color.into());
1931        self
1932    }
1933
1934    /// Sets the module-size override.
1935    #[must_use]
1936    pub const fn module_size(mut self, width: u32, height: u32) -> Self {
1937        self.module_size = Some(Some((width, height)));
1938        self
1939    }
1940
1941    /// Clears an inherited module-size override.
1942    #[must_use]
1943    pub const fn clear_module_size(mut self) -> Self {
1944        self.module_size = Some(None);
1945        self
1946    }
1947
1948    /// Sets the quiet-zone override.
1949    #[must_use]
1950    pub const fn quiet_zone(mut self, quiet_zone: bool) -> Self {
1951        self.quiet_zone = Some(quiet_zone);
1952        self
1953    }
1954}
1955
1956impl QrTemplate {
1957    /// Black on white, default size, with quiet zone — the standard look.
1958    #[must_use]
1959    pub fn minimal() -> Self {
1960        Self { dark_color: "#000000".into(), light_color: "#ffffff".into(), module_size: None, quiet_zone: true }
1961    }
1962
1963    /// Light modules on a dark background.
1964    #[must_use]
1965    pub fn dark_mode() -> Self {
1966        Self { dark_color: "#e0e0e0".into(), light_color: "#1a1a2e".into(), module_size: None, quiet_zone: true }
1967    }
1968
1969    /// Pure black/white, maximum contrast (accessibility).
1970    #[must_use]
1971    pub fn high_contrast() -> Self {
1972        Self { dark_color: "#000000".into(), light_color: "#ffffff".into(), module_size: None, quiet_zone: true }
1973    }
1974
1975    /// Corporate navy on white.
1976    #[must_use]
1977    pub fn corporate() -> Self {
1978        Self { dark_color: "#003366".into(), light_color: "#ffffff".into(), module_size: None, quiet_zone: true }
1979    }
1980
1981    /// Returns a copy of this template with a different dark module color.
1982    #[must_use]
1983    pub fn with_dark_color(mut self, color: impl Into<String>) -> Self {
1984        self.dark_color = color.into();
1985        self
1986    }
1987
1988    /// Returns a copy of this template with a different light module color.
1989    #[must_use]
1990    pub fn with_light_color(mut self, color: impl Into<String>) -> Self {
1991        self.light_color = color.into();
1992        self
1993    }
1994
1995    /// Returns a copy of this template with fixed module dimensions.
1996    #[must_use]
1997    pub const fn with_module_size(mut self, width: u32, height: u32) -> Self {
1998        self.module_size = Some((width, height));
1999        self
2000    }
2001
2002    /// Returns a copy of this template without fixed module dimensions.
2003    #[must_use]
2004    pub const fn without_module_size(mut self) -> Self {
2005        self.module_size = None;
2006        self
2007    }
2008
2009    /// Returns a copy of this template with the requested quiet-zone setting.
2010    #[must_use]
2011    pub const fn with_quiet_zone(mut self, quiet_zone: bool) -> Self {
2012        self.quiet_zone = quiet_zone;
2013        self
2014    }
2015
2016    /// Applies optional overrides to this template, inheriting unspecified
2017    /// fields from `self`.
2018    #[must_use]
2019    pub fn extend(&self, patch: &QrTemplatePatch) -> Self {
2020        Self {
2021            dark_color: patch.dark_color.clone().unwrap_or_else(|| self.dark_color.clone()),
2022            light_color: patch.light_color.clone().unwrap_or_else(|| self.light_color.clone()),
2023            module_size: patch.module_size.unwrap_or(self.module_size),
2024            quiet_zone: patch.quiet_zone.unwrap_or(self.quiet_zone),
2025        }
2026    }
2027
2028    /// Parses a template from a JSON string.
2029    ///
2030    /// This helper is available with the `template-json` feature. It preserves
2031    /// the dependency-light default build while offering a stable JSON entry
2032    /// point for applications that store style presets outside Rust code.
2033    ///
2034    /// # Errors
2035    ///
2036    /// Returns the underlying `serde_json` error when the JSON is malformed or
2037    /// does not match the [`QrTemplate`] schema.
2038    #[cfg(feature = "template-json")]
2039    pub fn from_json_str(input: &str) -> Result<Self, serde_json::Error> {
2040        serde_json::from_str(input)
2041    }
2042
2043    /// Serializes this template to a compact JSON string.
2044    ///
2045    /// This helper is available with the `template-json` feature.
2046    ///
2047    /// # Errors
2048    ///
2049    /// Returns the underlying `serde_json` error if serialization fails.
2050    #[cfg(feature = "template-json")]
2051    pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
2052        serde_json::to_string(self)
2053    }
2054}
2055
2056impl QrTemplatePatch {
2057    /// Parses a template patch from a JSON string.
2058    ///
2059    /// This helper is available with the `template-json` feature. Missing fields
2060    /// inherit from the parent template when passed to [`QrTemplate::extend`].
2061    ///
2062    /// # Errors
2063    ///
2064    /// Returns the underlying `serde_json` error when the JSON is malformed or
2065    /// does not match the [`QrTemplatePatch`] schema.
2066    #[cfg(feature = "template-json")]
2067    pub fn from_json_str(input: &str) -> Result<Self, serde_json::Error> {
2068        let value: serde_json::Value = serde_json::from_str(input)?;
2069        let clears_module_size = value.get("module_size").is_some_and(serde_json::Value::is_null);
2070        let mut patch: Self = serde_json::from_value(value)?;
2071        if clears_module_size {
2072            patch.module_size = Some(None);
2073        }
2074        Ok(patch)
2075    }
2076
2077    /// Serializes this patch to a compact JSON string.
2078    ///
2079    /// This helper is available with the `template-json` feature.
2080    ///
2081    /// # Errors
2082    ///
2083    /// Returns the underlying `serde_json` error if serialization fails.
2084    #[cfg(feature = "template-json")]
2085    pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
2086        serde_json::to_string(self)
2087    }
2088}
2089
2090impl qrcode_render::RenderTemplate for QrTemplate {
2091    fn dark_color(&self) -> &str {
2092        &self.dark_color
2093    }
2094
2095    fn light_color(&self) -> &str {
2096        &self.light_color
2097    }
2098
2099    fn module_size(&self) -> Option<(u32, u32)> {
2100        self.module_size
2101    }
2102
2103    fn quiet_zone(&self) -> bool {
2104        self.quiet_zone
2105    }
2106}
2107
2108//}}}
2109//------------------------------------------------------------------------------
2110//{{{ Module iterators
2111
2112/// Iterator over the rows of a [`QrCode`], created by [`QrCode::rows`].
2113pub struct Rows<'a> {
2114    code: &'a QrCode,
2115    y: usize,
2116}
2117
2118impl<'a> Iterator for Rows<'a> {
2119    type Item = Row<'a>;
2120
2121    fn next(&mut self) -> Option<Self::Item> {
2122        let w = self.code.width;
2123        if self.y < w {
2124            let row = Row { code: self.code, y: self.y, x: 0 };
2125            self.y += 1;
2126            Some(row)
2127        } else {
2128            None
2129        }
2130    }
2131
2132    fn size_hint(&self) -> (usize, Option<usize>) {
2133        let rem = self.code.width - self.y;
2134        (rem, Some(rem))
2135    }
2136}
2137
2138impl<'a> ExactSizeIterator for Rows<'a> {
2139    fn len(&self) -> usize {
2140        self.code.width - self.y
2141    }
2142}
2143
2144impl<'a> FusedIterator for Rows<'a> {}
2145
2146/// A single row of modules, yielded by [`Rows`]. Iterates over [`Color`]s from
2147/// left to right (quiet zone excluded).
2148pub struct Row<'a> {
2149    code: &'a QrCode,
2150    y: usize,
2151    x: usize,
2152}
2153
2154impl<'a> Row<'a> {
2155    /// The number of modules in this row.
2156    #[must_use]
2157    pub fn len(&self) -> usize {
2158        self.code.width
2159    }
2160
2161    /// Whether the row is empty (always `false` for a valid QR code).
2162    #[must_use]
2163    pub fn is_empty(&self) -> bool {
2164        self.code.width == 0
2165    }
2166}
2167
2168impl<'a> Iterator for Row<'a> {
2169    type Item = Color;
2170
2171    fn next(&mut self) -> Option<Color> {
2172        let w = self.code.width;
2173        if self.x < w {
2174            let color = self.code.content[self.y * w + self.x];
2175            self.x += 1;
2176            Some(color)
2177        } else {
2178            None
2179        }
2180    }
2181
2182    fn size_hint(&self) -> (usize, Option<usize>) {
2183        let rem = self.code.width - self.x;
2184        (rem, Some(rem))
2185    }
2186}
2187
2188impl<'a> ExactSizeIterator for Row<'a> {
2189    fn len(&self) -> usize {
2190        self.code.width - self.x
2191    }
2192}
2193
2194impl<'a> FusedIterator for Row<'a> {}
2195
2196/// Iterator over the `(x, y)` coordinates of every dark module in a [`QrCode`],
2197/// created by [`QrCode::dark_modules`].
2198pub struct DarkModules<'a> {
2199    code: &'a QrCode,
2200    idx: usize,
2201}
2202
2203impl<'a> Iterator for DarkModules<'a> {
2204    type Item = (usize, usize);
2205
2206    fn next(&mut self) -> Option<(usize, usize)> {
2207        let w = self.code.width;
2208        let content = &self.code.content;
2209        while self.idx < content.len() {
2210            let i = self.idx;
2211            self.idx += 1;
2212            if content[i] == Color::Dark {
2213                return Some((i % w, i / w));
2214            }
2215        }
2216        None
2217    }
2218}
2219
2220impl<'a> FusedIterator for DarkModules<'a> {}
2221
2222//}}}
2223
2224#[cfg(test)]
2225mod tests {
2226    use crate::{EcLevel, QrCode, Version};
2227
2228    #[test]
2229    fn test_annex_i_qr() {
2230        // This uses the ISO Annex I as test vector.
2231        let code = QrCode::with_version(b"01234567", Version::Normal(1), EcLevel::M).unwrap();
2232        assert_eq!(
2233            &*code.to_debug_str('#', '.'),
2234            "\
2235             #######..#.##.#######\n\
2236             #.....#..####.#.....#\n\
2237             #.###.#.#.....#.###.#\n\
2238             #.###.#.##....#.###.#\n\
2239             #.###.#.#.###.#.###.#\n\
2240             #.....#.#...#.#.....#\n\
2241             #######.#.#.#.#######\n\
2242             ........#..##........\n\
2243             #.#####..#..#.#####..\n\
2244             ...#.#.##.#.#..#.##..\n\
2245             ..#...##.#.#.#..#####\n\
2246             ....#....#.....####..\n\
2247             ...######..#.#..#....\n\
2248             ........#.#####..##..\n\
2249             #######..##.#.##.....\n\
2250             #.....#.#.#####...#.#\n\
2251             #.###.#.#...#..#.##..\n\
2252             #.###.#.##..#..#.....\n\
2253             #.###.#.#.##.#..#.#..\n\
2254             #.....#........##.##.\n\
2255             #######.####.#..#.#.."
2256        );
2257    }
2258
2259    #[test]
2260    fn test_annex_i_micro_qr() {
2261        let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
2262        assert_eq!(
2263            &*code.to_debug_str('#', '.'),
2264            "\
2265             #######.#.#.#\n\
2266             #.....#.###.#\n\
2267             #.###.#..##.#\n\
2268             #.###.#..####\n\
2269             #.###.#.###..\n\
2270             #.....#.#...#\n\
2271             #######..####\n\
2272             .........##..\n\
2273             ##.#....#...#\n\
2274             .##.#.#.#.#.#\n\
2275             ###..#######.\n\
2276             ...#.#....##.\n\
2277             ###.#..##.###"
2278        );
2279    }
2280}
2281
2282#[cfg(test)]
2283mod api_tests {
2284    use crate::{
2285        AutoEncoder, Builder as CoreBuilder, Color, ConstVersion, ConstVersionEncoder, DynEncoder, DynRenderer,
2286        EcLevel, EncodeConfig, EncodedOutput, EncoderFactory, MicroEncoder, Mode, ModuleView, NumericMode,
2287        PluginRegistry, PostProcessor, QrCode, QrError, QrSymbol, RenderConfig, RenderOutput, RendererFactory,
2288        ResourceLimits, Version, VersionEncoder,
2289    };
2290    use alloc::{
2291        boxed::Box,
2292        string::{String, ToString},
2293        vec,
2294        vec::Vec,
2295    };
2296    use qrcode_core::traits::{
2297        Encoder as CoreEncoder, ModuleSource as CoreModuleSource, ModuleStorage as CoreModuleStorage,
2298        Renderer as CoreRenderer,
2299    };
2300
2301    fn colors(code: &QrCode) -> Vec<Color> {
2302        code.to_colors()
2303    }
2304
2305    struct TextPluginRenderer;
2306
2307    impl DynRenderer for TextPluginRenderer {
2308        fn render(&self, code: &dyn CoreModuleSource) -> Result<RenderOutput, crate::PluginError> {
2309            let mut output = String::new();
2310            for y in 0..code.height() {
2311                for x in 0..code.width() {
2312                    output.push(if code.get(x, y) == Color::Dark { '#' } else { '.' });
2313                }
2314            }
2315            Ok(RenderOutput::Text(output))
2316        }
2317    }
2318
2319    struct TextPluginFactory;
2320
2321    impl RendererFactory for TextPluginFactory {
2322        fn build(&self, _config: &RenderConfig) -> Box<dyn DynRenderer> {
2323            Box::new(TextPluginRenderer)
2324        }
2325    }
2326
2327    struct LengthPluginEncoder;
2328
2329    impl DynEncoder for LengthPluginEncoder {
2330        fn encode(&self, input: &[u8]) -> Result<EncodedOutput, crate::PluginError> {
2331            Ok(EncodedOutput::Bytes(input.len().to_string().into_bytes()))
2332        }
2333    }
2334
2335    struct LengthPluginFactory;
2336
2337    impl EncoderFactory for LengthPluginFactory {
2338        fn build(&self, _config: &EncodeConfig) -> Box<dyn DynEncoder> {
2339            Box::new(LengthPluginEncoder)
2340        }
2341    }
2342
2343    struct DarkenFirstModule;
2344
2345    impl PostProcessor for DarkenFirstModule {
2346        fn process(&self, modules: &mut dyn CoreModuleStorage) -> Result<(), crate::PluginError> {
2347            modules.set(0, 0, Color::Dark);
2348            Ok(())
2349        }
2350    }
2351
2352    #[test]
2353    fn builder_matches_with_error_correction_level() {
2354        let direct = QrCode::with_error_correction_level(b"Some data", EcLevel::H).unwrap();
2355        let built = QrCode::builder(b"Some data").ec_level(EcLevel::H).build().unwrap();
2356        assert_eq!(colors(&direct), colors(&built));
2357        assert_eq!(direct.version(), built.version());
2358        assert_eq!(direct.error_correction_level(), built.error_correction_level());
2359    }
2360
2361    #[test]
2362    fn with_limits_rejects_input_before_encoding() {
2363        let limits = ResourceLimits::new(3, Version::Normal(40), (4096, 4096));
2364        assert!(matches!(QrCode::with_limits(b"abcd", limits), Err(QrError::DataTooLong)));
2365    }
2366
2367    #[test]
2368    fn with_limits_caps_version_and_render_dimensions() {
2369        let version_limits = ResourceLimits::new(128, Version::Normal(1), (4096, 4096));
2370        assert!(matches!(QrCode::with_limits([0_u8; 128], version_limits), Err(QrError::DataTooLong)));
2371
2372        let render_limits = ResourceLimits::new(128, Version::Normal(1), (20, 20));
2373        assert!(matches!(
2374            QrCode::with_limits(b"hello", render_limits),
2375            Err(QrError::RenderSizeExceeded { width: 21, height: 21, .. })
2376        ));
2377    }
2378
2379    #[test]
2380    fn with_limits_rejects_malformed_budget() {
2381        let invalid_version = ResourceLimits::new(1, Version::Micro(1), (1, 1));
2382        assert!(matches!(QrCode::with_limits(b"x", invalid_version), Err(QrError::InvalidResourceLimits)));
2383
2384        let invalid_dimensions = ResourceLimits::new(1, Version::Normal(1), (0, 1));
2385        assert!(matches!(QrCode::with_limits(b"x", invalid_dimensions), Err(QrError::InvalidResourceLimits)));
2386
2387        let invalid_timeout = ResourceLimits::new(1, Version::Normal(1), (1, 1)).with_encoding_timeout_millis(0);
2388        assert!(matches!(QrCode::with_limits(b"x", invalid_timeout), Err(QrError::InvalidResourceLimits)));
2389    }
2390
2391    #[cfg(feature = "deterministic")]
2392    #[test]
2393    fn deterministic_constructor_matches_default_constructor() {
2394        let input = b"https://example.com/deterministic";
2395        let default = QrCode::new(input).unwrap();
2396        let deterministic = QrCode::new_deterministic(input).unwrap();
2397
2398        assert_eq!(deterministic.version(), default.version());
2399        assert_eq!(deterministic.error_correction_level(), default.error_correction_level());
2400        assert_eq!(deterministic.colors(), default.colors());
2401    }
2402
2403    #[test]
2404    fn stream_lazily_encodes_with_default_error_correction_level() {
2405        let codes = QrCode::stream(["alpha", "beta"]).collect::<Result<Vec<_>, _>>().unwrap();
2406
2407        assert_eq!(codes.iter().map(QrCode::error_correction_level).collect::<Vec<_>>(), [EcLevel::M, EcLevel::M]);
2408    }
2409
2410    #[test]
2411    fn stream_with_error_correction_level_matches_batch() {
2412        let inputs = ["alpha", "beta", "gamma"];
2413        let streamed =
2414            QrCode::stream_with_error_correction_level(inputs, EcLevel::H).collect::<Result<Vec<_>, _>>().unwrap();
2415        let batched = QrCode::batch(inputs, EcLevel::H).unwrap();
2416
2417        assert_eq!(streamed.iter().map(colors).collect::<Vec<_>>(), batched.iter().map(colors).collect::<Vec<_>>());
2418    }
2419
2420    #[test]
2421    fn stream_exposes_exact_remaining_len() {
2422        let mut stream = QrCode::stream(["alpha", "beta", "gamma"]);
2423
2424        assert_eq!(stream.len(), 3);
2425        assert!(stream.next().unwrap().is_ok());
2426        assert_eq!(stream.len(), 2);
2427    }
2428
2429    #[test]
2430    fn core_builder_trait_builds_qrcode_builder() {
2431        let code = CoreBuilder::build(QrCode::builder(b"Some data").ec_level(EcLevel::H)).unwrap();
2432
2433        assert_eq!(code.error_correction_level(), EcLevel::H);
2434    }
2435
2436    #[test]
2437    fn auto_encoder_matches_constructor() {
2438        let direct = QrCode::with_error_correction_level(b"Some data", EcLevel::H).unwrap();
2439        let encoded = AutoEncoder::new(EcLevel::H).encode(b"Some data").unwrap();
2440        assert_eq!(colors(&direct), colors(&encoded));
2441    }
2442
2443    #[test]
2444    fn micro_encoder_matches_constructor() {
2445        let direct = QrCode::micro_with_error_correction_level(b"123", EcLevel::L).unwrap();
2446        let encoded = MicroEncoder::new(EcLevel::L).encode(b"123").unwrap();
2447        assert_eq!(colors(&direct), colors(&encoded));
2448    }
2449
2450    #[test]
2451    fn version_encoder_matches_constructor() {
2452        let direct = QrCode::with_version(b"Some data", Version::Normal(1), EcLevel::M).unwrap();
2453        let encoded = VersionEncoder::new(Version::Normal(1), EcLevel::M).encode(b"Some data").unwrap();
2454        assert_eq!(colors(&direct), colors(&encoded));
2455    }
2456
2457    #[test]
2458    fn const_version_encoder_matches_dynamic_version() {
2459        const V5: Version = ConstVersion::<5>::VALUE;
2460        let direct = QrCode::with_version(b"Some data", Version::Normal(5), EcLevel::M).unwrap();
2461        let const_ctor = QrCode::with_const_version::<5, _>(b"Some data", EcLevel::M).unwrap();
2462        let encoded = ConstVersionEncoder::<5>::new(EcLevel::M).encode(b"Some data").unwrap();
2463
2464        assert_eq!(V5, Version::Normal(5));
2465        assert_eq!(ConstVersion::<5>::new().version(), Version::Normal(5));
2466        assert_eq!(ConstVersionEncoder::<5>::new(EcLevel::M).version(), Version::Normal(5));
2467        assert_eq!(colors(&direct), colors(&const_ctor));
2468        assert_eq!(colors(&direct), colors(&encoded));
2469    }
2470
2471    #[test]
2472    fn builder_matches_with_version() {
2473        let direct = QrCode::with_version(b"Some data", Version::Normal(1), EcLevel::M).unwrap();
2474        let built = QrCode::builder(b"Some data").version(Version::Normal(1)).build().unwrap();
2475        assert_eq!(colors(&direct), colors(&built));
2476    }
2477
2478    #[test]
2479    fn builder_micro_matches() {
2480        let direct = QrCode::micro_with_error_correction_level(b"123", EcLevel::L).unwrap();
2481        let built = QrCode::builder(b"123").ec_level(EcLevel::L).micro(true).build().unwrap();
2482        assert_eq!(colors(&direct), colors(&built));
2483        assert!(built.version().is_micro());
2484    }
2485
2486    #[test]
2487    fn render_with_uses_registered_renderer_and_postprocessors() {
2488        let code = QrCode::new(b"plugin").unwrap();
2489        let mut registry = PluginRegistry::new();
2490        registry.register_renderer("text", Box::new(TextPluginFactory));
2491        registry.register_postprocessor(Box::new(DarkenFirstModule));
2492
2493        let output = code.render_with(&registry, "text", &RenderConfig::new()).unwrap();
2494        let RenderOutput::Text(text) = output else {
2495            panic!("expected text output");
2496        };
2497
2498        assert_eq!(text.len(), code.width() * code.width());
2499        assert!(text.starts_with('#'));
2500    }
2501
2502    #[test]
2503    fn with_plugins_renders_through_bound_registry() {
2504        let code = QrCode::new(b"plugin").unwrap();
2505        let mut registry = PluginRegistry::new();
2506        registry.register_renderer("text", Box::new(TextPluginFactory));
2507        registry.register_postprocessor(Box::new(DarkenFirstModule));
2508
2509        let bound = code.with_plugins(&registry);
2510        let output = bound.render("text", &RenderConfig::new()).unwrap();
2511
2512        assert_eq!(bound.code().width(), code.width());
2513        assert!(bound.registry().renderer("text").is_some());
2514        assert_eq!(output, code.render_with(&registry, "text", &RenderConfig::new()).unwrap());
2515    }
2516
2517    #[test]
2518    fn render_with_reports_missing_renderer() {
2519        let code = QrCode::new(b"plugin").unwrap();
2520        let registry = PluginRegistry::new();
2521
2522        assert!(matches!(
2523            code.render_with(&registry, "missing", &RenderConfig::new()),
2524            Err(crate::PluginError::RendererNotFound(name)) if name == "missing"
2525        ));
2526    }
2527
2528    #[test]
2529    fn render_with_uses_builtin_plain_text_plugin() {
2530        let code = QrCode::new(b"plugin").unwrap();
2531        let mut registry = PluginRegistry::new();
2532        registry.register_plugin(&crate::render::plugin::PlainTextRendererPlugin);
2533        let config =
2534            RenderConfig::new().with_option("dark", "X").with_option("light", ".").with_option("quiet_zone", "0");
2535
2536        let output = code
2537            .render_with(&registry, crate::render::plugin::PlainTextRendererPlugin::RENDERER_NAME, &config)
2538            .unwrap();
2539        let expected =
2540            code.render::<char>().dark_color('X').light_color('.').quiet_zone(false).module_dimensions(1, 1).build();
2541
2542        assert_eq!(output, RenderOutput::Text(expected));
2543    }
2544
2545    #[test]
2546    fn render_with_uses_builtin_invert_modules_plugin() {
2547        let code = QrCode::new(b"plugin").unwrap();
2548        let mut registry = PluginRegistry::new();
2549        registry.register_plugin(&crate::render::plugin::PlainTextRendererPlugin);
2550        registry.register_plugin(&crate::render::plugin::InvertModulesPlugin);
2551        let config =
2552            RenderConfig::new().with_option("dark", "X").with_option("light", ".").with_option("quiet_zone", "0");
2553
2554        let output = code
2555            .render_with(&registry, crate::render::plugin::PlainTextRendererPlugin::RENDERER_NAME, &config)
2556            .unwrap();
2557        let direct =
2558            code.render::<char>().dark_color('.').light_color('X').quiet_zone(false).module_dimensions(1, 1).build();
2559
2560        assert_eq!(output, RenderOutput::Text(direct));
2561    }
2562
2563    #[test]
2564    fn encode_with_uses_registered_encoder() {
2565        let mut registry = PluginRegistry::new();
2566        registry.register_encoder("length", Box::new(LengthPluginFactory));
2567
2568        let output = QrCode::encode_with(&registry, "length", b"abcd", &EncodeConfig::new()).unwrap();
2569
2570        assert_eq!(output, EncodedOutput::Bytes(b"4".to_vec()));
2571    }
2572
2573    #[test]
2574    fn encode_with_reports_missing_encoder() {
2575        let registry = PluginRegistry::new();
2576
2577        assert!(matches!(
2578            QrCode::encode_with(&registry, "missing", b"abcd", &EncodeConfig::new()),
2579            Err(crate::PluginError::EncoderNotFound(name)) if name == "missing"
2580        ));
2581    }
2582
2583    #[test]
2584    fn builder_version_wins_over_micro() {
2585        let built = QrCode::builder(b"01234567").version(Version::Micro(2)).micro(true).build().unwrap();
2586        assert_eq!(built.version(), Version::Micro(2));
2587    }
2588
2589    #[test]
2590    fn builder_forces_byte_mode() {
2591        // Forcing Byte mode on digits must differ from the optimal (Numeric) mode.
2592        let optimal = QrCode::builder(b"01234567").version(Version::Normal(2)).build().unwrap();
2593        let byte = QrCode::builder(b"01234567").version(Version::Normal(2)).encoding_mode(Mode::Byte).build().unwrap();
2594        assert_ne!(colors(&optimal), colors(&byte));
2595    }
2596
2597    #[test]
2598    fn builder_typed_encoding_mode_matches_dynamic_mode() {
2599        let typed = QrCode::builder(b"01234567")
2600            .version(Version::Normal(2))
2601            .encoding_mode_typed::<NumericMode>()
2602            .unwrap()
2603            .build()
2604            .unwrap();
2605        let dynamic =
2606            QrCode::builder(b"01234567").version(Version::Normal(2)).encoding_mode(Mode::Numeric).build().unwrap();
2607
2608        assert_eq!(colors(&typed), colors(&dynamic));
2609    }
2610
2611    #[test]
2612    fn builder_typed_encoding_mode_rejects_invalid_input() {
2613        let err = QrCode::builder(b"12a").encoding_mode_typed::<NumericMode>().unwrap_err();
2614
2615        assert_eq!(err, QrError::InvalidCharacter { position: 2, byte: b'a' });
2616    }
2617
2618    #[test]
2619    fn rows_iterate_full_grid() {
2620        let code = QrCode::new(b"hello").unwrap();
2621        let w = code.width();
2622        let rows: Vec<Vec<Color>> = code.rows().map(|r| r.collect()).collect();
2623        assert_eq!(rows.len(), w);
2624        assert!(rows.iter().all(|r| r.len() == w));
2625        for y in 0..w {
2626            for x in 0..w {
2627                assert_eq!(rows[y][x], code[(x, y)]);
2628            }
2629        }
2630    }
2631
2632    #[test]
2633    fn rows_exact_size() {
2634        let code = QrCode::new(b"hello").unwrap();
2635        let mut rows = code.rows();
2636        let total = rows.len();
2637        let mut counted = 0;
2638        while rows.next().is_some() {
2639            counted += 1;
2640            assert_eq!(rows.len(), total - counted);
2641        }
2642    }
2643
2644    #[test]
2645    fn dark_modules_match_indexed_dark_cells() {
2646        let code = QrCode::new(b"hello").unwrap();
2647        let w = code.width();
2648        let expected: Vec<(usize, usize)> =
2649            (0..w).flat_map(|y| (0..w).map(move |x| (x, y))).filter(|&(x, y)| code[(x, y)] == Color::Dark).collect();
2650        let actual: Vec<(usize, usize)> = code.dark_modules().collect();
2651        // dark_modules scans in row-major order, matching the construction above.
2652        assert_eq!(expected, actual);
2653    }
2654
2655    #[test]
2656    fn for_url_uses_high_ec() {
2657        let code = QrCode::for_url(b"https://example.com").unwrap();
2658        assert_eq!(code.error_correction_level(), EcLevel::H);
2659    }
2660
2661    #[test]
2662    fn for_wifi_encodes_with_special_chars() {
2663        let code = QrCode::for_wifi("My;Net", "a,b", "WPA").unwrap();
2664        assert!(code.width() > 0);
2665    }
2666
2667    #[test]
2668    fn for_vcard_encodes() {
2669        let code = QrCode::for_vcard("John Doe", "+1234567890", "john@example.com").unwrap();
2670        assert!(code.width() > 0);
2671    }
2672
2673    #[test]
2674    fn for_gs1_encodes() {
2675        let code = QrCode::for_gs1("010491234512345915970331301234561842").unwrap();
2676        assert!(code.width() > 0);
2677        // GS1 uses FNC1 first position; smallest fitting version, medium EC.
2678        assert!(!code.version().is_micro());
2679        assert_eq!(code.error_correction_level(), crate::EcLevel::M);
2680    }
2681
2682    #[test]
2683    fn structured_append_encodes_n_symbols() {
2684        let codes = QrCode::structured_append(b"hello structured append world", 3, EcLevel::M).unwrap();
2685        assert_eq!(codes.len(), 3);
2686        assert!(codes.iter().all(|c| !c.version().is_micro()));
2687    }
2688
2689    #[test]
2690    fn structured_append_rejects_invalid_symbol_count() {
2691        assert_eq!(
2692            QrCode::structured_append(b"x", 1, EcLevel::M).err(),
2693            Some(crate::QrError::InvalidStructuredAppend { value: 1 })
2694        );
2695        assert_eq!(
2696            QrCode::structured_append(b"x", 17, EcLevel::M).err(),
2697            Some(crate::QrError::InvalidStructuredAppend { value: 17 })
2698        );
2699    }
2700
2701    #[test]
2702    fn info_reports_metadata() {
2703        let code = QrCode::with_version(b"01234567", Version::Normal(1), crate::EcLevel::M).unwrap();
2704        let info = code.info();
2705        assert_eq!(info.version(), Version::Normal(1));
2706        assert_eq!(info.ec_level(), crate::EcLevel::M);
2707        assert_eq!(info.width(), code.width());
2708        assert_eq!(info.module_count(), code.width() * code.width());
2709        assert!(info.data_capacity_bytes() > 0);
2710        assert!(info.mask_pattern().is_some());
2711        assert!(info.mask_penalty_score().is_some());
2712        assert!(info.encoding_modes().contains(Mode::Numeric));
2713        assert!(!info.encoding_modes().contains(Mode::Byte));
2714        assert_eq!(info.remaining_capacity(), Some(87));
2715        assert_eq!(info.remaining_capacity_bits(), Some(87));
2716        // higher EC level => fewer data bytes for the same version
2717        let code_h = QrCode::with_version(b"01234567", Version::Normal(1), crate::EcLevel::H).unwrap();
2718        assert!(info.data_capacity_bytes() > code_h.info().data_capacity_bytes());
2719    }
2720
2721    #[test]
2722    fn info_reports_forced_encoding_mode() {
2723        let code = QrCode::builder(b"01234567").version(Version::Normal(1)).encoding_mode(Mode::Byte).build().unwrap();
2724
2725        assert!(code.info().encoding_modes().contains(Mode::Byte));
2726        assert!(!code.info().encoding_modes().contains(Mode::Numeric));
2727    }
2728
2729    #[cfg(feature = "serde")]
2730    #[test]
2731    fn info_reports_unknown_mask_for_serialized_legacy_data() {
2732        let data = QrCode::new(b"legacy").unwrap().to_serializable();
2733        let code = QrCode::from_serializable(data);
2734
2735        assert_eq!(code.info().mask_pattern(), None);
2736        assert_eq!(code.info().mask_penalty_score(), None);
2737        assert!(code.info().encoding_modes().is_empty());
2738        assert_eq!(code.info().remaining_capacity(), None);
2739    }
2740
2741    #[test]
2742    fn colors_borrows_without_clone() {
2743        let code = QrCode::new(b"hello").unwrap();
2744        let borrowed = code.colors();
2745        assert_eq!(borrowed.len(), code.width() * code.width());
2746        // matches the cloning accessor
2747        assert_eq!(borrowed, code.to_colors().as_slice());
2748    }
2749
2750    #[test]
2751    fn module_storage_reads_and_writes_grid() {
2752        let mut code = QrCode::new(b"hello").unwrap();
2753        let width = code.width();
2754        let before = CoreModuleStorage::get(&code, 0, 0);
2755        CoreModuleStorage::set(&mut code, 0, 0, !before);
2756        assert_eq!(CoreModuleStorage::width(&code), width);
2757        assert_eq!(CoreModuleStorage::height(&code), width);
2758        assert_eq!(CoreModuleStorage::modules(&code).len(), width * width);
2759        assert_eq!(CoreModuleStorage::get(&code, 0, 0), !before);
2760    }
2761
2762    #[test]
2763    fn module_source_exposes_read_only_grid() {
2764        let code = QrCode::new(b"hello").unwrap();
2765        let width = code.width();
2766        assert_eq!(CoreModuleSource::width(&code), width);
2767        assert_eq!(CoreModuleSource::height(&code), width);
2768        assert_eq!(CoreModuleSource::modules(&code), code.colors());
2769        assert_eq!(CoreModuleSource::get(&code, 0, 0), code[(0, 0)]);
2770    }
2771
2772    #[test]
2773    fn qr_symbol_exposes_metadata() {
2774        let code = QrCode::with_version(b"hello", Version::Normal(1), EcLevel::H).unwrap();
2775
2776        assert_eq!(QrSymbol::version(&code), Version::Normal(1));
2777        assert_eq!(QrSymbol::error_correction_level(&code), EcLevel::H);
2778        assert_eq!(QrSymbol::quiet_zone(&code), 4);
2779    }
2780
2781    #[test]
2782    fn qr_symbol_uses_micro_quiet_zone() {
2783        let code = QrCode::with_version(b"123", Version::Micro(1), EcLevel::L).unwrap();
2784
2785        assert_eq!(QrSymbol::quiet_zone(&code), 2);
2786    }
2787
2788    #[test]
2789    fn module_view_exposes_borrowed_source() {
2790        let code = QrCode::new(b"hello").unwrap();
2791        let view = code.module_view();
2792
2793        assert_eq!(view.width(), code.width());
2794        assert_eq!(view.height(), code.width());
2795        assert_eq!(view.modules(), code.colors());
2796        assert_eq!(view.get(0, 0), code[(0, 0)]);
2797    }
2798
2799    #[test]
2800    fn qr_code_ref_exposes_borrowed_symbol() {
2801        let code = QrCode::with_version(b"hello", Version::Normal(1), EcLevel::H).unwrap();
2802        let borrowed = code.as_ref();
2803
2804        assert_eq!(borrowed.width(), code.width());
2805        assert_eq!(borrowed.height(), code.width());
2806        assert_eq!(borrowed.modules(), code.colors());
2807        assert_eq!(borrowed.get(0, 0), code[(0, 0)]);
2808        assert_eq!(borrowed.version(), code.version());
2809        assert_eq!(borrowed.error_correction_level(), code.error_correction_level());
2810        assert_eq!(borrowed.quiet_zone(), 4);
2811    }
2812
2813    #[test]
2814    fn render_error_is_available_from_facade() {
2815        let err = crate::render::RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 };
2816
2817        assert_eq!(err.to_string(), "invalid module source dimensions: width=3, height=2, len=4");
2818    }
2819
2820    #[test]
2821    fn renderer_trait_path_matches_builder_output() {
2822        let code = QrCode::new(b"hello").unwrap();
2823        let builder_output = code.render::<char>().build();
2824        let renderer = code.render::<char>();
2825        let trait_output = CoreRenderer::render(&renderer, &code).unwrap();
2826        assert_eq!(trait_output, builder_output);
2827    }
2828
2829    #[test]
2830    fn render_builder_matches_render_output() {
2831        let code = QrCode::new(b"render builder").unwrap();
2832
2833        assert_eq!(code.render_builder::<char>().build(), code.render::<char>().build());
2834    }
2835
2836    #[cfg(feature = "async")]
2837    #[test]
2838    fn render_async_matches_render_output() {
2839        let code = QrCode::new(b"render async").unwrap();
2840        let expected = code.render::<char>().build();
2841        let runtime = tokio::runtime::Builder::new_current_thread().build().unwrap();
2842        let rendered = runtime.block_on(code.render_async::<char>()).unwrap();
2843
2844        assert_eq!(rendered, expected);
2845    }
2846
2847    #[test]
2848    fn renderer_from_symbol_matches_qrcode_render() {
2849        let code = QrCode::new(b"hello").unwrap();
2850
2851        let from_symbol = crate::render::Renderer::<char>::from_symbol(&code)
2852            .quiet_zone(false)
2853            .dark_color('X')
2854            .light_color('.')
2855            .build();
2856        let from_qrcode = code.render::<char>().quiet_zone(false).dark_color('X').light_color('.').build();
2857        assert_eq!(from_symbol, from_qrcode);
2858    }
2859
2860    #[test]
2861    fn renderer_from_borrowed_symbol_matches_owned_symbol() {
2862        let code = QrCode::with_version(b"123", Version::Micro(1), EcLevel::L).unwrap();
2863        let borrowed = code.as_ref();
2864
2865        let from_borrowed =
2866            crate::render::Renderer::<char>::from_symbol(&borrowed).dark_color('X').light_color('.').build();
2867        let from_owned = crate::render::Renderer::<char>::from_symbol(&code).dark_color('X').light_color('.').build();
2868
2869        assert_eq!(borrowed.quiet_zone(), 2);
2870        assert_eq!(from_borrowed, from_owned);
2871    }
2872
2873    #[test]
2874    fn renderer_trait_accepts_read_only_module_source() {
2875        let code = QrCode::new(b"hello").unwrap();
2876        let mut inverted = code.to_colors();
2877        for color in &mut inverted {
2878            *color = !*color;
2879        }
2880        let view = ModuleView::new(&inverted, code.width()).unwrap();
2881        let mut renderer = code.render::<char>();
2882        renderer.quiet_zone(false).dark_color('X').light_color('.');
2883
2884        let trait_output = CoreRenderer::render(&renderer, &view).unwrap();
2885        let expected = crate::render::Renderer::<char>::from_source(&view, 4)
2886            .quiet_zone(false)
2887            .dark_color('X')
2888            .light_color('.')
2889            .build();
2890        let original = code.render::<char>().quiet_zone(false).dark_color('X').light_color('.').build();
2891        assert_eq!(trait_output, expected);
2892        assert_ne!(trait_output, original);
2893    }
2894
2895    #[test]
2896    fn batch_encodes_many_and_short_circuits() {
2897        let codes = QrCode::batch(vec![b"hi"; 1000], crate::EcLevel::M).unwrap();
2898        assert_eq!(codes.len(), 1000);
2899        // short-circuit: a 5000-byte input cannot fit even v40-L.
2900        let huge: Vec<u8> = (0..5000).map(|i| (i % 256) as u8).collect();
2901        let mixed: Vec<&[u8]> = vec![&b"ok"[..], &huge[..], &b"also ok"[..]];
2902        assert!(QrCode::batch(mixed, crate::EcLevel::L).is_err());
2903    }
2904
2905    #[test]
2906    fn batch_render_builds_stably_named_outputs() {
2907        let codes = QrCode::batch(["alpha", "beta"], crate::EcLevel::M).unwrap();
2908        let rendered = QrCode::batch_render(&codes)
2909            .prefix("ticket-")
2910            .extension(".txt")
2911            .start_index(7)
2912            .index_width(3)
2913            .quiet_zone(false)
2914            .build::<char>();
2915
2916        assert_eq!(rendered[0].name(), "ticket-007.txt");
2917        assert_eq!(rendered[1].name(), "ticket-008.txt");
2918        assert_eq!(rendered[0].image(), &codes[0].render::<char>().quiet_zone(false).build());
2919    }
2920
2921    #[test]
2922    fn batch_render_can_omit_extension_and_apply_dimensions() {
2923        let codes = QrCode::batch(["alpha"], crate::EcLevel::M).unwrap();
2924        let rendered =
2925            QrCode::batch_render(&codes).extension("").index_width(0).module_dimensions(1, 1).build::<char>();
2926
2927        assert_eq!(rendered[0].name(), "qr-1");
2928        let (_, image) = rendered.into_iter().next().unwrap().into_parts();
2929        assert!(!image.is_empty());
2930    }
2931
2932    #[cfg(feature = "parallel")]
2933    #[test]
2934    fn par_batch_preserves_order_and_matches_batch() {
2935        let inputs = vec!["alpha", "beta", "gamma", "delta"];
2936        let sequential = QrCode::batch(inputs.clone(), crate::EcLevel::Q).unwrap();
2937        let parallel = QrCode::par_batch(inputs, crate::EcLevel::Q).unwrap();
2938
2939        assert_eq!(parallel.iter().map(colors).collect::<Vec<_>>(), sequential.iter().map(colors).collect::<Vec<_>>());
2940    }
2941
2942    #[cfg(feature = "parallel")]
2943    #[test]
2944    fn par_batch_returns_first_ordered_error() {
2945        let too_large_for_v1 = vec![b'x'; 32];
2946        let too_large_for_any_qr = vec![b'y'; qrcode_core::DEFAULT_MAX_DATA_LENGTH + 1];
2947        let inputs: Vec<&[u8]> = vec![b"ok", &too_large_for_v1, &too_large_for_any_qr];
2948
2949        let sequential = match QrCode::batch(inputs.clone(), crate::EcLevel::H) {
2950            Ok(_) => panic!("sequential batch should reject the oversized input"),
2951            Err(error) => error,
2952        };
2953        let parallel = match QrCode::par_batch(inputs, crate::EcLevel::H) {
2954            Ok(_) => panic!("parallel batch should reject the oversized input"),
2955            Err(error) => error,
2956        };
2957
2958        assert_eq!(parallel, sequential);
2959    }
2960
2961    #[cfg(feature = "eps")]
2962    #[test]
2963    fn template_applies_colors() {
2964        let code = QrCode::new(b"template").unwrap();
2965        let minimal = code.render::<crate::render::eps::Color>().template(&crate::QrTemplate::minimal()).build();
2966        let dark = code.render::<crate::render::eps::Color>().template(&crate::QrTemplate::dark_mode()).build();
2967        // minimal => black foreground ("0 0 0 setrgbcolor"); dark_mode changes it.
2968        assert!(minimal.contains("0 0 0 setrgbcolor"), "minimal should use a black foreground");
2969        assert!(!dark.contains("0 0 0 setrgbcolor"), "dark_mode should change the foreground");
2970        assert_ne!(minimal, dark);
2971    }
2972
2973    #[test]
2974    fn template_patch_inherits_unspecified_values() {
2975        let base = crate::QrTemplate::corporate().with_module_size(8, 9).with_quiet_zone(false);
2976        let patch = crate::QrTemplatePatch::new().dark_color("#112233").quiet_zone(true);
2977        let extended = base.extend(&patch);
2978
2979        assert_eq!(extended.dark_color, "#112233");
2980        assert_eq!(extended.light_color, base.light_color);
2981        assert_eq!(extended.module_size, Some((8, 9)));
2982        assert!(extended.quiet_zone);
2983    }
2984
2985    #[test]
2986    fn template_patch_can_clear_inherited_module_size() {
2987        let base = crate::QrTemplate::minimal().with_module_size(4, 4);
2988        let extended = base.extend(&crate::QrTemplatePatch::new().clear_module_size());
2989
2990        assert_eq!(extended.module_size, None);
2991    }
2992
2993    #[cfg(feature = "template-json")]
2994    #[test]
2995    fn template_json_helpers_round_trip_templates_and_patches() {
2996        let template = crate::QrTemplate::from_json_str(
2997            r##"{
2998                "dark_color":"#112233",
2999                "light_color":"#ffffff",
3000                "module_size":[4,5],
3001                "quiet_zone":false
3002            }"##,
3003        )
3004        .unwrap();
3005        assert_eq!(template.dark_color, "#112233");
3006        assert_eq!(template.module_size, Some((4, 5)));
3007        assert!(!template.quiet_zone);
3008
3009        let encoded = template.to_json_string().unwrap();
3010        assert_eq!(crate::QrTemplate::from_json_str(&encoded).unwrap(), template);
3011
3012        let patch = crate::QrTemplatePatch::from_json_str(r##"{"dark_color":"#445566","module_size":null}"##).unwrap();
3013        let extended = template.extend(&patch);
3014        assert_eq!(extended.dark_color, "#445566");
3015        assert_eq!(extended.light_color, template.light_color);
3016        assert_eq!(extended.module_size, None);
3017        assert!(!extended.quiet_zone);
3018        assert!(patch.to_json_string().unwrap().contains("#445566"));
3019    }
3020
3021    #[cfg(feature = "template-json")]
3022    #[test]
3023    fn template_json_helpers_report_malformed_input() {
3024        assert!(crate::QrTemplate::from_json_str("{").is_err());
3025        assert!(crate::QrTemplatePatch::from_json_str(r#"{"quiet_zone":"yes"}"#).is_err());
3026    }
3027
3028    #[test]
3029    fn analyze_reports_diagnostics() {
3030        let code = QrCode::with_version(b"01234567", Version::Normal(1), crate::EcLevel::M).unwrap();
3031        let a = code.analyze();
3032        let total = code.width() * code.width();
3033        assert!(a.functional_modules() > 0, "should have functional modules");
3034        assert!(a.data_modules() > 0, "should have data modules");
3035        assert_eq!(a.functional_modules() + a.data_modules(), total);
3036        assert!(a.dark_ratio() > 0.0 && a.dark_ratio() < 1.0);
3037        let dark = code.colors().iter().filter(|c| **c == Color::Dark).count();
3038        assert!((a.dark_ratio() - dark as f64 / total as f64).abs() < 1e-9);
3039    }
3040
3041    #[test]
3042    fn force_mode_without_version_auto_selects() {
3043        // Forcing Byte on digits must differ from auto (Numeric) without pinning a version.
3044        let auto = QrCode::new(b"0123456789").unwrap();
3045        let forced_byte = QrCode::builder(b"0123456789").force_mode(Mode::Byte).build().unwrap();
3046        assert_ne!(colors(&auto), colors(&forced_byte));
3047        // Forcing Numeric on digits matches auto (which also picks Numeric).
3048        let forced_num = QrCode::builder(b"0123456789").force_mode(Mode::Numeric).build().unwrap();
3049        assert_eq!(colors(&auto), colors(&forced_num));
3050        // Odd-length Kanji input surfaces InvalidCharacter via the length check.
3051        let err = QrCode::builder(b"\x93").force_mode(Mode::Kanji).build();
3052        assert!(matches!(err, Err(crate::QrError::InvalidCharacter { .. })));
3053    }
3054}
3055
3056#[cfg(all(test, feature = "image"))]
3057mod image_tests {
3058    use crate::{EcLevel, QrCode, Version};
3059    use image::{Luma, Rgb, load_from_memory};
3060
3061    #[test]
3062    fn test_annex_i_qr_as_image() {
3063        let code = QrCode::new(b"01234567").unwrap();
3064        let image = code.render::<Luma<u8>>().build();
3065        let expected =
3066            load_from_memory(include_bytes!("../docs/images/test_annex_i_qr_as_image.png")).unwrap().to_luma8();
3067        assert_eq!(image.dimensions(), expected.dimensions());
3068        assert_eq!(image.into_raw(), expected.into_raw());
3069    }
3070
3071    #[test]
3072    fn test_annex_i_micro_qr_as_image() {
3073        let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
3074        let image = code
3075            .render()
3076            .min_dimensions(200, 200)
3077            .dark_color(Rgb([128, 0, 0]))
3078            .light_color(Rgb([255, 255, 128]))
3079            .build();
3080        let expected =
3081            load_from_memory(include_bytes!("../docs/images/test_annex_i_micro_qr_as_image.png")).unwrap().to_rgb8();
3082        assert_eq!(image.dimensions(), expected.dimensions());
3083        assert_eq!(image.into_raw(), expected.into_raw());
3084    }
3085}
3086
3087#[cfg(all(test, feature = "svg"))]
3088mod svg_tests {
3089    use crate::render::svg::Color as SvgColor;
3090    use crate::{EcLevel, QrCode, Version};
3091
3092    #[test]
3093    fn test_annex_i_qr_as_svg() {
3094        let code = QrCode::new(b"01234567").unwrap();
3095        let image = code.render::<SvgColor>().build();
3096        let expected = include_str!("../docs/images/test_annex_i_qr_as_svg.svg");
3097        assert_eq!(&image, expected);
3098    }
3099
3100    #[test]
3101    fn test_annex_i_micro_qr_as_svg() {
3102        let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
3103        let image = code
3104            .render()
3105            .min_dimensions(200, 200)
3106            .dark_color(SvgColor("#800000"))
3107            .light_color(SvgColor("#ffff80"))
3108            .build();
3109        let expected = include_str!("../docs/images/test_annex_i_micro_qr_as_svg.svg");
3110        assert_eq!(&image, expected);
3111    }
3112}
3113
3114#[cfg(all(test, feature = "eps"))]
3115mod eps_tests {
3116    use crate::render::eps::Color as EpsColor;
3117    use crate::{EcLevel, QrCode, Version};
3118
3119    #[test]
3120    fn test_annex_i_qr_as_eps() {
3121        let code = QrCode::new(b"01234567").unwrap();
3122        let image = code.render::<EpsColor>().build();
3123        let expected = include_str!("../docs/images/test_annex_i_qr_as_eps.eps");
3124        assert_eq!(&image, expected);
3125    }
3126
3127    #[test]
3128    fn test_annex_i_micro_qr_as_eps() {
3129        let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
3130        let image = code
3131            .render()
3132            .min_dimensions(200, 200)
3133            .dark_color(EpsColor([0.5, 0.0, 0.0]))
3134            .light_color(EpsColor([1.0, 1.0, 0.5]))
3135            .build();
3136        let expected = include_str!("../docs/images/test_annex_i_micro_qr_as_eps.eps");
3137        assert_eq!(&image, expected);
3138    }
3139}
3140
3141#[cfg(all(test, feature = "pic"))]
3142mod pic_tests {
3143    use crate::render::pic::Color as PicColor;
3144    use crate::{EcLevel, QrCode, Version};
3145
3146    #[test]
3147    fn test_annex_i_qr_as_pic() {
3148        let code = QrCode::new(b"01234567").unwrap();
3149        let image = code.render::<PicColor>().build();
3150        let expected = include_str!("../docs/images/test_annex_i_qr_as_pic.pic");
3151        assert_eq!(&image, expected);
3152    }
3153
3154    #[test]
3155    fn test_annex_i_micro_qr_as_pic() {
3156        let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
3157        let image = code.render::<PicColor>().min_dimensions(1, 1).build();
3158        let expected = include_str!("../docs/images/test_annex_i_micro_qr_as_pic.pic");
3159        assert_eq!(&image, expected);
3160    }
3161}