parent
d8603dcc23
commit
4622aec0f1
@ -1,75 +0,0 @@
|
||||
use crate::error::Error;
|
||||
|
||||
use cstr_core::CStr;
|
||||
|
||||
extern "C" {
|
||||
fn display_qrcode(
|
||||
x: cty::c_int,
|
||||
y: cty::c_int,
|
||||
data: *const cty::uint16_t,
|
||||
scale: cty::uint8_t,
|
||||
);
|
||||
}
|
||||
|
||||
const NVERSIONS: usize = 10; // range of versions (=capacities) that we support
|
||||
const QR_WIDTHS: [u32; NVERSIONS] = [21, 25, 29, 33, 37, 41, 45, 49, 53, 57];
|
||||
const THRESHOLDS_BINARY: [usize; NVERSIONS] = [14, 26, 42, 62, 84, 106, 122, 152, 180, 213];
|
||||
const THRESHOLDS_ALPHANUM: [usize; NVERSIONS] = [20, 38, 61, 90, 122, 154, 178, 221, 262, 311];
|
||||
const ALPHANUM: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $*+-./:";
|
||||
|
||||
const MAX_DATA: usize = THRESHOLDS_ALPHANUM[THRESHOLDS_ALPHANUM.len() - 1] + 1; //FIXME
|
||||
|
||||
fn is_alphanum_only(data: &str) -> bool {
|
||||
data.chars().all(|c| ALPHANUM.contains(c))
|
||||
}
|
||||
|
||||
fn qr_version_index(data: &str, thresholds: &[usize]) -> Option<usize> {
|
||||
for (i, threshold) in thresholds.iter().enumerate() {
|
||||
if data.len() <= *threshold {
|
||||
return Some(i);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn render_qrcode(
|
||||
x: i16,
|
||||
y: i16,
|
||||
data: &str,
|
||||
max_size: u32,
|
||||
case_sensitive: bool,
|
||||
) -> Result<(), Error> {
|
||||
let data_len = data.len();
|
||||
let version_idx;
|
||||
let mut buffer = [0u8; MAX_DATA];
|
||||
assert!(data_len < buffer.len());
|
||||
buffer.as_mut_slice()[..data_len].copy_from_slice(data.as_bytes());
|
||||
|
||||
if case_sensitive && !is_alphanum_only(data) {
|
||||
version_idx = match qr_version_index(data, &THRESHOLDS_BINARY) {
|
||||
Some(idx) => idx,
|
||||
_ => return Err(Error::OutOfRange),
|
||||
}
|
||||
} else if let Some(idx) = qr_version_index(data, &THRESHOLDS_ALPHANUM) {
|
||||
version_idx = idx;
|
||||
if data_len > THRESHOLDS_BINARY[idx] {
|
||||
for c in buffer.iter_mut() {
|
||||
c.make_ascii_uppercase()
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return Err(Error::OutOfRange);
|
||||
}
|
||||
|
||||
let size = QR_WIDTHS[version_idx];
|
||||
let scale = max_size / size;
|
||||
assert!((1..=10).contains(&scale));
|
||||
|
||||
unsafe {
|
||||
buffer[data_len] = 0u8;
|
||||
let cstr = CStr::from_bytes_with_nul_unchecked(&buffer[..data_len + 1]);
|
||||
|
||||
display_qrcode(x.into(), y.into(), cstr.as_ptr() as _, scale as u8);
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
use heapless::String;
|
||||
use qrcodegen::{QrCode, QrCodeEcc, Version};
|
||||
|
||||
use crate::{
|
||||
error::Error,
|
||||
ui::{
|
||||
component::{Component, Event, EventCtx, Never},
|
||||
constant,
|
||||
display::{pixeldata, pixeldata_dirty, rect_fill_rounded, set_window, Color},
|
||||
geometry::{Insets, Offset, Rect},
|
||||
},
|
||||
};
|
||||
|
||||
const NVERSIONS: usize = 10; // range of versions (=capacities) that we support
|
||||
const THRESHOLDS_BINARY: [usize; NVERSIONS] = [14, 26, 42, 62, 84, 106, 122, 152, 180, 213];
|
||||
const THRESHOLDS_ALPHANUM: [usize; NVERSIONS] = [20, 38, 61, 90, 122, 154, 178, 221, 262, 311];
|
||||
const ALPHANUMERIC_CHARSET: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:";
|
||||
const MAX_DATA: usize = THRESHOLDS_ALPHANUM[THRESHOLDS_ALPHANUM.len() - 1];
|
||||
|
||||
const QR_MAX_VERSION: Version = Version::new(NVERSIONS as u8 - 1);
|
||||
const CORNER_RADIUS: u8 = 4;
|
||||
|
||||
const DARK: Color = Color::rgb(0, 0, 0);
|
||||
const LIGHT: Color = Color::rgb(0xff, 0xff, 0xff);
|
||||
|
||||
pub struct Qr {
|
||||
text: String<MAX_DATA>,
|
||||
border: i16,
|
||||
area: Rect,
|
||||
}
|
||||
|
||||
impl Qr {
|
||||
pub fn new<T>(text: T, case_sensitive: bool) -> Result<Self, Error>
|
||||
where
|
||||
T: AsRef<str>,
|
||||
{
|
||||
let indata = text.as_ref();
|
||||
let mut s = String::new();
|
||||
|
||||
if !case_sensitive
|
||||
&& Self::is_smaller_for_alphanumeric(indata.len())
|
||||
&& Self::is_alphanumeric_after_conversion(indata)
|
||||
{
|
||||
for c in indata.chars() {
|
||||
s.push(c.to_ascii_uppercase())
|
||||
.map_err(|_| Error::OutOfRange)?;
|
||||
}
|
||||
} else {
|
||||
s.push_str(indata).map_err(|_| Error::OutOfRange)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
text: s,
|
||||
border: 0,
|
||||
area: Rect::zero(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_border(mut self, border: i16) -> Self {
|
||||
self.border = border;
|
||||
self
|
||||
}
|
||||
|
||||
fn is_alphanumeric_after_conversion(data: &str) -> bool {
|
||||
data.chars()
|
||||
.all(|c| ALPHANUMERIC_CHARSET.contains(c.to_ascii_uppercase()))
|
||||
}
|
||||
|
||||
fn is_smaller_for_alphanumeric(len: usize) -> bool {
|
||||
for version in 0..NVERSIONS {
|
||||
if len <= THRESHOLDS_ALPHANUM[version] {
|
||||
return len > THRESHOLDS_BINARY[version];
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn draw(qr: &QrCode, area: Rect, border: i16, scale: i16) {
|
||||
if border > 0 {
|
||||
rect_fill_rounded(
|
||||
area.inset(Insets::uniform(-border)),
|
||||
LIGHT,
|
||||
DARK,
|
||||
CORNER_RADIUS,
|
||||
);
|
||||
}
|
||||
|
||||
let window = area.clamp(constant::screen());
|
||||
set_window(window);
|
||||
|
||||
for y in window.y0..window.y1 {
|
||||
for x in window.x0..window.x1 {
|
||||
let rx = (x - window.x0) / scale;
|
||||
let ry = (y - window.y0) / scale;
|
||||
if qr.get_module(rx.into(), ry.into()) {
|
||||
pixeldata(DARK);
|
||||
} else {
|
||||
pixeldata(LIGHT);
|
||||
};
|
||||
}
|
||||
}
|
||||
pixeldata_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Qr {
|
||||
type Msg = Never;
|
||||
|
||||
fn place(&mut self, bounds: Rect) -> Rect {
|
||||
self.area = bounds;
|
||||
bounds
|
||||
}
|
||||
|
||||
fn event(&mut self, _ctx: &mut EventCtx, _event: Event) -> Option<Self::Msg> {
|
||||
None
|
||||
}
|
||||
|
||||
fn paint(&mut self) {
|
||||
let mut outbuffer = [0u8; QR_MAX_VERSION.buffer_len()];
|
||||
let mut tempbuffer = [0u8; QR_MAX_VERSION.buffer_len()];
|
||||
|
||||
let qr = QrCode::encode_text(
|
||||
self.text.as_ref(),
|
||||
&mut tempbuffer,
|
||||
&mut outbuffer,
|
||||
QrCodeEcc::Medium,
|
||||
Version::MIN,
|
||||
QR_MAX_VERSION,
|
||||
None,
|
||||
true,
|
||||
);
|
||||
let qr = unwrap!(qr);
|
||||
let size = qr.size() as i16;
|
||||
|
||||
let avail_space = self.area.width().min(self.area.height());
|
||||
let avail_space = avail_space - 2 * self.border;
|
||||
let scale = avail_space / size;
|
||||
assert!((1..=10).contains(&scale));
|
||||
|
||||
let area = Rect::from_center_and_size(self.area.center(), Offset::uniform(size * scale));
|
||||
Self::draw(&qr, area, self.border, scale);
|
||||
}
|
||||
|
||||
fn bounds(&self, sink: &mut dyn FnMut(Rect)) {
|
||||
sink(self.area)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ui_debug")]
|
||||
impl crate::trace::Trace for Qr {
|
||||
fn trace(&self, t: &mut dyn crate::trace::Tracer) {
|
||||
t.open("Qr");
|
||||
t.field("text", &self.text.as_str());
|
||||
t.close();
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 2fc287904a8c2f2cb504c5710ee58684dd2bf697
|
||||
Subproject commit 2643e824eb15064662e6c4d99b010740275a0be1
|
Loading…
Reference in new issue