#![cfg_attr(feature = "image", doc = "```rust")]
#![cfg_attr(not(feature = "image"), doc = "```ignore")]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(missing_docs)]
#![deny(clippy::uninlined_format_args, clippy::manual_range_contains, clippy::semicolon_if_nothing_returned)]
#![allow(
clippy::must_use_candidate, // This is just annoying.
)]
extern crate alloc;
pub mod render;
pub mod structured_append;
pub use qrcode_core::ConstVersion;
pub use qrcode_core::{
AlphanumericMode, ByteMode, DynEncoder, DynRenderer, EncodeConfig, EncodedOutput, EncoderFactory, EncodingMode,
KanjiMode, ModuleGrid, NumericMode, PluginError, PluginRegistry, PostProcessor, QrPlugin, RenderConfig,
RenderOutput, RendererFactory, StaticVersion,
};
pub use qrcode_core::{bits, canvas, ec, optimize, plugin, traits, types};
pub use qrcode_decode as decode;
pub use qrcode_parse as parse;
pub use crate::types::{Color, EcLevel, Mode, QrError, QrResult, Version};
pub use qrcode_core::QrCodeRef;
use qrcode_core::cast;
pub use qrcode_core::traits::{
Builder, Encoder, ModuleSource, ModuleStorage, ModuleView, QrSymbol, Renderer as CoreRenderer,
};
#[cfg(not(feature = "std"))]
#[allow(unused_imports)]
use alloc::{
borrow::ToOwned,
format,
string::{String, ToString},
vec,
vec::Vec,
};
use crate::cast::As;
use crate::render::{Pixel, Renderer};
use core::iter::FusedIterator;
use core::ops::Index;
#[derive(Clone)]
pub struct QrCode {
content: Vec<Color>,
version: Version,
ec_level: EcLevel,
width: usize,
}
pub struct QrCodePlugins<'a> {
code: &'a QrCode,
registry: &'a PluginRegistry,
}
impl QrCodePlugins<'_> {
#[must_use]
pub fn code(&self) -> &QrCode {
self.code
}
#[must_use]
pub fn registry(&self) -> &PluginRegistry {
self.registry
}
pub fn render(&self, renderer_name: &str, config: &RenderConfig) -> Result<RenderOutput, PluginError> {
self.code.render_with(self.registry, renderer_name, config)
}
}
const _: () = {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<QrCode>();
};
impl QrCode {
pub fn new<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
AutoEncoder::default().encode(data.as_ref())
}
pub fn with_error_correction_level<D: AsRef<[u8]>>(data: D, ec_level: EcLevel) -> QrResult<Self> {
AutoEncoder::new(ec_level).encode(data.as_ref())
}
pub fn new_micro<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
MicroEncoder::default().encode(data.as_ref())
}
pub fn micro_with_error_correction_level<D: AsRef<[u8]>>(data: D, ec_level: EcLevel) -> QrResult<Self> {
MicroEncoder::new(ec_level).encode(data.as_ref())
}
pub fn with_version<D: AsRef<[u8]>>(data: D, version: Version, ec_level: EcLevel) -> QrResult<Self> {
VersionEncoder::new(version, ec_level).encode(data.as_ref())
}
pub fn with_const_version<const N: i16, D: AsRef<[u8]>>(data: D, ec_level: EcLevel) -> QrResult<Self> {
ConstVersionEncoder::<N>::new(ec_level).encode(data.as_ref())
}
fn encode_auto(data: &[u8], ec_level: EcLevel) -> QrResult<Self> {
let bits = bits::encode_auto(data, ec_level)?;
Self::with_bits(bits, ec_level)
}
fn encode_auto_micro(data: &[u8], ec_level: EcLevel) -> QrResult<Self> {
let bits = bits::encode_auto_micro(data, ec_level)?;
Self::with_bits(bits, ec_level)
}
fn encode_with_version(data: &[u8], version: Version, ec_level: EcLevel) -> QrResult<Self> {
let mut bits = bits::Bits::new(version);
bits.push_optimal_data(data)?;
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();
#[cfg(feature = "log")]
log::debug!("qrcode_rs: encoding at version {version:?}, ec {ec_level:?}");
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();
let width = version.width().as_usize();
#[cfg(feature = "log")]
log::info!("qrcode_rs: encoded version {version:?} ec {ec_level:?} ({} modules)", width * width);
Ok(Self { content: canvas.into_colors(), version, ec_level, width })
}
pub fn batch<I, D>(inputs: I, ec_level: EcLevel) -> QrResult<Vec<Self>>
where
I: IntoIterator<Item = D>,
D: AsRef<[u8]>,
{
Self::stream_with_error_correction_level(inputs, ec_level).collect()
}
pub fn stream<I, D>(inputs: I) -> QrCodeStream<I::IntoIter>
where
I: IntoIterator<Item = D>,
D: AsRef<[u8]>,
{
Self::stream_with_error_correction_level(inputs, EcLevel::M)
}
pub fn stream_with_error_correction_level<I, D>(inputs: I, ec_level: EcLevel) -> QrCodeStream<I::IntoIter>
where
I: IntoIterator<Item = D>,
D: AsRef<[u8]>,
{
QrCodeStream { inputs: inputs.into_iter(), ec_level }
}
pub const fn version(&self) -> Version {
self.version
}
pub const fn error_correction_level(&self) -> EcLevel {
self.ec_level
}
pub const 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")
}
#[must_use]
pub fn info(&self) -> Info {
Info {
version: self.version,
ec_level: self.ec_level,
width: self.width,
module_count: self.width * self.width,
max_allowed_errors: self.max_allowed_errors(),
data_capacity_bytes: bits::data_capacity_bits(self.version, self.ec_level).map(|b| b / 8).unwrap_or(0),
}
}
#[must_use]
pub fn analyze(&self) -> Analysis {
let total = self.width * self.width;
let dark = self.content.iter().filter(|c| **c == Color::Dark).count();
let functional =
(0..self.width).map(|y| (0..self.width).filter(|x| self.is_functional(*x, y)).count()).sum::<usize>();
Analysis {
dark_ratio: if total == 0 { 0.0 } else { dark as f64 / total as f64 },
functional_modules: functional,
data_modules: total - functional,
}
}
pub fn is_functional(&self, x: usize, y: usize) -> bool {
let x = x.try_into().expect("coordinate is too large for QR code");
let y = y.try_into().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()
}
pub fn colors(&self) -> &[Color] {
&self.content
}
#[must_use]
pub fn module_view(&self) -> ModuleView<'_> {
ModuleView::new(&self.content, self.width).expect("QrCode stores a non-empty square module grid")
}
#[must_use]
pub fn as_ref(&self) -> QrCodeRef<'_> {
QrCodeRef::new(&self.content, self.width, self.version, self.ec_level)
.expect("QrCode stores a non-empty square module grid")
}
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> {
Renderer::from_symbol(self)
}
pub fn render_builder<P: Pixel>(&self) -> Renderer<'_, P> {
self.render()
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn render_async<P>(&self) -> Result<P::Image, tokio::task::JoinError>
where
P: Pixel + Send + 'static,
P::Image: Send + 'static,
{
let code = self.clone();
tokio::task::spawn_blocking(move || code.render::<P>().build()).await
}
pub fn encode_with(
registry: &PluginRegistry,
encoder_name: &str,
input: &[u8],
config: &EncodeConfig,
) -> Result<EncodedOutput, PluginError> {
registry.build_encoder(encoder_name, config)?.encode(input)
}
pub fn render_with(
&self,
registry: &PluginRegistry,
renderer_name: &str,
config: &RenderConfig,
) -> Result<RenderOutput, PluginError> {
let mut modules = ModuleGrid::new(self.content.clone(), self.width, self.width)?;
registry.process_modules(&mut modules)?;
registry.build_renderer(renderer_name, config)?.render(&modules)
}
pub fn with_plugins<'a>(&'a self, registry: &'a PluginRegistry) -> QrCodePlugins<'a> {
QrCodePlugins { code: self, registry }
}
}
impl QrCode {
pub fn builder<D: AsRef<[u8]>>(data: D) -> QrCodeBuilder<D> {
QrCodeBuilder::new(data)
}
pub fn rows(&self) -> Rows<'_> {
Rows { code: self, y: 0 }
}
pub fn dark_modules(&self) -> DarkModules<'_> {
DarkModules { code: self, idx: 0 }
}
pub fn for_url<D: AsRef<[u8]>>(url: D) -> QrResult<Self> {
Self::with_error_correction_level(url, EcLevel::H)
}
pub fn for_text<D: AsRef<[u8]>>(text: D) -> QrResult<Self> {
Self::new(text)
}
pub fn for_wifi(ssid: &str, password: &str, auth: &str) -> QrResult<Self> {
Self::new(parse::wifi::encode_wifi(ssid, password, auth))
}
pub fn for_vcard(name: &str, phone: &str, email: &str) -> QrResult<Self> {
Self::new(parse::vcard::encode_vcard(name, phone, email))
}
pub fn for_gs1<D: AsRef<[u8]>>(data: D) -> QrResult<Self> {
let data = data.as_ref();
for v in 1..=40 {
let version = Version::Normal(v);
let mut bits = bits::Bits::new(version);
if bits.push_fnc1_first_position().is_err()
|| bits.push_optimal_data(data).is_err()
|| bits.push_terminator(EcLevel::M).is_err()
{
continue;
}
return Self::with_bits(bits, EcLevel::M);
}
Err(QrError::DataTooLong)
}
pub fn structured_append<D: AsRef<[u8]>>(payload: D, symbols: u8, ec: EcLevel) -> QrResult<Vec<Self>> {
let sa = structured_append::StructuredAppend::new(symbols, payload.as_ref())?;
sa.encode(ec)
}
#[must_use]
pub fn alt_text<D: AsRef<[u8]>>(data: D) -> String {
let text = String::from_utf8_lossy(data.as_ref());
if text.starts_with("http://") || text.starts_with("https://") {
format!("QR code linking to {text}")
} else {
format!("QR code containing: {text}")
}
}
#[must_use]
pub fn alt_text_custom<D: AsRef<[u8]>, F: FnOnce(&[u8]) -> String>(data: D, f: F) -> String {
f(data.as_ref())
}
fn with_mode<D: AsRef<[u8]>>(data: D, version: Version, ec_level: EcLevel, mode: Mode) -> QrResult<Self> {
let mut bits = bits::Bits::new(version);
match mode {
Mode::Numeric => bits.push_numeric_data(data.as_ref())?,
Mode::Alphanumeric => bits.push_alphanumeric_data(data.as_ref())?,
Mode::Byte => bits.push_byte_data(data.as_ref())?,
Mode::Kanji => bits.push_kanji_data(data.as_ref())?,
}
bits.push_terminator(ec_level)?;
Self::with_bits(bits, ec_level)
}
fn with_mode_auto<D: AsRef<[u8]>>(data: D, ec_level: EcLevel, mode: Mode) -> QrResult<Self> {
let data = data.as_ref();
let mut last_err = QrError::DataTooLong;
for v in 1..=40 {
let version = Version::Normal(v);
let mut bits = bits::Bits::new(version);
let pushed = match mode {
Mode::Numeric => bits.push_numeric_data(data),
Mode::Alphanumeric => bits.push_alphanumeric_data(data),
Mode::Byte => bits.push_byte_data(data),
Mode::Kanji => bits.push_kanji_data(data),
};
if let Err(e) = pushed {
last_err = e;
continue;
}
if let Err(e) = bits.push_terminator(ec_level) {
last_err = e;
continue;
}
return Self::with_bits(bits, ec_level);
}
Err(last_err)
}
}
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]
}
}
impl ModuleStorage for QrCode {
fn get(&self, x: usize, y: usize) -> Color {
self[(x, y)]
}
fn set(&mut self, x: usize, y: usize, color: Color) {
let index = y * self.width + x;
self.content[index] = color;
}
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.width
}
fn modules(&self) -> &[Color] {
self.colors()
}
}
impl QrSymbol for QrCode {
fn version(&self) -> Version {
self.version
}
fn error_correction_level(&self) -> EcLevel {
self.ec_level
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AutoEncoder {
ec_level: EcLevel,
}
impl AutoEncoder {
#[must_use]
pub const fn new(ec_level: EcLevel) -> Self {
Self { ec_level }
}
#[must_use]
pub const fn ec_level(&self) -> EcLevel {
self.ec_level
}
}
impl Default for AutoEncoder {
fn default() -> Self {
Self { ec_level: EcLevel::M }
}
}
impl Encoder for AutoEncoder {
type Output = QrCode;
type Error = QrError;
fn encode(&self, input: &[u8]) -> QrResult<QrCode> {
QrCode::encode_auto(input, self.ec_level)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MicroEncoder {
ec_level: EcLevel,
}
impl MicroEncoder {
#[must_use]
pub const fn new(ec_level: EcLevel) -> Self {
Self { ec_level }
}
#[must_use]
pub const fn ec_level(&self) -> EcLevel {
self.ec_level
}
}
impl Default for MicroEncoder {
fn default() -> Self {
Self { ec_level: EcLevel::M }
}
}
impl Encoder for MicroEncoder {
type Output = QrCode;
type Error = QrError;
fn encode(&self, input: &[u8]) -> QrResult<QrCode> {
QrCode::encode_auto_micro(input, self.ec_level)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct VersionEncoder {
version: Version,
ec_level: EcLevel,
}
impl VersionEncoder {
#[must_use]
pub const fn new(version: Version, ec_level: EcLevel) -> Self {
Self { version, ec_level }
}
#[must_use]
pub const fn version(&self) -> Version {
self.version
}
#[must_use]
pub const fn ec_level(&self) -> EcLevel {
self.ec_level
}
}
impl Encoder for VersionEncoder {
type Output = QrCode;
type Error = QrError;
fn encode(&self, input: &[u8]) -> QrResult<QrCode> {
QrCode::encode_with_version(input, self.version, self.ec_level)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ConstVersionEncoder<const N: i16> {
ec_level: EcLevel,
}
impl<const N: i16> ConstVersionEncoder<N> {
#[must_use]
pub const fn new(ec_level: EcLevel) -> Self {
let _ = ConstVersion::<N>::VALUE;
Self { ec_level }
}
#[must_use]
pub const fn version(&self) -> Version {
ConstVersion::<N>::VALUE
}
#[must_use]
pub const fn ec_level(&self) -> EcLevel {
self.ec_level
}
}
impl<const N: i16> Encoder for ConstVersionEncoder<N> {
type Output = QrCode;
type Error = QrError;
fn encode(&self, input: &[u8]) -> QrResult<QrCode> {
QrCode::encode_with_version(input, ConstVersion::<N>::VALUE, self.ec_level)
}
}
#[derive(Clone, Debug)]
pub struct QrCodeBuilder<D: AsRef<[u8]>> {
data: D,
ec_level: EcLevel,
version: Option<Version>,
micro: bool,
mode_hint: Option<Mode>,
}
impl<D: AsRef<[u8]>> QrCodeBuilder<D> {
fn new(data: D) -> Self {
Self { data, ec_level: EcLevel::M, version: None, micro: false, mode_hint: None }
}
#[must_use]
pub fn ec_level(mut self, ec_level: EcLevel) -> Self {
self.ec_level = ec_level;
self
}
#[must_use]
pub fn version(mut self, version: Version) -> Self {
self.version = Some(version);
self
}
#[must_use]
pub fn micro(mut self, yes: bool) -> Self {
self.micro = yes;
self
}
#[must_use]
pub fn encoding_mode(mut self, mode: Mode) -> Self {
self.mode_hint = Some(mode);
self
}
pub fn encoding_mode_typed<M: EncodingMode>(mut self) -> QrResult<Self> {
if let Some((position, byte)) = M::invalid_character(self.data.as_ref()) {
return Err(QrError::InvalidCharacter { position, byte });
}
self.mode_hint = Some(M::MODE);
Ok(self)
}
#[must_use]
pub fn force_mode(self, mode: Mode) -> Self {
self.encoding_mode(mode)
}
pub fn force_mode_typed<M: EncodingMode>(self) -> QrResult<Self> {
self.encoding_mode_typed::<M>()
}
pub fn build(self) -> QrResult<QrCode> {
if let Some(version) = self.version {
if let Some(mode) = self.mode_hint {
return QrCode::with_mode(self.data, version, self.ec_level, mode);
}
return QrCode::with_version(self.data, version, self.ec_level);
}
if let Some(mode) = self.mode_hint {
return QrCode::with_mode_auto(self.data, self.ec_level, mode);
}
if self.micro {
return QrCode::micro_with_error_correction_level(self.data, self.ec_level);
}
QrCode::with_error_correction_level(self.data, self.ec_level)
}
}
impl<D: AsRef<[u8]>> Builder for QrCodeBuilder<D> {
type Output = QrCode;
type Error = QrError;
fn build(self) -> QrResult<QrCode> {
QrCodeBuilder::build(self)
}
}
#[derive(Clone, Debug)]
pub struct QrCodeStream<I> {
inputs: I,
ec_level: EcLevel,
}
impl<I> Iterator for QrCodeStream<I>
where
I: Iterator,
I::Item: AsRef<[u8]>,
{
type Item = QrResult<QrCode>;
fn next(&mut self) -> Option<Self::Item> {
self.inputs.next().map(|input| QrCode::with_error_correction_level(input, self.ec_level))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inputs.size_hint()
}
}
impl<I> FusedIterator for QrCodeStream<I>
where
I: FusedIterator,
I::Item: AsRef<[u8]>,
{
}
impl<I> ExactSizeIterator for QrCodeStream<I>
where
I: ExactSizeIterator,
I::Item: AsRef<[u8]>,
{
fn len(&self) -> usize {
self.inputs.len()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct Info {
version: Version,
ec_level: EcLevel,
width: usize,
module_count: usize,
max_allowed_errors: usize,
data_capacity_bytes: usize,
}
impl Info {
#[must_use]
pub const fn version(&self) -> Version {
self.version
}
#[must_use]
pub const fn ec_level(&self) -> EcLevel {
self.ec_level
}
#[must_use]
pub const fn width(&self) -> usize {
self.width
}
#[must_use]
pub const fn module_count(&self) -> usize {
self.module_count
}
#[must_use]
pub const fn max_allowed_errors(&self) -> usize {
self.max_allowed_errors
}
#[must_use]
pub const fn data_capacity_bytes(&self) -> usize {
self.data_capacity_bytes
}
}
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct QrCodeData {
pub version: Version,
pub ec_level: EcLevel,
pub width: usize,
pub content: Vec<Color>,
}
#[cfg(feature = "serde")]
impl QrCode {
#[must_use]
pub fn to_serializable(&self) -> QrCodeData {
QrCodeData { version: self.version, ec_level: self.ec_level, width: self.width, content: self.content.clone() }
}
#[must_use]
pub fn from_serializable(data: QrCodeData) -> Self {
debug_assert_eq!(data.content.len(), data.width * data.width, "malformed QrCodeData");
Self { content: data.content, version: data.version, ec_level: data.ec_level, width: data.width }
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[non_exhaustive]
pub struct Analysis {
dark_ratio: f64,
functional_modules: usize,
data_modules: usize,
}
impl Analysis {
#[must_use]
pub const fn dark_ratio(&self) -> f64 {
self.dark_ratio
}
#[must_use]
pub const fn functional_modules(&self) -> usize {
self.functional_modules
}
#[must_use]
pub const fn data_modules(&self) -> usize {
self.data_modules
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct QrTemplate {
pub dark_color: String,
pub light_color: String,
pub module_size: Option<(u32, u32)>,
pub quiet_zone: bool,
}
impl QrTemplate {
#[must_use]
pub fn minimal() -> Self {
Self { dark_color: "#000000".into(), light_color: "#ffffff".into(), module_size: None, quiet_zone: true }
}
#[must_use]
pub fn dark_mode() -> Self {
Self { dark_color: "#e0e0e0".into(), light_color: "#1a1a2e".into(), module_size: None, quiet_zone: true }
}
#[must_use]
pub fn high_contrast() -> Self {
Self { dark_color: "#000000".into(), light_color: "#ffffff".into(), module_size: None, quiet_zone: true }
}
#[must_use]
pub fn corporate() -> Self {
Self { dark_color: "#003366".into(), light_color: "#ffffff".into(), module_size: None, quiet_zone: true }
}
}
impl qrcode_render::RenderTemplate for QrTemplate {
fn dark_color(&self) -> &str {
&self.dark_color
}
fn light_color(&self) -> &str {
&self.light_color
}
fn module_size(&self) -> Option<(u32, u32)> {
self.module_size
}
fn quiet_zone(&self) -> bool {
self.quiet_zone
}
}
pub struct Rows<'a> {
code: &'a QrCode,
y: usize,
}
impl<'a> Iterator for Rows<'a> {
type Item = Row<'a>;
fn next(&mut self) -> Option<Self::Item> {
let w = self.code.width;
if self.y < w {
let row = Row { code: self.code, y: self.y, x: 0 };
self.y += 1;
Some(row)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let rem = self.code.width - self.y;
(rem, Some(rem))
}
}
impl<'a> ExactSizeIterator for Rows<'a> {
fn len(&self) -> usize {
self.code.width - self.y
}
}
impl<'a> FusedIterator for Rows<'a> {}
pub struct Row<'a> {
code: &'a QrCode,
y: usize,
x: usize,
}
impl<'a> Row<'a> {
#[must_use]
pub fn len(&self) -> usize {
self.code.width
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.code.width == 0
}
}
impl<'a> Iterator for Row<'a> {
type Item = Color;
fn next(&mut self) -> Option<Color> {
let w = self.code.width;
if self.x < w {
let color = self.code.content[self.y * w + self.x];
self.x += 1;
Some(color)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let rem = self.code.width - self.x;
(rem, Some(rem))
}
}
impl<'a> ExactSizeIterator for Row<'a> {
fn len(&self) -> usize {
self.code.width - self.x
}
}
impl<'a> FusedIterator for Row<'a> {}
pub struct DarkModules<'a> {
code: &'a QrCode,
idx: usize,
}
impl<'a> Iterator for DarkModules<'a> {
type Item = (usize, usize);
fn next(&mut self) -> Option<(usize, usize)> {
let w = self.code.width;
let content = &self.code.content;
while self.idx < content.len() {
let i = self.idx;
self.idx += 1;
if content[i] == Color::Dark {
return Some((i % w, i / w));
}
}
None
}
}
impl<'a> FusedIterator for DarkModules<'a> {}
#[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(test)]
mod api_tests {
use crate::{
AutoEncoder, Builder as CoreBuilder, Color, ConstVersion, ConstVersionEncoder, DynEncoder, DynRenderer,
EcLevel, EncodeConfig, EncodedOutput, EncoderFactory, MicroEncoder, Mode, ModuleView, NumericMode,
PluginRegistry, PostProcessor, QrCode, QrError, QrSymbol, RenderConfig, RenderOutput, RendererFactory, Version,
VersionEncoder,
};
use alloc::{
boxed::Box,
string::{String, ToString},
vec,
vec::Vec,
};
use qrcode_core::traits::{
Encoder as CoreEncoder, ModuleSource as CoreModuleSource, ModuleStorage as CoreModuleStorage,
Renderer as CoreRenderer,
};
fn colors(code: &QrCode) -> Vec<Color> {
code.to_colors()
}
struct TextPluginRenderer;
impl DynRenderer for TextPluginRenderer {
fn render(&self, code: &dyn CoreModuleSource) -> Result<RenderOutput, crate::PluginError> {
let mut output = String::new();
for y in 0..code.height() {
for x in 0..code.width() {
output.push(if code.get(x, y) == Color::Dark { '#' } else { '.' });
}
}
Ok(RenderOutput::Text(output))
}
}
struct TextPluginFactory;
impl RendererFactory for TextPluginFactory {
fn build(&self, _config: &RenderConfig) -> Box<dyn DynRenderer> {
Box::new(TextPluginRenderer)
}
}
struct LengthPluginEncoder;
impl DynEncoder for LengthPluginEncoder {
fn encode(&self, input: &[u8]) -> Result<EncodedOutput, crate::PluginError> {
Ok(EncodedOutput::Bytes(input.len().to_string().into_bytes()))
}
}
struct LengthPluginFactory;
impl EncoderFactory for LengthPluginFactory {
fn build(&self, _config: &EncodeConfig) -> Box<dyn DynEncoder> {
Box::new(LengthPluginEncoder)
}
}
struct DarkenFirstModule;
impl PostProcessor for DarkenFirstModule {
fn process(&self, modules: &mut dyn CoreModuleStorage) -> Result<(), crate::PluginError> {
modules.set(0, 0, Color::Dark);
Ok(())
}
}
#[test]
fn builder_matches_with_error_correction_level() {
let direct = QrCode::with_error_correction_level(b"Some data", EcLevel::H).unwrap();
let built = QrCode::builder(b"Some data").ec_level(EcLevel::H).build().unwrap();
assert_eq!(colors(&direct), colors(&built));
assert_eq!(direct.version(), built.version());
assert_eq!(direct.error_correction_level(), built.error_correction_level());
}
#[test]
fn stream_lazily_encodes_with_default_error_correction_level() {
let codes = QrCode::stream(["alpha", "beta"]).collect::<Result<Vec<_>, _>>().unwrap();
assert_eq!(codes.iter().map(QrCode::error_correction_level).collect::<Vec<_>>(), [EcLevel::M, EcLevel::M]);
}
#[test]
fn stream_with_error_correction_level_matches_batch() {
let inputs = ["alpha", "beta", "gamma"];
let streamed =
QrCode::stream_with_error_correction_level(inputs, EcLevel::H).collect::<Result<Vec<_>, _>>().unwrap();
let batched = QrCode::batch(inputs, EcLevel::H).unwrap();
assert_eq!(streamed.iter().map(colors).collect::<Vec<_>>(), batched.iter().map(colors).collect::<Vec<_>>());
}
#[test]
fn stream_exposes_exact_remaining_len() {
let mut stream = QrCode::stream(["alpha", "beta", "gamma"]);
assert_eq!(stream.len(), 3);
assert!(stream.next().unwrap().is_ok());
assert_eq!(stream.len(), 2);
}
#[test]
fn core_builder_trait_builds_qrcode_builder() {
let code = CoreBuilder::build(QrCode::builder(b"Some data").ec_level(EcLevel::H)).unwrap();
assert_eq!(code.error_correction_level(), EcLevel::H);
}
#[test]
fn auto_encoder_matches_constructor() {
let direct = QrCode::with_error_correction_level(b"Some data", EcLevel::H).unwrap();
let encoded = AutoEncoder::new(EcLevel::H).encode(b"Some data").unwrap();
assert_eq!(colors(&direct), colors(&encoded));
}
#[test]
fn micro_encoder_matches_constructor() {
let direct = QrCode::micro_with_error_correction_level(b"123", EcLevel::L).unwrap();
let encoded = MicroEncoder::new(EcLevel::L).encode(b"123").unwrap();
assert_eq!(colors(&direct), colors(&encoded));
}
#[test]
fn version_encoder_matches_constructor() {
let direct = QrCode::with_version(b"Some data", Version::Normal(1), EcLevel::M).unwrap();
let encoded = VersionEncoder::new(Version::Normal(1), EcLevel::M).encode(b"Some data").unwrap();
assert_eq!(colors(&direct), colors(&encoded));
}
#[test]
fn const_version_encoder_matches_dynamic_version() {
const V5: Version = ConstVersion::<5>::VALUE;
let direct = QrCode::with_version(b"Some data", Version::Normal(5), EcLevel::M).unwrap();
let const_ctor = QrCode::with_const_version::<5, _>(b"Some data", EcLevel::M).unwrap();
let encoded = ConstVersionEncoder::<5>::new(EcLevel::M).encode(b"Some data").unwrap();
assert_eq!(V5, Version::Normal(5));
assert_eq!(ConstVersion::<5>::new().version(), Version::Normal(5));
assert_eq!(ConstVersionEncoder::<5>::new(EcLevel::M).version(), Version::Normal(5));
assert_eq!(colors(&direct), colors(&const_ctor));
assert_eq!(colors(&direct), colors(&encoded));
}
#[test]
fn builder_matches_with_version() {
let direct = QrCode::with_version(b"Some data", Version::Normal(1), EcLevel::M).unwrap();
let built = QrCode::builder(b"Some data").version(Version::Normal(1)).build().unwrap();
assert_eq!(colors(&direct), colors(&built));
}
#[test]
fn builder_micro_matches() {
let direct = QrCode::micro_with_error_correction_level(b"123", EcLevel::L).unwrap();
let built = QrCode::builder(b"123").ec_level(EcLevel::L).micro(true).build().unwrap();
assert_eq!(colors(&direct), colors(&built));
assert!(built.version().is_micro());
}
#[test]
fn render_with_uses_registered_renderer_and_postprocessors() {
let code = QrCode::new(b"plugin").unwrap();
let mut registry = PluginRegistry::new();
registry.register_renderer("text", Box::new(TextPluginFactory));
registry.register_postprocessor(Box::new(DarkenFirstModule));
let output = code.render_with(®istry, "text", &RenderConfig::new()).unwrap();
let RenderOutput::Text(text) = output else {
panic!("expected text output");
};
assert_eq!(text.len(), code.width() * code.width());
assert!(text.starts_with('#'));
}
#[test]
fn with_plugins_renders_through_bound_registry() {
let code = QrCode::new(b"plugin").unwrap();
let mut registry = PluginRegistry::new();
registry.register_renderer("text", Box::new(TextPluginFactory));
registry.register_postprocessor(Box::new(DarkenFirstModule));
let bound = code.with_plugins(®istry);
let output = bound.render("text", &RenderConfig::new()).unwrap();
assert_eq!(bound.code().width(), code.width());
assert!(bound.registry().renderer("text").is_some());
assert_eq!(output, code.render_with(®istry, "text", &RenderConfig::new()).unwrap());
}
#[test]
fn render_with_reports_missing_renderer() {
let code = QrCode::new(b"plugin").unwrap();
let registry = PluginRegistry::new();
assert!(matches!(
code.render_with(®istry, "missing", &RenderConfig::new()),
Err(crate::PluginError::RendererNotFound(name)) if name == "missing"
));
}
#[test]
fn render_with_uses_builtin_plain_text_plugin() {
let code = QrCode::new(b"plugin").unwrap();
let mut registry = PluginRegistry::new();
registry.register_plugin(&crate::render::plugin::PlainTextRendererPlugin);
let config =
RenderConfig::new().with_option("dark", "X").with_option("light", ".").with_option("quiet_zone", "0");
let output = code
.render_with(®istry, crate::render::plugin::PlainTextRendererPlugin::RENDERER_NAME, &config)
.unwrap();
let expected =
code.render::<char>().dark_color('X').light_color('.').quiet_zone(false).module_dimensions(1, 1).build();
assert_eq!(output, RenderOutput::Text(expected));
}
#[test]
fn render_with_uses_builtin_invert_modules_plugin() {
let code = QrCode::new(b"plugin").unwrap();
let mut registry = PluginRegistry::new();
registry.register_plugin(&crate::render::plugin::PlainTextRendererPlugin);
registry.register_plugin(&crate::render::plugin::InvertModulesPlugin);
let config =
RenderConfig::new().with_option("dark", "X").with_option("light", ".").with_option("quiet_zone", "0");
let output = code
.render_with(®istry, crate::render::plugin::PlainTextRendererPlugin::RENDERER_NAME, &config)
.unwrap();
let direct =
code.render::<char>().dark_color('.').light_color('X').quiet_zone(false).module_dimensions(1, 1).build();
assert_eq!(output, RenderOutput::Text(direct));
}
#[test]
fn encode_with_uses_registered_encoder() {
let mut registry = PluginRegistry::new();
registry.register_encoder("length", Box::new(LengthPluginFactory));
let output = QrCode::encode_with(®istry, "length", b"abcd", &EncodeConfig::new()).unwrap();
assert_eq!(output, EncodedOutput::Bytes(b"4".to_vec()));
}
#[test]
fn encode_with_reports_missing_encoder() {
let registry = PluginRegistry::new();
assert!(matches!(
QrCode::encode_with(®istry, "missing", b"abcd", &EncodeConfig::new()),
Err(crate::PluginError::EncoderNotFound(name)) if name == "missing"
));
}
#[test]
fn builder_version_wins_over_micro() {
let built = QrCode::builder(b"01234567").version(Version::Micro(2)).micro(true).build().unwrap();
assert_eq!(built.version(), Version::Micro(2));
}
#[test]
fn builder_forces_byte_mode() {
let optimal = QrCode::builder(b"01234567").version(Version::Normal(2)).build().unwrap();
let byte = QrCode::builder(b"01234567").version(Version::Normal(2)).encoding_mode(Mode::Byte).build().unwrap();
assert_ne!(colors(&optimal), colors(&byte));
}
#[test]
fn builder_typed_encoding_mode_matches_dynamic_mode() {
let typed = QrCode::builder(b"01234567")
.version(Version::Normal(2))
.encoding_mode_typed::<NumericMode>()
.unwrap()
.build()
.unwrap();
let dynamic =
QrCode::builder(b"01234567").version(Version::Normal(2)).encoding_mode(Mode::Numeric).build().unwrap();
assert_eq!(colors(&typed), colors(&dynamic));
}
#[test]
fn builder_typed_encoding_mode_rejects_invalid_input() {
let err = QrCode::builder(b"12a").encoding_mode_typed::<NumericMode>().unwrap_err();
assert_eq!(err, QrError::InvalidCharacter { position: 2, byte: b'a' });
}
#[test]
fn rows_iterate_full_grid() {
let code = QrCode::new(b"hello").unwrap();
let w = code.width();
let rows: Vec<Vec<Color>> = code.rows().map(|r| r.collect()).collect();
assert_eq!(rows.len(), w);
assert!(rows.iter().all(|r| r.len() == w));
for y in 0..w {
for x in 0..w {
assert_eq!(rows[y][x], code[(x, y)]);
}
}
}
#[test]
fn rows_exact_size() {
let code = QrCode::new(b"hello").unwrap();
let mut rows = code.rows();
let total = rows.len();
let mut counted = 0;
while rows.next().is_some() {
counted += 1;
assert_eq!(rows.len(), total - counted);
}
}
#[test]
fn dark_modules_match_indexed_dark_cells() {
let code = QrCode::new(b"hello").unwrap();
let w = code.width();
let expected: Vec<(usize, usize)> =
(0..w).flat_map(|y| (0..w).map(move |x| (x, y))).filter(|&(x, y)| code[(x, y)] == Color::Dark).collect();
let actual: Vec<(usize, usize)> = code.dark_modules().collect();
assert_eq!(expected, actual);
}
#[test]
fn for_url_uses_high_ec() {
let code = QrCode::for_url(b"https://example.com").unwrap();
assert_eq!(code.error_correction_level(), EcLevel::H);
}
#[test]
fn for_wifi_encodes_with_special_chars() {
let code = QrCode::for_wifi("My;Net", "a,b", "WPA").unwrap();
assert!(code.width() > 0);
}
#[test]
fn for_vcard_encodes() {
let code = QrCode::for_vcard("John Doe", "+1234567890", "john@example.com").unwrap();
assert!(code.width() > 0);
}
#[test]
fn for_gs1_encodes() {
let code = QrCode::for_gs1("010491234512345915970331301234561842").unwrap();
assert!(code.width() > 0);
assert!(!code.version().is_micro());
assert_eq!(code.error_correction_level(), crate::EcLevel::M);
}
#[test]
fn structured_append_encodes_n_symbols() {
let codes = QrCode::structured_append(b"hello structured append world", 3, EcLevel::M).unwrap();
assert_eq!(codes.len(), 3);
assert!(codes.iter().all(|c| !c.version().is_micro()));
}
#[test]
fn structured_append_rejects_invalid_symbol_count() {
assert_eq!(
QrCode::structured_append(b"x", 1, EcLevel::M).err(),
Some(crate::QrError::InvalidStructuredAppend { value: 1 })
);
assert_eq!(
QrCode::structured_append(b"x", 17, EcLevel::M).err(),
Some(crate::QrError::InvalidStructuredAppend { value: 17 })
);
}
#[test]
fn info_reports_metadata() {
let code = QrCode::with_version(b"01234567", Version::Normal(1), crate::EcLevel::M).unwrap();
let info = code.info();
assert_eq!(info.version(), Version::Normal(1));
assert_eq!(info.ec_level(), crate::EcLevel::M);
assert_eq!(info.width(), code.width());
assert_eq!(info.module_count(), code.width() * code.width());
assert!(info.data_capacity_bytes() > 0);
let code_h = QrCode::with_version(b"01234567", Version::Normal(1), crate::EcLevel::H).unwrap();
assert!(info.data_capacity_bytes() > code_h.info().data_capacity_bytes());
}
#[test]
fn colors_borrows_without_clone() {
let code = QrCode::new(b"hello").unwrap();
let borrowed = code.colors();
assert_eq!(borrowed.len(), code.width() * code.width());
assert_eq!(borrowed, code.to_colors().as_slice());
}
#[test]
fn module_storage_reads_and_writes_grid() {
let mut code = QrCode::new(b"hello").unwrap();
let width = code.width();
let before = CoreModuleStorage::get(&code, 0, 0);
CoreModuleStorage::set(&mut code, 0, 0, !before);
assert_eq!(CoreModuleStorage::width(&code), width);
assert_eq!(CoreModuleStorage::height(&code), width);
assert_eq!(CoreModuleStorage::modules(&code).len(), width * width);
assert_eq!(CoreModuleStorage::get(&code, 0, 0), !before);
}
#[test]
fn module_source_exposes_read_only_grid() {
let code = QrCode::new(b"hello").unwrap();
let width = code.width();
assert_eq!(CoreModuleSource::width(&code), width);
assert_eq!(CoreModuleSource::height(&code), width);
assert_eq!(CoreModuleSource::modules(&code), code.colors());
assert_eq!(CoreModuleSource::get(&code, 0, 0), code[(0, 0)]);
}
#[test]
fn qr_symbol_exposes_metadata() {
let code = QrCode::with_version(b"hello", Version::Normal(1), EcLevel::H).unwrap();
assert_eq!(QrSymbol::version(&code), Version::Normal(1));
assert_eq!(QrSymbol::error_correction_level(&code), EcLevel::H);
assert_eq!(QrSymbol::quiet_zone(&code), 4);
}
#[test]
fn qr_symbol_uses_micro_quiet_zone() {
let code = QrCode::with_version(b"123", Version::Micro(1), EcLevel::L).unwrap();
assert_eq!(QrSymbol::quiet_zone(&code), 2);
}
#[test]
fn module_view_exposes_borrowed_source() {
let code = QrCode::new(b"hello").unwrap();
let view = code.module_view();
assert_eq!(view.width(), code.width());
assert_eq!(view.height(), code.width());
assert_eq!(view.modules(), code.colors());
assert_eq!(view.get(0, 0), code[(0, 0)]);
}
#[test]
fn qr_code_ref_exposes_borrowed_symbol() {
let code = QrCode::with_version(b"hello", Version::Normal(1), EcLevel::H).unwrap();
let borrowed = code.as_ref();
assert_eq!(borrowed.width(), code.width());
assert_eq!(borrowed.height(), code.width());
assert_eq!(borrowed.modules(), code.colors());
assert_eq!(borrowed.get(0, 0), code[(0, 0)]);
assert_eq!(borrowed.version(), code.version());
assert_eq!(borrowed.error_correction_level(), code.error_correction_level());
assert_eq!(borrowed.quiet_zone(), 4);
}
#[test]
fn render_error_is_available_from_facade() {
let err = crate::render::RenderError::InvalidModuleSource { width: 3, height: 2, len: 4 };
assert_eq!(err.to_string(), "invalid module source dimensions: width=3, height=2, len=4");
}
#[test]
fn renderer_trait_path_matches_builder_output() {
let code = QrCode::new(b"hello").unwrap();
let builder_output = code.render::<char>().build();
let renderer = code.render::<char>();
let trait_output = CoreRenderer::render(&renderer, &code).unwrap();
assert_eq!(trait_output, builder_output);
}
#[test]
fn render_builder_matches_render_output() {
let code = QrCode::new(b"render builder").unwrap();
assert_eq!(code.render_builder::<char>().build(), code.render::<char>().build());
}
#[cfg(feature = "async")]
#[test]
fn render_async_matches_render_output() {
let code = QrCode::new(b"render async").unwrap();
let expected = code.render::<char>().build();
let runtime = tokio::runtime::Builder::new_current_thread().build().unwrap();
let rendered = runtime.block_on(code.render_async::<char>()).unwrap();
assert_eq!(rendered, expected);
}
#[test]
fn renderer_from_symbol_matches_qrcode_render() {
let code = QrCode::new(b"hello").unwrap();
let from_symbol = crate::render::Renderer::<char>::from_symbol(&code)
.quiet_zone(false)
.dark_color('X')
.light_color('.')
.build();
let from_qrcode = code.render::<char>().quiet_zone(false).dark_color('X').light_color('.').build();
assert_eq!(from_symbol, from_qrcode);
}
#[test]
fn renderer_from_borrowed_symbol_matches_owned_symbol() {
let code = QrCode::with_version(b"123", Version::Micro(1), EcLevel::L).unwrap();
let borrowed = code.as_ref();
let from_borrowed =
crate::render::Renderer::<char>::from_symbol(&borrowed).dark_color('X').light_color('.').build();
let from_owned = crate::render::Renderer::<char>::from_symbol(&code).dark_color('X').light_color('.').build();
assert_eq!(borrowed.quiet_zone(), 2);
assert_eq!(from_borrowed, from_owned);
}
#[test]
fn renderer_trait_accepts_read_only_module_source() {
let code = QrCode::new(b"hello").unwrap();
let mut inverted = code.to_colors();
for color in &mut inverted {
*color = !*color;
}
let view = ModuleView::new(&inverted, code.width()).unwrap();
let mut renderer = code.render::<char>();
renderer.quiet_zone(false).dark_color('X').light_color('.');
let trait_output = CoreRenderer::render(&renderer, &view).unwrap();
let expected = crate::render::Renderer::<char>::from_source(&view, 4)
.quiet_zone(false)
.dark_color('X')
.light_color('.')
.build();
let original = code.render::<char>().quiet_zone(false).dark_color('X').light_color('.').build();
assert_eq!(trait_output, expected);
assert_ne!(trait_output, original);
}
#[test]
fn batch_encodes_many_and_short_circuits() {
let codes = QrCode::batch(vec![b"hi"; 1000], crate::EcLevel::M).unwrap();
assert_eq!(codes.len(), 1000);
let huge: Vec<u8> = (0..5000).map(|i| (i % 256) as u8).collect();
let mixed: Vec<&[u8]> = vec![&b"ok"[..], &huge[..], &b"also ok"[..]];
assert!(QrCode::batch(mixed, crate::EcLevel::L).is_err());
}
#[cfg(feature = "eps")]
#[test]
fn template_applies_colors() {
let code = QrCode::new(b"template").unwrap();
let minimal = code.render::<crate::render::eps::Color>().template(&crate::QrTemplate::minimal()).build();
let dark = code.render::<crate::render::eps::Color>().template(&crate::QrTemplate::dark_mode()).build();
assert!(minimal.contains("0 0 0 setrgbcolor"), "minimal should use a black foreground");
assert!(!dark.contains("0 0 0 setrgbcolor"), "dark_mode should change the foreground");
assert_ne!(minimal, dark);
}
#[test]
fn analyze_reports_diagnostics() {
let code = QrCode::with_version(b"01234567", Version::Normal(1), crate::EcLevel::M).unwrap();
let a = code.analyze();
let total = code.width() * code.width();
assert!(a.functional_modules() > 0, "should have functional modules");
assert!(a.data_modules() > 0, "should have data modules");
assert_eq!(a.functional_modules() + a.data_modules(), total);
assert!(a.dark_ratio() > 0.0 && a.dark_ratio() < 1.0);
let dark = code.colors().iter().filter(|c| **c == Color::Dark).count();
assert!((a.dark_ratio() - dark as f64 / total as f64).abs() < 1e-9);
}
#[test]
fn force_mode_without_version_auto_selects() {
let auto = QrCode::new(b"0123456789").unwrap();
let forced_byte = QrCode::builder(b"0123456789").force_mode(Mode::Byte).build().unwrap();
assert_ne!(colors(&auto), colors(&forced_byte));
let forced_num = QrCode::builder(b"0123456789").force_mode(Mode::Numeric).build().unwrap();
assert_eq!(colors(&auto), colors(&forced_num));
let err = QrCode::builder(b"\x93").force_mode(Mode::Kanji).build();
assert!(matches!(err, Err(crate::QrError::InvalidCharacter { .. })));
}
}
#[cfg(all(test, feature = "image"))]
mod image_tests {
use crate::{EcLevel, QrCode, Version};
use image::{Luma, Rgb, load_from_memory};
#[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!("../docs/images/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!("../docs/images/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!("../docs/images/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!("../docs/images/test_annex_i_micro_qr_as_svg.svg");
assert_eq!(&image, expected);
}
}
#[cfg(all(test, feature = "eps"))]
mod eps_tests {
use crate::render::eps::Color as EpsColor;
use crate::{EcLevel, QrCode, Version};
#[test]
fn test_annex_i_qr_as_eps() {
let code = QrCode::new(b"01234567").unwrap();
let image = code.render::<EpsColor>().build();
let expected = include_str!("../docs/images/test_annex_i_qr_as_eps.eps");
assert_eq!(&image, expected);
}
#[test]
fn test_annex_i_micro_qr_as_eps() {
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
let image = code
.render()
.min_dimensions(200, 200)
.dark_color(EpsColor([0.5, 0.0, 0.0]))
.light_color(EpsColor([1.0, 1.0, 0.5]))
.build();
let expected = include_str!("../docs/images/test_annex_i_micro_qr_as_eps.eps");
assert_eq!(&image, expected);
}
}
#[cfg(all(test, feature = "pic"))]
mod pic_tests {
use crate::render::pic::Color as PicColor;
use crate::{EcLevel, QrCode, Version};
#[test]
fn test_annex_i_qr_as_pic() {
let code = QrCode::new(b"01234567").unwrap();
let image = code.render::<PicColor>().build();
let expected = include_str!("../docs/images/test_annex_i_qr_as_pic.pic");
assert_eq!(&image, expected);
}
#[test]
fn test_annex_i_micro_qr_as_pic() {
let code = QrCode::with_version(b"01234567", Version::Micro(2), EcLevel::L).unwrap();
let image = code.render::<PicColor>().min_dimensions(1, 1).build();
let expected = include_str!("../docs/images/test_annex_i_micro_qr_as_pic.pic");
assert_eq!(&image, expected);
}
}