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
157impl fmt::Display for RenderError {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 match self {
160 RenderError::InvalidModuleSource { width, height, len } => {
161 write!(f, "invalid module source dimensions: width={width}, height={height}, len={len}")
162 }
163 RenderError::ModuleSourceTooWide { width } => write!(f, "module source width {width} exceeds u32::MAX"),
164 }
165 }
166}
167
168#[cfg(feature = "std")]
169impl std::error::Error for RenderError {}
170
171pub struct Renderer<'a, P: Pixel> {
174 content: &'a [Color],
175 modules_count: u32, quiet_zone: u32,
177 module_size: (u32, u32),
178
179 dark_color: P,
180 light_color: P,
181 has_quiet_zone: bool,
182}
183
184impl<'a, P: Pixel> Renderer<'a, P> {
185 pub fn new(content: &'a [Color], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> {
190 assert_eq!(modules_count * modules_count, content.len());
191 Renderer {
192 content,
193 modules_count: modules_count.as_u32(),
194 quiet_zone,
195 module_size: P::default_unit_size(),
196 dark_color: P::default_color(Color::Dark),
197 light_color: P::default_color(Color::Light),
198 has_quiet_zone: true,
199 }
200 }
201
202 pub fn from_source<C>(source: &'a C, quiet_zone: u32) -> Renderer<'a, P>
214 where
215 C: qrcode_core::ModuleSource + ?Sized,
216 {
217 match Self::try_from_source(source, quiet_zone) {
218 Ok(renderer) => renderer,
219 Err(err) => panic!("{err}"),
220 }
221 }
222
223 pub fn from_symbol<S>(symbol: &'a S) -> Renderer<'a, P>
232 where
233 S: qrcode_core::QrSymbol + ?Sized,
234 {
235 Self::from_source(symbol, symbol.quiet_zone())
236 }
237
238 pub fn try_from_source<C>(source: &'a C, quiet_zone: u32) -> Result<Renderer<'a, P>, RenderError>
250 where
251 C: qrcode_core::ModuleSource + ?Sized,
252 {
253 let width = source.width();
254 let height = source.height();
255 let len = source.modules().len();
256 let Some(expected_len) = width.checked_mul(height) else {
257 return Err(RenderError::InvalidModuleSource { width, height, len });
258 };
259 if width == 0 || width != height || len != expected_len {
260 return Err(RenderError::InvalidModuleSource { width, height, len });
261 }
262 if width > u32::MAX as usize {
263 return Err(RenderError::ModuleSourceTooWide { width });
264 }
265 Ok(Self::new(source.modules(), width, quiet_zone))
266 }
267
268 pub fn try_from_symbol<S>(symbol: &'a S) -> Result<Renderer<'a, P>, RenderError>
279 where
280 S: qrcode_core::QrSymbol + ?Sized,
281 {
282 Self::try_from_source(symbol, symbol.quiet_zone())
283 }
284
285 pub fn dark_color(&mut self, color: P) -> &mut Self {
287 self.dark_color = color;
288 self
289 }
290
291 pub fn light_color(&mut self, color: P) -> &mut Self {
293 self.light_color = color;
294 self
295 }
296
297 pub fn quiet_zone(&mut self, has_quiet_zone: bool) -> &mut Self {
299 self.has_quiet_zone = has_quiet_zone;
300 self
301 }
302
303 pub fn module_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
305 self.module_size = (max(width, 1), max(height, 1));
306 self
307 }
308
309 pub fn min_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
318 let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
319 let width_in_modules = self.modules_count + quiet_zone;
320 let unit_width = width.div_ceil(width_in_modules);
321 let unit_height = height.div_ceil(width_in_modules);
322 self.module_dimensions(unit_width, unit_height)
323 }
324
325 pub fn max_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
337 let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
338 let width_in_modules = self.modules_count + quiet_zone;
339 let unit_width = width / width_in_modules;
340 let unit_height = height / width_in_modules;
341 self.module_dimensions(unit_width, unit_height)
342 }
343
344 pub fn for_web(&mut self) -> &mut Self {
349 self.min_dimensions(200, 200)
350 }
351
352 pub fn for_print(&mut self, dpi: u32) -> &mut Self {
361 self.min_dimensions(dpi.max(72), dpi.max(72))
362 }
363
364 pub fn for_social(&mut self, platform: &str) -> &mut Self {
376 let size = match platform {
377 "twitter" | "x" => 400,
378 "facebook" | "fb" => 600,
379 "instagram" | "ig" => 1080,
380 "wechat" | "weixin" => 600,
381 _ => 400,
382 };
383 self.min_dimensions(size, size)
384 }
385
386 pub fn build(&self) -> P::Image {
388 let w = self.modules_count;
389 let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 };
390 let width = w + 2 * qz;
391
392 let (mw, mh) = self.module_size;
393 let real_width = width * mw;
394 let real_height = width * mh;
395
396 let mut canvas = P::Canvas::new(real_width, real_height, self.dark_color, self.light_color);
397 let mut i = 0;
398 for y in 0..width {
399 for x in 0..width {
400 if qz <= x && x < w + qz && qz <= y && y < w + qz {
401 if self.content[i] != Color::Light {
402 canvas.draw_dark_rect(x * mw, y * mh, mw, mh);
403 }
404 i += 1;
405 }
406 }
407 }
408
409 canvas.into_image()
410 }
411}
412
413impl<C, P> qrcode_core::Renderer<C> for Renderer<'_, P>
414where
415 C: qrcode_core::ModuleSource + ?Sized,
416 P: Pixel,
417{
418 type Output = P::Image;
419 type Error = RenderError;
420
421 fn render(&self, code: &C) -> Result<Self::Output, Self::Error> {
422 let mut renderer = Renderer::try_from_source(code, self.quiet_zone)?;
423 renderer.module_size = self.module_size;
424 renderer.dark_color = self.dark_color;
425 renderer.light_color = self.light_color;
426 renderer.has_quiet_zone = self.has_quiet_zone;
427 Ok(renderer.build())
428 }
429}
430
431impl<'a, P: Pixel> qrcode_core::Builder for &'a Renderer<'a, P> {
432 type Output = P::Image;
433 type Error = RenderError;
434
435 fn build(self) -> Result<Self::Output, Self::Error> {
436 Ok(Renderer::build(self))
441 }
442}
443
444impl<'a, P: StyledPixel> Renderer<'a, P> {
445 pub fn template<T: RenderTemplate>(mut self, tmpl: &T) -> Self {
449 self.dark_color = P::from_hex(tmpl.dark_color());
450 self.light_color = P::from_hex(tmpl.light_color());
451 if let Some((w, h)) = tmpl.module_size() {
452 self.module_size = (w, h);
453 }
454 self.has_quiet_zone = tmpl.quiet_zone();
455 self
456 }
457}
458
459#[cfg(test)]
462mod tests {
463 use super::{RenderError, Renderer};
464 use qrcode_core::{Color, EcLevel, ModuleSource, QrSymbol, Renderer as CoreRenderer, Version};
465
466 struct BadSource {
467 modules: [Color; 4],
468 }
469
470 impl ModuleSource for BadSource {
471 fn get(&self, x: usize, y: usize) -> Color {
472 self.modules[y * self.width() + x]
473 }
474
475 fn width(&self) -> usize {
476 3
477 }
478
479 fn height(&self) -> usize {
480 2
481 }
482
483 fn modules(&self) -> &[Color] {
484 &self.modules
485 }
486 }
487
488 struct SymbolSource {
489 version: Version,
490 modules: [Color; 1],
491 }
492
493 impl ModuleSource for SymbolSource {
494 fn get(&self, _x: usize, _y: usize) -> Color {
495 self.modules[0]
496 }
497
498 fn width(&self) -> usize {
499 1
500 }
501
502 fn height(&self) -> usize {
503 1
504 }
505
506 fn modules(&self) -> &[Color] {
507 &self.modules
508 }
509 }
510
511 impl QrSymbol for SymbolSource {
512 fn version(&self) -> Version {
513 self.version
514 }
515
516 fn error_correction_level(&self) -> EcLevel {
517 EcLevel::M
518 }
519 }
520
521 #[test]
522 fn try_from_source_returns_error_for_invalid_dimensions() {
523 let source = BadSource { modules: [Color::Dark; 4] };
524
525 let result = Renderer::<char>::try_from_source(&source, 0);
526 assert!(matches!(result, Err(RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 })));
527 }
528
529 #[test]
530 fn core_renderer_returns_error_for_invalid_source() {
531 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
532 let renderer = Renderer::<char>::new(&modules, 2, 0);
533 let source = BadSource { modules: [Color::Dark; 4] };
534
535 assert_eq!(
536 CoreRenderer::render(&renderer, &source).unwrap_err(),
537 RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 }
538 );
539 }
540
541 #[test]
542 fn core_renderer_matches_direct_builder_output() {
543 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
544 let source = qrcode_core::ModuleView::new(&modules, 2).unwrap();
545 let mut renderer = Renderer::<char>::new(&modules, 2, 1);
546 renderer.dark_color('#').light_color('.');
547
548 assert_eq!(CoreRenderer::render(&renderer, &source).unwrap(), renderer.build());
549 }
550
551 #[test]
552 fn core_builder_returns_the_same_output_as_inherent_builder() {
553 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
554 let mut renderer = Renderer::<char>::new(&modules, 2, 1);
555 renderer.dark_color('#').light_color('.').module_dimensions(2, 3);
556 let expected = renderer.build();
557
558 assert_eq!(qrcode_core::Builder::build(&renderer), Ok(expected));
559 }
560
561 #[test]
562 fn from_symbol_uses_normal_qr_quiet_zone() {
563 let source = SymbolSource { version: Version::Normal(1), modules: [Color::Dark] };
564
565 let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
566
567 assert_eq!(output.lines().next().map(str::len), Some(9));
568 }
569
570 #[test]
571 fn from_symbol_uses_micro_qr_quiet_zone() {
572 let source = SymbolSource { version: Version::Micro(1), modules: [Color::Dark] };
573
574 let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
575
576 assert_eq!(output.lines().next().map(str::len), Some(5));
577 }
578}