From e8348da6469a125fb79951e0170c24ee6ad6cdba Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Mon, 12 Dec 2022 11:43:36 -0600 Subject: [PATCH] port code_128 reader --- src/encode_hints.rs | 2 +- src/oned/Code128Writer.java | 567 -------------------------- src/oned/code_128_reader.rs | 2 +- src/oned/code_128_writer.rs | 775 ++++++++++++++++++++++++++++++++++++ src/oned/itf_writer.rs | 144 +++---- src/oned/mod.rs | 5 +- 6 files changed, 856 insertions(+), 639 deletions(-) delete mode 100644 src/oned/Code128Writer.java create mode 100644 src/oned/code_128_writer.rs diff --git a/src/encode_hints.rs b/src/encode_hints.rs index 9de767f..40d869f 100644 --- a/src/encode_hints.rs +++ b/src/encode_hints.rs @@ -325,5 +325,5 @@ pub enum EncodeHintValue { * This can yield slightly smaller bar codes. This option and {@link #FORCE_CODE_SET} are mutually * exclusive. */ - Code128Compact(String), + Code128Compact(bool), } diff --git a/src/oned/Code128Writer.java b/src/oned/Code128Writer.java deleted file mode 100644 index 6f9f0fb..0000000 --- a/src/oned/Code128Writer.java +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright 2010 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.common.BitMatrix; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -/** - * This object renders a CODE128 code as a {@link BitMatrix}. - * - * @author erik.barbara@gmail.com (Erik Barbara) - */ -public final class Code128Writer extends OneDimensionalCodeWriter { - - private static final int CODE_START_A = 103; - private static final int CODE_START_B = 104; - private static final int CODE_START_C = 105; - private static final int CODE_CODE_A = 101; - private static final int CODE_CODE_B = 100; - private static final int CODE_CODE_C = 99; - private static final int CODE_STOP = 106; - - // Dummy characters used to specify control characters in input - private static final char ESCAPE_FNC_1 = '\u00f1'; - private static final char ESCAPE_FNC_2 = '\u00f2'; - private static final char ESCAPE_FNC_3 = '\u00f3'; - private static final char ESCAPE_FNC_4 = '\u00f4'; - - private static final int CODE_FNC_1 = 102; // Code A, Code B, Code C - private static final int CODE_FNC_2 = 97; // Code A, Code B - private static final int CODE_FNC_3 = 96; // Code A, Code B - private static final int CODE_FNC_4_A = 101; // Code A - private static final int CODE_FNC_4_B = 100; // Code B - - // RXingResults of minimal lookahead for code C - private enum CType { - UNCODABLE, - ONE_DIGIT, - TWO_DIGITS, - FNC_1 - } - - @Override - protected Collection getSupportedWriteFormats() { - return Collections.singleton(BarcodeFormat.CODE_128); - } - - @Override - public boolean[] encode(String contents) { - return encode(contents, null); - } - - @Override - protected boolean[] encode(String contents, Map hints) { - - int forcedCodeSet = check(contents, hints); - - boolean hasCompactionHint = hints != null && hints.containsKey(EncodeHintType.CODE128_COMPACT) && - Boolean.parseBoolean(hints.get(EncodeHintType.CODE128_COMPACT).toString()); - - return hasCompactionHint ? new MinimalEncoder().encode(contents) : encodeFast(contents, forcedCodeSet); - } - - private static int check(String contents, Map hints) { - int length = contents.length(); - // Check length - if (length < 1 || length > 80) { - throw new IllegalArgumentException( - "Contents length should be between 1 and 80 characters, but got " + length); - } - - // Check for forced code set hint. - int forcedCodeSet = -1; - if (hints != null && hints.containsKey(EncodeHintType.FORCE_CODE_SET)) { - String codeSetHint = hints.get(EncodeHintType.FORCE_CODE_SET).toString(); - switch (codeSetHint) { - case "A": - forcedCodeSet = CODE_CODE_A; - break; - case "B": - forcedCodeSet = CODE_CODE_B; - break; - case "C": - forcedCodeSet = CODE_CODE_C; - break; - default: - throw new IllegalArgumentException("Unsupported code set hint: " + codeSetHint); - } - } - - // Check content - for (int i = 0; i < length; i++) { - char c = contents.charAt(i); - // check for non ascii characters that are not special GS1 characters - switch (c) { - // special function characters - case ESCAPE_FNC_1: - case ESCAPE_FNC_2: - case ESCAPE_FNC_3: - case ESCAPE_FNC_4: - break; - // non ascii characters - default: - if (c > 127) { - // no full Latin-1 character set available at the moment - // shift and manual code change are not supported - throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) c); - } - } - // check characters for compatibility with forced code set - switch (forcedCodeSet) { - case CODE_CODE_A: - // allows no ascii above 95 (no lower caps, no special symbols) - if (c > 95 && c <= 127) { - throw new IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + (int) c); - } - break; - case CODE_CODE_B: - // allows no ascii below 32 (terminal symbols) - if (c <= 32) { - throw new IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + (int) c); - } - break; - case CODE_CODE_C: - // allows only numbers and no FNC 2/3/4 - if (c < 48 || (c > 57 && c <= 127) || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4) { - throw new IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + (int) c); - } - break; - } - } - return forcedCodeSet; - } - - private static boolean[] encodeFast(String contents, int forcedCodeSet) { - int length = contents.length(); - - Collection patterns = new ArrayList<>(); // temporary storage for patterns - int checkSum = 0; - int checkWeight = 1; - int codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C) - int position = 0; // position in contents - - while (position < length) { - //Select code to use - int newCodeSet; - if (forcedCodeSet == -1) { - newCodeSet = chooseCode(contents, position, codeSet); - } else { - newCodeSet = forcedCodeSet; - } - - //Get the pattern index - int patternIndex; - if (newCodeSet == codeSet) { - // Encode the current character - // First handle escapes - switch (contents.charAt(position)) { - case ESCAPE_FNC_1: - patternIndex = CODE_FNC_1; - break; - case ESCAPE_FNC_2: - patternIndex = CODE_FNC_2; - break; - case ESCAPE_FNC_3: - patternIndex = CODE_FNC_3; - break; - case ESCAPE_FNC_4: - if (codeSet == CODE_CODE_A) { - patternIndex = CODE_FNC_4_A; - } else { - patternIndex = CODE_FNC_4_B; - } - break; - default: - // Then handle normal characters otherwise - switch (codeSet) { - case CODE_CODE_A: - patternIndex = contents.charAt(position) - ' '; - if (patternIndex < 0) { - // everything below a space character comes behind the underscore in the code patterns table - patternIndex += '`'; - } - break; - case CODE_CODE_B: - patternIndex = contents.charAt(position) - ' '; - break; - default: - // CODE_CODE_C - if (position + 1 == length) { - // this is the last character, but the encoding is C, which always encodes two characers - throw new IllegalArgumentException("Bad number of characters for digit only encoding."); - } - patternIndex = Integer.parseInt(contents.substring(position, position + 2)); - position++; // Also incremented below - break; - } - } - position++; - } else { - // Should we change the current code? - // Do we have a code set? - if (codeSet == 0) { - // No, we don't have a code set - switch (newCodeSet) { - case CODE_CODE_A: - patternIndex = CODE_START_A; - break; - case CODE_CODE_B: - patternIndex = CODE_START_B; - break; - default: - patternIndex = CODE_START_C; - break; - } - } else { - // Yes, we have a code set - patternIndex = newCodeSet; - } - codeSet = newCodeSet; - } - - // Get the pattern - patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]); - - // Compute checksum - checkSum += patternIndex * checkWeight; - if (position != 0) { - checkWeight++; - } - } - return produceRXingResult(patterns, checkSum); - } - - static boolean[] produceRXingResult(Collection patterns, int checkSum) { - // Compute and append checksum - checkSum %= 103; - patterns.add(Code128Reader.CODE_PATTERNS[checkSum]); - - // Append stop code - patterns.add(Code128Reader.CODE_PATTERNS[CODE_STOP]); - - // Compute code width - int codeWidth = 0; - for (int[] pattern : patterns) { - for (int width : pattern) { - codeWidth += width; - } - } - - // Compute result - boolean[] result = new boolean[codeWidth]; - int pos = 0; - for (int[] pattern : patterns) { - pos += appendPattern(result, pos, pattern, true); - } - - return result; - } - - private static CType findCType(CharSequence value, int start) { - int last = value.length(); - if (start >= last) { - return CType.UNCODABLE; - } - char c = value.charAt(start); - if (c == ESCAPE_FNC_1) { - return CType.FNC_1; - } - if (c < '0' || c > '9') { - return CType.UNCODABLE; - } - if (start + 1 >= last) { - return CType.ONE_DIGIT; - } - c = value.charAt(start + 1); - if (c < '0' || c > '9') { - return CType.ONE_DIGIT; - } - return CType.TWO_DIGITS; - } - - private static int chooseCode(CharSequence value, int start, int oldCode) { - CType lookahead = findCType(value, start); - if (lookahead == CType.ONE_DIGIT) { - if (oldCode == CODE_CODE_A) { - return CODE_CODE_A; - } - return CODE_CODE_B; - } - if (lookahead == CType.UNCODABLE) { - if (start < value.length()) { - char c = value.charAt(start); - if (c < ' ' || (oldCode == CODE_CODE_A && (c < '`' || (c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)))) { - // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4 - return CODE_CODE_A; - } - } - return CODE_CODE_B; // no choice - } - if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) { - return CODE_CODE_A; - } - if (oldCode == CODE_CODE_C) { // can continue in code C - return CODE_CODE_C; - } - if (oldCode == CODE_CODE_B) { - if (lookahead == CType.FNC_1) { - return CODE_CODE_B; // can continue in code B - } - // Seen two consecutive digits, see what follows - lookahead = findCType(value, start + 2); - if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) { - return CODE_CODE_B; // not worth switching now - } - if (lookahead == CType.FNC_1) { // two digits, then FNC_1... - lookahead = findCType(value, start + 3); - if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch - return CODE_CODE_C; - } else { - return CODE_CODE_B; // otherwise not worth switching - } - } - // At this point, there are at least 4 consecutive digits. - // Look ahead to choose whether to switch now or on the next round. - int index = start + 4; - while ((lookahead = findCType(value, index)) == CType.TWO_DIGITS) { - index += 2; - } - if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later - return CODE_CODE_B; - } - return CODE_CODE_C; // even number of digits, switch now - } - // Here oldCode == 0, which means we are choosing the initial code - if (lookahead == CType.FNC_1) { // ignore FNC_1 - lookahead = findCType(value, start + 1); - } - if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C - return CODE_CODE_C; - } - return CODE_CODE_B; - } - - /** - * Encodes minimally using Divide-And-Conquer with Memoization - **/ - private static final class MinimalEncoder { - - private enum Charset { A, B, C, NONE } - private enum Latch { A, B, C, SHIFT, NONE } - - static final String A = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u0000\u0001\u0002" + - "\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011" + - "\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" + - "\u00FF"; - static final String B = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr" + - "stuvwxyz{|}~\u007F\u00FF"; - - private static final int CODE_SHIFT = 98; - - private int[][] memoizedCost; - private Latch[][] minPath; - - private boolean[] encode(String contents) { - memoizedCost = new int[4][contents.length()]; - minPath = new Latch[4][contents.length()]; - - encode(contents, Charset.NONE, 0); - - Collection patterns = new ArrayList<>(); - int[] checkSum = new int[] {0}; - int[] checkWeight = new int[] {1}; - int length = contents.length(); - Charset charset = Charset.NONE; - for (int i = 0; i < length; i++) { - Latch latch = minPath[charset.ordinal()][i]; - switch (latch) { - case A: - charset = Charset.A; - addPattern(patterns, i == 0 ? CODE_START_A : CODE_CODE_A, checkSum, checkWeight, i); - break; - case B: - charset = Charset.B; - addPattern(patterns, i == 0 ? CODE_START_B : CODE_CODE_B, checkSum, checkWeight, i); - break; - case C: - charset = Charset.C; - addPattern(patterns, i == 0 ? CODE_START_C : CODE_CODE_C, checkSum, checkWeight, i); - break; - case SHIFT: - addPattern(patterns, CODE_SHIFT, checkSum, checkWeight, i); - break; - } - if (charset == Charset.C) { - if (contents.charAt(i) == ESCAPE_FNC_1) { - addPattern(patterns, CODE_FNC_1, checkSum, checkWeight, i); - } else { - addPattern(patterns, Integer.parseInt(contents.substring(i, i + 2)), checkSum, checkWeight, i); - assert i + 1 < length; //the algorithm never leads to a single trailing digit in character set C - if (i + 1 < length) { - i++; - } - } - } else { // charset A or B - int patternIndex; - switch (contents.charAt(i)) { - case ESCAPE_FNC_1: - patternIndex = CODE_FNC_1; - break; - case ESCAPE_FNC_2: - patternIndex = CODE_FNC_2; - break; - case ESCAPE_FNC_3: - patternIndex = CODE_FNC_3; - break; - case ESCAPE_FNC_4: - if ((charset == Charset.A && latch != Latch.SHIFT) || - (charset == Charset.B && latch == Latch.SHIFT)) { - patternIndex = CODE_FNC_4_A; - } else { - patternIndex = CODE_FNC_4_B; - } - break; - default: - patternIndex = contents.charAt(i) - ' '; - } - if ((charset == Charset.A && latch != Latch.SHIFT) || - (charset == Charset.B && latch == Latch.SHIFT)) { - if (patternIndex < 0) { - patternIndex += '`'; - } - } - addPattern(patterns, patternIndex, checkSum, checkWeight, i); - } - } - memoizedCost = null; - minPath = null; - return produceRXingResult(patterns, checkSum[0]); - } - - private static void addPattern(Collection patterns, - int patternIndex, - int[] checkSum, - int[] checkWeight, - int position) { - patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]); - if (position != 0) { - checkWeight[0]++; - } - checkSum[0] += patternIndex * checkWeight[0]; - } - - private static boolean isDigit(char c) { - return c >= '0' && c <= '9'; - } - - private boolean canEncode(CharSequence contents, Charset charset,int position) { - char c = contents.charAt(position); - switch (charset) { - case A: return c == ESCAPE_FNC_1 || - c == ESCAPE_FNC_2 || - c == ESCAPE_FNC_3 || - c == ESCAPE_FNC_4 || - A.indexOf(c) >= 0; - case B: return c == ESCAPE_FNC_1 || - c == ESCAPE_FNC_2 || - c == ESCAPE_FNC_3 || - c == ESCAPE_FNC_4 || - B.indexOf(c) >= 0; - case C: return c == ESCAPE_FNC_1 || - (position + 1 < contents.length() && - isDigit(c) && - isDigit(contents.charAt(position + 1))); - default: return false; - } - } - - /** - * Encode the string starting at position position starting with the character set charset - **/ - private int encode(CharSequence contents, Charset charset, int position) { - assert position < contents.length(); - int mCost = memoizedCost[charset.ordinal()][position]; - if (mCost > 0) { - return mCost; - } - - int minCost = Integer.MAX_VALUE; - Latch minLatch = Latch.NONE; - boolean atEnd = position + 1 >= contents.length(); - - Charset[] sets = new Charset[] { Charset.A, Charset.B }; - for (int i = 0; i <= 1; i++) { - if (canEncode(contents, sets[i], position)) { - int cost = 1; - Latch latch = Latch.NONE; - if (charset != sets[i]) { - cost++; - latch = Latch.valueOf(sets[i].toString()); - } - if (!atEnd) { - cost += encode(contents, sets[i], position + 1); - } - if (cost < minCost) { - minCost = cost; - minLatch = latch; - } - cost = 1; - if (charset == sets[(i + 1) % 2]) { - cost++; - latch = Latch.SHIFT; - if (!atEnd) { - cost += encode(contents, charset, position + 1); - } - if (cost < minCost) { - minCost = cost; - minLatch = latch; - } - } - } - } - if (canEncode(contents, Charset.C, position)) { - int cost = 1; - Latch latch = Latch.NONE; - if (charset != Charset.C) { - cost++; - latch = Latch.C; - } - int advance = contents.charAt(position) == ESCAPE_FNC_1 ? 1 : 2; - if (position + advance < contents.length()) { - cost += encode(contents, Charset.C, position + advance); - } - if (cost < minCost) { - minCost = cost; - minLatch = latch; - } - } - if (minCost == Integer.MAX_VALUE) { - throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) contents.charAt(position)); - } - memoizedCost[charset.ordinal()][position] = minCost; - minPath[charset.ordinal()][position] = minLatch; - return minCost; - } - } -} diff --git a/src/oned/code_128_reader.rs b/src/oned/code_128_reader.rs index 5519bd8..481716d 100644 --- a/src/oned/code_128_reader.rs +++ b/src/oned/code_128_reader.rs @@ -453,7 +453,7 @@ use lazy_static::lazy_static; lazy_static! { -static ref CODE_PATTERNS: [Vec; 107] = [ +pub static ref CODE_PATTERNS: [Vec; 107] = [ vec![2, 1, 2, 2, 2, 2], // 0 vec![2, 2, 2, 1, 2, 2], vec![2, 2, 2, 2, 2, 1], diff --git a/src/oned/code_128_writer.rs b/src/oned/code_128_writer.rs new file mode 100644 index 0000000..50d500b --- /dev/null +++ b/src/oned/code_128_writer.rs @@ -0,0 +1,775 @@ +/* + * Copyright 2010 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::{code_128_reader, Code128Reader, OneDimensionalCodeWriter, CODE_PATTERNS}; + +const CODE_START_A: usize = 103; +const CODE_START_B: usize = 104; +const CODE_START_C: usize = 105; +const CODE_CODE_A: usize = 101; +const CODE_CODE_B: usize = 100; +const CODE_CODE_C: usize = 99; +const CODE_STOP: usize = 106; + +// Dummy characters used to specify control characters in input +const ESCAPE_FNC_1: char = '\u{00f1}'; +const ESCAPE_FNC_2: char = '\u{00f2}'; +const ESCAPE_FNC_3: char = '\u{00f3}'; +const ESCAPE_FNC_4: char = '\u{00f4}'; + +const CODE_FNC_1: usize = 102; // Code A, Code B, Code C +const CODE_FNC_2: usize = 97; // Code A, Code B +const CODE_FNC_3: usize = 96; // Code A, Code B +const CODE_FNC_4_A: usize = 101; // Code A +const CODE_FNC_4_B: usize = 100; // Code B + +// RXingResults of minimal lookahead for code C +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum CType { + UNCODABLE, + ONE_DIGIT, + TWO_DIGITS, + FNC_1, +} + +/** + * This object renders a CODE128 code as a {@link BitMatrix}. + * + * @author erik.barbara@gmail.com (Erik Barbara) + */ +#[derive(OneDWriter)] +pub struct Code128Writer; +impl Default for Code128Writer { + fn default() -> Self { + Self {} + } +} +impl OneDimensionalCodeWriter for Code128Writer { + fn encode_oned(&self, contents: &str) -> Result, Exceptions> { + self.encode_oned_with_hints(contents, &HashMap::new()) + } + + fn getSupportedWriteFormats(&self) -> Option> { + Some(vec![BarcodeFormat::CODE_128]) + } + + fn encode_oned_with_hints( + &self, + contents: &str, + hints: &crate::EncodingHintDictionary, + ) -> Result, Exceptions> { + let forcedCodeSet = check(contents, hints)?; + + let hasCompactionHint = if let Some(EncodeHintValue::Code128Compact(compat)) = + hints.get(&EncodeHintType::CODE128_COMPACT) + { + *compat + } else { + false + }; + // let hasCompactionHint = hints != null && hints.containsKey(EncodeHintType::CODE128_COMPACT) && + // Boolean.parseBoolean(hints.get(EncodeHintType::CODE128_COMPACT).toString()); + + if hasCompactionHint { + MinimalEncoder::encode(contents) + } else { + encodeFast(contents, forcedCodeSet) + } + } +} + +fn check(contents: &str, hints: &crate::EncodingHintDictionary) -> Result { + let length = contents.chars().count(); + // Check length + if length < 1 || length > 80 { + return Err(Exceptions::IllegalArgumentException(format!( + "Contents length should be between 1 and 80 characters, but got {}", + length + ))); + } + + // Check for forced code set hint. + let mut forcedCodeSet = -1_i32; + if hints.contains_key(&EncodeHintType::FORCE_CODE_SET) { + let Some(EncodeHintValue::ForceCodeSet(codeSetHint)) = hints.get(&EncodeHintType::FORCE_CODE_SET) else { panic!("This must exist by checks previous")}; + match codeSetHint.as_str() { + "A" => forcedCodeSet = CODE_CODE_A as i32, + "B" => forcedCodeSet = CODE_CODE_B as i32, + "C" => forcedCodeSet = CODE_CODE_C as i32, + _ => { + return Err(Exceptions::IllegalArgumentException(format!( + "Unsupported code set hint: {}", + codeSetHint + ))) + } + } + } + + // Check content + for ch in contents.chars() { + let c = ch as u32; + // for (int i = 0; i < length; i++) { + // char c = contents.charAt(i); + // check for non ascii characters that are not special GS1 characters + match ch { + // special function characters + ESCAPE_FNC_1 | ESCAPE_FNC_2 | ESCAPE_FNC_3 | ESCAPE_FNC_4 => {} + // non ascii characters + _ => { + if c > 127 { + // no full Latin-1 character set available at the moment + // shift and manual code change are not supported + return Err(Exceptions::IllegalArgumentException(format!( + "Bad character in input: ASCII value={}", + c + ))); + } + } + } + // check characters for compatibility with forced code set + const CODE_CODE_A_I32: i32 = CODE_CODE_A as i32; + const CODE_CODE_B_I32: i32 = CODE_CODE_B as i32; + const CODE_CODE_C_I32: i32 = CODE_CODE_C as i32; + match forcedCodeSet { + CODE_CODE_A_I32 => + // allows no ascii above 95 (no lower caps, no special symbols) + { + if c > 95 && c <= 127 { + return Err(Exceptions::IllegalArgumentException(format!( + "Bad character in input for forced code set A: ASCII value={}", + c + ))); + } + } + CODE_CODE_B_I32 => + // allows no ascii below 32 (terminal symbols) + { + if c <= 32 { + return Err(Exceptions::IllegalArgumentException(format!( + "Bad character in input for forced code set B: ASCII value={}", + c + ))); + } + } + CODE_CODE_C_I32 => + // allows only numbers and no FNC 2/3/4 + { + if c < 48 + || (c > 57 && c <= 127) + || ch == ESCAPE_FNC_2 + || ch == ESCAPE_FNC_3 + || ch == ESCAPE_FNC_4 + { + return Err(Exceptions::IllegalArgumentException(format!( + "Bad character in input for forced code set C: ASCII value={}", + c + ))); + } + } + _ => {} + } + } + Ok(forcedCodeSet) +} + +fn encodeFast(contents: &str, forcedCodeSet: i32) -> Result, Exceptions> { + let length = contents.chars().count(); + + let mut patterns: Vec> = Vec::new(); //new ArrayList<>(); // temporary storage for patterns + let mut checkSum = 0; + let mut checkWeight = 1; + let mut codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C) + let mut position = 0; // position in contents + + while position < length { + //Select code to use + let newCodeSet = if forcedCodeSet == -1 { + chooseCode(contents, position, codeSet) + } else { + forcedCodeSet as usize // THIS IS RISKY + }; + + //Get the pattern index + let mut patternIndex: isize; + if newCodeSet == codeSet { + // Encode the current character + // First handle escapes + match contents.chars().nth(position).unwrap() { + ESCAPE_FNC_1 => patternIndex = CODE_FNC_1 as isize, + ESCAPE_FNC_2 => patternIndex = CODE_FNC_2 as isize, + ESCAPE_FNC_3 => patternIndex = CODE_FNC_3 as isize, + ESCAPE_FNC_4 => { + if codeSet == CODE_CODE_A { + patternIndex = CODE_FNC_4_A as isize; + } else { + patternIndex = CODE_FNC_4_B as isize; + } + } + _ => + // Then handle normal characters otherwise + { + match codeSet as usize { + CODE_CODE_A => { + patternIndex = + contents.chars().nth(position).unwrap() as isize - ' ' as isize; + if patternIndex < 0 { + // everything below a space character comes behind the underscore in the code patterns table + patternIndex += '`' as isize; + } + } + CODE_CODE_B => { + patternIndex = + contents.chars().nth(position).unwrap() as isize - ' ' as isize + } + _ => { + // CODE_CODE_C + if position + 1 == length { + // this is the last character, but the encoding is C, which always encodes two characers + return Err(Exceptions::IllegalArgumentException( + "Bad number of characters for digit only encoding.".to_owned(), + )); + } + patternIndex = + contents[position..position + 1].parse::().unwrap(); + // patternIndex = Integer.parseInt(contents.substring(position, position + 2)); + position += 1; + } // Also incremented below + } + } + } + position += 1; + } else { + // Should we change the current code? + // Do we have a code set? + if codeSet == 0 { + // No, we don't have a code set + match newCodeSet as usize { + CODE_CODE_A => patternIndex = CODE_START_A as isize, + CODE_CODE_B => patternIndex = CODE_START_B as isize, + _ => patternIndex = CODE_START_C as isize, + } + } else { + // Yes, we have a code set + patternIndex = newCodeSet as isize; + } + codeSet = newCodeSet; + } + + // Get the pattern + patterns.push( + code_128_reader::CODE_PATTERNS[patternIndex as usize] + .iter() + .map(|x| *x as usize) + .collect(), + ); + + // Compute checksum + checkSum += patternIndex * checkWeight; + if position != 0 { + checkWeight += 1; + } + } + + Ok(produceRXingResult(&mut patterns, checkSum as usize)) +} + +fn produceRXingResult(patterns: &mut Vec>, checkSum: usize) -> Vec { + // Compute and append checksum + let mut checkSum = checkSum; + checkSum %= 103; + patterns.push( + code_128_reader::CODE_PATTERNS[checkSum] + .iter() + .map(|x| *x as usize) + .collect(), + ); + + // Append stop code + patterns.push( + code_128_reader::CODE_PATTERNS[CODE_STOP] + .iter() + .map(|x| *x as usize) + .collect(), + ); + + // Compute code width + let mut codeWidth = 0_usize; + for pattern in &mut *patterns { + // for (int[] pattern : patterns) { + for width in pattern { + // for (int width : pattern) { + codeWidth += *width as usize; + } + } + + // Compute result + let mut result = vec![false; codeWidth]; + let mut pos = 0; + for pattern in patterns { + // for (int[] pattern : patterns) { + pos += Code128Writer::appendPattern(&mut result, pos, pattern, true) as usize; + } + + return result; +} + +fn findCType(value: &str, start: usize) -> CType { + let last = value.chars().count(); + if start >= last { + return CType::UNCODABLE; + } + let c = value.chars().nth(start).unwrap(); + if c == ESCAPE_FNC_1 { + return CType::FNC_1; + } + if c < '0' || c > '9' { + return CType::UNCODABLE; + } + if start + 1 >= last { + return CType::ONE_DIGIT; + } + let c = value.chars().nth(start + 1).unwrap(); + if c < '0' || c > '9' { + return CType::ONE_DIGIT; + } + return CType::TWO_DIGITS; +} + +fn chooseCode(value: &str, start: usize, oldCode: usize) -> usize { + let mut lookahead = findCType(value, start); + if lookahead == CType::ONE_DIGIT { + if oldCode == CODE_CODE_A { + return CODE_CODE_A; + } + return CODE_CODE_B; + } + if lookahead == CType::UNCODABLE { + if start < value.chars().count() { + let c = value.chars().nth(start).unwrap(); + if c < ' ' + || (oldCode == CODE_CODE_A && (c < '`' || (c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4))) + { + // can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4 + return CODE_CODE_A; + } + } + return CODE_CODE_B; // no choice + } + if oldCode == CODE_CODE_A && lookahead == CType::FNC_1 { + return CODE_CODE_A; + } + if oldCode == CODE_CODE_C { + // can continue in code C + return CODE_CODE_C; + } + if oldCode == CODE_CODE_B { + if lookahead == CType::FNC_1 { + return CODE_CODE_B; // can continue in code B + } + // Seen two consecutive digits, see what follows + lookahead = findCType(value, start + 2); + if lookahead == CType::UNCODABLE || lookahead == CType::ONE_DIGIT { + return CODE_CODE_B; // not worth switching now + } + if lookahead == CType::FNC_1 { + // two digits, then FNC_1... + lookahead = findCType(value, start + 3); + if lookahead == CType::TWO_DIGITS { + // then two more digits, switch + return CODE_CODE_C; + } else { + return CODE_CODE_B; // otherwise not worth switching + } + } + // At this point, there are at least 4 consecutive digits. + // Look ahead to choose whether to switch now or on the next round. + let mut index = start + 4; + let mut lookahead = findCType(value, index); + while lookahead == CType::TWO_DIGITS { + // while (lookahead = findCType(value, index)) == CType::TWO_DIGITS { + index += 2; + lookahead = findCType(value, index); + } + if lookahead == CType::ONE_DIGIT { + // odd number of digits, switch later + return CODE_CODE_B; + } + return CODE_CODE_C; // even number of digits, switch now + } + // Here oldCode == 0, which means we are choosing the initial code + if lookahead == CType::FNC_1 { + // ignore FNC_1 + lookahead = findCType(value, start + 1); + } + if lookahead == CType::TWO_DIGITS { + // at least two digits, start in code C + return CODE_CODE_C; + } + return CODE_CODE_B; +} + +/** + * Encodes minimally using Divide-And-Conquer with Memoization + **/ +// struct MinimalEncoder { +// memoizedCost:Vec>, +// minPath:Vec>, +// } +mod MinimalEncoder { + use crate::{oned::code_128_reader, Exceptions}; + + use super::{ + produceRXingResult, CODE_CODE_A, CODE_CODE_B, CODE_CODE_C, CODE_FNC_1, CODE_FNC_2, + CODE_FNC_3, CODE_FNC_4_A, CODE_FNC_4_B, CODE_START_A, CODE_START_B, CODE_START_C, + ESCAPE_FNC_1, ESCAPE_FNC_2, ESCAPE_FNC_3, ESCAPE_FNC_4, + }; + + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum Charset { + A, + B, + C, + NONE, + } + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + enum Latch { + A, + B, + C, + SHIFT, + NONE, + } + + const A : &str = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u{0000}\u{0001}\u{0002}/ +\u{0003}\u{0004}\u{0005}\u{0006}\u{0007}\u{0008}\u{0009}\n\u{000B}\u{000C}\r\u{000E}\u{000F}\u{0010}\u{0011}/ +\u{0012}\u{0013}\u{0014}\u{0015}\u{0016}\u{0017}\u{0018}\u{0019}\u{001A}\u{001B}\u{001C}\u{001D}\u{001E}\u{001F}/ +\u{00FF}"; + const B: &str = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr\ +stuvwxyz{|}~\u{007F}\u{00FF}"; + + const CODE_SHIFT: usize = 98; + + pub fn encode(contents: &str) -> Result, Exceptions> { + let length = contents.chars().count(); + let mut memoizedCost = vec![vec![0_u32; 4]; length]; //new int[4][contents.length()]; + let mut minPath = vec![vec![Latch::NONE; 4]; length]; //new Latch[4][contents.length()]; + + encode_with_start_position(contents, Charset::NONE, 0, &mut memoizedCost, &mut minPath)?; + + let mut patterns: Vec> = Vec::new(); //new ArrayList<>(); + let mut checkSum = vec![0_usize]; //new int[] {0}; + let mut checkWeight = vec![1]; //new int[] {1}; + let mut charset = Charset::NONE; + let mut i = 0; + while i < length { + // for i in 0..length { + // for (int i = 0; i < length; i++) { + let latch = minPath[charset.ordinal()][i]; + match latch { + Latch::A => { + charset = Charset::A; + addPattern( + &mut patterns, + if i == 0 { CODE_START_A } else { CODE_CODE_A }, + &mut checkSum, + &mut checkWeight, + i, + ); + } + Latch::B => { + charset = Charset::B; + addPattern( + &mut patterns, + if i == 0 { CODE_START_B } else { CODE_CODE_B }, + &mut checkSum, + &mut checkWeight, + i, + ); + } + Latch::C => { + charset = Charset::C; + addPattern( + &mut patterns, + if i == 0 { CODE_START_C } else { CODE_CODE_C }, + &mut checkSum, + &mut checkWeight, + i, + ); + } + Latch::SHIFT => addPattern( + &mut patterns, + CODE_SHIFT, + &mut checkSum, + &mut checkWeight, + i, + ), + Latch::NONE => { /* skip */ } + } + if charset == Charset::C { + if contents.chars().nth(i).unwrap() == ESCAPE_FNC_1 { + addPattern( + &mut patterns, + CODE_FNC_1, + &mut checkSum, + &mut checkWeight, + i, + ); + } else { + addPattern( + &mut patterns, + (contents[i..i + 2]).parse::().unwrap(), + &mut checkSum, + &mut checkWeight, + i, + ); + assert!(i + 1 < length); //the algorithm never leads to a single trailing digit in character set C + if i + 1 < length { + i += 1; + } + } + } else { + // charset A or B + let mut patternIndex = match contents.chars().nth(i).unwrap() { + ESCAPE_FNC_1 => CODE_FNC_1, + ESCAPE_FNC_2 => CODE_FNC_2, + ESCAPE_FNC_3 => CODE_FNC_3, + ESCAPE_FNC_4 => { + if (charset == Charset::A && latch != Latch::SHIFT) + || (charset == Charset::B && latch == Latch::SHIFT) + { + CODE_FNC_4_A + } else { + CODE_FNC_4_B + } + } + _ => contents.chars().nth(i).unwrap() as usize - ' ' as usize, + } as isize; + if (charset == Charset::A && latch != Latch::SHIFT) + || (charset == Charset::B && latch == Latch::SHIFT) + { + if patternIndex < 0 { + patternIndex += '`' as isize; + } + } + addPattern( + &mut patterns, + patternIndex as usize, + &mut checkSum, + &mut checkWeight, + i, + ); + } + + i += 1; + } + // memoizedCost.clear(); + // minPath.clear(); + + Ok(produceRXingResult(&mut patterns, checkSum[0])) + } + + fn addPattern( + patterns: &mut Vec>, + patternIndex: usize, + checkSum: &mut [usize], + checkWeight: &mut [u32], + position: usize, + ) { + patterns.push( + code_128_reader::CODE_PATTERNS[patternIndex] + .iter() + .map(|x| *x as usize) + .collect(), + ); + if position != 0 { + checkWeight[0] += 1; + } + checkSum[0] += patternIndex * checkWeight[0] as usize; + } + + fn isDigit(c: char) -> bool { + return c >= '0' && c <= '9'; + } + + fn canEncode(contents: &str, charset: Charset, position: usize) -> bool { + let c = contents.chars().nth(position).unwrap(); + match charset { + Charset::A => { + c == ESCAPE_FNC_1 + || c == ESCAPE_FNC_2 + || c == ESCAPE_FNC_3 + || c == ESCAPE_FNC_4 + || A.find(c).is_some() + } + Charset::B => { + c == ESCAPE_FNC_1 + || c == ESCAPE_FNC_2 + || c == ESCAPE_FNC_3 + || c == ESCAPE_FNC_4 + || B.find(c).is_some() + } + Charset::C => { + c == ESCAPE_FNC_1 + || (position + 1 < contents.chars().count() + && isDigit(c) + && isDigit(contents.chars().nth(position + 1).unwrap())) + } + _ => false, + } + } + + /** + * Encode the string starting at position position starting with the character set charset + **/ + fn encode_with_start_position( + contents: &str, + charset: Charset, + position: usize, + memoizedCost: &mut Vec>, + minPath: &mut Vec>, + ) -> Result { + assert!(position < contents.chars().count()); + let mCost = memoizedCost[charset.ordinal()][position]; + if mCost > 0 { + return Ok(mCost); + } + + let mut minCost = u32::MAX; + let mut minLatch = Latch::NONE; + let atEnd = position + 1 >= contents.chars().count(); + + let sets = [Charset::A, Charset::B]; + for i in 0..=1 { + // for (int i = 0; i <= 1; i++) { + if canEncode(contents, sets[i], position) { + let mut cost = 1; + let mut latch = Latch::NONE; + if charset != sets[i] { + cost += 1; + latch = sets[i].into(); //Latch::valueOf(sets[i].toString()); + } + if !atEnd { + cost += encode_with_start_position( + contents, + sets[i], + position + 1, + memoizedCost, + minPath, + )?; + } + if cost < minCost { + minCost = cost; + minLatch = latch; + } + cost = 1; + if charset == sets[(i + 1) % 2] { + cost += 1; + latch = Latch::SHIFT; + if !atEnd { + cost += encode_with_start_position( + contents, + charset, + position + 1, + memoizedCost, + minPath, + )?; + } + if cost < minCost { + minCost = cost; + minLatch = latch; + } + } + } + } + if canEncode(contents, Charset::C, position) { + let mut cost = 1; + let mut latch = Latch::NONE; + if charset != Charset::C { + cost += 1; + latch = Latch::C; + } + let advance = if contents.chars().nth(position).unwrap() == ESCAPE_FNC_1 { + 1 + } else { + 2 + }; + if position + advance < contents.chars().count() { + cost += encode_with_start_position( + contents, + Charset::C, + position + advance, + memoizedCost, + minPath, + )?; + } + if cost < minCost { + minCost = cost; + minLatch = latch; + } + } + if minCost == u32::MAX { + return Err(Exceptions::IllegalArgumentException(format!( + "Bad character in input: ASCII value={}", + contents.chars().nth(position).unwrap_or('x') + ))); + // throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) contents.charAt(position)); + } + memoizedCost[charset.ordinal()][position] = minCost; + minPath[charset.ordinal()][position] = minLatch; + Ok(minCost) + } + + trait HasOrdinal { + fn ordinal(&self) -> usize; + } + + impl HasOrdinal for Charset { + fn ordinal(&self) -> usize { + match self { + Charset::A => 0, + Charset::B => 1, + Charset::C => 2, + Charset::NONE => 3, + } + } + } + impl HasOrdinal for Latch { + fn ordinal(&self) -> usize { + match self { + Latch::A => 0, + Latch::B => 1, + Latch::C => 2, + Latch::SHIFT => 3, + Latch::NONE => 4, + } + } + } + impl From for Latch { + fn from(cs: Charset) -> Self { + match cs { + Charset::A => Latch::A, + Charset::B => Latch::B, + Charset::C => Latch::C, + Charset::NONE => Latch::NONE, + } + } + } +} diff --git a/src/oned/itf_writer.rs b/src/oned/itf_writer.rs index 6afecaf..3ee2f32 100644 --- a/src/oned/itf_writer.rs +++ b/src/oned/itf_writer.rs @@ -20,109 +20,115 @@ use crate::BarcodeFormat; use super::OneDimensionalCodeWriter; - /** * This object renders a ITF code as a {@link BitMatrix}. * * @author erik.barbara@gmail.com (Erik Barbara) */ #[derive(OneDWriter)] -pub struct ITFWriter; +pub struct ITFWriter; impl Default for ITFWriter { fn default() -> Self { - Self { } + Self {} } } impl OneDimensionalCodeWriter for ITFWriter { fn encode_oned(&self, contents: &str) -> Result, Exceptions> { - let length = contents.chars().count(); - if length % 2 != 0 { - return Err(Exceptions::IllegalArgumentException("The length of the input should be even".to_owned())); - } - if length > 80 { - return Err(Exceptions::IllegalArgumentException(format!("Requested contents should be less than 80 digits long, but got {}" , length))); - } - - Self::checkNumeric(contents)?; - - let mut result = vec![false;9 + 9 * length]; - let mut pos = Self::appendPattern(&mut result, 0, &START_PATTERN, true) as usize; - let mut i = 0; - while i < length { - // for (int i = 0; i < length; i += 2) { - let one = contents.chars().nth(i).unwrap().to_digit(10).unwrap() as usize; - let two = contents.chars().nth(i + 1).unwrap().to_digit(10).unwrap() as usize; - let mut encoding = [0;10];//new int[10]; - for j in 0..5 { - // for (int j = 0; j < 5; j++) { - encoding[2 * j] = PATTERNS[one][j]; - encoding[2 * j + 1] = PATTERNS[two][j]; + let length = contents.chars().count(); + if length % 2 != 0 { + return Err(Exceptions::IllegalArgumentException( + "The length of the input should be even".to_owned(), + )); + } + if length > 80 { + return Err(Exceptions::IllegalArgumentException(format!( + "Requested contents should be less than 80 digits long, but got {}", + length + ))); } - pos += Self::appendPattern(&mut result, pos, &encoding, true) as usize; - i+=2; - } - Self::appendPattern(&mut result, pos, &END_PATTERN, true); - - Ok( result) + Self::checkNumeric(contents)?; + + let mut result = vec![false; 9 + 9 * length]; + let mut pos = Self::appendPattern(&mut result, 0, &START_PATTERN, true) as usize; + let mut i = 0; + while i < length { + // for (int i = 0; i < length; i += 2) { + let one = contents.chars().nth(i).unwrap().to_digit(10).unwrap() as usize; + let two = contents.chars().nth(i + 1).unwrap().to_digit(10).unwrap() as usize; + let mut encoding = [0; 10]; //new int[10]; + for j in 0..5 { + // for (int j = 0; j < 5; j++) { + encoding[2 * j] = PATTERNS[one][j]; + encoding[2 * j + 1] = PATTERNS[two][j]; + } + pos += Self::appendPattern(&mut result, pos, &encoding, true) as usize; + + i += 2; + } + Self::appendPattern(&mut result, pos, &END_PATTERN, true); + + Ok(result) } fn getSupportedWriteFormats(&self) -> Option> { Some(vec![BarcodeFormat::ITF]) } - - } -const START_PATTERN : [usize;4]= [1, 1, 1, 1]; - const END_PATTERN :[usize;3]= [3, 1, 1]; +const START_PATTERN: [usize; 4] = [1, 1, 1, 1]; +const END_PATTERN: [usize; 3] = [3, 1, 1]; - const W : usize= 3; // Pixel width of a 3x wide line - const N :usize= 1; // Pixed width of a narrow line +const W: usize = 3; // Pixel width of a 3x wide line +const N: usize = 1; // Pixed width of a narrow line - // See ITFReader.PATTERNS +// See ITFReader.PATTERNS - const PATTERNS : [[usize;5];10]= [ - [N, N, W, W, N], // 0 - [W, N, N, N, W], // 1 - [N, W, N, N, W], // 2 - [W, W, N, N, N], // 3 - [N, N, W, N, W], // 4 - [W, N, W, N, N], // 5 - [N, W, W, N, N], // 6 - [N, N, N, W, W], // 7 - [W, N, N, W, N], // 8 - [N, W, N, W, N] // 9 - ]; +const PATTERNS: [[usize; 5]; 10] = [ + [N, N, W, W, N], // 0 + [W, N, N, N, W], // 1 + [N, W, N, N, W], // 2 + [W, W, N, N, N], // 3 + [N, N, W, N, W], // 4 + [W, N, W, N, N], // 5 + [N, W, W, N, N], // 6 + [N, N, N, W, W], // 7 + [W, N, N, W, N], // 8 + [N, W, N, W, N], // 9 +]; /** * Tests {@link ITFWriter}. */ #[cfg(test)] mod ITFWriterTestCase { - use crate::{Writer, BarcodeFormat, common::BitMatrixTestCase}; + use crate::{common::BitMatrixTestCase, BarcodeFormat, Writer}; use super::ITFWriter; + #[test] + fn testEncode() { + doTest( + "00123456789012", + "0000010101010111000111000101110100010101110001110111010001010001110100011\ +100010101000101011100011101011101000111000101110100010101110001110100000", + ); + } - #[test] - fn testEncode() { - doTest("00123456789012", - "0000010101010111000111000101110100010101110001110111010001010001110100011\ -100010101000101011100011101011101000111000101110100010101110001110100000"); - } - - fn doTest( input:&str, expected:&str) { - let result = ITFWriter::default().encode(input, &BarcodeFormat::ITF, 0, 0).expect("encode"); - assert_eq!(expected, BitMatrixTestCase::matrix_to_string(&result)); - } - - //@Test(expected = IllegalArgumentException.class) - #[test] - #[should_panic] - fn testEncodeIllegalCharacters() { - ITFWriter::default().encode("00123456789abc", &BarcodeFormat::ITF, 0, 0).expect("should fail"); - } + fn doTest(input: &str, expected: &str) { + let result = ITFWriter::default() + .encode(input, &BarcodeFormat::ITF, 0, 0) + .expect("encode"); + assert_eq!(expected, BitMatrixTestCase::matrix_to_string(&result)); + } + //@Test(expected = IllegalArgumentException.class) + #[test] + #[should_panic] + fn testEncodeIllegalCharacters() { + ITFWriter::default() + .encode("00123456789abc", &BarcodeFormat::ITF, 0, 0) + .expect("should fail"); + } } diff --git a/src/oned/mod.rs b/src/oned/mod.rs index 1d7f1aa..1136963 100644 --- a/src/oned/mod.rs +++ b/src/oned/mod.rs @@ -63,4 +63,7 @@ mod code_93_writer; pub use code_93_writer::*; mod itf_writer; -pub use itf_writer::*; \ No newline at end of file +pub use itf_writer::*; + +mod code_128_writer; +pub use code_128_writer::*;