diff --git a/one-d-reader-derive/src/lib.rs b/one-d-reader-derive/src/lib.rs index 153e8a4..97c5a61 100644 --- a/one-d-reader-derive/src/lib.rs +++ b/one-d-reader-derive/src/lib.rs @@ -104,4 +104,78 @@ fn impl_ean_reader_macro(ast: &syn::DeriveInput) -> TokenStream { } }; gen.into() +} + +#[proc_macro_derive(OneDWriter)] +pub fn one_d_writer_derive(input: TokenStream) -> TokenStream { + // Construct a representation of Rust code as a syntax tree + // that we can manipulate + let ast = syn::parse(input).unwrap(); + + // Build the trait implementation + impl_one_d_writer_macro(&ast) +} + +fn impl_one_d_writer_macro(ast: &syn::DeriveInput) -> TokenStream { + let name = &ast.ident; + let gen = quote! { + use crate::{ + EncodeHintType, EncodeHintValue, Exceptions, Writer, + }; + use std::collections::HashMap; + impl Writer for #name { + fn encode( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + ) -> Result { + self.encode_with_hints(contents, format, width, height, &HashMap::new()) + } + + fn encode_with_hints( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + hints: &crate::EncodingHintDictionary, + ) -> Result { + if contents.is_empty() { + return Err(Exceptions::IllegalArgumentException( + "Found empty contents".to_owned(), + )); + } + + if width < 0 || height < 0 { + return Err(Exceptions::IllegalArgumentException(format!( + "Negative size is not allowed. Input: {}x{}", + width, height + ))); + } + if let Some(supportedFormats) = self.getSupportedWriteFormats() { + if !supportedFormats.contains(format) { + return Err(Exceptions::IllegalArgumentException(format!( + "Can only encode {:?}, but got {:?}", + supportedFormats, format + ))); + } + } + + let mut sidesMargin = self.getDefaultMargin(); + if let Some(EncodeHintValue::Margin(margin)) = hints.get(&EncodeHintType::MARGIN) { + sidesMargin = margin.parse::().unwrap(); + } + // if hints.contains_key(&EncodeHintType::MARGIN) { + // sidesMargin = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString()); + // } + + let code = self.encode_oned_with_hints(contents, hints)?; + + Self::renderRXingResult(&code, width, height, sidesMargin) + } + } + }; + gen.into() } \ No newline at end of file diff --git a/src/common/BitMatrixTestCase.rs b/src/common/BitMatrixTestCase.rs index 4408b56..a61d21b 100644 --- a/src/common/BitMatrixTestCase.rs +++ b/src/common/BitMatrixTestCase.rs @@ -339,7 +339,7 @@ fn test_xor_case() { // } } -fn matrix_to_string(result: &BitMatrix) -> String { +pub fn matrix_to_string(result: &BitMatrix) -> String { assert_eq!(1, result.getHeight()); let mut builder = String::with_capacity(result.getWidth().try_into().unwrap()); for i in 0..result.getWidth() { diff --git a/src/common/mod.rs b/src/common/mod.rs index b4197a9..2ea7bb2 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -10,7 +10,7 @@ mod StringUtilsTestCase; mod BitArrayTestCase; #[cfg(test)] -mod BitMatrixTestCase; +pub(crate) mod BitMatrixTestCase; #[cfg(test)] mod BitSourceTestCase; diff --git a/src/oned/CodaBarWriter.java b/src/oned/CodaBarWriter.java deleted file mode 100644 index 523338f..0000000 --- a/src/oned/CodaBarWriter.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; - -import java.util.Collection; -import java.util.Collections; - -/** - * This class renders CodaBar as {@code boolean[]}. - * - * @author dsbnatut@gmail.com (Kazuki Nishiura) - */ -public final class CodaBarWriter extends OneDimensionalCodeWriter { - - private static final char[] START_END_CHARS = {'A', 'B', 'C', 'D'}; - private static final char[] ALT_START_END_CHARS = {'T', 'N', '*', 'E'}; - private static final char[] CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED = {'/', ':', '+', '.'}; - private static final char DEFAULT_GUARD = START_END_CHARS[0]; - - @Override - protected Collection getSupportedWriteFormats() { - return Collections.singleton(BarcodeFormat.CODABAR); - } - - @Override - public boolean[] encode(String contents) { - - if (contents.length() < 2) { - // Can't have a start/end guard, so tentatively add default guards - contents = DEFAULT_GUARD + contents + DEFAULT_GUARD; - } else { - // Verify input and calculate decoded length. - char firstChar = Character.toUpperCase(contents.charAt(0)); - char lastChar = Character.toUpperCase(contents.charAt(contents.length() - 1)); - boolean startsNormal = CodaBarReader.arrayContains(START_END_CHARS, firstChar); - boolean endsNormal = CodaBarReader.arrayContains(START_END_CHARS, lastChar); - boolean startsAlt = CodaBarReader.arrayContains(ALT_START_END_CHARS, firstChar); - boolean endsAlt = CodaBarReader.arrayContains(ALT_START_END_CHARS, lastChar); - if (startsNormal) { - if (!endsNormal) { - throw new IllegalArgumentException("Invalid start/end guards: " + contents); - } - // else already has valid start/end - } else if (startsAlt) { - if (!endsAlt) { - throw new IllegalArgumentException("Invalid start/end guards: " + contents); - } - // else already has valid start/end - } else { - // Doesn't start with a guard - if (endsNormal || endsAlt) { - throw new IllegalArgumentException("Invalid start/end guards: " + contents); - } - // else doesn't end with guard either, so add a default - contents = DEFAULT_GUARD + contents + DEFAULT_GUARD; - } - } - - // The start character and the end character are decoded to 10 length each. - int resultLength = 20; - for (int i = 1; i < contents.length() - 1; i++) { - if (Character.isDigit(contents.charAt(i)) || contents.charAt(i) == '-' || contents.charAt(i) == '$') { - resultLength += 9; - } else if (CodaBarReader.arrayContains(CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED, contents.charAt(i))) { - resultLength += 10; - } else { - throw new IllegalArgumentException("Cannot encode : '" + contents.charAt(i) + '\''); - } - } - // A blank is placed between each character. - resultLength += contents.length() - 1; - - boolean[] result = new boolean[resultLength]; - int position = 0; - for (int index = 0; index < contents.length(); index++) { - char c = Character.toUpperCase(contents.charAt(index)); - if (index == 0 || index == contents.length() - 1) { - // The start/end chars are not in the CodaBarReader.ALPHABET. - switch (c) { - case 'T': - c = 'A'; - break; - case 'N': - c = 'B'; - break; - case '*': - c = 'C'; - break; - case 'E': - c = 'D'; - break; - } - } - int code = 0; - for (int i = 0; i < CodaBarReader.ALPHABET.length; i++) { - // Found any, because I checked above. - if (c == CodaBarReader.ALPHABET[i]) { - code = CodaBarReader.CHARACTER_ENCODINGS[i]; - break; - } - } - boolean color = true; - int counter = 0; - int bit = 0; - while (bit < 7) { // A character consists of 7 digit. - result[position] = color; - position++; - if (((code >> (6 - bit)) & 1) == 0 || counter == 1) { - color = !color; // Flip the color. - bit++; - counter = 0; - } else { - counter++; - } - } - if (index < contents.length() - 1) { - result[position] = false; - position++; - } - } - return result; - } -} - diff --git a/src/oned/CodaBarWriterTestCase.java b/src/oned/CodaBarWriterTestCase.java deleted file mode 100644 index 37e3049..0000000 --- a/src/oned/CodaBarWriterTestCase.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.common.BitMatrixTestCase; -import org.junit.Assert; -import org.junit.Test; - -/** - * @author dsbnatut@gmail.com (Kazuki Nishiura) - * @author Sean Owen - */ -public final class CodaBarWriterTestCase extends Assert { - - @Test - public void testEncode() { - doTest("B515-3/B", - "00000" + - "1001001011" + "0110101001" + "0101011001" + "0110101001" + "0101001101" + - "0110010101" + "01101101011" + "01001001011" + - "00000"); - } - - @Test - public void testEncode2() { - doTest("T123T", - "00000" + - "1011001001" + "0101011001" + "0101001011" + "0110010101" + "01011001001" + - "00000"); - } - - @Test - public void testAltStartEnd() { - assertEquals(encode("T123456789-$T"), encode("A123456789-$A")); - } - - private static void doTest(String input, CharSequence expected) { - BitMatrix result = encode(input); - assertEquals(expected, BitMatrixTestCase.matrixToString(result)); - } - - private static BitMatrix encode(String input) { - return new CodaBarWriter().encode(input, BarcodeFormat.CODABAR, 0, 0); - } - -} diff --git a/src/oned/OneDimensionalCodeWriter.java b/src/oned/OneDimensionalCodeWriter.java deleted file mode 100644 index 97759f9..0000000 --- a/src/oned/OneDimensionalCodeWriter.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.Writer; -import com.google.zxing.common.BitMatrix; - -import java.util.Collection; -import java.util.Map; -import java.util.regex.Pattern; - -/** - *

Encapsulates functionality and implementation that is common to one-dimensional barcodes.

- * - * @author dsbnatut@gmail.com (Kazuki Nishiura) - */ -public abstract class OneDimensionalCodeWriter implements Writer { - private static final Pattern NUMERIC = Pattern.compile("[0-9]+"); - - /** - * Encode the contents to boolean array expression of one-dimensional barcode. - * Start code and end code should be included in result, and side margins should not be included. - * - * @param contents barcode contents to encode - * @return a {@code boolean[]} of horizontal pixels (false = white, true = black) - */ - public abstract boolean[] encode(String contents); - - /** - * Can be overwritten if the encode requires to read the hints map. Otherwise it defaults to {@code encode}. - * @param contents barcode contents to encode - * @param hints encoding hints - * @return a {@code boolean[]} of horizontal pixels (false = white, true = black) - */ - protected boolean[] encode(String contents, Map hints) { - return encode(contents); - } - - @Override - public final BitMatrix encode(String contents, BarcodeFormat format, int width, int height) { - return encode(contents, format, width, height, null); - } - - /** - * Encode the contents following specified format. - * {@code width} and {@code height} are required size. This method may return bigger size - * {@code BitMatrix} when specified size is too small. The user can set both {@code width} and - * {@code height} to zero to get minimum size barcode. If negative value is set to {@code width} - * or {@code height}, {@code IllegalArgumentException} is thrown. - */ - @Override - public BitMatrix encode(String contents, - BarcodeFormat format, - int width, - int height, - Map hints) { - if (contents.isEmpty()) { - throw new IllegalArgumentException("Found empty contents"); - } - - if (width < 0 || height < 0) { - throw new IllegalArgumentException("Negative size is not allowed. Input: " - + width + 'x' + height); - } - Collection supportedFormats = getSupportedWriteFormats(); - if (supportedFormats != null && !supportedFormats.contains(format)) { - throw new IllegalArgumentException("Can only encode " + supportedFormats + - ", but got " + format); - } - - int sidesMargin = getDefaultMargin(); - if (hints != null && hints.containsKey(EncodeHintType.MARGIN)) { - sidesMargin = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString()); - } - - boolean[] code = encode(contents, hints); - return renderRXingResult(code, width, height, sidesMargin); - } - - protected Collection getSupportedWriteFormats() { - return null; - } - - /** - * @return a byte array of horizontal pixels (0 = white, 1 = black) - */ - private static BitMatrix renderRXingResult(boolean[] code, int width, int height, int sidesMargin) { - int inputWidth = code.length; - // Add quiet zone on both sides. - int fullWidth = inputWidth + sidesMargin; - int outputWidth = Math.max(width, fullWidth); - int outputHeight = Math.max(1, height); - - int multiple = outputWidth / fullWidth; - int leftPadding = (outputWidth - (inputWidth * multiple)) / 2; - - BitMatrix output = new BitMatrix(outputWidth, outputHeight); - for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { - if (code[inputX]) { - output.setRegion(outputX, 0, multiple, outputHeight); - } - } - return output; - } - - /** - * @param contents string to check for numeric characters - * @throws IllegalArgumentException if input contains characters other than digits 0-9. - */ - protected static void checkNumeric(String contents) { - if (!NUMERIC.matcher(contents).matches()) { - throw new IllegalArgumentException("Input should only contain digits 0-9"); - } - } - - /** - * @param target encode black/white pattern into this array - * @param pos position to start encoding at in {@code target} - * @param pattern lengths of black/white runs to encode - * @param startColor starting color - false for white, true for black - * @return the number of elements added to target. - */ - protected static int appendPattern(boolean[] target, int pos, int[] pattern, boolean startColor) { - boolean color = startColor; - int numAdded = 0; - for (int len : pattern) { - for (int j = 0; j < len; j++) { - target[pos++] = color; - } - numAdded += len; - color = !color; // flip color after each segment - } - return numAdded; - } - - public int getDefaultMargin() { - // CodaBar spec requires a side margin to be more than ten times wider than narrow space. - // This seems like a decent idea for a default for all formats. - return 10; - } -} - diff --git a/src/oned/coda_bar_reader.rs b/src/oned/coda_bar_reader.rs index eafdfff..18096e1 100644 --- a/src/oned/coda_bar_reader.rs +++ b/src/oned/coda_bar_reader.rs @@ -174,11 +174,11 @@ impl CodaBarReader { // These values are critical for determining how permissive the decoding // will be. All stripe sizes must be within the window these define, as // compared to the average stripe size. - const MAX_ACCEPTABLE: f32 = 2.0; - const PADDING: f32 = 1.5; + pub const MAX_ACCEPTABLE: f32 = 2.0; + pub const PADDING: f32 = 1.5; // const ALPHABET_STRING : &str= "0123456789-$:/.+ABCD"; - const ALPHABET: [char; 20] = [ + pub const ALPHABET: [char; 20] = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '$', ':', '/', '.', '+', 'A', 'B', 'C', 'D', ]; @@ -187,7 +187,7 @@ impl CodaBarReader { * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow. */ - const CHARACTER_ENCODINGS: [u32; 20] = [ + pub const CHARACTER_ENCODINGS: [u32; 20] = [ 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD ]; @@ -195,10 +195,10 @@ impl CodaBarReader { // minimal number of characters that should be present (including start and stop characters) // under normal circumstances this should be set to 3, but can be set higher // as a last-ditch attempt to reduce false positives. - const MIN_CHARACTER_LENGTH: u32 = 3; + pub const MIN_CHARACTER_LENGTH: u32 = 3; // official start and end patterns - const STARTEND_ENCODING: [char; 4] = ['A', 'B', 'C', 'D']; + pub const STARTEND_ENCODING: [char; 4] = ['A', 'B', 'C', 'D']; // some Codabar generator allow the Codabar string to be closed by every // character. This will cause lots of false positives! diff --git a/src/oned/coda_bar_writer.rs b/src/oned/coda_bar_writer.rs new file mode 100644 index 0000000..9c51d1b --- /dev/null +++ b/src/oned/coda_bar_writer.rs @@ -0,0 +1,230 @@ +/* + * 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 one_d_reader_derive::OneDWriter; + +use crate::BarcodeFormat; + +use super::{CodaBarReader, OneDimensionalCodeWriter}; + +const START_END_CHARS: [char; 4] = ['A', 'B', 'C', 'D']; +const ALT_START_END_CHARS: [char; 4] = ['T', 'N', '*', 'E']; +const CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED: [char; 4] = ['/', ':', '+', '.']; +const DEFAULT_GUARD: char = START_END_CHARS[0]; + +/** + * This class renders CodaBar as {@code boolean[]}. + * + * @author dsbnatut@gmail.com (Kazuki Nishiura) + */ +#[derive(OneDWriter)] +pub struct CodaBarWriter; + +impl OneDimensionalCodeWriter for CodaBarWriter { + fn encode_oned(&self, contents: &str) -> Result, crate::Exceptions> { + let contents = if contents.chars().count() < 2 { + // Can't have a start/end guard, so tentatively add default guards + format!("{}{}{}", DEFAULT_GUARD, contents, DEFAULT_GUARD) + } else { + // Verify input and calculate decoded length. + let firstChar = contents.chars().nth(0).unwrap().to_ascii_uppercase(); + let lastChar = contents + .chars() + .nth(contents.chars().count() - 1) + .unwrap() + .to_ascii_uppercase(); + let startsNormal = CodaBarReader::arrayContains(&START_END_CHARS, firstChar); + let endsNormal = CodaBarReader::arrayContains(&START_END_CHARS, lastChar); + let startsAlt = CodaBarReader::arrayContains(&ALT_START_END_CHARS, firstChar); + let endsAlt = CodaBarReader::arrayContains(&ALT_START_END_CHARS, lastChar); + if startsNormal { + if !endsNormal { + return Err(Exceptions::IllegalArgumentException(format!( + "Invalid start/end guards: {}", + contents + ))); + } + // else already has valid start/end + contents.to_owned() + } else if startsAlt { + if !endsAlt { + return Err(Exceptions::IllegalArgumentException(format!( + "Invalid start/end guards: {}", + contents + ))); + } + // else already has valid start/end + contents.to_owned() + } else { + // Doesn't start with a guard + if endsNormal || endsAlt { + return Err(Exceptions::IllegalArgumentException(format!( + "Invalid start/end guards: {}", + contents + ))); + } + // else doesn't end with guard either, so add a default + format!("{}{}{}", DEFAULT_GUARD, contents, DEFAULT_GUARD) + } + }; + + // The start character and the end character are decoded to 10 length each. + let mut resultLength = 20; + //for i in 1..contents.chars().count() { + for ch in contents[1..contents.chars().count() - 1].chars() { + // for (int i = 1; i < contents.length() - 1; i++) { + if ch.is_digit(10) || ch == '-' || ch == '$' { + resultLength += 9; + } else if CodaBarReader::arrayContains( + &CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED, + ch, + ) { + resultLength += 10; + } else { + return Err(Exceptions::IllegalArgumentException(format!( + "Cannot encode : '{}'", + ch + ))); + } + } + // A blank is placed between each character. + resultLength += contents.chars().count() - 1; + + let mut result = vec![false; resultLength]; + let mut position = 0; + for index in 0..contents.chars().count() { + // for (int index = 0; index < contents.length(); index++) { + let mut c = contents.chars().nth(index).unwrap().to_ascii_uppercase(); + if index == 0 || index == contents.chars().count() - 1 { + // The start/end chars are not in the CodaBarReader.ALPHABET. + c = match c { + 'T' => 'A', + 'N' => 'B', + '*' => 'C', + 'E' => 'D', + _ => c, + } + } + let mut code = 0; + for i in 0..CodaBarReader::ALPHABET.len() { + // for (int i = 0; i < CodaBarReader::ALPHABET.length; i++) { + // Found any, because I checked above. + if c == CodaBarReader::ALPHABET[i] { + code = CodaBarReader::CHARACTER_ENCODINGS[i]; + break; + } + } + let mut color = true; + let mut counter = 0; + let mut bit = 0; + while bit < 7 { + // A character consists of 7 digit. + result[position] = color; + position += 1; + if ((code >> (6 - bit)) & 1) == 0 || counter == 1 { + color = !color; // Flip the color. + bit += 1; + counter = 0; + } else { + counter += 1; + } + } + if index < contents.chars().count() - 1 { + result[position] = false; + position += 1; + } + } + Ok(result) + } + + fn getSupportedWriteFormats(&self) -> Option> { + Some(vec![BarcodeFormat::CODABAR]) + } +} + +impl Default for CodaBarWriter { + fn default() -> Self { + Self {} + } +} + +/** + * @author dsbnatut@gmail.com (Kazuki Nishiura) + * @author Sean Owen + */ +#[cfg(test)] +mod CodaBarWriterTestCase { + use crate::{ + common::{BitMatrix, BitMatrixTestCase}, + oned::OneDimensionalCodeWriter, + BarcodeFormat, Writer, + }; + + use super::CodaBarWriter; + + #[test] + fn testEncode() { + doTest( + "B515-3/B", + &format!( + "{}{}{}{}{}{}{}{}{}{}", + "00000", + "1001001011", + "0110101001", + "0101011001", + "0110101001", + "0101001101", + "0110010101", + "01101101011", + "01001001011", + "00000" + ), + ); + } + + #[test] + fn testEncode2() { + doTest( + "T123T", + &format!( + "{}{}{}{}{}{}{}", + "00000", + "1011001001", + "0101011001", + "0101001011", + "0110010101", + "01011001001", + "00000" + ), + ); + } + + #[test] + fn testAltStartEnd() { + assert_eq!(encode("T123456789-$T"), encode("A123456789-$A")); + } + + fn doTest(input: &str, expected: &str) { + let result = encode(input); + assert_eq!(expected, BitMatrixTestCase::matrix_to_string(&result)); + } + + fn encode(input: &str) -> BitMatrix { + CodaBarWriter::default() + .encode(input, &BarcodeFormat::CODABAR, 0, 0) + .expect("must encode") + } +} diff --git a/src/oned/mod.rs b/src/oned/mod.rs index 4a4fc3e..080a037 100644 --- a/src/oned/mod.rs +++ b/src/oned/mod.rs @@ -46,3 +46,9 @@ pub use upc_a_reader::*; mod upc_e_reader; pub use upc_e_reader::*; + +mod one_d_code_writer; +pub use one_d_code_writer::*; + +mod coda_bar_writer; +pub use coda_bar_writer::*; diff --git a/src/oned/one_d_code_writer.rs b/src/oned/one_d_code_writer.rs new file mode 100644 index 0000000..22f2c06 --- /dev/null +++ b/src/oned/one_d_code_writer.rs @@ -0,0 +1,203 @@ +/* + * 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 std::collections::HashMap; + +use crate::{ + common::BitMatrix, BarcodeFormat, EncodeHintType, EncodeHintValue, Exceptions, Writer, +}; + +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + pub static ref NUMERIC: Regex = Regex::new("[0-9]+").unwrap(); +} + +/** + *

Encapsulates functionality and implementation that is common to one-dimensional barcodes.

+ * + * @author dsbnatut@gmail.com (Kazuki Nishiura) + */ +pub trait OneDimensionalCodeWriter: Writer { + // private static final Pattern NUMERIC = Pattern.compile("[0-9]+"); + + /** + * Encode the contents to boolean array expression of one-dimensional barcode. + * Start code and end code should be included in result, and side margins should not be included. + * + * @param contents barcode contents to encode + * @return a {@code boolean[]} of horizontal pixels (false = white, true = black) + */ + fn encode_oned(&self, contents: &str) -> Result, Exceptions>; + + /** + * Can be overwritten if the encode requires to read the hints map. Otherwise it defaults to {@code encode}. + * @param contents barcode contents to encode + * @param hints encoding hints + * @return a {@code boolean[]} of horizontal pixels (false = white, true = black) + */ + fn encode_oned_with_hints( + &self, + contents: &str, + _hints: &crate::EncodingHintDictionary, + ) -> Result, Exceptions> { + self.encode_oned(contents) + } + + fn getSupportedWriteFormats(&self) -> Option> { + None + } + + /** + * @return a byte array of horizontal pixels (0 = white, 1 = black) + */ + fn renderRXingResult( + code: &[bool], + width: i32, + height: i32, + sidesMargin: u32, + ) -> Result { + let inputWidth = code.len(); + // Add quiet zone on both sides. + let fullWidth = inputWidth + sidesMargin as usize; + let outputWidth = width.max(fullWidth as i32); + let outputHeight = 1.max(height); + + let multiple = outputWidth as usize / fullWidth; + let leftPadding = (outputWidth as isize - (inputWidth as isize * multiple as isize)) / 2; + + let mut output = BitMatrix::new(outputWidth as u32, outputHeight as u32)?; + + let mut inputX = 0; + let mut outputX = leftPadding; + + while inputX < inputWidth { + // for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { + if code[inputX] { + output.setRegion(outputX as u32, 0, multiple as u32, outputHeight as u32)?; + } + + inputX += 1; + outputX += multiple as isize; + } + Ok(output) + } + + /** + * @param contents string to check for numeric characters + * @throws IllegalArgumentException if input contains characters other than digits 0-9. + */ + fn checkNumeric(contents: &str) -> Result<(), Exceptions> { + if !NUMERIC.is_match(contents) { + Err(Exceptions::IllegalArgumentException( + "Input should only contain digits 0-9".to_owned(), + )) + } else { + Ok(()) + } + } + + /** + * @param target encode black/white pattern into this array + * @param pos position to start encoding at in {@code target} + * @param pattern lengths of black/white runs to encode + * @param startColor starting color - false for white, true for black + * @return the number of elements added to target. + */ + fn appendPattern(target: &mut [bool], pos: usize, pattern: &[usize], startColor: bool) -> u32 { + let mut color = startColor; + let mut numAdded = 0; + let mut pos = pos; + for len in pattern { + // for (int len : pattern) { + for _j in 0..*len { + // for (int j = 0; j < len; j++) { + target[pos] = color; + pos += 1; + } + numAdded += len; + color = !color; // flip color after each segment + } + numAdded as u32 + } + + fn getDefaultMargin(&self) -> u32 { + // CodaBar spec requires a side margin to be more than ten times wider than narrow space. + // This seems like a decent idea for a default for all formats. + 10 + } +} + +struct L; +impl Writer for L { + fn encode( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + ) -> Result { + self.encode_with_hints(contents, format, width, height, &HashMap::new()) + } + + fn encode_with_hints( + &self, + contents: &str, + format: &crate::BarcodeFormat, + width: i32, + height: i32, + hints: &crate::EncodingHintDictionary, + ) -> Result { + if contents.is_empty() { + return Err(Exceptions::IllegalArgumentException( + "Found empty contents".to_owned(), + )); + } + + if width < 0 || height < 0 { + return Err(Exceptions::IllegalArgumentException(format!( + "Negative size is not allowed. Input: {}x{}", + width, height + ))); + } + if let Some(supportedFormats) = self.getSupportedWriteFormats() { + if !supportedFormats.contains(format) { + return Err(Exceptions::IllegalArgumentException(format!( + "Can only encode {:?}, but got {:?}", + supportedFormats, format + ))); + } + } + + let mut sidesMargin = self.getDefaultMargin(); + if let Some(EncodeHintValue::Margin(margin)) = hints.get(&EncodeHintType::MARGIN) { + sidesMargin = margin.parse::().unwrap(); + } + // if hints.contains_key(&EncodeHintType::MARGIN) { + // sidesMargin = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString()); + // } + + let code = self.encode_oned_with_hints(contents, hints)?; + + Self::renderRXingResult(&code, width, height, sidesMargin) + } +} +impl OneDimensionalCodeWriter for L { + fn encode_oned(&self, contents: &str) -> Result, Exceptions> { + todo!() + } +}