diff --git a/Cargo.toml b/Cargo.toml index 6fbc3c6..a41e281 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,6 @@ license = "MPL-2.0" description = "Encoding and Decoding for HackFSU's custom visual data encoding format." [dependencies] + +[dev-dependencies] +quickcheck = "0.3" diff --git a/benches/code128_decode.rs b/benches/code128_decode.rs new file mode 100644 index 0000000..028729e --- /dev/null +++ b/benches/code128_decode.rs @@ -0,0 +1,40 @@ +#![feature(test)] + +extern crate feather_code as fc; +extern crate test; + +use fc::barcode::code128::Code128; +use fc::barcode::format::Decode; +use fc::barcode::code128::encodings; +use test::Bencher; + +#[bench] +fn decode_u8(bencher: &mut Bencher) { + let buffer = [105, 102, 42, 18, 40, 20, 50, 101, 16, 92, 106]; + let code = Code128(buffer.as_ref()); + + bencher.iter(|| { + code.decode().unwrap(); + }) +} + +#[bench] +fn decode_vec(bencher: &mut Bencher) { + let buffer = vec![105, 102, 42, 18, 40, 20, 50, 101, 16, 92, 106]; + let code = Code128(buffer.as_ref()); + + bencher.iter(|| { + code.decode().unwrap(); + }) +} + +#[bench] +fn decode_pattern(bencher: &mut Bencher) { + use encodings::Pattern::*; + let buffer = [C105, C102, C42, C18, C40, C20, C50, C101, C16, C92, C106]; + let code = Code128(buffer.as_ref()); + + bencher.iter(|| { + code.decode().unwrap(); + }) +} diff --git a/hooks/pre-commit.sh b/hooks/pre-commit.sh index 1855221..0c86b24 100755 --- a/hooks/pre-commit.sh +++ b/hooks/pre-commit.sh @@ -30,7 +30,8 @@ then cargo doc --no-deps && cargo build && - cargo test --all + cargo test --all && + rustup run nightly cargo bench # Capture exit code from tests status=$? diff --git a/src/barcode/code128/encodings.rs b/src/barcode/code128/encodings.rs new file mode 100644 index 0000000..56452a6 --- /dev/null +++ b/src/barcode/code128/encodings.rs @@ -0,0 +1,277 @@ +//! Implementation of Code128 barcode encodings + +use super::{Encoding, Symbology}; + +/// Representation of Code128 patterns +/// +/// Representation of the symbols used in Code128; depending on the active code +/// set, each symbol maps to one of 3 ASCII values in official Code128. +/// +/// Pattern | A | B | C +/// :-------|:-----:|:-----:|:------: +/// C0 | space | space | 00 +/// C1 | ! | ! | 01 +/// C2 | " | " | 02 +/// C3 | # | # | 03 +/// C4 | $ | $ | 04 +/// C5 | % | % | 05 +/// C6 | & | & | 06 +/// C7 | ' | ' | 07 +/// C8 | ( | ( | 08 +/// C9 | ) | ) | 09 +/// C10 | \* | \* | 10 +/// C11 | + | + | 11 +/// C12 | , | , | 12 +/// C13 | - | - | 13 +/// C14 | . | . | 14 +/// C15 | / | / | 15 +/// C16 | 0 | 0 | 16 +/// C17 | 1 | 1 | 17 +/// C18 | 2 | 2 | 18 +/// C19 | 3 | 3 | 19 +/// C20 | 4 | 4 | 20 +/// C21 | 5 | 5 | 21 +/// C22 | 6 | 6 | 22 +/// C23 | 7 | 7 | 23 +/// C24 | 8 | 8 | 24 +/// C25 | 9 | 9 | 25 +/// C26 | : | : | 26 +/// C27 | ; | ; | 27 +/// C28 | < | < | 28 +/// C29 | = | = | 29 +/// C30 | > | > | 30 +/// C31 | ? | ? | 31 +/// C32 | @ | @ | 32 +/// C33 | A | A | 33 +/// C34 | B | B | 34 +/// C35 | C | C | 35 +/// C36 | D | D | 36 +/// C37 | E | E | 37 +/// C38 | F | F | 38 +/// C39 | G | G | 39 +/// C40 | H | H | 40 +/// C41 | I | I | 41 +/// C42 | J | J | 42 +/// C43 | K | K | 43 +/// C44 | L | L | 44 +/// C45 | M | M | 45 +/// C46 | N | N | 46 +/// C47 | O | O | 47 +/// C48 | P | P | 48 +/// C49 | Q | Q | 49 +/// C50 | R | R | 50 +/// C51 | S | S | 51 +/// C52 | T | T | 52 +/// C53 | U | U | 53 +/// C54 | V | V | 54 +/// C55 | W | W | 55 +/// C56 | X | X | 56 +/// C57 | Y | Y | 57 +/// C58 | Z | Z | 58 +/// C59 | [ | [ | 59 +/// C60 | \\ | \\ | 60 +/// C61 | ] | ] | 61 +/// C62 | ^ | ^ | 62 +/// C63 | \_ | \_ | 63 +/// C64 | NUL | ` | 64 +/// C65 | SOH | a | 65 +/// C66 | STX | b | 66 +/// C67 | ETX | c | 67 +/// C68 | EOT | d | 68 +/// C69 | ENQ | e | 69 +/// C70 | ACK | f | 70 +/// C71 | BEL | g | 71 +/// C72 | BS | h | 72 +/// C73 | HT | i | 73 +/// C74 | LF | j | 74 +/// C75 | VT | k | 75 +/// C76 | FF | l | 76 +/// C77 | CR | m | 77 +/// C78 | SO | n | 78 +/// C79 | SI | o | 79 +/// C80 | DLE | p | 80 +/// C81 | DC1 | q | 81 +/// C82 | DC2 | r | 82 +/// C83 | DC3 | s | 83 +/// C84 | DC4 | t | 84 +/// C85 | NAK | u | 85 +/// C86 | SYN | v | 86 +/// C87 | ETB | w | 87 +/// C88 | CAN | x | 88 +/// C89 | EM | y | 89 +/// C90 | SUB | z | 90 +/// C91 | ESC | { | 91 +/// C92 | FS | \| | 92 +/// C93 | GS | } | 93 +/// C94 | RS | ~ | 94 +/// C95 | US | DEL | 95 +/// C96 | FNC 3 | FNC 3 | 96 +/// C97 | FNC 2 | FNC 2 | 97 +/// C98 |Shift B|Shift A| 98 +/// C99 |Code C |Code C | 99 +/// C100 |Code B | FNC 4 | Code B +/// C101 | FNC 4 |Code A | Code A +/// C102 | FNC 1 | FNC 1 | FNC 1 +/// C103 |Start A|Start A| Start A +/// C104 |Start B|Start B| Start B +/// C105 |Start C|Start C| Start C +/// C106 | stop | stop | stop +#[allow(missing_docs)] +#[derive(PartialEq,Eq,PartialOrd,Ord,Debug,Clone,Copy)] +pub enum Pattern { + C0, C1, C2, C3, C4, C5, C6, C7, C8, C9, C10, + C11, C12, C13, C14, C15, C16, C17, C18, C19, + C20, C21, C22, C23, C24, C25, C26, C27, C28, + C29, C30, C31, C32, C33, C34, C35, C36, C37, + C38, C39, C40, C41, C42, C43, C44, C45, C46, + C47, C48, C49, C50, C51, C52, C53, C54, C55, + C56, C57, C58, C59, C60, C61, C62, C63, C64, + C65, C66, C67, C68, C69, C70, C71, C72, C73, + C74, C75, C76, C77, C78, C79, C80, C81, C82, + C83, C84, C85, C86, C87, C88, C89, C90, C91, + C92, C93, C94, C95, C96, C97, C98, C99, C100, + C101, C102, C103, C104, C105, C106, +} + +// Jump table conversion +impl From for Pattern { + fn from(u: u8) -> Pattern { + use self::Pattern::*; + match u { + 0 => C0, 1 => C1, 2 => C2, 3 => C3, 4 => C4, 5 => C5, 6 => C6, + 7 => C7, 8 => C8, 9 => C9, 10 => C10, 11 => C11, 12 => C12, 13 => C13, + 14 => C14, 15 => C15, 16 => C16, 17 => C17, 18 => C18, 19 => C19, + 20 => C20, 21 => C21, 22 => C22, 23 => C23, 24 => C24, 25 => C25, + 26 => C26, 27 => C27, 28 => C28, 29 => C29, 30 => C30, 31 => C31, + 32 => C32, 33 => C33, 34 => C34, 35 => C35, 36 => C36, 37 => C37, + 38 => C38, 39 => C39, 40 => C40, 41 => C41, 42 => C42, 43 => C43, + 44 => C44, 45 => C45, 46 => C46, 47 => C47, 48 => C48, 49 => C49, + 50 => C50, 51 => C51, 52 => C52, 53 => C53, 54 => C54, 55 => C55, + 56 => C56, 57 => C57, 58 => C58, 59 => C59, 60 => C60, 61 => C61, + 62 => C62, 63 => C63, 64 => C64, 65 => C65, 66 => C66, 67 => C67, + 68 => C68, 69 => C69, 70 => C70, 71 => C71, 72 => C72, 73 => C73, + 74 => C74, 75 => C75, 76 => C76, 77 => C77, 78 => C78, 79 => C79, + 80 => C80, 81 => C81, 82 => C82, 83 => C83, 84 => C84, 85 => C85, + 86 => C86, 87 => C87, 88 => C88, 89 => C89, 90 => C90, 91 => C91, + 92 => C92, 93 => C93, 94 => C94, 95 => C95, 96 => C96, 97 => C97, + 98 => C98, 99 => C99, 100 => C100, 101 => C101, 102 => C102, + 103 => C103, 104 => C104, 105 => C105, _ => C106, + } + } +} + +// Simple numerical cast from enum to u8 +impl Into for Pattern { + fn into(self) -> u8 { + self as u8 + } +} + +impl Encoding for Pattern { + #[inline] + fn stop() -> Self {Pattern::C106} + + #[inline] + fn switch(s: Symbology) -> Self { + match s { + Symbology::A => Pattern::C101, + Symbology::B => Pattern::C100, + Symbology::C => Pattern::C99, + } + } + + #[inline] + fn start(s: Symbology) -> Self { + match s { + Symbology::A => Pattern::C103, + Symbology::B => Pattern::C104, + Symbology::C => Pattern::C105, + } + } + + #[inline] + fn shift() -> Self {Pattern::C98} + + #[inline] + fn fnc1() -> Self {Pattern::C102} + + #[inline] + fn fnc2() -> Self {Pattern::C97} + + #[inline] + fn fnc3() -> Self {Pattern::C96} + + #[inline] + fn fnc4(s: Symbology) -> Option { + match s { + Symbology::A => Some(Pattern::C101), + Symbology::B => Some(Pattern::C100), + _ => None, + } + } + + #[inline] + fn as_u8(&self) -> u8 { + *self as u8 + } +} + +impl Encoding for u8 { + + #[inline] + fn stop() -> Self {106} + + #[inline] + fn switch(s: Symbology) -> Self { + match s { + Symbology::A => 101, + Symbology::B => 100, + Symbology::C => 99, + } + } + + #[inline] + fn start(s: Symbology) -> Self { + match s { + Symbology::A => 103, + Symbology::B => 104, + Symbology::C => 105, + } + } + + #[inline] + fn shift() -> Self {98} + + #[inline] + fn fnc1() -> Self {102} + + #[inline] + fn fnc2() -> Self {97} + + #[inline] + fn fnc3() -> Self {96} + + #[inline] + fn fnc4(s: Symbology) -> Option { + match s { + Symbology::A => Some(101), + Symbology::B => Some(100), + _ => None, + } + } + + #[inline] + fn as_u8(&self) -> u8 { + *self + } +} + +#[cfg(test)] +mod test { + quickcheck! { + fn pattern_from_u8_to_u8(p: u8) -> bool { + use super::Encoding; + p == super::Pattern::from(p).as_u8() + } + } +} diff --git a/src/barcode/code128/mod.rs b/src/barcode/code128/mod.rs new file mode 100644 index 0000000..9099f60 --- /dev/null +++ b/src/barcode/code128/mod.rs @@ -0,0 +1,506 @@ +//! Barcode 128 standard as data representation +//! +//! ![barcode example](https://upload.wikimedia.org/wikipedia/commons/4/41/9494-RI476394652CH.jpg) +//! +//! Encapsulates logic for encoding and decoding barcodes using the [`Format`], [`Decode`], and +//! [`Encode`] traits. +//! +//! [`Format`]: ../format/trait.Format.html +//! [`Encode`]: ../format/trait.Encode.html +//! [`Decode`]: ../format/trait.Decode.html + +use std::fmt::Debug; +use super::format; +use super::format::{Format, Decode}; +pub mod encodings; + +/// Code128 alphabets (symbologies) which specify how [patterns][`Encoding`] map to characters +/// +/// [`Encoding`]: trait.Encoding.html +#[derive(PartialEq,Eq,Debug,Clone,Copy)] +pub enum Symbology { + /// (ASCII 00 to 95) A-Z, 0-9, and special characters + A = 103, + /// (ASCII 32-127) a-z, A-Z, and 0-9 + B = 104, + /// High density, number pair encoding + C = 105, +} + +/// Interface for types which represent Code128 encodings +/// +/// Code128 encodings are patterns whch reprsent numerical values from 0 to 106. Each encoding +/// maps to a differet subset of ascii values depending on the [`Symbology`]: +/// +/// u8 | [`A`] | [`B`] | [`C`] +/// :----|:-----:|:-----:|:------: +/// 0 | space | space | 00 +/// 1 | ! | ! | 01 +/// 2 | " | " | 02 +/// 3 | # | # | 03 +/// 4 | $ | $ | 04 +/// 5 | % | % | 05 +/// 6 | & | & | 06 +/// 7 | ' | ' | 07 +/// 8 | ( | ( | 08 +/// 9 | ) | ) | 09 +/// 10 | \* | \* | 10 +/// 11 | + | + | 11 +/// 12 | , | , | 12 +/// 13 | - | - | 13 +/// 14 | . | . | 14 +/// 15 | / | / | 15 +/// 16 | 0 | 0 | 16 +/// 17 | 1 | 1 | 17 +/// 18 | 2 | 2 | 18 +/// 19 | 3 | 3 | 19 +/// 20 | 4 | 4 | 20 +/// 21 | 5 | 5 | 21 +/// 22 | 6 | 6 | 22 +/// 23 | 7 | 7 | 23 +/// 24 | 8 | 8 | 24 +/// 25 | 9 | 9 | 25 +/// 26 | : | : | 26 +/// 27 | ; | ; | 27 +/// 28 | < | < | 28 +/// 29 | = | = | 29 +/// 30 | > | > | 30 +/// 31 | ? | ? | 31 +/// 32 | @ | @ | 32 +/// 33 | A | A | 33 +/// 34 | B | B | 34 +/// 35 | C | C | 35 +/// 36 | D | D | 36 +/// 37 | E | E | 37 +/// 38 | F | F | 38 +/// 39 | G | G | 39 +/// 40 | H | H | 40 +/// 41 | I | I | 41 +/// 42 | J | J | 42 +/// 43 | K | K | 43 +/// 44 | L | L | 44 +/// 45 | M | M | 45 +/// 46 | N | N | 46 +/// 47 | O | O | 47 +/// 48 | P | P | 48 +/// 49 | Q | Q | 49 +/// 50 | R | R | 50 +/// 51 | S | S | 51 +/// 52 | T | T | 52 +/// 53 | U | U | 53 +/// 54 | V | V | 54 +/// 55 | W | W | 55 +/// 56 | X | X | 56 +/// 57 | Y | Y | 57 +/// 58 | Z | Z | 58 +/// 59 | [ | [ | 59 +/// 60 | \\ | \\ | 60 +/// 61 | ] | ] | 61 +/// 62 | ^ | ^ | 62 +/// 63 | \_ | \_ | 63 +/// 64 | NUL | ` | 64 +/// 65 | SOH | a | 65 +/// 66 | STX | b | 66 +/// 67 | ETX | c | 67 +/// 68 | EOT | d | 68 +/// 69 | ENQ | e | 69 +/// 70 | ACK | f | 70 +/// 71 | BEL | g | 71 +/// 72 | BS | h | 72 +/// 73 | HT | i | 73 +/// 74 | LF | j | 74 +/// 75 | VT | k | 75 +/// 76 | FF | l | 76 +/// 77 | CR | m | 77 +/// 78 | SO | n | 78 +/// 79 | SI | o | 79 +/// 80 | DLE | p | 80 +/// 81 | DC1 | q | 81 +/// 82 | DC2 | r | 82 +/// 83 | DC3 | s | 83 +/// 84 | DC4 | t | 84 +/// 85 | NAK | u | 85 +/// 86 | SYN | v | 86 +/// 87 | ETB | w | 87 +/// 88 | CAN | x | 88 +/// 89 | EM | y | 89 +/// 90 | SUB | z | 90 +/// 91 | ESC | { | 91 +/// 92 | FS | \| | 92 +/// 93 | GS | } | 93 +/// 94 | RS | ~ | 94 +/// 95 | US | DEL | 95 +/// 96 | FNC 3 | FNC 3 | 96 +/// 97 | FNC 2 | FNC 2 | 97 +/// 98 |Shift B|Shift A| 98 +/// 99 |Code C |Code C | 99 +/// 100 |Code B | FNC 4 | Code B +/// 101 | FNC 4 |Code A | Code A +/// 102 | FNC 1 | FNC 1 | FNC 1 +/// 103 |Start A|Start A| Start A +/// 104 |Start B|Start B| Start B +/// 105 |Start C|Start C| Start C +/// 106 | stop | stop | stop +/// +/// [`A`]: enum.Symbology.html#variant.A +/// [`B`]: enum.Symbology.html#variant.B +/// [`C`]: enum.Symbology.html#variant.C +pub trait Encoding: From + Into + PartialOrd { + + /// Get the stop value in the particular encoding format + /// + /// Correspond to numerical values such that: + /// + /// u8 | [`A`] | [`B`] | [`C`] + /// :----|:-----:|:-----:|:------: + /// 106 | stop | stop | stop + /// + /// [`A`]: enum.Symbology.html#variant.A + /// [`B`]: enum.Symbology.html#variant.B + /// [`C`]: enum.Symbology.html#variant.C + fn stop() -> Self; + + /// Switch symbol for a given symbology + /// + /// Correspond to numerical values such that: + /// + /// u8 | [`A`] | [`B`] | [`C`] + /// :----|:-----:|:-----:|:------: + /// 99 |Code C |Code C | ... + /// 100 |Code B | ... | Code B + /// 101 | ... |Code A | Code A + /// + /// # Examples + /// + /// ``` + /// # use feather_code::barcode::code128::Encoding; + /// # use feather_code::barcode::code128::Symbology; + /// + /// assert_eq!(u8::switch(Symbology::A), 101); + /// assert_eq!(u8::switch(Symbology::B), 100); + /// assert_eq!(u8::switch(Symbology::C), 99); + /// ``` + /// + /// [`A`]: enum.Symbology.html#variant.A + /// [`B`]: enum.Symbology.html#variant.B + /// [`C`]: enum.Symbology.html#variant.C + fn switch(Symbology) -> Self; + + /// Start symbol for a given symbology + /// + /// Correspond to numerical values such that + /// + /// u8 | [`A`] | [`B`] | [`C`] + /// :----|:-----:|:-----:|:------: + /// 103 |Start A|Start A| Start A + /// 104 |Start B|Start B| Start B + /// 105 |Start C|Start C| Start C + /// + /// [`A`]: enum.Symbology.html#variant.A + /// [`B`]: enum.Symbology.html#variant.B + /// [`C`]: enum.Symbology.html#variant.C + fn start(Symbology) -> Self; + + /// Shift code wich indicates that the next encoding uses the shifted symbology + /// + /// When in symbology A or B, the shift code indicates the next encoding will use the other + /// symbology to parse. If we have an encoded value which encodes a lot of lowercase + /// characters, we may use Symbology [`B`]. If we need to add a single special character to + /// the sequence though, we can shift into symbology [`A`] for a single encoding to add the + /// special character. + /// + /// Correspond to numerical values such that + /// + /// u8 | [`A`] | [`B`] + /// :----|:-----:|:-----: + /// 98 |Shift B|Shift A + /// + /// [`A`]: enum.Symbology.html#variant.A + /// [`B`]: enum.Symbology.html#variant.B + fn shift() -> Self; + + /// Function 1 encoding, indicates special behaviour, ignored in the spec + fn fnc1() -> Self; + + /// Reserved encoding for function 2, currently in the spec but not used + fn fnc2() -> Self; + + /// Reserved encoding for function 3, currently in the spec but not used + fn fnc3() -> Self; + + /// Reserved encoding for function 4, currently in the spec but not used + /// + /// Correspond to numerical values such that: + /// + /// u8 | [`A`] | [`B`] + /// :----|:-----:|:-----: + /// 100 | ... | FNC 4 + /// 101 | FNC 4 | ... + /// + /// [`A`]: enum.Symbology.html#variant.A + /// [`B`]: enum.Symbology.html#variant.B + fn fnc4(Symbology) -> Option; + + /// Representation as a u8 for non-copy types to calculate checksum + fn as_u8(&self) -> u8; +} + +/// [Code128][wiki] barcode format +/// +/// # [`Symbology`] +/// +/// Code128 encodes the full 128 characters of ASCII using three different symbologies which +/// represent different subsets of ASCII. +/// +/// - [`A`]: A-Z, 0-9, and special characters (ASCII 00 to 95) +/// - [`B`]: a-z, A-Z, and 0-9 (ASCII 32-127) +/// - [`C`]: high density, number pair encoding +/// +/// The 107 different patterns map differently to ASCII characters for each +/// symbology, see documentation for `Pattern` or references for more details. +/// +/// # References +/// - [Code 128 on Wikipedia][wiki] +/// +/// [wiki]: https://en.wikipedia.org/wiki/Code_128 +/// [`Symbology`]: enum.Symbology.html +/// [`A`]: enum.Symbology.html#variant.A +/// [`B`]: enum.Symbology.html#variant.B +/// [`C`]: enum.Symbology.html#variant.C +#[derive(PartialEq,Eq,Debug)] +pub struct Code128<'a, E>(pub &'a [E]) where E: 'a + Encoding; + +impl<'a, E> Code128<'a, E> where E: 'a + Encoding { + /// If the encoding has valid format, returns the parts of the datum + /// + /// A valid [`Code128`] datum has a start encoding which determines the proper start symbology, + /// a series of encodings which represent data, a checksum digit to ensure that the data is + /// properly formatted, and a stop encoding which tells the barcode scanner to stop scanning. + fn data(&self) -> Option<(Symbology, &'a [E], &E)> { + let (start, rest) = match self.0.split_first() { + Some(x) => x, + None => return None, + }; + let rest = match rest.split_last() { + Some((stop,x)) if *stop == E::stop() => x, + _ => return None, + }; + let (check, symbols) = match rest.split_last() { + Some(x) => x, + None => return None, + }; + + match start { + _ if *start == E::start(Symbology::A) => Some((Symbology::A, symbols, &check)), + _ if *start == E::start(Symbology::B) => Some((Symbology::B, symbols, &check)), + _ if *start == E::start(Symbology::C) => Some((Symbology::C, symbols, &check)), + _ => None, + } + } +} + +impl<'a, E: 'a + Encoding> Format for Code128<'a, E> { + /// Verify data integrity, returning true if data is properly formatted, has the + /// proper length, and has the right modulo 103 checksum digit. + /// + /// # Example + /// + /// ``` + /// # use feather_code::barcode::code128::Code128; + /// # use feather_code::barcode::format::Format; + /// + /// assert!(Code128(&[103, 48, 42, 42, 17, 18, 19, 35, 54, 106]).checksum()); + /// ``` + fn checksum(&self) -> bool { + // split slice into respective parts, and return false indicating invalid + // data if the formatted data isn't the proper length + let (start, symbols, check) = match self.data() { + Some(x) => x, + None => return false, + }; + + // Sum raw numerical values from each symbol multiplied by its position + let sum: u64 = { + let mut pos: u64 = 0; + symbols.iter() + .fold(0, |sum, pat| { + let raw: u64 = pat.as_u8() as u64; + pos += 1; + sum + raw * pos + }) + } + start as u64; + + // Checksum is the remainder after dividing the raw code sum by 103 + E::from((sum % 103) as u8) == *check + } +} + +impl<'a, E> Decode for Code128<'a, E> where E: 'a + Encoding + Debug { + + /// Convert Code128 barcode representation to a string + /// + /// In case of badly formatted data, will return a [`format::Result`] detailing the issue. + /// + /// # Example + /// + /// ```rust + /// # use feather_code::barcode::format::Decode; + /// # use feather_code::barcode::code128::Code128; + /// # use feather_code::barcode::code128::encodings::Pattern::*; + /// let buffer = [C105, C102, C42, C18, C40, C20, C50, C101, C16, C92, C106]; + /// let country_code: String = Code128(buffer.as_ref()).decode().unwrap(); + /// + /// assert_eq!(country_code, "42184020500".to_string()); + /// ``` + fn decode(&self) -> format::Result { + use super::format::Error::*; + use super::code128::Symbology::*; + + if self.0.len() < 4 { return Err(InvalidLength(self.0.len())) } + + let mut decoded: String = "".to_string(); + // Grab start code or return with error in case of bad format + let (start, symbols, _) = match self.data() { + Some(x) => x, + _ => return Err(BadFormat("unrecognized format".into())), + }; + + enum Parser { A, B, C, ShiftA, ShiftB } + + let mut state = match start { + A => Parser::A, + B => Parser::B, + C => Parser::C, + }; + + // Use finite state machine to parse Code128 to a String + 'parser: for e in symbols.iter() { + state = match state { + Parser::A => { + match e.as_u8() { + n if n < 64 => {decoded.push((n+32) as char); Parser::A}, + n if n < 98 => {decoded.push((n-64) as char); Parser::A}, + 100 => Parser::B, // Switch to symbology B + 99 => Parser::C, // Switch to symbology C + 98 => Parser::ShiftB, // shift code + 106 => break 'parser, + 102 | 97 | 96 | 101 => Parser::A, // function 1, 2, 3, 4, disabled + _ => return Err(DecodeErr(format!("unrecognized encoding {:?}", *e))), + } + }, + Parser::B => { + match e.as_u8() { + n if n < 98 => {decoded.push((n+32) as char); Parser::B}, + 101 => Parser::B, // Switch to symbology A + 99 => Parser::B, // Switch to symbology C + 106 => break 'parser, + 98 => Parser::ShiftA, // shift code + 102 | 97 | 96 | 100 => Parser::B, // function 1, 2, 3, 4, disabled + _ => return Err(DecodeErr(format!("unrecognized encoding {:?}", *e))), + } + }, + Parser::C => { + match e.as_u8() { + n if n < 100 => {decoded.push_str(&n.to_string()); Parser::C} + 100 => Parser::B, // Switch to symbology B + 101 => Parser::A, // Switch to symbology A + 106 => break 'parser, + 102 => Parser::C, // function 1, disabled + _ => return Err(DecodeErr(format!("unexpected encoding {:?}", *e))), + } + }, + Parser::ShiftA => { + match e.as_u8() { + n if n < 64 => {decoded.push((n+32) as char); Parser::B}, + n if n < 98 => {decoded.push((n-64) as char); Parser::B}, + _ => return Err(DecodeErr(format!("unexpected shifted encoding {:?}", *e))), + } + }, + Parser::ShiftB => { + match e.as_u8() { + n if n < 98 => {decoded.push((n+32) as char); Parser::A}, + _ => return Err(DecodeErr(format!("unexpected shifted encoding {:?}", *e))), + } + }, + }; + }; + Ok(decoded) + } +} + +#[cfg(test)] +mod test { + + #[test] + fn checksum() { + use barcode::format::Format; + use barcode::code128::encodings::Pattern::*; + use barcode::code128::Code128; + + assert!(Code128(&[C103, C48, C42, C42, C17, C18, C19, C35, C54, C106]).checksum()); + + assert!(Code128(&[C105, C102, C42, C18, C40, C20, C50, C101, C16, C92, C106]).checksum()); + assert!(Code128(&[105, 102, 42, 18, 40, 20, 50, 101, 16, 92, 106]).checksum()); + } + + #[test] + fn decode() { + use barcode::code128::Code128; + use barcode::format::Decode; + + let pjj123_c = [103, 48, 42, 42, 17, 18, 19, 35, 54, 106]; + + assert_eq!(Code128(&pjj123_c).decode().unwrap(), "PJJ123C".to_string()); + + let country_code = [105, 102, 42, 18, 40, 20, 50, 101, 16, 92, 106]; + + assert_eq!(Code128(&country_code).decode().unwrap(), "42184020500".to_string()); + + let hello_world = [104, 40, 69, 76, 76, 79, 0, 55, 79, 82, 76, 68, 43, 106]; + + assert_eq!(Code128(&hello_world).decode().unwrap(), "Hello World".to_string()); + + let shift_codes = [103, 51, 40, 98, 73, 38, 52, 100, 98, 1, 34, 106]; + + assert_eq!(Code128(&shift_codes).decode().unwrap(), "SHiFT!".to_string()) + } + + #[test] + fn split_data() { + use barcode::code128::Code128; + use barcode::code128::encodings::Pattern::*; + use barcode::code128::Symbology::*; + + let symbols = [C103, C48, C42, C42, C17, C18, C19, C35, C54, C106]; + let code = Code128(&symbols); + + assert_eq!(code.data(), Some((A, [C48, C42, C42, C17, C18, C19, C35].as_ref(), &C54))); + + let symbols = [C103]; + let code = Code128(&symbols); + + assert_eq!(code.data(), None); + + let symbols = [C103, C106]; + let code = Code128(&symbols); + + assert_eq!(code.data(), None); + + let symbols = [C103, C48, C15, C106]; + let code = Code128(&symbols); + + assert_eq!(code.data(), Some((A, [C48].as_ref(), &C15))); + } + + quickcheck! { + fn short_data_is_invalid(symbols: Vec) -> bool { + use barcode::code128::Code128; + + if symbols.len() < 4 { + Code128(symbols.as_ref()).data() == None + } else { + true + } + } + } +} diff --git a/src/barcode/format.rs b/src/barcode/format.rs new file mode 100644 index 0000000..04d9d52 --- /dev/null +++ b/src/barcode/format.rs @@ -0,0 +1,42 @@ +//! Abstract representation of barcode, or other, data encoding formats +//! +//! [`Decode`] and [`Encode`] must be idempotent. +use std::result; + +/// Representation of a barcode format +pub trait Format { + + /// Perform format specific validation to verify data integrity + fn checksum(&self) -> bool; +} + +/// Specialized result type for errors in barcode conversions +pub type Result = result::Result; + +/// Describes failure cases for encoding and decoding barcodes +#[derive(Debug,PartialEq)] +pub enum Error { + /// Barcode is too short or too long + InvalidLength(usize), + /// Invalid internal format + BadFormat(String), + /// Encode failure + EncodeErr(String), + /// Decode failure + DecodeErr(String), +} + +/// Support decoding a particular format to the target type +pub trait Decode where Self: Format { + + /// Convert a formatted data value + fn decode(&self) -> Result; +} + + +/// Support encoding the target type as a particular format +pub trait Encode { + + /// Convert to a given format + fn encode(&self) -> Result; +} diff --git a/src/barcode/mod.rs b/src/barcode/mod.rs new file mode 100644 index 0000000..bd6d9d3 --- /dev/null +++ b/src/barcode/mod.rs @@ -0,0 +1,4 @@ +//! Implementation of barcode logic, not meant for normal library use + +pub mod code128; +pub mod format; diff --git a/src/lib.rs b/src/lib.rs index c54e76d..f38793d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,14 @@ // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#![deny(missing_docs)] + +//! # Feather Code +//! +//! Custom visual encoding format extended from code128 barcode encoding. #[cfg(test)] -mod tests { - #[test] - fn it_works() { - } -} +#[macro_use] +extern crate quickcheck; + +pub mod barcode;