use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BarcodeType {
Code128,
Code39,
Ean13,
Ean8,
UpcA,
Itf,
Code93,
Codabar,
}
impl std::fmt::Display for BarcodeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BarcodeType::Code128 => write!(f, "Code 128"),
BarcodeType::Code39 => write!(f, "Code 39"),
BarcodeType::Ean13 => write!(f, "EAN-13"),
BarcodeType::Ean8 => write!(f, "EAN-8"),
BarcodeType::UpcA => write!(f, "UPC-A"),
BarcodeType::Itf => write!(f, "ITF"),
BarcodeType::Code93 => write!(f, "Code 93"),
BarcodeType::Codabar => write!(f, "Codabar"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum QrErrorCorrection {
Low,
#[default]
Medium,
Quartile,
High,
}
#[derive(Debug, Clone)]
pub struct QrCodeOptions {
pub size: u32,
pub error_correction: QrErrorCorrection,
pub quiet_zone: u32,
pub foreground: [u8; 4],
pub background: [u8; 4],
}
impl Default for QrCodeOptions {
fn default() -> Self {
Self {
size: 200,
error_correction: QrErrorCorrection::Medium,
quiet_zone: 4,
foreground: [0, 0, 0, 255], background: [255, 255, 255, 255], }
}
}
impl QrCodeOptions {
pub fn new() -> Self {
Self::default()
}
pub fn size(mut self, size: u32) -> Self {
self.size = size;
self
}
pub fn error_correction(mut self, level: QrErrorCorrection) -> Self {
self.error_correction = level;
self
}
pub fn quiet_zone(mut self, modules: u32) -> Self {
self.quiet_zone = modules;
self
}
pub fn foreground(mut self, r: u8, g: u8, b: u8, a: u8) -> Self {
self.foreground = [r, g, b, a];
self
}
pub fn background(mut self, r: u8, g: u8, b: u8, a: u8) -> Self {
self.background = [r, g, b, a];
self
}
}
#[derive(Debug, Clone)]
pub struct BarcodeOptions {
pub width: u32,
pub height: u32,
pub foreground: [u8; 4],
pub background: [u8; 4],
pub show_text: bool,
}
impl Default for BarcodeOptions {
fn default() -> Self {
Self {
width: 200,
height: 80,
foreground: [0, 0, 0, 255], background: [255, 255, 255, 255], show_text: false, }
}
}
impl BarcodeOptions {
pub fn new() -> Self {
Self::default()
}
pub fn width(mut self, width: u32) -> Self {
self.width = width;
self
}
pub fn height(mut self, height: u32) -> Self {
self.height = height;
self
}
pub fn foreground(mut self, r: u8, g: u8, b: u8, a: u8) -> Self {
self.foreground = [r, g, b, a];
self
}
pub fn background(mut self, r: u8, g: u8, b: u8, a: u8) -> Self {
self.background = [r, g, b, a];
self
}
pub fn show_text(mut self, show: bool) -> Self {
self.show_text = show;
self
}
}
pub struct BarcodeGenerator;
#[cfg(feature = "barcodes")]
impl BarcodeGenerator {
pub fn generate_1d(
barcode_type: BarcodeType,
data: &str,
options: &BarcodeOptions,
) -> Result<Vec<u8>> {
use barcoders::generators::image::*;
use barcoders::sym::codabar::Codabar;
use barcoders::sym::code128::Code128;
use barcoders::sym::code39::Code39;
use barcoders::sym::code93::Code93;
use barcoders::sym::ean13::EAN13;
use barcoders::sym::ean8::EAN8;
use barcoders::sym::tf::TF;
let encoded: Vec<u8> = match barcode_type {
BarcodeType::Code128 => {
let data_with_prefix = if data.starts_with('\u{00C0}')
|| data.starts_with('\u{0181}')
|| data.starts_with('\u{0106}')
{
data.to_string()
} else {
format!("\u{0181}{}", data) };
let barcode = Code128::new(&data_with_prefix)
.map_err(|e| Error::Barcode(format!("Code128 encoding error: {}", e)))?;
barcode.encode()
},
BarcodeType::Code39 => {
let barcode = Code39::new(data)
.map_err(|e| Error::Barcode(format!("Code39 encoding error: {}", e)))?;
barcode.encode()
},
BarcodeType::Ean13 => {
let barcode = EAN13::new(data)
.map_err(|e| Error::Barcode(format!("EAN-13 encoding error: {}", e)))?;
barcode.encode()
},
BarcodeType::Ean8 => {
let barcode = EAN8::new(data)
.map_err(|e| Error::Barcode(format!("EAN-8 encoding error: {}", e)))?;
barcode.encode()
},
BarcodeType::UpcA => {
let upc_data = if data.len() == 11 {
format!("0{}", data)
} else if data.len() == 12 {
format!("0{}", &data[..11])
} else {
return Err(Error::Barcode("UPC-A requires 11 or 12 digits".to_string()));
};
let barcode = EAN13::new(&upc_data)
.map_err(|e| Error::Barcode(format!("UPC-A encoding error: {}", e)))?;
barcode.encode()
},
BarcodeType::Itf => {
let barcode = TF::interleaved(data)
.map_err(|e| Error::Barcode(format!("ITF encoding error: {}", e)))?;
barcode.encode()
},
BarcodeType::Code93 => {
let barcode = Code93::new(data)
.map_err(|e| Error::Barcode(format!("Code93 encoding error: {}", e)))?;
barcode.encode()
},
BarcodeType::Codabar => {
let barcode = Codabar::new(data)
.map_err(|e| Error::Barcode(format!("Codabar encoding error: {}", e)))?;
barcode.encode()
},
};
let image_gen = Image::PNG {
height: options.height,
xdim: 1, rotation: Rotation::Zero,
foreground: Color::new(options.foreground),
background: Color::new(options.background),
};
let png_bytes = image_gen
.generate(&encoded)
.map_err(|e| Error::Barcode(format!("Image generation error: {}", e)))?;
if let Ok(img) = image::load_from_memory(&png_bytes) {
let scaled = img.resize_exact(
options.width,
options.height,
image::imageops::FilterType::Nearest,
);
let mut buf = Vec::new();
scaled
.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png)
.map_err(|e| Error::Barcode(format!("PNG encoding error: {}", e)))?;
Ok(buf)
} else {
Ok(png_bytes)
}
}
pub fn generate_qr(data: &str, options: &QrCodeOptions) -> Result<Vec<u8>> {
use qrcode::{EcLevel, QrCode};
let ec_level = match options.error_correction {
QrErrorCorrection::Low => EcLevel::L,
QrErrorCorrection::Medium => EcLevel::M,
QrErrorCorrection::Quartile => EcLevel::Q,
QrErrorCorrection::High => EcLevel::H,
};
let code = QrCode::with_error_correction_level(data, ec_level)
.map_err(|e| Error::Barcode(format!("QR code encoding error: {}", e)))?;
let qr_width = code.width();
let module_count = qr_width + (options.quiet_zone as usize * 2);
let module_size = (options.size as usize / module_count).max(1);
let actual_size = module_count * module_size;
let mut img = image::RgbaImage::new(actual_size as u32, actual_size as u32);
for pixel in img.pixels_mut() {
*pixel = image::Rgba(options.background);
}
let quiet_px = options.quiet_zone as usize * module_size;
for (y, row) in code.to_colors().chunks(qr_width).enumerate() {
for (x, &module) in row.iter().enumerate() {
if module == qrcode::Color::Dark {
let start_x = quiet_px + x * module_size;
let start_y = quiet_px + y * module_size;
for dy in 0..module_size {
for dx in 0..module_size {
let px = (start_x + dx) as u32;
let py = (start_y + dy) as u32;
if px < actual_size as u32 && py < actual_size as u32 {
img.put_pixel(px, py, image::Rgba(options.foreground));
}
}
}
}
}
}
let final_img = if actual_size != options.size as usize {
image::DynamicImage::ImageRgba8(img).resize_exact(
options.size,
options.size,
image::imageops::FilterType::Nearest,
)
} else {
image::DynamicImage::ImageRgba8(img)
};
let mut buf = Vec::new();
final_img
.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png)
.map_err(|e| Error::Barcode(format!("PNG encoding error: {}", e)))?;
Ok(buf)
}
pub fn generate_qr_simple(data: &str, size: u32) -> Result<Vec<u8>> {
Self::generate_qr(data, &QrCodeOptions::default().size(size))
}
pub fn generate_code128(data: &str, width: u32, height: u32) -> Result<Vec<u8>> {
Self::generate_1d(
BarcodeType::Code128,
data,
&BarcodeOptions::default().width(width).height(height),
)
}
pub fn generate_ean13(data: &str, width: u32, height: u32) -> Result<Vec<u8>> {
Self::generate_1d(
BarcodeType::Ean13,
data,
&BarcodeOptions::default().width(width).height(height),
)
}
}
#[cfg(not(feature = "barcodes"))]
impl BarcodeGenerator {
pub fn generate_1d(
_barcode_type: BarcodeType,
_data: &str,
_options: &BarcodeOptions,
) -> Result<Vec<u8>> {
Err(Error::Barcode("Barcode generation requires the 'barcodes' feature".to_string()))
}
pub fn generate_qr(_data: &str, _options: &QrCodeOptions) -> Result<Vec<u8>> {
Err(Error::Barcode("QR code generation requires the 'barcodes' feature".to_string()))
}
pub fn generate_qr_simple(_data: &str, _size: u32) -> Result<Vec<u8>> {
Err(Error::Barcode("QR code generation requires the 'barcodes' feature".to_string()))
}
pub fn generate_code128(_data: &str, _width: u32, _height: u32) -> Result<Vec<u8>> {
Err(Error::Barcode("Barcode generation requires the 'barcodes' feature".to_string()))
}
pub fn generate_ean13(_data: &str, _width: u32, _height: u32) -> Result<Vec<u8>> {
Err(Error::Barcode("Barcode generation requires the 'barcodes' feature".to_string()))
}
}
#[cfg(all(test, feature = "barcodes"))]
mod tests {
use super::*;
#[test]
fn test_generate_qr_code() {
let png = BarcodeGenerator::generate_qr_simple("https://example.com", 200).unwrap();
assert!(!png.is_empty());
assert_eq!(&png[..8], &[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
}
#[test]
fn test_generate_code128() {
let png = BarcodeGenerator::generate_code128("ABC123", 200, 80).unwrap();
assert!(!png.is_empty());
assert_eq!(&png[..8], &[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
}
#[test]
fn test_generate_ean13() {
let png = BarcodeGenerator::generate_ean13("5901234123457", 200, 80).unwrap();
assert!(!png.is_empty());
assert_eq!(&png[..8], &[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]);
}
#[test]
fn test_qr_options() {
let options = QrCodeOptions::new()
.size(300)
.error_correction(QrErrorCorrection::High)
.quiet_zone(6)
.foreground(0, 0, 128, 255)
.background(255, 255, 255, 255);
let png = BarcodeGenerator::generate_qr("Test data", &options).unwrap();
assert!(!png.is_empty());
}
#[test]
fn test_barcode_options() {
let options = BarcodeOptions::new()
.width(300)
.height(100)
.foreground(0, 0, 0, 255)
.background(255, 255, 255, 255);
let png = BarcodeGenerator::generate_1d(BarcodeType::Code128, "TEST", &options).unwrap();
assert!(!png.is_empty());
}
#[test]
fn test_barcode_type_display() {
assert_eq!(BarcodeType::Code128.to_string(), "Code 128");
assert_eq!(BarcodeType::Ean13.to_string(), "EAN-13");
}
}
#[cfg(all(test, not(feature = "barcodes")))]
mod tests_no_feature {
use super::*;
#[test]
fn test_feature_not_enabled() {
let result = BarcodeGenerator::generate_qr_simple("test", 200);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("requires the 'barcodes' feature"));
}
#[test]
fn test_generate_1d_not_enabled() {
let result =
BarcodeGenerator::generate_1d(BarcodeType::Code128, "test", &BarcodeOptions::new());
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("requires the 'barcodes' feature"));
}
#[test]
fn test_generate_qr_not_enabled() {
let result = BarcodeGenerator::generate_qr("test", &QrCodeOptions::new());
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("requires the 'barcodes' feature"));
}
#[test]
fn test_generate_code128_not_enabled() {
let result = BarcodeGenerator::generate_code128("test", 200, 80);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("requires the 'barcodes' feature"));
}
#[test]
fn test_generate_ean13_not_enabled() {
let result = BarcodeGenerator::generate_ean13("1234567890128", 200, 80);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("requires the 'barcodes' feature"));
}
}
#[cfg(test)]
mod tests_common {
use super::*;
#[test]
fn test_barcode_type_display_all() {
assert_eq!(BarcodeType::Code128.to_string(), "Code 128");
assert_eq!(BarcodeType::Code39.to_string(), "Code 39");
assert_eq!(BarcodeType::Ean13.to_string(), "EAN-13");
assert_eq!(BarcodeType::Ean8.to_string(), "EAN-8");
assert_eq!(BarcodeType::UpcA.to_string(), "UPC-A");
assert_eq!(BarcodeType::Itf.to_string(), "ITF");
assert_eq!(BarcodeType::Code93.to_string(), "Code 93");
assert_eq!(BarcodeType::Codabar.to_string(), "Codabar");
}
#[test]
fn test_barcode_type_equality() {
assert_eq!(BarcodeType::Code128, BarcodeType::Code128);
assert_ne!(BarcodeType::Code128, BarcodeType::Code39);
assert_ne!(BarcodeType::Ean13, BarcodeType::Ean8);
}
#[test]
fn test_barcode_type_copy() {
let bt = BarcodeType::Code128;
let bt2 = bt; assert_eq!(bt, bt2);
}
#[test]
fn test_qr_error_correction_default() {
assert_eq!(QrErrorCorrection::default(), QrErrorCorrection::Medium);
}
#[test]
fn test_qr_error_correction_equality() {
assert_eq!(QrErrorCorrection::Low, QrErrorCorrection::Low);
assert_eq!(QrErrorCorrection::Medium, QrErrorCorrection::Medium);
assert_eq!(QrErrorCorrection::Quartile, QrErrorCorrection::Quartile);
assert_eq!(QrErrorCorrection::High, QrErrorCorrection::High);
assert_ne!(QrErrorCorrection::Low, QrErrorCorrection::High);
}
#[test]
fn test_qr_error_correction_copy() {
let ec = QrErrorCorrection::High;
let ec2 = ec;
assert_eq!(ec, ec2);
}
#[test]
fn test_qr_code_options_default() {
let opts = QrCodeOptions::default();
assert_eq!(opts.size, 200);
assert_eq!(opts.error_correction, QrErrorCorrection::Medium);
assert_eq!(opts.quiet_zone, 4);
assert_eq!(opts.foreground, [0, 0, 0, 255]);
assert_eq!(opts.background, [255, 255, 255, 255]);
}
#[test]
fn test_qr_code_options_new() {
let opts = QrCodeOptions::new();
assert_eq!(opts.size, 200);
}
#[test]
fn test_qr_code_options_size() {
let opts = QrCodeOptions::new().size(500);
assert_eq!(opts.size, 500);
}
#[test]
fn test_qr_code_options_error_correction() {
let opts = QrCodeOptions::new().error_correction(QrErrorCorrection::High);
assert_eq!(opts.error_correction, QrErrorCorrection::High);
}
#[test]
fn test_qr_code_options_quiet_zone() {
let opts = QrCodeOptions::new().quiet_zone(8);
assert_eq!(opts.quiet_zone, 8);
}
#[test]
fn test_qr_code_options_foreground() {
let opts = QrCodeOptions::new().foreground(255, 0, 0, 128);
assert_eq!(opts.foreground, [255, 0, 0, 128]);
}
#[test]
fn test_qr_code_options_background() {
let opts = QrCodeOptions::new().background(0, 0, 255, 200);
assert_eq!(opts.background, [0, 0, 255, 200]);
}
#[test]
fn test_qr_code_options_chaining() {
let opts = QrCodeOptions::new()
.size(300)
.error_correction(QrErrorCorrection::Low)
.quiet_zone(2)
.foreground(128, 128, 128, 255)
.background(200, 200, 200, 255);
assert_eq!(opts.size, 300);
assert_eq!(opts.error_correction, QrErrorCorrection::Low);
assert_eq!(opts.quiet_zone, 2);
assert_eq!(opts.foreground, [128, 128, 128, 255]);
assert_eq!(opts.background, [200, 200, 200, 255]);
}
#[test]
fn test_barcode_options_default() {
let opts = BarcodeOptions::default();
assert_eq!(opts.width, 200);
assert_eq!(opts.height, 80);
assert_eq!(opts.foreground, [0, 0, 0, 255]);
assert_eq!(opts.background, [255, 255, 255, 255]);
assert!(!opts.show_text);
}
#[test]
fn test_barcode_options_new() {
let opts = BarcodeOptions::new();
assert_eq!(opts.width, 200);
assert_eq!(opts.height, 80);
}
#[test]
fn test_barcode_options_width() {
let opts = BarcodeOptions::new().width(400);
assert_eq!(opts.width, 400);
}
#[test]
fn test_barcode_options_height() {
let opts = BarcodeOptions::new().height(120);
assert_eq!(opts.height, 120);
}
#[test]
fn test_barcode_options_foreground() {
let opts = BarcodeOptions::new().foreground(0, 0, 128, 255);
assert_eq!(opts.foreground, [0, 0, 128, 255]);
}
#[test]
fn test_barcode_options_background() {
let opts = BarcodeOptions::new().background(255, 255, 0, 255);
assert_eq!(opts.background, [255, 255, 0, 255]);
}
#[test]
fn test_barcode_options_show_text() {
let opts = BarcodeOptions::new().show_text(true);
assert!(opts.show_text);
}
#[test]
fn test_barcode_options_chaining() {
let opts = BarcodeOptions::new()
.width(300)
.height(100)
.foreground(10, 20, 30, 255)
.background(240, 250, 255, 255)
.show_text(true);
assert_eq!(opts.width, 300);
assert_eq!(opts.height, 100);
assert_eq!(opts.foreground, [10, 20, 30, 255]);
assert_eq!(opts.background, [240, 250, 255, 255]);
assert!(opts.show_text);
}
}