#![cfg_attr(feature = "image", doc = "```rust")]
#![cfg_attr(not(feature = "image"), doc = "```ignore")]
#![cfg_attr(feature = "bench", feature(test, external_doc))] #![deny(warnings, clippy::pedantic)]
#![allow(
clippy::must_use_candidate, // This is just annoying.
)]
#![cfg_attr(feature = "bench", doc(include = "../README.md"))]
use std::ops::Index;
pub mod bits;
pub mod canvas;
mod cast;
pub mod ec;
pub mod optimize;
pub mod render;
pub mod types;
pub use crate::types::{Color, EcLevel, QrResult, Version};
use crate::cast::As;
use crate::render::{Pixel, Renderer};
use checked_int_cast::CheckedIntCast;
#[derive(Clone)]
pub struct QrCode {
content: Vec<Color>,
version: Version,
ec_level: EcLevel,
width: usize,
}
impl QrCode {
pub fn new<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
Self::with_error_correction_level(data, EcLevel::M)
}
pub fn with_error_correction_level<D: AsRef<[u8]>>(data: D, ec_level: EcLevel) -> QrResult<Self> {
let bits = bits::encode_auto(data.as_ref(), ec_level)?;
Self::with_bits(bits, ec_level)
}
pub fn with_version<D: AsRef<[u8]>>(data: D, version: Version, ec_level: EcLevel) -> QrResult<Self> {
let mut bits = bits::Bits::new(version);
bits.push_optimal_data(data.as_ref())?;
bits.push_terminator(ec_level)?;
Self::with_bits(bits, ec_level)
}
pub fn with_bits(bits: bits::Bits, ec_level: EcLevel) -> QrResult<Self> {
let version = bits.version();
let data = bits.into_bytes();
let (encoded_data, ec_data) = ec::construct_codewords(&*data, version, ec_level)?;
let mut canvas = canvas::Canvas::new(version, ec_level);
canvas.draw_all_functional_patterns();
canvas.draw_data(&*encoded_data, &*ec_data);
let canvas = canvas.apply_best_mask();
Ok(Self { content: canvas.into_colors(), version, ec_level, width: version.width().as_usize() })
}
pub fn version(&self) -> Version {
self.version
}
pub fn error_correction_level(&self) -> EcLevel {
self.ec_level
}
pub fn width(&self) -> usize {
self.width
}
pub fn max_allowed_errors(&self) -> usize {
ec::max_allowed_errors(self.version, self.ec_level).expect("invalid version or ec_level")
}
pub fn is_functional(&self, x: usize, y: usize) -> bool {
let x = x.as_i16_checked().expect("coordinate is too large for QR code");
let y = y.as_i16_checked().expect("coordinate is too large for QR code");
canvas::is_functional(self.version, self.version.width(), x, y)
}
pub fn to_debug_str(&self, on_char: char, off_char: char) -> String {
self.render().quiet_zone(false).dark_color(on_char).light_color(off_char).build()
}
#[deprecated(since = "0.4.0", note = "use `to_colors()` instead")]
pub fn to_vec(&self) -> Vec<bool> {
self.content.iter().map(|c| *c != Color::Light).collect()
}
#[deprecated(since = "0.4.0", note = "use `into_colors()` instead")]
pub fn into_vec(self) -> Vec<bool> {
self.content.into_iter().map(|c| c != Color::Light).collect()
}
pub fn to_colors(&self) -> Vec<Color> {
self.content.clone()
}
pub fn into_colors(self) -> Vec<Color> {
self.content
}
#[cfg_attr(feature = "image", doc = " ```rust")]
#[cfg_attr(not(feature = "image"), doc = " ```ignore")]
pub fn render<P: Pixel>(&self) -> Renderer<P> {
let quiet_zone = if self.version.is_micro() { 2 } else { 4 };
Renderer::new(&self.content, self.width, quiet_zone)
}
}
impl Index<(usize, usize)> for QrCode {
type Output = Color;
fn index(&self, (x, y): (usize, usize)) -> &Color {
let index = y * self.width + x;
&self.content[index]
}
}
#[cfg(test)]
mod tests {
use crate::{EcLevel, QrCode, Version};
#[test]
fn test_annex_i_qr() {
let code = QrCode::with_version(b"01234567", Version::Normal(1), EcLevel::M).unwrap();
assert_eq!(
&*code.to_debug_str('#', '.'),
"\
#######..#.##.#######\n\
#.....#..####.#.....#\n\
#.###.#.#.....#.###.#\n\
#.###.#.##....#.###.#\n\
#.###.#.#.###.#.###.#\n\
#.....#.#...#.#.....#\n\
#######.#.#.#.#######\n\
........#..##........\n\
#.#####..#..#.#####..\n\
...#.#.##.#.#..#.##..\n\
..#...##.#.#.#..#####\n\
....#....#.....####..\n\
...######..#.#..#....\n\
........#.#####..##..\n\
#######..##.#.##.....\n\
#.....#.#.#####...#.#\n\
#.###.#.#...#..#.##..\n\
#.###.#.##..#..#.....\n\
#.###.#.#.##.#..#.#..\n\
#.....#........##.##.\n\
#######.####.#..#.#.."
);
}
#[test]
fn test_annex_i_micro_qr() {
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
assert_eq!(
&*code.to_debug_str('#', '.'),
"\
#######.#.#.#\n\
#.....#.###.#\n\
#.###.#..##.#\n\
#.###.#..####\n\
#.###.#.###..\n\
#.....#.#...#\n\
#######..####\n\
.........##..\n\
##.#....#...#\n\
.##.#.#.#.#.#\n\
###..#######.\n\
...#.#....##.\n\
###.#..##.###"
);
}
}
#[cfg(all(test, feature = "image"))]
mod image_tests {
use crate::{EcLevel, QrCode, Version};
use image::{load_from_memory, Luma, Rgb};
#[test]
fn test_annex_i_qr_as_image() {
let code = QrCode::new(b"01234567").unwrap();
let image = code.render::<Luma<u8>>().build();
let expected = load_from_memory(include_bytes!("test_annex_i_qr_as_image.png")).unwrap().to_luma8();
assert_eq!(image.dimensions(), expected.dimensions());
assert_eq!(image.into_raw(), expected.into_raw());
}
#[test]
fn test_annex_i_micro_qr_as_image() {
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
let image = code
.render()
.min_dimensions(200, 200)
.dark_color(Rgb([128, 0, 0]))
.light_color(Rgb([255, 255, 128]))
.build();
let expected = load_from_memory(include_bytes!("test_annex_i_micro_qr_as_image.png")).unwrap().to_rgb8();
assert_eq!(image.dimensions(), expected.dimensions());
assert_eq!(image.into_raw(), expected.into_raw());
}
}
#[cfg(all(test, feature = "svg"))]
mod svg_tests {
use crate::render::svg::Color as SvgColor;
use crate::{EcLevel, QrCode, Version};
#[test]
fn test_annex_i_qr_as_svg() {
let code = QrCode::new(b"01234567").unwrap();
let image = code.render::<SvgColor>().build();
let expected = include_str!("test_annex_i_qr_as_svg.svg");
assert_eq!(&image, expected);
}
#[test]
fn test_annex_i_micro_qr_as_svg() {
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
let image = code
.render()
.min_dimensions(200, 200)
.dark_color(SvgColor("#800000"))
.light_color(SvgColor("#ffff80"))
.build();
let expected = include_str!("test_annex_i_micro_qr_as_svg.svg");
assert_eq!(&image, expected);
}
}