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: StyledPixel> Renderer<'a, P> {
432 pub fn template<T: RenderTemplate>(mut self, tmpl: &T) -> Self {
436 self.dark_color = P::from_hex(tmpl.dark_color());
437 self.light_color = P::from_hex(tmpl.light_color());
438 if let Some((w, h)) = tmpl.module_size() {
439 self.module_size = (w, h);
440 }
441 self.has_quiet_zone = tmpl.quiet_zone();
442 self
443 }
444}
445
446#[cfg(test)]
449mod tests {
450 use super::{RenderError, Renderer};
451 use qrcode_core::{Color, EcLevel, ModuleSource, QrSymbol, Renderer as CoreRenderer, Version};
452
453 struct BadSource {
454 modules: [Color; 4],
455 }
456
457 impl ModuleSource for BadSource {
458 fn get(&self, x: usize, y: usize) -> Color {
459 self.modules[y * self.width() + x]
460 }
461
462 fn width(&self) -> usize {
463 3
464 }
465
466 fn height(&self) -> usize {
467 2
468 }
469
470 fn modules(&self) -> &[Color] {
471 &self.modules
472 }
473 }
474
475 struct SymbolSource {
476 version: Version,
477 modules: [Color; 1],
478 }
479
480 impl ModuleSource for SymbolSource {
481 fn get(&self, _x: usize, _y: usize) -> Color {
482 self.modules[0]
483 }
484
485 fn width(&self) -> usize {
486 1
487 }
488
489 fn height(&self) -> usize {
490 1
491 }
492
493 fn modules(&self) -> &[Color] {
494 &self.modules
495 }
496 }
497
498 impl QrSymbol for SymbolSource {
499 fn version(&self) -> Version {
500 self.version
501 }
502
503 fn error_correction_level(&self) -> EcLevel {
504 EcLevel::M
505 }
506 }
507
508 #[test]
509 fn try_from_source_returns_error_for_invalid_dimensions() {
510 let source = BadSource { modules: [Color::Dark; 4] };
511
512 let result = Renderer::<char>::try_from_source(&source, 0);
513 assert!(matches!(result, Err(RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 })));
514 }
515
516 #[test]
517 fn core_renderer_returns_error_for_invalid_source() {
518 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
519 let renderer = Renderer::<char>::new(&modules, 2, 0);
520 let source = BadSource { modules: [Color::Dark; 4] };
521
522 assert_eq!(
523 CoreRenderer::render(&renderer, &source).unwrap_err(),
524 RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 }
525 );
526 }
527
528 #[test]
529 fn core_renderer_matches_direct_builder_output() {
530 let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
531 let source = qrcode_core::ModuleView::new(&modules, 2).unwrap();
532 let mut renderer = Renderer::<char>::new(&modules, 2, 1);
533 renderer.dark_color('#').light_color('.');
534
535 assert_eq!(CoreRenderer::render(&renderer, &source).unwrap(), renderer.build());
536 }
537
538 #[test]
539 fn from_symbol_uses_normal_qr_quiet_zone() {
540 let source = SymbolSource { version: Version::Normal(1), modules: [Color::Dark] };
541
542 let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
543
544 assert_eq!(output.lines().next().map(str::len), Some(9));
545 }
546
547 #[test]
548 fn from_symbol_uses_micro_qr_quiet_zone() {
549 let source = SymbolSource { version: Version::Micro(1), modules: [Color::Dark] };
550
551 let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
552
553 assert_eq!(output.lines().next().map(str::len), Some(5));
554 }
555}