1#![cfg_attr(not(feature = "std"), no_std)]
26
27extern crate alloc;
28
29#[cfg(not(feature = "std"))]
30#[allow(unused_imports)]
31use alloc::{
32 borrow::ToOwned,
33 format,
34 string::{String, ToString},
35 vec,
36 vec::Vec,
37};
38
39use core::cmp::max;
40use core::fmt;
41use qrcode_core::As;
42pub use qrcode_core::Color;
43
44pub mod ansi;
45pub mod colors;
46#[cfg(feature = "image")]
47pub mod image;
48pub mod plugin;
49pub mod string;
50pub mod unicode;
51
52pub trait Pixel: Copy + Sized {
57 type Image: Sized + 'static;
59
60 type Canvas: Canvas<Pixel = Self, Image = Self::Image>;
63
64 fn default_unit_size() -> (u32, u32) {
66 (8, 8)
67 }
68
69 fn default_color(color: Color) -> Self;
71}
72
73pub trait StyledPixel: Pixel {
81 fn from_hex(hex: &str) -> Self;
84}
85
86pub trait RenderTemplate {
93 fn dark_color(&self) -> &str;
95
96 fn light_color(&self) -> &str;
98
99 fn module_size(&self) -> Option<(u32, u32)>;
101
102 fn quiet_zone(&self) -> bool;
104}
105
106pub trait Canvas: Sized {
108 type Pixel: Sized;
110 type Image: Sized;
112
113 fn new(width: u32, height: u32, dark_pixel: Self::Pixel, light_pixel: Self::Pixel) -> Self;
115
116 fn draw_dark_pixel(&mut self, x: u32, y: u32);
118
119 fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) {
122 for y in top..(top + height) {
123 for x in left..(left + width) {
124 self.draw_dark_pixel(x, y);
125 }
126 }
127 }
128
129 fn into_image(self) -> Self::Image;
131}
132
133#[derive(Clone, Copy, Debug, PartialEq, Eq)]
139pub enum RenderError {
140 InvalidModuleSource {
142 width: usize,
144 height: usize,
146 len: usize,
148 },
149
150 ModuleSourceTooWide {
152 width: usize,
154 },
155
156 OutputTooLarge,
159}
160
161impl fmt::Display for RenderError {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 match self {
164 RenderError::InvalidModuleSource { width, height, len } => {
165 write!(f, "invalid module source dimensions: width={width}, height={height}, len={len}")
166 }
167 RenderError::ModuleSourceTooWide { width } => write!(f, "module source width {width} exceeds u32::MAX"),
168 RenderError::OutputTooLarge => f.write_str("rendered output dimensions exceed u32::MAX"),
169 }
170 }
171}
172
173#[cfg(feature = "std")]
174impl std::error::Error for RenderError {}
175
176pub struct Renderer<'a, P: Pixel> {
179 content: &'a [Color],
180 modules_count: u32, quiet_zone: u32,
182 module_size: (u32, u32),
183
184 dark_color: P,
185 light_color: P,
186 has_quiet_zone: bool,
187}
188
189impl<'a, P: Pixel> Renderer<'a, P> {
190 pub fn new(content: &'a [Color], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> {
195 assert_eq!(modules_count * modules_count, content.len());
196 Renderer {
197 content,
198 modules_count: modules_count.as_u32(),
199 quiet_zone,
200 module_size: P::default_unit_size(),
201 dark_color: P::default_color(Color::Dark),
202 light_color: P::default_color(Color::Light),
203 has_quiet_zone: true,
204 }
205 }
206
207 pub fn from_source<C>(source: &'a C, quiet_zone: u32) -> Renderer<'a, P>
219 where
220 C: qrcode_core::ModuleSource + ?Sized,
221 {
222 match Self::try_from_source(source, quiet_zone) {
223 Ok(renderer) => renderer,
224 Err(err) => panic!("{err}"),
225 }
226 }
227
228 pub fn from_symbol<S>(symbol: &'a S) -> Renderer<'a, P>
237 where
238 S: qrcode_core::QrSymbol + ?Sized,
239 {
240 Self::from_source(symbol, symbol.quiet_zone())
241 }
242
243 pub fn try_from_source<C>(source: &'a C, quiet_zone: u32) -> Result<Renderer<'a, P>, RenderError>
255 where
256 C: qrcode_core::ModuleSource + ?Sized,
257 {
258 let width = source.width();
259 let height = source.height();
260 let len = source.modules().len();
261 let Some(expected_len) = width.checked_mul(height) else {
262 return Err(RenderError::InvalidModuleSource { width, height, len });
263 };
264 if width == 0 || width != height || len != expected_len {
265 return Err(RenderError::InvalidModuleSource { width, height, len });
266 }
267 if width > u32::MAX as usize {
268 return Err(RenderError::ModuleSourceTooWide { width });
269 }
270 Ok(Self::new(source.modules(), width, quiet_zone))
271 }
272
273 pub fn try_from_symbol<S>(symbol: &'a S) -> Result<Renderer<'a, P>, RenderError>
284 where
285 S: qrcode_core::QrSymbol + ?Sized,
286 {
287 Self::try_from_source(symbol, symbol.quiet_zone())
288 }
289
290 pub fn dark_color(&mut self, color: P) -> &mut Self {
292 self.dark_color = color;
293 self
294 }
295
296 pub fn light_color(&mut self, color: P) -> &mut Self {
298 self.light_color = color;
299 self
300 }
301
302 pub fn quiet_zone(&mut self, has_quiet_zone: bool) -> &mut Self {
304 self.has_quiet_zone = has_quiet_zone;
305 self
306 }
307
308 pub fn module_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
310 self.module_size = (max(width, 1), max(height, 1));
311 self
312 }
313
314 pub fn min_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
323 let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
324 let width_in_modules = self.modules_count + quiet_zone;
325 let unit_width = width.div_ceil(width_in_modules);
326 let unit_height = height.div_ceil(width_in_modules);
327 self.module_dimensions(unit_width, unit_height)
328 }
329
330 pub fn max_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
342 let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
343 let width_in_modules = self.modules_count + quiet_zone;
344 let unit_width = width / width_in_modules;
345 let unit_height = height / width_in_modules;
346 self.module_dimensions(unit_width, unit_height)
347 }
348
349 pub fn for_web(&mut self) -> &mut Self {
354 self.min_dimensions(200, 200)
355 }
356
357 pub fn for_print(&mut self, dpi: u32) -> &mut Self {
366 self.min_dimensions(dpi.max(72), dpi.max(72))
367 }
368
369 pub fn for_social(&mut self, platform: &str) -> &mut Self {
381 let size = match platform {
382 "twitter" | "x" => 400,
383 "facebook" | "fb" => 600,
384 "instagram" | "ig" => 1080,
385 "wechat" | "weixin" => 600,
386 _ => 400,
387 };
388 self.min_dimensions(size, size)
389 }
390
391 pub fn try_build(&self) -> Result<P::Image, RenderError> {
398 let w = self.modules_count;
399 let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 };
400 let quiet = qz.checked_mul(2).ok_or(RenderError::OutputTooLarge)?;
401 let width = w.checked_add(quiet).ok_or(RenderError::OutputTooLarge)?;
402
403 let (mw, mh) = self.module_size;
404 let real_width = width.checked_mul(mw).ok_or(RenderError::OutputTooLarge)?;
405 let real_height = width.checked_mul(mh).ok_or(RenderError::OutputTooLarge)?;
406
407 let mut canvas = P::Canvas::new(real_width, real_height, self.dark_color, self.light_color);
408 for (y, row) in self.content.chunks_exact(w as usize).enumerate() {
409 let top = (y as u32 + qz) * mh;
410 for (x, &module) in row.iter().enumerate() {
411 if module != Color::Light {
412 canvas.draw_dark_rect((x as u32 + qz) * mw, top, mw, mh);
413 }
414 }
415 }
416
417 Ok(canvas.into_image())
418 }
419
420 pub fn build(&self) -> P::Image {
427 self.try_build().unwrap_or_else(|err| panic!("{err}"))
428 }
429}
430
431impl<C, P> qrcode_core::Renderer<C> for Renderer<'_, P>
432where
433 C: qrcode_core::ModuleSource + ?Sized,
434 P: Pixel,
435{
436 type Output = P::Image;
437 type Error = RenderError;
438
439 fn render(&self, code: &C) -> Result<Self::Output, Self::Error> {
440 let mut renderer = Renderer::try_from_source(code, self.quiet_zone)?;
441 renderer.module_size = self.module_size;
442 renderer.dark_color = self.dark_color;
443 renderer.light_color = self.light_color;
444 renderer.has_quiet_zone = self.has_quiet_zone;
445 renderer.try_build()
446 }
447}
448
449impl<'a, P: Pixel> qrcode_core::Builder for &'a Renderer<'a, P> {
450 type Output = P::Image;
451 type Error = RenderError;
452
453 fn build(self) -> Result<Self::Output, Self::Error> {
454 Ok(Renderer::build(self))
459 }
460}
461
462impl<'a, P: StyledPixel> Renderer<'a, P> {
463 pub fn template<T: RenderTemplate>(mut self, tmpl: &T) -> Self {
467 self.dark_color = P::from_hex(tmpl.dark_color());
468 self.light_color = P::from_hex(tmpl.light_color());
469 if let Some((w, h)) = tmpl.module_size() {
470 self.module_size = (w, h);
471 }
472 self.has_quiet_zone = tmpl.quiet_zone();
473 self
474 }
475}
476
477#[cfg(test)]
480mod tests {
481 use super::{RenderError, Renderer};
482 use qrcode_core::{Color, EcLevel, ModuleSource, QrSymbol, Renderer as CoreRenderer, Version};
483
484 struct BadSource {
485 modules: [Color; 4],
486 }
487
488 impl ModuleSource for BadSource {
489 fn get(&self, x: usize, y: usize) -> Color {
490 self.modules[y * self.width() + x]
491 }
492
493 fn width(&self) -> usize {
494 3
495 }
496
497 fn height(&self) -> usize {
498 2
499 }
500
501 fn modules(&self) -> &[Color] {
502 &self.modules
503 }
504 }
505
506 struct SymbolSource {
507 version: Version,
508 modules: [Color; 1],
509 }
510
511 impl ModuleSource for SymbolSource {
512 fn get(&self, _x: usize, _y: usize) -> Color {
513 self.modules[0]
514 }
515
516 fn width(&self) -> usize {
517 1
518 }
519
520 fn height(&self) -> usize {
521 1
522 }
523
524 fn modules(&self) -> &[Color] {
525 &self.modules
526 }
527 }
528
529 impl QrSymbol for SymbolSource {
530 fn version(&self) -> Version {
531 self.version
532 }
533
534 fn error_correction_level(&self) -> EcLevel {
535 EcLevel::M
536 }
537 }
538
539 #[test]
540 fn try_from_source_returns_error_for_invalid_dimensions() {
541 let source = BadSource { modules: [Color::Dark; 4] };
542
543 let result = Renderer::<char>::try_from_source(&source, 0);
544 assert!(matches!(result, Err(RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 })));
545 }
546
547 #[test]
548 fn core_renderer_returns_error_for_invalid_source() {
549 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
550 let renderer = Renderer::<char>::new(&modules, 2, 0);
551 let source = BadSource { modules: [Color::Dark; 4] };
552
553 assert_eq!(
554 CoreRenderer::render(&renderer, &source).unwrap_err(),
555 RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 }
556 );
557 }
558
559 #[test]
560 fn core_renderer_matches_direct_builder_output() {
561 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
562 let source = qrcode_core::ModuleView::new(&modules, 2).unwrap();
563 let mut renderer = Renderer::<char>::new(&modules, 2, 1);
564 renderer.dark_color('#').light_color('.');
565
566 assert_eq!(CoreRenderer::render(&renderer, &source).unwrap(), renderer.build());
567 }
568
569 #[test]
570 fn core_builder_returns_the_same_output_as_inherent_builder() {
571 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
572 let mut renderer = Renderer::<char>::new(&modules, 2, 1);
573 renderer.dark_color('#').light_color('.').module_dimensions(2, 3);
574 let expected = renderer.build();
575
576 assert_eq!(qrcode_core::Builder::build(&renderer), Ok(expected));
577 }
578
579 #[test]
580 fn try_build_rejects_overflowing_dimensions() {
581 let modules = [Color::Dark];
582 let mut renderer = Renderer::<char>::new(&modules, 1, u32::MAX);
583 renderer.module_dimensions(u32::MAX, 1);
584
585 assert_eq!(renderer.try_build(), Err(RenderError::OutputTooLarge));
586 }
587
588 #[test]
589 fn build_keeps_quiet_zone_while_scanning_only_source_modules() {
590 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
591 let mut renderer = Renderer::<char>::new(&modules, 2, 1);
592 renderer.dark_color('#').light_color('.').module_dimensions(1, 1);
593
594 assert_eq!(renderer.build(), "....\n.#..\n..#.\n....");
595 }
596
597 #[test]
598 fn from_symbol_uses_normal_qr_quiet_zone() {
599 let source = SymbolSource { version: Version::Normal(1), modules: [Color::Dark] };
600
601 let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
602
603 assert_eq!(output.lines().next().map(str::len), Some(9));
604 }
605
606 #[test]
607 fn from_symbol_uses_micro_qr_quiet_zone() {
608 let source = SymbolSource { version: Version::Micro(1), modules: [Color::Dark] };
609
610 let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
611
612 assert_eq!(output.lines().next().map(str::len), Some(5));
613 }
614}