#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use alloc::{
borrow::ToOwned,
format,
string::{String, ToString},
vec,
vec::Vec,
};
use core::cmp::max;
use core::fmt;
use qrcode_core::As;
pub use qrcode_core::Color;
pub mod ansi;
pub mod colors;
#[cfg(feature = "image")]
pub mod image;
pub mod plugin;
pub mod string;
pub mod unicode;
pub trait Pixel: Copy + Sized {
type Image: Sized + 'static;
type Canvas: Canvas<Pixel = Self, Image = Self::Image>;
fn default_unit_size() -> (u32, u32) {
(8, 8)
}
fn default_color(color: Color) -> Self;
}
pub trait StyledPixel: Pixel {
fn from_hex(hex: &str) -> Self;
}
pub trait RenderTemplate {
fn dark_color(&self) -> &str;
fn light_color(&self) -> &str;
fn module_size(&self) -> Option<(u32, u32)>;
fn quiet_zone(&self) -> bool;
}
pub trait Canvas: Sized {
type Pixel: Sized;
type Image: Sized;
fn new(width: u32, height: u32, dark_pixel: Self::Pixel, light_pixel: Self::Pixel) -> Self;
fn draw_dark_pixel(&mut self, x: u32, y: u32);
fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) {
for y in top..(top + height) {
for x in left..(left + width) {
self.draw_dark_pixel(x, y);
}
}
}
fn into_image(self) -> Self::Image;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RenderError {
InvalidModuleSource {
width: usize,
height: usize,
len: usize,
},
ModuleSourceTooWide {
width: usize,
},
}
impl fmt::Display for RenderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RenderError::InvalidModuleSource { width, height, len } => {
write!(f, "invalid module source dimensions: width={width}, height={height}, len={len}")
}
RenderError::ModuleSourceTooWide { width } => write!(f, "module source width {width} exceeds u32::MAX"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for RenderError {}
pub struct Renderer<'a, P: Pixel> {
content: &'a [Color],
modules_count: u32, quiet_zone: u32,
module_size: (u32, u32),
dark_color: P,
light_color: P,
has_quiet_zone: bool,
}
impl<'a, P: Pixel> Renderer<'a, P> {
pub fn new(content: &'a [Color], modules_count: usize, quiet_zone: u32) -> Renderer<'a, P> {
assert_eq!(modules_count * modules_count, content.len());
Renderer {
content,
modules_count: modules_count.as_u32(),
quiet_zone,
module_size: P::default_unit_size(),
dark_color: P::default_color(Color::Dark),
light_color: P::default_color(Color::Light),
has_quiet_zone: true,
}
}
pub fn from_source<C>(source: &'a C, quiet_zone: u32) -> Renderer<'a, P>
where
C: qrcode_core::ModuleSource + ?Sized,
{
match Self::try_from_source(source, quiet_zone) {
Ok(renderer) => renderer,
Err(err) => panic!("{err}"),
}
}
pub fn from_symbol<S>(symbol: &'a S) -> Renderer<'a, P>
where
S: qrcode_core::QrSymbol + ?Sized,
{
Self::from_source(symbol, symbol.quiet_zone())
}
pub fn try_from_source<C>(source: &'a C, quiet_zone: u32) -> Result<Renderer<'a, P>, RenderError>
where
C: qrcode_core::ModuleSource + ?Sized,
{
let width = source.width();
let height = source.height();
let len = source.modules().len();
let Some(expected_len) = width.checked_mul(height) else {
return Err(RenderError::InvalidModuleSource { width, height, len });
};
if width == 0 || width != height || len != expected_len {
return Err(RenderError::InvalidModuleSource { width, height, len });
}
if width > u32::MAX as usize {
return Err(RenderError::ModuleSourceTooWide { width });
}
Ok(Self::new(source.modules(), width, quiet_zone))
}
pub fn try_from_symbol<S>(symbol: &'a S) -> Result<Renderer<'a, P>, RenderError>
where
S: qrcode_core::QrSymbol + ?Sized,
{
Self::try_from_source(symbol, symbol.quiet_zone())
}
pub fn dark_color(&mut self, color: P) -> &mut Self {
self.dark_color = color;
self
}
pub fn light_color(&mut self, color: P) -> &mut Self {
self.light_color = color;
self
}
pub fn quiet_zone(&mut self, has_quiet_zone: bool) -> &mut Self {
self.has_quiet_zone = has_quiet_zone;
self
}
pub fn module_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
self.module_size = (max(width, 1), max(height, 1));
self
}
pub fn min_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
let width_in_modules = self.modules_count + quiet_zone;
let unit_width = width.div_ceil(width_in_modules);
let unit_height = height.div_ceil(width_in_modules);
self.module_dimensions(unit_width, unit_height)
}
pub fn max_dimensions(&mut self, width: u32, height: u32) -> &mut Self {
let quiet_zone = if self.has_quiet_zone { 2 } else { 0 } * self.quiet_zone;
let width_in_modules = self.modules_count + quiet_zone;
let unit_width = width / width_in_modules;
let unit_height = height / width_in_modules;
self.module_dimensions(unit_width, unit_height)
}
pub fn for_web(&mut self) -> &mut Self {
self.min_dimensions(200, 200)
}
pub fn for_print(&mut self, dpi: u32) -> &mut Self {
self.min_dimensions(dpi.max(72), dpi.max(72))
}
pub fn for_social(&mut self, platform: &str) -> &mut Self {
let size = match platform {
"twitter" | "x" => 400,
"facebook" | "fb" => 600,
"instagram" | "ig" => 1080,
"wechat" | "weixin" => 600,
_ => 400,
};
self.min_dimensions(size, size)
}
pub fn build(&self) -> P::Image {
let w = self.modules_count;
let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 };
let width = w + 2 * qz;
let (mw, mh) = self.module_size;
let real_width = width * mw;
let real_height = width * mh;
let mut canvas = P::Canvas::new(real_width, real_height, self.dark_color, self.light_color);
let mut i = 0;
for y in 0..width {
for x in 0..width {
if qz <= x && x < w + qz && qz <= y && y < w + qz {
if self.content[i] != Color::Light {
canvas.draw_dark_rect(x * mw, y * mh, mw, mh);
}
i += 1;
}
}
}
canvas.into_image()
}
}
impl<C, P> qrcode_core::Renderer<C> for Renderer<'_, P>
where
C: qrcode_core::ModuleSource + ?Sized,
P: Pixel,
{
type Output = P::Image;
type Error = RenderError;
fn render(&self, code: &C) -> Result<Self::Output, Self::Error> {
let mut renderer = Renderer::try_from_source(code, self.quiet_zone)?;
renderer.module_size = self.module_size;
renderer.dark_color = self.dark_color;
renderer.light_color = self.light_color;
renderer.has_quiet_zone = self.has_quiet_zone;
Ok(renderer.build())
}
}
impl<'a, P: StyledPixel> Renderer<'a, P> {
pub fn template<T: RenderTemplate>(mut self, tmpl: &T) -> Self {
self.dark_color = P::from_hex(tmpl.dark_color());
self.light_color = P::from_hex(tmpl.light_color());
if let Some((w, h)) = tmpl.module_size() {
self.module_size = (w, h);
}
self.has_quiet_zone = tmpl.quiet_zone();
self
}
}
#[cfg(test)]
mod tests {
use super::{RenderError, Renderer};
use qrcode_core::{Color, EcLevel, ModuleSource, QrSymbol, Renderer as CoreRenderer, Version};
struct BadSource {
modules: [Color; 4],
}
impl ModuleSource for BadSource {
fn get(&self, x: usize, y: usize) -> Color {
self.modules[y * self.width() + x]
}
fn width(&self) -> usize {
3
}
fn height(&self) -> usize {
2
}
fn modules(&self) -> &[Color] {
&self.modules
}
}
struct SymbolSource {
version: Version,
modules: [Color; 1],
}
impl ModuleSource for SymbolSource {
fn get(&self, _x: usize, _y: usize) -> Color {
self.modules[0]
}
fn width(&self) -> usize {
1
}
fn height(&self) -> usize {
1
}
fn modules(&self) -> &[Color] {
&self.modules
}
}
impl QrSymbol for SymbolSource {
fn version(&self) -> Version {
self.version
}
fn error_correction_level(&self) -> EcLevel {
EcLevel::M
}
}
#[test]
fn try_from_source_returns_error_for_invalid_dimensions() {
let source = BadSource { modules: [Color::Dark; 4] };
let result = Renderer::<char>::try_from_source(&source, 0);
assert!(matches!(result, Err(RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 })));
}
#[test]
fn core_renderer_returns_error_for_invalid_source() {
let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
let renderer = Renderer::<char>::new(&modules, 2, 0);
let source = BadSource { modules: [Color::Dark; 4] };
assert_eq!(
CoreRenderer::render(&renderer, &source).unwrap_err(),
RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 }
);
}
#[test]
fn core_renderer_matches_direct_builder_output() {
let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
let source = qrcode_core::ModuleView::new(&modules, 2).unwrap();
let mut renderer = Renderer::<char>::new(&modules, 2, 1);
renderer.dark_color('#').light_color('.');
assert_eq!(CoreRenderer::render(&renderer, &source).unwrap(), renderer.build());
}
#[test]
fn from_symbol_uses_normal_qr_quiet_zone() {
let source = SymbolSource { version: Version::Normal(1), modules: [Color::Dark] };
let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
assert_eq!(output.lines().next().map(str::len), Some(9));
}
#[test]
fn from_symbol_uses_micro_qr_quiet_zone() {
let source = SymbolSource { version: Version::Micro(1), modules: [Color::Dark] };
let output = Renderer::<char>::from_symbol(&source).dark_color('#').light_color('.').build();
assert_eq!(output.lines().next().map(str::len), Some(5));
}
}