Initial commit
This commit is contained in:
176
src/datamatrix.rs
Normal file
176
src/datamatrix.rs
Normal file
@@ -0,0 +1,176 @@
|
||||
use datamatrix::{DataMatrixBuilder, EncodationType};
|
||||
use datamatrix::data::DataEncodingError;
|
||||
use datamatrix::placement::PathSegment;
|
||||
use flagset::FlagSet;
|
||||
use js_sys::{JsString, Uint8Array};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const TS_ENCODINGS: &'static str = r#"
|
||||
/// Ugh, I hate this so much. But this is what it says in the spec...
|
||||
type EncodationName = "ascii" | "c40" | "text" | "x12" | "edifact" | "base256";
|
||||
type Encodation = EncodationName | Array<EncodationName>;
|
||||
|
||||
interface CodingOptions {
|
||||
size?: SymbolSizeSpec;
|
||||
macros?: bool;
|
||||
encodings?: Encodation;
|
||||
}
|
||||
"#;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(typescript_type = "CodingOptions")]
|
||||
pub type CodingOptions;
|
||||
type Encodation;
|
||||
|
||||
#[wasm_bindgen(method, structural, getter = size)]
|
||||
fn co_size(this: &CodingOptions) -> Option<crate::size_spec::SizeSpec>;
|
||||
|
||||
#[wasm_bindgen(method, structural, getter = macros)]
|
||||
fn co_macros(this: &CodingOptions) -> Option<bool>;
|
||||
|
||||
#[wasm_bindgen(method, structural, getter = encodings)]
|
||||
fn co_encodings(this: &CodingOptions) -> Option<Encodation>;
|
||||
|
||||
#[wasm_bindgen(typescript_type = "Uint8Array | string")]
|
||||
pub type Buffer;
|
||||
}
|
||||
|
||||
enum DataSource {
|
||||
String(String),
|
||||
Bytes(Vec<u8>),
|
||||
}
|
||||
fn buffer_from_js(buf: Buffer) -> Result<DataSource, JsError> {
|
||||
buf.dyn_into::<Uint8Array>()
|
||||
.map(|array| DataSource::Bytes(array.to_vec()))
|
||||
.or_else(|js| {
|
||||
js.dyn_into::<js_sys::JsString>()
|
||||
.and_then(|s| s.as_string().ok_or_else(|| JsValue::from(s).into()))
|
||||
.map(DataSource::String)
|
||||
})
|
||||
.or_else(|_| Err(JsError::new("Buffer must be a string or Uint8Array")))
|
||||
}
|
||||
|
||||
fn encodation_type_from_js_string(s: JsValue) -> EncodationType {
|
||||
let s = s
|
||||
.as_string()
|
||||
.unwrap_or_else(|| wasm_bindgen::throw_str("Invalid encodation type"));
|
||||
let string = s.as_str();
|
||||
match string {
|
||||
"ascii" => EncodationType::Ascii,
|
||||
"c40" => EncodationType::C40,
|
||||
"text" => EncodationType::Text,
|
||||
"x12" => EncodationType::X12,
|
||||
"edifact" => EncodationType::Edifact,
|
||||
"base256" => EncodationType::Base256,
|
||||
_ => wasm_bindgen::throw_str("Invalid encodation type"),
|
||||
}
|
||||
}
|
||||
|
||||
fn encodation_type_from_js(e: Encodation) -> FlagSet<EncodationType> {
|
||||
e.dyn_into::<JsString>()
|
||||
.map(|s| encodation_type_from_js_string(s.into()).into())
|
||||
.or_else(|e| {
|
||||
e.dyn_into::<js_sys::Array>().map(|a| {
|
||||
a.iter()
|
||||
.map(|js| encodation_type_from_js_string(js))
|
||||
.fold(FlagSet::default(), |a, n| a | n)
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|_| wasm_bindgen::throw_str("Invalid encodation type"))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct DataMatrix{
|
||||
dmtx: datamatrix::DataMatrix,
|
||||
bitmap: datamatrix::placement::Bitmap<bool>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DataMatrix {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn encode(data: Buffer, options: Option<CodingOptions>) -> Result<DataMatrix, JsError> {
|
||||
let mut builder = DataMatrixBuilder::new();
|
||||
if let Some(options) = options {
|
||||
if let Some(size) = options.co_size() {
|
||||
let size = crate::size_spec::symbolset_from_json(size);
|
||||
builder = builder.with_symbol_list(size);
|
||||
}
|
||||
if let Some(encoding) = options.co_encodings() {
|
||||
builder = builder.with_encodation_types(encodation_type_from_js(encoding));
|
||||
}
|
||||
if let Some(macros) = options.co_macros() {
|
||||
builder = builder.with_macros(macros);
|
||||
}
|
||||
}
|
||||
|
||||
match buffer_from_js(data)? {
|
||||
DataSource::Bytes(bytes) => builder.encode(bytes.as_slice()),
|
||||
DataSource::String(str) => builder.encode_str(&str),
|
||||
}
|
||||
.map(|dmtx| {
|
||||
let bitmap = dmtx.bitmap();
|
||||
DataMatrix{dmtx, bitmap}
|
||||
})
|
||||
.map_err(|err| match err {
|
||||
DataEncodingError::TooMuchOrIllegalData => "Too much data",
|
||||
DataEncodingError::SymbolListEmpty => "No valid symbol size",
|
||||
})
|
||||
.map_err(|err| JsError::new(err))
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Get the full set of codewords encoded into this barcode
|
||||
pub fn codewords(&self) -> Box<[u8]> {
|
||||
self.dmtx.codewords().to_vec().into_boxed_slice()
|
||||
}
|
||||
|
||||
/// Get the data part of the low-level encoding of the barcode
|
||||
pub fn data_codewords(&self) -> Box<[u8]> {
|
||||
self.dmtx.data_codewords().to_vec().into_boxed_slice()
|
||||
}
|
||||
|
||||
/// Draw the barcode via an SVG path. Note: this path expects even/odd fill
|
||||
/// i.e., embed this in `<path fill-rule="evenodd" d="..." />
|
||||
pub fn svg_path(&self, x0: Option<isize>, y0: Option<isize>) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut path: String = String::new();
|
||||
|
||||
let x0 = x0.unwrap_or(0);
|
||||
let y0 = y0.unwrap_or(0);
|
||||
|
||||
write!(path, "M{x0},{y0}").unwrap();
|
||||
for part in self.bitmap.path() {
|
||||
match part {
|
||||
PathSegment::Move(dx, dy) => write!(path, "m{dx},{dy}"),
|
||||
PathSegment::Horizontal(h) => write!(path, "h{h}"),
|
||||
PathSegment::Vertical(v) => write!(path, "v{v}"),
|
||||
PathSegment::Close => write!(path, "z"),
|
||||
}.unwrap();
|
||||
}
|
||||
path
|
||||
}
|
||||
|
||||
/// Return an array of filled pixel coordinates.
|
||||
/// The returned buffer is `[x0,y0,x1,y1,...]`
|
||||
pub fn pixels(&self) -> Box<[u8]> {
|
||||
let mut ret = Vec::new();
|
||||
for (x,y) in self.bitmap.pixels() {
|
||||
if x > 255 || y > 255 {
|
||||
panic!("Returned too high of a pixel number");
|
||||
}
|
||||
ret.push(x as u8);
|
||||
ret.push(y as u8);
|
||||
}
|
||||
ret.into_boxed_slice()
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.bitmap.width()
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.bitmap.height()
|
||||
}
|
||||
}
|
||||
14
src/lib.rs
Normal file
14
src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
mod utils;
|
||||
|
||||
macro_rules! hash_map {
|
||||
($($($key:expr),+ => $value:expr),*$(,)?) => {{
|
||||
let mut res = HashMap::new();
|
||||
$($(
|
||||
res.insert($key, $value);
|
||||
)+)*
|
||||
res
|
||||
}};
|
||||
}
|
||||
|
||||
mod size_spec;
|
||||
mod datamatrix;
|
||||
183
src/size_spec.rs
Normal file
183
src/size_spec.rs
Normal file
@@ -0,0 +1,183 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
use datamatrix::{SymbolList, SymbolSize};
|
||||
use wasm_bindgen::throw_val;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(typescript_custom_section)]
|
||||
const SIZE_SPEC_TS: &'static str = r#"
|
||||
interface SymbolSizeFilter {
|
||||
square?: bool;
|
||||
rectangle?: bool;
|
||||
extended?: bool;
|
||||
min_width?: number;
|
||||
max_width?: number;
|
||||
min_height?: number;
|
||||
max_height?: number;
|
||||
}
|
||||
|
||||
type SymbolSize = "10" | "10x10" | "12" | "12x12" | "14" | "14x14" | "16" |
|
||||
"16x16" | "18" | "18x18" | "20" | "20x20" | "22" | "22x22" | "24" |
|
||||
"24x24" | "26" | "26x26" | "32" | "32x32" | "36" | "36x36" | "40" |
|
||||
"40x40" | "44" | "44x44" | "48" | "48x48" | "52" | "52x52" | "64" |
|
||||
"64x64" | "72" | "72x72" | "80" | "80x80" | "88" | "88x88" | "96" |
|
||||
"96x96" | "104" | "104x104" | "120" | "120x120" | "132" | "132x132" |
|
||||
"144" | "144x144" | "8x18" | "8x32" | "12x26" | "12x36" | "16x36" |
|
||||
"16x48" | "8x48" | "8x64" | "8x80" | "8x96" | "8x120" | "8x144" |
|
||||
"12x64" | "12x88" | "16x64" | "20x36" | "20x44" | "20x64" | "22x48" |
|
||||
"24x48" | "24x64" | "26x40" | "26x48" | "26x64";
|
||||
|
||||
type SymbolSizeSpec = SymbolSize | SymbolSizeFilter | Array<SymbolSizeSpec>;
|
||||
"#;
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(typescript_type = "SymbolSizeSpec")]
|
||||
pub type SizeSpec;
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct SizeFilter {
|
||||
square: Option<bool>,
|
||||
rectangle: Option<bool>,
|
||||
extended: Option<bool>,
|
||||
min_width: Option<usize>,
|
||||
max_width: Option<usize>,
|
||||
min_height: Option<usize>,
|
||||
max_height: Option<usize>,
|
||||
}
|
||||
|
||||
static SYMBOL_SIZE_STRINGS: LazyLock<HashMap<&'static str, SymbolSize>> = LazyLock::new(|| {
|
||||
hash_map! {
|
||||
"10" , "10x10" => SymbolSize::Square10,
|
||||
"12" , "12x12" => SymbolSize::Square12,
|
||||
"14" , "14x14" => SymbolSize::Square14,
|
||||
"16" , "16x16" => SymbolSize::Square16,
|
||||
"18" , "18x18" => SymbolSize::Square18,
|
||||
"20" , "20x20" => SymbolSize::Square20,
|
||||
"22" , "22x22" => SymbolSize::Square22,
|
||||
"24" , "24x24" => SymbolSize::Square24,
|
||||
"26" , "26x26" => SymbolSize::Square26,
|
||||
"32", "32x32" => SymbolSize::Square32,
|
||||
"36", "36x36" => SymbolSize::Square36,
|
||||
"40", "40x40" => SymbolSize::Square40,
|
||||
"44", "44x44" => SymbolSize::Square44,
|
||||
"48", "48x48" => SymbolSize::Square48,
|
||||
"52", "52x52" => SymbolSize::Square52,
|
||||
"64", "64x64" => SymbolSize::Square64,
|
||||
"72", "72x72" => SymbolSize::Square72,
|
||||
"80", "80x80" => SymbolSize::Square80,
|
||||
"88", "88x88" => SymbolSize::Square88,
|
||||
"96", "96x96" => SymbolSize::Square96,
|
||||
"104", "104x104" => SymbolSize::Square104,
|
||||
"120", "120x120" => SymbolSize::Square120,
|
||||
"132", "132x132" => SymbolSize::Square132,
|
||||
"144", "144x144" => SymbolSize::Square144,
|
||||
"8x18" => SymbolSize::Rect8x18,
|
||||
"8x32" => SymbolSize::Rect8x32,
|
||||
"12x26" => SymbolSize::Rect12x26,
|
||||
"12x36" => SymbolSize::Rect12x36,
|
||||
"16x36" => SymbolSize::Rect16x36,
|
||||
"16x48" => SymbolSize::Rect16x48,
|
||||
"8x48" => SymbolSize::Rect8x48,
|
||||
"8x64" => SymbolSize::Rect8x64,
|
||||
"8x80" => SymbolSize::Rect8x80,
|
||||
"8x96" => SymbolSize::Rect8x96,
|
||||
"8x120" => SymbolSize::Rect8x120,
|
||||
"8x144" => SymbolSize::Rect8x144,
|
||||
"12x64" => SymbolSize::Rect12x64,
|
||||
"12x88" => SymbolSize::Rect12x88,
|
||||
"16x64" => SymbolSize::Rect16x64,
|
||||
"20x36" => SymbolSize::Rect20x36,
|
||||
"20x44" => SymbolSize::Rect20x44,
|
||||
"20x64" => SymbolSize::Rect20x64,
|
||||
"22x48" => SymbolSize::Rect22x48,
|
||||
"24x48" => SymbolSize::Rect24x48,
|
||||
"24x64" => SymbolSize::Rect24x64,
|
||||
"26x40" => SymbolSize::Rect26x40,
|
||||
"26x48" => SymbolSize::Rect26x48,
|
||||
"26x64" => SymbolSize::Rect26x64,
|
||||
}
|
||||
});
|
||||
|
||||
fn symbolsize_from_string(name: &str) -> Option<SymbolSize> {
|
||||
SYMBOL_SIZE_STRINGS.get(name).copied()
|
||||
}
|
||||
|
||||
fn symbolsize_from_int(name: u32) -> Option<SymbolSize> {
|
||||
Some(match name {
|
||||
10 => SymbolSize::Square10,
|
||||
12 => SymbolSize::Square12,
|
||||
14 => SymbolSize::Square14,
|
||||
16 => SymbolSize::Square16,
|
||||
18 => SymbolSize::Square18,
|
||||
20 => SymbolSize::Square20,
|
||||
22 => SymbolSize::Square22,
|
||||
24 => SymbolSize::Square24,
|
||||
26 => SymbolSize::Square26,
|
||||
32 => SymbolSize::Square32,
|
||||
36 => SymbolSize::Square36,
|
||||
40 => SymbolSize::Square40,
|
||||
44 => SymbolSize::Square44,
|
||||
48 => SymbolSize::Square48,
|
||||
52 => SymbolSize::Square52,
|
||||
64 => SymbolSize::Square64,
|
||||
72 => SymbolSize::Square72,
|
||||
80 => SymbolSize::Square80,
|
||||
88 => SymbolSize::Square88,
|
||||
96 => SymbolSize::Square96,
|
||||
104 => SymbolSize::Square104,
|
||||
120 => SymbolSize::Square120,
|
||||
132 => SymbolSize::Square132,
|
||||
144 => SymbolSize::Square144,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn symbolset_from_json(json: SizeSpec) -> SymbolList {
|
||||
if let Some(size) = json.as_string() {
|
||||
if let Some(size) = symbolsize_from_string(&size) {
|
||||
return size.into()
|
||||
}
|
||||
} else if let Some(size) = json.as_f64() {
|
||||
if size as u32 as f64 == size {
|
||||
if let Some(size) = symbolsize_from_int(size as u32) {
|
||||
return size.into()
|
||||
}
|
||||
}
|
||||
} else if let Some(arr) = json.dyn_ref::<js_sys::Array>() {
|
||||
let mut symset = SymbolList::with_whitelist(std::iter::empty());
|
||||
for item in arr.values() {
|
||||
match item {
|
||||
Err(err) => throw_val(err),
|
||||
Ok(val) => symset.extend(symbolset_from_json(val.into())),
|
||||
}
|
||||
}
|
||||
return symset;
|
||||
} else if let Ok(value) = serde_wasm_bindgen::from_value::<SizeFilter>(json.into()) {
|
||||
let mut symset = if value.extended.unwrap_or(false) {
|
||||
SymbolList::with_extended_rectangles()
|
||||
} else {
|
||||
SymbolList::default()
|
||||
};
|
||||
if !value.square.unwrap_or(true) {
|
||||
symset = symset.enforce_rectangular()
|
||||
}
|
||||
if !value.rectangle.unwrap_or(true) {
|
||||
symset = symset.enforce_square()
|
||||
}
|
||||
if let Some(v) = value.min_height {
|
||||
symset = symset.enforce_height_in(v..);
|
||||
}
|
||||
if let Some(v) = value.max_height {
|
||||
symset = symset.enforce_height_in(..v + 1);
|
||||
}
|
||||
if let Some(v) = value.min_width {
|
||||
symset = symset.enforce_width_in(v..);
|
||||
}
|
||||
if let Some(v) = value.max_width {
|
||||
symset = symset.enforce_width_in(..v + 1);
|
||||
}
|
||||
return symset;
|
||||
}
|
||||
wasm_bindgen::throw_str("Invalid size");
|
||||
}
|
||||
11
src/utils.rs
Normal file
11
src/utils.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
#[allow(unused)]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
Reference in New Issue
Block a user