use std::{fmt::Display, str::from_utf8};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum Orientation {
#[default]
Unknown,
Up,
Right,
Down,
Left,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Point {
pub x: i32,
pub y: i32,
}
impl Point {
pub const fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
impl From<[i32; 2]> for Point {
fn from([x, y]: [i32; 2]) -> Self {
Self { x, y }
}
}
impl From<Point> for [i32; 2] {
fn from(p: Point) -> Self {
[p.x, p.y]
}
}
#[derive(Default, Clone)]
pub struct Symbol {
symbol_type: SymbolType,
pub(crate) modifiers: u32,
pub(crate) data: Vec<u8>,
pub(crate) raw_data: Option<Vec<u8>>,
pub(crate) pts: Vec<Point>,
pub(crate) orientation: Orientation,
pub(crate) components: Vec<Symbol>,
pub(crate) quality: i32,
}
impl Symbol {
pub(crate) fn new(symbol_type: SymbolType) -> Self {
Self {
symbol_type,
quality: 1,
..Default::default()
}
}
pub(crate) fn composite(self, other: Self) -> Self {
Self {
symbol_type: SymbolType::Composite,
orientation: self.orientation,
data: self.data.iter().chain(&other.data).cloned().collect(),
components: vec![self, other],
..Default::default()
}
}
pub(crate) fn add_point(&mut self, x: i32, y: i32) {
self.pts.push(Point::new(x, y));
}
}
impl Symbol {
pub fn symbol_type(&self) -> SymbolType {
self.symbol_type
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn raw_data(&self) -> Option<&[u8]> {
self.raw_data.as_deref()
}
pub fn data_string(&self) -> Option<&str> {
from_utf8(&self.data).ok()
}
pub fn orientation(&self) -> Orientation {
self.orientation
}
pub fn components(&self) -> &[Symbol] {
&self.components
}
pub fn points(&self) -> &[Point] {
&self.pts
}
pub fn bounds(&self) -> Option<Bounds> {
let mut iter = self.pts.iter().copied();
let Point { x: x0, y: y0 } = iter.next()?;
let (mut min_x, mut max_x) = (x0, x0);
let (mut min_y, mut max_y) = (y0, y0);
for Point { x, y } in iter {
if x < min_x {
min_x = x;
} else if x > max_x {
max_x = x;
}
if y < min_y {
min_y = y;
} else if y > max_y {
max_y = y;
}
}
let width = (max_x as i64 - min_x as i64).max(0).min(u32::MAX as i64) as u32;
let height = (max_y as i64 - min_y as i64).max(0).min(u32::MAX as i64) as u32;
Some(Bounds {
x: min_x,
y: min_y,
width,
height,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Bounds {
pub x: i32,
pub y: i32,
pub width: u32,
pub height: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord, Hash)]
pub enum SymbolType {
#[default]
None = 0,
Partial = 1,
Ean2 = 2,
Ean5 = 5,
Ean8 = 8,
Upce = 9,
Isbn10 = 10,
Upca = 12,
Ean13 = 13,
Isbn13 = 14,
Composite = 15,
I25 = 25,
Databar = 34,
DatabarExp = 35,
Codabar = 38,
Code39 = 39,
QrCode = 64,
SqCode = 80,
Code93 = 93,
Code128 = 128,
}
impl SymbolType {
pub(crate) const ALL: [Self; 17] = [
Self::Ean13,
Self::Ean2,
Self::Ean5,
Self::Ean8,
Self::Upca,
Self::Upce,
Self::Isbn10,
Self::Isbn13,
Self::I25,
Self::Databar,
Self::DatabarExp,
Self::Codabar,
Self::Code39,
Self::Code93,
Self::Code128,
Self::QrCode,
Self::SqCode,
];
}
impl Display for SymbolType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::None => "None",
Self::Partial => "Partial",
Self::Ean2 => "EAN-2",
Self::Ean5 => "EAN-5",
Self::Ean8 => "EAN-8",
Self::Upce => "UPC-E",
Self::Isbn10 => "ISBN-10",
Self::Upca => "UPC-A",
Self::Ean13 => "EAN-13",
Self::Isbn13 => "ISBN-13",
Self::Composite => "COMPOSITE",
Self::I25 => "I2/5",
Self::Databar => "DataBar",
Self::DatabarExp => "DataBar-Exp",
Self::Codabar => "Codabar",
Self::Code39 => "CODE-39",
Self::Code93 => "CODE-93",
Self::Code128 => "CODE-128",
Self::QrCode => "QR-Code",
Self::SqCode => "SQ-Code",
}
)
}
}
impl From<SymbolType> for i32 {
fn from(value: SymbolType) -> Self {
value as i32
}
}
impl From<i32> for SymbolType {
fn from(value: i32) -> Self {
match value {
0 => Self::None,
1 => Self::Partial,
2 => Self::Ean2,
5 => Self::Ean5,
8 => Self::Ean8,
9 => Self::Upce,
10 => Self::Isbn10,
12 => Self::Upca,
13 => Self::Ean13,
14 => Self::Isbn13,
15 => Self::Composite,
25 => Self::I25,
34 => Self::Databar,
35 => Self::DatabarExp,
38 => Self::Codabar,
39 => Self::Code39,
64 => Self::QrCode,
80 => Self::SqCode,
93 => Self::Code93,
128 => Self::Code128,
_ => Self::None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseSymbolTypeError(pub String);
impl Display for ParseSymbolTypeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "unknown symbology: {:?}", self.0)
}
}
impl std::error::Error for ParseSymbolTypeError {}
impl std::str::FromStr for SymbolType {
type Err = ParseSymbolTypeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::ALL
.iter()
.copied()
.find(|sym| sym.to_string() == s)
.ok_or_else(|| ParseSymbolTypeError(s.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_points_empty_when_no_points_recorded() {
let sym = Symbol::new(SymbolType::QrCode);
assert!(sym.points().is_empty());
}
#[test]
fn test_bounds_returns_none_when_no_points_recorded() {
let sym = Symbol::new(SymbolType::QrCode);
assert_eq!(sym.bounds(), None);
}
#[test]
fn test_bounds_with_single_point_has_zero_extent() {
let mut sym = Symbol::new(SymbolType::Code128);
sym.add_point(7, 11);
assert_eq!(
sym.bounds(),
Some(Bounds {
x: 7,
y: 11,
width: 0,
height: 0,
})
);
}
#[test]
fn test_bounds_aabb_of_qr_corners() {
let mut sym = Symbol::new(SymbolType::QrCode);
sym.add_point(88, 1996);
sym.add_point(211, 2121);
sym.add_point(88, 2121);
sym.add_point(211, 1996);
assert_eq!(sym.points().len(), 4);
assert_eq!(
sym.bounds(),
Some(Bounds {
x: 88,
y: 1996,
width: 123,
height: 125,
})
);
}
#[test]
fn test_bounds_with_negative_coordinates() {
let mut sym = Symbol::new(SymbolType::Code128);
sym.add_point(-3, -7);
sym.add_point(2, 4);
assert_eq!(
sym.bounds(),
Some(Bounds {
x: -3,
y: -7,
width: 5,
height: 11,
})
);
}
}