1#![cfg_attr(feature = "image", doc = "```rust")]
6#![cfg_attr(not(feature = "image"), doc = "```ignore")]
7#![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, )]
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
45pub 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;
56pub 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#[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
97pub struct QrCodePlugins<'a> {
103 code: &'a QrCode,
104 registry: &'a PluginRegistry,
105}
106
107impl QrCodePlugins<'_> {
108 #[must_use]
110 pub fn code(&self) -> &QrCode {
111 self.code
112 }
113
114 #[must_use]
116 pub fn registry(&self) -> &PluginRegistry {
117 self.registry
118 }
119
120 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
132const _: () = {
134 const fn assert_send_sync<T: Send + Sync>() {}
135 assert_send_sync::<QrCode>();
136};
137
138impl QrCode {
139 pub fn new<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
153 AutoEncoder::default().encode(data.as_ref())
154 }
155
156 #[cfg(feature = "deterministic")]
168 pub fn new_deterministic<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
169 Self::new(data)
170 }
171
172 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 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 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 pub fn new_micro<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
266 MicroEncoder::default().encode(data.as_ref())
267 }
268
269 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 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 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 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 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 #[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 #[must_use]
470 pub fn batch_render(codes: &[Self]) -> BatchRender<'_> {
471 BatchRender::new(codes)
472 }
473
474 #[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 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 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 pub const fn version(&self) -> Version {
549 self.version
550 }
551
552 pub const fn error_correction_level(&self) -> EcLevel {
554 self.ec_level
555 }
556
557 pub const fn width(&self) -> usize {
561 self.width
562 }
563
564 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 #[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 #[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 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 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 pub fn colors(&self) -> &[Color] {
659 &self.content
660 }
661
662 #[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 #[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 pub fn to_colors(&self) -> Vec<Color> {
708 self.content.clone()
709 }
710
711 pub fn into_colors(self) -> Vec<Color> {
713 self.content
714 }
715
716 #[cfg_attr(feature = "image", doc = " ```rust")]
724 #[cfg_attr(not(feature = "image"), doc = " ```ignore")]
725 pub fn render<P: Pixel>(&self) -> Renderer<'_, P> {
738 Renderer::from_symbol(self)
739 }
740
741 pub fn render_builder<P: Pixel>(&self) -> Renderer<'_, P> {
762 self.render()
763 }
764
765 #[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 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 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 pub fn with_plugins<'a>(&'a self, registry: &'a PluginRegistry) -> QrCodePlugins<'a> {
846 QrCodePlugins { code: self, registry }
847 }
848}
849
850impl QrCode {
851 pub fn builder<D: AsRef<[u8]>>(data: D) -> QrCodeBuilder<D> {
869 QrCodeBuilder::new(data)
870 }
871
872 pub fn rows(&self) -> Rows<'_> {
890 Rows { code: self, y: 0 }
891 }
892
893 pub fn dark_modules(&self) -> DarkModules<'_> {
906 DarkModules { code: self, idx: 0 }
907 }
908
909 pub fn for_url<D: AsRef<[u8]>>(url: D) -> QrResult<Self> {
915 Self::with_error_correction_level(url, EcLevel::H)
916 }
917
918 pub fn for_text<D: AsRef<[u8]>>(text: D) -> QrResult<Self> {
924 Self::new(text)
925 }
926
927 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 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 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 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 #[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 #[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 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 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1177pub struct AutoEncoder {
1178 ec_level: EcLevel,
1179}
1180
1181impl AutoEncoder {
1182 #[must_use]
1184 pub const fn new(ec_level: EcLevel) -> Self {
1185 Self { ec_level }
1186 }
1187
1188 #[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1215pub struct MicroEncoder {
1216 ec_level: EcLevel,
1217}
1218
1219impl MicroEncoder {
1220 #[must_use]
1222 pub const fn new(ec_level: EcLevel) -> Self {
1223 Self { ec_level }
1224 }
1225
1226 #[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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1252pub struct VersionEncoder {
1253 version: Version,
1254 ec_level: EcLevel,
1255}
1256
1257impl VersionEncoder {
1258 #[must_use]
1260 pub const fn new(version: Version, ec_level: EcLevel) -> Self {
1261 Self { version, ec_level }
1262 }
1263
1264 #[must_use]
1266 pub const fn version(&self) -> Version {
1267 self.version
1268 }
1269
1270 #[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#[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 #[must_use]
1311 pub const fn new(ec_level: EcLevel) -> Self {
1312 let _ = ConstVersion::<N>::VALUE;
1313 Self { ec_level }
1314 }
1315
1316 #[must_use]
1318 pub const fn version(&self) -> Version {
1319 ConstVersion::<N>::VALUE
1320 }
1321
1322 #[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#[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 #[must_use]
1363 pub fn ec_level(mut self, ec_level: EcLevel) -> Self {
1364 self.ec_level = ec_level;
1365 self
1366 }
1367
1368 #[must_use]
1372 pub fn version(mut self, version: Version) -> Self {
1373 self.version = Some(version);
1374 self
1375 }
1376
1377 #[must_use]
1381 pub fn micro(mut self, yes: bool) -> Self {
1382 self.micro = yes;
1383 self
1384 }
1385
1386 #[must_use]
1396 pub fn encoding_mode(mut self, mode: Mode) -> Self {
1397 self.mode_hint = Some(mode);
1398 self
1399 }
1400
1401 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 #[must_use]
1440 pub fn force_mode(self, mode: Mode) -> Self {
1441 self.encoding_mode(mode)
1442 }
1443
1444 pub fn force_mode_typed<M: EncodingMode>(self) -> QrResult<Self> {
1453 self.encoding_mode_typed::<M>()
1454 }
1455
1456 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#[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#[derive(Clone, Debug, PartialEq, Eq)]
1540pub struct BatchRendered<T> {
1541 name: String,
1542 image: T,
1543}
1544
1545impl<T> BatchRendered<T> {
1546 #[must_use]
1548 pub fn new(name: impl Into<String>, image: T) -> Self {
1549 Self { name: name.into(), image }
1550 }
1551
1552 #[must_use]
1554 pub fn name(&self) -> &str {
1555 &self.name
1556 }
1557
1558 #[must_use]
1560 pub const fn image(&self) -> &T {
1561 &self.image
1562 }
1563
1564 #[must_use]
1566 pub fn into_image(self) -> T {
1567 self.image
1568 }
1569
1570 #[must_use]
1572 pub fn into_parts(self) -> (String, T) {
1573 (self.name, self.image)
1574 }
1575}
1576
1577#[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 #[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 #[must_use]
1607 pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
1608 self.prefix = prefix.into();
1609 self
1610 }
1611
1612 #[must_use]
1616 pub fn extension(mut self, extension: impl Into<String>) -> Self {
1617 self.extension = extension.into();
1618 self
1619 }
1620
1621 #[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 #[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 #[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 #[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 #[must_use]
1651 pub const fn default_module_dimensions(mut self) -> Self {
1652 self.module_dimensions = None;
1653 self
1654 }
1655
1656 #[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#[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 #[must_use]
1712 pub const fn version(&self) -> Version {
1713 self.version
1714 }
1715
1716 #[must_use]
1718 pub const fn ec_level(&self) -> EcLevel {
1719 self.ec_level
1720 }
1721
1722 #[must_use]
1724 pub const fn width(&self) -> usize {
1725 self.width
1726 }
1727
1728 #[must_use]
1730 pub const fn module_count(&self) -> usize {
1731 self.module_count
1732 }
1733
1734 #[must_use]
1736 pub const fn max_allowed_errors(&self) -> usize {
1737 self.max_allowed_errors
1738 }
1739
1740 #[must_use]
1742 pub const fn data_capacity_bytes(&self) -> usize {
1743 self.data_capacity_bytes
1744 }
1745
1746 #[must_use]
1751 pub const fn encoding_modes(&self) -> EncodingModes {
1752 self.encoding_modes
1753 }
1754
1755 #[must_use]
1760 pub const fn remaining_capacity(&self) -> Option<usize> {
1761 self.remaining_capacity_bits
1762 }
1763
1764 #[must_use]
1767 pub const fn remaining_capacity_bits(&self) -> Option<usize> {
1768 self.remaining_capacity_bits
1769 }
1770
1771 #[must_use]
1777 pub const fn mask_pattern(&self) -> Option<canvas::MaskPattern> {
1778 self.mask_pattern
1779 }
1780
1781 #[must_use]
1788 pub const fn mask_penalty_score(&self) -> Option<u16> {
1789 self.mask_penalty_score
1790 }
1791}
1792
1793#[cfg(feature = "serde")]
1801#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1802pub struct QrCodeData {
1803 pub version: Version,
1805 pub ec_level: EcLevel,
1807 pub width: usize,
1809 pub content: Vec<Color>,
1811}
1812
1813#[cfg(feature = "serde")]
1814impl QrCode {
1815 #[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 #[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#[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 #[must_use]
1858 pub const fn dark_ratio(&self) -> f64 {
1859 self.dark_ratio
1860 }
1861
1862 #[must_use]
1864 pub const fn functional_modules(&self) -> usize {
1865 self.functional_modules
1866 }
1867
1868 #[must_use]
1870 pub const fn data_modules(&self) -> usize {
1871 self.data_modules
1872 }
1873}
1874
1875#[derive(Clone, Debug, PartialEq, Eq)]
1883#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1884pub struct QrTemplate {
1885 pub dark_color: String,
1887 pub light_color: String,
1889 pub module_size: Option<(u32, u32)>,
1891 pub quiet_zone: bool,
1893}
1894
1895#[derive(Clone, Debug, Default, PartialEq, Eq)]
1901#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1902pub struct QrTemplatePatch {
1903 pub dark_color: Option<String>,
1905 pub light_color: Option<String>,
1907 pub module_size: Option<Option<(u32, u32)>>,
1909 pub quiet_zone: Option<bool>,
1911}
1912
1913impl QrTemplatePatch {
1914 #[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 #[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 #[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 #[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 #[must_use]
1943 pub const fn clear_module_size(mut self) -> Self {
1944 self.module_size = Some(None);
1945 self
1946 }
1947
1948 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[must_use]
2004 pub const fn without_module_size(mut self) -> Self {
2005 self.module_size = None;
2006 self
2007 }
2008
2009 #[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 #[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 #[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 #[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 #[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 #[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
2108pub 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
2146pub struct Row<'a> {
2149 code: &'a QrCode,
2150 y: usize,
2151 x: usize,
2152}
2153
2154impl<'a> Row<'a> {
2155 #[must_use]
2157 pub fn len(&self) -> usize {
2158 self.code.width
2159 }
2160
2161 #[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
2196pub 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#[cfg(test)]
2225mod tests {
2226 use crate::{EcLevel, QrCode, Version};
2227
2228 #[test]
2229 fn test_annex_i_qr() {
2230 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(®istry, "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(®istry);
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(®istry, "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(®istry, "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(®istry, 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(®istry, 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(®istry, "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(®istry, "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 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 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 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 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 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 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 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 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 let forced_num = QrCode::builder(b"0123456789").force_mode(Mode::Numeric).build().unwrap();
3049 assert_eq!(colors(&auto), colors(&forced_num));
3050 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}