#![deny(missing_docs)]
#![deny(warnings)]
#![allow(
clippy::must_use_candidate, // This is just annoying.
clippy::use_self, // Rust 1.33 doesn't support Self::EnumVariant, let's try again in 1.37.
clippy::match_like_matches_macro, // MSRV is lower than what's needed for matches!
clippy::wrong_self_convention, // TODO fix code and remove
)]
#![cfg_attr(feature = "bench", doc(include = "../README.md"))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![cfg_attr(bench, feature(test))]
#[cfg(bench)]
extern crate test;
#[cfg(feature = "bmp")]
pub extern crate bmp_monochrome;
use std::ops::Index;
pub mod bits;
pub mod canvas;
mod cast;
pub mod ec;
pub mod optimize;
mod render;
pub mod types;
#[cfg(feature = "fuzz")]
mod fuzz;
#[cfg(feature = "fuzz")]
pub use crate::fuzz::{split_merge_rt, QrCodeData};
#[cfg(all(feature = "bmp", feature = "decode"))]
pub mod decode;
pub mod structured;
pub use crate::types::{Color, EcLevel, QrResult, Version};
use crate::cast::As;
#[derive(Clone, Debug)]
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();
let y = y.as_i16();
canvas::is_functional(self.version, self.version.width(), x, y)
}
pub fn to_vec(&self) -> Vec<bool> {
self.content.iter().map(|c| *c != Color::Light).collect()
}
pub fn iter(&self) -> QrCodeIterator {
QrCodeIterator::new(self)
}
pub fn into_colors(self) -> Vec<Color> {
self.content
}
}
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]
}
}
pub struct QrCodeIterator<'a> {
qr_code: &'a QrCode,
index: usize,
}
impl<'a> QrCodeIterator<'a> {
fn new(qr_code: &'a QrCode) -> Self {
let index = 0;
QrCodeIterator { qr_code, index }
}
}
impl<'a> Iterator for QrCodeIterator<'a> {
type Item = bool;
fn next(&mut self) -> Option<bool> {
let result = self
.qr_code
.content
.get(self.index)
.map(|c| c == &Color::Dark);
self.index += 1;
result
}
}
#[cfg(test)]
mod tests {
use super::{EcLevel, QrCode, Version};
use crate::types::QrError;
use std::time::{Duration, Instant};
#[cfg(all(feature = "bmp", feature = "decode"))]
#[test]
fn test_roundtrip() {
use crate::decode::BmpDecode;
use bmp_monochrome::Bmp;
use rand::distributions::Alphanumeric;
use rand::Rng;
use std::io::Cursor;
let rand_string: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.collect();
let qr_code = QrCode::new(rand_string.as_bytes()).unwrap();
let mut cursor = Cursor::new(vec![]);
qr_code
.to_bmp()
.mul(3)
.unwrap()
.add_white_border(3)
.unwrap()
.write(&mut cursor)
.unwrap();
cursor.set_position(0);
let bmp = Bmp::read(cursor).unwrap();
let result = bmp.normalize().decode().unwrap();
let decoded = std::str::from_utf8(&result).unwrap();
assert_eq!(rand_string, decoded);
}
#[test]
fn test_with_version() {
let qr_code = QrCode::with_version(b"test", Version::Normal(1), EcLevel::H).unwrap();
assert_eq!(qr_code.version(), Version::Normal(1));
assert_eq!(qr_code.error_correction_level(), EcLevel::H);
assert_eq!(qr_code.max_allowed_errors(), 8);
}
#[test]
fn test_equivalence_of_pixel_accessors() {
let qr_code = QrCode::new(b"test").unwrap();
let output_via_iter = qr_code.iter().collect::<Vec<_>>();
let output_via_to_vec = qr_code.to_vec();
let mut output_via_index = vec![];
for y in 0..qr_code.width() {
for x in 0..qr_code.width() {
output_via_index.push(qr_code[(x, y)].select(true, false));
}
}
assert_eq!(output_via_iter.as_slice(), output_via_to_vec.as_slice());
assert_eq!(output_via_iter.as_slice(), output_via_index.as_slice());
}
#[test]
fn test_very_large_input() {
let start = Instant::now();
let input = {
let stencil = include_bytes!("../test_data/large_base64.in");
const TARGET_LEN: usize = 10 * 1024 * 1024;
let data = stencil.repeat(TARGET_LEN / stencil.len() + 1);
assert!(data.len() >= TARGET_LEN);
data
};
let err = QrCode::new(&*input).unwrap_err();
assert_eq!(err, QrError::DataTooLong);
assert!(start.elapsed() < Duration::from_secs(1));
}
}
#[cfg(bench)]
pub(crate) mod bench {
use super::{EcLevel, QrCode};
#[bench]
fn bench_qr_code_with_low_ecc(bencher: &mut test::Bencher) {
bencher.iter(|| {
let data = include_bytes!("../test_data/large_base64.in");
let qr_code = QrCode::with_error_correction_level(data, EcLevel::L).unwrap();
qr_code.iter().map(|b| if b { 1 } else { 0 }).sum::<usize>()
});
}
}