mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 04:12:34 +00:00
coda_bar reader passes
This commit is contained in:
@@ -22,6 +22,7 @@ image = {version = "0.24.3", optional = true}
|
|||||||
imageproc = {version = "0.23.0", optional = true}
|
imageproc = {version = "0.23.0", optional = true}
|
||||||
unicode-segmentation = "1.10.0"
|
unicode-segmentation = "1.10.0"
|
||||||
codepage-437 = "0.1.0"
|
codepage-437 = "0.1.0"
|
||||||
|
one-d-reader-derive = { path = "one-d-reader-derive" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
java-properties = "1.4.1"
|
java-properties = "1.4.1"
|
||||||
|
|||||||
13
one-d-reader-derive/Cargo.toml
Normal file
13
one-d-reader-derive/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "one-d-reader-derive"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "1.0"
|
||||||
|
quote = "1.0"
|
||||||
84
one-d-reader-derive/src/lib.rs
Normal file
84
one-d-reader-derive/src/lib.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn;
|
||||||
|
|
||||||
|
#[proc_macro_derive(OneDReader)]
|
||||||
|
pub fn one_d_reader_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_reader_macro(&ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn impl_one_d_reader_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
|
let name = &ast.ident;
|
||||||
|
let gen = quote! {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use crate::result_point::ResultPoint;
|
||||||
|
use crate::DecodeHintType;
|
||||||
|
use crate::DecodingHintDictionary;
|
||||||
|
use crate::RXingResultMetadataType;
|
||||||
|
use crate::RXingResultMetadataValue;
|
||||||
|
use crate::RXingResultPoint;
|
||||||
|
use crate::Reader;
|
||||||
|
|
||||||
|
impl Reader for #name {
|
||||||
|
fn decode(&mut self, image: &crate::BinaryBitmap) -> Result<crate::RXingResult, Exceptions> {
|
||||||
|
self.decode_with_hints(image, &HashMap::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that we don't try rotation without the try harder flag, even if rotation was supported.
|
||||||
|
fn decode_with_hints(
|
||||||
|
&mut self,
|
||||||
|
image: &crate::BinaryBitmap,
|
||||||
|
hints: &DecodingHintDictionary,
|
||||||
|
) -> Result<crate::RXingResult, Exceptions> {
|
||||||
|
if let Ok(res) = self.doDecode(image, hints) {
|
||||||
|
Ok(res)
|
||||||
|
}else {
|
||||||
|
let tryHarder = hints.contains_key(&DecodeHintType::TRY_HARDER);
|
||||||
|
if tryHarder && image.isRotateSupported() {
|
||||||
|
let rotatedImage = image.rotateCounterClockwise();
|
||||||
|
let mut result = self.doDecode(&rotatedImage, hints)?;
|
||||||
|
// Record that we found it rotated 90 degrees CCW / 270 degrees CW
|
||||||
|
let metadata = result.getRXingResultMetadata();
|
||||||
|
let mut orientation = 270;
|
||||||
|
if metadata.contains_key(&RXingResultMetadataType::ORIENTATION) {
|
||||||
|
// But if we found it reversed in doDecode(), add in that result here:
|
||||||
|
orientation = (orientation +
|
||||||
|
if let Some(crate::RXingResultMetadataValue::Orientation(or)) = metadata.get(&RXingResultMetadataType::ORIENTATION) {
|
||||||
|
*or
|
||||||
|
}else {
|
||||||
|
0
|
||||||
|
}) % 360;
|
||||||
|
}
|
||||||
|
result.putMetadata(RXingResultMetadataType::ORIENTATION, RXingResultMetadataValue::Orientation(orientation));
|
||||||
|
// Update result points
|
||||||
|
// let points = result.getRXingResultPoints();
|
||||||
|
// if points != null {
|
||||||
|
let height = rotatedImage.getHeight();
|
||||||
|
// for point in result.getRXingResultPointsMut().iter_mut() {
|
||||||
|
let total_points = result.getRXingResultPoints().len();
|
||||||
|
let points = result.getRXingResultPointsMut();
|
||||||
|
for i in 0..total_points{
|
||||||
|
// for (int i = 0; i < points.length; i++) {
|
||||||
|
points[i] = RXingResultPoint::new(height as f32- points[i].getY() - 1.0, points[i].getX());
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gen.into()
|
||||||
|
}
|
||||||
@@ -85,9 +85,9 @@ impl Binarizer for GlobalHistogramBinarizer {
|
|||||||
let mut center = localLuminances[1]; // & 0xff;
|
let mut center = localLuminances[1]; // & 0xff;
|
||||||
for x in 1..width - 1 {
|
for x in 1..width - 1 {
|
||||||
// for (int x = 1; x < width - 1; x++) {
|
// for (int x = 1; x < width - 1; x++) {
|
||||||
let right = localLuminances[x + 1] & 0xff;
|
let right = localLuminances[x + 1];
|
||||||
// A simple -1 4 -1 box filter with a weight of 2.
|
// A simple -1 4 -1 box filter with a weight of 2.
|
||||||
if ((center * 4) - left - right) as u32 / 2 < blackPoint {
|
if ((center as i64 * 4) - left as i64 - right as i64) / 2 < blackPoint as i64 {
|
||||||
row.set(x);
|
row.set(x);
|
||||||
}
|
}
|
||||||
left = center;
|
left = center;
|
||||||
|
|||||||
@@ -1,343 +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.DecodeHintType;
|
|
||||||
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.Arrays;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Decodes Codabar barcodes.</p>
|
|
||||||
*
|
|
||||||
* @author Bas Vijfwinkel
|
|
||||||
* @author David Walker
|
|
||||||
*/
|
|
||||||
public final class CodaBarReader extends OneDReader {
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
private static final float MAX_ACCEPTABLE = 2.0f;
|
|
||||||
private static final float PADDING = 1.5f;
|
|
||||||
|
|
||||||
private static final String ALPHABET_STRING = "0123456789-$:/.+ABCD";
|
|
||||||
static final char[] ALPHABET = ALPHABET_STRING.toCharArray();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static final int[] CHARACTER_ENCODINGS = {
|
|
||||||
0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9
|
|
||||||
0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
private static final int MIN_CHARACTER_LENGTH = 3;
|
|
||||||
|
|
||||||
// official start and end patterns
|
|
||||||
private static final char[] STARTEND_ENCODING = {'A', 'B', 'C', 'D'};
|
|
||||||
// some Codabar generator allow the Codabar string to be closed by every
|
|
||||||
// character. This will cause lots of false positives!
|
|
||||||
|
|
||||||
// some industries use a checksum standard but this is not part of the original Codabar standard
|
|
||||||
// for more information see : http://www.mecsw.com/specs/codabar.html
|
|
||||||
|
|
||||||
// Keep some instance variables to avoid reallocations
|
|
||||||
private final StringBuilder decodeRowRXingResult;
|
|
||||||
private int[] counters;
|
|
||||||
private int counterLength;
|
|
||||||
|
|
||||||
public CodaBarReader() {
|
|
||||||
decodeRowRXingResult = new StringBuilder(20);
|
|
||||||
counters = new int[80];
|
|
||||||
counterLength = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RXingResult decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints) throws NotFoundException {
|
|
||||||
|
|
||||||
Arrays.fill(counters, 0);
|
|
||||||
setCounters(row);
|
|
||||||
int startOffset = findStartPattern();
|
|
||||||
int nextStart = startOffset;
|
|
||||||
|
|
||||||
decodeRowRXingResult.setLength(0);
|
|
||||||
do {
|
|
||||||
int charOffset = toNarrowWidePattern(nextStart);
|
|
||||||
if (charOffset == -1) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
// Hack: We store the position in the alphabet table into a
|
|
||||||
// StringBuilder, so that we can access the decoded patterns in
|
|
||||||
// validatePattern. We'll translate to the actual characters later.
|
|
||||||
decodeRowRXingResult.append((char) charOffset);
|
|
||||||
nextStart += 8;
|
|
||||||
// Stop as soon as we see the end character.
|
|
||||||
if (decodeRowRXingResult.length() > 1 &&
|
|
||||||
arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available
|
|
||||||
|
|
||||||
// Look for whitespace after pattern:
|
|
||||||
int trailingWhitespace = counters[nextStart - 1];
|
|
||||||
int lastPatternSize = 0;
|
|
||||||
for (int i = -8; i < -1; i++) {
|
|
||||||
lastPatternSize += counters[nextStart + i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to see whitespace equal to 50% of the last pattern size,
|
|
||||||
// otherwise this is probably a false positive. The exception is if we are
|
|
||||||
// at the end of the row. (I.e. the barcode barely fits.)
|
|
||||||
if (nextStart < counterLength && trailingWhitespace < lastPatternSize / 2) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
validatePattern(startOffset);
|
|
||||||
|
|
||||||
// Translate character table offsets to actual characters.
|
|
||||||
for (int i = 0; i < decodeRowRXingResult.length(); i++) {
|
|
||||||
decodeRowRXingResult.setCharAt(i, ALPHABET[decodeRowRXingResult.charAt(i)]);
|
|
||||||
}
|
|
||||||
// Ensure a valid start and end character
|
|
||||||
char startchar = decodeRowRXingResult.charAt(0);
|
|
||||||
if (!arrayContains(STARTEND_ENCODING, startchar)) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
char endchar = decodeRowRXingResult.charAt(decodeRowRXingResult.length() - 1);
|
|
||||||
if (!arrayContains(STARTEND_ENCODING, endchar)) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove stop/start characters character and check if a long enough string is contained
|
|
||||||
if (decodeRowRXingResult.length() <= MIN_CHARACTER_LENGTH) {
|
|
||||||
// Almost surely a false positive ( start + stop + at least 1 character)
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hints == null || !hints.containsKey(DecodeHintType.RETURN_CODABAR_START_END)) {
|
|
||||||
decodeRowRXingResult.deleteCharAt(decodeRowRXingResult.length() - 1);
|
|
||||||
decodeRowRXingResult.deleteCharAt(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
int runningCount = 0;
|
|
||||||
for (int i = 0; i < startOffset; i++) {
|
|
||||||
runningCount += counters[i];
|
|
||||||
}
|
|
||||||
float left = runningCount;
|
|
||||||
for (int i = startOffset; i < nextStart - 1; i++) {
|
|
||||||
runningCount += counters[i];
|
|
||||||
}
|
|
||||||
float right = runningCount;
|
|
||||||
|
|
||||||
RXingResult result = new RXingResult(
|
|
||||||
decodeRowRXingResult.toString(),
|
|
||||||
null,
|
|
||||||
new RXingResultPoint[]{
|
|
||||||
new RXingResultPoint(left, rowNumber),
|
|
||||||
new RXingResultPoint(right, rowNumber)},
|
|
||||||
BarcodeFormat.CODABAR);
|
|
||||||
result.putMetadata(RXingResultMetadataType.SYMBOLOGY_IDENTIFIER, "]F0");
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validatePattern(int start) throws NotFoundException {
|
|
||||||
// First, sum up the total size of our four categories of stripe sizes;
|
|
||||||
int[] sizes = {0, 0, 0, 0};
|
|
||||||
int[] counts = {0, 0, 0, 0};
|
|
||||||
int end = decodeRowRXingResult.length() - 1;
|
|
||||||
|
|
||||||
// We break out of this loop in the middle, in order to handle
|
|
||||||
// inter-character spaces properly.
|
|
||||||
int pos = start;
|
|
||||||
for (int i = 0; i <= end; i++) {
|
|
||||||
int pattern = CHARACTER_ENCODINGS[decodeRowRXingResult.charAt(i)];
|
|
||||||
for (int j = 6; j >= 0; j--) {
|
|
||||||
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for
|
|
||||||
// long stripes, while 0 and 1 are for short stripes.
|
|
||||||
int category = (j & 1) + (pattern & 1) * 2;
|
|
||||||
sizes[category] += counters[pos + j];
|
|
||||||
counts[category]++;
|
|
||||||
pattern >>= 1;
|
|
||||||
}
|
|
||||||
// We ignore the inter-character space - it could be of any size.
|
|
||||||
pos += 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate our allowable size thresholds using fixed-point math.
|
|
||||||
float[] maxes = new float[4];
|
|
||||||
float[] mins = new float[4];
|
|
||||||
// Define the threshold of acceptability to be the midpoint between the
|
|
||||||
// average small stripe and the average large stripe. No stripe lengths
|
|
||||||
// should be on the "wrong" side of that line.
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
mins[i] = 0.0f; // Accept arbitrarily small "short" stripes.
|
|
||||||
mins[i + 2] = ((float) sizes[i] / counts[i] + (float) sizes[i + 2] / counts[i + 2]) / 2.0f;
|
|
||||||
maxes[i] = mins[i + 2];
|
|
||||||
maxes[i + 2] = (sizes[i + 2] * MAX_ACCEPTABLE + PADDING) / counts[i + 2];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now verify that all of the stripes are within the thresholds.
|
|
||||||
pos = start;
|
|
||||||
for (int i = 0; i <= end; i++) {
|
|
||||||
int pattern = CHARACTER_ENCODINGS[decodeRowRXingResult.charAt(i)];
|
|
||||||
for (int j = 6; j >= 0; j--) {
|
|
||||||
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for
|
|
||||||
// long stripes, while 0 and 1 are for short stripes.
|
|
||||||
int category = (j & 1) + (pattern & 1) * 2;
|
|
||||||
int size = counters[pos + j];
|
|
||||||
if (size < mins[category] || size > maxes[category]) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
pattern >>= 1;
|
|
||||||
}
|
|
||||||
pos += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the size of all runs of white and black pixels, starting with white.
|
|
||||||
* This is just like recordPattern, except it records all the counters, and
|
|
||||||
* uses our builtin "counters" member for storage.
|
|
||||||
* @param row row to count from
|
|
||||||
*/
|
|
||||||
private void setCounters(BitArray row) throws NotFoundException {
|
|
||||||
counterLength = 0;
|
|
||||||
// Start from the first white bit.
|
|
||||||
int i = row.getNextUnset(0);
|
|
||||||
int end = row.getSize();
|
|
||||||
if (i >= end) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
boolean isWhite = true;
|
|
||||||
int count = 0;
|
|
||||||
while (i < end) {
|
|
||||||
if (row.get(i) != isWhite) {
|
|
||||||
count++;
|
|
||||||
} else {
|
|
||||||
counterAppend(count);
|
|
||||||
count = 1;
|
|
||||||
isWhite = !isWhite;
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
counterAppend(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void counterAppend(int e) {
|
|
||||||
counters[counterLength] = e;
|
|
||||||
counterLength++;
|
|
||||||
if (counterLength >= counters.length) {
|
|
||||||
int[] temp = new int[counterLength * 2];
|
|
||||||
System.arraycopy(counters, 0, temp, 0, counterLength);
|
|
||||||
counters = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int findStartPattern() throws NotFoundException {
|
|
||||||
for (int i = 1; i < counterLength; i += 2) {
|
|
||||||
int charOffset = toNarrowWidePattern(i);
|
|
||||||
if (charOffset != -1 && arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) {
|
|
||||||
// Look for whitespace before start pattern, >= 50% of width of start pattern
|
|
||||||
// We make an exception if the whitespace is the first element.
|
|
||||||
int patternSize = 0;
|
|
||||||
for (int j = i; j < i + 7; j++) {
|
|
||||||
patternSize += counters[j];
|
|
||||||
}
|
|
||||||
if (i == 1 || counters[i - 1] >= patternSize / 2) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean arrayContains(char[] array, char key) {
|
|
||||||
if (array != null) {
|
|
||||||
for (char c : array) {
|
|
||||||
if (c == key) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assumes that counters[position] is a bar.
|
|
||||||
private int toNarrowWidePattern(int position) {
|
|
||||||
int end = position + 7;
|
|
||||||
if (end >= counterLength) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int[] theCounters = counters;
|
|
||||||
|
|
||||||
int maxBar = 0;
|
|
||||||
int minBar = Integer.MAX_VALUE;
|
|
||||||
for (int j = position; j < end; j += 2) {
|
|
||||||
int currentCounter = theCounters[j];
|
|
||||||
if (currentCounter < minBar) {
|
|
||||||
minBar = currentCounter;
|
|
||||||
}
|
|
||||||
if (currentCounter > maxBar) {
|
|
||||||
maxBar = currentCounter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int thresholdBar = (minBar + maxBar) / 2;
|
|
||||||
|
|
||||||
int maxSpace = 0;
|
|
||||||
int minSpace = Integer.MAX_VALUE;
|
|
||||||
for (int j = position + 1; j < end; j += 2) {
|
|
||||||
int currentCounter = theCounters[j];
|
|
||||||
if (currentCounter < minSpace) {
|
|
||||||
minSpace = currentCounter;
|
|
||||||
}
|
|
||||||
if (currentCounter > maxSpace) {
|
|
||||||
maxSpace = currentCounter;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int thresholdSpace = (minSpace + maxSpace) / 2;
|
|
||||||
|
|
||||||
int bitmask = 1 << 7;
|
|
||||||
int pattern = 0;
|
|
||||||
for (int i = 0; i < 7; i++) {
|
|
||||||
int threshold = (i & 1) == 0 ? thresholdBar : thresholdSpace;
|
|
||||||
bitmask >>= 1;
|
|
||||||
if (theCounters[position + i] > threshold) {
|
|
||||||
pattern |= bitmask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
|
|
||||||
if (CHARACTER_ENCODINGS[i] == pattern) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 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 java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records EAN prefix to GS1 Member Organization, where the member organization
|
|
||||||
* correlates strongly with a country. This is an imperfect means of identifying
|
|
||||||
* a country of origin by EAN-13 barcode value. See
|
|
||||||
* <a href="http://en.wikipedia.org/wiki/List_of_GS1_country_codes">
|
|
||||||
* http://en.wikipedia.org/wiki/List_of_GS1_country_codes</a>.
|
|
||||||
*
|
|
||||||
* @author Sean Owen
|
|
||||||
*/
|
|
||||||
final class EANManufacturerOrgSupport {
|
|
||||||
|
|
||||||
private final List<int[]> ranges = new ArrayList<>();
|
|
||||||
private final List<String> countryIdentifiers = new ArrayList<>();
|
|
||||||
|
|
||||||
String lookupCountryIdentifier(String productCode) {
|
|
||||||
initIfNeeded();
|
|
||||||
int prefix = Integer.parseInt(productCode.substring(0, 3));
|
|
||||||
int max = ranges.size();
|
|
||||||
for (int i = 0; i < max; i++) {
|
|
||||||
int[] range = ranges.get(i);
|
|
||||||
int start = range[0];
|
|
||||||
if (prefix < start) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
int end = range.length == 1 ? start : range[1];
|
|
||||||
if (prefix <= end) {
|
|
||||||
return countryIdentifiers.get(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void add(int[] range, String id) {
|
|
||||||
ranges.add(range);
|
|
||||||
countryIdentifiers.add(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized void initIfNeeded() {
|
|
||||||
if (!ranges.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
add(new int[] {0,19}, "US/CA");
|
|
||||||
add(new int[] {30,39}, "US");
|
|
||||||
add(new int[] {60,139}, "US/CA");
|
|
||||||
add(new int[] {300,379}, "FR");
|
|
||||||
add(new int[] {380}, "BG");
|
|
||||||
add(new int[] {383}, "SI");
|
|
||||||
add(new int[] {385}, "HR");
|
|
||||||
add(new int[] {387}, "BA");
|
|
||||||
add(new int[] {400,440}, "DE");
|
|
||||||
add(new int[] {450,459}, "JP");
|
|
||||||
add(new int[] {460,469}, "RU");
|
|
||||||
add(new int[] {471}, "TW");
|
|
||||||
add(new int[] {474}, "EE");
|
|
||||||
add(new int[] {475}, "LV");
|
|
||||||
add(new int[] {476}, "AZ");
|
|
||||||
add(new int[] {477}, "LT");
|
|
||||||
add(new int[] {478}, "UZ");
|
|
||||||
add(new int[] {479}, "LK");
|
|
||||||
add(new int[] {480}, "PH");
|
|
||||||
add(new int[] {481}, "BY");
|
|
||||||
add(new int[] {482}, "UA");
|
|
||||||
add(new int[] {484}, "MD");
|
|
||||||
add(new int[] {485}, "AM");
|
|
||||||
add(new int[] {486}, "GE");
|
|
||||||
add(new int[] {487}, "KZ");
|
|
||||||
add(new int[] {489}, "HK");
|
|
||||||
add(new int[] {490,499}, "JP");
|
|
||||||
add(new int[] {500,509}, "GB");
|
|
||||||
add(new int[] {520}, "GR");
|
|
||||||
add(new int[] {528}, "LB");
|
|
||||||
add(new int[] {529}, "CY");
|
|
||||||
add(new int[] {531}, "MK");
|
|
||||||
add(new int[] {535}, "MT");
|
|
||||||
add(new int[] {539}, "IE");
|
|
||||||
add(new int[] {540,549}, "BE/LU");
|
|
||||||
add(new int[] {560}, "PT");
|
|
||||||
add(new int[] {569}, "IS");
|
|
||||||
add(new int[] {570,579}, "DK");
|
|
||||||
add(new int[] {590}, "PL");
|
|
||||||
add(new int[] {594}, "RO");
|
|
||||||
add(new int[] {599}, "HU");
|
|
||||||
add(new int[] {600,601}, "ZA");
|
|
||||||
add(new int[] {603}, "GH");
|
|
||||||
add(new int[] {608}, "BH");
|
|
||||||
add(new int[] {609}, "MU");
|
|
||||||
add(new int[] {611}, "MA");
|
|
||||||
add(new int[] {613}, "DZ");
|
|
||||||
add(new int[] {616}, "KE");
|
|
||||||
add(new int[] {618}, "CI");
|
|
||||||
add(new int[] {619}, "TN");
|
|
||||||
add(new int[] {621}, "SY");
|
|
||||||
add(new int[] {622}, "EG");
|
|
||||||
add(new int[] {624}, "LY");
|
|
||||||
add(new int[] {625}, "JO");
|
|
||||||
add(new int[] {626}, "IR");
|
|
||||||
add(new int[] {627}, "KW");
|
|
||||||
add(new int[] {628}, "SA");
|
|
||||||
add(new int[] {629}, "AE");
|
|
||||||
add(new int[] {640,649}, "FI");
|
|
||||||
add(new int[] {690,695}, "CN");
|
|
||||||
add(new int[] {700,709}, "NO");
|
|
||||||
add(new int[] {729}, "IL");
|
|
||||||
add(new int[] {730,739}, "SE");
|
|
||||||
add(new int[] {740}, "GT");
|
|
||||||
add(new int[] {741}, "SV");
|
|
||||||
add(new int[] {742}, "HN");
|
|
||||||
add(new int[] {743}, "NI");
|
|
||||||
add(new int[] {744}, "CR");
|
|
||||||
add(new int[] {745}, "PA");
|
|
||||||
add(new int[] {746}, "DO");
|
|
||||||
add(new int[] {750}, "MX");
|
|
||||||
add(new int[] {754,755}, "CA");
|
|
||||||
add(new int[] {759}, "VE");
|
|
||||||
add(new int[] {760,769}, "CH");
|
|
||||||
add(new int[] {770}, "CO");
|
|
||||||
add(new int[] {773}, "UY");
|
|
||||||
add(new int[] {775}, "PE");
|
|
||||||
add(new int[] {777}, "BO");
|
|
||||||
add(new int[] {779}, "AR");
|
|
||||||
add(new int[] {780}, "CL");
|
|
||||||
add(new int[] {784}, "PY");
|
|
||||||
add(new int[] {785}, "PE");
|
|
||||||
add(new int[] {786}, "EC");
|
|
||||||
add(new int[] {789,790}, "BR");
|
|
||||||
add(new int[] {800,839}, "IT");
|
|
||||||
add(new int[] {840,849}, "ES");
|
|
||||||
add(new int[] {850}, "CU");
|
|
||||||
add(new int[] {858}, "SK");
|
|
||||||
add(new int[] {859}, "CZ");
|
|
||||||
add(new int[] {860}, "YU");
|
|
||||||
add(new int[] {865}, "MN");
|
|
||||||
add(new int[] {867}, "KP");
|
|
||||||
add(new int[] {868,869}, "TR");
|
|
||||||
add(new int[] {870,879}, "NL");
|
|
||||||
add(new int[] {880}, "KR");
|
|
||||||
add(new int[] {885}, "TH");
|
|
||||||
add(new int[] {888}, "SG");
|
|
||||||
add(new int[] {890}, "IN");
|
|
||||||
add(new int[] {893}, "VN");
|
|
||||||
add(new int[] {896}, "PK");
|
|
||||||
add(new int[] {899}, "ID");
|
|
||||||
add(new int[] {900,919}, "AT");
|
|
||||||
add(new int[] {930,939}, "AU");
|
|
||||||
add(new int[] {940,949}, "AZ");
|
|
||||||
add(new int[] {955}, "MY");
|
|
||||||
add(new int[] {958}, "MO");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,296 +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.BinaryBitmap;
|
|
||||||
import com.google.zxing.ChecksumException;
|
|
||||||
import com.google.zxing.DecodeHintType;
|
|
||||||
import com.google.zxing.FormatException;
|
|
||||||
import com.google.zxing.NotFoundException;
|
|
||||||
import com.google.zxing.Reader;
|
|
||||||
import com.google.zxing.ReaderException;
|
|
||||||
import com.google.zxing.RXingResult;
|
|
||||||
import com.google.zxing.RXingResultMetadataType;
|
|
||||||
import com.google.zxing.RXingResultPoint;
|
|
||||||
import com.google.zxing.common.BitArray;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.EnumMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encapsulates functionality and implementation that is common to all families
|
|
||||||
* of one-dimensional barcodes.
|
|
||||||
*
|
|
||||||
* @author dswitkin@google.com (Daniel Switkin)
|
|
||||||
* @author Sean Owen
|
|
||||||
*/
|
|
||||||
public abstract class OneDReader implements Reader {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RXingResult decode(BinaryBitmap image) throws NotFoundException, FormatException {
|
|
||||||
return decode(image, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that we don't try rotation without the try harder flag, even if rotation was supported.
|
|
||||||
@Override
|
|
||||||
public RXingResult decode(BinaryBitmap image,
|
|
||||||
Map<DecodeHintType,?> hints) throws NotFoundException, FormatException {
|
|
||||||
try {
|
|
||||||
return doDecode(image, hints);
|
|
||||||
} catch (NotFoundException nfe) {
|
|
||||||
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
|
|
||||||
if (tryHarder && image.isRotateSupported()) {
|
|
||||||
BinaryBitmap rotatedImage = image.rotateCounterClockwise();
|
|
||||||
RXingResult result = doDecode(rotatedImage, hints);
|
|
||||||
// Record that we found it rotated 90 degrees CCW / 270 degrees CW
|
|
||||||
Map<RXingResultMetadataType,?> metadata = result.getRXingResultMetadata();
|
|
||||||
int orientation = 270;
|
|
||||||
if (metadata != null && metadata.containsKey(RXingResultMetadataType.ORIENTATION)) {
|
|
||||||
// But if we found it reversed in doDecode(), add in that result here:
|
|
||||||
orientation = (orientation +
|
|
||||||
(Integer) metadata.get(RXingResultMetadataType.ORIENTATION)) % 360;
|
|
||||||
}
|
|
||||||
result.putMetadata(RXingResultMetadataType.ORIENTATION, orientation);
|
|
||||||
// Update result points
|
|
||||||
RXingResultPoint[] points = result.getRXingResultPoints();
|
|
||||||
if (points != null) {
|
|
||||||
int height = rotatedImage.getHeight();
|
|
||||||
for (int i = 0; i < points.length; i++) {
|
|
||||||
points[i] = new RXingResultPoint(height - points[i].getY() - 1, points[i].getX());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
throw nfe;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reset() {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We're going to examine rows from the middle outward, searching alternately above and below the
|
|
||||||
* middle, and farther out each time. rowStep is the number of rows between each successive
|
|
||||||
* attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
|
|
||||||
* middle + rowStep, then middle - (2 * rowStep), etc.
|
|
||||||
* rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
|
|
||||||
* decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
|
|
||||||
* image if "trying harder".
|
|
||||||
*
|
|
||||||
* @param image The image to decode
|
|
||||||
* @param hints Any hints that were requested
|
|
||||||
* @return The contents of the decoded barcode
|
|
||||||
* @throws NotFoundException Any spontaneous errors which occur
|
|
||||||
*/
|
|
||||||
private RXingResult doDecode(BinaryBitmap image,
|
|
||||||
Map<DecodeHintType,?> hints) throws NotFoundException {
|
|
||||||
int width = image.getWidth();
|
|
||||||
int height = image.getHeight();
|
|
||||||
BitArray row = new BitArray(width);
|
|
||||||
|
|
||||||
boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
|
|
||||||
int rowStep = Math.max(1, height >> (tryHarder ? 8 : 5));
|
|
||||||
int maxLines;
|
|
||||||
if (tryHarder) {
|
|
||||||
maxLines = height; // Look at the whole image, not just the center
|
|
||||||
} else {
|
|
||||||
maxLines = 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image
|
|
||||||
}
|
|
||||||
|
|
||||||
int middle = height / 2;
|
|
||||||
for (int x = 0; x < maxLines; x++) {
|
|
||||||
|
|
||||||
// Scanning from the middle out. Determine which row we're looking at next:
|
|
||||||
int rowStepsAboveOrBelow = (x + 1) / 2;
|
|
||||||
boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
|
|
||||||
int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
|
|
||||||
if (rowNumber < 0 || rowNumber >= height) {
|
|
||||||
// Oops, if we run off the top or bottom, stop
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Estimate black point for this row and load it:
|
|
||||||
try {
|
|
||||||
row = image.getBlackRow(rowNumber, row);
|
|
||||||
} catch (NotFoundException ignored) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
|
|
||||||
// handle decoding upside down barcodes.
|
|
||||||
for (int attempt = 0; attempt < 2; attempt++) {
|
|
||||||
if (attempt == 1) { // trying again?
|
|
||||||
row.reverse(); // reverse the row and continue
|
|
||||||
// This means we will only ever draw result points *once* in the life of this method
|
|
||||||
// since we want to avoid drawing the wrong points after flipping the row, and,
|
|
||||||
// don't want to clutter with noise from every single row scan -- just the scans
|
|
||||||
// that start on the center line.
|
|
||||||
if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
|
|
||||||
Map<DecodeHintType,Object> newHints = new EnumMap<>(DecodeHintType.class);
|
|
||||||
newHints.putAll(hints);
|
|
||||||
newHints.remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
|
|
||||||
hints = newHints;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// Look for a barcode
|
|
||||||
RXingResult result = decodeRow(rowNumber, row, hints);
|
|
||||||
// We found our barcode
|
|
||||||
if (attempt == 1) {
|
|
||||||
// But it was upside down, so note that
|
|
||||||
result.putMetadata(RXingResultMetadataType.ORIENTATION, 180);
|
|
||||||
// And remember to flip the result points horizontally.
|
|
||||||
RXingResultPoint[] points = result.getRXingResultPoints();
|
|
||||||
if (points != null) {
|
|
||||||
points[0] = new RXingResultPoint(width - points[0].getX() - 1, points[0].getY());
|
|
||||||
points[1] = new RXingResultPoint(width - points[1].getX() - 1, points[1].getY());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (ReaderException re) {
|
|
||||||
// continue -- just couldn't decode this row
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Records the size of successive runs of white and black pixels in a row, starting at a given point.
|
|
||||||
* The values are recorded in the given array, and the number of runs recorded is equal to the size
|
|
||||||
* of the array. If the row starts on a white pixel at the given start point, then the first count
|
|
||||||
* recorded is the run of white pixels starting from that point; likewise it is the count of a run
|
|
||||||
* of black pixels if the row begin on a black pixels at that point.
|
|
||||||
*
|
|
||||||
* @param row row to count from
|
|
||||||
* @param start offset into row to start at
|
|
||||||
* @param counters array into which to record counts
|
|
||||||
* @throws NotFoundException if counters cannot be filled entirely from row before running out
|
|
||||||
* of pixels
|
|
||||||
*/
|
|
||||||
protected static void recordPattern(BitArray row,
|
|
||||||
int start,
|
|
||||||
int[] counters) throws NotFoundException {
|
|
||||||
int numCounters = counters.length;
|
|
||||||
Arrays.fill(counters, 0, numCounters, 0);
|
|
||||||
int end = row.getSize();
|
|
||||||
if (start >= end) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
boolean isWhite = !row.get(start);
|
|
||||||
int counterPosition = 0;
|
|
||||||
int i = start;
|
|
||||||
while (i < end) {
|
|
||||||
if (row.get(i) != isWhite) {
|
|
||||||
counters[counterPosition]++;
|
|
||||||
} else {
|
|
||||||
if (++counterPosition == numCounters) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
counters[counterPosition] = 1;
|
|
||||||
isWhite = !isWhite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
// If we read fully the last section of pixels and filled up our counters -- or filled
|
|
||||||
// the last counter but ran off the side of the image, OK. Otherwise, a problem.
|
|
||||||
if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end))) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void recordPatternInReverse(BitArray row, int start, int[] counters)
|
|
||||||
throws NotFoundException {
|
|
||||||
// This could be more efficient I guess
|
|
||||||
int numTransitionsLeft = counters.length;
|
|
||||||
boolean last = row.get(start);
|
|
||||||
while (start > 0 && numTransitionsLeft >= 0) {
|
|
||||||
if (row.get(--start) != last) {
|
|
||||||
numTransitionsLeft--;
|
|
||||||
last = !last;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (numTransitionsLeft >= 0) {
|
|
||||||
throw NotFoundException.getNotFoundInstance();
|
|
||||||
}
|
|
||||||
recordPattern(row, start + 1, counters);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines how closely a set of observed counts of runs of black/white values matches a given
|
|
||||||
* target pattern. This is reported as the ratio of the total variance from the expected pattern
|
|
||||||
* proportions across all pattern elements, to the length of the pattern.
|
|
||||||
*
|
|
||||||
* @param counters observed counters
|
|
||||||
* @param pattern expected pattern
|
|
||||||
* @param maxIndividualVariance The most any counter can differ before we give up
|
|
||||||
* @return ratio of total variance between counters and pattern compared to total pattern size
|
|
||||||
*/
|
|
||||||
protected static float patternMatchVariance(int[] counters,
|
|
||||||
int[] pattern,
|
|
||||||
float maxIndividualVariance) {
|
|
||||||
int numCounters = counters.length;
|
|
||||||
int total = 0;
|
|
||||||
int patternLength = 0;
|
|
||||||
for (int i = 0; i < numCounters; i++) {
|
|
||||||
total += counters[i];
|
|
||||||
patternLength += pattern[i];
|
|
||||||
}
|
|
||||||
if (total < patternLength) {
|
|
||||||
// If we don't even have one pixel per unit of bar width, assume this is too small
|
|
||||||
// to reliably match, so fail:
|
|
||||||
return Float.POSITIVE_INFINITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
float unitBarWidth = (float) total / patternLength;
|
|
||||||
maxIndividualVariance *= unitBarWidth;
|
|
||||||
|
|
||||||
float totalVariance = 0.0f;
|
|
||||||
for (int x = 0; x < numCounters; x++) {
|
|
||||||
int counter = counters[x];
|
|
||||||
float scaledPattern = pattern[x] * unitBarWidth;
|
|
||||||
float variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
|
|
||||||
if (variance > maxIndividualVariance) {
|
|
||||||
return Float.POSITIVE_INFINITY;
|
|
||||||
}
|
|
||||||
totalVariance += variance;
|
|
||||||
}
|
|
||||||
return totalVariance / total;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Attempts to decode a one-dimensional barcode format given a single row of
|
|
||||||
* an image.</p>
|
|
||||||
*
|
|
||||||
* @param rowNumber row number from top of the row
|
|
||||||
* @param row the black/white pixel data of the row
|
|
||||||
* @param hints decode hints
|
|
||||||
* @return {@link RXingResult} containing encoded string and start/end of barcode
|
|
||||||
* @throws NotFoundException if no potential barcode is found
|
|
||||||
* @throws ChecksumException if a potential barcode is found but does not pass its checksum
|
|
||||||
* @throws FormatException if a potential barcode is found but format is invalid
|
|
||||||
*/
|
|
||||||
public abstract RXingResult decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)
|
|
||||||
throws NotFoundException, ChecksumException, FormatException;
|
|
||||||
|
|
||||||
}
|
|
||||||
427
src/oned/coda_bar_reader.rs
Normal file
427
src/oned/coda_bar_reader.rs
Normal file
@@ -0,0 +1,427 @@
|
|||||||
|
/*
|
||||||
|
* 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;
|
||||||
|
use crate::BarcodeFormat;
|
||||||
|
use crate::Exceptions;
|
||||||
|
use crate::RXingResult;
|
||||||
|
|
||||||
|
use super::OneDReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Decodes Codabar barcodes.</p>
|
||||||
|
*
|
||||||
|
* @author Bas Vijfwinkel
|
||||||
|
* @author David Walker
|
||||||
|
*/
|
||||||
|
#[derive(OneDReader)]
|
||||||
|
pub struct CodaBarReader {
|
||||||
|
// Keep some instance variables to avoid reallocations
|
||||||
|
decodeRowRXingResult: String,
|
||||||
|
counters: Vec<u32>,
|
||||||
|
counterLength: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CodaBarReader {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
decodeRowRXingResult: Default::default(),
|
||||||
|
counters: Default::default(),
|
||||||
|
counterLength: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OneDReader for CodaBarReader {
|
||||||
|
fn decodeRow(
|
||||||
|
&mut self,
|
||||||
|
rowNumber: u32,
|
||||||
|
row: &crate::common::BitArray,
|
||||||
|
hints: &crate::DecodingHintDictionary,
|
||||||
|
) -> Result<crate::RXingResult, crate::Exceptions> {
|
||||||
|
self.counters.fill(0);
|
||||||
|
// Arrays.fill(counters, 0);
|
||||||
|
self.setCounters(row)?;
|
||||||
|
let startOffset = self.findStartPattern()? as usize;
|
||||||
|
let mut nextStart = startOffset;
|
||||||
|
|
||||||
|
self.decodeRowRXingResult.clear();
|
||||||
|
loop {
|
||||||
|
let charOffset = self.toNarrowWidePattern(nextStart);
|
||||||
|
if charOffset == -1 {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
// Hack: We store the position in the alphabet table into a
|
||||||
|
// StringBuilder, so that we can access the decoded patterns in
|
||||||
|
// validatePattern. We'll translate to the actual characters later.
|
||||||
|
self.decodeRowRXingResult
|
||||||
|
.push(char::from_u32(charOffset as u32).unwrap());
|
||||||
|
nextStart += 8;
|
||||||
|
// Stop as soon as we see the end character.
|
||||||
|
if self.decodeRowRXingResult.chars().count() > 1
|
||||||
|
&& Self::arrayContains(
|
||||||
|
&Self::STARTEND_ENCODING,
|
||||||
|
Self::ALPHABET[charOffset as usize],
|
||||||
|
)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if !(nextStart < self.counterLength) {
|
||||||
|
break;
|
||||||
|
} // no fixed end pattern so keep on reading while data is available
|
||||||
|
} //while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available
|
||||||
|
|
||||||
|
// Look for whitespace after pattern:
|
||||||
|
let trailingWhitespace = self.counters[nextStart - 1];
|
||||||
|
let mut lastPatternSize = 0;
|
||||||
|
for i in -8isize..-1isize {
|
||||||
|
// for (int i = -8; i < -1; i++) {
|
||||||
|
lastPatternSize += self.counters[(nextStart as isize + i) as usize];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to see whitespace equal to 50% of the last pattern size,
|
||||||
|
// otherwise this is probably a false positive. The exception is if we are
|
||||||
|
// at the end of the row. (I.e. the barcode barely fits.)
|
||||||
|
if nextStart < self.counterLength && trailingWhitespace < lastPatternSize / 2 {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.validatePattern(startOffset)?;
|
||||||
|
|
||||||
|
// Translate character table offsets to actual characters.
|
||||||
|
for i in 0..self.decodeRowRXingResult.chars().count() {
|
||||||
|
// for (int i = 0; i < decodeRowRXingResult.length(); i++) {
|
||||||
|
self.decodeRowRXingResult.replace_range(
|
||||||
|
i..=i,
|
||||||
|
&Self::ALPHABET[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize]
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
// self.decodeRowRXingResult.setCharAt(i, Self::ALPHABET[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize]);
|
||||||
|
}
|
||||||
|
// Ensure a valid start and end character
|
||||||
|
let startchar = self.decodeRowRXingResult.chars().nth(0).unwrap();
|
||||||
|
if !Self::arrayContains(&Self::STARTEND_ENCODING, startchar) {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
let endchar = self
|
||||||
|
.decodeRowRXingResult
|
||||||
|
.chars()
|
||||||
|
.nth(self.decodeRowRXingResult.chars().count() - 1)
|
||||||
|
.unwrap();
|
||||||
|
if !Self::arrayContains(&Self::STARTEND_ENCODING, endchar) {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove stop/start characters character and check if a long enough string is contained
|
||||||
|
if (self.decodeRowRXingResult.chars().count()) <= Self::MIN_CHARACTER_LENGTH as usize {
|
||||||
|
// Almost surely a false positive ( start + stop + at least 1 character)
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hints.contains_key(&DecodeHintType::RETURN_CODABAR_START_END) {
|
||||||
|
// self.decodeRowRXingResult.deleteCharAt(self.decodeRowRXingResult.chars().count() - 1);
|
||||||
|
// self.decodeRowRXingResult.deleteCharAt(0);
|
||||||
|
self.decodeRowRXingResult =
|
||||||
|
self.decodeRowRXingResult[1..self.decodeRowRXingResult.len()-1].to_owned();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut runningCount = 0;
|
||||||
|
for i in 0..startOffset {
|
||||||
|
// for (int i = 0; i < startOffset; i++) {
|
||||||
|
runningCount += self.counters[i];
|
||||||
|
}
|
||||||
|
let left: f32 = runningCount as f32;
|
||||||
|
for i in startOffset..(nextStart - 1) {
|
||||||
|
// for (int i = startOffset; i < nextStart - 1; i++) {
|
||||||
|
runningCount += self.counters[i];
|
||||||
|
}
|
||||||
|
let right: f32 = runningCount as f32;
|
||||||
|
|
||||||
|
let mut result = RXingResult::new(
|
||||||
|
&self.decodeRowRXingResult,
|
||||||
|
Vec::new(),
|
||||||
|
vec![
|
||||||
|
RXingResultPoint::new(left, rowNumber as f32),
|
||||||
|
RXingResultPoint::new(right, rowNumber as f32),
|
||||||
|
],
|
||||||
|
BarcodeFormat::CODABAR,
|
||||||
|
);
|
||||||
|
|
||||||
|
result.putMetadata(
|
||||||
|
RXingResultMetadataType::SYMBOLOGY_IDENTIFIER,
|
||||||
|
RXingResultMetadataValue::SymbologyIdentifier("]F0".to_owned()),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
|
// const ALPHABET_STRING : &str= "0123456789-$:/.+ABCD";
|
||||||
|
const ALPHABET: [char; 20] = [
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '$', ':', '/', '.', '+', 'A', 'B',
|
||||||
|
'C', 'D',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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] = [
|
||||||
|
0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9
|
||||||
|
0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD
|
||||||
|
];
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// official start and end patterns
|
||||||
|
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!
|
||||||
|
|
||||||
|
// some industries use a checksum standard but this is not part of the original Codabar standard
|
||||||
|
// for more information see : http://www.mecsw.com/specs/codabar.html
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
decodeRowRXingResult: String::with_capacity(20),
|
||||||
|
counters: vec![0; 80], //Vec::with_capacity(80),
|
||||||
|
counterLength: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validatePattern(&self, start: usize) -> Result<(), Exceptions> {
|
||||||
|
// First, sum up the total size of our four categories of stripe sizes;
|
||||||
|
let mut sizes = [0, 0, 0, 0];
|
||||||
|
let mut counts = [0, 0, 0, 0];
|
||||||
|
let end = self.decodeRowRXingResult.chars().count() - 1;
|
||||||
|
|
||||||
|
// We break out of this loop in the middle, in order to handle
|
||||||
|
// inter-character spaces properly.
|
||||||
|
let mut pos = start;
|
||||||
|
for i in 0..=end {
|
||||||
|
// for (int i = 0; i <= end; i++) {
|
||||||
|
let mut pattern = Self::CHARACTER_ENCODINGS
|
||||||
|
[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize];
|
||||||
|
for j in (0_usize..=6).rev() {
|
||||||
|
// for (int j = 6; j >= 0; j--) {
|
||||||
|
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for
|
||||||
|
// long stripes, while 0 and 1 are for short stripes.
|
||||||
|
let category = (j & 1) + ((pattern as usize) & 1) * 2;
|
||||||
|
sizes[category] += self.counters[(pos + j) as usize];
|
||||||
|
counts[category] += 1;
|
||||||
|
pattern >>= 1;
|
||||||
|
}
|
||||||
|
// We ignore the inter-character space - it could be of any size.
|
||||||
|
pos += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate our allowable size thresholds using fixed-point math.
|
||||||
|
let mut maxes = [0.0; 4]; //new float[4];
|
||||||
|
let mut mins = [0.0; 4]; //new float[4];
|
||||||
|
// Define the threshold of acceptability to be the midpoint between the
|
||||||
|
// average small stripe and the average large stripe. No stripe lengths
|
||||||
|
// should be on the "wrong" side of that line.
|
||||||
|
for i in 0..2 {
|
||||||
|
// for (int i = 0; i < 2; i++) {
|
||||||
|
mins[i] = 0.0; // Accept arbitrarily small "short" stripes.
|
||||||
|
mins[i + 2] = ((sizes[i] as f32) / (counts[i] as f32)
|
||||||
|
+ (sizes[i + 2] as f32) / (counts[i + 2] as f32))
|
||||||
|
/ 2.0;
|
||||||
|
maxes[i] = mins[i + 2];
|
||||||
|
maxes[i + 2] = ((sizes[i + 2] as f32) * Self::MAX_ACCEPTABLE + Self::PADDING)
|
||||||
|
/ (counts[i + 2] as f32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now verify that all of the stripes are within the thresholds.
|
||||||
|
pos = start;
|
||||||
|
for i in 0..=end {
|
||||||
|
// for (int i = 0; i <= end; i++) {
|
||||||
|
let mut pattern = Self::CHARACTER_ENCODINGS
|
||||||
|
[self.decodeRowRXingResult.chars().nth(i).unwrap() as usize];
|
||||||
|
for j in (0usize..=6).rev() {
|
||||||
|
// for (int j = 6; j >= 0; j--) {
|
||||||
|
// Even j = bars, while odd j = spaces. Categories 2 and 3 are for
|
||||||
|
// long stripes, while 0 and 1 are for short stripes.
|
||||||
|
let category = (j & 1) + ((pattern as usize) & 1) * 2;
|
||||||
|
let size = self.counters[(pos + j)];
|
||||||
|
if (size as f32) < mins[category] || (size as f32) > maxes[category] {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
pattern >>= 1;
|
||||||
|
}
|
||||||
|
pos += 8;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the size of all runs of white and black pixels, starting with white.
|
||||||
|
* This is just like recordPattern, except it records all the counters, and
|
||||||
|
* uses our builtin "counters" member for storage.
|
||||||
|
* @param row row to count from
|
||||||
|
*/
|
||||||
|
fn setCounters(&mut self, row: &BitArray) -> Result<(), Exceptions> {
|
||||||
|
self.counterLength = 0;
|
||||||
|
// Start from the first white bit.
|
||||||
|
let mut i = row.getNextUnset(0);
|
||||||
|
let end = row.getSize();
|
||||||
|
if i >= end {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
let mut isWhite = true;
|
||||||
|
let mut count = 0;
|
||||||
|
while i < end {
|
||||||
|
if row.get(i) != isWhite {
|
||||||
|
count += 1;
|
||||||
|
} else {
|
||||||
|
self.counterAppend(count);
|
||||||
|
count = 1;
|
||||||
|
isWhite = !isWhite;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
self.counterAppend(count);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn counterAppend(&mut self, e: u32) {
|
||||||
|
self.counters[self.counterLength] = e;
|
||||||
|
self.counterLength += 1;
|
||||||
|
if self.counterLength >= self.counters.len() {
|
||||||
|
let mut temp = vec![0; self.counterLength * 2]; //new int[counterLength * 2];
|
||||||
|
temp[0..self.counterLength].clone_from_slice(&self.counters[..]);
|
||||||
|
// System.arraycopy(counters, 0, temp, 0, counterLength);
|
||||||
|
self.counters = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findStartPattern(&mut self) -> Result<u32, Exceptions> {
|
||||||
|
let mut i = 1;
|
||||||
|
while i < self.counterLength {
|
||||||
|
// for (int i = 1; i < counterLength; i += 2) {
|
||||||
|
let charOffset = self.toNarrowWidePattern(i);
|
||||||
|
if charOffset != -1
|
||||||
|
&& Self::arrayContains(
|
||||||
|
&Self::STARTEND_ENCODING,
|
||||||
|
Self::ALPHABET[charOffset as usize],
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Look for whitespace before start pattern, >= 50% of width of start pattern
|
||||||
|
// We make an exception if the whitespace is the first element.
|
||||||
|
let mut patternSize = 0;
|
||||||
|
for j in i..(i + 7) {
|
||||||
|
// for (int j = i; j < i + 7; j++) {
|
||||||
|
patternSize += self.counters[j];
|
||||||
|
}
|
||||||
|
if i == 1 || self.counters[i - 1] >= patternSize / 2 {
|
||||||
|
return Ok(i as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
Err(Exceptions::NotFoundException("".to_owned()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arrayContains(array: &[char], key: char) -> bool {
|
||||||
|
// if (array != null) {
|
||||||
|
for c in array {
|
||||||
|
if c == &key {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assumes that counters[position] is a bar.
|
||||||
|
fn toNarrowWidePattern(&mut self, position: usize) -> i32 {
|
||||||
|
let end = position + 7;
|
||||||
|
if end >= self.counterLength {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let theCounters = &self.counters;
|
||||||
|
|
||||||
|
let mut maxBar = 0;
|
||||||
|
let mut minBar = u32::MAX;
|
||||||
|
let mut j = position;
|
||||||
|
while j < end {
|
||||||
|
// for (int j = position; j < end; j += 2) {
|
||||||
|
let currentCounter = theCounters[j];
|
||||||
|
if currentCounter < minBar {
|
||||||
|
minBar = currentCounter;
|
||||||
|
}
|
||||||
|
if currentCounter > maxBar {
|
||||||
|
maxBar = currentCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
j += 2;
|
||||||
|
}
|
||||||
|
let thresholdBar = (minBar + maxBar) / 2;
|
||||||
|
|
||||||
|
let mut maxSpace = 0;
|
||||||
|
let mut minSpace = u32::MAX;
|
||||||
|
let mut j = position + 1;
|
||||||
|
while j < end {
|
||||||
|
// for (int j = position + 1; j < end; j += 2) {
|
||||||
|
let currentCounter = theCounters[j];
|
||||||
|
if currentCounter < minSpace {
|
||||||
|
minSpace = currentCounter;
|
||||||
|
}
|
||||||
|
if currentCounter > maxSpace {
|
||||||
|
maxSpace = currentCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
j += 2;
|
||||||
|
}
|
||||||
|
let thresholdSpace = (minSpace + maxSpace) / 2;
|
||||||
|
|
||||||
|
let mut bitmask = 1 << 7;
|
||||||
|
let mut pattern = 0;
|
||||||
|
for i in 0..7 {
|
||||||
|
// for (int i = 0; i < 7; i++) {
|
||||||
|
let threshold = if (i & 1) == 0 {
|
||||||
|
thresholdBar
|
||||||
|
} else {
|
||||||
|
thresholdSpace
|
||||||
|
};
|
||||||
|
bitmask >>= 1;
|
||||||
|
if theCounters[position + i] > threshold {
|
||||||
|
pattern |= bitmask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..Self::CHARACTER_ENCODINGS.len() {
|
||||||
|
// for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
|
||||||
|
if Self::CHARACTER_ENCODINGS[i] == pattern {
|
||||||
|
return i as i32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
183
src/oned/ean_manufacturer_org_support.rs
Normal file
183
src/oned/ean_manufacturer_org_support.rs
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records EAN prefix to GS1 Member Organization, where the member organization
|
||||||
|
* correlates strongly with a country. This is an imperfect means of identifying
|
||||||
|
* a country of origin by EAN-13 barcode value. See
|
||||||
|
* <a href="http://en.wikipedia.org/wiki/List_of_GS1_country_codes">
|
||||||
|
* http://en.wikipedia.org/wiki/List_of_GS1_country_codes</a>.
|
||||||
|
*
|
||||||
|
* @author Sean Owen
|
||||||
|
*/
|
||||||
|
pub struct EANManufacturerOrgSupport {
|
||||||
|
ranges: Vec<Vec<u32>>, //= new ArrayList<>();
|
||||||
|
countryIdentifiers: Vec<String>, // = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for EANManufacturerOrgSupport {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ranges: Default::default(),
|
||||||
|
countryIdentifiers: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EANManufacturerOrgSupport {
|
||||||
|
pub fn lookupCountryIdentifier(&mut self, productCode: &str) -> Option<String> {
|
||||||
|
self.initIfNeeded();
|
||||||
|
let prefix = productCode[0..3].parse::<u32>().expect("must parse prefix");
|
||||||
|
// let prefix = Integer.parseInt(productCode.substring(0, 3));
|
||||||
|
let max = self.ranges.len();
|
||||||
|
for i in 0..max {
|
||||||
|
// for (int i = 0; i < max; i++) {
|
||||||
|
let range = self.ranges.get(i).expect("must have index i or fail");
|
||||||
|
let start = range[0];
|
||||||
|
if prefix < start {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let end = if range.len() == 1 { start } else { range[1] };
|
||||||
|
if prefix <= end {
|
||||||
|
return Some(
|
||||||
|
self.countryIdentifiers
|
||||||
|
.get(i)
|
||||||
|
.expect("must have index i or fail")
|
||||||
|
.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add(&mut self, range: Vec<u32>, id: String) {
|
||||||
|
self.ranges.push(range);
|
||||||
|
self.countryIdentifiers.push(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initIfNeeded(&mut self) {
|
||||||
|
if !self.ranges.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.add(vec![0, 19], "US/CA".to_owned());
|
||||||
|
self.add(vec![30, 39], "US".to_owned());
|
||||||
|
self.add(vec![60, 139], "US/CA".to_owned());
|
||||||
|
self.add(vec![300, 379], "FR".to_owned());
|
||||||
|
self.add(vec![380], "BG".to_owned());
|
||||||
|
self.add(vec![383], "SI".to_owned());
|
||||||
|
self.add(vec![385], "HR".to_owned());
|
||||||
|
self.add(vec![387], "BA".to_owned());
|
||||||
|
self.add(vec![400, 440], "DE".to_owned());
|
||||||
|
self.add(vec![450, 459], "JP".to_owned());
|
||||||
|
self.add(vec![460, 469], "RU".to_owned());
|
||||||
|
self.add(vec![471], "TW".to_owned());
|
||||||
|
self.add(vec![474], "EE".to_owned());
|
||||||
|
self.add(vec![475], "LV".to_owned());
|
||||||
|
self.add(vec![476], "AZ".to_owned());
|
||||||
|
self.add(vec![477], "LT".to_owned());
|
||||||
|
self.add(vec![478], "UZ".to_owned());
|
||||||
|
self.add(vec![479], "LK".to_owned());
|
||||||
|
self.add(vec![480], "PH".to_owned());
|
||||||
|
self.add(vec![481], "BY".to_owned());
|
||||||
|
self.add(vec![482], "UA".to_owned());
|
||||||
|
self.add(vec![484], "MD".to_owned());
|
||||||
|
self.add(vec![485], "AM".to_owned());
|
||||||
|
self.add(vec![486], "GE".to_owned());
|
||||||
|
self.add(vec![487], "KZ".to_owned());
|
||||||
|
self.add(vec![489], "HK".to_owned());
|
||||||
|
self.add(vec![490, 499], "JP".to_owned());
|
||||||
|
self.add(vec![500, 509], "GB".to_owned());
|
||||||
|
self.add(vec![520], "GR".to_owned());
|
||||||
|
self.add(vec![528], "LB".to_owned());
|
||||||
|
self.add(vec![529], "CY".to_owned());
|
||||||
|
self.add(vec![531], "MK".to_owned());
|
||||||
|
self.add(vec![535], "MT".to_owned());
|
||||||
|
self.add(vec![539], "IE".to_owned());
|
||||||
|
self.add(vec![540, 549], "BE/LU".to_owned());
|
||||||
|
self.add(vec![560], "PT".to_owned());
|
||||||
|
self.add(vec![569], "IS".to_owned());
|
||||||
|
self.add(vec![570, 579], "DK".to_owned());
|
||||||
|
self.add(vec![590], "PL".to_owned());
|
||||||
|
self.add(vec![594], "RO".to_owned());
|
||||||
|
self.add(vec![599], "HU".to_owned());
|
||||||
|
self.add(vec![600, 601], "ZA".to_owned());
|
||||||
|
self.add(vec![603], "GH".to_owned());
|
||||||
|
self.add(vec![608], "BH".to_owned());
|
||||||
|
self.add(vec![609], "MU".to_owned());
|
||||||
|
self.add(vec![611], "MA".to_owned());
|
||||||
|
self.add(vec![613], "DZ".to_owned());
|
||||||
|
self.add(vec![616], "KE".to_owned());
|
||||||
|
self.add(vec![618], "CI".to_owned());
|
||||||
|
self.add(vec![619], "TN".to_owned());
|
||||||
|
self.add(vec![621], "SY".to_owned());
|
||||||
|
self.add(vec![622], "EG".to_owned());
|
||||||
|
self.add(vec![624], "LY".to_owned());
|
||||||
|
self.add(vec![625], "JO".to_owned());
|
||||||
|
self.add(vec![626], "IR".to_owned());
|
||||||
|
self.add(vec![627], "KW".to_owned());
|
||||||
|
self.add(vec![628], "SA".to_owned());
|
||||||
|
self.add(vec![629], "AE".to_owned());
|
||||||
|
self.add(vec![640, 649], "FI".to_owned());
|
||||||
|
self.add(vec![690, 695], "CN".to_owned());
|
||||||
|
self.add(vec![700, 709], "NO".to_owned());
|
||||||
|
self.add(vec![729], "IL".to_owned());
|
||||||
|
self.add(vec![730, 739], "SE".to_owned());
|
||||||
|
self.add(vec![740], "GT".to_owned());
|
||||||
|
self.add(vec![741], "SV".to_owned());
|
||||||
|
self.add(vec![742], "HN".to_owned());
|
||||||
|
self.add(vec![743], "NI".to_owned());
|
||||||
|
self.add(vec![744], "CR".to_owned());
|
||||||
|
self.add(vec![745], "PA".to_owned());
|
||||||
|
self.add(vec![746], "DO".to_owned());
|
||||||
|
self.add(vec![750], "MX".to_owned());
|
||||||
|
self.add(vec![754, 755], "CA".to_owned());
|
||||||
|
self.add(vec![759], "VE".to_owned());
|
||||||
|
self.add(vec![760, 769], "CH".to_owned());
|
||||||
|
self.add(vec![770], "CO".to_owned());
|
||||||
|
self.add(vec![773], "UY".to_owned());
|
||||||
|
self.add(vec![775], "PE".to_owned());
|
||||||
|
self.add(vec![777], "BO".to_owned());
|
||||||
|
self.add(vec![779], "AR".to_owned());
|
||||||
|
self.add(vec![780], "CL".to_owned());
|
||||||
|
self.add(vec![784], "PY".to_owned());
|
||||||
|
self.add(vec![785], "PE".to_owned());
|
||||||
|
self.add(vec![786], "EC".to_owned());
|
||||||
|
self.add(vec![789, 790], "BR".to_owned());
|
||||||
|
self.add(vec![800, 839], "IT".to_owned());
|
||||||
|
self.add(vec![840, 849], "ES".to_owned());
|
||||||
|
self.add(vec![850], "CU".to_owned());
|
||||||
|
self.add(vec![858], "SK".to_owned());
|
||||||
|
self.add(vec![859], "CZ".to_owned());
|
||||||
|
self.add(vec![860], "YU".to_owned());
|
||||||
|
self.add(vec![865], "MN".to_owned());
|
||||||
|
self.add(vec![867], "KP".to_owned());
|
||||||
|
self.add(vec![868, 869], "TR".to_owned());
|
||||||
|
self.add(vec![870, 879], "NL".to_owned());
|
||||||
|
self.add(vec![880], "KR".to_owned());
|
||||||
|
self.add(vec![885], "TH".to_owned());
|
||||||
|
self.add(vec![888], "SG".to_owned());
|
||||||
|
self.add(vec![890], "IN".to_owned());
|
||||||
|
self.add(vec![893], "VN".to_owned());
|
||||||
|
self.add(vec![896], "PK".to_owned());
|
||||||
|
self.add(vec![899], "ID".to_owned());
|
||||||
|
self.add(vec![900, 919], "AT".to_owned());
|
||||||
|
self.add(vec![930, 939], "AU".to_owned());
|
||||||
|
self.add(vec![940, 949], "AZ".to_owned());
|
||||||
|
self.add(vec![955], "MY".to_owned());
|
||||||
|
self.add(vec![958], "MO".to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,10 @@
|
|||||||
|
mod one_d_reader;
|
||||||
pub mod rss;
|
pub mod rss;
|
||||||
|
|
||||||
|
pub use one_d_reader::*;
|
||||||
|
|
||||||
|
mod ean_manufacturer_org_support;
|
||||||
|
pub use ean_manufacturer_org_support::*;
|
||||||
|
|
||||||
|
mod coda_bar_reader;
|
||||||
|
pub use coda_bar_reader::*;
|
||||||
|
|||||||
284
src/oned/one_d_reader.rs
Normal file
284
src/oned/one_d_reader.rs
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* 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 crate::{
|
||||||
|
common::BitArray, BinaryBitmap, DecodeHintType, DecodingHintDictionary, Exceptions,
|
||||||
|
RXingResult, RXingResultMetadataType, RXingResultMetadataValue, RXingResultPoint, Reader,
|
||||||
|
ResultPoint,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates functionality and implementation that is common to all families
|
||||||
|
* of one-dimensional barcodes.
|
||||||
|
*
|
||||||
|
* @author dswitkin@google.com (Daniel Switkin)
|
||||||
|
* @author Sean Owen
|
||||||
|
*/
|
||||||
|
pub trait OneDReader: Reader {
|
||||||
|
/**
|
||||||
|
* We're going to examine rows from the middle outward, searching alternately above and below the
|
||||||
|
* middle, and farther out each time. rowStep is the number of rows between each successive
|
||||||
|
* attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
|
||||||
|
* middle + rowStep, then middle - (2 * rowStep), etc.
|
||||||
|
* rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
|
||||||
|
* decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
|
||||||
|
* image if "trying harder".
|
||||||
|
*
|
||||||
|
* @param image The image to decode
|
||||||
|
* @param hints Any hints that were requested
|
||||||
|
* @return The contents of the decoded barcode
|
||||||
|
* @throws NotFoundException Any spontaneous errors which occur
|
||||||
|
*/
|
||||||
|
fn doDecode(
|
||||||
|
&mut self,
|
||||||
|
image: &BinaryBitmap,
|
||||||
|
hints: &DecodingHintDictionary,
|
||||||
|
) -> Result<RXingResult, Exceptions> {
|
||||||
|
let mut hints = hints.clone();
|
||||||
|
let width = image.getWidth();
|
||||||
|
let height = image.getHeight();
|
||||||
|
let mut row = BitArray::with_size(width);
|
||||||
|
|
||||||
|
let tryHarder = hints.contains_key(&DecodeHintType::TRY_HARDER);
|
||||||
|
let rowStep = 1.max(height >> (if tryHarder { 8 } else { 5 }));
|
||||||
|
let maxLines;
|
||||||
|
if tryHarder {
|
||||||
|
maxLines = height; // Look at the whole image, not just the center
|
||||||
|
} else {
|
||||||
|
maxLines = 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image
|
||||||
|
}
|
||||||
|
|
||||||
|
let middle = height / 2;
|
||||||
|
for x in 0..maxLines {
|
||||||
|
// for (int x = 0; x < maxLines; x++) {
|
||||||
|
|
||||||
|
// Scanning from the middle out. Determine which row we're looking at next:
|
||||||
|
let rowStepsAboveOrBelow = (x + 1) / 2;
|
||||||
|
let isAbove = (x & 0x01) == 0; // i.e. is x even?
|
||||||
|
let rowNumber: isize = middle as isize
|
||||||
|
+ rowStep as isize
|
||||||
|
* (if isAbove {
|
||||||
|
rowStepsAboveOrBelow as isize
|
||||||
|
} else {
|
||||||
|
-(rowStepsAboveOrBelow as isize)
|
||||||
|
});
|
||||||
|
if rowNumber < 0 || rowNumber >= height as isize {
|
||||||
|
// Oops, if we run off the top or bottom, stop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate black point for this row and load it:
|
||||||
|
let mut row = if let Ok(res) = image.getBlackRow(rowNumber as usize, &mut row) {
|
||||||
|
res
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
// try {
|
||||||
|
// row = image.getBlackRow(rowNumber, row);
|
||||||
|
// } catch (NotFoundException ignored) {
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
|
||||||
|
// handle decoding upside down barcodes.
|
||||||
|
for attempt in 0..2 {
|
||||||
|
// for (int attempt = 0; attempt < 2; attempt++) {
|
||||||
|
if attempt == 1 {
|
||||||
|
// trying again?
|
||||||
|
row.reverse(); // reverse the row and continue
|
||||||
|
// This means we will only ever draw result points *once* in the life of this method
|
||||||
|
// since we want to avoid drawing the wrong points after flipping the row, and,
|
||||||
|
// don't want to clutter with noise from every single row scan -- just the scans
|
||||||
|
// that start on the center line.
|
||||||
|
if hints.contains_key(&DecodeHintType::NEED_RESULT_POINT_CALLBACK) {
|
||||||
|
// let newHints = HashMap::new();
|
||||||
|
// newHints.putAll(hints);
|
||||||
|
// newHints.remove(&DecodeHintType::NEED_RESULT_POINT_CALLBACK);
|
||||||
|
hints.remove(&DecodeHintType::NEED_RESULT_POINT_CALLBACK);
|
||||||
|
// hints = newHints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//try {
|
||||||
|
// Look for a barcode
|
||||||
|
let Ok(mut result) = self.decodeRow(rowNumber as u32, &row, &hints) else {
|
||||||
|
continue
|
||||||
|
};
|
||||||
|
// We found our barcode
|
||||||
|
if attempt == 1 {
|
||||||
|
// But it was upside down, so note that
|
||||||
|
result.putMetadata(
|
||||||
|
RXingResultMetadataType::ORIENTATION,
|
||||||
|
RXingResultMetadataValue::Orientation(180),
|
||||||
|
);
|
||||||
|
// And remember to flip the result points horizontally.
|
||||||
|
let points = result.getRXingResultPointsMut();
|
||||||
|
if !points.is_empty() && points.len() >= 2 {
|
||||||
|
points[0] = RXingResultPoint::new(
|
||||||
|
width as f32 - points[0].getX() - 1.0,
|
||||||
|
points[0].getY(),
|
||||||
|
);
|
||||||
|
points[1] = RXingResultPoint::new(
|
||||||
|
width as f32 - points[1].getX() - 1.0,
|
||||||
|
points[1].getY(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(result);
|
||||||
|
// } catch (ReaderException re) {
|
||||||
|
// // continue -- just couldn't decode this row
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records the size of successive runs of white and black pixels in a row, starting at a given point.
|
||||||
|
* The values are recorded in the given array, and the number of runs recorded is equal to the size
|
||||||
|
* of the array. If the row starts on a white pixel at the given start point, then the first count
|
||||||
|
* recorded is the run of white pixels starting from that point; likewise it is the count of a run
|
||||||
|
* of black pixels if the row begin on a black pixels at that point.
|
||||||
|
*
|
||||||
|
* @param row row to count from
|
||||||
|
* @param start offset into row to start at
|
||||||
|
* @param counters array into which to record counts
|
||||||
|
* @throws NotFoundException if counters cannot be filled entirely from row before running out
|
||||||
|
* of pixels
|
||||||
|
*/
|
||||||
|
fn recordPattern(row: &BitArray, start: usize, counters: &mut [u32]) -> Result<(), Exceptions> {
|
||||||
|
let numCounters = counters.len();
|
||||||
|
// Arrays.fill(counters, 0, numCounters, 0);
|
||||||
|
counters.fill(0);
|
||||||
|
let end = row.getSize();
|
||||||
|
if start >= end {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
let mut isWhite = !row.get(start);
|
||||||
|
let mut counterPosition = 0;
|
||||||
|
let mut i = start;
|
||||||
|
while i < end {
|
||||||
|
if row.get(i) != isWhite {
|
||||||
|
counters[counterPosition] += 1;
|
||||||
|
} else {
|
||||||
|
counterPosition += 1;
|
||||||
|
if counterPosition == numCounters {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
counters[counterPosition] = 1;
|
||||||
|
isWhite = !isWhite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
// If we read fully the last section of pixels and filled up our counters -- or filled
|
||||||
|
// the last counter but ran off the side of the image, OK. Otherwise, a problem.
|
||||||
|
if !(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end)) {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recordPatternInReverse(
|
||||||
|
row: &BitArray,
|
||||||
|
start: usize,
|
||||||
|
counters: &mut [u32],
|
||||||
|
) -> Result<(), Exceptions> {
|
||||||
|
let mut start = start;
|
||||||
|
// This could be more efficient I guess
|
||||||
|
let mut numTransitionsLeft = counters.len() as isize;
|
||||||
|
let mut last = row.get(start);
|
||||||
|
while start > 0 && numTransitionsLeft >= 0 {
|
||||||
|
start -= 1;
|
||||||
|
if row.get(start) != last {
|
||||||
|
numTransitionsLeft -= 1;
|
||||||
|
last = !last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if numTransitionsLeft >= 0 {
|
||||||
|
return Err(Exceptions::NotFoundException("".to_owned()));
|
||||||
|
}
|
||||||
|
Self::recordPattern(row, start + 1, counters)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines how closely a set of observed counts of runs of black/white values matches a given
|
||||||
|
* target pattern. This is reported as the ratio of the total variance from the expected pattern
|
||||||
|
* proportions across all pattern elements, to the length of the pattern.
|
||||||
|
*
|
||||||
|
* @param counters observed counters
|
||||||
|
* @param pattern expected pattern
|
||||||
|
* @param maxIndividualVariance The most any counter can differ before we give up
|
||||||
|
* @return ratio of total variance between counters and pattern compared to total pattern size
|
||||||
|
*/
|
||||||
|
fn patternMatchVariance(counters: &[u32], pattern: &[u32], maxIndividualVariance: f32) -> f32 {
|
||||||
|
let mut maxIndividualVariance = maxIndividualVariance;
|
||||||
|
let numCounters = counters.len();
|
||||||
|
let mut total = 0.0;
|
||||||
|
let mut patternLength = 0;
|
||||||
|
for i in 0..numCounters {
|
||||||
|
// for (int i = 0; i < numCounters; i++) {
|
||||||
|
total += counters[i] as f32;
|
||||||
|
patternLength += pattern[i];
|
||||||
|
}
|
||||||
|
if total < patternLength as f32 {
|
||||||
|
// If we don't even have one pixel per unit of bar width, assume this is too small
|
||||||
|
// to reliably match, so fail:
|
||||||
|
return f32::INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
let unitBarWidth = total / patternLength as f32;
|
||||||
|
maxIndividualVariance *= unitBarWidth as f32;
|
||||||
|
|
||||||
|
let mut totalVariance = 0.0;
|
||||||
|
for x in 0..numCounters {
|
||||||
|
// for (int x = 0; x < numCounters; x++) {
|
||||||
|
let counter = counters[x];
|
||||||
|
let scaledPattern = (pattern[x] as f32) * unitBarWidth;
|
||||||
|
let variance = if (counter as f32) > scaledPattern {
|
||||||
|
counter as f32 - scaledPattern
|
||||||
|
} else {
|
||||||
|
scaledPattern - counter as f32
|
||||||
|
};
|
||||||
|
if variance > maxIndividualVariance {
|
||||||
|
return f32::INFINITY;
|
||||||
|
}
|
||||||
|
totalVariance += variance;
|
||||||
|
}
|
||||||
|
return totalVariance / total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Attempts to decode a one-dimensional barcode format given a single row of
|
||||||
|
* an image.</p>
|
||||||
|
*
|
||||||
|
* @param rowNumber row number from top of the row
|
||||||
|
* @param row the black/white pixel data of the row
|
||||||
|
* @param hints decode hints
|
||||||
|
* @return {@link RXingResult} containing encoded string and start/end of barcode
|
||||||
|
* @throws NotFoundException if no potential barcode is found
|
||||||
|
* @throws ChecksumException if a potential barcode is found but does not pass its checksum
|
||||||
|
* @throws FormatException if a potential barcode is found but format is invalid
|
||||||
|
*/
|
||||||
|
fn decodeRow(
|
||||||
|
&mut self,
|
||||||
|
rowNumber: u32,
|
||||||
|
row: &BitArray,
|
||||||
|
hints: &DecodingHintDictionary,
|
||||||
|
) -> Result<RXingResult, Exceptions>;
|
||||||
|
}
|
||||||
@@ -134,6 +134,10 @@ impl RXingResult {
|
|||||||
return &self.resultPoints;
|
return &self.resultPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getRXingResultPointsMut(&mut self) -> &mut Vec<RXingResultPoint> {
|
||||||
|
&mut self.resultPoints
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@link BarcodeFormat} representing the format of the barcode that was decoded
|
* @return {@link BarcodeFormat} representing the format of the barcode that was decoded
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,21 +14,23 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.zxing.oned;
|
use rxing::{BarcodeFormat, MultiFormatReader, oned::{OneDReader, CodaBarReader}};
|
||||||
|
|
||||||
import com.google.zxing.BarcodeFormat;
|
mod common;
|
||||||
import com.google.zxing.MultiFormatReader;
|
|
||||||
import com.google.zxing.common.AbstractBlackBoxTestCase;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Sean Owen
|
* @author Sean Owen
|
||||||
*/
|
*/
|
||||||
public final class CodabarBlackBox1TestCase extends AbstractBlackBoxTestCase {
|
#[test]
|
||||||
|
fn codabar_black_box1_test_case() {
|
||||||
public CodabarBlackBox1TestCase() {
|
let mut tester = common::AbstractBlackBoxTestCase::new(
|
||||||
super("src/test/resources/blackbox/codabar-1", new MultiFormatReader(), BarcodeFormat.CODABAR);
|
"test_resources/blackbox/codabar-1",
|
||||||
addTest(11, 11, 0.0f);
|
CodaBarReader::new(),
|
||||||
addTest(11, 11, 180.0f);
|
BarcodeFormat::CODABAR,
|
||||||
}
|
);
|
||||||
|
// super("src/test/resources/blackbox/codabar-1", new MultiFormatReader(), BarcodeFormat.CODABAR);
|
||||||
|
tester.add_test(11, 11, 0.0);
|
||||||
|
tester.add_test(11, 11, 180.0);
|
||||||
|
|
||||||
|
tester.test_black_box();
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user