diff --git a/Cargo.toml b/Cargo.toml index 9745310..9c114e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ resvg = {version = "0.35", optional = true, default-features=false} serde = { version = "1.0", features = ["derive", "rc"], optional = true } thiserror = "1.0.44" multimap = "0.9" +bit_reverse = "0.1.8" [dev-dependencies] java-properties = "2.0" diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs index 2c52328..36c2515 100644 --- a/benches/benchmarks.rs +++ b/benches/benchmarks.rs @@ -7,7 +7,7 @@ use rxing::multi::{GenericMultipleBarcodeReader, MultipleBarcodeReader}; use rxing::oned::rss::expanded::RSSExpandedReader; use rxing::oned::rss::RSS14Reader; use rxing::oned::{ - CodaBarReader, Code39Reader, Code93Reader, EAN13Reader, EAN8Reader, ITFReader, UPCAReader, + CodaBarReader, Code39Reader, Code93Reader, EAN13Reader, EAN8Reader, TelepenReader, ITFReader, UPCAReader, UPCEReader, }; use rxing::pdf417::PDF417Reader; @@ -165,6 +165,17 @@ fn rss_expanded_benchmark(c: &mut Criterion) { }); } +fn telepen_benchmark(c: &mut Criterion) { + let mut image = get_image("test_resources/blackbox/telepen-1/02.png"); + let mut reader = TelepenReader::default(); + + c.bench_function("telepen", |b| { + b.iter(|| { + let _res = reader.decode(&mut image); + }); + }); +} + fn upca_benchmark(c: &mut Criterion) { let mut image = get_image("test_resources/blackbox/upca-4/1.png"); c.bench_function("upca", |b| { @@ -210,6 +221,7 @@ criterion_group!( qrcode_benchmark, rss14_benchmark, rss_expanded_benchmark, + telepen_benchmark, upca_benchmark, upce_benchmark, multi_barcode_benchmark diff --git a/src/barcode_format.rs b/src/barcode_format.rs index 43b0464..2946991 100644 --- a/src/barcode_format.rs +++ b/src/barcode_format.rs @@ -73,6 +73,9 @@ pub enum BarcodeFormat { /** RSS EXPANDED */ RSS_EXPANDED, + /** TELEPEN */ + TELEPEN, + /** UPC-A 1D format. */ UPC_A, @@ -107,6 +110,7 @@ impl Display for BarcodeFormat { BarcodeFormat::MICRO_QR_CODE => "mqr", BarcodeFormat::RSS_14 => "rss 14", BarcodeFormat::RSS_EXPANDED => "rss expanded", + BarcodeFormat::TELEPEN => "telepen", BarcodeFormat::UPC_A => "upc a", BarcodeFormat::UPC_E => "upc e", BarcodeFormat::UPC_EAN_EXTENSION => "upc/ean extension", @@ -149,6 +153,7 @@ impl From<&str> for BarcodeFormat { "rss 14" | "rss_14" | "rss14" | "gs1 databar" | "gs1 databar coupon" | "gs1_databar_coupon" => BarcodeFormat::RSS_14, "rss expanded" | "expanded rss" | "rss_expanded" => BarcodeFormat::RSS_EXPANDED, + "telepen" => BarcodeFormat::TELEPEN, "upc a" | "upc_a" | "upca" => BarcodeFormat::UPC_A, "upc e" | "upc_e" | "upce" => BarcodeFormat::UPC_E, "upc ean extension" | "upc extension" | "ean extension" | "upc/ean extension" diff --git a/src/decode_hints.rs b/src/decode_hints.rs index dbc2885..9359915 100644 --- a/src/decode_hints.rs +++ b/src/decode_hints.rs @@ -121,6 +121,12 @@ pub enum DecodeHintType { */ #[cfg(feature = "allow_forced_iso_ied_18004_compliance")] QR_ASSUME_SPEC_CONFORM_INPUT, + + /* + * Will translate the ASCII values parsed by the Telepen reader into the Telepen Numeric form. + */ + TELEPEN_AS_NUMERIC, + /* * Data type the hint is expecting. * Among the possible values the {@link Void} stands out as being used for @@ -231,4 +237,9 @@ pub enum DecodeHintValue { */ #[cfg(feature = "allow_forced_iso_ied_18004_compliance")] QrAssumeSpecConformInput(bool), + + /** + * Translate the ASCII values parsed by the Telepen reader into the Telepen Numeric form; use {@link Boolean#TRUE}. + */ + TelepenAsNumeric(bool), } diff --git a/src/encode_hints.rs b/src/encode_hints.rs index ed26d65..07cf387 100644 --- a/src/encode_hints.rs +++ b/src/encode_hints.rs @@ -180,6 +180,11 @@ pub enum EncodeHintType { * exclusive. */ CODE128_COMPACT, + + /* + * Will translate the numeric values received by the Telepen writer into the Telepen Alphanumeric form. + */ + TELEPEN_AS_NUMERIC, } #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -333,4 +338,9 @@ pub enum EncodeHintValue { * exclusive. */ Code128Compact(bool), + + /** + * Translate the numeric values received by the Telepen reader into the Telepen Alphaumeric form; use {@link Boolean#TRUE}. + */ + TelepenAsNumeric(bool), } diff --git a/src/multi_format_reader.rs b/src/multi_format_reader.rs index 6b0f1d5..2d3e99e 100644 --- a/src/multi_format_reader.rs +++ b/src/multi_format_reader.rs @@ -165,7 +165,8 @@ impl MultiFormatReader { || self.possible_formats.contains(&BarcodeFormat::CODE_128) || self.possible_formats.contains(&BarcodeFormat::ITF) || self.possible_formats.contains(&BarcodeFormat::RSS_14) - || self.possible_formats.contains(&BarcodeFormat::RSS_EXPANDED); + || self.possible_formats.contains(&BarcodeFormat::RSS_EXPANDED) + || self.possible_formats.contains(&BarcodeFormat::TELEPEN); if one_d && !self.try_harder { if let Ok(res) = self.one_d_reader.decode_with_hints(image, &self.hints) { return Ok(res); diff --git a/src/multi_format_writer.rs b/src/multi_format_writer.rs index d0f164a..5536df6 100644 --- a/src/multi_format_writer.rs +++ b/src/multi_format_writer.rs @@ -22,7 +22,7 @@ use crate::{ datamatrix::DataMatrixWriter, oned::{ CodaBarWriter, Code128Writer, Code39Writer, Code93Writer, EAN13Writer, EAN8Writer, - ITFWriter, UPCAWriter, UPCEWriter, + ITFWriter, TelepenWriter, UPCAWriter, UPCEWriter, }, pdf417::PDF417Writer, qrcode::QRCodeWriter, @@ -70,6 +70,7 @@ impl Writer for MultiFormatWriter { BarcodeFormat::PDF_417 => Box::::default(), BarcodeFormat::CODABAR => Box::::default(), BarcodeFormat::DATA_MATRIX => Box::::default(), + BarcodeFormat::TELEPEN => Box::::default(), BarcodeFormat::AZTEC => Box::::default(), _ => { return Err(Exceptions::illegal_argument_with(format!( diff --git a/src/multi_use_multi_format_reader.rs b/src/multi_use_multi_format_reader.rs index f78e69e..19eec6d 100644 --- a/src/multi_use_multi_format_reader.rs +++ b/src/multi_use_multi_format_reader.rs @@ -176,7 +176,8 @@ impl MultiUseMultiFormatReader { || self.possible_formats.contains(&BarcodeFormat::CODE_128) || self.possible_formats.contains(&BarcodeFormat::ITF) || self.possible_formats.contains(&BarcodeFormat::RSS_14) - || self.possible_formats.contains(&BarcodeFormat::RSS_EXPANDED); + || self.possible_formats.contains(&BarcodeFormat::RSS_EXPANDED) + || self.possible_formats.contains(&BarcodeFormat::TELEPEN); if one_d && !self.try_harder { if let Ok(res) = self.one_d_reader.decode_with_hints(image, &self.hints) { return Ok(res); diff --git a/src/oned/mod.rs b/src/oned/mod.rs index e195fcf..d024ce7 100644 --- a/src/oned/mod.rs +++ b/src/oned/mod.rs @@ -24,6 +24,9 @@ pub use code_128_reader::*; mod itf_reader; pub use itf_reader::*; +mod telepen_reader; +pub use telepen_reader::*; + mod upc_ean_reader; pub use upc_ean_reader::*; @@ -77,6 +80,9 @@ pub use upc_a_writer::*; mod ean_13_writer; pub use ean_13_writer::*; +mod telepen_writer; +pub use telepen_writer::*; + mod upc_ean_writer; pub use upc_ean_writer::*; @@ -85,3 +91,5 @@ pub use ean_8_writer::*; mod upc_e_writer; pub use upc_e_writer::*; + +mod telepen_common; \ No newline at end of file diff --git a/src/oned/multi_format_one_d_reader.rs b/src/oned/multi_format_one_d_reader.rs index b65d6a6..8f2951b 100644 --- a/src/oned/multi_format_one_d_reader.rs +++ b/src/oned/multi_format_one_d_reader.rs @@ -23,6 +23,7 @@ use super::Code93Reader; use super::ITFReader; use super::MultiFormatUPCEANReader; use super::OneDReader; +use super::TelepenReader; use crate::common::Result; use crate::DecodeHintValue; use crate::Exceptions; @@ -104,6 +105,11 @@ impl OneDReader for MultiFormatOneDReader { return Ok(res); } } + if possible_formats.contains(&BarcodeFormat::TELEPEN) { + if let Ok(res) = TelepenReader::default().decode_row(row_number, row, hints) { + return Ok(res); + } + } } else { if let Ok(res) = MultiFormatUPCEANReader::new(internal_hints).decode_row(row_number, row, hints) @@ -133,6 +139,9 @@ impl OneDReader for MultiFormatOneDReader { if let Ok(res) = rss_expanded_reader.decode_row(row_number, row, hints) { return Ok(res); } + if let Ok(res) = TelepenReader::default().decode_row(row_number, row, hints) { + return Ok(res); + } } Err(Exceptions::NOT_FOUND) diff --git a/src/oned/telepen_common.rs b/src/oned/telepen_common.rs new file mode 100644 index 0000000..b487811 --- /dev/null +++ b/src/oned/telepen_common.rs @@ -0,0 +1,98 @@ +use crate::Exceptions; +use crate::common::Result; + +pub fn calculate_checksum(contents: &str) -> char { + let mut sum = 0; + + for c in contents.chars() { + sum += c as u32; + } + + let remainder = sum % 127; + let diff = 127 - remainder; + return if diff != 127 { + diff as u8 as char + } + else { + 0 as char + }; +} + +pub fn ascii_to_numeric(contents: &str) -> String { + let mut number = String::new(); + + for c in contents.chars().map(|x| x as u32) { + if c >= 27 { + number.push_str(&format!("{:0>2}", (c - 27))); + } + else { + number.push_str(&format!("{:0>2}", (c - 17))); + } + } + + return number; +} + +pub fn numeric_to_ascii(contents: &str) -> Result { + if contents.len() % 2 != 0 { + return Err(Exceptions::illegal_argument_with("Input must contain an even number of characters.")); + } + + let mut ascii = String::new(); + let mut i = 0; + + while i < contents.len() { + let first = contents.chars().nth(i).unwrap() as u8; + let second = contents.chars().nth(i + 1).unwrap() as u8; + + if second == 88 && first >= 48 && first <= 57 { + ascii.push((17 + first - 48) as char); + } + else if second >= 48 && second <= 57 && first >= 48 && first <= 57 { + ascii.push((27 + (first - 48) * 10 + (second - 48)) as char); + } + else { + return Err(Exceptions::illegal_argument_with(format!("Input contains an invalid character around position {}.", i.to_string()))); + } + + i += 2; + } + + return Ok(ascii); +} + +#[test] +fn telepen_checksum_test1() { + let contents = "Hello world!"; + let checksum = calculate_checksum(contents); + assert_eq!('\u{1a}', checksum); +} + +#[test] +fn telepen_checksum_test2() { + let contents = "ABC123456"; + let checksum = calculate_checksum(contents); + assert_eq!('\u{1}', checksum); +} + +#[test] +fn telepen_alpha_to_numeric_test() { + let mut ascii = "'=Siu"; + let mut result = ascii_to_numeric(ascii); + assert_eq!("1234567890", result); + + ascii = "& oe"; + result = ascii_to_numeric(ascii); + assert_eq!("11058474", result); +} + +#[test] +fn telepen_numeric_to_ascii_test() { + let mut numeric = "1234567890"; + let mut result = numeric_to_ascii(numeric).unwrap(); + assert_eq!("'=Siu", result); + + numeric = "11058474"; + result = numeric_to_ascii(numeric).unwrap(); + assert_eq!("& oe", result); +} \ No newline at end of file diff --git a/src/oned/telepen_reader.rs b/src/oned/telepen_reader.rs new file mode 100644 index 0000000..968aefd --- /dev/null +++ b/src/oned/telepen_reader.rs @@ -0,0 +1,436 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use rxing_one_d_proc_derive::OneDReader; + +use crate::common::{BitArray, Result}; +use crate::Exceptions; +use crate::RXingResult; +use crate::DecodeHintValue; +use crate::oned::telepen_common; +use crate::{point_f, BarcodeFormat}; +use bit_reverse::ParallelReverse; + +use super::OneDReader; + +/** + *

Decodes Telepen barcodes.

+ * + * @author Chris Wood + */ +#[derive(OneDReader)] +pub struct TelepenReader { + // Keep some instance variables to avoid reallocations + counters: Vec, + counterLength: usize, +} + +impl Default for TelepenReader { + fn default() -> Self { + Self { + counters: vec![0; 80], + counterLength: 0, + } + } +} + +impl OneDReader for TelepenReader { + fn decode_row( + &mut self, + rowNumber: u32, + row: &crate::common::BitArray, + hints: &crate::DecodingHintDictionary, + ) -> Result { + self.counters.fill(0); + self.setCounters(row, (row.get_size() as f32 * 0.001) as u32)?; + + let startOffset = self.findStartPattern()? as usize; + let end = self.findEndPattern(startOffset)? as usize; + + let theCounters = &self.counters; + let mut maxBar = 0; + let mut minBar = u32::MAX; + + // 0 position value will be whitespace prior to the barcode beginning + let mut j = startOffset; + + // Calculate a median bar / gap width by establishing the smallest and + // largest gaps. + while j <= end { + let currentCounter = theCounters[j]; + if currentCounter < minBar { + minBar = currentCounter; + } + if currentCounter > maxBar { + maxBar = currentCounter; + } + + j += 1; + } + + // Calculate median value as float. + let mut thresholdBar = (minBar + maxBar) as f64 / 2.0; + + // Lean very slightly toward thicker bars. + thresholdBar = thresholdBar - (thresholdBar / 10.0); + + // Start of the barcode is always a black bar. + let mut isBlack = true; + + let mut pattern: Vec = vec![0; self.counterLength]; + let mut patternLength: usize = 0; + + pattern.fill(0); + + j = startOffset; + + // Categorise each value into narrow or wide black or white. Each + // permutation is signified by an integer value. + // + // 0 = Narrow White + // 1 = Narrow Black + // 2 = Wide White + // 3 = Wide Black + // + // Wide elements are 3x the width of narrow, thus: + // + // Black narrow: B + // Black wide: BBB + // White narrow: . + // White wide: ... + + while j <= end { + let currentCounter = theCounters[j]; + if (currentCounter as f64) < thresholdBar { + if isBlack { + // Narrow black: B + pattern[patternLength] = 1; + } + else { + // Narrow white: . + pattern[patternLength] = 0; + } + + } + else { + if isBlack { + // Wide black: BBB + pattern[patternLength] = 3; + } + else { + // Wide white: ... + pattern[patternLength] = 2; + } + } + + patternLength += 1; + j += 1; + isBlack = !isBlack; + } + + let mut bits: BitArray = BitArray::new(); + let mut state = 0; + j = 0; + + // Convert narrow-wide sequence into bit array. + while j < patternLength - 1 { + + if pattern[j] == 3 && pattern[j + 1] == 2 { + // BBB... = 010 + bits.appendBit(false); + bits.appendBit(true); + bits.appendBit(false); + } + else if pattern[j] == 3 && pattern[j + 1] == 0 { + // BBB. = 00 + bits.appendBit(false); + bits.appendBit(false); + } + else if pattern[j] == 1 && pattern[j + 1] == 2 && state == 0 { + // B... = 01 + bits.appendBit(false); + bits.appendBit(true); + state = 1; + } + else if pattern[j] == 1 && pattern[j + 1] == 2 && state == 1 { + // B... = 10 + bits.appendBit(true); + bits.appendBit(false); + state = 0; + } + else if pattern[j] == 1 && pattern[j + 1] == 0 { + // B. = 1 + bits.appendBit(true); + } + + j += 2; + } + + let byteLength = bits.getSizeInBytes(); + + // Any Telepen barcode will be longer than two bytes. + if byteLength < 3 { + return Err(Exceptions::NOT_FOUND); + } + + let mut bytes: Vec = vec![0; byteLength]; + bits.toBytes(0, bytes.as_mut_slice(), 0, byteLength); + + j = 0; + + // Tweak our byte array to clean things up a little. + while j < byteLength { + // Telepen is little-endian, so need to swap the + // bits around for each byte to be correct. + bytes[j] = bytes[j].swap_bits(); + + // The first bit in the byte can always be disregarded + // as the highest ASCII decimal value is 127. It might be + // set because it is used as a parity bit during encoding + // (ensuring that there is an equal number of 1 bits in the + // byte). + if bytes[j] >= 128 { + bytes[j] -= 128; + } + + j += 1; + } + + // First character should be _ which is decimal 95. + if bytes[0] != 95 { + return Err(Exceptions::NOT_FOUND); + } + + // Last character should be z which is decimal 122. + if bytes[byteLength - 1] != 122 { + return Err(Exceptions::NOT_FOUND); + } + + // Content bytes + let contentBytes = bytes[1 .. byteLength - 2].to_vec(); + let mut contentString = String::from_utf8_lossy(&contentBytes).to_string(); + + // Penultimate byte is a block check character. + let check = bytes[byteLength - 2]; + + let checksum = telepen_common::calculate_checksum(&contentString); + + // Validate checksum + if check != checksum as u8 { + return Err(Exceptions::NOT_FOUND); + } + + if matches!( + hints.get(&DecodeHintType::TELEPEN_AS_NUMERIC), + Some(DecodeHintValue::TelepenAsNumeric(true)) + ) { + contentString = telepen_common::ascii_to_numeric(&contentString); + } + + let mut runningCount = 0; + runningCount += self.counters.iter().take(startOffset).sum::(); + let left: f32 = runningCount as f32; + + runningCount += self + .counters + .iter() + .skip(startOffset) + .take(self.counterLength - startOffset - end) + .sum::(); + + let right: f32 = runningCount as f32; + + let mut result = RXingResult::new( + &contentString, + bytes, + vec![ + point_f(left, rowNumber as f32), + point_f(right, rowNumber as f32), + ], + BarcodeFormat::TELEPEN, + ); + + result.putMetadata( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier("]B0".to_owned()), + ); + + Ok(result) + } +} +impl TelepenReader { + + pub fn new() -> Self { + Self { + counters: vec![0; 80], //Vec::with_capacity(80), + counterLength: 0, + } + } + + /** + * Records the size of all runs of white and black pixels, starting with white. + * This is just like recordPattern, except it records all the counters, and + * uses our builtin "counters" member for storage. + * @param row row to count from + */ + fn setCounters(&mut self, row: &BitArray, minToleratedWidth: u32) -> Result<()> { + self.counterLength = 0; + + let mut i = 1; + let end = row.get_size(); + let mut currentColor = false; + let mut count = 1; + + // Move to first white pixel + while i < end && row.get(i) { + i += 1; + } + + while i < end { + if row.get(i) == currentColor { + count += 1; + } + else { + if count >= minToleratedWidth || self.counterLength == 0 { + self.counterAppend(count); + } + else { + // Noise from previous bar. Treat it as the + // previous colour. + self.counters[self.counterLength - 1] += count; + } + + count = 1; + currentColor = !currentColor; + } + + i += 1; + } + + if count >= minToleratedWidth { + self.counterAppend(count); + } + else { + // Noise from previous bar. Treat it as the + // previous colour. + self.counters[self.counterLength - 1] += count; + } + + Ok(()) + } + + fn counterAppend(&mut self, e: u32) { + self.counters[self.counterLength] = e; + self.counterLength += 1; + if self.counterLength >= self.counters.len() { + let mut temp = vec![0; self.counterLength * 2]; + temp[0..self.counterLength].clone_from_slice(&self.counters[..]); + self.counters = temp; + } + } + + fn findStartPattern(&mut self) -> Result { + if self.counterLength <= 20 { + return Err(Exceptions::NOT_FOUND); + } + + let mut i = 0; + while i < self.counterLength - 20 { + // Read next 20 in sequence. All 20 must be either between 28% and 38% + // of biggest, or between 90% to 100% of biggest. + let mut j = 0; + let mut maxBar: f32 = 0.0; + let mut minBar: f32 = f32::MAX; + + while i + j < self.counterLength && j < 20 { + if (self.counters[i + j] as f32) > maxBar { + maxBar = self.counters[i + j] as f32; + } + + if (self.counters[ i + j] as f32) < minBar { + minBar = self.counters[ i + j] as f32; + } + + j += 1; + } + + j = 0; + + let median = minBar as f32 + maxBar as f32 / 2.0; + let mut passed = true; + + // First 10 items must be: + // N-N-N-N-N-N-N-N-N-N-W + while i + j < self.counterLength && j < 11 { + if j < 10 { + // Narrow + if (self.counters[i + j] as f32) > median { + passed = false; + break; + } + else if j == 10 { + // Wide + if (self.counters[i + j] as f32) < median { + passed = false; + break; + } + } + } + + j += 1; + } + + if !passed { + i += 1; + continue; + } + + return Ok(i as u32); + } + Err(Exceptions::NOT_FOUND) + } + + fn findEndPattern(&mut self, start: usize) -> Result { + const TOLERANCE: f32 = 0.5; + + let mut i = start; + while i < self.counterLength { + // Read next 20 in sequence. All 20 must be either between 28% and 38% + // of biggest, or between 90% to 100% of biggest. + let mut j = 0; + let mut maxBar: f32 = 0.0; + + while i + j < self.counterLength && j < 20 { + if (self.counters[i + j] as f32) > maxBar { + maxBar = self.counters[i + j] as f32; + } + + j += 1; + } + + i = start + 20; + + while i < self.counterLength { + if (self.counters[i] as f32) > (maxBar as f32 * (1.0 + TOLERANCE)) { + return Ok((i - 1) as u32); + } + + i += 1; + } + } + return Ok((self.counterLength - 1) as u32); + } +} diff --git a/src/oned/telepen_writer.rs b/src/oned/telepen_writer.rs new file mode 100644 index 0000000..08346e5 --- /dev/null +++ b/src/oned/telepen_writer.rs @@ -0,0 +1,276 @@ +/* + * Copyright 2011 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use rxing_one_d_proc_derive::OneDWriter; +use regex::Regex; +use crate::common::Result; +use crate::BarcodeFormat; +use crate::oned::telepen_common; + +use super::OneDimensionalCodeWriter; + +/** + * This class renders Telepen as {@code boolean[]}. + * + * @author Chris Wood + */ +#[derive(OneDWriter, Default)] +pub struct TelepenWriter; + +impl OneDimensionalCodeWriter for TelepenWriter { + fn encode_oned(&self, contents: &str) -> Result> { + self.encode_oned_with_hints(contents, &HashMap::new()) + } + + fn encode_oned_with_hints(&self, contents: &str, + hints: &crate::EncodingHintDictionary) -> Result> { + + let mut decodedContents = contents.to_string(); + + if matches!( + hints.get(&EncodeHintType::TELEPEN_AS_NUMERIC), + Some(EncodeHintValue::TelepenAsNumeric(true)) + ) { + decodedContents = telepen_common::numeric_to_ascii(&contents)?; + } + + // Calculate the checksum character + let checksum = telepen_common::calculate_checksum(&decodedContents); + + // Build binary string + let mut binary = String::new(); + + // Opening character is always _ + binary = self.add_to_binary('_', binary); + + // Content + for c in decodedContents.chars() { + if c as u32 > 127 { + return Err(Exceptions::illegal_argument_with(format!( + "Telepen only supports ASCII characters." + ))); + } + + binary = self.add_to_binary(c, binary) + } + + // Checksum + binary = self.add_to_binary(checksum, binary); + + // Closing character is always z. + binary = self.add_to_binary('z', binary); + + let re = Regex::new(r"^01|10$|01*0|00|1").unwrap(); + let matches: Vec<&str> = re.find_iter(&binary).map(|m| m.as_str()).collect(); + + let mut result = vec![false; 2000]; + let mut resultPosition = 0; + + for b in matches { + match b { + "010" => { + // BBB... + result[resultPosition] = true; + result[resultPosition + 1] = true; + result[resultPosition + 2] = true; + result[resultPosition + 3] = false; + result[resultPosition + 4] = false; + result[resultPosition + 5] = false; + + resultPosition += 6; + }, + "00" => { + // BBB. + result[resultPosition] = true; + result[resultPosition + 1] = true; + result[resultPosition + 2] = true; + result[resultPosition + 3] = false; + + resultPosition += 4; + }, + "1" => { + // B. + result[resultPosition] = true; + result[resultPosition + 1] = false; + + resultPosition += 2; + }, + "01" => { + // B... + result[resultPosition] = true; + result[resultPosition + 1] = false; + result[resultPosition + 2] = false; + result[resultPosition + 3] = false; + + resultPosition += 4; + }, + "10" => { + // B... + result[resultPosition] = true; + result[resultPosition + 1] = false; + result[resultPosition + 2] = false; + result[resultPosition + 3] = false; + + resultPosition += 4; + }, + "0" => { + return Err(Exceptions::illegal_argument_with(format!( + "Invalid bit combination!" + ))); + }, + _ => { + // B... (B.)* B... + result[resultPosition] = true; + result[resultPosition + 1] = false; + result[resultPosition + 2] = false; + result[resultPosition + 3] = false; + + resultPosition += 4; + + for _j in 2 .. b.len() - 2 { + result[resultPosition] = true; + result[resultPosition + 1] = false; + + resultPosition += 2; + } + + result[resultPosition] = true; + result[resultPosition + 1] = false; + result[resultPosition + 2] = false; + result[resultPosition + 3] = false; + + resultPosition += 4; + } + } + } + + Ok(result[0 .. resultPosition - 1].to_vec()) + } + + fn getSupportedWriteFormats(&self) -> Option> { + Some(vec![BarcodeFormat::TELEPEN]) + } +} + +impl TelepenWriter { + fn get_bits(&self, byte: u8) -> Vec { + let mut bits = vec![false; 8]; + let mut oneCount = 0; + + for i in 0..8 { + let mask = 1 << i; + bits[i] = (mask & byte) > 0; + + if bits[i] { + oneCount += 1; + } + } + + // Set parity bit - there must be an even number + // of 1s in the 8 bits. + bits[7] = oneCount % 2 != 0; + + return bits; + } + + fn add_to_binary(&self, c: char, mut binary: String) -> String { + let byte = c as u8; + let bits = self.get_bits(byte); + + for i in 0..8 { + binary.push(if bits[i] { + '1' + } + else { + '0' + }); + } + + return binary; + } +} + +/** + * @author Chris Wood + */ +#[cfg(test)] +mod TelepenWriterTestCase { + use std::collections::HashMap; + + use crate::{ + common::{bit_matrix_test_case, BitMatrix}, + BarcodeFormat, Writer, EncodeHintType, EncodeHintValue, + }; + + use super::TelepenWriter; + + #[test] + #[should_panic] + fn testAsciiOnly() { + encode("АБВГДЕЁЖ"); + } + + #[test] + fn testEncode() { + doTest( + "Hello world!", + concat!( + "00000", + "10101010101110001110111000111000101110001000100011101010100010001110101010001000101010101000100011101110111000101010101000101000101010101000100011100010001010001110101010001000111010111010101010111011101011101110001010111010111000101010101", + "00000", + ), + false + ); + + doTest( + "11058474", + concat!( + "00000", + "101010101011100010001000111000101110111011100010101010101000100010111000100010001010111010001000111000101010101", + "00000", + ), + true + ); + } + + fn doTest(input: &str, expected: &str, numeric: bool) { + let result: BitMatrix; + + if numeric { + result = encode_with_hints(input); + } + else { + result = encode(input); + }; + + assert_eq!(expected, bit_matrix_test_case::matrix_to_string(&result)); + } + + fn encode(input: &str) -> BitMatrix { + TelepenWriter + .encode(input, &BarcodeFormat::TELEPEN, 0, 0) + .expect("must encode") + } + + fn encode_with_hints(input: &str) -> BitMatrix { + let mut hints = HashMap::new(); + hints.insert(EncodeHintType::TELEPEN_AS_NUMERIC, EncodeHintValue::TelepenAsNumeric(true)); + + TelepenWriter + .encode_with_hints(input, &BarcodeFormat::TELEPEN, 0, 0, &hints) + .expect("must encode") + } +} diff --git a/test_resources/blackbox/telepen-1/01.metadata.txt b/test_resources/blackbox/telepen-1/01.metadata.txt new file mode 100644 index 0000000..c4d7058 --- /dev/null +++ b/test_resources/blackbox/telepen-1/01.metadata.txt @@ -0,0 +1 @@ +SYMBOLOGY_IDENTIFIER=]B0 \ No newline at end of file diff --git a/test_resources/blackbox/telepen-1/01.png b/test_resources/blackbox/telepen-1/01.png new file mode 100644 index 0000000..a330a74 Binary files /dev/null and b/test_resources/blackbox/telepen-1/01.png differ diff --git a/test_resources/blackbox/telepen-1/01.txt b/test_resources/blackbox/telepen-1/01.txt new file mode 100644 index 0000000..8318c86 --- /dev/null +++ b/test_resources/blackbox/telepen-1/01.txt @@ -0,0 +1 @@ +Test \ No newline at end of file diff --git a/test_resources/blackbox/telepen-1/02.png b/test_resources/blackbox/telepen-1/02.png new file mode 100644 index 0000000..8cf5b77 Binary files /dev/null and b/test_resources/blackbox/telepen-1/02.png differ diff --git a/test_resources/blackbox/telepen-1/02.txt b/test_resources/blackbox/telepen-1/02.txt new file mode 100644 index 0000000..121eab0 --- /dev/null +++ b/test_resources/blackbox/telepen-1/02.txt @@ -0,0 +1 @@ +Wikipedia \ No newline at end of file diff --git a/test_resources/blackbox/telepen-1/03.png b/test_resources/blackbox/telepen-1/03.png new file mode 100644 index 0000000..cb85ec7 Binary files /dev/null and b/test_resources/blackbox/telepen-1/03.png differ diff --git a/test_resources/blackbox/telepen-1/03.txt b/test_resources/blackbox/telepen-1/03.txt new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/test_resources/blackbox/telepen-1/03.txt @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/test_resources/blackbox/telepen-1/04.png b/test_resources/blackbox/telepen-1/04.png new file mode 100644 index 0000000..5da62ef Binary files /dev/null and b/test_resources/blackbox/telepen-1/04.png differ diff --git a/test_resources/blackbox/telepen-1/04.txt b/test_resources/blackbox/telepen-1/04.txt new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/test_resources/blackbox/telepen-1/04.txt @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/test_resources/blackbox/telepen-1/05.png b/test_resources/blackbox/telepen-1/05.png new file mode 100644 index 0000000..c13033b Binary files /dev/null and b/test_resources/blackbox/telepen-1/05.png differ diff --git a/test_resources/blackbox/telepen-1/05.txt b/test_resources/blackbox/telepen-1/05.txt new file mode 100644 index 0000000..6769dd6 --- /dev/null +++ b/test_resources/blackbox/telepen-1/05.txt @@ -0,0 +1 @@ +Hello world! \ No newline at end of file diff --git a/test_resources/blackbox/telepen-2/01.png b/test_resources/blackbox/telepen-2/01.png new file mode 100644 index 0000000..4204cac Binary files /dev/null and b/test_resources/blackbox/telepen-2/01.png differ diff --git a/test_resources/blackbox/telepen-2/01.txt b/test_resources/blackbox/telepen-2/01.txt new file mode 100644 index 0000000..6a537b5 --- /dev/null +++ b/test_resources/blackbox/telepen-2/01.txt @@ -0,0 +1 @@ +1234567890 \ No newline at end of file diff --git a/test_resources/blackbox/telepen-2/02.png b/test_resources/blackbox/telepen-2/02.png new file mode 100644 index 0000000..7cadeba Binary files /dev/null and b/test_resources/blackbox/telepen-2/02.png differ diff --git a/test_resources/blackbox/telepen-2/02.txt b/test_resources/blackbox/telepen-2/02.txt new file mode 100644 index 0000000..8070467 --- /dev/null +++ b/test_resources/blackbox/telepen-2/02.txt @@ -0,0 +1 @@ +11058474 \ No newline at end of file diff --git a/test_resources/blackbox/telepen-2/03.png b/test_resources/blackbox/telepen-2/03.png new file mode 100644 index 0000000..43c12d3 Binary files /dev/null and b/test_resources/blackbox/telepen-2/03.png differ diff --git a/test_resources/blackbox/telepen-2/03.txt b/test_resources/blackbox/telepen-2/03.txt new file mode 100644 index 0000000..592f76f --- /dev/null +++ b/test_resources/blackbox/telepen-2/03.txt @@ -0,0 +1 @@ +10398404 \ No newline at end of file diff --git a/test_resources/blackbox/telepen-2/04.png b/test_resources/blackbox/telepen-2/04.png new file mode 100644 index 0000000..1d1c385 Binary files /dev/null and b/test_resources/blackbox/telepen-2/04.png differ diff --git a/test_resources/blackbox/telepen-2/04.txt b/test_resources/blackbox/telepen-2/04.txt new file mode 100644 index 0000000..3a69469 --- /dev/null +++ b/test_resources/blackbox/telepen-2/04.txt @@ -0,0 +1 @@ +10483931 \ No newline at end of file diff --git a/test_resources/blackbox/telepen-2/05.png b/test_resources/blackbox/telepen-2/05.png new file mode 100644 index 0000000..770815e Binary files /dev/null and b/test_resources/blackbox/telepen-2/05.png differ diff --git a/test_resources/blackbox/telepen-2/05.txt b/test_resources/blackbox/telepen-2/05.txt new file mode 100644 index 0000000..c03f14f --- /dev/null +++ b/test_resources/blackbox/telepen-2/05.txt @@ -0,0 +1 @@ +11193263 \ No newline at end of file diff --git a/test_resources/blackbox/telepen-2/06.png b/test_resources/blackbox/telepen-2/06.png new file mode 100644 index 0000000..508a1e3 Binary files /dev/null and b/test_resources/blackbox/telepen-2/06.png differ diff --git a/test_resources/blackbox/telepen-2/06.txt b/test_resources/blackbox/telepen-2/06.txt new file mode 100644 index 0000000..db65f54 --- /dev/null +++ b/test_resources/blackbox/telepen-2/06.txt @@ -0,0 +1 @@ +11264314 \ No newline at end of file diff --git a/test_resources/blackbox/telepen-2/07.png b/test_resources/blackbox/telepen-2/07.png new file mode 100644 index 0000000..58a3b17 Binary files /dev/null and b/test_resources/blackbox/telepen-2/07.png differ diff --git a/test_resources/blackbox/telepen-2/07.txt b/test_resources/blackbox/telepen-2/07.txt new file mode 100644 index 0000000..e55cdad --- /dev/null +++ b/test_resources/blackbox/telepen-2/07.txt @@ -0,0 +1 @@ +11188986 \ No newline at end of file diff --git a/tests/telepen_blackbox_1_test_case.rs b/tests/telepen_blackbox_1_test_case.rs new file mode 100644 index 0000000..4d7b7f4 --- /dev/null +++ b/tests/telepen_blackbox_1_test_case.rs @@ -0,0 +1,53 @@ +/* + * Copyright 2008 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use rxing::{oned::TelepenReader, BarcodeFormat}; + +mod common; + +/** + * @author Chris Wood + */ +#[test] +fn telepen_alpha_test_case() { + const NUMTESTS: u32 = 5; + + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/telepen-1", + TelepenReader::new(), + BarcodeFormat::TELEPEN, + ); + // super("src/test/resources/blackbox/Telepen-1", new MultiFormatReader(), BarcodeFormat.Telepen); + tester.add_test(NUMTESTS, 2, 0.0); + tester.add_test(NUMTESTS, 2, 180.0); + + tester.test_black_box(); +} + +#[test] +fn telepen_numeric_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/telepen-2", + TelepenReader::new(), + BarcodeFormat::TELEPEN, + ); + + tester.add_test_complex(7, 1, 0, 0, 0.0); + tester.add_hint(rxing::DecodeHintType::TELEPEN_AS_NUMERIC, rxing::DecodeHintValue::TelepenAsNumeric(true)); + tester.ignore_pure = true; + + tester.test_black_box(); +}