use std::ffi::c_char;
use std::ffi::c_int;
use std::slice;
#[link(name = "zpl")]
extern "C" {
fn zpl_render_png(
zpl_data: *const c_char,
zpl_len: c_int,
dpi: c_int,
width: c_int,
height: c_int,
png_out: *mut *mut c_char,
png_len: *mut c_int,
) -> c_int;
fn zpl_render_png_simple(
zpl_data: *const c_char,
zpl_len: c_int,
png_out: *mut *mut c_char,
png_len: *mut c_int,
) -> c_int;
fn zpl_free(ptr: *mut c_char);
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
ParseError,
RenderError,
InternalError,
Unknown(i32),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::ParseError => write!(f, "Failed to parse ZPL"),
Error::RenderError => write!(f, "Failed to render label"),
Error::InternalError => write!(f, "Internal library error"),
Error::Unknown(code) => write!(f, "Unknown error (code: {})", code),
}
}
}
impl std::error::Error for Error {}
impl From<c_int> for Error {
fn from(code: c_int) -> Self {
match code {
-1 => Error::ParseError,
-2 => Error::RenderError,
-3 => Error::InternalError,
n => Error::Unknown(n),
}
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Dpi {
#[default]
Dpi203,
Dpi300,
Dpi600,
}
impl Dpi {
fn as_c_int(self) -> c_int {
match self {
Dpi::Dpi203 => 203,
Dpi::Dpi300 => 300,
Dpi::Dpi600 => 600,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct RenderOptions {
pub dpi: Dpi,
pub width: Option<i32>,
pub height: Option<i32>,
}
impl RenderOptions {
pub fn new() -> Self {
Self::default()
}
pub fn dpi(mut self, dpi: Dpi) -> Self {
self.dpi = dpi;
self
}
pub fn width(mut self, width: i32) -> Self {
self.width = Some(width);
self
}
pub fn height(mut self, height: i32) -> Self {
self.height = Some(height);
self
}
pub fn size(mut self, width: i32, height: i32) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
}
pub fn render(zpl: &str) -> Result<Vec<u8>> {
render_bytes(zpl.as_bytes())
}
pub fn render_bytes(zpl: &[u8]) -> Result<Vec<u8>> {
let mut png_ptr: *mut c_char = std::ptr::null_mut();
let mut png_len: c_int = 0;
let result = unsafe {
zpl_render_png_simple(
zpl.as_ptr() as *const c_char,
zpl.len() as c_int,
&mut png_ptr,
&mut png_len,
)
};
if result != 0 {
return Err(Error::from(result));
}
if png_ptr.is_null() || png_len <= 0 {
return Err(Error::InternalError);
}
let png_data = unsafe {
let slice = slice::from_raw_parts(png_ptr as *const u8, png_len as usize);
let owned = slice.to_vec();
zpl_free(png_ptr);
owned
};
Ok(png_data)
}
pub fn render_with_options(zpl: &str, options: &RenderOptions) -> Result<Vec<u8>> {
render_bytes_with_options(zpl.as_bytes(), options)
}
pub fn render_bytes_with_options(zpl: &[u8], options: &RenderOptions) -> Result<Vec<u8>> {
let mut png_ptr: *mut c_char = std::ptr::null_mut();
let mut png_len: c_int = 0;
let result = unsafe {
zpl_render_png(
zpl.as_ptr() as *const c_char,
zpl.len() as c_int,
options.dpi.as_c_int(),
options.width.unwrap_or(0),
options.height.unwrap_or(0),
&mut png_ptr,
&mut png_len,
)
};
if result != 0 {
return Err(Error::from(result));
}
if png_ptr.is_null() || png_len <= 0 {
return Err(Error::InternalError);
}
let png_data = unsafe {
let slice = slice::from_raw_parts(png_ptr as *const u8, png_len as usize);
let owned = slice.to_vec();
zpl_free(png_ptr);
owned
};
Ok(png_data)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_render_simple() {
let zpl = "^XA^FO50,50^A0N,30,30^FDHello Rust!^FS^XZ";
let png = render(zpl).expect("render failed");
assert!(png.len() > 8);
assert_eq!(&png[0..4], &[0x89, b'P', b'N', b'G']);
}
#[test]
fn test_render_with_options() {
let zpl = "^XA^FO50,50^A0N,30,30^FDHello!^FS^XZ";
let options = RenderOptions::new()
.dpi(Dpi::Dpi300)
.size(812, 1218);
let png = render_with_options(zpl, &options).expect("render failed");
assert!(png.len() > 8);
assert_eq!(&png[0..4], &[0x89, b'P', b'N', b'G']);
}
#[test]
fn test_render_with_barcode() {
let zpl = r#"^XA
^FO50,50^A0N,30,30^FDTest Label^FS
^FO50,100^BCN,100,Y,N,N,N^FD123456789^FS
^FO50,250^BQN,2,5^FDMA,https://example.com^FS
^XZ"#;
let png = render(zpl).expect("render failed");
assert!(png.len() > 1000); }
#[test]
fn test_empty_label() {
let result = render("^XA^XZ");
assert!(result.is_ok());
}
#[test]
fn test_render_bytes() {
let zpl = b"^XA^FO50,50^A0N,30,30^FDBytes!^FS^XZ";
let png = render_bytes(zpl).expect("render failed");
assert_eq!(&png[0..4], &[0x89, b'P', b'N', b'G']);
}
}