From ba05bf73eb00782ba0efee94aefd92679943d26a Mon Sep 17 00:00:00 2001 From: Henry Schimke Date: Thu, 8 Dec 2022 10:51:36 -0600 Subject: [PATCH] code 128 integration passes --- src/oned/Code128Reader.java | 562 ------------------------- src/oned/code_128_reader.rs | 584 ++++++++++++++++++++++++++ src/oned/code_93_reader.rs | 2 +- src/oned/mod.rs | 3 + src/oned/multi_format_one_d_reader.rs | 13 +- tests/Code128BlackBox1TestCase.java | 34 -- tests/Code128BlackBox2TestCase.java | 34 -- tests/Code128BlackBox3TestCase.java | 34 -- tests/code_128_blackbox.rs | 60 +++ tests/code_93_blackbox_1_testcase.rs | 17 +- 10 files changed, 663 insertions(+), 680 deletions(-) delete mode 100644 src/oned/Code128Reader.java create mode 100644 src/oned/code_128_reader.rs delete mode 100644 tests/Code128BlackBox1TestCase.java delete mode 100644 tests/Code128BlackBox2TestCase.java delete mode 100644 tests/Code128BlackBox3TestCase.java create mode 100644 tests/code_128_blackbox.rs diff --git a/src/oned/Code128Reader.java b/src/oned/Code128Reader.java deleted file mode 100644 index a5da0fb..0000000 --- a/src/oned/Code128Reader.java +++ /dev/null @@ -1,562 +0,0 @@ -/* - * 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.ChecksumException; -import com.google.zxing.DecodeHintType; -import com.google.zxing.FormatException; -import com.google.zxing.NotFoundException; -import com.google.zxing.RXingResult; -import com.google.zxing.RXingResultMetadataType; -import com.google.zxing.RXingResultPoint; -import com.google.zxing.common.BitArray; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -/** - *

Decodes Code 128 barcodes.

- * - * @author Sean Owen - */ -public final class Code128Reader extends OneDReader { - - static final int[][] CODE_PATTERNS = { - {2, 1, 2, 2, 2, 2}, // 0 - {2, 2, 2, 1, 2, 2}, - {2, 2, 2, 2, 2, 1}, - {1, 2, 1, 2, 2, 3}, - {1, 2, 1, 3, 2, 2}, - {1, 3, 1, 2, 2, 2}, // 5 - {1, 2, 2, 2, 1, 3}, - {1, 2, 2, 3, 1, 2}, - {1, 3, 2, 2, 1, 2}, - {2, 2, 1, 2, 1, 3}, - {2, 2, 1, 3, 1, 2}, // 10 - {2, 3, 1, 2, 1, 2}, - {1, 1, 2, 2, 3, 2}, - {1, 2, 2, 1, 3, 2}, - {1, 2, 2, 2, 3, 1}, - {1, 1, 3, 2, 2, 2}, // 15 - {1, 2, 3, 1, 2, 2}, - {1, 2, 3, 2, 2, 1}, - {2, 2, 3, 2, 1, 1}, - {2, 2, 1, 1, 3, 2}, - {2, 2, 1, 2, 3, 1}, // 20 - {2, 1, 3, 2, 1, 2}, - {2, 2, 3, 1, 1, 2}, - {3, 1, 2, 1, 3, 1}, - {3, 1, 1, 2, 2, 2}, - {3, 2, 1, 1, 2, 2}, // 25 - {3, 2, 1, 2, 2, 1}, - {3, 1, 2, 2, 1, 2}, - {3, 2, 2, 1, 1, 2}, - {3, 2, 2, 2, 1, 1}, - {2, 1, 2, 1, 2, 3}, // 30 - {2, 1, 2, 3, 2, 1}, - {2, 3, 2, 1, 2, 1}, - {1, 1, 1, 3, 2, 3}, - {1, 3, 1, 1, 2, 3}, - {1, 3, 1, 3, 2, 1}, // 35 - {1, 1, 2, 3, 1, 3}, - {1, 3, 2, 1, 1, 3}, - {1, 3, 2, 3, 1, 1}, - {2, 1, 1, 3, 1, 3}, - {2, 3, 1, 1, 1, 3}, // 40 - {2, 3, 1, 3, 1, 1}, - {1, 1, 2, 1, 3, 3}, - {1, 1, 2, 3, 3, 1}, - {1, 3, 2, 1, 3, 1}, - {1, 1, 3, 1, 2, 3}, // 45 - {1, 1, 3, 3, 2, 1}, - {1, 3, 3, 1, 2, 1}, - {3, 1, 3, 1, 2, 1}, - {2, 1, 1, 3, 3, 1}, - {2, 3, 1, 1, 3, 1}, // 50 - {2, 1, 3, 1, 1, 3}, - {2, 1, 3, 3, 1, 1}, - {2, 1, 3, 1, 3, 1}, - {3, 1, 1, 1, 2, 3}, - {3, 1, 1, 3, 2, 1}, // 55 - {3, 3, 1, 1, 2, 1}, - {3, 1, 2, 1, 1, 3}, - {3, 1, 2, 3, 1, 1}, - {3, 3, 2, 1, 1, 1}, - {3, 1, 4, 1, 1, 1}, // 60 - {2, 2, 1, 4, 1, 1}, - {4, 3, 1, 1, 1, 1}, - {1, 1, 1, 2, 2, 4}, - {1, 1, 1, 4, 2, 2}, - {1, 2, 1, 1, 2, 4}, // 65 - {1, 2, 1, 4, 2, 1}, - {1, 4, 1, 1, 2, 2}, - {1, 4, 1, 2, 2, 1}, - {1, 1, 2, 2, 1, 4}, - {1, 1, 2, 4, 1, 2}, // 70 - {1, 2, 2, 1, 1, 4}, - {1, 2, 2, 4, 1, 1}, - {1, 4, 2, 1, 1, 2}, - {1, 4, 2, 2, 1, 1}, - {2, 4, 1, 2, 1, 1}, // 75 - {2, 2, 1, 1, 1, 4}, - {4, 1, 3, 1, 1, 1}, - {2, 4, 1, 1, 1, 2}, - {1, 3, 4, 1, 1, 1}, - {1, 1, 1, 2, 4, 2}, // 80 - {1, 2, 1, 1, 4, 2}, - {1, 2, 1, 2, 4, 1}, - {1, 1, 4, 2, 1, 2}, - {1, 2, 4, 1, 1, 2}, - {1, 2, 4, 2, 1, 1}, // 85 - {4, 1, 1, 2, 1, 2}, - {4, 2, 1, 1, 1, 2}, - {4, 2, 1, 2, 1, 1}, - {2, 1, 2, 1, 4, 1}, - {2, 1, 4, 1, 2, 1}, // 90 - {4, 1, 2, 1, 2, 1}, - {1, 1, 1, 1, 4, 3}, - {1, 1, 1, 3, 4, 1}, - {1, 3, 1, 1, 4, 1}, - {1, 1, 4, 1, 1, 3}, // 95 - {1, 1, 4, 3, 1, 1}, - {4, 1, 1, 1, 1, 3}, - {4, 1, 1, 3, 1, 1}, - {1, 1, 3, 1, 4, 1}, - {1, 1, 4, 1, 3, 1}, // 100 - {3, 1, 1, 1, 4, 1}, - {4, 1, 1, 1, 3, 1}, - {2, 1, 1, 4, 1, 2}, - {2, 1, 1, 2, 1, 4}, - {2, 1, 1, 2, 3, 2}, // 105 - {2, 3, 3, 1, 1, 1, 2} - }; - - private static final float MAX_AVG_VARIANCE = 0.25f; - private static final float MAX_INDIVIDUAL_VARIANCE = 0.7f; - - private static final int CODE_SHIFT = 98; - - private static final int CODE_CODE_C = 99; - private static final int CODE_CODE_B = 100; - private static final int CODE_CODE_A = 101; - - private static final int CODE_FNC_1 = 102; - private static final int CODE_FNC_2 = 97; - private static final int CODE_FNC_3 = 96; - private static final int CODE_FNC_4_A = 101; - private static final int CODE_FNC_4_B = 100; - - 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_STOP = 106; - - private static int[] findStartPattern(BitArray row) throws NotFoundException { - int width = row.getSize(); - int rowOffset = row.getNextSet(0); - - int counterPosition = 0; - int[] counters = new int[6]; - int patternStart = rowOffset; - boolean isWhite = false; - int patternLength = counters.length; - - for (int i = rowOffset; i < width; i++) { - if (row.get(i) != isWhite) { - counters[counterPosition]++; - } else { - if (counterPosition == patternLength - 1) { - float bestVariance = MAX_AVG_VARIANCE; - int bestMatch = -1; - for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) { - float variance = patternMatchVariance(counters, CODE_PATTERNS[startCode], - MAX_INDIVIDUAL_VARIANCE); - if (variance < bestVariance) { - bestVariance = variance; - bestMatch = startCode; - } - } - // Look for whitespace before start pattern, >= 50% of width of start pattern - if (bestMatch >= 0 && - row.isRange(Math.max(0, patternStart - (i - patternStart) / 2), patternStart, false)) { - return new int[]{patternStart, i, bestMatch}; - } - patternStart += counters[0] + counters[1]; - System.arraycopy(counters, 2, counters, 0, counterPosition - 1); - counters[counterPosition - 1] = 0; - counters[counterPosition] = 0; - counterPosition--; - } else { - counterPosition++; - } - counters[counterPosition] = 1; - isWhite = !isWhite; - } - } - throw NotFoundException.getNotFoundInstance(); - } - - private static int decodeCode(BitArray row, int[] counters, int rowOffset) - throws NotFoundException { - recordPattern(row, rowOffset, counters); - float bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept - int bestMatch = -1; - for (int d = 0; d < CODE_PATTERNS.length; d++) { - int[] pattern = CODE_PATTERNS[d]; - float variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE); - if (variance < bestVariance) { - bestVariance = variance; - bestMatch = d; - } - } - // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6. - if (bestMatch >= 0) { - return bestMatch; - } else { - throw NotFoundException.getNotFoundInstance(); - } - } - - @Override - public RXingResult decodeRow(int rowNumber, BitArray row, Map hints) - throws NotFoundException, FormatException, ChecksumException { - - boolean convertFNC1 = hints != null && hints.containsKey(DecodeHintType.ASSUME_GS1); - - int symbologyModifier = 0; - - int[] startPatternInfo = findStartPattern(row); - int startCode = startPatternInfo[2]; - - List rawCodes = new ArrayList<>(20); - rawCodes.add((byte) startCode); - - int codeSet; - switch (startCode) { - case CODE_START_A: - codeSet = CODE_CODE_A; - break; - case CODE_START_B: - codeSet = CODE_CODE_B; - break; - case CODE_START_C: - codeSet = CODE_CODE_C; - break; - default: - throw FormatException.getFormatInstance(); - } - - boolean done = false; - boolean isNextShifted = false; - - StringBuilder result = new StringBuilder(20); - - int lastStart = startPatternInfo[0]; - int nextStart = startPatternInfo[1]; - int[] counters = new int[6]; - - int lastCode = 0; - int code = 0; - int checksumTotal = startCode; - int multiplier = 0; - boolean lastCharacterWasPrintable = true; - boolean upperMode = false; - boolean shiftUpperMode = false; - - while (!done) { - - boolean unshift = isNextShifted; - isNextShifted = false; - - // Save off last code - lastCode = code; - - // Decode another code from image - code = decodeCode(row, counters, nextStart); - - rawCodes.add((byte) code); - - // Remember whether the last code was printable or not (excluding CODE_STOP) - if (code != CODE_STOP) { - lastCharacterWasPrintable = true; - } - - // Add to checksum computation (if not CODE_STOP of course) - if (code != CODE_STOP) { - multiplier++; - checksumTotal += multiplier * code; - } - - // Advance to where the next code will to start - lastStart = nextStart; - for (int counter : counters) { - nextStart += counter; - } - - // Take care of illegal start codes - switch (code) { - case CODE_START_A: - case CODE_START_B: - case CODE_START_C: - throw FormatException.getFormatInstance(); - } - - switch (codeSet) { - - case CODE_CODE_A: - if (code < 64) { - if (shiftUpperMode == upperMode) { - result.append((char) (' ' + code)); - } else { - result.append((char) (' ' + code + 128)); - } - shiftUpperMode = false; - } else if (code < 96) { - if (shiftUpperMode == upperMode) { - result.append((char) (code - 64)); - } else { - result.append((char) (code + 64)); - } - shiftUpperMode = false; - } else { - // Don't let CODE_STOP, which always appears, affect whether whether we think the last - // code was printable or not. - if (code != CODE_STOP) { - lastCharacterWasPrintable = false; - } - switch (code) { - case CODE_FNC_1: - if (result.length() == 0) { // FNC1 at first or second character determines the symbology - symbologyModifier = 1; - } else if (result.length() == 1) { - symbologyModifier = 2; - } - if (convertFNC1) { - if (result.length() == 0) { - // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code - // is FNC1 then this is GS1-128. We add the symbology identifier. - result.append("]C1"); - } else { - // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) - result.append((char) 29); - } - } - break; - case CODE_FNC_2: - symbologyModifier = 4; - break; - case CODE_FNC_3: - // do nothing? - break; - case CODE_FNC_4_A: - if (!upperMode && shiftUpperMode) { - upperMode = true; - shiftUpperMode = false; - } else if (upperMode && shiftUpperMode) { - upperMode = false; - shiftUpperMode = false; - } else { - shiftUpperMode = true; - } - break; - case CODE_SHIFT: - isNextShifted = true; - codeSet = CODE_CODE_B; - break; - case CODE_CODE_B: - codeSet = CODE_CODE_B; - break; - case CODE_CODE_C: - codeSet = CODE_CODE_C; - break; - case CODE_STOP: - done = true; - break; - } - } - break; - case CODE_CODE_B: - if (code < 96) { - if (shiftUpperMode == upperMode) { - result.append((char) (' ' + code)); - } else { - result.append((char) (' ' + code + 128)); - } - shiftUpperMode = false; - } else { - if (code != CODE_STOP) { - lastCharacterWasPrintable = false; - } - switch (code) { - case CODE_FNC_1: - if (result.length() == 0) { // FNC1 at first or second character determines the symbology - symbologyModifier = 1; - } else if (result.length() == 1) { - symbologyModifier = 2; - } - if (convertFNC1) { - if (result.length() == 0) { - // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code - // is FNC1 then this is GS1-128. We add the symbology identifier. - result.append("]C1"); - } else { - // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) - result.append((char) 29); - } - } - break; - case CODE_FNC_2: - symbologyModifier = 4; - break; - case CODE_FNC_3: - // do nothing? - break; - case CODE_FNC_4_B: - if (!upperMode && shiftUpperMode) { - upperMode = true; - shiftUpperMode = false; - } else if (upperMode && shiftUpperMode) { - upperMode = false; - shiftUpperMode = false; - } else { - shiftUpperMode = true; - } - break; - case CODE_SHIFT: - isNextShifted = true; - codeSet = CODE_CODE_A; - break; - case CODE_CODE_A: - codeSet = CODE_CODE_A; - break; - case CODE_CODE_C: - codeSet = CODE_CODE_C; - break; - case CODE_STOP: - done = true; - break; - } - } - break; - case CODE_CODE_C: - if (code < 100) { - if (code < 10) { - result.append('0'); - } - result.append(code); - } else { - if (code != CODE_STOP) { - lastCharacterWasPrintable = false; - } - switch (code) { - case CODE_FNC_1: - if (result.length() == 0) { // FNC1 at first or second character determines the symbology - symbologyModifier = 1; - } else if (result.length() == 1) { - symbologyModifier = 2; - } - if (convertFNC1) { - if (result.length() == 0) { - // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code - // is FNC1 then this is GS1-128. We add the symbology identifier. - result.append("]C1"); - } else { - // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) - result.append((char) 29); - } - } - break; - case CODE_CODE_A: - codeSet = CODE_CODE_A; - break; - case CODE_CODE_B: - codeSet = CODE_CODE_B; - break; - case CODE_STOP: - done = true; - break; - } - } - break; - } - - // Unshift back to another code set if we were shifted - if (unshift) { - codeSet = codeSet == CODE_CODE_A ? CODE_CODE_B : CODE_CODE_A; - } - - } - - int lastPatternSize = nextStart - lastStart; - - // Check for ample whitespace following pattern, but, to do this we first need to remember that - // we fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left - // to read off. Would be slightly better to properly read. Here we just skip it: - nextStart = row.getNextUnset(nextStart); - if (!row.isRange(nextStart, - Math.min(row.getSize(), nextStart + (nextStart - lastStart) / 2), - false)) { - throw NotFoundException.getNotFoundInstance(); - } - - // Pull out from sum the value of the penultimate check code - checksumTotal -= multiplier * lastCode; - // lastCode is the checksum then: - if (checksumTotal % 103 != lastCode) { - throw ChecksumException.getChecksumInstance(); - } - - // Need to pull out the check digits from string - int resultLength = result.length(); - if (resultLength == 0) { - // false positive - throw NotFoundException.getNotFoundInstance(); - } - - // Only bother if the result had at least one character, and if the checksum digit happened to - // be a printable character. If it was just interpreted as a control code, nothing to remove. - if (resultLength > 0 && lastCharacterWasPrintable) { - if (codeSet == CODE_CODE_C) { - result.delete(resultLength - 2, resultLength); - } else { - result.delete(resultLength - 1, resultLength); - } - } - - float left = (startPatternInfo[1] + startPatternInfo[0]) / 2.0f; - float right = lastStart + lastPatternSize / 2.0f; - - int rawCodesSize = rawCodes.size(); - byte[] rawBytes = new byte[rawCodesSize]; - for (int i = 0; i < rawCodesSize; i++) { - rawBytes[i] = rawCodes.get(i); - } - RXingResult resultObject = new RXingResult( - result.toString(), - rawBytes, - new RXingResultPoint[]{ - new RXingResultPoint(left, rowNumber), - new RXingResultPoint(right, rowNumber)}, - BarcodeFormat.CODE_128); - resultObject.putMetadata(RXingResultMetadataType.SYMBOLOGY_IDENTIFIER, "]C" + symbologyModifier); - return resultObject; - - } - -} diff --git a/src/oned/code_128_reader.rs b/src/oned/code_128_reader.rs new file mode 100644 index 0000000..b5177af --- /dev/null +++ b/src/oned/code_128_reader.rs @@ -0,0 +1,584 @@ +/* + * 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 one_d_reader_derive::OneDReader; + +use crate::{common::BitArray, BarcodeFormat, Exceptions, RXingResult}; + +use super::OneDReader; + +/** + *

Decodes Code 128 barcodes.

+ * + * @author Sean Owen + */ +#[derive(OneDReader)] +pub struct Code128Reader; + +impl OneDReader for Code128Reader { + fn decodeRow( + &mut self, + rowNumber: u32, + row: &crate::common::BitArray, + hints: &crate::DecodingHintDictionary, + ) -> Result { + let convertFNC1 = hints.contains_key(&DecodeHintType::ASSUME_GS1); + + let mut symbologyModifier = 0; + + let startPatternInfo = Self::findStartPattern(row)?; + let startCode = startPatternInfo[2] as u8; + + let mut rawCodes: Vec = Vec::with_capacity(20); //new ArrayList<>(20); + rawCodes.push(startCode); + + let mut codeSet = match startCode { + // switch (startCode) { + CODE_START_A => CODE_CODE_A, + CODE_START_B => CODE_CODE_B, + CODE_START_C => CODE_CODE_C, + _ => return Err(Exceptions::FormatException("".to_owned())), + }; + + let mut done = false; + let mut isNextShifted = false; + + let mut result = String::with_capacity(20); //new StringBuilder(20); + + let mut lastStart = startPatternInfo[0]; + let mut nextStart = startPatternInfo[1]; + let mut counters = [0_u32; 6]; //new int[6]; + + let mut lastCode = 0; + let mut code = 0; + let mut checksumTotal = startCode as usize; + let mut multiplier = 0; + let mut lastCharacterWasPrintable = true; + let mut upperMode = false; + let mut shiftUpperMode = false; + + while !done { + let unshift = isNextShifted; + isNextShifted = false; + + // Save off last code + lastCode = code; + + // Decode another code from image + code = Self::decodeCode(row, &mut counters, nextStart)?; + + rawCodes.push(code); + + // Remember whether the last code was printable or not (excluding CODE_STOP) + if code != CODE_STOP { + lastCharacterWasPrintable = true; + } + + // Add to checksum computation (if not CODE_STOP of course) + if code != CODE_STOP { + multiplier += 1; + checksumTotal += multiplier as usize * code as usize; + } + + // Advance to where the next code will to start + lastStart = nextStart; + + nextStart += counters.iter().sum::() as usize; + + // for counter in counters { + // // for (int counter : counters) { + // nextStart += counter; + // } + + // Take care of illegal start codes + match code { + CODE_START_A | CODE_START_B | CODE_START_C => { + return Err(Exceptions::FormatException("".to_owned())) + } + _ => {} + } + + match codeSet { + CODE_CODE_A => { + if code < 64 { + if shiftUpperMode == upperMode { + result.push((b' ' + code) as char); + } else { + result.push((b' ' + code + 128) as char); + } + shiftUpperMode = false; + } else if code < 96 { + if shiftUpperMode == upperMode { + result.push((code - 64) as char); + } else { + result.push((code + 64) as char); + } + shiftUpperMode = false; + } else { + // Don't let CODE_STOP, which always appears, affect whether whether we think the last + // code was printable or not. + if code != CODE_STOP { + lastCharacterWasPrintable = false; + } + match code { + CODE_FNC_1 => { + if result.chars().count() == 0 { + // FNC1 at first or second character determines the symbology + symbologyModifier = 1; + } else if result.chars().count() == 1 { + symbologyModifier = 2; + } + if convertFNC1 { + if result.chars().count() == 0 { + // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code + // is FNC1 then this is GS1-128. We add the symbology identifier. + result.push_str("]C1"); + } else { + // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) + result.push(29 as char); + } + } + } + CODE_FNC_2 => symbologyModifier = 4, + CODE_FNC_3 => + // do nothing? + {} + CODE_FNC_4_A => { + if !upperMode && shiftUpperMode { + upperMode = true; + shiftUpperMode = false; + } else if upperMode && shiftUpperMode { + upperMode = false; + shiftUpperMode = false; + } else { + shiftUpperMode = true; + } + } + CODE_SHIFT => { + isNextShifted = true; + codeSet = CODE_CODE_B; + } + CODE_CODE_B => codeSet = CODE_CODE_B, + CODE_CODE_C => codeSet = CODE_CODE_C, + CODE_STOP => done = true, + _ => {} + } + } + } + CODE_CODE_B => { + if code < 96 { + if shiftUpperMode == upperMode { + result.push((b' ' + code) as char); + } else { + result.push((b' ' + code + 128) as char); + } + shiftUpperMode = false; + } else { + if code != CODE_STOP { + lastCharacterWasPrintable = false; + } + match code { + CODE_FNC_1 => { + if result.chars().count() == 0 { + // FNC1 at first or second character determines the symbology + symbologyModifier = 1; + } else if result.chars().count() == 1 { + symbologyModifier = 2; + } + if convertFNC1 { + if result.chars().count() == 0 { + // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code + // is FNC1 then this is GS1-128. We add the symbology identifier. + result.push_str("]C1"); + } else { + // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) + result.push(29 as char); + } + } + } + CODE_FNC_2 => symbologyModifier = 4, + CODE_FNC_3 => + // do nothing? + {} + CODE_FNC_4_B => { + if !upperMode && shiftUpperMode { + upperMode = true; + shiftUpperMode = false; + } else if upperMode && shiftUpperMode { + upperMode = false; + shiftUpperMode = false; + } else { + shiftUpperMode = true; + } + } + CODE_SHIFT => { + isNextShifted = true; + codeSet = CODE_CODE_A; + } + CODE_CODE_A => codeSet = CODE_CODE_A, + + CODE_CODE_C => codeSet = CODE_CODE_C, + + CODE_STOP => done = true, + + _ => {} + } + } + } + CODE_CODE_C => { + if code < 100 { + if code < 10 { + result.push('0'); + } + result.push_str(&code.to_string()); + } else { + if code != CODE_STOP { + lastCharacterWasPrintable = false; + } + match code { + CODE_FNC_1 => { + if result.chars().count() == 0 { + // FNC1 at first or second character determines the symbology + symbologyModifier = 1; + } else if result.chars().count() == 1 { + symbologyModifier = 2; + } + if convertFNC1 { + if result.chars().count() == 0 { + // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code + // is FNC1 then this is GS1-128. We add the symbology identifier. + result.push_str("]C1"); + } else { + // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS) + result.push(29 as char); + } + } + } + CODE_CODE_A => codeSet = CODE_CODE_A, + + CODE_CODE_B => codeSet = CODE_CODE_B, + + CODE_STOP => done = true, + + _ => {} + } + } + } + _ => {} + } + + // Unshift back to another code set if we were shifted + if unshift { + codeSet = if codeSet == CODE_CODE_A { + CODE_CODE_B + } else { + CODE_CODE_A + }; + } + } + + let lastPatternSize = nextStart - lastStart; + + // Check for ample whitespace following pattern, but, to do this we first need to remember that + // we fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left + // to read off. Would be slightly better to properly read. Here we just skip it: + nextStart = row.getNextUnset(nextStart); + if !row.isRange( + nextStart, + row.getSize().min(nextStart + (nextStart - lastStart) / 2), + false, + )? { + return Err(Exceptions::NotFoundException("".to_owned())); + } + + // Pull out from sum the value of the penultimate check code + checksumTotal -= multiplier as usize * lastCode as usize; + // lastCode is the checksum then: + if (checksumTotal % 103) as u8 != lastCode { + return Err(Exceptions::ChecksumException("".to_owned())); + } + + // Need to pull out the check digits from string + let resultLength = result.chars().count(); + if resultLength == 0 { + // false positive + return Err(Exceptions::NotFoundException("".to_owned())); + } + + // Only bother if the result had at least one character, and if the checksum digit happened to + // be a printable character. If it was just interpreted as a control code, nothing to remove. + if resultLength > 0 && lastCharacterWasPrintable { + let len_trim = if codeSet == CODE_CODE_C { + resultLength - 2 + } else { + resultLength - 1 + }; + let new_str = result.chars().take(len_trim).collect(); + result = new_str; + // if codeSet == CODE_CODE_C { + // result.delete(resultLength - 2, resultLength); + // } else { + // result.delete(resultLength - 1, resultLength); + // } + } + + let left: f32 = (startPatternInfo[1] + startPatternInfo[0]) as f32 / 2.0; + let right: f32 = lastStart as f32 + lastPatternSize as f32 / 2.0; + + let rawCodesSize = rawCodes.len(); + let mut rawBytes = vec![0u8; rawCodesSize]; + for i in 0..rawCodesSize { + // for (int i = 0; i < rawCodesSize; i++) { + rawBytes[i] = *rawCodes.get(i).unwrap(); + } + let mut resultObject = RXingResult::new( + &result, + rawBytes, + vec![ + RXingResultPoint::new(left, rowNumber as f32), + RXingResultPoint::new(right, rowNumber as f32), + ], + BarcodeFormat::CODE_128, + ); + + resultObject.putMetadata( + RXingResultMetadataType::SYMBOLOGY_IDENTIFIER, + RXingResultMetadataValue::SymbologyIdentifier(format!("]C{}", symbologyModifier)), + ); + + Ok(resultObject) + } +} +impl Code128Reader { + fn findStartPattern(row: &BitArray) -> Result<[usize; 3], Exceptions> { + let width = row.getSize(); + let rowOffset = row.getNextSet(0); + + let mut counterPosition = 0; + let mut counters = [0_u32; 6]; + let mut patternStart = rowOffset; + let mut isWhite = false; + let patternLength = counters.len(); + + for i in rowOffset..width { + // for (int i = rowOffset; i < width; i++) { + if row.get(i) != isWhite { + counters[counterPosition] += 1; + } else { + if counterPosition == patternLength - 1 { + let mut bestVariance = MAX_AVG_VARIANCE; + let mut bestMatch = -1_isize; + for startCode in CODE_START_A..=CODE_START_C { + // for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) { + let variance = Self::patternMatchVariance( + &counters, + &CODE_PATTERNS[startCode as usize], + MAX_INDIVIDUAL_VARIANCE, + ); + if variance < bestVariance { + bestVariance = variance; + bestMatch = startCode as isize; + } + } + // Look for whitespace before start pattern, >= 50% of width of start pattern + if bestMatch >= 0 + && row.isRange( + 0.max(patternStart as isize - (i as isize - patternStart as isize) / 2) + as usize, + patternStart, + false, + )? + { + return Ok([patternStart, i, bestMatch as usize]); + } + patternStart += (counters[0] + counters[1]) as usize; + let slc = &counters[2..(counterPosition - 1 + 2)].to_vec(); + counters[0..(counterPosition - 1)].copy_from_slice(slc); + // System.arraycopy(counters, 2, counters, 0, counterPosition - 1); + counters[counterPosition - 1] = 0; + counters[counterPosition] = 0; + counterPosition -= 1; + } else { + counterPosition += 1; + } + counters[counterPosition] = 1; + isWhite = !isWhite; + } + } + + Err(Exceptions::NotFoundException("".to_owned())) + } + + fn decodeCode( + row: &BitArray, + counters: &mut [u32; 6], + rowOffset: usize, + ) -> Result { + Self::recordPattern(row, rowOffset, counters)?; + let mut bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept + let mut bestMatch = -1_isize; + for d in 0..CODE_PATTERNS.len() { + // for (int d = 0; d < CODE_PATTERNS.len(); d++) { + let pattern = &CODE_PATTERNS[d]; + let variance = Self::patternMatchVariance(counters, &pattern, MAX_INDIVIDUAL_VARIANCE); + if variance < bestVariance { + bestVariance = variance; + bestMatch = d as isize; + } + } + // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6. + if bestMatch >= 0 { + Ok(bestMatch as u8) + } else { + Err(Exceptions::NotFoundException("".to_owned())) + } + } +} + +use lazy_static::lazy_static; + +lazy_static! { + +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], + vec![1, 2, 1, 2, 2, 3], + vec![1, 2, 1, 3, 2, 2], + vec![1, 3, 1, 2, 2, 2], // 5 + vec![1, 2, 2, 2, 1, 3], + vec![1, 2, 2, 3, 1, 2], + vec![1, 3, 2, 2, 1, 2], + vec![2, 2, 1, 2, 1, 3], + vec![2, 2, 1, 3, 1, 2], // 10 + vec![2, 3, 1, 2, 1, 2], + vec![1, 1, 2, 2, 3, 2], + vec![1, 2, 2, 1, 3, 2], + vec![1, 2, 2, 2, 3, 1], + vec![1, 1, 3, 2, 2, 2], // 15 + vec![1, 2, 3, 1, 2, 2], + vec![1, 2, 3, 2, 2, 1], + vec![2, 2, 3, 2, 1, 1], + vec![2, 2, 1, 1, 3, 2], + vec![2, 2, 1, 2, 3, 1], // 20 + vec![2, 1, 3, 2, 1, 2], + vec![2, 2, 3, 1, 1, 2], + vec![3, 1, 2, 1, 3, 1], + vec![3, 1, 1, 2, 2, 2], + vec![3, 2, 1, 1, 2, 2], // 25 + vec![3, 2, 1, 2, 2, 1], + vec![3, 1, 2, 2, 1, 2], + vec![3, 2, 2, 1, 1, 2], + vec![3, 2, 2, 2, 1, 1], + vec![2, 1, 2, 1, 2, 3], // 30 + vec![2, 1, 2, 3, 2, 1], + vec![2, 3, 2, 1, 2, 1], + vec![1, 1, 1, 3, 2, 3], + vec![1, 3, 1, 1, 2, 3], + vec![1, 3, 1, 3, 2, 1], // 35 + vec![1, 1, 2, 3, 1, 3], + vec![1, 3, 2, 1, 1, 3], + vec![1, 3, 2, 3, 1, 1], + vec![2, 1, 1, 3, 1, 3], + vec![2, 3, 1, 1, 1, 3], // 40 + vec![2, 3, 1, 3, 1, 1], + vec![1, 1, 2, 1, 3, 3], + vec![1, 1, 2, 3, 3, 1], + vec![1, 3, 2, 1, 3, 1], + vec![1, 1, 3, 1, 2, 3], // 45 + vec![1, 1, 3, 3, 2, 1], + vec![1, 3, 3, 1, 2, 1], + vec![3, 1, 3, 1, 2, 1], + vec![2, 1, 1, 3, 3, 1], + vec![2, 3, 1, 1, 3, 1], // 50 + vec![2, 1, 3, 1, 1, 3], + vec![2, 1, 3, 3, 1, 1], + vec![2, 1, 3, 1, 3, 1], + vec![3, 1, 1, 1, 2, 3], + vec![3, 1, 1, 3, 2, 1], // 55 + vec![3, 3, 1, 1, 2, 1], + vec![3, 1, 2, 1, 1, 3], + vec![3, 1, 2, 3, 1, 1], + vec![3, 3, 2, 1, 1, 1], + vec![3, 1, 4, 1, 1, 1], // 60 + vec![2, 2, 1, 4, 1, 1], + vec![4, 3, 1, 1, 1, 1], + vec![1, 1, 1, 2, 2, 4], + vec![1, 1, 1, 4, 2, 2], + vec![1, 2, 1, 1, 2, 4], // 65 + vec![1, 2, 1, 4, 2, 1], + vec![1, 4, 1, 1, 2, 2], + vec![1, 4, 1, 2, 2, 1], + vec![1, 1, 2, 2, 1, 4], + vec![1, 1, 2, 4, 1, 2], // 70 + vec![1, 2, 2, 1, 1, 4], + vec![1, 2, 2, 4, 1, 1], + vec![1, 4, 2, 1, 1, 2], + vec![1, 4, 2, 2, 1, 1], + vec![2, 4, 1, 2, 1, 1], // 75 + vec![2, 2, 1, 1, 1, 4], + vec![4, 1, 3, 1, 1, 1], + vec![2, 4, 1, 1, 1, 2], + vec![1, 3, 4, 1, 1, 1], + vec![1, 1, 1, 2, 4, 2], // 80 + vec![1, 2, 1, 1, 4, 2], + vec![1, 2, 1, 2, 4, 1], + vec![1, 1, 4, 2, 1, 2], + vec![1, 2, 4, 1, 1, 2], + vec![1, 2, 4, 2, 1, 1], // 85 + vec![4, 1, 1, 2, 1, 2], + vec![4, 2, 1, 1, 1, 2], + vec![4, 2, 1, 2, 1, 1], + vec![2, 1, 2, 1, 4, 1], + vec![2, 1, 4, 1, 2, 1], // 90 + vec![4, 1, 2, 1, 2, 1], + vec![1, 1, 1, 1, 4, 3], + vec![1, 1, 1, 3, 4, 1], + vec![1, 3, 1, 1, 4, 1], + vec![1, 1, 4, 1, 1, 3], // 95 + vec![1, 1, 4, 3, 1, 1], + vec![4, 1, 1, 1, 1, 3], + vec![4, 1, 1, 3, 1, 1], + vec![1, 1, 3, 1, 4, 1], + vec![1, 1, 4, 1, 3, 1], // 100 + vec![3, 1, 1, 1, 4, 1], + vec![4, 1, 1, 1, 3, 1], + vec![2, 1, 1, 4, 1, 2], + vec![2, 1, 1, 2, 1, 4], + vec![2, 1, 1, 2, 3, 2], // 105 + vec![2, 3, 3, 1, 1, 1, 2], +]; +} + +const MAX_AVG_VARIANCE: f32 = 0.25; +const MAX_INDIVIDUAL_VARIANCE: f32 = 0.7; + +const CODE_SHIFT: u8 = 98; + +const CODE_CODE_C: u8 = 99; +const CODE_CODE_B: u8 = 100; +const CODE_CODE_A: u8 = 101; + +const CODE_FNC_1: u8 = 102; +const CODE_FNC_2: u8 = 97; +const CODE_FNC_3: u8 = 96; +const CODE_FNC_4_A: u8 = 101; +const CODE_FNC_4_B: u8 = 100; + +const CODE_START_A: u8 = 103; +const CODE_START_B: u8 = 104; +const CODE_START_C: u8 = 105; +const CODE_STOP: u8 = 106; diff --git a/src/oned/code_93_reader.rs b/src/oned/code_93_reader.rs index 6c258e3..4b76eeb 100644 --- a/src/oned/code_93_reader.rs +++ b/src/oned/code_93_reader.rs @@ -79,7 +79,7 @@ impl OneDReader for Code93Reader { // lastPatternSize += counter; // } - let lastPatternSize : u32 = theCounters.iter().sum(); + let lastPatternSize: u32 = theCounters.iter().sum(); // Should be at least one more black module if nextStart == end || !row.get(nextStart) { diff --git a/src/oned/mod.rs b/src/oned/mod.rs index 484dcaf..c0ed462 100644 --- a/src/oned/mod.rs +++ b/src/oned/mod.rs @@ -17,3 +17,6 @@ pub use multi_format_one_d_reader::*; mod code_93_reader; pub use code_93_reader::*; + +mod code_128_reader; +pub use code_128_reader::*; diff --git a/src/oned/multi_format_one_d_reader.rs b/src/oned/multi_format_one_d_reader.rs index 35f3313..fce26c0 100644 --- a/src/oned/multi_format_one_d_reader.rs +++ b/src/oned/multi_format_one_d_reader.rs @@ -15,6 +15,7 @@ */ use super::CodaBarReader; +use super::Code128Reader; use super::Code39Reader; use super::Code93Reader; use super::OneDReader; @@ -74,11 +75,11 @@ impl MultiFormatOneDReader { ))); } if possibleFormats.contains(&BarcodeFormat::CODE_93) { - readers.push(Box::new( Code93Reader::new())); + readers.push(Box::new(Code93Reader::new())); + } + if possibleFormats.contains(&BarcodeFormat::CODE_128) { + readers.push(Box::new(Code128Reader {})); } - // if (possibleFormats.contains(&BarcodeFormat::CODE_128)) { - // readers.add(new Code128Reader()); - // } // if (possibleFormats.contains(&BarcodeFormat::ITF)) { // readers.add(new ITFReader()); // } @@ -96,8 +97,8 @@ impl MultiFormatOneDReader { // readers.push(new MultiFormatUPCEANReader(hints)); readers.push(Box::new(Code39Reader::new())); readers.push(Box::new(CodaBarReader::new())); - readers.push(Box::new( Code93Reader::new())); - // readers.push(new Code128Reader()); + readers.push(Box::new(Code93Reader::new())); + readers.push(Box::new(Code128Reader {})); // readers.push(new ITFReader()); // readers.push(new RSS14Reader()); // readers.push(new RSSExpandedReader()); diff --git a/tests/Code128BlackBox1TestCase.java b/tests/Code128BlackBox1TestCase.java deleted file mode 100644 index 1a49f0e..0000000 --- a/tests/Code128BlackBox1TestCase.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; - -/** - * @author Sean Owen - */ -public final class Code128BlackBox1TestCase extends AbstractBlackBoxTestCase { - - public Code128BlackBox1TestCase() { - super("src/test/resources/blackbox/code128-1", new MultiFormatReader(), BarcodeFormat.CODE_128); - addTest(6, 6, 0.0f); - addTest(6, 6, 180.0f); - } - -} \ No newline at end of file diff --git a/tests/Code128BlackBox2TestCase.java b/tests/Code128BlackBox2TestCase.java deleted file mode 100644 index 3bffceb..0000000 --- a/tests/Code128BlackBox2TestCase.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; - -/** - * @author dswitkin@google.com (Daniel Switkin) - */ -public final class Code128BlackBox2TestCase extends AbstractBlackBoxTestCase { - - public Code128BlackBox2TestCase() { - super("src/test/resources/blackbox/code128-2", new MultiFormatReader(), BarcodeFormat.CODE_128); - addTest(36, 39, 0.0f); - addTest(36, 39, 180.0f); - } - -} \ No newline at end of file diff --git a/tests/Code128BlackBox3TestCase.java b/tests/Code128BlackBox3TestCase.java deleted file mode 100644 index fb2193d..0000000 --- a/tests/Code128BlackBox3TestCase.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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. - */ - -package com.google.zxing.oned; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.MultiFormatReader; -import com.google.zxing.common.AbstractBlackBoxTestCase; - -/** - * @author Sean Owen - */ -public final class Code128BlackBox3TestCase extends AbstractBlackBoxTestCase { - - public Code128BlackBox3TestCase() { - super("src/test/resources/blackbox/code128-3", new MultiFormatReader(), BarcodeFormat.CODE_128); - addTest(2, 2, 0.0f); - addTest(2, 2, 180.0f); - } - -} \ No newline at end of file diff --git a/tests/code_128_blackbox.rs b/tests/code_128_blackbox.rs new file mode 100644 index 0000000..a3f7c48 --- /dev/null +++ b/tests/code_128_blackbox.rs @@ -0,0 +1,60 @@ +use rxing::{ + oned::{CodaBarReader, Code39Reader, OneDReader}, + BarcodeFormat, MultiFormatReader, +}; + +mod common; + +/** + * @author Sean Owen + */ +#[test] +fn code128_black_box1_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/code128-1", + MultiFormatReader::default(), + BarcodeFormat::CODE_128, + ); + + // super("src/test/resources/blackbox/code128-1", new MultiFormatReader(), BarcodeFormat.CODE_128); + tester.add_test(6, 6, 0.0); + tester.add_test(6, 6, 180.0); + + tester.test_black_box() +} + +/** + * @author dswitkin@google.com (Daniel Switkin) + */ +#[test] +fn code128_black_box2_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/code128-2", + MultiFormatReader::default(), + BarcodeFormat::CODE_128, + ); + + // super("src/test/resources/blackbox/code128-2", new MultiFormatReader(), BarcodeFormat.CODE_128); + tester.add_test(36, 39, 0.0); + tester.add_test(36, 39, 180.0); + + tester.test_black_box() +} + +/** + * @author Sean Owen + */ +#[test] +fn code128_black_box3_test_case() { + let mut tester = common::AbstractBlackBoxTestCase::new( + "test_resources/blackbox/code128-3", + MultiFormatReader::default(), + BarcodeFormat::CODE_128, + ); + + // super("src/test/resources/blackbox/code128-3", new MultiFormatReader(), BarcodeFormat.CODE_128); + tester.add_test(2, 2, 0.0); + tester.add_test(2, 2, 180.0); + + tester.test_black_box() +} diff --git a/tests/code_93_blackbox_1_testcase.rs b/tests/code_93_blackbox_1_testcase.rs index 3b7bdd0..b8b2d1d 100644 --- a/tests/code_93_blackbox_1_testcase.rs +++ b/tests/code_93_blackbox_1_testcase.rs @@ -15,8 +15,8 @@ */ use rxing::{ - oned::{CodaBarReader, Code39Reader, OneDReader}, - BarcodeFormat, MultiFormatReader, + oned::{CodaBarReader, Code39Reader, OneDReader}, + BarcodeFormat, MultiFormatReader, }; mod common; @@ -25,16 +25,15 @@ mod common; * @author Sean Owen */ #[test] - fn code93_black_box1_test_case() { +fn code93_black_box1_test_case() { let mut tester = common::AbstractBlackBoxTestCase::new( - "test_resources/blackbox/code93-1", - MultiFormatReader::default(), - BarcodeFormat::CODE_93, - ); + "test_resources/blackbox/code93-1", + MultiFormatReader::default(), + BarcodeFormat::CODE_93, + ); // super("src/test/resources/blackbox/code93-1", new MultiFormatReader(), BarcodeFormat.CODE_93); tester.add_test(3, 3, 0.0); tester.add_test(3, 3, 180.0); tester.test_black_box() - } - +}