#![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 const MAX_BUFFER_BYTES: usize = 256 * 1024 * 1024;
pub(crate) fn checked_area(width: u32, height: u32) -> Result<usize, RenderError> {
(width as usize).checked_mul(height as usize).ok_or(RenderError::OutputTooLarge)
}
pub(crate) fn check_buffer_size(bytes: usize) -> Result<(), RenderError> {
if bytes > MAX_BUFFER_BYTES || bytes > isize::MAX as usize { Err(RenderError::OutputTooLarge) } else { Ok(()) }
}
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 validate_dimensions(
_width: u32,
_height: u32,
_dark_pixel: &Self::Pixel,
_light_pixel: &Self::Pixel,
) -> Result<(), RenderError> {
Ok(())
}
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,
},
OutputTooLarge,
}
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"),
RenderError::OutputTooLarge => f.write_str("rendered output exceeds coordinate or backend resource limits"),
}
}
}
#[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 * u64::from(self.quiet_zone) } else { 0 };
let width_in_modules = (u64::from(self.modules_count) + quiet_zone).max(1);
let unit_width = u64::from(width).div_ceil(width_in_modules) as u32;
let unit_height = u64::from(height).div_ceil(width_in_modules) as u32;
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 * u64::from(self.quiet_zone) } else { 0 };
let width_in_modules = (u64::from(self.modules_count) + quiet_zone).max(1);
let unit_width = (u64::from(width) / width_in_modules) as u32;
let unit_height = (u64::from(height) / width_in_modules) as u32;
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 try_build(&self) -> Result<P::Image, RenderError> {
let w = self.modules_count;
if w == 0 {
return Err(RenderError::InvalidModuleSource { width: 0, height: 0, len: self.content.len() });
}
let qz = if self.has_quiet_zone { self.quiet_zone } else { 0 };
let quiet = qz.checked_mul(2).ok_or(RenderError::OutputTooLarge)?;
let width = w.checked_add(quiet).ok_or(RenderError::OutputTooLarge)?;
let (mw, mh) = self.module_size;
let real_width = width.checked_mul(mw).ok_or(RenderError::OutputTooLarge)?;
let real_height = width.checked_mul(mh).ok_or(RenderError::OutputTooLarge)?;
P::Canvas::validate_dimensions(real_width, real_height, &self.dark_color, &self.light_color)?;
let mut canvas = P::Canvas::new(real_width, real_height, self.dark_color, self.light_color);
for (y, row) in self.content.chunks_exact(w as usize).enumerate() {
let top = (y as u32 + qz) * mh;
for (x, &module) in row.iter().enumerate() {
if module != Color::Light {
canvas.draw_dark_rect((x as u32 + qz) * mw, top, mw, mh);
}
}
}
Ok(canvas.into_image())
}
pub fn build(&self) -> P::Image {
self.try_build().unwrap_or_else(|err| panic!("{err}"))
}
}
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;
renderer.try_build()
}
}
impl<'a, P: Pixel> qrcode_core::Builder for &'a Renderer<'a, P> {
type Output = P::Image;
type Error = RenderError;
fn build(self) -> Result<Self::Output, Self::Error> {
self.try_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_dimensions(w, h);
}
self.has_quiet_zone = tmpl.quiet_zone();
self
}
}
#[cfg(test)]
mod tests {
use super::{Canvas, Pixel, RenderError, RenderTemplate, Renderer};
use qrcode_core::{Color, EcLevel, ModuleSource, QrSymbol, Renderer as CoreRenderer, Version};
#[derive(Clone, Copy)]
struct VectorPixel;
struct VectorCanvas(u32, u32);
impl Pixel for VectorPixel {
type Image = (u32, u32);
type Canvas = VectorCanvas;
fn default_color(_color: Color) -> Self {
Self
}
}
impl Canvas for VectorCanvas {
type Pixel = VectorPixel;
type Image = (u32, u32);
fn new(width: u32, height: u32, _dark: VectorPixel, _light: VectorPixel) -> Self {
Self(width, height)
}
fn draw_dark_pixel(&mut self, _x: u32, _y: u32) {}
fn draw_dark_rect(&mut self, _left: u32, _top: u32, _width: u32, _height: u32) {}
fn into_image(self) -> Self::Image {
(self.0, self.1)
}
}
#[derive(Clone, Copy)]
struct RejectingPixel;
struct RejectingCanvas;
impl Pixel for RejectingPixel {
type Image = ();
type Canvas = RejectingCanvas;
fn default_color(_color: Color) -> Self {
Self
}
}
impl Canvas for RejectingCanvas {
type Pixel = RejectingPixel;
type Image = ();
fn new(_width: u32, _height: u32, _dark: RejectingPixel, _light: RejectingPixel) -> Self {
panic!("rejected dimensions must not reach allocation")
}
fn validate_dimensions(
_width: u32,
_height: u32,
_dark: &RejectingPixel,
_light: &RejectingPixel,
) -> Result<(), RenderError> {
Err(RenderError::OutputTooLarge)
}
fn draw_dark_pixel(&mut self, _x: u32, _y: u32) {}
fn into_image(self) {}
}
#[test]
fn default_canvas_validation_preserves_large_vector_dimensions() {
let modules = [Color::Dark];
assert_eq!(
Renderer::<VectorPixel>::new(&modules, 1, 0).module_dimensions(u32::MAX, u32::MAX).try_build(),
Ok((u32::MAX, u32::MAX))
);
}
#[test]
fn try_build_validates_backend_limits_before_constructing_canvas() {
let modules = [Color::Light];
assert_eq!(Renderer::<RejectingPixel>::new(&modules, 1, 0).try_build(), Err(RenderError::OutputTooLarge));
}
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 core_builder_returns_the_same_output_as_inherent_builder() {
let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
let mut renderer = Renderer::<char>::new(&modules, 2, 1);
renderer.dark_color('#').light_color('.').module_dimensions(2, 3);
let expected = renderer.build();
assert_eq!(qrcode_core::Builder::build(&renderer), Ok(expected));
}
#[test]
fn core_builder_returns_dimension_errors_without_panicking() {
let modules = [Color::Dark];
for (quiet_zone, module_width, module_height) in [(u32::MAX, 1, 1), (1, u32::MAX, 1), (1, 1, u32::MAX)] {
let mut renderer = Renderer::<char>::new(&modules, 1, quiet_zone);
renderer.module_dimensions(module_width, module_height);
assert_eq!(qrcode_core::Builder::build(&renderer), Err(RenderError::OutputTooLarge));
}
}
#[test]
fn empty_module_grid_returns_errors_before_creating_a_canvas() {
let mut renderer = Renderer::<char>::new(&[], 0, 0);
let error = RenderError::InvalidModuleSource { width: 0, height: 0, len: 0 };
assert_eq!(renderer.try_build(), Err(error));
assert_eq!(qrcode_core::Builder::build(&renderer), Err(error));
assert_eq!(renderer.min_dimensions(100, 100).try_build(), Err(error));
assert_eq!(renderer.max_dimensions(100, 100).try_build(), Err(error));
}
#[test]
fn dimension_presets_defer_oversized_quiet_zone_errors_to_try_build() {
let modules = [Color::Dark; 4];
for quiet_zone in [u32::MAX, u32::MAX / 2] {
let mut renderer = Renderer::<char>::new(&modules, 2, quiet_zone);
assert_eq!(renderer.min_dimensions(100, 100).try_build(), Err(RenderError::OutputTooLarge));
assert_eq!(renderer.max_dimensions(100, 100).try_build(), Err(RenderError::OutputTooLarge));
}
}
#[test]
fn dimension_presets_ignore_a_disabled_quiet_zone() {
let modules = [Color::Dark];
let mut renderer = Renderer::<char>::new(&modules, 1, u32::MAX);
renderer.quiet_zone(false).dark_color('#');
assert_eq!(renderer.min_dimensions(1, 1).try_build(), Ok("#".into()));
assert_eq!(renderer.max_dimensions(1, 1).try_build(), Ok("#".into()));
}
#[test]
fn template_module_dimensions_follow_the_module_size_setter() {
struct ModuleSizeTemplate((u32, u32));
impl RenderTemplate for ModuleSizeTemplate {
fn dark_color(&self) -> &str {
"#000"
}
fn light_color(&self) -> &str {
"#fff"
}
fn module_size(&self) -> Option<(u32, u32)> {
Some(self.0)
}
fn quiet_zone(&self) -> bool {
false
}
}
let modules = [Color::Dark];
for dimensions in [(0, 0), (0, 2), (2, 0), (2, 3)] {
let template = ModuleSizeTemplate(dimensions);
let renderer = Renderer::<super::ansi::Color>::new(&modules, 1, 0).template(&template);
let mut manual = Renderer::<super::ansi::Color>::new(&modules, 1, 0);
manual.module_dimensions(dimensions.0, dimensions.1).quiet_zone(false);
assert_eq!(renderer.try_build(), manual.try_build());
}
}
#[test]
fn try_build_rejects_overflowing_dimensions() {
let modules = [Color::Dark];
let mut renderer = Renderer::<char>::new(&modules, 1, u32::MAX);
renderer.module_dimensions(u32::MAX, 1);
assert_eq!(renderer.try_build(), Err(RenderError::OutputTooLarge));
}
#[test]
fn build_keeps_quiet_zone_while_scanning_only_source_modules() {
let modules = [Color::Dark, Color::Light, Color::Light, Color::Dark];
let mut renderer = Renderer::<char>::new(&modules, 2, 1);
renderer.dark_color('#').light_color('.').module_dimensions(1, 1);
assert_eq!(renderer.build(), "....\n.#..\n..#.\n....");
}
#[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));
}
}