307 lines
8.7 KiB
Rust
307 lines
8.7 KiB
Rust
//! Encode and display information in a QR code.
|
|
use crate::canvas;
|
|
use crate::renderer::{self, Renderer};
|
|
use crate::Backend;
|
|
|
|
use iced_native::layout;
|
|
use iced_native::{
|
|
Color, Element, Hasher, Layout, Length, Point, Rectangle, Size, Widget,
|
|
};
|
|
use thiserror::Error;
|
|
|
|
const DEFAULT_CELL_SIZE: u16 = 4;
|
|
const QUIET_ZONE: usize = 2;
|
|
|
|
/// A type of matrix barcode consisting of squares arranged in a grid which
|
|
/// can be read by an imaging device, such as a camera.
|
|
#[derive(Debug)]
|
|
pub struct QRCode<'a> {
|
|
state: &'a State,
|
|
dark: Color,
|
|
light: Color,
|
|
cell_size: u16,
|
|
}
|
|
|
|
impl<'a> QRCode<'a> {
|
|
/// Creates a new [`QRCode`] with the provided [`State`].
|
|
pub fn new(state: &'a State) -> Self {
|
|
Self {
|
|
cell_size: DEFAULT_CELL_SIZE,
|
|
dark: Color::BLACK,
|
|
light: Color::WHITE,
|
|
state,
|
|
}
|
|
}
|
|
|
|
/// Sets both the dark and light [`Color`]s of the [`QRCode`].
|
|
pub fn color(mut self, dark: Color, light: Color) -> Self {
|
|
self.dark = dark;
|
|
self.light = light;
|
|
self
|
|
}
|
|
|
|
/// Sets the size of the squares of the grid cell of the [`QRCode`].
|
|
pub fn cell_size(mut self, cell_size: u16) -> Self {
|
|
self.cell_size = cell_size;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<'a, Message, B> Widget<Message, Renderer<B>> for QRCode<'a>
|
|
where
|
|
B: Backend,
|
|
{
|
|
fn width(&self) -> Length {
|
|
Length::Shrink
|
|
}
|
|
|
|
fn height(&self) -> Length {
|
|
Length::Shrink
|
|
}
|
|
|
|
fn layout(
|
|
&self,
|
|
_renderer: &Renderer<B>,
|
|
_limits: &layout::Limits,
|
|
) -> layout::Node {
|
|
let side_length = (self.state.width + 2 * QUIET_ZONE) as f32
|
|
* f32::from(self.cell_size);
|
|
|
|
layout::Node::new(Size::new(
|
|
f32::from(side_length),
|
|
f32::from(side_length),
|
|
))
|
|
}
|
|
|
|
fn hash_layout(&self, state: &mut Hasher) {
|
|
use std::hash::Hash;
|
|
|
|
self.state.contents.hash(state);
|
|
}
|
|
|
|
fn draw(
|
|
&self,
|
|
_renderer: &mut Renderer<B>,
|
|
_style: &renderer::Style,
|
|
_layout: Layout<'_>,
|
|
_cursor_position: Point,
|
|
_viewport: &Rectangle,
|
|
) {
|
|
// let bounds = layout.bounds();
|
|
// let side_length = self.state.width + 2 * QUIET_ZONE;
|
|
|
|
// // Reuse cache if possible
|
|
// let geometry = self.state.cache.draw(bounds.size(), |frame| {
|
|
// // Scale units to cell size
|
|
// frame.scale(f32::from(self.cell_size));
|
|
|
|
// // Draw background
|
|
// frame.fill_rectangle(
|
|
// Point::ORIGIN,
|
|
// Size::new(side_length as f32, side_length as f32),
|
|
// self.light,
|
|
// );
|
|
|
|
// // Avoid drawing on the quiet zone
|
|
// frame.translate(Vector::new(QUIET_ZONE as f32, QUIET_ZONE as f32));
|
|
|
|
// // Draw contents
|
|
// self.state
|
|
// .contents
|
|
// .iter()
|
|
// .enumerate()
|
|
// .filter(|(_, value)| **value == qrcode::Color::Dark)
|
|
// .for_each(|(index, _)| {
|
|
// let row = index / self.state.width;
|
|
// let column = index % self.state.width;
|
|
|
|
// frame.fill_rectangle(
|
|
// Point::new(column as f32, row as f32),
|
|
// Size::UNIT,
|
|
// self.dark,
|
|
// );
|
|
// });
|
|
// });
|
|
|
|
// (
|
|
// Primitive::Translate {
|
|
// translation: Vector::new(bounds.x, bounds.y),
|
|
// content: Box::new(geometry.into_primitive()),
|
|
// },
|
|
// mouse::Interaction::default(),
|
|
// )
|
|
// TODO
|
|
}
|
|
}
|
|
|
|
impl<'a, Message, B> Into<Element<'a, Message, Renderer<B>>> for QRCode<'a>
|
|
where
|
|
B: Backend,
|
|
{
|
|
fn into(self) -> Element<'a, Message, Renderer<B>> {
|
|
Element::new(self)
|
|
}
|
|
}
|
|
|
|
/// The state of a [`QRCode`].
|
|
///
|
|
/// It stores the data that will be displayed.
|
|
#[derive(Debug)]
|
|
pub struct State {
|
|
contents: Vec<qrcode::Color>,
|
|
width: usize,
|
|
cache: canvas::Cache,
|
|
}
|
|
|
|
impl State {
|
|
/// Creates a new [`State`] with the provided data.
|
|
///
|
|
/// This method uses an [`ErrorCorrection::Medium`] and chooses the smallest
|
|
/// size to display the data.
|
|
pub fn new(data: impl AsRef<[u8]>) -> Result<Self, Error> {
|
|
let encoded = qrcode::QrCode::new(data)?;
|
|
|
|
Ok(Self::build(encoded))
|
|
}
|
|
|
|
/// Creates a new [`State`] with the provided [`ErrorCorrection`].
|
|
pub fn with_error_correction(
|
|
data: impl AsRef<[u8]>,
|
|
error_correction: ErrorCorrection,
|
|
) -> Result<Self, Error> {
|
|
let encoded = qrcode::QrCode::with_error_correction_level(
|
|
data,
|
|
error_correction.into(),
|
|
)?;
|
|
|
|
Ok(Self::build(encoded))
|
|
}
|
|
|
|
/// Creates a new [`State`] with the provided [`Version`] and
|
|
/// [`ErrorCorrection`].
|
|
pub fn with_version(
|
|
data: impl AsRef<[u8]>,
|
|
version: Version,
|
|
error_correction: ErrorCorrection,
|
|
) -> Result<Self, Error> {
|
|
let encoded = qrcode::QrCode::with_version(
|
|
data,
|
|
version.into(),
|
|
error_correction.into(),
|
|
)?;
|
|
|
|
Ok(Self::build(encoded))
|
|
}
|
|
|
|
fn build(encoded: qrcode::QrCode) -> Self {
|
|
let width = encoded.width();
|
|
let contents = encoded.into_colors();
|
|
|
|
Self {
|
|
contents,
|
|
width,
|
|
cache: canvas::Cache::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
/// The size of a [`QRCode`].
|
|
///
|
|
/// The higher the version the larger the grid of cells, and therefore the more
|
|
/// information the [`QRCode`] can carry.
|
|
pub enum Version {
|
|
/// A normal QR code version. It should be between 1 and 40.
|
|
Normal(u8),
|
|
|
|
/// A micro QR code version. It should be between 1 and 4.
|
|
Micro(u8),
|
|
}
|
|
|
|
impl From<Version> for qrcode::Version {
|
|
fn from(version: Version) -> Self {
|
|
match version {
|
|
Version::Normal(v) => qrcode::Version::Normal(i16::from(v)),
|
|
Version::Micro(v) => qrcode::Version::Micro(i16::from(v)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The error correction level.
|
|
///
|
|
/// It controls the amount of data that can be damaged while still being able
|
|
/// to recover the original information.
|
|
///
|
|
/// A higher error correction level allows for more corrupted data.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub enum ErrorCorrection {
|
|
/// Low error correction. 7% of the data can be restored.
|
|
Low,
|
|
/// Medium error correction. 15% of the data can be restored.
|
|
Medium,
|
|
/// Quartile error correction. 25% of the data can be restored.
|
|
Quartile,
|
|
/// High error correction. 30% of the data can be restored.
|
|
High,
|
|
}
|
|
|
|
impl From<ErrorCorrection> for qrcode::EcLevel {
|
|
fn from(ec_level: ErrorCorrection) -> Self {
|
|
match ec_level {
|
|
ErrorCorrection::Low => qrcode::EcLevel::L,
|
|
ErrorCorrection::Medium => qrcode::EcLevel::M,
|
|
ErrorCorrection::Quartile => qrcode::EcLevel::Q,
|
|
ErrorCorrection::High => qrcode::EcLevel::H,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An error that occurred when building a [`State`] for a [`QRCode`].
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
|
|
pub enum Error {
|
|
/// The data is too long to encode in a QR code for the chosen [`Version`].
|
|
#[error(
|
|
"The data is too long to encode in a QR code for the chosen version"
|
|
)]
|
|
DataTooLong,
|
|
|
|
/// The chosen [`Version`] and [`ErrorCorrection`] combination is invalid.
|
|
#[error(
|
|
"The chosen version and error correction level combination is invalid."
|
|
)]
|
|
InvalidVersion,
|
|
|
|
/// One or more characters in the provided data are not supported by the
|
|
/// chosen [`Version`].
|
|
#[error(
|
|
"One or more characters in the provided data are not supported by the \
|
|
chosen version"
|
|
)]
|
|
UnsupportedCharacterSet,
|
|
|
|
/// The chosen ECI designator is invalid. A valid designator should be
|
|
/// between 0 and 999999.
|
|
#[error(
|
|
"The chosen ECI designator is invalid. A valid designator should be \
|
|
between 0 and 999999."
|
|
)]
|
|
InvalidEciDesignator,
|
|
|
|
/// A character that does not belong to the character set was found.
|
|
#[error("A character that does not belong to the character set was found")]
|
|
InvalidCharacter,
|
|
}
|
|
|
|
impl From<qrcode::types::QrError> for Error {
|
|
fn from(error: qrcode::types::QrError) -> Self {
|
|
use qrcode::types::QrError;
|
|
|
|
match error {
|
|
QrError::DataTooLong => Error::DataTooLong,
|
|
QrError::InvalidVersion => Error::InvalidVersion,
|
|
QrError::UnsupportedCharacterSet => Error::UnsupportedCharacterSet,
|
|
QrError::InvalidEciDesignator => Error::InvalidEciDesignator,
|
|
QrError::InvalidCharacter => Error::InvalidCharacter,
|
|
}
|
|
}
|
|
}
|