zedbar/config.rs
1//! Type-safe configuration system for Zedbar decoders
2//!
3//! This module provides a compile-time verified configuration API that prevents
4//! invalid configuration combinations. The type system ensures you can only
5//! set configurations that are valid for each symbology.
6//!
7//! # Examples
8//!
9//! ## Valid Configuration
10//!
11//! ```
12//! use zedbar::config::*;
13//! use zedbar::DecoderConfig;
14//!
15//! let config = DecoderConfig::new()
16//! .enable(Ean13)
17//! .enable(Code39)
18//! .set_length_limits(Code39, 4, 20) // ✓ Code39 supports variable length
19//! .set_binary(QrCode, true) // ✓ QR codes support binary mode
20//! .position_tracking(true);
21//! ```
22//!
23//! ## Type-Safe Compile Errors
24//!
25//! The following configurations will NOT compile:
26//!
27//! ```compile_fail
28//! # use zedbar::config::*;
29//! # use zedbar::DecoderConfig;
30//! # let config = DecoderConfig::new();
31//! // ❌ EAN-13 has fixed length, doesn't support length limits
32//! config.set_length_limits(Ean13, 1, 20);
33//! ```
34//!
35//! ```compile_fail
36//! # use zedbar::config::*;
37//! # use zedbar::DecoderConfig;
38//! # let config = DecoderConfig::new();
39//! // ❌ Code39 is not a 2D code, doesn't support binary mode
40//! config.set_binary(Code39, true);
41//! ```
42//!
43//! ```compile_fail
44//! # use zedbar::config::*;
45//! # use zedbar::DecoderConfig;
46//! # let config = DecoderConfig::new();
47//! // ❌ QR codes don't use length limits
48//! config.set_length_limits(QrCode, 1, 100);
49//! ```
50
51use crate::SymbolType;
52use std::collections::{HashMap, HashSet};
53
54pub(crate) mod internal;
55pub mod symbologies;
56
57// Re-export symbology types for convenience
58pub use symbologies::*;
59
60// ============================================================================
61// Configuration Value Types
62// ============================================================================
63
64/// Enable/disable flag for a symbology
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub struct Enable(pub bool);
67
68/// Add checksum validation
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct AddChecksum(pub bool);
71
72/// Emit checksum in decoded data
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct EmitChecksum(pub bool);
75
76/// Binary mode (for 2D codes like QR)
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub struct Binary(pub bool);
79
80/// Minimum symbol length
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub struct MinLength(pub u32);
83
84/// Maximum symbol length
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub struct MaxLength(pub u32);
87
88/// Edge detection uncertainty threshold
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub struct Uncertainty(pub u32);
91
92/// Position tracking enable/disable
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub struct PositionTracking(pub bool);
95
96/// Test inverted images
97#[derive(Debug, Clone, Copy, PartialEq, Eq)]
98pub struct TestInverted(pub bool);
99
100/// Horizontal scan density
101#[derive(Debug, Clone, Copy, PartialEq, Eq)]
102pub struct XDensity(pub u32);
103
104/// Vertical scan density
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub struct YDensity(pub u32);
107
108// ============================================================================
109// Capability Traits
110// ============================================================================
111
112/// Marker trait for symbologies that can be enabled/disabled
113pub trait SupportsEnable: Symbology {}
114
115/// Marker trait for symbologies that support checksum configuration
116pub trait SupportsChecksum: Symbology {}
117
118/// Marker trait for symbologies that support variable length limits
119pub trait SupportsLengthLimits: Symbology {}
120
121/// Marker trait for symbologies that support binary mode
122pub trait SupportsBinary: Symbology {}
123
124/// Marker trait for symbologies that support uncertainty configuration
125pub trait SupportsUncertainty: Symbology {}
126
127/// Base trait that all symbology types must implement
128pub trait Symbology: Sized {
129 /// The corresponding SymbolType enum value
130 const TYPE: SymbolType;
131
132 /// Human-readable name
133 const NAME: &'static str;
134}
135
136// ============================================================================
137// User-Facing Configuration Builder
138// ============================================================================
139
140/// Type-safe configuration builder for ZBar decoders
141///
142/// This builder uses the type system to ensure only valid configurations
143/// can be set for each symbology type.
144///
145/// # Example
146/// ```no_run
147/// use zedbar::config::*;
148///
149/// let config = DecoderConfig::new()
150/// .enable(Ean13)
151/// .enable(Code39)
152/// .set_checksum(Code39, true, false)
153/// .set_length_limits(Code39, 4, 20)
154/// .position_tracking(true);
155/// ```
156#[derive(Debug, Clone)]
157pub struct DecoderConfig {
158 /// Which symbologies are enabled
159 pub(crate) enabled: HashSet<SymbolType>,
160
161 /// Checksum configuration: (add_check, emit_check)
162 pub(crate) checksum_flags: HashMap<SymbolType, (bool, bool)>,
163
164 /// Length limits: (min, max)
165 pub(crate) length_limits: HashMap<SymbolType, (u32, u32)>,
166
167 /// Binary mode enabled for 2D codes
168 pub(crate) binary_mode: HashSet<SymbolType>,
169
170 /// Uncertainty threshold per symbology
171 pub(crate) uncertainty: HashMap<SymbolType, u32>,
172
173 /// Global scanner configuration
174 pub(crate) position_tracking: bool,
175 pub(crate) test_inverted: bool,
176 pub(crate) x_density: u32,
177 pub(crate) y_density: u32,
178 pub(crate) upscale_small_images: bool,
179}
180
181impl Default for DecoderConfig {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187impl DecoderConfig {
188 /// Create a new configuration with sensible defaults
189 ///
190 /// By default:
191 /// - EAN-13, EAN-8, I25, DataBar, Codabar, Code39, Code93, Code128, QR, SQ are enabled
192 /// - UPC-A, UPC-E, ISBN-10, ISBN-13 can be emitted as variants (not separately enabled)
193 /// - Position tracking is enabled
194 /// - Scan density is 1x1
195 /// - Inverted image testing is disabled
196 pub fn new() -> Self {
197 let mut config = Self {
198 enabled: HashSet::new(),
199 checksum_flags: HashMap::new(),
200 length_limits: HashMap::new(),
201 binary_mode: HashSet::new(),
202 uncertainty: HashMap::new(),
203 position_tracking: true,
204 test_inverted: false,
205 x_density: 1,
206 y_density: 1,
207 upscale_small_images: true,
208 };
209
210 // Enable common symbologies by default
211 config.enabled.insert(SymbolType::Ean13);
212 config.enabled.insert(SymbolType::Ean8);
213 // Note: UPC-A, UPC-E, ISBN-10, ISBN-13 are NOT enabled by default
214 // They can be emitted as variants of EAN-13/EAN-8 when detected
215 config.enabled.insert(SymbolType::I25);
216 config.enabled.insert(SymbolType::Databar);
217 config.enabled.insert(SymbolType::DatabarExp);
218 config.enabled.insert(SymbolType::Codabar);
219 config.enabled.insert(SymbolType::Code39);
220 config.enabled.insert(SymbolType::Code93);
221 config.enabled.insert(SymbolType::Code128);
222 config.enabled.insert(SymbolType::QrCode);
223 config.enabled.insert(SymbolType::SqCode);
224
225 // Set default checksum behavior for EAN/UPC
226 // EAN-13 and EAN-8 are enabled with emit_check
227 for sym in [SymbolType::Ean13, SymbolType::Ean8] {
228 config.checksum_flags.insert(sym, (false, true)); // don't add, do emit
229 }
230
231 // UPC-A, UPC-E, ISBN variants are not enabled but have emit_check
232 // This allows them to be reported as variants when EAN decoding detects them
233 for sym in [
234 SymbolType::Upca,
235 SymbolType::Upce,
236 SymbolType::Isbn10,
237 SymbolType::Isbn13,
238 ] {
239 config.checksum_flags.insert(sym, (false, true)); // don't add, do emit
240 }
241
242 // Set default length limits for variable-length symbologies
243 config.length_limits.insert(SymbolType::I25, (6, 256));
244 config.length_limits.insert(SymbolType::Codabar, (4, 256));
245 config.length_limits.insert(SymbolType::Code39, (1, 256));
246
247 // Set default uncertainty values
248 config.uncertainty.insert(SymbolType::Codabar, 1);
249
250 config
251 }
252
253 // ========================================================================
254 // Per-Symbology Configuration
255 // ========================================================================
256
257 /// Enable a symbology
258 pub fn enable<S: Symbology + SupportsEnable>(mut self, _: S) -> Self {
259 self.enabled.insert(S::TYPE);
260 self
261 }
262
263 /// Disable a symbology
264 pub fn disable<S: Symbology + SupportsEnable>(mut self, _: S) -> Self {
265 self.enabled.remove(&S::TYPE);
266 self
267 }
268
269 /// Disable all symbologies
270 ///
271 /// Useful when you want to start with a clean slate and only enable
272 /// specific symbologies.
273 ///
274 /// # Example
275 /// ```
276 /// use zedbar::config::*;
277 /// use zedbar::DecoderConfig;
278 ///
279 /// let config = DecoderConfig::new()
280 /// .disable_all()
281 /// .enable(QrCode)
282 /// .enable(Code39);
283 /// ```
284 pub fn disable_all(mut self) -> Self {
285 self.enabled.clear();
286 self
287 }
288
289 /// Check if a symbology is enabled
290 pub fn is_enabled(&self, sym: SymbolType) -> bool {
291 self.enabled.contains(&sym)
292 }
293
294 /// Configure checksum behavior for a symbology
295 ///
296 /// # Arguments
297 /// * `add_check` - Validate checksum during decoding
298 /// * `emit_check` - Include checksum digit in decoded data
299 pub fn set_checksum<S: Symbology + SupportsChecksum>(
300 mut self,
301 _: S,
302 add_check: bool,
303 emit_check: bool,
304 ) -> Self {
305 self.checksum_flags.insert(S::TYPE, (add_check, emit_check));
306 self
307 }
308
309 /// Set minimum and maximum length limits
310 ///
311 /// Only valid for variable-length symbologies like Code39, Code128, etc.
312 pub fn set_length_limits<S: Symbology + SupportsLengthLimits>(
313 mut self,
314 _: S,
315 min: u32,
316 max: u32,
317 ) -> Self {
318 assert!(min <= max, "min length must be <= max length");
319 assert!(max <= 256, "max length must be <= 256");
320 self.length_limits.insert(S::TYPE, (min, max));
321 self
322 }
323
324 /// Enable or disable binary mode for 2D codes
325 ///
326 /// When enabled, binary data is preserved without text conversion.
327 pub fn set_binary<S: Symbology + SupportsBinary>(mut self, _: S, enabled: bool) -> Self {
328 if enabled {
329 self.binary_mode.insert(S::TYPE);
330 } else {
331 self.binary_mode.remove(&S::TYPE);
332 }
333 self
334 }
335
336 /// Set uncertainty threshold for edge detection
337 ///
338 /// Higher values are more tolerant of poor quality images but may
339 /// produce more false positives.
340 pub fn set_uncertainty<S: Symbology + SupportsUncertainty>(
341 mut self,
342 _: S,
343 threshold: u32,
344 ) -> Self {
345 self.uncertainty.insert(S::TYPE, threshold);
346 self
347 }
348
349 // ========================================================================
350 // Global Scanner Configuration
351 // ========================================================================
352
353 /// Enable or disable position tracking
354 ///
355 /// When enabled, the scanner records the pixel coordinates of each
356 /// detected symbol.
357 pub fn position_tracking(mut self, enabled: bool) -> Self {
358 self.position_tracking = enabled;
359 self
360 }
361
362 /// Enable or disable inverted image testing
363 ///
364 /// When enabled, if no symbols are found in the normal image, the
365 /// scanner will try again with an inverted (negative) image.
366 pub fn test_inverted(mut self, enabled: bool) -> Self {
367 self.test_inverted = enabled;
368 self
369 }
370
371 /// Set scan density for both axes
372 ///
373 /// Higher density means more scan lines, which improves detection
374 /// but increases processing time. A value of 1 means scan every line.
375 pub fn scan_density(mut self, x: u32, y: u32) -> Self {
376 assert!(x > 0, "x density must be > 0");
377 assert!(y > 0, "y density must be > 0");
378 self.x_density = x;
379 self.y_density = y;
380 self
381 }
382
383 /// Enable or disable automatic upscaling of small images
384 ///
385 /// When enabled (the default), small images (< 200px in either dimension)
386 /// are automatically upscaled before scanning to improve QR code detection.
387 /// Small QR codes often have modules that are only 2-3 pixels wide, which
388 /// is too small for reliable finder pattern detection.
389 ///
390 /// Disable this if you want to minimize processing time and are confident
391 /// your images have sufficient resolution.
392 pub fn upscale_small_images(mut self, enabled: bool) -> Self {
393 self.upscale_small_images = enabled;
394 self
395 }
396
397 /// Set horizontal scan density
398 pub fn x_density(mut self, density: u32) -> Self {
399 assert!(density > 0, "density must be > 0");
400 self.x_density = density;
401 self
402 }
403
404 /// Set vertical scan density
405 pub fn y_density(mut self, density: u32) -> Self {
406 assert!(density > 0, "density must be > 0");
407 self.y_density = density;
408 self
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
417 fn test_default_config() {
418 let config = DecoderConfig::new();
419 assert!(config.is_enabled(SymbolType::Ean13));
420 assert!(config.is_enabled(SymbolType::Code39));
421 assert!(config.position_tracking);
422 assert!(!config.test_inverted);
423 assert_eq!(config.x_density, 1);
424 assert_eq!(config.y_density, 1);
425 }
426
427 #[test]
428 fn test_builder_pattern() {
429 let config = DecoderConfig::new()
430 .enable(Ean13)
431 .disable(Code39)
432 .set_checksum(Code39, true, false)
433 .position_tracking(false)
434 .scan_density(2, 2);
435
436 assert!(config.is_enabled(SymbolType::Ean13));
437 assert!(!config.is_enabled(SymbolType::Code39));
438 assert!(!config.position_tracking);
439 assert_eq!(config.x_density, 2);
440 assert_eq!(config.y_density, 2);
441 }
442
443 #[test]
444 fn test_type_safe_length_limits() {
445 // Variable-length symbologies can have length limits
446 let config = DecoderConfig::new()
447 .set_length_limits(Code39, 5, 20)
448 .set_length_limits(Code128, 1, 50)
449 .set_length_limits(I25, 6, 30);
450
451 assert_eq!(
452 config.length_limits.get(&SymbolType::Code39),
453 Some(&(5, 20))
454 );
455 assert_eq!(
456 config.length_limits.get(&SymbolType::Code128),
457 Some(&(1, 50))
458 );
459 assert_eq!(config.length_limits.get(&SymbolType::I25), Some(&(6, 30)));
460 }
461
462 #[test]
463 fn test_type_safe_binary_mode() {
464 // Only 2D codes support binary mode
465 let config = DecoderConfig::new()
466 .set_binary(QrCode, true)
467 .set_binary(SqCode, false);
468
469 assert!(config.binary_mode.contains(&SymbolType::QrCode));
470 assert!(!config.binary_mode.contains(&SymbolType::SqCode));
471 }
472
473 #[test]
474 fn test_checksum_configuration() {
475 let config = DecoderConfig::new()
476 .set_checksum(Codabar, true, false) // validate but don't emit
477 .set_checksum(Ean13, false, true); // don't validate, do emit
478
479 assert_eq!(
480 config.checksum_flags.get(&SymbolType::Codabar),
481 Some(&(true, false))
482 );
483 assert_eq!(
484 config.checksum_flags.get(&SymbolType::Ean13),
485 Some(&(false, true))
486 );
487 }
488
489 #[test]
490 fn test_uncertainty_configuration() {
491 let config = DecoderConfig::new()
492 .set_uncertainty(Code39, 2)
493 .set_uncertainty(QrCode, 0)
494 .set_uncertainty(Codabar, 1);
495
496 assert_eq!(config.uncertainty.get(&SymbolType::Code39), Some(&2));
497 assert_eq!(config.uncertainty.get(&SymbolType::QrCode), Some(&0));
498 assert_eq!(config.uncertainty.get(&SymbolType::Codabar), Some(&1));
499 }
500
501 #[test]
502 fn test_config_to_state_conversion() {
503 let config = DecoderConfig::new()
504 .enable(Ean13)
505 .set_checksum(Ean13, false, true)
506 .position_tracking(true)
507 .scan_density(2, 3);
508
509 let state: internal::DecoderState = (&config).into();
510
511 // Verify EAN is enabled
512 assert!(state.is_enabled(SymbolType::Ean13));
513 assert!(state.ean_enabled());
514
515 // Verify checksum config
516 let ean13_config = state.get(SymbolType::Ean13).unwrap();
517 assert!(!ean13_config.checksum.add_check);
518 assert!(ean13_config.checksum.emit_check);
519
520 // Verify scanner config
521 assert_eq!(state.scanner.x_density, 2);
522 assert_eq!(state.scanner.y_density, 3);
523 assert!(state.scanner.position_tracking);
524 }
525}