mirror of
https://github.com/starovoid/rxing.git
synced 2026-07-26 20:32:34 +00:00
oned moved
This commit is contained in:
@@ -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.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
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 decodeRowResult;
|
||||
private int[] counters;
|
||||
private int counterLength;
|
||||
|
||||
public CodaBarReader() {
|
||||
decodeRowResult = new StringBuilder(20);
|
||||
counters = new int[80];
|
||||
counterLength = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints) throws NotFoundException {
|
||||
|
||||
Arrays.fill(counters, 0);
|
||||
setCounters(row);
|
||||
int startOffset = findStartPattern();
|
||||
int nextStart = startOffset;
|
||||
|
||||
decodeRowResult.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.
|
||||
decodeRowResult.append((char) charOffset);
|
||||
nextStart += 8;
|
||||
// Stop as soon as we see the end character.
|
||||
if (decodeRowResult.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 < decodeRowResult.length(); i++) {
|
||||
decodeRowResult.setCharAt(i, ALPHABET[decodeRowResult.charAt(i)]);
|
||||
}
|
||||
// Ensure a valid start and end character
|
||||
char startchar = decodeRowResult.charAt(0);
|
||||
if (!arrayContains(STARTEND_ENCODING, startchar)) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
char endchar = decodeRowResult.charAt(decodeRowResult.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 (decodeRowResult.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)) {
|
||||
decodeRowResult.deleteCharAt(decodeRowResult.length() - 1);
|
||||
decodeRowResult.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;
|
||||
|
||||
Result result = new Result(
|
||||
decodeRowResult.toString(),
|
||||
null,
|
||||
new ResultPoint[]{
|
||||
new ResultPoint(left, rowNumber),
|
||||
new ResultPoint(right, rowNumber)},
|
||||
BarcodeFormat.CODABAR);
|
||||
result.putMetadata(ResultMetadataType.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 = decodeRowResult.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[decodeRowResult.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[decodeRowResult.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,140 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This class renders CodaBar as {@code boolean[]}.
|
||||
*
|
||||
* @author dsbnatut@gmail.com (Kazuki Nishiura)
|
||||
*/
|
||||
public final class CodaBarWriter extends OneDimensionalCodeWriter {
|
||||
|
||||
private static final char[] START_END_CHARS = {'A', 'B', 'C', 'D'};
|
||||
private static final char[] ALT_START_END_CHARS = {'T', 'N', '*', 'E'};
|
||||
private static final char[] CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED = {'/', ':', '+', '.'};
|
||||
private static final char DEFAULT_GUARD = START_END_CHARS[0];
|
||||
|
||||
@Override
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return Collections.singleton(BarcodeFormat.CODABAR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] encode(String contents) {
|
||||
|
||||
if (contents.length() < 2) {
|
||||
// Can't have a start/end guard, so tentatively add default guards
|
||||
contents = DEFAULT_GUARD + contents + DEFAULT_GUARD;
|
||||
} else {
|
||||
// Verify input and calculate decoded length.
|
||||
char firstChar = Character.toUpperCase(contents.charAt(0));
|
||||
char lastChar = Character.toUpperCase(contents.charAt(contents.length() - 1));
|
||||
boolean startsNormal = CodaBarReader.arrayContains(START_END_CHARS, firstChar);
|
||||
boolean endsNormal = CodaBarReader.arrayContains(START_END_CHARS, lastChar);
|
||||
boolean startsAlt = CodaBarReader.arrayContains(ALT_START_END_CHARS, firstChar);
|
||||
boolean endsAlt = CodaBarReader.arrayContains(ALT_START_END_CHARS, lastChar);
|
||||
if (startsNormal) {
|
||||
if (!endsNormal) {
|
||||
throw new IllegalArgumentException("Invalid start/end guards: " + contents);
|
||||
}
|
||||
// else already has valid start/end
|
||||
} else if (startsAlt) {
|
||||
if (!endsAlt) {
|
||||
throw new IllegalArgumentException("Invalid start/end guards: " + contents);
|
||||
}
|
||||
// else already has valid start/end
|
||||
} else {
|
||||
// Doesn't start with a guard
|
||||
if (endsNormal || endsAlt) {
|
||||
throw new IllegalArgumentException("Invalid start/end guards: " + contents);
|
||||
}
|
||||
// else doesn't end with guard either, so add a default
|
||||
contents = DEFAULT_GUARD + contents + DEFAULT_GUARD;
|
||||
}
|
||||
}
|
||||
|
||||
// The start character and the end character are decoded to 10 length each.
|
||||
int resultLength = 20;
|
||||
for (int i = 1; i < contents.length() - 1; i++) {
|
||||
if (Character.isDigit(contents.charAt(i)) || contents.charAt(i) == '-' || contents.charAt(i) == '$') {
|
||||
resultLength += 9;
|
||||
} else if (CodaBarReader.arrayContains(CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED, contents.charAt(i))) {
|
||||
resultLength += 10;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Cannot encode : '" + contents.charAt(i) + '\'');
|
||||
}
|
||||
}
|
||||
// A blank is placed between each character.
|
||||
resultLength += contents.length() - 1;
|
||||
|
||||
boolean[] result = new boolean[resultLength];
|
||||
int position = 0;
|
||||
for (int index = 0; index < contents.length(); index++) {
|
||||
char c = Character.toUpperCase(contents.charAt(index));
|
||||
if (index == 0 || index == contents.length() - 1) {
|
||||
// The start/end chars are not in the CodaBarReader.ALPHABET.
|
||||
switch (c) {
|
||||
case 'T':
|
||||
c = 'A';
|
||||
break;
|
||||
case 'N':
|
||||
c = 'B';
|
||||
break;
|
||||
case '*':
|
||||
c = 'C';
|
||||
break;
|
||||
case 'E':
|
||||
c = 'D';
|
||||
break;
|
||||
}
|
||||
}
|
||||
int code = 0;
|
||||
for (int i = 0; i < CodaBarReader.ALPHABET.length; i++) {
|
||||
// Found any, because I checked above.
|
||||
if (c == CodaBarReader.ALPHABET[i]) {
|
||||
code = CodaBarReader.CHARACTER_ENCODINGS[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
boolean color = true;
|
||||
int counter = 0;
|
||||
int bit = 0;
|
||||
while (bit < 7) { // A character consists of 7 digit.
|
||||
result[position] = color;
|
||||
position++;
|
||||
if (((code >> (6 - bit)) & 1) == 0 || counter == 1) {
|
||||
color = !color; // Flip the color.
|
||||
bit++;
|
||||
counter = 0;
|
||||
} else {
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
if (index < contents.length() - 1) {
|
||||
result[position] = false;
|
||||
position++;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Decodes Code 128 barcodes.</p>
|
||||
*
|
||||
* @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 Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> 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<Byte> 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);
|
||||
}
|
||||
Result resultObject = new Result(
|
||||
result.toString(),
|
||||
rawBytes,
|
||||
new ResultPoint[]{
|
||||
new ResultPoint(left, rowNumber),
|
||||
new ResultPoint(right, rowNumber)},
|
||||
BarcodeFormat.CODE_128);
|
||||
resultObject.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]C" + symbologyModifier);
|
||||
return resultObject;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,567 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This object renders a CODE128 code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author erik.barbara@gmail.com (Erik Barbara)
|
||||
*/
|
||||
public final class Code128Writer extends OneDimensionalCodeWriter {
|
||||
|
||||
private static final int CODE_START_A = 103;
|
||||
private static final int CODE_START_B = 104;
|
||||
private static final int CODE_START_C = 105;
|
||||
private static final int CODE_CODE_A = 101;
|
||||
private static final int CODE_CODE_B = 100;
|
||||
private static final int CODE_CODE_C = 99;
|
||||
private static final int CODE_STOP = 106;
|
||||
|
||||
// Dummy characters used to specify control characters in input
|
||||
private static final char ESCAPE_FNC_1 = '\u00f1';
|
||||
private static final char ESCAPE_FNC_2 = '\u00f2';
|
||||
private static final char ESCAPE_FNC_3 = '\u00f3';
|
||||
private static final char ESCAPE_FNC_4 = '\u00f4';
|
||||
|
||||
private static final int CODE_FNC_1 = 102; // Code A, Code B, Code C
|
||||
private static final int CODE_FNC_2 = 97; // Code A, Code B
|
||||
private static final int CODE_FNC_3 = 96; // Code A, Code B
|
||||
private static final int CODE_FNC_4_A = 101; // Code A
|
||||
private static final int CODE_FNC_4_B = 100; // Code B
|
||||
|
||||
// Results of minimal lookahead for code C
|
||||
private enum CType {
|
||||
UNCODABLE,
|
||||
ONE_DIGIT,
|
||||
TWO_DIGITS,
|
||||
FNC_1
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return Collections.singleton(BarcodeFormat.CODE_128);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] encode(String contents) {
|
||||
return encode(contents, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean[] encode(String contents, Map<EncodeHintType,?> hints) {
|
||||
|
||||
int forcedCodeSet = check(contents, hints);
|
||||
|
||||
boolean hasCompactionHint = hints != null && hints.containsKey(EncodeHintType.CODE128_COMPACT) &&
|
||||
Boolean.parseBoolean(hints.get(EncodeHintType.CODE128_COMPACT).toString());
|
||||
|
||||
return hasCompactionHint ? new MinimalEncoder().encode(contents) : encodeFast(contents, forcedCodeSet);
|
||||
}
|
||||
|
||||
private static int check(String contents, Map<EncodeHintType,?> hints) {
|
||||
int length = contents.length();
|
||||
// Check length
|
||||
if (length < 1 || length > 80) {
|
||||
throw new IllegalArgumentException(
|
||||
"Contents length should be between 1 and 80 characters, but got " + length);
|
||||
}
|
||||
|
||||
// Check for forced code set hint.
|
||||
int forcedCodeSet = -1;
|
||||
if (hints != null && hints.containsKey(EncodeHintType.FORCE_CODE_SET)) {
|
||||
String codeSetHint = hints.get(EncodeHintType.FORCE_CODE_SET).toString();
|
||||
switch (codeSetHint) {
|
||||
case "A":
|
||||
forcedCodeSet = CODE_CODE_A;
|
||||
break;
|
||||
case "B":
|
||||
forcedCodeSet = CODE_CODE_B;
|
||||
break;
|
||||
case "C":
|
||||
forcedCodeSet = CODE_CODE_C;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported code set hint: " + codeSetHint);
|
||||
}
|
||||
}
|
||||
|
||||
// Check content
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = contents.charAt(i);
|
||||
// check for non ascii characters that are not special GS1 characters
|
||||
switch (c) {
|
||||
// special function characters
|
||||
case ESCAPE_FNC_1:
|
||||
case ESCAPE_FNC_2:
|
||||
case ESCAPE_FNC_3:
|
||||
case ESCAPE_FNC_4:
|
||||
break;
|
||||
// non ascii characters
|
||||
default:
|
||||
if (c > 127) {
|
||||
// no full Latin-1 character set available at the moment
|
||||
// shift and manual code change are not supported
|
||||
throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) c);
|
||||
}
|
||||
}
|
||||
// check characters for compatibility with forced code set
|
||||
switch (forcedCodeSet) {
|
||||
case CODE_CODE_A:
|
||||
// allows no ascii above 95 (no lower caps, no special symbols)
|
||||
if (c > 95 && c <= 127) {
|
||||
throw new IllegalArgumentException("Bad character in input for forced code set A: ASCII value=" + (int) c);
|
||||
}
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
// allows no ascii below 32 (terminal symbols)
|
||||
if (c <= 32) {
|
||||
throw new IllegalArgumentException("Bad character in input for forced code set B: ASCII value=" + (int) c);
|
||||
}
|
||||
break;
|
||||
case CODE_CODE_C:
|
||||
// allows only numbers and no FNC 2/3/4
|
||||
if (c < 48 || (c > 57 && c <= 127) || c == ESCAPE_FNC_2 || c == ESCAPE_FNC_3 || c == ESCAPE_FNC_4) {
|
||||
throw new IllegalArgumentException("Bad character in input for forced code set C: ASCII value=" + (int) c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return forcedCodeSet;
|
||||
}
|
||||
|
||||
private static boolean[] encodeFast(String contents, int forcedCodeSet) {
|
||||
int length = contents.length();
|
||||
|
||||
Collection<int[]> patterns = new ArrayList<>(); // temporary storage for patterns
|
||||
int checkSum = 0;
|
||||
int checkWeight = 1;
|
||||
int codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C)
|
||||
int position = 0; // position in contents
|
||||
|
||||
while (position < length) {
|
||||
//Select code to use
|
||||
int newCodeSet;
|
||||
if (forcedCodeSet == -1) {
|
||||
newCodeSet = chooseCode(contents, position, codeSet);
|
||||
} else {
|
||||
newCodeSet = forcedCodeSet;
|
||||
}
|
||||
|
||||
//Get the pattern index
|
||||
int patternIndex;
|
||||
if (newCodeSet == codeSet) {
|
||||
// Encode the current character
|
||||
// First handle escapes
|
||||
switch (contents.charAt(position)) {
|
||||
case ESCAPE_FNC_1:
|
||||
patternIndex = CODE_FNC_1;
|
||||
break;
|
||||
case ESCAPE_FNC_2:
|
||||
patternIndex = CODE_FNC_2;
|
||||
break;
|
||||
case ESCAPE_FNC_3:
|
||||
patternIndex = CODE_FNC_3;
|
||||
break;
|
||||
case ESCAPE_FNC_4:
|
||||
if (codeSet == CODE_CODE_A) {
|
||||
patternIndex = CODE_FNC_4_A;
|
||||
} else {
|
||||
patternIndex = CODE_FNC_4_B;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Then handle normal characters otherwise
|
||||
switch (codeSet) {
|
||||
case CODE_CODE_A:
|
||||
patternIndex = contents.charAt(position) - ' ';
|
||||
if (patternIndex < 0) {
|
||||
// everything below a space character comes behind the underscore in the code patterns table
|
||||
patternIndex += '`';
|
||||
}
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
patternIndex = contents.charAt(position) - ' ';
|
||||
break;
|
||||
default:
|
||||
// CODE_CODE_C
|
||||
if (position + 1 == length) {
|
||||
// this is the last character, but the encoding is C, which always encodes two characers
|
||||
throw new IllegalArgumentException("Bad number of characters for digit only encoding.");
|
||||
}
|
||||
patternIndex = Integer.parseInt(contents.substring(position, position + 2));
|
||||
position++; // Also incremented below
|
||||
break;
|
||||
}
|
||||
}
|
||||
position++;
|
||||
} else {
|
||||
// Should we change the current code?
|
||||
// Do we have a code set?
|
||||
if (codeSet == 0) {
|
||||
// No, we don't have a code set
|
||||
switch (newCodeSet) {
|
||||
case CODE_CODE_A:
|
||||
patternIndex = CODE_START_A;
|
||||
break;
|
||||
case CODE_CODE_B:
|
||||
patternIndex = CODE_START_B;
|
||||
break;
|
||||
default:
|
||||
patternIndex = CODE_START_C;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Yes, we have a code set
|
||||
patternIndex = newCodeSet;
|
||||
}
|
||||
codeSet = newCodeSet;
|
||||
}
|
||||
|
||||
// Get the pattern
|
||||
patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]);
|
||||
|
||||
// Compute checksum
|
||||
checkSum += patternIndex * checkWeight;
|
||||
if (position != 0) {
|
||||
checkWeight++;
|
||||
}
|
||||
}
|
||||
return produceResult(patterns, checkSum);
|
||||
}
|
||||
|
||||
static boolean[] produceResult(Collection<int[]> patterns, int checkSum) {
|
||||
// Compute and append checksum
|
||||
checkSum %= 103;
|
||||
patterns.add(Code128Reader.CODE_PATTERNS[checkSum]);
|
||||
|
||||
// Append stop code
|
||||
patterns.add(Code128Reader.CODE_PATTERNS[CODE_STOP]);
|
||||
|
||||
// Compute code width
|
||||
int codeWidth = 0;
|
||||
for (int[] pattern : patterns) {
|
||||
for (int width : pattern) {
|
||||
codeWidth += width;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute result
|
||||
boolean[] result = new boolean[codeWidth];
|
||||
int pos = 0;
|
||||
for (int[] pattern : patterns) {
|
||||
pos += appendPattern(result, pos, pattern, true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static CType findCType(CharSequence value, int start) {
|
||||
int last = value.length();
|
||||
if (start >= last) {
|
||||
return CType.UNCODABLE;
|
||||
}
|
||||
char c = value.charAt(start);
|
||||
if (c == ESCAPE_FNC_1) {
|
||||
return CType.FNC_1;
|
||||
}
|
||||
if (c < '0' || c > '9') {
|
||||
return CType.UNCODABLE;
|
||||
}
|
||||
if (start + 1 >= last) {
|
||||
return CType.ONE_DIGIT;
|
||||
}
|
||||
c = value.charAt(start + 1);
|
||||
if (c < '0' || c > '9') {
|
||||
return CType.ONE_DIGIT;
|
||||
}
|
||||
return CType.TWO_DIGITS;
|
||||
}
|
||||
|
||||
private static int chooseCode(CharSequence value, int start, int oldCode) {
|
||||
CType lookahead = findCType(value, start);
|
||||
if (lookahead == CType.ONE_DIGIT) {
|
||||
if (oldCode == CODE_CODE_A) {
|
||||
return CODE_CODE_A;
|
||||
}
|
||||
return CODE_CODE_B;
|
||||
}
|
||||
if (lookahead == CType.UNCODABLE) {
|
||||
if (start < value.length()) {
|
||||
char c = value.charAt(start);
|
||||
if (c < ' ' || (oldCode == CODE_CODE_A && (c < '`' || (c >= ESCAPE_FNC_1 && c <= ESCAPE_FNC_4)))) {
|
||||
// can continue in code A, encodes ASCII 0 to 95 or FNC1 to FNC4
|
||||
return CODE_CODE_A;
|
||||
}
|
||||
}
|
||||
return CODE_CODE_B; // no choice
|
||||
}
|
||||
if (oldCode == CODE_CODE_A && lookahead == CType.FNC_1) {
|
||||
return CODE_CODE_A;
|
||||
}
|
||||
if (oldCode == CODE_CODE_C) { // can continue in code C
|
||||
return CODE_CODE_C;
|
||||
}
|
||||
if (oldCode == CODE_CODE_B) {
|
||||
if (lookahead == CType.FNC_1) {
|
||||
return CODE_CODE_B; // can continue in code B
|
||||
}
|
||||
// Seen two consecutive digits, see what follows
|
||||
lookahead = findCType(value, start + 2);
|
||||
if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) {
|
||||
return CODE_CODE_B; // not worth switching now
|
||||
}
|
||||
if (lookahead == CType.FNC_1) { // two digits, then FNC_1...
|
||||
lookahead = findCType(value, start + 3);
|
||||
if (lookahead == CType.TWO_DIGITS) { // then two more digits, switch
|
||||
return CODE_CODE_C;
|
||||
} else {
|
||||
return CODE_CODE_B; // otherwise not worth switching
|
||||
}
|
||||
}
|
||||
// At this point, there are at least 4 consecutive digits.
|
||||
// Look ahead to choose whether to switch now or on the next round.
|
||||
int index = start + 4;
|
||||
while ((lookahead = findCType(value, index)) == CType.TWO_DIGITS) {
|
||||
index += 2;
|
||||
}
|
||||
if (lookahead == CType.ONE_DIGIT) { // odd number of digits, switch later
|
||||
return CODE_CODE_B;
|
||||
}
|
||||
return CODE_CODE_C; // even number of digits, switch now
|
||||
}
|
||||
// Here oldCode == 0, which means we are choosing the initial code
|
||||
if (lookahead == CType.FNC_1) { // ignore FNC_1
|
||||
lookahead = findCType(value, start + 1);
|
||||
}
|
||||
if (lookahead == CType.TWO_DIGITS) { // at least two digits, start in code C
|
||||
return CODE_CODE_C;
|
||||
}
|
||||
return CODE_CODE_B;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes minimally using Divide-And-Conquer with Memoization
|
||||
**/
|
||||
private static final class MinimalEncoder {
|
||||
|
||||
private enum Charset { A, B, C, NONE }
|
||||
private enum Latch { A, B, C, SHIFT, NONE }
|
||||
|
||||
static final String A = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u0000\u0001\u0002" +
|
||||
"\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011" +
|
||||
"\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F" +
|
||||
"\u00FF";
|
||||
static final String B = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqr" +
|
||||
"stuvwxyz{|}~\u007F\u00FF";
|
||||
|
||||
private static final int CODE_SHIFT = 98;
|
||||
|
||||
private int[][] memoizedCost;
|
||||
private Latch[][] minPath;
|
||||
|
||||
private boolean[] encode(String contents) {
|
||||
memoizedCost = new int[4][contents.length()];
|
||||
minPath = new Latch[4][contents.length()];
|
||||
|
||||
encode(contents, Charset.NONE, 0);
|
||||
|
||||
Collection<int[]> patterns = new ArrayList<>();
|
||||
int[] checkSum = new int[] {0};
|
||||
int[] checkWeight = new int[] {1};
|
||||
int length = contents.length();
|
||||
Charset charset = Charset.NONE;
|
||||
for (int i = 0; i < length; i++) {
|
||||
Latch latch = minPath[charset.ordinal()][i];
|
||||
switch (latch) {
|
||||
case A:
|
||||
charset = Charset.A;
|
||||
addPattern(patterns, i == 0 ? CODE_START_A : CODE_CODE_A, checkSum, checkWeight, i);
|
||||
break;
|
||||
case B:
|
||||
charset = Charset.B;
|
||||
addPattern(patterns, i == 0 ? CODE_START_B : CODE_CODE_B, checkSum, checkWeight, i);
|
||||
break;
|
||||
case C:
|
||||
charset = Charset.C;
|
||||
addPattern(patterns, i == 0 ? CODE_START_C : CODE_CODE_C, checkSum, checkWeight, i);
|
||||
break;
|
||||
case SHIFT:
|
||||
addPattern(patterns, CODE_SHIFT, checkSum, checkWeight, i);
|
||||
break;
|
||||
}
|
||||
if (charset == Charset.C) {
|
||||
if (contents.charAt(i) == ESCAPE_FNC_1) {
|
||||
addPattern(patterns, CODE_FNC_1, checkSum, checkWeight, i);
|
||||
} else {
|
||||
addPattern(patterns, Integer.parseInt(contents.substring(i, i + 2)), checkSum, checkWeight, i);
|
||||
assert i + 1 < length; //the algorithm never leads to a single trailing digit in character set C
|
||||
if (i + 1 < length) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else { // charset A or B
|
||||
int patternIndex;
|
||||
switch (contents.charAt(i)) {
|
||||
case ESCAPE_FNC_1:
|
||||
patternIndex = CODE_FNC_1;
|
||||
break;
|
||||
case ESCAPE_FNC_2:
|
||||
patternIndex = CODE_FNC_2;
|
||||
break;
|
||||
case ESCAPE_FNC_3:
|
||||
patternIndex = CODE_FNC_3;
|
||||
break;
|
||||
case ESCAPE_FNC_4:
|
||||
if ((charset == Charset.A && latch != Latch.SHIFT) ||
|
||||
(charset == Charset.B && latch == Latch.SHIFT)) {
|
||||
patternIndex = CODE_FNC_4_A;
|
||||
} else {
|
||||
patternIndex = CODE_FNC_4_B;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
patternIndex = contents.charAt(i) - ' ';
|
||||
}
|
||||
if ((charset == Charset.A && latch != Latch.SHIFT) ||
|
||||
(charset == Charset.B && latch == Latch.SHIFT)) {
|
||||
if (patternIndex < 0) {
|
||||
patternIndex += '`';
|
||||
}
|
||||
}
|
||||
addPattern(patterns, patternIndex, checkSum, checkWeight, i);
|
||||
}
|
||||
}
|
||||
memoizedCost = null;
|
||||
minPath = null;
|
||||
return produceResult(patterns, checkSum[0]);
|
||||
}
|
||||
|
||||
private static void addPattern(Collection<int[]> patterns,
|
||||
int patternIndex,
|
||||
int[] checkSum,
|
||||
int[] checkWeight,
|
||||
int position) {
|
||||
patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]);
|
||||
if (position != 0) {
|
||||
checkWeight[0]++;
|
||||
}
|
||||
checkSum[0] += patternIndex * checkWeight[0];
|
||||
}
|
||||
|
||||
private static boolean isDigit(char c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
private boolean canEncode(CharSequence contents, Charset charset,int position) {
|
||||
char c = contents.charAt(position);
|
||||
switch (charset) {
|
||||
case A: return c == ESCAPE_FNC_1 ||
|
||||
c == ESCAPE_FNC_2 ||
|
||||
c == ESCAPE_FNC_3 ||
|
||||
c == ESCAPE_FNC_4 ||
|
||||
A.indexOf(c) >= 0;
|
||||
case B: return c == ESCAPE_FNC_1 ||
|
||||
c == ESCAPE_FNC_2 ||
|
||||
c == ESCAPE_FNC_3 ||
|
||||
c == ESCAPE_FNC_4 ||
|
||||
B.indexOf(c) >= 0;
|
||||
case C: return c == ESCAPE_FNC_1 ||
|
||||
(position + 1 < contents.length() &&
|
||||
isDigit(c) &&
|
||||
isDigit(contents.charAt(position + 1)));
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the string starting at position position starting with the character set charset
|
||||
**/
|
||||
private int encode(CharSequence contents, Charset charset, int position) {
|
||||
assert position < contents.length();
|
||||
int mCost = memoizedCost[charset.ordinal()][position];
|
||||
if (mCost > 0) {
|
||||
return mCost;
|
||||
}
|
||||
|
||||
int minCost = Integer.MAX_VALUE;
|
||||
Latch minLatch = Latch.NONE;
|
||||
boolean atEnd = position + 1 >= contents.length();
|
||||
|
||||
Charset[] sets = new Charset[] { Charset.A, Charset.B };
|
||||
for (int i = 0; i <= 1; i++) {
|
||||
if (canEncode(contents, sets[i], position)) {
|
||||
int cost = 1;
|
||||
Latch latch = Latch.NONE;
|
||||
if (charset != sets[i]) {
|
||||
cost++;
|
||||
latch = Latch.valueOf(sets[i].toString());
|
||||
}
|
||||
if (!atEnd) {
|
||||
cost += encode(contents, sets[i], position + 1);
|
||||
}
|
||||
if (cost < minCost) {
|
||||
minCost = cost;
|
||||
minLatch = latch;
|
||||
}
|
||||
cost = 1;
|
||||
if (charset == sets[(i + 1) % 2]) {
|
||||
cost++;
|
||||
latch = Latch.SHIFT;
|
||||
if (!atEnd) {
|
||||
cost += encode(contents, charset, position + 1);
|
||||
}
|
||||
if (cost < minCost) {
|
||||
minCost = cost;
|
||||
minLatch = latch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (canEncode(contents, Charset.C, position)) {
|
||||
int cost = 1;
|
||||
Latch latch = Latch.NONE;
|
||||
if (charset != Charset.C) {
|
||||
cost++;
|
||||
latch = Latch.C;
|
||||
}
|
||||
int advance = contents.charAt(position) == ESCAPE_FNC_1 ? 1 : 2;
|
||||
if (position + advance < contents.length()) {
|
||||
cost += encode(contents, Charset.C, position + advance);
|
||||
}
|
||||
if (cost < minCost) {
|
||||
minCost = cost;
|
||||
minLatch = latch;
|
||||
}
|
||||
}
|
||||
if (minCost == Integer.MAX_VALUE) {
|
||||
throw new IllegalArgumentException("Bad character in input: ASCII value=" + (int) contents.charAt(position));
|
||||
}
|
||||
memoizedCost[charset.ordinal()][position] = minCost;
|
||||
minPath[charset.ordinal()][position] = minLatch;
|
||||
return minCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,340 +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.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Decodes Code 39 barcodes. Supports "Full ASCII Code 39" if USE_CODE_39_EXTENDED_MODE is set.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @see Code93Reader
|
||||
*/
|
||||
public final class Code39Reader extends OneDReader {
|
||||
|
||||
static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%";
|
||||
|
||||
/**
|
||||
* These represent the encodings of characters, as patterns of wide and narrow bars.
|
||||
* The 9 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 = {
|
||||
0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064, // 0-9
|
||||
0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C, // A-J
|
||||
0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016, // K-T
|
||||
0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8, // U-$
|
||||
0x0A2, 0x08A, 0x02A // /-%
|
||||
};
|
||||
|
||||
static final int ASTERISK_ENCODING = 0x094;
|
||||
|
||||
private final boolean usingCheckDigit;
|
||||
private final boolean extendedMode;
|
||||
private final StringBuilder decodeRowResult;
|
||||
private final int[] counters;
|
||||
|
||||
/**
|
||||
* Creates a reader that assumes all encoded data is data, and does not treat the final
|
||||
* character as a check digit. It will not decoded "extended Code 39" sequences.
|
||||
*/
|
||||
public Code39Reader() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reader that can be configured to check the last character as a check digit.
|
||||
* It will not decoded "extended Code 39" sequences.
|
||||
*
|
||||
* @param usingCheckDigit if true, treat the last data character as a check digit, not
|
||||
* data, and verify that the checksum passes.
|
||||
*/
|
||||
public Code39Reader(boolean usingCheckDigit) {
|
||||
this(usingCheckDigit, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a reader that can be configured to check the last character as a check digit,
|
||||
* or optionally attempt to decode "extended Code 39" sequences that are used to encode
|
||||
* the full ASCII character set.
|
||||
*
|
||||
* @param usingCheckDigit if true, treat the last data character as a check digit, not
|
||||
* data, and verify that the checksum passes.
|
||||
* @param extendedMode if true, will attempt to decode extended Code 39 sequences in the
|
||||
* text.
|
||||
*/
|
||||
public Code39Reader(boolean usingCheckDigit, boolean extendedMode) {
|
||||
this.usingCheckDigit = usingCheckDigit;
|
||||
this.extendedMode = extendedMode;
|
||||
decodeRowResult = new StringBuilder(20);
|
||||
counters = new int[9];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, ChecksumException, FormatException {
|
||||
|
||||
int[] theCounters = counters;
|
||||
Arrays.fill(theCounters, 0);
|
||||
StringBuilder result = decodeRowResult;
|
||||
result.setLength(0);
|
||||
|
||||
int[] start = findAsteriskPattern(row, theCounters);
|
||||
// Read off white space
|
||||
int nextStart = row.getNextSet(start[1]);
|
||||
int end = row.getSize();
|
||||
|
||||
char decodedChar;
|
||||
int lastStart;
|
||||
do {
|
||||
recordPattern(row, nextStart, theCounters);
|
||||
int pattern = toNarrowWidePattern(theCounters);
|
||||
if (pattern < 0) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
decodedChar = patternToChar(pattern);
|
||||
result.append(decodedChar);
|
||||
lastStart = nextStart;
|
||||
for (int counter : theCounters) {
|
||||
nextStart += counter;
|
||||
}
|
||||
// Read off white space
|
||||
nextStart = row.getNextSet(nextStart);
|
||||
} while (decodedChar != '*');
|
||||
result.setLength(result.length() - 1); // remove asterisk
|
||||
|
||||
// Look for whitespace after pattern:
|
||||
int lastPatternSize = 0;
|
||||
for (int counter : theCounters) {
|
||||
lastPatternSize += counter;
|
||||
}
|
||||
int whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize;
|
||||
// If 50% of last pattern size, following last pattern, is not whitespace, fail
|
||||
// (but if it's whitespace to the very end of the image, that's OK)
|
||||
if (nextStart != end && (whiteSpaceAfterEnd * 2) < lastPatternSize) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
if (usingCheckDigit) {
|
||||
int max = result.length() - 1;
|
||||
int total = 0;
|
||||
for (int i = 0; i < max; i++) {
|
||||
total += ALPHABET_STRING.indexOf(decodeRowResult.charAt(i));
|
||||
}
|
||||
if (result.charAt(max) != ALPHABET_STRING.charAt(total % 43)) {
|
||||
throw ChecksumException.getChecksumInstance();
|
||||
}
|
||||
result.setLength(max);
|
||||
}
|
||||
|
||||
if (result.length() == 0) {
|
||||
// false positive
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
String resultString;
|
||||
if (extendedMode) {
|
||||
resultString = decodeExtended(result);
|
||||
} else {
|
||||
resultString = result.toString();
|
||||
}
|
||||
|
||||
float left = (start[1] + start[0]) / 2.0f;
|
||||
float right = lastStart + lastPatternSize / 2.0f;
|
||||
|
||||
Result resultObject = new Result(
|
||||
resultString,
|
||||
null,
|
||||
new ResultPoint[]{
|
||||
new ResultPoint(left, rowNumber),
|
||||
new ResultPoint(right, rowNumber)},
|
||||
BarcodeFormat.CODE_39);
|
||||
resultObject.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]A0");
|
||||
return resultObject;
|
||||
}
|
||||
|
||||
private static int[] findAsteriskPattern(BitArray row, int[] counters) throws NotFoundException {
|
||||
int width = row.getSize();
|
||||
int rowOffset = row.getNextSet(0);
|
||||
|
||||
int counterPosition = 0;
|
||||
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) {
|
||||
// Look for whitespace before start pattern, >= 50% of width of start pattern
|
||||
if (toNarrowWidePattern(counters) == ASTERISK_ENCODING &&
|
||||
row.isRange(Math.max(0, patternStart - ((i - patternStart) / 2)), patternStart, false)) {
|
||||
return new int[]{patternStart, i};
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
// For efficiency, returns -1 on failure. Not throwing here saved as many as 700 exceptions
|
||||
// per image when using some of our blackbox images.
|
||||
private static int toNarrowWidePattern(int[] counters) {
|
||||
int numCounters = counters.length;
|
||||
int maxNarrowCounter = 0;
|
||||
int wideCounters;
|
||||
do {
|
||||
int minCounter = Integer.MAX_VALUE;
|
||||
for (int counter : counters) {
|
||||
if (counter < minCounter && counter > maxNarrowCounter) {
|
||||
minCounter = counter;
|
||||
}
|
||||
}
|
||||
maxNarrowCounter = minCounter;
|
||||
wideCounters = 0;
|
||||
int totalWideCountersWidth = 0;
|
||||
int pattern = 0;
|
||||
for (int i = 0; i < numCounters; i++) {
|
||||
int counter = counters[i];
|
||||
if (counter > maxNarrowCounter) {
|
||||
pattern |= 1 << (numCounters - 1 - i);
|
||||
wideCounters++;
|
||||
totalWideCountersWidth += counter;
|
||||
}
|
||||
}
|
||||
if (wideCounters == 3) {
|
||||
// Found 3 wide counters, but are they close enough in width?
|
||||
// We can perform a cheap, conservative check to see if any individual
|
||||
// counter is more than 1.5 times the average:
|
||||
for (int i = 0; i < numCounters && wideCounters > 0; i++) {
|
||||
int counter = counters[i];
|
||||
if (counter > maxNarrowCounter) {
|
||||
wideCounters--;
|
||||
// totalWideCountersWidth = 3 * average, so this checks if counter >= 3/2 * average
|
||||
if ((counter * 2) >= totalWideCountersWidth) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
} while (wideCounters > 3);
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static char patternToChar(int pattern) throws NotFoundException {
|
||||
for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
|
||||
if (CHARACTER_ENCODINGS[i] == pattern) {
|
||||
return ALPHABET_STRING.charAt(i);
|
||||
}
|
||||
}
|
||||
if (pattern == ASTERISK_ENCODING) {
|
||||
return '*';
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
private static String decodeExtended(CharSequence encoded) throws FormatException {
|
||||
int length = encoded.length();
|
||||
StringBuilder decoded = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = encoded.charAt(i);
|
||||
if (c == '+' || c == '$' || c == '%' || c == '/') {
|
||||
char next = encoded.charAt(i + 1);
|
||||
char decodedChar = '\0';
|
||||
switch (c) {
|
||||
case '+':
|
||||
// +A to +Z map to a to z
|
||||
if (next >= 'A' && next <= 'Z') {
|
||||
decodedChar = (char) (next + 32);
|
||||
} else {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
break;
|
||||
case '$':
|
||||
// $A to $Z map to control codes SH to SB
|
||||
if (next >= 'A' && next <= 'Z') {
|
||||
decodedChar = (char) (next - 64);
|
||||
} else {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
break;
|
||||
case '%':
|
||||
// %A to %E map to control codes ESC to US
|
||||
if (next >= 'A' && next <= 'E') {
|
||||
decodedChar = (char) (next - 38);
|
||||
} else if (next >= 'F' && next <= 'J') {
|
||||
decodedChar = (char) (next - 11);
|
||||
} else if (next >= 'K' && next <= 'O') {
|
||||
decodedChar = (char) (next + 16);
|
||||
} else if (next >= 'P' && next <= 'T') {
|
||||
decodedChar = (char) (next + 43);
|
||||
} else if (next == 'U') {
|
||||
decodedChar = (char) 0;
|
||||
} else if (next == 'V') {
|
||||
decodedChar = '@';
|
||||
} else if (next == 'W') {
|
||||
decodedChar = '`';
|
||||
} else if (next == 'X' || next == 'Y' || next == 'Z') {
|
||||
decodedChar = (char) 127;
|
||||
} else {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
break;
|
||||
case '/':
|
||||
// /A to /O map to ! to , and /Z maps to :
|
||||
if (next >= 'A' && next <= 'O') {
|
||||
decodedChar = (char) (next - 32);
|
||||
} else if (next == 'Z') {
|
||||
decodedChar = ':';
|
||||
} else {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
break;
|
||||
}
|
||||
decoded.append(decodedChar);
|
||||
// bump up i again since we read two characters
|
||||
i++;
|
||||
} else {
|
||||
decoded.append(c);
|
||||
}
|
||||
}
|
||||
return decoded.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This object renders a CODE39 code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author erik.barbara@gmail.com (Erik Barbara)
|
||||
*/
|
||||
public final class Code39Writer extends OneDimensionalCodeWriter {
|
||||
|
||||
@Override
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return Collections.singleton(BarcodeFormat.CODE_39);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] encode(String contents) {
|
||||
int length = contents.length();
|
||||
if (length > 80) {
|
||||
throw new IllegalArgumentException(
|
||||
"Requested contents should be less than 80 digits long, but got " + length);
|
||||
}
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int indexInString = Code39Reader.ALPHABET_STRING.indexOf(contents.charAt(i));
|
||||
if (indexInString < 0) {
|
||||
contents = tryToConvertToExtendedMode(contents);
|
||||
length = contents.length();
|
||||
if (length > 80) {
|
||||
throw new IllegalArgumentException("Requested contents should be less than 80 digits long, but got " +
|
||||
length + " (extended full ASCII mode)");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int[] widths = new int[9];
|
||||
int codeWidth = 24 + 1 + (13 * length);
|
||||
boolean[] result = new boolean[codeWidth];
|
||||
toIntArray(Code39Reader.ASTERISK_ENCODING, widths);
|
||||
int pos = appendPattern(result, 0, widths, true);
|
||||
int[] narrowWhite = {1};
|
||||
pos += appendPattern(result, pos, narrowWhite, false);
|
||||
//append next character to byte matrix
|
||||
for (int i = 0; i < length; i++) {
|
||||
int indexInString = Code39Reader.ALPHABET_STRING.indexOf(contents.charAt(i));
|
||||
toIntArray(Code39Reader.CHARACTER_ENCODINGS[indexInString], widths);
|
||||
pos += appendPattern(result, pos, widths, true);
|
||||
pos += appendPattern(result, pos, narrowWhite, false);
|
||||
}
|
||||
toIntArray(Code39Reader.ASTERISK_ENCODING, widths);
|
||||
appendPattern(result, pos, widths, true);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void toIntArray(int a, int[] toReturn) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
int temp = a & (1 << (8 - i));
|
||||
toReturn[i] = temp == 0 ? 1 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
private static String tryToConvertToExtendedMode(String contents) {
|
||||
int length = contents.length();
|
||||
StringBuilder extendedContent = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char character = contents.charAt(i);
|
||||
switch (character) {
|
||||
case '\u0000':
|
||||
extendedContent.append("%U");
|
||||
break;
|
||||
case ' ':
|
||||
case '-':
|
||||
case '.':
|
||||
extendedContent.append(character);
|
||||
break;
|
||||
case '@':
|
||||
extendedContent.append("%V");
|
||||
break;
|
||||
case '`':
|
||||
extendedContent.append("%W");
|
||||
break;
|
||||
default:
|
||||
if (character <= 26) {
|
||||
extendedContent.append('$');
|
||||
extendedContent.append((char) ('A' + (character - 1)));
|
||||
} else if (character < ' ') {
|
||||
extendedContent.append('%');
|
||||
extendedContent.append((char) ('A' + (character - 27)));
|
||||
} else if (character <= ',' || character == '/' || character == ':') {
|
||||
extendedContent.append('/');
|
||||
extendedContent.append((char) ('A' + (character - 33)));
|
||||
} else if (character <= '9') {
|
||||
extendedContent.append((char) ('0' + (character - 48)));
|
||||
} else if (character <= '?') {
|
||||
extendedContent.append('%');
|
||||
extendedContent.append((char) ('F' + (character - 59)));
|
||||
} else if (character <= 'Z') {
|
||||
extendedContent.append((char) ('A' + (character - 65)));
|
||||
} else if (character <= '_') {
|
||||
extendedContent.append('%');
|
||||
extendedContent.append((char) ('K' + (character - 91)));
|
||||
} else if (character <= 'z') {
|
||||
extendedContent.append('+');
|
||||
extendedContent.append((char) ('A' + (character - 97)));
|
||||
} else if (character <= 127) {
|
||||
extendedContent.append('%');
|
||||
extendedContent.append((char) ('P' + (character - 123)));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Requested content contains a non-encodable character: '" + contents.charAt(i) + "'");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return extendedContent.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.ChecksumException;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Decodes Code 93 barcodes.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
* @see Code39Reader
|
||||
*/
|
||||
public final class Code93Reader extends OneDReader {
|
||||
|
||||
// Note that 'abcd' are dummy characters in place of control characters.
|
||||
static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*";
|
||||
private static final char[] ALPHABET = ALPHABET_STRING.toCharArray();
|
||||
|
||||
/**
|
||||
* These represent the encodings of characters, as patterns of wide and narrow bars.
|
||||
* The 9 least-significant bits of each int correspond to the pattern of wide and narrow.
|
||||
*/
|
||||
static final int[] CHARACTER_ENCODINGS = {
|
||||
0x114, 0x148, 0x144, 0x142, 0x128, 0x124, 0x122, 0x150, 0x112, 0x10A, // 0-9
|
||||
0x1A8, 0x1A4, 0x1A2, 0x194, 0x192, 0x18A, 0x168, 0x164, 0x162, 0x134, // A-J
|
||||
0x11A, 0x158, 0x14C, 0x146, 0x12C, 0x116, 0x1B4, 0x1B2, 0x1AC, 0x1A6, // K-T
|
||||
0x196, 0x19A, 0x16C, 0x166, 0x136, 0x13A, // U-Z
|
||||
0x12E, 0x1D4, 0x1D2, 0x1CA, 0x16E, 0x176, 0x1AE, // - - %
|
||||
0x126, 0x1DA, 0x1D6, 0x132, 0x15E, // Control chars? $-*
|
||||
};
|
||||
static final int ASTERISK_ENCODING = CHARACTER_ENCODINGS[47];
|
||||
|
||||
private final StringBuilder decodeRowResult;
|
||||
private final int[] counters;
|
||||
|
||||
public Code93Reader() {
|
||||
decodeRowResult = new StringBuilder(20);
|
||||
counters = new int[6];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, ChecksumException, FormatException {
|
||||
|
||||
int[] start = findAsteriskPattern(row);
|
||||
// Read off white space
|
||||
int nextStart = row.getNextSet(start[1]);
|
||||
int end = row.getSize();
|
||||
|
||||
int[] theCounters = counters;
|
||||
Arrays.fill(theCounters, 0);
|
||||
StringBuilder result = decodeRowResult;
|
||||
result.setLength(0);
|
||||
|
||||
char decodedChar;
|
||||
int lastStart;
|
||||
do {
|
||||
recordPattern(row, nextStart, theCounters);
|
||||
int pattern = toPattern(theCounters);
|
||||
if (pattern < 0) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
decodedChar = patternToChar(pattern);
|
||||
result.append(decodedChar);
|
||||
lastStart = nextStart;
|
||||
for (int counter : theCounters) {
|
||||
nextStart += counter;
|
||||
}
|
||||
// Read off white space
|
||||
nextStart = row.getNextSet(nextStart);
|
||||
} while (decodedChar != '*');
|
||||
result.deleteCharAt(result.length() - 1); // remove asterisk
|
||||
|
||||
int lastPatternSize = 0;
|
||||
for (int counter : theCounters) {
|
||||
lastPatternSize += counter;
|
||||
}
|
||||
|
||||
// Should be at least one more black module
|
||||
if (nextStart == end || !row.get(nextStart)) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
if (result.length() < 2) {
|
||||
// false positive -- need at least 2 checksum digits
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
checkChecksums(result);
|
||||
// Remove checksum digits
|
||||
result.setLength(result.length() - 2);
|
||||
|
||||
String resultString = decodeExtended(result);
|
||||
|
||||
float left = (start[1] + start[0]) / 2.0f;
|
||||
float right = lastStart + lastPatternSize / 2.0f;
|
||||
|
||||
Result resultObject = new Result(
|
||||
resultString,
|
||||
null,
|
||||
new ResultPoint[]{
|
||||
new ResultPoint(left, rowNumber),
|
||||
new ResultPoint(right, rowNumber)},
|
||||
BarcodeFormat.CODE_93);
|
||||
resultObject.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]G0");
|
||||
return resultObject;
|
||||
}
|
||||
|
||||
private int[] findAsteriskPattern(BitArray row) throws NotFoundException {
|
||||
int width = row.getSize();
|
||||
int rowOffset = row.getNextSet(0);
|
||||
|
||||
Arrays.fill(counters, 0);
|
||||
int[] theCounters = counters;
|
||||
int patternStart = rowOffset;
|
||||
boolean isWhite = false;
|
||||
int patternLength = theCounters.length;
|
||||
|
||||
int counterPosition = 0;
|
||||
for (int i = rowOffset; i < width; i++) {
|
||||
if (row.get(i) != isWhite) {
|
||||
theCounters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == patternLength - 1) {
|
||||
if (toPattern(theCounters) == ASTERISK_ENCODING) {
|
||||
return new int[]{patternStart, i};
|
||||
}
|
||||
patternStart += theCounters[0] + theCounters[1];
|
||||
System.arraycopy(theCounters, 2, theCounters, 0, counterPosition - 1);
|
||||
theCounters[counterPosition - 1] = 0;
|
||||
theCounters[counterPosition] = 0;
|
||||
counterPosition--;
|
||||
} else {
|
||||
counterPosition++;
|
||||
}
|
||||
theCounters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
private static int toPattern(int[] counters) {
|
||||
int sum = 0;
|
||||
for (int counter : counters) {
|
||||
sum += counter;
|
||||
}
|
||||
int pattern = 0;
|
||||
int max = counters.length;
|
||||
for (int i = 0; i < max; i++) {
|
||||
int scaled = Math.round(counters[i] * 9.0f / sum);
|
||||
if (scaled < 1 || scaled > 4) {
|
||||
return -1;
|
||||
}
|
||||
if ((i & 0x01) == 0) {
|
||||
for (int j = 0; j < scaled; j++) {
|
||||
pattern = (pattern << 1) | 0x01;
|
||||
}
|
||||
} else {
|
||||
pattern <<= scaled;
|
||||
}
|
||||
}
|
||||
return pattern;
|
||||
}
|
||||
|
||||
private static char patternToChar(int pattern) throws NotFoundException {
|
||||
for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
|
||||
if (CHARACTER_ENCODINGS[i] == pattern) {
|
||||
return ALPHABET[i];
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
private static String decodeExtended(CharSequence encoded) throws FormatException {
|
||||
int length = encoded.length();
|
||||
StringBuilder decoded = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i++) {
|
||||
char c = encoded.charAt(i);
|
||||
if (c >= 'a' && c <= 'd') {
|
||||
if (i >= length - 1) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
char next = encoded.charAt(i + 1);
|
||||
char decodedChar = '\0';
|
||||
switch (c) {
|
||||
case 'd':
|
||||
// +A to +Z map to a to z
|
||||
if (next >= 'A' && next <= 'Z') {
|
||||
decodedChar = (char) (next + 32);
|
||||
} else {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
break;
|
||||
case 'a':
|
||||
// $A to $Z map to control codes SH to SB
|
||||
if (next >= 'A' && next <= 'Z') {
|
||||
decodedChar = (char) (next - 64);
|
||||
} else {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
break;
|
||||
case 'b':
|
||||
if (next >= 'A' && next <= 'E') {
|
||||
// %A to %E map to control codes ESC to USep
|
||||
decodedChar = (char) (next - 38);
|
||||
} else if (next >= 'F' && next <= 'J') {
|
||||
// %F to %J map to ; < = > ?
|
||||
decodedChar = (char) (next - 11);
|
||||
} else if (next >= 'K' && next <= 'O') {
|
||||
// %K to %O map to [ \ ] ^ _
|
||||
decodedChar = (char) (next + 16);
|
||||
} else if (next >= 'P' && next <= 'T') {
|
||||
// %P to %T map to { | } ~ DEL
|
||||
decodedChar = (char) (next + 43);
|
||||
} else if (next == 'U') {
|
||||
// %U map to NUL
|
||||
decodedChar = '\0';
|
||||
} else if (next == 'V') {
|
||||
// %V map to @
|
||||
decodedChar = '@';
|
||||
} else if (next == 'W') {
|
||||
// %W map to `
|
||||
decodedChar = '`';
|
||||
} else if (next >= 'X' && next <= 'Z') {
|
||||
// %X to %Z all map to DEL (127)
|
||||
decodedChar = 127;
|
||||
} else {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
// /A to /O map to ! to , and /Z maps to :
|
||||
if (next >= 'A' && next <= 'O') {
|
||||
decodedChar = (char) (next - 32);
|
||||
} else if (next == 'Z') {
|
||||
decodedChar = ':';
|
||||
} else {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
break;
|
||||
}
|
||||
decoded.append(decodedChar);
|
||||
// bump up i again since we read two characters
|
||||
i++;
|
||||
} else {
|
||||
decoded.append(c);
|
||||
}
|
||||
}
|
||||
return decoded.toString();
|
||||
}
|
||||
|
||||
private static void checkChecksums(CharSequence result) throws ChecksumException {
|
||||
int length = result.length();
|
||||
checkOneChecksum(result, length - 2, 20);
|
||||
checkOneChecksum(result, length - 1, 15);
|
||||
}
|
||||
|
||||
private static void checkOneChecksum(CharSequence result, int checkPosition, int weightMax)
|
||||
throws ChecksumException {
|
||||
int weight = 1;
|
||||
int total = 0;
|
||||
for (int i = checkPosition - 1; i >= 0; i--) {
|
||||
total += weight * ALPHABET_STRING.indexOf(result.charAt(i));
|
||||
if (++weight > weightMax) {
|
||||
weight = 1;
|
||||
}
|
||||
}
|
||||
if (result.charAt(checkPosition) != ALPHABET[total % 47]) {
|
||||
throw ChecksumException.getChecksumInstance();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Copyright 2015 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.zxing.oned;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This object renders a CODE93 code as a BitMatrix
|
||||
*/
|
||||
public class Code93Writer extends OneDimensionalCodeWriter {
|
||||
|
||||
@Override
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return Collections.singleton(BarcodeFormat.CODE_93);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param contents barcode contents to encode. It should not be encoded for extended characters.
|
||||
* @return a {@code boolean[]} of horizontal pixels (false = white, true = black)
|
||||
*/
|
||||
@Override
|
||||
public boolean[] encode(String contents) {
|
||||
contents = convertToExtended(contents);
|
||||
int length = contents.length();
|
||||
if (length > 80) {
|
||||
throw new IllegalArgumentException("Requested contents should be less than 80 digits long after " +
|
||||
"converting to extended encoding, but got " + length);
|
||||
}
|
||||
|
||||
//length of code + 2 start/stop characters + 2 checksums, each of 9 bits, plus a termination bar
|
||||
int codeWidth = (contents.length() + 2 + 2) * 9 + 1;
|
||||
|
||||
boolean[] result = new boolean[codeWidth];
|
||||
|
||||
//start character (*)
|
||||
int pos = appendPattern(result, 0, Code93Reader.ASTERISK_ENCODING);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int indexInString = Code93Reader.ALPHABET_STRING.indexOf(contents.charAt(i));
|
||||
pos += appendPattern(result, pos, Code93Reader.CHARACTER_ENCODINGS[indexInString]);
|
||||
}
|
||||
|
||||
//add two checksums
|
||||
int check1 = computeChecksumIndex(contents, 20);
|
||||
pos += appendPattern(result, pos, Code93Reader.CHARACTER_ENCODINGS[check1]);
|
||||
|
||||
//append the contents to reflect the first checksum added
|
||||
contents += Code93Reader.ALPHABET_STRING.charAt(check1);
|
||||
|
||||
int check2 = computeChecksumIndex(contents, 15);
|
||||
pos += appendPattern(result, pos, Code93Reader.CHARACTER_ENCODINGS[check2]);
|
||||
|
||||
//end character (*)
|
||||
pos += appendPattern(result, pos, Code93Reader.ASTERISK_ENCODING);
|
||||
|
||||
//termination bar (single black bar)
|
||||
result[pos] = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param target output to append to
|
||||
* @param pos start position
|
||||
* @param pattern pattern to append
|
||||
* @param startColor unused
|
||||
* @return 9
|
||||
* @deprecated without replacement; intended as an internal-only method
|
||||
*/
|
||||
@Deprecated
|
||||
protected static int appendPattern(boolean[] target, int pos, int[] pattern, boolean startColor) {
|
||||
for (int bit : pattern) {
|
||||
target[pos++] = bit != 0;
|
||||
}
|
||||
return 9;
|
||||
}
|
||||
|
||||
private static int appendPattern(boolean[] target, int pos, int a) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
int temp = a & (1 << (8 - i));
|
||||
target[pos + i] = temp != 0;
|
||||
}
|
||||
return 9;
|
||||
}
|
||||
|
||||
private static int computeChecksumIndex(String contents, int maxWeight) {
|
||||
int weight = 1;
|
||||
int total = 0;
|
||||
|
||||
for (int i = contents.length() - 1; i >= 0; i--) {
|
||||
int indexInString = Code93Reader.ALPHABET_STRING.indexOf(contents.charAt(i));
|
||||
total += indexInString * weight;
|
||||
if (++weight > maxWeight) {
|
||||
weight = 1;
|
||||
}
|
||||
}
|
||||
return total % 47;
|
||||
}
|
||||
|
||||
static String convertToExtended(String contents) {
|
||||
int length = contents.length();
|
||||
StringBuilder extendedContent = new StringBuilder(length * 2);
|
||||
for (int i = 0; i < length; i++) {
|
||||
char character = contents.charAt(i);
|
||||
// ($)=a, (%)=b, (/)=c, (+)=d. see Code93Reader.ALPHABET_STRING
|
||||
if (character == 0) {
|
||||
// NUL: (%)U
|
||||
extendedContent.append("bU");
|
||||
} else if (character <= 26) {
|
||||
// SOH - SUB: ($)A - ($)Z
|
||||
extendedContent.append('a');
|
||||
extendedContent.append((char) ('A' + character - 1));
|
||||
} else if (character <= 31) {
|
||||
// ESC - US: (%)A - (%)E
|
||||
extendedContent.append('b');
|
||||
extendedContent.append((char) ('A' + character - 27));
|
||||
} else if (character == ' ' || character == '$' || character == '%' || character == '+') {
|
||||
// space $ % +
|
||||
extendedContent.append(character);
|
||||
} else if (character <= ',') {
|
||||
// ! " # & ' ( ) * ,: (/)A - (/)L
|
||||
extendedContent.append('c');
|
||||
extendedContent.append((char) ('A' + character - '!'));
|
||||
} else if (character <= '9') {
|
||||
extendedContent.append(character);
|
||||
} else if (character == ':') {
|
||||
// :: (/)Z
|
||||
extendedContent.append("cZ");
|
||||
} else if (character <= '?') {
|
||||
// ; - ?: (%)F - (%)J
|
||||
extendedContent.append('b');
|
||||
extendedContent.append((char) ('F' + character - ';'));
|
||||
} else if (character == '@') {
|
||||
// @: (%)V
|
||||
extendedContent.append("bV");
|
||||
} else if (character <= 'Z') {
|
||||
// A - Z
|
||||
extendedContent.append(character);
|
||||
} else if (character <= '_') {
|
||||
// [ - _: (%)K - (%)O
|
||||
extendedContent.append('b');
|
||||
extendedContent.append((char) ('K' + character - '['));
|
||||
} else if (character == '`') {
|
||||
// `: (%)W
|
||||
extendedContent.append("bW");
|
||||
} else if (character <= 'z') {
|
||||
// a - z: (*)A - (*)Z
|
||||
extendedContent.append('d');
|
||||
extendedContent.append((char) ('A' + character - 'a'));
|
||||
} else if (character <= 127) {
|
||||
// { - DEL: (%)P - (%)T
|
||||
extendedContent.append('b');
|
||||
extendedContent.append((char) ('P' + character - '{'));
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Requested content contains a non-encodable character: '" + character + "'");
|
||||
}
|
||||
}
|
||||
return extendedContent.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,138 +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.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>Implements decoding of the EAN-13 format.</p>
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author Sean Owen
|
||||
* @author alasdair@google.com (Alasdair Mackintosh)
|
||||
*/
|
||||
public final class EAN13Reader extends UPCEANReader {
|
||||
|
||||
// For an EAN-13 barcode, the first digit is represented by the parities used
|
||||
// to encode the next six digits, according to the table below. For example,
|
||||
// if the barcode is 5 123456 789012 then the value of the first digit is
|
||||
// signified by using odd for '1', even for '2', even for '3', odd for '4',
|
||||
// odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13
|
||||
//
|
||||
// Parity of next 6 digits
|
||||
// Digit 0 1 2 3 4 5
|
||||
// 0 Odd Odd Odd Odd Odd Odd
|
||||
// 1 Odd Odd Even Odd Even Even
|
||||
// 2 Odd Odd Even Even Odd Even
|
||||
// 3 Odd Odd Even Even Even Odd
|
||||
// 4 Odd Even Odd Odd Even Even
|
||||
// 5 Odd Even Even Odd Odd Even
|
||||
// 6 Odd Even Even Even Odd Odd
|
||||
// 7 Odd Even Odd Even Odd Even
|
||||
// 8 Odd Even Odd Even Even Odd
|
||||
// 9 Odd Even Even Odd Even Odd
|
||||
//
|
||||
// Note that the encoding for '0' uses the same parity as a UPC barcode. Hence
|
||||
// a UPC barcode can be converted to an EAN-13 barcode by prepending a 0.
|
||||
//
|
||||
// The encoding is represented by the following array, which is a bit pattern
|
||||
// using Odd = 0 and Even = 1. For example, 5 is represented by:
|
||||
//
|
||||
// Odd Even Even Odd Odd Even
|
||||
// in binary:
|
||||
// 0 1 1 0 0 1 == 0x19
|
||||
//
|
||||
static final int[] FIRST_DIGIT_ENCODINGS = {
|
||||
0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A
|
||||
};
|
||||
|
||||
private final int[] decodeMiddleCounters;
|
||||
|
||||
public EAN13Reader() {
|
||||
decodeMiddleCounters = new int[4];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int decodeMiddle(BitArray row,
|
||||
int[] startRange,
|
||||
StringBuilder resultString) throws NotFoundException {
|
||||
int[] counters = decodeMiddleCounters;
|
||||
counters[0] = 0;
|
||||
counters[1] = 0;
|
||||
counters[2] = 0;
|
||||
counters[3] = 0;
|
||||
int end = row.getSize();
|
||||
int rowOffset = startRange[1];
|
||||
|
||||
int lgPatternFound = 0;
|
||||
|
||||
for (int x = 0; x < 6 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS);
|
||||
resultString.append((char) ('0' + bestMatch % 10));
|
||||
for (int counter : counters) {
|
||||
rowOffset += counter;
|
||||
}
|
||||
if (bestMatch >= 10) {
|
||||
lgPatternFound |= 1 << (5 - x);
|
||||
}
|
||||
}
|
||||
|
||||
determineFirstDigit(resultString, lgPatternFound);
|
||||
|
||||
int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
|
||||
rowOffset = middleRange[1];
|
||||
|
||||
for (int x = 0; x < 6 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
|
||||
resultString.append((char) ('0' + bestMatch));
|
||||
for (int counter : counters) {
|
||||
rowOffset += counter;
|
||||
}
|
||||
}
|
||||
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
BarcodeFormat getBarcodeFormat() {
|
||||
return BarcodeFormat.EAN_13;
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on pattern of odd-even ('L' and 'G') patterns used to encoded the explicitly-encoded
|
||||
* digits in a barcode, determines the implicitly encoded first digit and adds it to the
|
||||
* result string.
|
||||
*
|
||||
* @param resultString string to insert decoded first digit into
|
||||
* @param lgPatternFound int whose bits indicates the pattern of odd/even L/G patterns used to
|
||||
* encode digits
|
||||
* @throws NotFoundException if first digit cannot be determined
|
||||
*/
|
||||
private static void determineFirstDigit(StringBuilder resultString, int lgPatternFound)
|
||||
throws NotFoundException {
|
||||
for (int d = 0; d < 10; d++) {
|
||||
if (lgPatternFound == FIRST_DIGIT_ENCODINGS[d]) {
|
||||
resultString.insert(0, (char) ('0' + d));
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.FormatException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This object renders an EAN13 code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author aripollak@gmail.com (Ari Pollak)
|
||||
*/
|
||||
public final class EAN13Writer extends UPCEANWriter {
|
||||
|
||||
private static final int CODE_WIDTH = 3 + // start guard
|
||||
(7 * 6) + // left bars
|
||||
5 + // middle guard
|
||||
(7 * 6) + // right bars
|
||||
3; // end guard
|
||||
|
||||
@Override
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return Collections.singleton(BarcodeFormat.EAN_13);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] encode(String contents) {
|
||||
int length = contents.length();
|
||||
switch (length) {
|
||||
case 12:
|
||||
// No check digit present, calculate it and add it
|
||||
int check;
|
||||
try {
|
||||
check = UPCEANReader.getStandardUPCEANChecksum(contents);
|
||||
} catch (FormatException fe) {
|
||||
throw new IllegalArgumentException(fe);
|
||||
}
|
||||
contents += check;
|
||||
break;
|
||||
case 13:
|
||||
try {
|
||||
if (!UPCEANReader.checkStandardUPCEANChecksum(contents)) {
|
||||
throw new IllegalArgumentException("Contents do not pass checksum");
|
||||
}
|
||||
} catch (FormatException ignored) {
|
||||
throw new IllegalArgumentException("Illegal contents");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Requested contents should be 12 or 13 digits long, but got " + length);
|
||||
}
|
||||
|
||||
checkNumeric(contents);
|
||||
|
||||
int firstDigit = Character.digit(contents.charAt(0), 10);
|
||||
int parities = EAN13Reader.FIRST_DIGIT_ENCODINGS[firstDigit];
|
||||
boolean[] result = new boolean[CODE_WIDTH];
|
||||
int pos = 0;
|
||||
|
||||
pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
|
||||
|
||||
// See EAN13Reader for a description of how the first digit & left bars are encoded
|
||||
for (int i = 1; i <= 6; i++) {
|
||||
int digit = Character.digit(contents.charAt(i), 10);
|
||||
if ((parities >> (6 - i) & 1) == 1) {
|
||||
digit += 10;
|
||||
}
|
||||
pos += appendPattern(result, pos, UPCEANReader.L_AND_G_PATTERNS[digit], false);
|
||||
}
|
||||
|
||||
pos += appendPattern(result, pos, UPCEANReader.MIDDLE_PATTERN, false);
|
||||
|
||||
for (int i = 7; i <= 12; i++) {
|
||||
int digit = Character.digit(contents.charAt(i), 10);
|
||||
pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], true);
|
||||
}
|
||||
appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,75 +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.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>Implements decoding of the EAN-8 format.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class EAN8Reader extends UPCEANReader {
|
||||
|
||||
private final int[] decodeMiddleCounters;
|
||||
|
||||
public EAN8Reader() {
|
||||
decodeMiddleCounters = new int[4];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int decodeMiddle(BitArray row,
|
||||
int[] startRange,
|
||||
StringBuilder result) throws NotFoundException {
|
||||
int[] counters = decodeMiddleCounters;
|
||||
counters[0] = 0;
|
||||
counters[1] = 0;
|
||||
counters[2] = 0;
|
||||
counters[3] = 0;
|
||||
int end = row.getSize();
|
||||
int rowOffset = startRange[1];
|
||||
|
||||
for (int x = 0; x < 4 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
|
||||
result.append((char) ('0' + bestMatch));
|
||||
for (int counter : counters) {
|
||||
rowOffset += counter;
|
||||
}
|
||||
}
|
||||
|
||||
int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
|
||||
rowOffset = middleRange[1];
|
||||
|
||||
for (int x = 0; x < 4 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
|
||||
result.append((char) ('0' + bestMatch));
|
||||
for (int counter : counters) {
|
||||
rowOffset += counter;
|
||||
}
|
||||
}
|
||||
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
BarcodeFormat getBarcodeFormat() {
|
||||
return BarcodeFormat.EAN_8;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.FormatException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This object renders an EAN8 code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author aripollak@gmail.com (Ari Pollak)
|
||||
*/
|
||||
public final class EAN8Writer extends UPCEANWriter {
|
||||
|
||||
private static final int CODE_WIDTH = 3 + // start guard
|
||||
(7 * 4) + // left bars
|
||||
5 + // middle guard
|
||||
(7 * 4) + // right bars
|
||||
3; // end guard
|
||||
|
||||
@Override
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return Collections.singleton(BarcodeFormat.EAN_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a byte array of horizontal pixels (false = white, true = black)
|
||||
*/
|
||||
@Override
|
||||
public boolean[] encode(String contents) {
|
||||
int length = contents.length();
|
||||
switch (length) {
|
||||
case 7:
|
||||
// No check digit present, calculate it and add it
|
||||
int check;
|
||||
try {
|
||||
check = UPCEANReader.getStandardUPCEANChecksum(contents);
|
||||
} catch (FormatException fe) {
|
||||
throw new IllegalArgumentException(fe);
|
||||
}
|
||||
contents += check;
|
||||
break;
|
||||
case 8:
|
||||
try {
|
||||
if (!UPCEANReader.checkStandardUPCEANChecksum(contents)) {
|
||||
throw new IllegalArgumentException("Contents do not pass checksum");
|
||||
}
|
||||
} catch (FormatException ignored) {
|
||||
throw new IllegalArgumentException("Illegal contents");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Requested contents should be 7 or 8 digits long, but got " + length);
|
||||
}
|
||||
|
||||
checkNumeric(contents);
|
||||
|
||||
boolean[] result = new boolean[CODE_WIDTH];
|
||||
int pos = 0;
|
||||
|
||||
pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
|
||||
|
||||
for (int i = 0; i <= 3; i++) {
|
||||
int digit = Character.digit(contents.charAt(i), 10);
|
||||
pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], false);
|
||||
}
|
||||
|
||||
pos += appendPattern(result, pos, UPCEANReader.MIDDLE_PATTERN, false);
|
||||
|
||||
for (int i = 4; i <= 7; i++) {
|
||||
int digit = Character.digit(contents.charAt(i), 10);
|
||||
pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], true);
|
||||
}
|
||||
appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,379 +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.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Implements decoding of the ITF format, or Interleaved Two of Five.</p>
|
||||
*
|
||||
* <p>This Reader will scan ITF barcodes of certain lengths only.
|
||||
* At the moment it reads length 6, 8, 10, 12, 14, 16, 18, 20, 24, and 44 as these have appeared "in the wild". Not all
|
||||
* lengths are scanned, especially shorter ones, to avoid false positives. This in turn is due to a lack of
|
||||
* required checksum function.</p>
|
||||
*
|
||||
* <p>The checksum is optional and is not applied by this Reader. The consumer of the decoded
|
||||
* value will have to apply a checksum if required.</p>
|
||||
*
|
||||
* <p><a href="http://en.wikipedia.org/wiki/Interleaved_2_of_5">http://en.wikipedia.org/wiki/Interleaved_2_of_5</a>
|
||||
* is a great reference for Interleaved 2 of 5 information.</p>
|
||||
*
|
||||
* @author kevin.osullivan@sita.aero, SITA Lab.
|
||||
*/
|
||||
public final class ITFReader extends OneDReader {
|
||||
|
||||
private static final float MAX_AVG_VARIANCE = 0.38f;
|
||||
private static final float MAX_INDIVIDUAL_VARIANCE = 0.5f;
|
||||
|
||||
private static final int W = 3; // Pixel width of a 3x wide line
|
||||
private static final int w = 2; // Pixel width of a 2x wide line
|
||||
private static final int N = 1; // Pixed width of a narrow line
|
||||
|
||||
/** Valid ITF lengths. Anything longer than the largest value is also allowed. */
|
||||
private static final int[] DEFAULT_ALLOWED_LENGTHS = {6, 8, 10, 12, 14};
|
||||
|
||||
// Stores the actual narrow line width of the image being decoded.
|
||||
private int narrowLineWidth = -1;
|
||||
|
||||
/**
|
||||
* Start/end guard pattern.
|
||||
*
|
||||
* Note: The end pattern is reversed because the row is reversed before
|
||||
* searching for the END_PATTERN
|
||||
*/
|
||||
private static final int[] START_PATTERN = {N, N, N, N};
|
||||
private static final int[][] END_PATTERN_REVERSED = {
|
||||
{N, N, w}, // 2x
|
||||
{N, N, W} // 3x
|
||||
};
|
||||
|
||||
// See ITFWriter.PATTERNS
|
||||
|
||||
/**
|
||||
* Patterns of Wide / Narrow lines to indicate each digit
|
||||
*/
|
||||
private static final int[][] PATTERNS = {
|
||||
{N, N, w, w, N}, // 0
|
||||
{w, N, N, N, w}, // 1
|
||||
{N, w, N, N, w}, // 2
|
||||
{w, w, N, N, N}, // 3
|
||||
{N, N, w, N, w}, // 4
|
||||
{w, N, w, N, N}, // 5
|
||||
{N, w, w, N, N}, // 6
|
||||
{N, N, N, w, w}, // 7
|
||||
{w, N, N, w, N}, // 8
|
||||
{N, w, N, w, N}, // 9
|
||||
{N, N, W, W, N}, // 0
|
||||
{W, N, N, N, W}, // 1
|
||||
{N, W, N, N, W}, // 2
|
||||
{W, W, N, N, N}, // 3
|
||||
{N, N, W, N, W}, // 4
|
||||
{W, N, W, N, N}, // 5
|
||||
{N, W, W, N, N}, // 6
|
||||
{N, N, N, W, W}, // 7
|
||||
{W, N, N, W, N}, // 8
|
||||
{N, W, N, W, N} // 9
|
||||
};
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)
|
||||
throws FormatException, NotFoundException {
|
||||
|
||||
// Find out where the Middle section (payload) starts & ends
|
||||
int[] startRange = decodeStart(row);
|
||||
int[] endRange = decodeEnd(row);
|
||||
|
||||
StringBuilder result = new StringBuilder(20);
|
||||
decodeMiddle(row, startRange[1], endRange[0], result);
|
||||
String resultString = result.toString();
|
||||
|
||||
int[] allowedLengths = null;
|
||||
if (hints != null) {
|
||||
allowedLengths = (int[]) hints.get(DecodeHintType.ALLOWED_LENGTHS);
|
||||
|
||||
}
|
||||
if (allowedLengths == null) {
|
||||
allowedLengths = DEFAULT_ALLOWED_LENGTHS;
|
||||
}
|
||||
|
||||
// To avoid false positives with 2D barcodes (and other patterns), make
|
||||
// an assumption that the decoded string must be a 'standard' length if it's short
|
||||
int length = resultString.length();
|
||||
boolean lengthOK = false;
|
||||
int maxAllowedLength = 0;
|
||||
for (int allowedLength : allowedLengths) {
|
||||
if (length == allowedLength) {
|
||||
lengthOK = true;
|
||||
break;
|
||||
}
|
||||
if (allowedLength > maxAllowedLength) {
|
||||
maxAllowedLength = allowedLength;
|
||||
}
|
||||
}
|
||||
if (!lengthOK && length > maxAllowedLength) {
|
||||
lengthOK = true;
|
||||
}
|
||||
if (!lengthOK) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
|
||||
Result resultObject = new Result(
|
||||
resultString,
|
||||
null, // no natural byte representation for these barcodes
|
||||
new ResultPoint[] {new ResultPoint(startRange[1], rowNumber),
|
||||
new ResultPoint(endRange[0], rowNumber)},
|
||||
BarcodeFormat.ITF);
|
||||
resultObject.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]I0");
|
||||
return resultObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param row row of black/white values to search
|
||||
* @param payloadStart offset of start pattern
|
||||
* @param resultString {@link StringBuilder} to append decoded chars to
|
||||
* @throws NotFoundException if decoding could not complete successfully
|
||||
*/
|
||||
private static void decodeMiddle(BitArray row,
|
||||
int payloadStart,
|
||||
int payloadEnd,
|
||||
StringBuilder resultString) throws NotFoundException {
|
||||
|
||||
// Digits are interleaved in pairs - 5 black lines for one digit, and the
|
||||
// 5
|
||||
// interleaved white lines for the second digit.
|
||||
// Therefore, need to scan 10 lines and then
|
||||
// split these into two arrays
|
||||
int[] counterDigitPair = new int[10];
|
||||
int[] counterBlack = new int[5];
|
||||
int[] counterWhite = new int[5];
|
||||
|
||||
while (payloadStart < payloadEnd) {
|
||||
|
||||
// Get 10 runs of black/white.
|
||||
recordPattern(row, payloadStart, counterDigitPair);
|
||||
// Split them into each array
|
||||
for (int k = 0; k < 5; k++) {
|
||||
int twoK = 2 * k;
|
||||
counterBlack[k] = counterDigitPair[twoK];
|
||||
counterWhite[k] = counterDigitPair[twoK + 1];
|
||||
}
|
||||
|
||||
int bestMatch = decodeDigit(counterBlack);
|
||||
resultString.append((char) ('0' + bestMatch));
|
||||
bestMatch = decodeDigit(counterWhite);
|
||||
resultString.append((char) ('0' + bestMatch));
|
||||
|
||||
for (int counterDigit : counterDigitPair) {
|
||||
payloadStart += counterDigit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify where the start of the middle / payload section starts.
|
||||
*
|
||||
* @param row row of black/white values to search
|
||||
* @return Array, containing index of start of 'start block' and end of
|
||||
* 'start block'
|
||||
*/
|
||||
private int[] decodeStart(BitArray row) throws NotFoundException {
|
||||
int endStart = skipWhiteSpace(row);
|
||||
int[] startPattern = findGuardPattern(row, endStart, START_PATTERN);
|
||||
|
||||
// Determine the width of a narrow line in pixels. We can do this by
|
||||
// getting the width of the start pattern and dividing by 4 because its
|
||||
// made up of 4 narrow lines.
|
||||
this.narrowLineWidth = (startPattern[1] - startPattern[0]) / 4;
|
||||
|
||||
validateQuietZone(row, startPattern[0]);
|
||||
|
||||
return startPattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* The start & end patterns must be pre/post fixed by a quiet zone. This
|
||||
* zone must be at least 10 times the width of a narrow line. Scan back until
|
||||
* we either get to the start of the barcode or match the necessary number of
|
||||
* quiet zone pixels.
|
||||
*
|
||||
* Note: Its assumed the row is reversed when using this method to find
|
||||
* quiet zone after the end pattern.
|
||||
*
|
||||
* ref: http://www.barcode-1.net/i25code.html
|
||||
*
|
||||
* @param row bit array representing the scanned barcode.
|
||||
* @param startPattern index into row of the start or end pattern.
|
||||
* @throws NotFoundException if the quiet zone cannot be found
|
||||
*/
|
||||
private void validateQuietZone(BitArray row, int startPattern) throws NotFoundException {
|
||||
|
||||
int quietCount = this.narrowLineWidth * 10; // expect to find this many pixels of quiet zone
|
||||
|
||||
// if there are not so many pixel at all let's try as many as possible
|
||||
quietCount = Math.min(quietCount, startPattern);
|
||||
|
||||
for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--) {
|
||||
if (row.get(i)) {
|
||||
break;
|
||||
}
|
||||
quietCount--;
|
||||
}
|
||||
if (quietCount != 0) {
|
||||
// Unable to find the necessary number of quiet zone pixels.
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip all whitespace until we get to the first black line.
|
||||
*
|
||||
* @param row row of black/white values to search
|
||||
* @return index of the first black line.
|
||||
* @throws NotFoundException Throws exception if no black lines are found in the row
|
||||
*/
|
||||
private static int skipWhiteSpace(BitArray row) throws NotFoundException {
|
||||
int width = row.getSize();
|
||||
int endStart = row.getNextSet(0);
|
||||
if (endStart == width) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
return endStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Identify where the end of the middle / payload section ends.
|
||||
*
|
||||
* @param row row of black/white values to search
|
||||
* @return Array, containing index of start of 'end block' and end of 'end
|
||||
* block'
|
||||
*/
|
||||
private int[] decodeEnd(BitArray row) throws NotFoundException {
|
||||
|
||||
// For convenience, reverse the row and then
|
||||
// search from 'the start' for the end block
|
||||
row.reverse();
|
||||
try {
|
||||
int endStart = skipWhiteSpace(row);
|
||||
int[] endPattern;
|
||||
try {
|
||||
endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED[0]);
|
||||
} catch (NotFoundException nfe) {
|
||||
endPattern = findGuardPattern(row, endStart, END_PATTERN_REVERSED[1]);
|
||||
}
|
||||
|
||||
// The start & end patterns must be pre/post fixed by a quiet zone. This
|
||||
// zone must be at least 10 times the width of a narrow line.
|
||||
// ref: http://www.barcode-1.net/i25code.html
|
||||
validateQuietZone(row, endPattern[0]);
|
||||
|
||||
// Now recalculate the indices of where the 'endblock' starts & stops to
|
||||
// accommodate
|
||||
// the reversed nature of the search
|
||||
int temp = endPattern[0];
|
||||
endPattern[0] = row.getSize() - endPattern[1];
|
||||
endPattern[1] = row.getSize() - temp;
|
||||
|
||||
return endPattern;
|
||||
} finally {
|
||||
// Put the row back the right way.
|
||||
row.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param row row of black/white values to search
|
||||
* @param rowOffset position to start search
|
||||
* @param pattern pattern of counts of number of black and white pixels that are
|
||||
* being searched for as a pattern
|
||||
* @return start/end horizontal offset of guard pattern, as an array of two
|
||||
* ints
|
||||
* @throws NotFoundException if pattern is not found
|
||||
*/
|
||||
private static int[] findGuardPattern(BitArray row,
|
||||
int rowOffset,
|
||||
int[] pattern) throws NotFoundException {
|
||||
int patternLength = pattern.length;
|
||||
int[] counters = new int[patternLength];
|
||||
int width = row.getSize();
|
||||
boolean isWhite = false;
|
||||
|
||||
int counterPosition = 0;
|
||||
int patternStart = rowOffset;
|
||||
for (int x = rowOffset; x < width; x++) {
|
||||
if (row.get(x) != isWhite) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == patternLength - 1) {
|
||||
if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
|
||||
return new int[]{patternStart, x};
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to decode a sequence of ITF black/white lines into single
|
||||
* digit.
|
||||
*
|
||||
* @param counters the counts of runs of observed black/white/black/... values
|
||||
* @return The decoded digit
|
||||
* @throws NotFoundException if digit cannot be decoded
|
||||
*/
|
||||
private static int decodeDigit(int[] counters) throws NotFoundException {
|
||||
float bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
|
||||
int bestMatch = -1;
|
||||
int max = PATTERNS.length;
|
||||
for (int i = 0; i < max; i++) {
|
||||
int[] pattern = PATTERNS[i];
|
||||
float variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
|
||||
if (variance < bestVariance) {
|
||||
bestVariance = variance;
|
||||
bestMatch = i;
|
||||
} else if (variance == bestVariance) {
|
||||
// if we find a second 'best match' with the same variance, we can not reliably report to have a suitable match
|
||||
bestMatch = -1;
|
||||
}
|
||||
}
|
||||
if (bestMatch >= 0) {
|
||||
return bestMatch % 10;
|
||||
} else {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* This object renders a ITF code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author erik.barbara@gmail.com (Erik Barbara)
|
||||
*/
|
||||
public final class ITFWriter extends OneDimensionalCodeWriter {
|
||||
|
||||
private static final int[] START_PATTERN = {1, 1, 1, 1};
|
||||
private static final int[] END_PATTERN = {3, 1, 1};
|
||||
|
||||
private static final int W = 3; // Pixel width of a 3x wide line
|
||||
private static final int N = 1; // Pixed width of a narrow line
|
||||
|
||||
// See ITFReader.PATTERNS
|
||||
|
||||
private static final int[][] PATTERNS = {
|
||||
{N, N, W, W, N}, // 0
|
||||
{W, N, N, N, W}, // 1
|
||||
{N, W, N, N, W}, // 2
|
||||
{W, W, N, N, N}, // 3
|
||||
{N, N, W, N, W}, // 4
|
||||
{W, N, W, N, N}, // 5
|
||||
{N, W, W, N, N}, // 6
|
||||
{N, N, N, W, W}, // 7
|
||||
{W, N, N, W, N}, // 8
|
||||
{N, W, N, W, N} // 9
|
||||
};
|
||||
|
||||
@Override
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return Collections.singleton(BarcodeFormat.ITF);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] encode(String contents) {
|
||||
int length = contents.length();
|
||||
if (length % 2 != 0) {
|
||||
throw new IllegalArgumentException("The length of the input should be even");
|
||||
}
|
||||
if (length > 80) {
|
||||
throw new IllegalArgumentException(
|
||||
"Requested contents should be less than 80 digits long, but got " + length);
|
||||
}
|
||||
|
||||
checkNumeric(contents);
|
||||
|
||||
boolean[] result = new boolean[9 + 9 * length];
|
||||
int pos = appendPattern(result, 0, START_PATTERN, true);
|
||||
for (int i = 0; i < length; i += 2) {
|
||||
int one = Character.digit(contents.charAt(i), 10);
|
||||
int two = Character.digit(contents.charAt(i + 1), 10);
|
||||
int[] encoding = new int[10];
|
||||
for (int j = 0; j < 5; j++) {
|
||||
encoding[2 * j] = PATTERNS[one][j];
|
||||
encoding[2 * j + 1] = PATTERNS[two][j];
|
||||
}
|
||||
pos += appendPattern(result, pos, encoding, true);
|
||||
}
|
||||
appendPattern(result, pos, END_PATTERN, true);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,114 +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.Reader;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.oned.rss.RSS14Reader;
|
||||
import com.google.zxing.oned.rss.expanded.RSSExpandedReader;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class MultiFormatOneDReader extends OneDReader {
|
||||
|
||||
private static final OneDReader[] EMPTY_ONED_ARRAY = new OneDReader[0];
|
||||
|
||||
private final OneDReader[] readers;
|
||||
|
||||
public MultiFormatOneDReader(Map<DecodeHintType,?> hints) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<BarcodeFormat> possibleFormats = hints == null ? null :
|
||||
(Collection<BarcodeFormat>) hints.get(DecodeHintType.POSSIBLE_FORMATS);
|
||||
boolean useCode39CheckDigit = hints != null &&
|
||||
hints.get(DecodeHintType.ASSUME_CODE_39_CHECK_DIGIT) != null;
|
||||
Collection<OneDReader> readers = new ArrayList<>();
|
||||
if (possibleFormats != null) {
|
||||
if (possibleFormats.contains(BarcodeFormat.EAN_13) ||
|
||||
possibleFormats.contains(BarcodeFormat.UPC_A) ||
|
||||
possibleFormats.contains(BarcodeFormat.EAN_8) ||
|
||||
possibleFormats.contains(BarcodeFormat.UPC_E)) {
|
||||
readers.add(new MultiFormatUPCEANReader(hints));
|
||||
}
|
||||
if (possibleFormats.contains(BarcodeFormat.CODE_39)) {
|
||||
readers.add(new Code39Reader(useCode39CheckDigit));
|
||||
}
|
||||
if (possibleFormats.contains(BarcodeFormat.CODE_93)) {
|
||||
readers.add(new Code93Reader());
|
||||
}
|
||||
if (possibleFormats.contains(BarcodeFormat.CODE_128)) {
|
||||
readers.add(new Code128Reader());
|
||||
}
|
||||
if (possibleFormats.contains(BarcodeFormat.ITF)) {
|
||||
readers.add(new ITFReader());
|
||||
}
|
||||
if (possibleFormats.contains(BarcodeFormat.CODABAR)) {
|
||||
readers.add(new CodaBarReader());
|
||||
}
|
||||
if (possibleFormats.contains(BarcodeFormat.RSS_14)) {
|
||||
readers.add(new RSS14Reader());
|
||||
}
|
||||
if (possibleFormats.contains(BarcodeFormat.RSS_EXPANDED)) {
|
||||
readers.add(new RSSExpandedReader());
|
||||
}
|
||||
}
|
||||
if (readers.isEmpty()) {
|
||||
readers.add(new MultiFormatUPCEANReader(hints));
|
||||
readers.add(new Code39Reader());
|
||||
readers.add(new CodaBarReader());
|
||||
readers.add(new Code93Reader());
|
||||
readers.add(new Code128Reader());
|
||||
readers.add(new ITFReader());
|
||||
readers.add(new RSS14Reader());
|
||||
readers.add(new RSSExpandedReader());
|
||||
}
|
||||
this.readers = readers.toArray(EMPTY_ONED_ARRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber,
|
||||
BitArray row,
|
||||
Map<DecodeHintType,?> hints) throws NotFoundException {
|
||||
for (OneDReader reader : readers) {
|
||||
try {
|
||||
return reader.decodeRow(rowNumber, row, hints);
|
||||
} catch (ReaderException re) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
for (Reader reader : readers) {
|
||||
reader.reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,125 +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.Reader;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>A reader that can read all available UPC/EAN formats. If a caller wants to try to
|
||||
* read all such formats, it is most efficient to use this implementation rather than invoke
|
||||
* individual readers.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class MultiFormatUPCEANReader extends OneDReader {
|
||||
|
||||
private static final UPCEANReader[] EMPTY_READER_ARRAY = new UPCEANReader[0];
|
||||
|
||||
private final UPCEANReader[] readers;
|
||||
|
||||
public MultiFormatUPCEANReader(Map<DecodeHintType,?> hints) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<BarcodeFormat> possibleFormats = hints == null ? null :
|
||||
(Collection<BarcodeFormat>) hints.get(DecodeHintType.POSSIBLE_FORMATS);
|
||||
Collection<UPCEANReader> readers = new ArrayList<>();
|
||||
if (possibleFormats != null) {
|
||||
if (possibleFormats.contains(BarcodeFormat.EAN_13)) {
|
||||
readers.add(new EAN13Reader());
|
||||
} else if (possibleFormats.contains(BarcodeFormat.UPC_A)) {
|
||||
readers.add(new UPCAReader());
|
||||
}
|
||||
if (possibleFormats.contains(BarcodeFormat.EAN_8)) {
|
||||
readers.add(new EAN8Reader());
|
||||
}
|
||||
if (possibleFormats.contains(BarcodeFormat.UPC_E)) {
|
||||
readers.add(new UPCEReader());
|
||||
}
|
||||
}
|
||||
if (readers.isEmpty()) {
|
||||
readers.add(new EAN13Reader());
|
||||
// UPC-A is covered by EAN-13
|
||||
readers.add(new EAN8Reader());
|
||||
readers.add(new UPCEReader());
|
||||
}
|
||||
this.readers = readers.toArray(EMPTY_READER_ARRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber,
|
||||
BitArray row,
|
||||
Map<DecodeHintType,?> hints) throws NotFoundException {
|
||||
// Compute this location once and reuse it on multiple implementations
|
||||
int[] startGuardPattern = UPCEANReader.findStartGuardPattern(row);
|
||||
for (UPCEANReader reader : readers) {
|
||||
try {
|
||||
Result result = reader.decodeRow(rowNumber, row, startGuardPattern, hints);
|
||||
// Special case: a 12-digit code encoded in UPC-A is identical to a "0"
|
||||
// followed by those 12 digits encoded as EAN-13. Each will recognize such a code,
|
||||
// UPC-A as a 12-digit string and EAN-13 as a 13-digit string starting with "0".
|
||||
// Individually these are correct and their readers will both read such a code
|
||||
// and correctly call it EAN-13, or UPC-A, respectively.
|
||||
//
|
||||
// In this case, if we've been looking for both types, we'd like to call it
|
||||
// a UPC-A code. But for efficiency we only run the EAN-13 decoder to also read
|
||||
// UPC-A. So we special case it here, and convert an EAN-13 result to a UPC-A
|
||||
// result if appropriate.
|
||||
//
|
||||
// But, don't return UPC-A if UPC-A was not a requested format!
|
||||
boolean ean13MayBeUPCA =
|
||||
result.getBarcodeFormat() == BarcodeFormat.EAN_13 &&
|
||||
result.getText().charAt(0) == '0';
|
||||
@SuppressWarnings("unchecked")
|
||||
Collection<BarcodeFormat> possibleFormats =
|
||||
hints == null ? null : (Collection<BarcodeFormat>) hints.get(DecodeHintType.POSSIBLE_FORMATS);
|
||||
boolean canReturnUPCA = possibleFormats == null || possibleFormats.contains(BarcodeFormat.UPC_A);
|
||||
|
||||
if (ean13MayBeUPCA && canReturnUPCA) {
|
||||
// Transfer the metadata across
|
||||
Result resultUPCA = new Result(result.getText().substring(1),
|
||||
result.getRawBytes(),
|
||||
result.getResultPoints(),
|
||||
BarcodeFormat.UPC_A);
|
||||
resultUPCA.putAllMetadata(result.getResultMetadata());
|
||||
return resultUPCA;
|
||||
}
|
||||
return result;
|
||||
} catch (ReaderException ignored) {
|
||||
// continue
|
||||
}
|
||||
}
|
||||
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
for (Reader reader : readers) {
|
||||
reader.reset();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
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 Result 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 Result 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();
|
||||
Result result = doDecode(rotatedImage, hints);
|
||||
// Record that we found it rotated 90 degrees CCW / 270 degrees CW
|
||||
Map<ResultMetadataType,?> metadata = result.getResultMetadata();
|
||||
int orientation = 270;
|
||||
if (metadata != null && metadata.containsKey(ResultMetadataType.ORIENTATION)) {
|
||||
// But if we found it reversed in doDecode(), add in that result here:
|
||||
orientation = (orientation +
|
||||
(Integer) metadata.get(ResultMetadataType.ORIENTATION)) % 360;
|
||||
}
|
||||
result.putMetadata(ResultMetadataType.ORIENTATION, orientation);
|
||||
// Update result points
|
||||
ResultPoint[] points = result.getResultPoints();
|
||||
if (points != null) {
|
||||
int height = rotatedImage.getHeight();
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
points[i] = new ResultPoint(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 Result 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
|
||||
Result result = decodeRow(rowNumber, row, hints);
|
||||
// We found our barcode
|
||||
if (attempt == 1) {
|
||||
// But it was upside down, so note that
|
||||
result.putMetadata(ResultMetadataType.ORIENTATION, 180);
|
||||
// And remember to flip the result points horizontally.
|
||||
ResultPoint[] points = result.getResultPoints();
|
||||
if (points != null) {
|
||||
points[0] = new ResultPoint(width - points[0].getX() - 1, points[0].getY());
|
||||
points[1] = new ResultPoint(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 Result} 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 Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, ChecksumException, FormatException;
|
||||
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* Copyright 2011 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.Writer;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates functionality and implementation that is common to one-dimensional barcodes.</p>
|
||||
*
|
||||
* @author dsbnatut@gmail.com (Kazuki Nishiura)
|
||||
*/
|
||||
public abstract class OneDimensionalCodeWriter implements Writer {
|
||||
private static final Pattern NUMERIC = Pattern.compile("[0-9]+");
|
||||
|
||||
/**
|
||||
* Encode the contents to boolean array expression of one-dimensional barcode.
|
||||
* Start code and end code should be included in result, and side margins should not be included.
|
||||
*
|
||||
* @param contents barcode contents to encode
|
||||
* @return a {@code boolean[]} of horizontal pixels (false = white, true = black)
|
||||
*/
|
||||
public abstract boolean[] encode(String contents);
|
||||
|
||||
/**
|
||||
* Can be overwritten if the encode requires to read the hints map. Otherwise it defaults to {@code encode}.
|
||||
* @param contents barcode contents to encode
|
||||
* @param hints encoding hints
|
||||
* @return a {@code boolean[]} of horizontal pixels (false = white, true = black)
|
||||
*/
|
||||
protected boolean[] encode(String contents, Map<EncodeHintType,?> hints) {
|
||||
return encode(contents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
|
||||
return encode(contents, format, width, height, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the contents following specified format.
|
||||
* {@code width} and {@code height} are required size. This method may return bigger size
|
||||
* {@code BitMatrix} when specified size is too small. The user can set both {@code width} and
|
||||
* {@code height} to zero to get minimum size barcode. If negative value is set to {@code width}
|
||||
* or {@code height}, {@code IllegalArgumentException} is thrown.
|
||||
*/
|
||||
@Override
|
||||
public BitMatrix encode(String contents,
|
||||
BarcodeFormat format,
|
||||
int width,
|
||||
int height,
|
||||
Map<EncodeHintType,?> hints) {
|
||||
if (contents.isEmpty()) {
|
||||
throw new IllegalArgumentException("Found empty contents");
|
||||
}
|
||||
|
||||
if (width < 0 || height < 0) {
|
||||
throw new IllegalArgumentException("Negative size is not allowed. Input: "
|
||||
+ width + 'x' + height);
|
||||
}
|
||||
Collection<BarcodeFormat> supportedFormats = getSupportedWriteFormats();
|
||||
if (supportedFormats != null && !supportedFormats.contains(format)) {
|
||||
throw new IllegalArgumentException("Can only encode " + supportedFormats +
|
||||
", but got " + format);
|
||||
}
|
||||
|
||||
int sidesMargin = getDefaultMargin();
|
||||
if (hints != null && hints.containsKey(EncodeHintType.MARGIN)) {
|
||||
sidesMargin = Integer.parseInt(hints.get(EncodeHintType.MARGIN).toString());
|
||||
}
|
||||
|
||||
boolean[] code = encode(contents, hints);
|
||||
return renderResult(code, width, height, sidesMargin);
|
||||
}
|
||||
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a byte array of horizontal pixels (0 = white, 1 = black)
|
||||
*/
|
||||
private static BitMatrix renderResult(boolean[] code, int width, int height, int sidesMargin) {
|
||||
int inputWidth = code.length;
|
||||
// Add quiet zone on both sides.
|
||||
int fullWidth = inputWidth + sidesMargin;
|
||||
int outputWidth = Math.max(width, fullWidth);
|
||||
int outputHeight = Math.max(1, height);
|
||||
|
||||
int multiple = outputWidth / fullWidth;
|
||||
int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
|
||||
|
||||
BitMatrix output = new BitMatrix(outputWidth, outputHeight);
|
||||
for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
|
||||
if (code[inputX]) {
|
||||
output.setRegion(outputX, 0, multiple, outputHeight);
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param contents string to check for numeric characters
|
||||
* @throws IllegalArgumentException if input contains characters other than digits 0-9.
|
||||
*/
|
||||
protected static void checkNumeric(String contents) {
|
||||
if (!NUMERIC.matcher(contents).matches()) {
|
||||
throw new IllegalArgumentException("Input should only contain digits 0-9");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param target encode black/white pattern into this array
|
||||
* @param pos position to start encoding at in {@code target}
|
||||
* @param pattern lengths of black/white runs to encode
|
||||
* @param startColor starting color - false for white, true for black
|
||||
* @return the number of elements added to target.
|
||||
*/
|
||||
protected static int appendPattern(boolean[] target, int pos, int[] pattern, boolean startColor) {
|
||||
boolean color = startColor;
|
||||
int numAdded = 0;
|
||||
for (int len : pattern) {
|
||||
for (int j = 0; j < len; j++) {
|
||||
target[pos++] = color;
|
||||
}
|
||||
numAdded += len;
|
||||
color = !color; // flip color after each segment
|
||||
}
|
||||
return numAdded;
|
||||
}
|
||||
|
||||
public int getDefaultMargin() {
|
||||
// CodaBar spec requires a side margin to be more than ten times wider than narrow space.
|
||||
// This seems like a decent idea for a default for all formats.
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,90 +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.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.Result;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Implements decoding of the UPC-A format.</p>
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class UPCAReader extends UPCEANReader {
|
||||
|
||||
private final UPCEANReader ean13Reader = new EAN13Reader();
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber,
|
||||
BitArray row,
|
||||
int[] startGuardRange,
|
||||
Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, FormatException, ChecksumException {
|
||||
return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, startGuardRange, hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, FormatException, ChecksumException {
|
||||
return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
|
||||
return maybeReturnResult(ean13Reader.decode(image));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decode(BinaryBitmap image, Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, FormatException {
|
||||
return maybeReturnResult(ean13Reader.decode(image, hints));
|
||||
}
|
||||
|
||||
@Override
|
||||
BarcodeFormat getBarcodeFormat() {
|
||||
return BarcodeFormat.UPC_A;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString)
|
||||
throws NotFoundException {
|
||||
return ean13Reader.decodeMiddle(row, startRange, resultString);
|
||||
}
|
||||
|
||||
private static Result maybeReturnResult(Result result) throws FormatException {
|
||||
String text = result.getText();
|
||||
if (text.charAt(0) == '0') {
|
||||
Result upcaResult = new Result(text.substring(1), null, result.getResultPoints(), BarcodeFormat.UPC_A);
|
||||
if (result.getResultMetadata() != null) {
|
||||
upcaResult.putAllMetadata(result.getResultMetadata());
|
||||
}
|
||||
return upcaResult;
|
||||
} else {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010 ZXing authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.Writer;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This object renders a UPC-A code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author qwandor@google.com (Andrew Walbran)
|
||||
*/
|
||||
public final class UPCAWriter implements Writer {
|
||||
|
||||
private final EAN13Writer subWriter = new EAN13Writer();
|
||||
|
||||
@Override
|
||||
public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
|
||||
return encode(contents, format, width, height, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BitMatrix encode(String contents,
|
||||
BarcodeFormat format,
|
||||
int width,
|
||||
int height,
|
||||
Map<EncodeHintType,?> hints) {
|
||||
if (format != BarcodeFormat.UPC_A) {
|
||||
throw new IllegalArgumentException("Can only encode UPC-A, but got " + format);
|
||||
}
|
||||
// Transform a UPC-A code into the equivalent EAN-13 code and write it that way
|
||||
return subWriter.encode('0' + contents, BarcodeFormat.EAN_13, width, height, hints);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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.NotFoundException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @see UPCEANExtension5Support
|
||||
*/
|
||||
final class UPCEANExtension2Support {
|
||||
|
||||
private final int[] decodeMiddleCounters = new int[4];
|
||||
private final StringBuilder decodeRowStringBuffer = new StringBuilder();
|
||||
|
||||
Result decodeRow(int rowNumber, BitArray row, int[] extensionStartRange) throws NotFoundException {
|
||||
|
||||
StringBuilder result = decodeRowStringBuffer;
|
||||
result.setLength(0);
|
||||
int end = decodeMiddle(row, extensionStartRange, result);
|
||||
|
||||
String resultString = result.toString();
|
||||
Map<ResultMetadataType,Object> extensionData = parseExtensionString(resultString);
|
||||
|
||||
Result extensionResult =
|
||||
new Result(resultString,
|
||||
null,
|
||||
new ResultPoint[] {
|
||||
new ResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0f, rowNumber),
|
||||
new ResultPoint(end, rowNumber),
|
||||
},
|
||||
BarcodeFormat.UPC_EAN_EXTENSION);
|
||||
if (extensionData != null) {
|
||||
extensionResult.putAllMetadata(extensionData);
|
||||
}
|
||||
return extensionResult;
|
||||
}
|
||||
|
||||
private int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) throws NotFoundException {
|
||||
int[] counters = decodeMiddleCounters;
|
||||
counters[0] = 0;
|
||||
counters[1] = 0;
|
||||
counters[2] = 0;
|
||||
counters[3] = 0;
|
||||
int end = row.getSize();
|
||||
int rowOffset = startRange[1];
|
||||
|
||||
int checkParity = 0;
|
||||
|
||||
for (int x = 0; x < 2 && rowOffset < end; x++) {
|
||||
int bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_AND_G_PATTERNS);
|
||||
resultString.append((char) ('0' + bestMatch % 10));
|
||||
for (int counter : counters) {
|
||||
rowOffset += counter;
|
||||
}
|
||||
if (bestMatch >= 10) {
|
||||
checkParity |= 1 << (1 - x);
|
||||
}
|
||||
if (x != 1) {
|
||||
// Read off separator if not last
|
||||
rowOffset = row.getNextSet(rowOffset);
|
||||
rowOffset = row.getNextUnset(rowOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (resultString.length() != 2) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
if (Integer.parseInt(resultString.toString()) % 4 != checkParity) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param raw raw content of extension
|
||||
* @return formatted interpretation of raw content as a {@link Map} mapping
|
||||
* one {@link ResultMetadataType} to appropriate value, or {@code null} if not known
|
||||
*/
|
||||
private static Map<ResultMetadataType,Object> parseExtensionString(String raw) {
|
||||
if (raw.length() != 2) {
|
||||
return null;
|
||||
}
|
||||
Map<ResultMetadataType,Object> result = new EnumMap<>(ResultMetadataType.class);
|
||||
result.put(ResultMetadataType.ISSUE_NUMBER, Integer.valueOf(raw));
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,180 +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 com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @see UPCEANExtension2Support
|
||||
*/
|
||||
final class UPCEANExtension5Support {
|
||||
|
||||
private static final int[] CHECK_DIGIT_ENCODINGS = {
|
||||
0x18, 0x14, 0x12, 0x11, 0x0C, 0x06, 0x03, 0x0A, 0x09, 0x05
|
||||
};
|
||||
|
||||
private final int[] decodeMiddleCounters = new int[4];
|
||||
private final StringBuilder decodeRowStringBuffer = new StringBuilder();
|
||||
|
||||
Result decodeRow(int rowNumber, BitArray row, int[] extensionStartRange) throws NotFoundException {
|
||||
|
||||
StringBuilder result = decodeRowStringBuffer;
|
||||
result.setLength(0);
|
||||
int end = decodeMiddle(row, extensionStartRange, result);
|
||||
|
||||
String resultString = result.toString();
|
||||
Map<ResultMetadataType,Object> extensionData = parseExtensionString(resultString);
|
||||
|
||||
Result extensionResult =
|
||||
new Result(resultString,
|
||||
null,
|
||||
new ResultPoint[] {
|
||||
new ResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0f, rowNumber),
|
||||
new ResultPoint(end, rowNumber),
|
||||
},
|
||||
BarcodeFormat.UPC_EAN_EXTENSION);
|
||||
if (extensionData != null) {
|
||||
extensionResult.putAllMetadata(extensionData);
|
||||
}
|
||||
return extensionResult;
|
||||
}
|
||||
|
||||
private int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) throws NotFoundException {
|
||||
int[] counters = decodeMiddleCounters;
|
||||
counters[0] = 0;
|
||||
counters[1] = 0;
|
||||
counters[2] = 0;
|
||||
counters[3] = 0;
|
||||
int end = row.getSize();
|
||||
int rowOffset = startRange[1];
|
||||
|
||||
int lgPatternFound = 0;
|
||||
|
||||
for (int x = 0; x < 5 && rowOffset < end; x++) {
|
||||
int bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_AND_G_PATTERNS);
|
||||
resultString.append((char) ('0' + bestMatch % 10));
|
||||
for (int counter : counters) {
|
||||
rowOffset += counter;
|
||||
}
|
||||
if (bestMatch >= 10) {
|
||||
lgPatternFound |= 1 << (4 - x);
|
||||
}
|
||||
if (x != 4) {
|
||||
// Read off separator if not last
|
||||
rowOffset = row.getNextSet(rowOffset);
|
||||
rowOffset = row.getNextUnset(rowOffset);
|
||||
}
|
||||
}
|
||||
|
||||
if (resultString.length() != 5) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
int checkDigit = determineCheckDigit(lgPatternFound);
|
||||
if (extensionChecksum(resultString.toString()) != checkDigit) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
private static int extensionChecksum(CharSequence s) {
|
||||
int length = s.length();
|
||||
int sum = 0;
|
||||
for (int i = length - 2; i >= 0; i -= 2) {
|
||||
sum += s.charAt(i) - '0';
|
||||
}
|
||||
sum *= 3;
|
||||
for (int i = length - 1; i >= 0; i -= 2) {
|
||||
sum += s.charAt(i) - '0';
|
||||
}
|
||||
sum *= 3;
|
||||
return sum % 10;
|
||||
}
|
||||
|
||||
private static int determineCheckDigit(int lgPatternFound)
|
||||
throws NotFoundException {
|
||||
for (int d = 0; d < 10; d++) {
|
||||
if (lgPatternFound == CHECK_DIGIT_ENCODINGS[d]) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param raw raw content of extension
|
||||
* @return formatted interpretation of raw content as a {@link Map} mapping
|
||||
* one {@link ResultMetadataType} to appropriate value, or {@code null} if not known
|
||||
*/
|
||||
private static Map<ResultMetadataType,Object> parseExtensionString(String raw) {
|
||||
if (raw.length() != 5) {
|
||||
return null;
|
||||
}
|
||||
Object value = parseExtension5String(raw);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
Map<ResultMetadataType,Object> result = new EnumMap<>(ResultMetadataType.class);
|
||||
result.put(ResultMetadataType.SUGGESTED_PRICE, value);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String parseExtension5String(String raw) {
|
||||
String currency;
|
||||
switch (raw.charAt(0)) {
|
||||
case '0':
|
||||
currency = "£";
|
||||
break;
|
||||
case '5':
|
||||
currency = "$";
|
||||
break;
|
||||
case '9':
|
||||
// Reference: http://www.jollytech.com
|
||||
switch (raw) {
|
||||
case "90000":
|
||||
// No suggested retail price
|
||||
return null;
|
||||
case "99991":
|
||||
// Complementary
|
||||
return "0.00";
|
||||
case "99990":
|
||||
return "Used";
|
||||
}
|
||||
// Otherwise... unknown currency?
|
||||
currency = "";
|
||||
break;
|
||||
default:
|
||||
currency = "";
|
||||
break;
|
||||
}
|
||||
int rawAmount = Integer.parseInt(raw.substring(1));
|
||||
String unitsString = String.valueOf(rawAmount / 100);
|
||||
int hundredths = rawAmount % 100;
|
||||
String hundredthsString = hundredths < 10 ? "0" + hundredths : String.valueOf(hundredths);
|
||||
return currency + unitsString + '.' + hundredthsString;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,40 +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 com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
final class UPCEANExtensionSupport {
|
||||
|
||||
private static final int[] EXTENSION_START_PATTERN = {1,1,2};
|
||||
|
||||
private final UPCEANExtension2Support twoSupport = new UPCEANExtension2Support();
|
||||
private final UPCEANExtension5Support fiveSupport = new UPCEANExtension5Support();
|
||||
|
||||
Result decodeRow(int rowNumber, BitArray row, int rowOffset) throws NotFoundException {
|
||||
int[] extensionStartRange = UPCEANReader.findGuardPattern(row, rowOffset, false, EXTENSION_START_PATTERN);
|
||||
try {
|
||||
return fiveSupport.decodeRow(rowNumber, row, extensionStartRange);
|
||||
} catch (ReaderException ignored) {
|
||||
return twoSupport.decodeRow(rowNumber, row, extensionStartRange);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,409 +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.ReaderException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.ResultPointCallback;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates functionality and implementation that is common to UPC and EAN families
|
||||
* of one-dimensional barcodes.</p>
|
||||
*
|
||||
* @author dswitkin@google.com (Daniel Switkin)
|
||||
* @author Sean Owen
|
||||
* @author alasdair@google.com (Alasdair Mackintosh)
|
||||
*/
|
||||
public abstract class UPCEANReader extends OneDReader {
|
||||
|
||||
// These two values are critical for determining how permissive the decoding will be.
|
||||
// We've arrived at these values through a lot of trial and error. Setting them any higher
|
||||
// lets false positives creep in quickly.
|
||||
private static final float MAX_AVG_VARIANCE = 0.48f;
|
||||
private static final float MAX_INDIVIDUAL_VARIANCE = 0.7f;
|
||||
|
||||
/**
|
||||
* Start/end guard pattern.
|
||||
*/
|
||||
static final int[] START_END_PATTERN = {1, 1, 1,};
|
||||
|
||||
/**
|
||||
* Pattern marking the middle of a UPC/EAN pattern, separating the two halves.
|
||||
*/
|
||||
static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1};
|
||||
/**
|
||||
* end guard pattern.
|
||||
*/
|
||||
static final int[] END_PATTERN = {1, 1, 1, 1, 1, 1};
|
||||
/**
|
||||
* "Odd", or "L" patterns used to encode UPC/EAN digits.
|
||||
*/
|
||||
static final int[][] L_PATTERNS = {
|
||||
{3, 2, 1, 1}, // 0
|
||||
{2, 2, 2, 1}, // 1
|
||||
{2, 1, 2, 2}, // 2
|
||||
{1, 4, 1, 1}, // 3
|
||||
{1, 1, 3, 2}, // 4
|
||||
{1, 2, 3, 1}, // 5
|
||||
{1, 1, 1, 4}, // 6
|
||||
{1, 3, 1, 2}, // 7
|
||||
{1, 2, 1, 3}, // 8
|
||||
{3, 1, 1, 2} // 9
|
||||
};
|
||||
|
||||
/**
|
||||
* As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.
|
||||
*/
|
||||
static final int[][] L_AND_G_PATTERNS;
|
||||
|
||||
static {
|
||||
L_AND_G_PATTERNS = new int[20][];
|
||||
System.arraycopy(L_PATTERNS, 0, L_AND_G_PATTERNS, 0, 10);
|
||||
for (int i = 10; i < 20; i++) {
|
||||
int[] widths = L_PATTERNS[i - 10];
|
||||
int[] reversedWidths = new int[widths.length];
|
||||
for (int j = 0; j < widths.length; j++) {
|
||||
reversedWidths[j] = widths[widths.length - j - 1];
|
||||
}
|
||||
L_AND_G_PATTERNS[i] = reversedWidths;
|
||||
}
|
||||
}
|
||||
|
||||
private final StringBuilder decodeRowStringBuffer;
|
||||
private final UPCEANExtensionSupport extensionReader;
|
||||
private final EANManufacturerOrgSupport eanManSupport;
|
||||
|
||||
protected UPCEANReader() {
|
||||
decodeRowStringBuffer = new StringBuilder(20);
|
||||
extensionReader = new UPCEANExtensionSupport();
|
||||
eanManSupport = new EANManufacturerOrgSupport();
|
||||
}
|
||||
|
||||
static int[] findStartGuardPattern(BitArray row) throws NotFoundException {
|
||||
boolean foundStart = false;
|
||||
int[] startRange = null;
|
||||
int nextStart = 0;
|
||||
int[] counters = new int[START_END_PATTERN.length];
|
||||
while (!foundStart) {
|
||||
Arrays.fill(counters, 0, START_END_PATTERN.length, 0);
|
||||
startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN, counters);
|
||||
int start = startRange[0];
|
||||
nextStart = startRange[1];
|
||||
// Make sure there is a quiet zone at least as big as the start pattern before the barcode.
|
||||
// If this check would run off the left edge of the image, do not accept this barcode,
|
||||
// as it is very likely to be a false positive.
|
||||
int quietStart = start - (nextStart - start);
|
||||
if (quietStart >= 0) {
|
||||
foundStart = row.isRange(quietStart, start, false);
|
||||
}
|
||||
}
|
||||
return startRange;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber, BitArray row, Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, ChecksumException, FormatException {
|
||||
return decodeRow(rowNumber, row, findStartGuardPattern(row), hints);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Like {@link #decodeRow(int, BitArray, Map)}, but
|
||||
* allows caller to inform method about where the UPC/EAN start pattern is
|
||||
* found. This allows this to be computed once and reused across many implementations.</p>
|
||||
*
|
||||
* @param rowNumber row index into the image
|
||||
* @param row encoding of the row of the barcode image
|
||||
* @param startGuardRange start/end column where the opening start pattern was found
|
||||
* @param hints optional hints that influence decoding
|
||||
* @return {@link Result} encapsulating the result of decoding a barcode in the row
|
||||
* @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 Result decodeRow(int rowNumber,
|
||||
BitArray row,
|
||||
int[] startGuardRange,
|
||||
Map<DecodeHintType,?> hints)
|
||||
throws NotFoundException, ChecksumException, FormatException {
|
||||
|
||||
ResultPointCallback resultPointCallback = hints == null ? null :
|
||||
(ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
|
||||
int symbologyIdentifier = 0;
|
||||
|
||||
if (resultPointCallback != null) {
|
||||
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
|
||||
(startGuardRange[0] + startGuardRange[1]) / 2.0f, rowNumber
|
||||
));
|
||||
}
|
||||
|
||||
StringBuilder result = decodeRowStringBuffer;
|
||||
result.setLength(0);
|
||||
int endStart = decodeMiddle(row, startGuardRange, result);
|
||||
|
||||
if (resultPointCallback != null) {
|
||||
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
|
||||
endStart, rowNumber
|
||||
));
|
||||
}
|
||||
|
||||
int[] endRange = decodeEnd(row, endStart);
|
||||
|
||||
if (resultPointCallback != null) {
|
||||
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
|
||||
(endRange[0] + endRange[1]) / 2.0f, rowNumber
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
// Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
|
||||
// spec might want more whitespace, but in practice this is the maximum we can count on.
|
||||
int end = endRange[1];
|
||||
int quietEnd = end + (end - endRange[0]);
|
||||
if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
String resultString = result.toString();
|
||||
// UPC/EAN should never be less than 8 chars anyway
|
||||
if (resultString.length() < 8) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
if (!checkChecksum(resultString)) {
|
||||
throw ChecksumException.getChecksumInstance();
|
||||
}
|
||||
|
||||
float left = (startGuardRange[1] + startGuardRange[0]) / 2.0f;
|
||||
float right = (endRange[1] + endRange[0]) / 2.0f;
|
||||
BarcodeFormat format = getBarcodeFormat();
|
||||
Result decodeResult = new Result(resultString,
|
||||
null, // no natural byte representation for these barcodes
|
||||
new ResultPoint[]{
|
||||
new ResultPoint(left, rowNumber),
|
||||
new ResultPoint(right, rowNumber)},
|
||||
format);
|
||||
|
||||
int extensionLength = 0;
|
||||
|
||||
try {
|
||||
Result extensionResult = extensionReader.decodeRow(rowNumber, row, endRange[1]);
|
||||
decodeResult.putMetadata(ResultMetadataType.UPC_EAN_EXTENSION, extensionResult.getText());
|
||||
decodeResult.putAllMetadata(extensionResult.getResultMetadata());
|
||||
decodeResult.addResultPoints(extensionResult.getResultPoints());
|
||||
extensionLength = extensionResult.getText().length();
|
||||
} catch (ReaderException re) {
|
||||
// continue
|
||||
}
|
||||
|
||||
int[] allowedExtensions =
|
||||
hints == null ? null : (int[]) hints.get(DecodeHintType.ALLOWED_EAN_EXTENSIONS);
|
||||
if (allowedExtensions != null) {
|
||||
boolean valid = false;
|
||||
for (int length : allowedExtensions) {
|
||||
if (extensionLength == length) {
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!valid) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
}
|
||||
|
||||
if (format == BarcodeFormat.EAN_13 || format == BarcodeFormat.UPC_A) {
|
||||
String countryID = eanManSupport.lookupCountryIdentifier(resultString);
|
||||
if (countryID != null) {
|
||||
decodeResult.putMetadata(ResultMetadataType.POSSIBLE_COUNTRY, countryID);
|
||||
}
|
||||
}
|
||||
if (format == BarcodeFormat.EAN_8) {
|
||||
symbologyIdentifier = 4;
|
||||
}
|
||||
|
||||
decodeResult.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]E" + symbologyIdentifier);
|
||||
|
||||
return decodeResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param s string of digits to check
|
||||
* @return {@link #checkStandardUPCEANChecksum(CharSequence)}
|
||||
* @throws FormatException if the string does not contain only digits
|
||||
*/
|
||||
boolean checkChecksum(String s) throws FormatException {
|
||||
return checkStandardUPCEANChecksum(s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the UPC/EAN checksum on a string of digits, and reports
|
||||
* whether the checksum is correct or not.
|
||||
*
|
||||
* @param s string of digits to check
|
||||
* @return true iff string of digits passes the UPC/EAN checksum algorithm
|
||||
* @throws FormatException if the string does not contain only digits
|
||||
*/
|
||||
static boolean checkStandardUPCEANChecksum(CharSequence s) throws FormatException {
|
||||
int length = s.length();
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
int check = Character.digit(s.charAt(length - 1), 10);
|
||||
return getStandardUPCEANChecksum(s.subSequence(0, length - 1)) == check;
|
||||
}
|
||||
|
||||
static int getStandardUPCEANChecksum(CharSequence s) throws FormatException {
|
||||
int length = s.length();
|
||||
int sum = 0;
|
||||
for (int i = length - 1; i >= 0; i -= 2) {
|
||||
int digit = s.charAt(i) - '0';
|
||||
if (digit < 0 || digit > 9) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
sum += digit;
|
||||
}
|
||||
sum *= 3;
|
||||
for (int i = length - 2; i >= 0; i -= 2) {
|
||||
int digit = s.charAt(i) - '0';
|
||||
if (digit < 0 || digit > 9) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
sum += digit;
|
||||
}
|
||||
return (1000 - sum) % 10;
|
||||
}
|
||||
|
||||
int[] decodeEnd(BitArray row, int endStart) throws NotFoundException {
|
||||
return findGuardPattern(row, endStart, false, START_END_PATTERN);
|
||||
}
|
||||
|
||||
static int[] findGuardPattern(BitArray row,
|
||||
int rowOffset,
|
||||
boolean whiteFirst,
|
||||
int[] pattern) throws NotFoundException {
|
||||
return findGuardPattern(row, rowOffset, whiteFirst, pattern, new int[pattern.length]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param row row of black/white values to search
|
||||
* @param rowOffset position to start search
|
||||
* @param whiteFirst if true, indicates that the pattern specifies white/black/white/...
|
||||
* pixel counts, otherwise, it is interpreted as black/white/black/...
|
||||
* @param pattern pattern of counts of number of black and white pixels that are being
|
||||
* searched for as a pattern
|
||||
* @param counters array of counters, as long as pattern, to re-use
|
||||
* @return start/end horizontal offset of guard pattern, as an array of two ints
|
||||
* @throws NotFoundException if pattern is not found
|
||||
*/
|
||||
private static int[] findGuardPattern(BitArray row,
|
||||
int rowOffset,
|
||||
boolean whiteFirst,
|
||||
int[] pattern,
|
||||
int[] counters) throws NotFoundException {
|
||||
int width = row.getSize();
|
||||
rowOffset = whiteFirst ? row.getNextUnset(rowOffset) : row.getNextSet(rowOffset);
|
||||
int counterPosition = 0;
|
||||
int patternStart = rowOffset;
|
||||
int patternLength = pattern.length;
|
||||
boolean isWhite = whiteFirst;
|
||||
for (int x = rowOffset; x < width; x++) {
|
||||
if (row.get(x) != isWhite) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == patternLength - 1) {
|
||||
if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
|
||||
return new int[]{patternStart, x};
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to decode a single UPC/EAN-encoded digit.
|
||||
*
|
||||
* @param row row of black/white values to decode
|
||||
* @param counters the counts of runs of observed black/white/black/... values
|
||||
* @param rowOffset horizontal offset to start decoding from
|
||||
* @param patterns the set of patterns to use to decode -- sometimes different encodings
|
||||
* for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
|
||||
* be used
|
||||
* @return horizontal offset of first pixel beyond the decoded digit
|
||||
* @throws NotFoundException if digit cannot be decoded
|
||||
*/
|
||||
static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns)
|
||||
throws NotFoundException {
|
||||
recordPattern(row, rowOffset, counters);
|
||||
float bestVariance = MAX_AVG_VARIANCE; // worst variance we'll accept
|
||||
int bestMatch = -1;
|
||||
int max = patterns.length;
|
||||
for (int i = 0; i < max; i++) {
|
||||
int[] pattern = patterns[i];
|
||||
float variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
|
||||
if (variance < bestVariance) {
|
||||
bestVariance = variance;
|
||||
bestMatch = i;
|
||||
}
|
||||
}
|
||||
if (bestMatch >= 0) {
|
||||
return bestMatch;
|
||||
} else {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the format of this decoder.
|
||||
*
|
||||
* @return The 1D format.
|
||||
*/
|
||||
abstract BarcodeFormat getBarcodeFormat();
|
||||
|
||||
/**
|
||||
* Subclasses override this to decode the portion of a barcode between the start
|
||||
* and end guard patterns.
|
||||
*
|
||||
* @param row row of black/white values to search
|
||||
* @param startRange start/end offset of start guard pattern
|
||||
* @param resultString {@link StringBuilder} to append decoded chars to
|
||||
* @return horizontal offset of first pixel after the "middle" that was decoded
|
||||
* @throws NotFoundException if decoding could not complete successfully
|
||||
*/
|
||||
protected abstract int decodeMiddle(BitArray row,
|
||||
int[] startRange,
|
||||
StringBuilder resultString) throws NotFoundException;
|
||||
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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;
|
||||
|
||||
/**
|
||||
* <p>Encapsulates functionality and implementation that is common to UPC and EAN families
|
||||
* of one-dimensional barcodes.</p>
|
||||
*
|
||||
* @author aripollak@gmail.com (Ari Pollak)
|
||||
* @author dsbnatut@gmail.com (Kazuki Nishiura)
|
||||
*/
|
||||
public abstract class UPCEANWriter extends OneDimensionalCodeWriter {
|
||||
|
||||
@Override
|
||||
public int getDefaultMargin() {
|
||||
// Use a different default more appropriate for UPC/EAN
|
||||
return 9;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,182 +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.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* <p>Implements decoding of the UPC-E format.</p>
|
||||
* <p><a href="http://www.barcodeisland.com/upce.phtml">This</a> is a great reference for
|
||||
* UPC-E information.</p>
|
||||
*
|
||||
* @author Sean Owen
|
||||
*/
|
||||
public final class UPCEReader extends UPCEANReader {
|
||||
|
||||
/**
|
||||
* The pattern that marks the middle, and end, of a UPC-E pattern.
|
||||
* There is no "second half" to a UPC-E barcode.
|
||||
*/
|
||||
private static final int[] MIDDLE_END_PATTERN = {1, 1, 1, 1, 1, 1};
|
||||
|
||||
// For an UPC-E barcode, the final digit is represented by the parities used
|
||||
// to encode the middle six digits, according to the table below.
|
||||
//
|
||||
// Parity of next 6 digits
|
||||
// Digit 0 1 2 3 4 5
|
||||
// 0 Even Even Even Odd Odd Odd
|
||||
// 1 Even Even Odd Even Odd Odd
|
||||
// 2 Even Even Odd Odd Even Odd
|
||||
// 3 Even Even Odd Odd Odd Even
|
||||
// 4 Even Odd Even Even Odd Odd
|
||||
// 5 Even Odd Odd Even Even Odd
|
||||
// 6 Even Odd Odd Odd Even Even
|
||||
// 7 Even Odd Even Odd Even Odd
|
||||
// 8 Even Odd Even Odd Odd Even
|
||||
// 9 Even Odd Odd Even Odd Even
|
||||
//
|
||||
// The encoding is represented by the following array, which is a bit pattern
|
||||
// using Odd = 0 and Even = 1. For example, 5 is represented by:
|
||||
//
|
||||
// Odd Even Even Odd Odd Even
|
||||
// in binary:
|
||||
// 0 1 1 0 0 1 == 0x19
|
||||
//
|
||||
|
||||
/**
|
||||
* See {@link #L_AND_G_PATTERNS}; these values similarly represent patterns of
|
||||
* even-odd parity encodings of digits that imply both the number system (0 or 1)
|
||||
* used, and the check digit.
|
||||
*/
|
||||
static final int[][] NUMSYS_AND_CHECK_DIGIT_PATTERNS = {
|
||||
{0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25},
|
||||
{0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A}
|
||||
};
|
||||
|
||||
private final int[] decodeMiddleCounters;
|
||||
|
||||
public UPCEReader() {
|
||||
decodeMiddleCounters = new int[4];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder result)
|
||||
throws NotFoundException {
|
||||
int[] counters = decodeMiddleCounters;
|
||||
counters[0] = 0;
|
||||
counters[1] = 0;
|
||||
counters[2] = 0;
|
||||
counters[3] = 0;
|
||||
int end = row.getSize();
|
||||
int rowOffset = startRange[1];
|
||||
|
||||
int lgPatternFound = 0;
|
||||
|
||||
for (int x = 0; x < 6 && rowOffset < end; x++) {
|
||||
int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS);
|
||||
result.append((char) ('0' + bestMatch % 10));
|
||||
for (int counter : counters) {
|
||||
rowOffset += counter;
|
||||
}
|
||||
if (bestMatch >= 10) {
|
||||
lgPatternFound |= 1 << (5 - x);
|
||||
}
|
||||
}
|
||||
|
||||
determineNumSysAndCheckDigit(result, lgPatternFound);
|
||||
|
||||
return rowOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int[] decodeEnd(BitArray row, int endStart) throws NotFoundException {
|
||||
return findGuardPattern(row, endStart, true, MIDDLE_END_PATTERN);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean checkChecksum(String s) throws FormatException {
|
||||
return super.checkChecksum(convertUPCEtoUPCA(s));
|
||||
}
|
||||
|
||||
private static void determineNumSysAndCheckDigit(StringBuilder resultString, int lgPatternFound)
|
||||
throws NotFoundException {
|
||||
|
||||
for (int numSys = 0; numSys <= 1; numSys++) {
|
||||
for (int d = 0; d < 10; d++) {
|
||||
if (lgPatternFound == NUMSYS_AND_CHECK_DIGIT_PATTERNS[numSys][d]) {
|
||||
resultString.insert(0, (char) ('0' + numSys));
|
||||
resultString.append((char) ('0' + d));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
@Override
|
||||
BarcodeFormat getBarcodeFormat() {
|
||||
return BarcodeFormat.UPC_E;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands a UPC-E value back into its full, equivalent UPC-A code value.
|
||||
*
|
||||
* @param upce UPC-E code as string of digits
|
||||
* @return equivalent UPC-A code as string of digits
|
||||
*/
|
||||
public static String convertUPCEtoUPCA(String upce) {
|
||||
char[] upceChars = new char[6];
|
||||
upce.getChars(1, 7, upceChars, 0);
|
||||
StringBuilder result = new StringBuilder(12);
|
||||
result.append(upce.charAt(0));
|
||||
char lastChar = upceChars[5];
|
||||
switch (lastChar) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
result.append(upceChars, 0, 2);
|
||||
result.append(lastChar);
|
||||
result.append("0000");
|
||||
result.append(upceChars, 2, 3);
|
||||
break;
|
||||
case '3':
|
||||
result.append(upceChars, 0, 3);
|
||||
result.append("00000");
|
||||
result.append(upceChars, 3, 2);
|
||||
break;
|
||||
case '4':
|
||||
result.append(upceChars, 0, 4);
|
||||
result.append("00000");
|
||||
result.append(upceChars[4]);
|
||||
break;
|
||||
default:
|
||||
result.append(upceChars, 0, 5);
|
||||
result.append("0000");
|
||||
result.append(lastChar);
|
||||
break;
|
||||
}
|
||||
// Only append check digit in conversion if supplied
|
||||
if (upce.length() >= 8) {
|
||||
result.append(upce.charAt(7));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
|
||||
/**
|
||||
* This object renders an UPC-E code as a {@link BitMatrix}.
|
||||
*
|
||||
* @author 0979097955s@gmail.com (RX)
|
||||
*/
|
||||
public final class UPCEWriter extends UPCEANWriter {
|
||||
|
||||
private static final int CODE_WIDTH = 3 + // start guard
|
||||
(7 * 6) + // bars
|
||||
6; // end guard
|
||||
|
||||
@Override
|
||||
protected Collection<BarcodeFormat> getSupportedWriteFormats() {
|
||||
return Collections.singleton(BarcodeFormat.UPC_E);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean[] encode(String contents) {
|
||||
int length = contents.length();
|
||||
switch (length) {
|
||||
case 7:
|
||||
// No check digit present, calculate it and add it
|
||||
int check;
|
||||
try {
|
||||
check = UPCEANReader.getStandardUPCEANChecksum(UPCEReader.convertUPCEtoUPCA(contents));
|
||||
} catch (FormatException fe) {
|
||||
throw new IllegalArgumentException(fe);
|
||||
}
|
||||
contents += check;
|
||||
break;
|
||||
case 8:
|
||||
try {
|
||||
if (!UPCEANReader.checkStandardUPCEANChecksum(UPCEReader.convertUPCEtoUPCA(contents))) {
|
||||
throw new IllegalArgumentException("Contents do not pass checksum");
|
||||
}
|
||||
} catch (FormatException ignored) {
|
||||
throw new IllegalArgumentException("Illegal contents");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Requested contents should be 7 or 8 digits long, but got " + length);
|
||||
}
|
||||
|
||||
checkNumeric(contents);
|
||||
|
||||
int firstDigit = Character.digit(contents.charAt(0), 10);
|
||||
if (firstDigit != 0 && firstDigit != 1) {
|
||||
throw new IllegalArgumentException("Number system must be 0 or 1");
|
||||
}
|
||||
|
||||
int checkDigit = Character.digit(contents.charAt(7), 10);
|
||||
int parities = UPCEReader.NUMSYS_AND_CHECK_DIGIT_PATTERNS[firstDigit][checkDigit];
|
||||
boolean[] result = new boolean[CODE_WIDTH];
|
||||
|
||||
int pos = appendPattern(result, 0, UPCEANReader.START_END_PATTERN, true);
|
||||
|
||||
for (int i = 1; i <= 6; i++) {
|
||||
int digit = Character.digit(contents.charAt(i), 10);
|
||||
if ((parities >> (6 - i) & 1) == 1) {
|
||||
digit += 10;
|
||||
}
|
||||
pos += appendPattern(result, pos, UPCEANReader.L_AND_G_PATTERNS[digit], false);
|
||||
}
|
||||
|
||||
appendPattern(result, pos, UPCEANReader.END_PATTERN, false);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,140 +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.rss;
|
||||
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.detector.MathUtils;
|
||||
import com.google.zxing.oned.OneDReader;
|
||||
|
||||
/**
|
||||
* Superclass of {@link OneDReader} implementations that read barcodes in the RSS family
|
||||
* of formats.
|
||||
*/
|
||||
public abstract class AbstractRSSReader extends OneDReader {
|
||||
|
||||
private static final float MAX_AVG_VARIANCE = 0.2f;
|
||||
private static final float MAX_INDIVIDUAL_VARIANCE = 0.45f;
|
||||
|
||||
private static final float MIN_FINDER_PATTERN_RATIO = 9.5f / 12.0f;
|
||||
private static final float MAX_FINDER_PATTERN_RATIO = 12.5f / 14.0f;
|
||||
|
||||
private final int[] decodeFinderCounters;
|
||||
private final int[] dataCharacterCounters;
|
||||
private final float[] oddRoundingErrors;
|
||||
private final float[] evenRoundingErrors;
|
||||
private final int[] oddCounts;
|
||||
private final int[] evenCounts;
|
||||
|
||||
protected AbstractRSSReader() {
|
||||
decodeFinderCounters = new int[4];
|
||||
dataCharacterCounters = new int[8];
|
||||
oddRoundingErrors = new float[4];
|
||||
evenRoundingErrors = new float[4];
|
||||
oddCounts = new int[dataCharacterCounters.length / 2];
|
||||
evenCounts = new int[dataCharacterCounters.length / 2];
|
||||
}
|
||||
|
||||
protected final int[] getDecodeFinderCounters() {
|
||||
return decodeFinderCounters;
|
||||
}
|
||||
|
||||
protected final int[] getDataCharacterCounters() {
|
||||
return dataCharacterCounters;
|
||||
}
|
||||
|
||||
protected final float[] getOddRoundingErrors() {
|
||||
return oddRoundingErrors;
|
||||
}
|
||||
|
||||
protected final float[] getEvenRoundingErrors() {
|
||||
return evenRoundingErrors;
|
||||
}
|
||||
|
||||
protected final int[] getOddCounts() {
|
||||
return oddCounts;
|
||||
}
|
||||
|
||||
protected final int[] getEvenCounts() {
|
||||
return evenCounts;
|
||||
}
|
||||
|
||||
protected static int parseFinderValue(int[] counters,
|
||||
int[][] finderPatterns) throws NotFoundException {
|
||||
for (int value = 0; value < finderPatterns.length; value++) {
|
||||
if (patternMatchVariance(counters, finderPatterns[value], MAX_INDIVIDUAL_VARIANCE) <
|
||||
MAX_AVG_VARIANCE) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array values to sum
|
||||
* @return sum of values
|
||||
* @deprecated call {@link MathUtils#sum(int[])}
|
||||
*/
|
||||
@Deprecated
|
||||
protected static int count(int[] array) {
|
||||
return MathUtils.sum(array);
|
||||
}
|
||||
|
||||
protected static void increment(int[] array, float[] errors) {
|
||||
int index = 0;
|
||||
float biggestError = errors[0];
|
||||
for (int i = 1; i < array.length; i++) {
|
||||
if (errors[i] > biggestError) {
|
||||
biggestError = errors[i];
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
array[index]++;
|
||||
}
|
||||
|
||||
protected static void decrement(int[] array, float[] errors) {
|
||||
int index = 0;
|
||||
float biggestError = errors[0];
|
||||
for (int i = 1; i < array.length; i++) {
|
||||
if (errors[i] < biggestError) {
|
||||
biggestError = errors[i];
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
array[index]--;
|
||||
}
|
||||
|
||||
protected static boolean isFinderPattern(int[] counters) {
|
||||
int firstTwoSum = counters[0] + counters[1];
|
||||
int sum = firstTwoSum + counters[2] + counters[3];
|
||||
float ratio = firstTwoSum / (float) sum;
|
||||
if (ratio >= MIN_FINDER_PATTERN_RATIO && ratio <= MAX_FINDER_PATTERN_RATIO) {
|
||||
// passes ratio test in spec, but see if the counts are unreasonable
|
||||
int minCounter = Integer.MAX_VALUE;
|
||||
int maxCounter = Integer.MIN_VALUE;
|
||||
for (int counter : counters) {
|
||||
if (counter > maxCounter) {
|
||||
maxCounter = counter;
|
||||
}
|
||||
if (counter < minCounter) {
|
||||
minCounter = counter;
|
||||
}
|
||||
}
|
||||
return maxCounter < 10 * minCounter;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.rss;
|
||||
|
||||
/**
|
||||
* Encapsulates a since character value in an RSS barcode, including its checksum information.
|
||||
*/
|
||||
public class DataCharacter {
|
||||
|
||||
private final int value;
|
||||
private final int checksumPortion;
|
||||
|
||||
public DataCharacter(int value, int checksumPortion) {
|
||||
this.value = value;
|
||||
this.checksumPortion = checksumPortion;
|
||||
}
|
||||
|
||||
public final int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public final int getChecksumPortion() {
|
||||
return checksumPortion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return value + "(" + checksumPortion + ')';
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object o) {
|
||||
if (!(o instanceof DataCharacter)) {
|
||||
return false;
|
||||
}
|
||||
DataCharacter that = (DataCharacter) o;
|
||||
return value == that.value && checksumPortion == that.checksumPortion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return value ^ checksumPortion;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.rss;
|
||||
|
||||
import com.google.zxing.ResultPoint;
|
||||
|
||||
/**
|
||||
* Encapsulates an RSS barcode finder pattern, including its start/end position and row.
|
||||
*/
|
||||
public final class FinderPattern {
|
||||
|
||||
private final int value;
|
||||
private final int[] startEnd;
|
||||
private final ResultPoint[] resultPoints;
|
||||
|
||||
public FinderPattern(int value, int[] startEnd, int start, int end, int rowNumber) {
|
||||
this.value = value;
|
||||
this.startEnd = startEnd;
|
||||
this.resultPoints = new ResultPoint[] {
|
||||
new ResultPoint(start, rowNumber),
|
||||
new ResultPoint(end, rowNumber),
|
||||
};
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public int[] getStartEnd() {
|
||||
return startEnd;
|
||||
}
|
||||
|
||||
public ResultPoint[] getResultPoints() {
|
||||
return resultPoints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof FinderPattern)) {
|
||||
return false;
|
||||
}
|
||||
FinderPattern that = (FinderPattern) o;
|
||||
return value == that.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.rss;
|
||||
|
||||
final class Pair extends DataCharacter {
|
||||
|
||||
private final FinderPattern finderPattern;
|
||||
private int count;
|
||||
|
||||
Pair(int value, int checksumPortion, FinderPattern finderPattern) {
|
||||
super(value, checksumPortion);
|
||||
this.finderPattern = finderPattern;
|
||||
}
|
||||
|
||||
FinderPattern getFinderPattern() {
|
||||
return finderPattern;
|
||||
}
|
||||
|
||||
int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
void incrementCount() {
|
||||
count++;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,471 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.rss;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.ResultPointCallback;
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.common.detector.MathUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Decodes RSS-14, including truncated and stacked variants. See ISO/IEC 24724:2006.
|
||||
*/
|
||||
public final class RSS14Reader extends AbstractRSSReader {
|
||||
|
||||
private static final int[] OUTSIDE_EVEN_TOTAL_SUBSET = {1,10,34,70,126};
|
||||
private static final int[] INSIDE_ODD_TOTAL_SUBSET = {4,20,48,81};
|
||||
private static final int[] OUTSIDE_GSUM = {0,161,961,2015,2715};
|
||||
private static final int[] INSIDE_GSUM = {0,336,1036,1516};
|
||||
private static final int[] OUTSIDE_ODD_WIDEST = {8,6,4,3,1};
|
||||
private static final int[] INSIDE_ODD_WIDEST = {2,4,6,8};
|
||||
|
||||
private static final int[][] FINDER_PATTERNS = {
|
||||
{3,8,2,1},
|
||||
{3,5,5,1},
|
||||
{3,3,7,1},
|
||||
{3,1,9,1},
|
||||
{2,7,4,1},
|
||||
{2,5,6,1},
|
||||
{2,3,8,1},
|
||||
{1,5,7,1},
|
||||
{1,3,9,1},
|
||||
};
|
||||
|
||||
private final List<Pair> possibleLeftPairs;
|
||||
private final List<Pair> possibleRightPairs;
|
||||
|
||||
public RSS14Reader() {
|
||||
possibleLeftPairs = new ArrayList<>();
|
||||
possibleRightPairs = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber,
|
||||
BitArray row,
|
||||
Map<DecodeHintType,?> hints) throws NotFoundException {
|
||||
Pair leftPair = decodePair(row, false, rowNumber, hints);
|
||||
addOrTally(possibleLeftPairs, leftPair);
|
||||
row.reverse();
|
||||
Pair rightPair = decodePair(row, true, rowNumber, hints);
|
||||
addOrTally(possibleRightPairs, rightPair);
|
||||
row.reverse();
|
||||
for (Pair left : possibleLeftPairs) {
|
||||
if (left.getCount() > 1) {
|
||||
for (Pair right : possibleRightPairs) {
|
||||
if (right.getCount() > 1 && checkChecksum(left, right)) {
|
||||
return constructResult(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
private static void addOrTally(Collection<Pair> possiblePairs, Pair pair) {
|
||||
if (pair == null) {
|
||||
return;
|
||||
}
|
||||
boolean found = false;
|
||||
for (Pair other : possiblePairs) {
|
||||
if (other.getValue() == pair.getValue()) {
|
||||
other.incrementCount();
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
possiblePairs.add(pair);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
possibleLeftPairs.clear();
|
||||
possibleRightPairs.clear();
|
||||
}
|
||||
|
||||
private static Result constructResult(Pair leftPair, Pair rightPair) {
|
||||
long symbolValue = 4537077L * leftPair.getValue() + rightPair.getValue();
|
||||
String text = String.valueOf(symbolValue);
|
||||
|
||||
StringBuilder buffer = new StringBuilder(14);
|
||||
for (int i = 13 - text.length(); i > 0; i--) {
|
||||
buffer.append('0');
|
||||
}
|
||||
buffer.append(text);
|
||||
|
||||
int checkDigit = 0;
|
||||
for (int i = 0; i < 13; i++) {
|
||||
int digit = buffer.charAt(i) - '0';
|
||||
checkDigit += (i & 0x01) == 0 ? 3 * digit : digit;
|
||||
}
|
||||
checkDigit = 10 - (checkDigit % 10);
|
||||
if (checkDigit == 10) {
|
||||
checkDigit = 0;
|
||||
}
|
||||
buffer.append(checkDigit);
|
||||
|
||||
ResultPoint[] leftPoints = leftPair.getFinderPattern().getResultPoints();
|
||||
ResultPoint[] rightPoints = rightPair.getFinderPattern().getResultPoints();
|
||||
Result result = new Result(
|
||||
buffer.toString(),
|
||||
null,
|
||||
new ResultPoint[] { leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1], },
|
||||
BarcodeFormat.RSS_14);
|
||||
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]e0");
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean checkChecksum(Pair leftPair, Pair rightPair) {
|
||||
int checkValue = (leftPair.getChecksumPortion() + 16 * rightPair.getChecksumPortion()) % 79;
|
||||
int targetCheckValue =
|
||||
9 * leftPair.getFinderPattern().getValue() + rightPair.getFinderPattern().getValue();
|
||||
if (targetCheckValue > 72) {
|
||||
targetCheckValue--;
|
||||
}
|
||||
if (targetCheckValue > 8) {
|
||||
targetCheckValue--;
|
||||
}
|
||||
return checkValue == targetCheckValue;
|
||||
}
|
||||
|
||||
private Pair decodePair(BitArray row, boolean right, int rowNumber, Map<DecodeHintType,?> hints) {
|
||||
try {
|
||||
int[] startEnd = findFinderPattern(row, right);
|
||||
FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd);
|
||||
|
||||
ResultPointCallback resultPointCallback = hints == null ? null :
|
||||
(ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
|
||||
|
||||
if (resultPointCallback != null) {
|
||||
startEnd = pattern.getStartEnd();
|
||||
float center = (startEnd[0] + startEnd[1] - 1) / 2.0f;
|
||||
if (right) {
|
||||
// row is actually reversed
|
||||
center = row.getSize() - 1 - center;
|
||||
}
|
||||
resultPointCallback.foundPossibleResultPoint(new ResultPoint(center, rowNumber));
|
||||
}
|
||||
|
||||
DataCharacter outside = decodeDataCharacter(row, pattern, true);
|
||||
DataCharacter inside = decodeDataCharacter(row, pattern, false);
|
||||
return new Pair(1597 * outside.getValue() + inside.getValue(),
|
||||
outside.getChecksumPortion() + 4 * inside.getChecksumPortion(),
|
||||
pattern);
|
||||
} catch (NotFoundException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private DataCharacter decodeDataCharacter(BitArray row, FinderPattern pattern, boolean outsideChar)
|
||||
throws NotFoundException {
|
||||
|
||||
int[] counters = getDataCharacterCounters();
|
||||
Arrays.fill(counters, 0);
|
||||
|
||||
if (outsideChar) {
|
||||
recordPatternInReverse(row, pattern.getStartEnd()[0], counters);
|
||||
} else {
|
||||
recordPattern(row, pattern.getStartEnd()[1], counters);
|
||||
// reverse it
|
||||
for (int i = 0, j = counters.length - 1; i < j; i++, j--) {
|
||||
int temp = counters[i];
|
||||
counters[i] = counters[j];
|
||||
counters[j] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
int numModules = outsideChar ? 16 : 15;
|
||||
float elementWidth = MathUtils.sum(counters) / (float) numModules;
|
||||
|
||||
int[] oddCounts = this.getOddCounts();
|
||||
int[] evenCounts = this.getEvenCounts();
|
||||
float[] oddRoundingErrors = this.getOddRoundingErrors();
|
||||
float[] evenRoundingErrors = this.getEvenRoundingErrors();
|
||||
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
float value = counters[i] / elementWidth;
|
||||
int count = (int) (value + 0.5f); // Round
|
||||
if (count < 1) {
|
||||
count = 1;
|
||||
} else if (count > 8) {
|
||||
count = 8;
|
||||
}
|
||||
int offset = i / 2;
|
||||
if ((i & 0x01) == 0) {
|
||||
oddCounts[offset] = count;
|
||||
oddRoundingErrors[offset] = value - count;
|
||||
} else {
|
||||
evenCounts[offset] = count;
|
||||
evenRoundingErrors[offset] = value - count;
|
||||
}
|
||||
}
|
||||
|
||||
adjustOddEvenCounts(outsideChar, numModules);
|
||||
|
||||
int oddSum = 0;
|
||||
int oddChecksumPortion = 0;
|
||||
for (int i = oddCounts.length - 1; i >= 0; i--) {
|
||||
oddChecksumPortion *= 9;
|
||||
oddChecksumPortion += oddCounts[i];
|
||||
oddSum += oddCounts[i];
|
||||
}
|
||||
int evenChecksumPortion = 0;
|
||||
int evenSum = 0;
|
||||
for (int i = evenCounts.length - 1; i >= 0; i--) {
|
||||
evenChecksumPortion *= 9;
|
||||
evenChecksumPortion += evenCounts[i];
|
||||
evenSum += evenCounts[i];
|
||||
}
|
||||
int checksumPortion = oddChecksumPortion + 3 * evenChecksumPortion;
|
||||
|
||||
if (outsideChar) {
|
||||
if ((oddSum & 0x01) != 0 || oddSum > 12 || oddSum < 4) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
int group = (12 - oddSum) / 2;
|
||||
int oddWidest = OUTSIDE_ODD_WIDEST[group];
|
||||
int evenWidest = 9 - oddWidest;
|
||||
int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, false);
|
||||
int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, true);
|
||||
int tEven = OUTSIDE_EVEN_TOTAL_SUBSET[group];
|
||||
int gSum = OUTSIDE_GSUM[group];
|
||||
return new DataCharacter(vOdd * tEven + vEven + gSum, checksumPortion);
|
||||
} else {
|
||||
if ((evenSum & 0x01) != 0 || evenSum > 10 || evenSum < 4) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
int group = (10 - evenSum) / 2;
|
||||
int oddWidest = INSIDE_ODD_WIDEST[group];
|
||||
int evenWidest = 9 - oddWidest;
|
||||
int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true);
|
||||
int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false);
|
||||
int tOdd = INSIDE_ODD_TOTAL_SUBSET[group];
|
||||
int gSum = INSIDE_GSUM[group];
|
||||
return new DataCharacter(vEven * tOdd + vOdd + gSum, checksumPortion);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int[] findFinderPattern(BitArray row, boolean rightFinderPattern)
|
||||
throws NotFoundException {
|
||||
|
||||
int[] counters = getDecodeFinderCounters();
|
||||
counters[0] = 0;
|
||||
counters[1] = 0;
|
||||
counters[2] = 0;
|
||||
counters[3] = 0;
|
||||
|
||||
int width = row.getSize();
|
||||
boolean isWhite = false;
|
||||
int rowOffset = 0;
|
||||
while (rowOffset < width) {
|
||||
isWhite = !row.get(rowOffset);
|
||||
if (rightFinderPattern == isWhite) {
|
||||
// Will encounter white first when searching for right finder pattern
|
||||
break;
|
||||
}
|
||||
rowOffset++;
|
||||
}
|
||||
|
||||
int counterPosition = 0;
|
||||
int patternStart = rowOffset;
|
||||
for (int x = rowOffset; x < width; x++) {
|
||||
if (row.get(x) != isWhite) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == 3) {
|
||||
if (isFinderPattern(counters)) {
|
||||
return new int[]{patternStart, x};
|
||||
}
|
||||
patternStart += counters[0] + counters[1];
|
||||
counters[0] = counters[2];
|
||||
counters[1] = counters[3];
|
||||
counters[2] = 0;
|
||||
counters[3] = 0;
|
||||
counterPosition--;
|
||||
} else {
|
||||
counterPosition++;
|
||||
}
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
|
||||
}
|
||||
|
||||
private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, boolean right, int[] startEnd)
|
||||
throws NotFoundException {
|
||||
// Actually we found elements 2-5
|
||||
boolean firstIsBlack = row.get(startEnd[0]);
|
||||
int firstElementStart = startEnd[0] - 1;
|
||||
// Locate element 1
|
||||
while (firstElementStart >= 0 && firstIsBlack != row.get(firstElementStart)) {
|
||||
firstElementStart--;
|
||||
}
|
||||
firstElementStart++;
|
||||
int firstCounter = startEnd[0] - firstElementStart;
|
||||
// Make 'counters' hold 1-4
|
||||
int[] counters = getDecodeFinderCounters();
|
||||
System.arraycopy(counters, 0, counters, 1, counters.length - 1);
|
||||
counters[0] = firstCounter;
|
||||
int value = parseFinderValue(counters, FINDER_PATTERNS);
|
||||
int start = firstElementStart;
|
||||
int end = startEnd[1];
|
||||
if (right) {
|
||||
// row is actually reversed
|
||||
start = row.getSize() - 1 - start;
|
||||
end = row.getSize() - 1 - end;
|
||||
}
|
||||
return new FinderPattern(value, new int[] {firstElementStart, startEnd[1]}, start, end, rowNumber);
|
||||
}
|
||||
|
||||
private void adjustOddEvenCounts(boolean outsideChar, int numModules) throws NotFoundException {
|
||||
|
||||
int oddSum = MathUtils.sum(getOddCounts());
|
||||
int evenSum = MathUtils.sum(getEvenCounts());
|
||||
|
||||
boolean incrementOdd = false;
|
||||
boolean decrementOdd = false;
|
||||
boolean incrementEven = false;
|
||||
boolean decrementEven = false;
|
||||
|
||||
if (outsideChar) {
|
||||
if (oddSum > 12) {
|
||||
decrementOdd = true;
|
||||
} else if (oddSum < 4) {
|
||||
incrementOdd = true;
|
||||
}
|
||||
if (evenSum > 12) {
|
||||
decrementEven = true;
|
||||
} else if (evenSum < 4) {
|
||||
incrementEven = true;
|
||||
}
|
||||
} else {
|
||||
if (oddSum > 11) {
|
||||
decrementOdd = true;
|
||||
} else if (oddSum < 5) {
|
||||
incrementOdd = true;
|
||||
}
|
||||
if (evenSum > 10) {
|
||||
decrementEven = true;
|
||||
} else if (evenSum < 4) {
|
||||
incrementEven = true;
|
||||
}
|
||||
}
|
||||
|
||||
int mismatch = oddSum + evenSum - numModules;
|
||||
boolean oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0);
|
||||
boolean evenParityBad = (evenSum & 0x01) == 1;
|
||||
/*if (mismatch == 2) {
|
||||
if (!(oddParityBad && evenParityBad)) {
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
decrementOdd = true;
|
||||
decrementEven = true;
|
||||
} else if (mismatch == -2) {
|
||||
if (!(oddParityBad && evenParityBad)) {
|
||||
throw ReaderException.getInstance();
|
||||
}
|
||||
incrementOdd = true;
|
||||
incrementEven = true;
|
||||
} else */
|
||||
switch (mismatch) {
|
||||
case 1:
|
||||
if (oddParityBad) {
|
||||
if (evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
decrementOdd = true;
|
||||
} else {
|
||||
if (!evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
decrementEven = true;
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
if (oddParityBad) {
|
||||
if (evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
incrementOdd = true;
|
||||
} else {
|
||||
if (!evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
incrementEven = true;
|
||||
}
|
||||
break;
|
||||
case 0:
|
||||
if (oddParityBad) {
|
||||
if (!evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
// Both bad
|
||||
if (oddSum < evenSum) {
|
||||
incrementOdd = true;
|
||||
decrementEven = true;
|
||||
} else {
|
||||
decrementOdd = true;
|
||||
incrementEven = true;
|
||||
}
|
||||
} else {
|
||||
if (evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
// Nothing to do!
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
if (incrementOdd) {
|
||||
if (decrementOdd) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
increment(getOddCounts(), getOddRoundingErrors());
|
||||
}
|
||||
if (decrementOdd) {
|
||||
decrement(getOddCounts(), getOddRoundingErrors());
|
||||
}
|
||||
if (incrementEven) {
|
||||
if (decrementEven) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
increment(getEvenCounts(), getOddRoundingErrors());
|
||||
}
|
||||
if (decrementEven) {
|
||||
decrement(getEvenCounts(), getEvenRoundingErrors());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2009 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.rss;
|
||||
|
||||
/** Adapted from listings in ISO/IEC 24724 Appendix B and Appendix G. */
|
||||
public final class RSSUtils {
|
||||
|
||||
private RSSUtils() {}
|
||||
|
||||
public static int getRSSvalue(int[] widths, int maxWidth, boolean noNarrow) {
|
||||
int n = 0;
|
||||
for (int width : widths) {
|
||||
n += width;
|
||||
}
|
||||
int val = 0;
|
||||
int narrowMask = 0;
|
||||
int elements = widths.length;
|
||||
for (int bar = 0; bar < elements - 1; bar++) {
|
||||
int elmWidth;
|
||||
for (elmWidth = 1, narrowMask |= 1 << bar;
|
||||
elmWidth < widths[bar];
|
||||
elmWidth++, narrowMask &= ~(1 << bar)) {
|
||||
int subVal = combins(n - elmWidth - 1, elements - bar - 2);
|
||||
if (noNarrow && (narrowMask == 0) &&
|
||||
(n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) {
|
||||
subVal -= combins(n - elmWidth - (elements - bar),
|
||||
elements - bar - 2);
|
||||
}
|
||||
if (elements - bar - 1 > 1) {
|
||||
int lessVal = 0;
|
||||
for (int mxwElement = n - elmWidth - (elements - bar - 2);
|
||||
mxwElement > maxWidth; mxwElement--) {
|
||||
lessVal += combins(n - elmWidth - mxwElement - 1,
|
||||
elements - bar - 3);
|
||||
}
|
||||
subVal -= lessVal * (elements - 1 - bar);
|
||||
} else if (n - elmWidth > maxWidth) {
|
||||
subVal--;
|
||||
}
|
||||
val += subVal;
|
||||
}
|
||||
n -= elmWidth;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
private static int combins(int n, int r) {
|
||||
int maxDenom;
|
||||
int minDenom;
|
||||
if (n - r > r) {
|
||||
minDenom = r;
|
||||
maxDenom = n - r;
|
||||
} else {
|
||||
minDenom = n - r;
|
||||
maxDenom = r;
|
||||
}
|
||||
int val = 1;
|
||||
int j = 1;
|
||||
for (int i = n; i > maxDenom; i--) {
|
||||
val *= i;
|
||||
if (j <= minDenom) {
|
||||
val /= j;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
while (j <= minDenom) {
|
||||
val /= j;
|
||||
j++;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,85 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class BitArrayBuilder {
|
||||
|
||||
private BitArrayBuilder() {
|
||||
}
|
||||
|
||||
static BitArray buildBitArray(List<ExpandedPair> pairs) {
|
||||
int charNumber = (pairs.size() * 2) - 1;
|
||||
if (pairs.get(pairs.size() - 1).getRightChar() == null) {
|
||||
charNumber -= 1;
|
||||
}
|
||||
|
||||
int size = 12 * charNumber;
|
||||
|
||||
BitArray binary = new BitArray(size);
|
||||
int accPos = 0;
|
||||
|
||||
ExpandedPair firstPair = pairs.get(0);
|
||||
int firstValue = firstPair.getRightChar().getValue();
|
||||
for (int i = 11; i >= 0; --i) {
|
||||
if ((firstValue & (1 << i)) != 0) {
|
||||
binary.set(accPos);
|
||||
}
|
||||
accPos++;
|
||||
}
|
||||
|
||||
for (int i = 1; i < pairs.size(); ++i) {
|
||||
ExpandedPair currentPair = pairs.get(i);
|
||||
|
||||
int leftValue = currentPair.getLeftChar().getValue();
|
||||
for (int j = 11; j >= 0; --j) {
|
||||
if ((leftValue & (1 << j)) != 0) {
|
||||
binary.set(accPos);
|
||||
}
|
||||
accPos++;
|
||||
}
|
||||
|
||||
if (currentPair.getRightChar() != null) {
|
||||
int rightValue = currentPair.getRightChar().getValue();
|
||||
for (int j = 11; j >= 0; --j) {
|
||||
if ((rightValue & (1 << j)) != 0) {
|
||||
binary.set(accPos);
|
||||
}
|
||||
accPos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return binary;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded;
|
||||
|
||||
import com.google.zxing.oned.rss.DataCharacter;
|
||||
import com.google.zxing.oned.rss.FinderPattern;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
*/
|
||||
final class ExpandedPair {
|
||||
|
||||
private final DataCharacter leftChar;
|
||||
private final DataCharacter rightChar;
|
||||
private final FinderPattern finderPattern;
|
||||
|
||||
ExpandedPair(DataCharacter leftChar,
|
||||
DataCharacter rightChar,
|
||||
FinderPattern finderPattern) {
|
||||
this.leftChar = leftChar;
|
||||
this.rightChar = rightChar;
|
||||
this.finderPattern = finderPattern;
|
||||
}
|
||||
|
||||
DataCharacter getLeftChar() {
|
||||
return this.leftChar;
|
||||
}
|
||||
|
||||
DataCharacter getRightChar() {
|
||||
return this.rightChar;
|
||||
}
|
||||
|
||||
FinderPattern getFinderPattern() {
|
||||
return this.finderPattern;
|
||||
}
|
||||
|
||||
boolean mustBeLast() {
|
||||
return this.rightChar == null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return
|
||||
"[ " + leftChar + " , " + rightChar + " : " +
|
||||
(finderPattern == null ? "null" : finderPattern.getValue()) + " ]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ExpandedPair)) {
|
||||
return false;
|
||||
}
|
||||
ExpandedPair that = (ExpandedPair) o;
|
||||
return Objects.equals(leftChar, that.leftChar) &&
|
||||
Objects.equals(rightChar, that.rightChar) &&
|
||||
Objects.equals(finderPattern, that.finderPattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(leftChar) ^ Objects.hashCode(rightChar) ^ Objects.hashCode(finderPattern);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,69 +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.rss.expanded;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* One row of an RSS Expanded Stacked symbol, consisting of 1+ expanded pairs.
|
||||
*/
|
||||
final class ExpandedRow {
|
||||
|
||||
private final List<ExpandedPair> pairs;
|
||||
private final int rowNumber;
|
||||
|
||||
ExpandedRow(List<ExpandedPair> pairs, int rowNumber) {
|
||||
this.pairs = new ArrayList<>(pairs);
|
||||
this.rowNumber = rowNumber;
|
||||
}
|
||||
|
||||
List<ExpandedPair> getPairs() {
|
||||
return this.pairs;
|
||||
}
|
||||
|
||||
int getRowNumber() {
|
||||
return this.rowNumber;
|
||||
}
|
||||
|
||||
boolean isEquivalent(List<ExpandedPair> otherPairs) {
|
||||
return this.pairs.equals(otherPairs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{ " + pairs + " }";
|
||||
}
|
||||
|
||||
/**
|
||||
* Two rows are equal if they contain the same pairs in the same order.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ExpandedRow)) {
|
||||
return false;
|
||||
}
|
||||
ExpandedRow that = (ExpandedRow) o;
|
||||
return this.pairs.equals(that.pairs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return pairs.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,768 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded;
|
||||
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultMetadataType;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.BitArray;
|
||||
import com.google.zxing.common.detector.MathUtils;
|
||||
import com.google.zxing.oned.rss.AbstractRSSReader;
|
||||
import com.google.zxing.oned.rss.DataCharacter;
|
||||
import com.google.zxing.oned.rss.FinderPattern;
|
||||
import com.google.zxing.oned.rss.RSSUtils;
|
||||
import com.google.zxing.oned.rss.expanded.decoders.AbstractExpandedDecoder;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
public final class RSSExpandedReader extends AbstractRSSReader {
|
||||
|
||||
private static final int[] SYMBOL_WIDEST = {7, 5, 4, 3, 1};
|
||||
private static final int[] EVEN_TOTAL_SUBSET = {4, 20, 52, 104, 204};
|
||||
private static final int[] GSUM = {0, 348, 1388, 2948, 3988};
|
||||
|
||||
private static final int[][] FINDER_PATTERNS = {
|
||||
{1,8,4,1}, // A
|
||||
{3,6,4,1}, // B
|
||||
{3,4,6,1}, // C
|
||||
{3,2,8,1}, // D
|
||||
{2,6,5,1}, // E
|
||||
{2,2,9,1} // F
|
||||
};
|
||||
|
||||
private static final int[][] WEIGHTS = {
|
||||
{ 1, 3, 9, 27, 81, 32, 96, 77},
|
||||
{ 20, 60, 180, 118, 143, 7, 21, 63},
|
||||
{189, 145, 13, 39, 117, 140, 209, 205},
|
||||
{193, 157, 49, 147, 19, 57, 171, 91},
|
||||
{ 62, 186, 136, 197, 169, 85, 44, 132},
|
||||
{185, 133, 188, 142, 4, 12, 36, 108},
|
||||
{113, 128, 173, 97, 80, 29, 87, 50},
|
||||
{150, 28, 84, 41, 123, 158, 52, 156},
|
||||
{ 46, 138, 203, 187, 139, 206, 196, 166},
|
||||
{ 76, 17, 51, 153, 37, 111, 122, 155},
|
||||
{ 43, 129, 176, 106, 107, 110, 119, 146},
|
||||
{ 16, 48, 144, 10, 30, 90, 59, 177},
|
||||
{109, 116, 137, 200, 178, 112, 125, 164},
|
||||
{ 70, 210, 208, 202, 184, 130, 179, 115},
|
||||
{134, 191, 151, 31, 93, 68, 204, 190},
|
||||
{148, 22, 66, 198, 172, 94, 71, 2},
|
||||
{ 6, 18, 54, 162, 64, 192,154, 40},
|
||||
{120, 149, 25, 75, 14, 42,126, 167},
|
||||
{ 79, 26, 78, 23, 69, 207,199, 175},
|
||||
{103, 98, 83, 38, 114, 131, 182, 124},
|
||||
{161, 61, 183, 127, 170, 88, 53, 159},
|
||||
{ 55, 165, 73, 8, 24, 72, 5, 15},
|
||||
{ 45, 135, 194, 160, 58, 174, 100, 89}
|
||||
};
|
||||
|
||||
private static final int FINDER_PAT_A = 0;
|
||||
private static final int FINDER_PAT_B = 1;
|
||||
private static final int FINDER_PAT_C = 2;
|
||||
private static final int FINDER_PAT_D = 3;
|
||||
private static final int FINDER_PAT_E = 4;
|
||||
private static final int FINDER_PAT_F = 5;
|
||||
|
||||
@SuppressWarnings("checkstyle:lineLength")
|
||||
private static final int[][] FINDER_PATTERN_SEQUENCES = {
|
||||
{ FINDER_PAT_A, FINDER_PAT_A },
|
||||
{ FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B },
|
||||
{ FINDER_PAT_A, FINDER_PAT_C, FINDER_PAT_B, FINDER_PAT_D },
|
||||
{ FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_C },
|
||||
{ FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_D, FINDER_PAT_F },
|
||||
{ FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F },
|
||||
{ FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_D },
|
||||
{ FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_E },
|
||||
{ FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F },
|
||||
{ FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F },
|
||||
};
|
||||
|
||||
private static final int MAX_PAIRS = 11;
|
||||
|
||||
private final List<ExpandedPair> pairs = new ArrayList<>(MAX_PAIRS);
|
||||
private final List<ExpandedRow> rows = new ArrayList<>();
|
||||
private final int [] startEnd = new int[2];
|
||||
private boolean startFromEven;
|
||||
|
||||
@Override
|
||||
public Result decodeRow(int rowNumber,
|
||||
BitArray row,
|
||||
Map<DecodeHintType,?> hints) throws NotFoundException, FormatException {
|
||||
// Rows can start with even pattern in case in prev rows there where odd number of patters.
|
||||
// So lets try twice
|
||||
this.pairs.clear();
|
||||
this.startFromEven = false;
|
||||
try {
|
||||
return constructResult(decodeRow2pairs(rowNumber, row));
|
||||
} catch (NotFoundException e) {
|
||||
// OK
|
||||
}
|
||||
|
||||
this.pairs.clear();
|
||||
this.startFromEven = true;
|
||||
return constructResult(decodeRow2pairs(rowNumber, row));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
this.pairs.clear();
|
||||
this.rows.clear();
|
||||
}
|
||||
|
||||
// Not private for testing
|
||||
List<ExpandedPair> decodeRow2pairs(int rowNumber, BitArray row) throws NotFoundException {
|
||||
boolean done = false;
|
||||
while (!done) {
|
||||
try {
|
||||
this.pairs.add(retrieveNextPair(row, this.pairs, rowNumber));
|
||||
} catch (NotFoundException nfe) {
|
||||
if (this.pairs.isEmpty()) {
|
||||
throw nfe;
|
||||
}
|
||||
// exit this loop when retrieveNextPair() fails and throws
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: verify sequence of finder patterns as in checkPairSequence()
|
||||
if (checkChecksum()) {
|
||||
return this.pairs;
|
||||
}
|
||||
|
||||
boolean tryStackedDecode = !this.rows.isEmpty();
|
||||
storeRow(rowNumber); // TODO: deal with reversed rows
|
||||
if (tryStackedDecode) {
|
||||
// When the image is 180-rotated, then rows are sorted in wrong direction.
|
||||
// Try twice with both the directions.
|
||||
List<ExpandedPair> ps = checkRows(false);
|
||||
if (ps != null) {
|
||||
return ps;
|
||||
}
|
||||
ps = checkRows(true);
|
||||
if (ps != null) {
|
||||
return ps;
|
||||
}
|
||||
}
|
||||
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
private List<ExpandedPair> checkRows(boolean reverse) {
|
||||
// Limit number of rows we are checking
|
||||
// We use recursive algorithm with pure complexity and don't want it to take forever
|
||||
// Stacked barcode can have up to 11 rows, so 25 seems reasonable enough
|
||||
if (this.rows.size() > 25) {
|
||||
this.rows.clear(); // We will never have a chance to get result, so clear it
|
||||
return null;
|
||||
}
|
||||
|
||||
this.pairs.clear();
|
||||
if (reverse) {
|
||||
Collections.reverse(this.rows);
|
||||
}
|
||||
|
||||
List<ExpandedPair> ps = null;
|
||||
try {
|
||||
ps = checkRows(new ArrayList<>(), 0);
|
||||
} catch (NotFoundException e) {
|
||||
// OK
|
||||
}
|
||||
|
||||
if (reverse) {
|
||||
Collections.reverse(this.rows);
|
||||
}
|
||||
|
||||
return ps;
|
||||
}
|
||||
|
||||
// Try to construct a valid rows sequence
|
||||
// Recursion is used to implement backtracking
|
||||
private List<ExpandedPair> checkRows(List<ExpandedRow> collectedRows, int currentRow) throws NotFoundException {
|
||||
for (int i = currentRow; i < rows.size(); i++) {
|
||||
ExpandedRow row = rows.get(i);
|
||||
this.pairs.clear();
|
||||
for (ExpandedRow collectedRow : collectedRows) {
|
||||
this.pairs.addAll(collectedRow.getPairs());
|
||||
}
|
||||
this.pairs.addAll(row.getPairs());
|
||||
|
||||
if (isValidSequence(this.pairs)) {
|
||||
if (checkChecksum()) {
|
||||
return this.pairs;
|
||||
}
|
||||
|
||||
List<ExpandedRow> rs = new ArrayList<>(collectedRows);
|
||||
rs.add(row);
|
||||
try {
|
||||
// Recursion: try to add more rows
|
||||
return checkRows(rs, i + 1);
|
||||
} catch (NotFoundException e) {
|
||||
// We failed, try the next candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
// Whether the pairs form a valid find pattern sequence,
|
||||
// either complete or a prefix
|
||||
private static boolean isValidSequence(List<ExpandedPair> pairs) {
|
||||
for (int[] sequence : FINDER_PATTERN_SEQUENCES) {
|
||||
if (pairs.size() <= sequence.length) {
|
||||
boolean stop = true;
|
||||
for (int j = 0; j < pairs.size(); j++) {
|
||||
if (pairs.get(j).getFinderPattern().getValue() != sequence[j]) {
|
||||
stop = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stop) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void storeRow(int rowNumber) {
|
||||
// Discard if duplicate above or below; otherwise insert in order by row number.
|
||||
int insertPos = 0;
|
||||
boolean prevIsSame = false;
|
||||
boolean nextIsSame = false;
|
||||
while (insertPos < this.rows.size()) {
|
||||
ExpandedRow erow = this.rows.get(insertPos);
|
||||
if (erow.getRowNumber() > rowNumber) {
|
||||
nextIsSame = erow.isEquivalent(this.pairs);
|
||||
break;
|
||||
}
|
||||
prevIsSame = erow.isEquivalent(this.pairs);
|
||||
insertPos++;
|
||||
}
|
||||
if (nextIsSame || prevIsSame) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the row was partially decoded (e.g. 2 pairs found instead of 3),
|
||||
// it will prevent us from detecting the barcode.
|
||||
// Try to merge partial rows
|
||||
|
||||
// Check whether the row is part of an already detected row
|
||||
if (isPartialRow(this.pairs, this.rows)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rows.add(insertPos, new ExpandedRow(this.pairs, rowNumber));
|
||||
|
||||
removePartialRows(this.pairs, this.rows);
|
||||
}
|
||||
|
||||
// Remove all the rows that contains only specified pairs
|
||||
private static void removePartialRows(Collection<ExpandedPair> pairs, Collection<ExpandedRow> rows) {
|
||||
for (Iterator<ExpandedRow> iterator = rows.iterator(); iterator.hasNext();) {
|
||||
ExpandedRow r = iterator.next();
|
||||
if (r.getPairs().size() != pairs.size()) {
|
||||
boolean allFound = true;
|
||||
for (ExpandedPair p : r.getPairs()) {
|
||||
if (!pairs.contains(p)) {
|
||||
allFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allFound) {
|
||||
// 'pairs' contains all the pairs from the row 'r'
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true when one of the rows already contains all the pairs
|
||||
private static boolean isPartialRow(Iterable<ExpandedPair> pairs, Iterable<ExpandedRow> rows) {
|
||||
for (ExpandedRow r : rows) {
|
||||
boolean allFound = true;
|
||||
for (ExpandedPair p : pairs) {
|
||||
boolean found = false;
|
||||
for (ExpandedPair pp : r.getPairs()) {
|
||||
if (p.equals(pp)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
allFound = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allFound) {
|
||||
// the row 'r' contain all the pairs from 'pairs'
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only used for unit testing
|
||||
List<ExpandedRow> getRows() {
|
||||
return this.rows;
|
||||
}
|
||||
|
||||
// Not private for unit testing
|
||||
static Result constructResult(List<ExpandedPair> pairs) throws NotFoundException, FormatException {
|
||||
BitArray binary = BitArrayBuilder.buildBitArray(pairs);
|
||||
|
||||
AbstractExpandedDecoder decoder = AbstractExpandedDecoder.createDecoder(binary);
|
||||
String resultingString = decoder.parseInformation();
|
||||
|
||||
ResultPoint[] firstPoints = pairs.get(0).getFinderPattern().getResultPoints();
|
||||
ResultPoint[] lastPoints = pairs.get(pairs.size() - 1).getFinderPattern().getResultPoints();
|
||||
|
||||
Result result = new Result(
|
||||
resultingString,
|
||||
null,
|
||||
new ResultPoint[]{firstPoints[0], firstPoints[1], lastPoints[0], lastPoints[1]},
|
||||
BarcodeFormat.RSS_EXPANDED
|
||||
);
|
||||
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]e0");
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean checkChecksum() {
|
||||
ExpandedPair firstPair = this.pairs.get(0);
|
||||
DataCharacter checkCharacter = firstPair.getLeftChar();
|
||||
DataCharacter firstCharacter = firstPair.getRightChar();
|
||||
|
||||
if (firstCharacter == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int checksum = firstCharacter.getChecksumPortion();
|
||||
int s = 2;
|
||||
|
||||
for (int i = 1; i < this.pairs.size(); ++i) {
|
||||
ExpandedPair currentPair = this.pairs.get(i);
|
||||
checksum += currentPair.getLeftChar().getChecksumPortion();
|
||||
s++;
|
||||
DataCharacter currentRightChar = currentPair.getRightChar();
|
||||
if (currentRightChar != null) {
|
||||
checksum += currentRightChar.getChecksumPortion();
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
checksum %= 211;
|
||||
|
||||
int checkCharacterValue = 211 * (s - 4) + checksum;
|
||||
|
||||
return checkCharacterValue == checkCharacter.getValue();
|
||||
}
|
||||
|
||||
private static int getNextSecondBar(BitArray row, int initialPos) {
|
||||
int currentPos;
|
||||
if (row.get(initialPos)) {
|
||||
currentPos = row.getNextUnset(initialPos);
|
||||
currentPos = row.getNextSet(currentPos);
|
||||
} else {
|
||||
currentPos = row.getNextSet(initialPos);
|
||||
currentPos = row.getNextUnset(currentPos);
|
||||
}
|
||||
return currentPos;
|
||||
}
|
||||
|
||||
// not private for testing
|
||||
ExpandedPair retrieveNextPair(BitArray row, List<ExpandedPair> previousPairs, int rowNumber)
|
||||
throws NotFoundException {
|
||||
boolean isOddPattern = previousPairs.size() % 2 == 0;
|
||||
if (startFromEven) {
|
||||
isOddPattern = !isOddPattern;
|
||||
}
|
||||
|
||||
FinderPattern pattern;
|
||||
|
||||
boolean keepFinding = true;
|
||||
int forcedOffset = -1;
|
||||
do {
|
||||
this.findNextPair(row, previousPairs, forcedOffset);
|
||||
pattern = parseFoundFinderPattern(row, rowNumber, isOddPattern);
|
||||
if (pattern == null) {
|
||||
forcedOffset = getNextSecondBar(row, this.startEnd[0]);
|
||||
} else {
|
||||
keepFinding = false;
|
||||
}
|
||||
} while (keepFinding);
|
||||
|
||||
// When stacked symbol is split over multiple rows, there's no way to guess if this pair can be last or not.
|
||||
// boolean mayBeLast = checkPairSequence(previousPairs, pattern);
|
||||
|
||||
DataCharacter leftChar = this.decodeDataCharacter(row, pattern, isOddPattern, true);
|
||||
|
||||
if (!previousPairs.isEmpty() && previousPairs.get(previousPairs.size() - 1).mustBeLast()) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
DataCharacter rightChar;
|
||||
try {
|
||||
rightChar = this.decodeDataCharacter(row, pattern, isOddPattern, false);
|
||||
} catch (NotFoundException ignored) {
|
||||
rightChar = null;
|
||||
}
|
||||
return new ExpandedPair(leftChar, rightChar, pattern);
|
||||
}
|
||||
|
||||
private void findNextPair(BitArray row, List<ExpandedPair> previousPairs, int forcedOffset)
|
||||
throws NotFoundException {
|
||||
int[] counters = this.getDecodeFinderCounters();
|
||||
counters[0] = 0;
|
||||
counters[1] = 0;
|
||||
counters[2] = 0;
|
||||
counters[3] = 0;
|
||||
|
||||
int width = row.getSize();
|
||||
|
||||
int rowOffset;
|
||||
if (forcedOffset >= 0) {
|
||||
rowOffset = forcedOffset;
|
||||
} else if (previousPairs.isEmpty()) {
|
||||
rowOffset = 0;
|
||||
} else {
|
||||
ExpandedPair lastPair = previousPairs.get(previousPairs.size() - 1);
|
||||
rowOffset = lastPair.getFinderPattern().getStartEnd()[1];
|
||||
}
|
||||
boolean searchingEvenPair = previousPairs.size() % 2 != 0;
|
||||
if (startFromEven) {
|
||||
searchingEvenPair = !searchingEvenPair;
|
||||
}
|
||||
|
||||
boolean isWhite = false;
|
||||
while (rowOffset < width) {
|
||||
isWhite = !row.get(rowOffset);
|
||||
if (!isWhite) {
|
||||
break;
|
||||
}
|
||||
rowOffset++;
|
||||
}
|
||||
|
||||
int counterPosition = 0;
|
||||
int patternStart = rowOffset;
|
||||
for (int x = rowOffset; x < width; x++) {
|
||||
if (row.get(x) != isWhite) {
|
||||
counters[counterPosition]++;
|
||||
} else {
|
||||
if (counterPosition == 3) {
|
||||
if (searchingEvenPair) {
|
||||
reverseCounters(counters);
|
||||
}
|
||||
|
||||
if (isFinderPattern(counters)) {
|
||||
this.startEnd[0] = patternStart;
|
||||
this.startEnd[1] = x;
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchingEvenPair) {
|
||||
reverseCounters(counters);
|
||||
}
|
||||
|
||||
patternStart += counters[0] + counters[1];
|
||||
counters[0] = counters[2];
|
||||
counters[1] = counters[3];
|
||||
counters[2] = 0;
|
||||
counters[3] = 0;
|
||||
counterPosition--;
|
||||
} else {
|
||||
counterPosition++;
|
||||
}
|
||||
counters[counterPosition] = 1;
|
||||
isWhite = !isWhite;
|
||||
}
|
||||
}
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
private static void reverseCounters(int [] counters) {
|
||||
int length = counters.length;
|
||||
for (int i = 0; i < length / 2; ++i) {
|
||||
int tmp = counters[i];
|
||||
counters[i] = counters[length - i - 1];
|
||||
counters[length - i - 1] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, boolean oddPattern) {
|
||||
// Actually we found elements 2-5.
|
||||
int firstCounter;
|
||||
int start;
|
||||
int end;
|
||||
|
||||
if (oddPattern) {
|
||||
// If pattern number is odd, we need to locate element 1 *before* the current block.
|
||||
|
||||
int firstElementStart = this.startEnd[0] - 1;
|
||||
// Locate element 1
|
||||
while (firstElementStart >= 0 && !row.get(firstElementStart)) {
|
||||
firstElementStart--;
|
||||
}
|
||||
|
||||
firstElementStart++;
|
||||
firstCounter = this.startEnd[0] - firstElementStart;
|
||||
start = firstElementStart;
|
||||
end = this.startEnd[1];
|
||||
|
||||
} else {
|
||||
// If pattern number is even, the pattern is reversed, so we need to locate element 1 *after* the current block.
|
||||
|
||||
start = this.startEnd[0];
|
||||
|
||||
end = row.getNextUnset(this.startEnd[1] + 1);
|
||||
firstCounter = end - this.startEnd[1];
|
||||
}
|
||||
|
||||
// Make 'counters' hold 1-4
|
||||
int [] counters = this.getDecodeFinderCounters();
|
||||
System.arraycopy(counters, 0, counters, 1, counters.length - 1);
|
||||
|
||||
counters[0] = firstCounter;
|
||||
int value;
|
||||
try {
|
||||
value = parseFinderValue(counters, FINDER_PATTERNS);
|
||||
} catch (NotFoundException ignored) {
|
||||
return null;
|
||||
}
|
||||
return new FinderPattern(value, new int[] {start, end}, start, end, rowNumber);
|
||||
}
|
||||
|
||||
DataCharacter decodeDataCharacter(BitArray row,
|
||||
FinderPattern pattern,
|
||||
boolean isOddPattern,
|
||||
boolean leftChar) throws NotFoundException {
|
||||
int[] counters = this.getDataCharacterCounters();
|
||||
Arrays.fill(counters, 0);
|
||||
|
||||
if (leftChar) {
|
||||
recordPatternInReverse(row, pattern.getStartEnd()[0], counters);
|
||||
} else {
|
||||
recordPattern(row, pattern.getStartEnd()[1], counters);
|
||||
// reverse it
|
||||
for (int i = 0, j = counters.length - 1; i < j; i++, j--) {
|
||||
int temp = counters[i];
|
||||
counters[i] = counters[j];
|
||||
counters[j] = temp;
|
||||
}
|
||||
} //counters[] has the pixels of the module
|
||||
|
||||
int numModules = 17; //left and right data characters have all the same length
|
||||
float elementWidth = MathUtils.sum(counters) / (float) numModules;
|
||||
|
||||
// Sanity check: element width for pattern and the character should match
|
||||
float expectedElementWidth = (pattern.getStartEnd()[1] - pattern.getStartEnd()[0]) / 15.0f;
|
||||
if (Math.abs(elementWidth - expectedElementWidth) / expectedElementWidth > 0.3f) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
int[] oddCounts = this.getOddCounts();
|
||||
int[] evenCounts = this.getEvenCounts();
|
||||
float[] oddRoundingErrors = this.getOddRoundingErrors();
|
||||
float[] evenRoundingErrors = this.getEvenRoundingErrors();
|
||||
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
float value = 1.0f * counters[i] / elementWidth;
|
||||
int count = (int) (value + 0.5f); // Round
|
||||
if (count < 1) {
|
||||
if (value < 0.3f) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
count = 1;
|
||||
} else if (count > 8) {
|
||||
if (value > 8.7f) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
count = 8;
|
||||
}
|
||||
int offset = i / 2;
|
||||
if ((i & 0x01) == 0) {
|
||||
oddCounts[offset] = count;
|
||||
oddRoundingErrors[offset] = value - count;
|
||||
} else {
|
||||
evenCounts[offset] = count;
|
||||
evenRoundingErrors[offset] = value - count;
|
||||
}
|
||||
}
|
||||
|
||||
adjustOddEvenCounts(numModules);
|
||||
|
||||
int weightRowNumber = 4 * pattern.getValue() + (isOddPattern ? 0 : 2) + (leftChar ? 0 : 1) - 1;
|
||||
|
||||
int oddSum = 0;
|
||||
int oddChecksumPortion = 0;
|
||||
for (int i = oddCounts.length - 1; i >= 0; i--) {
|
||||
if (isNotA1left(pattern, isOddPattern, leftChar)) {
|
||||
int weight = WEIGHTS[weightRowNumber][2 * i];
|
||||
oddChecksumPortion += oddCounts[i] * weight;
|
||||
}
|
||||
oddSum += oddCounts[i];
|
||||
}
|
||||
int evenChecksumPortion = 0;
|
||||
for (int i = evenCounts.length - 1; i >= 0; i--) {
|
||||
if (isNotA1left(pattern, isOddPattern, leftChar)) {
|
||||
int weight = WEIGHTS[weightRowNumber][2 * i + 1];
|
||||
evenChecksumPortion += evenCounts[i] * weight;
|
||||
}
|
||||
}
|
||||
int checksumPortion = oddChecksumPortion + evenChecksumPortion;
|
||||
|
||||
if ((oddSum & 0x01) != 0 || oddSum > 13 || oddSum < 4) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
int group = (13 - oddSum) / 2;
|
||||
int oddWidest = SYMBOL_WIDEST[group];
|
||||
int evenWidest = 9 - oddWidest;
|
||||
int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true);
|
||||
int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false);
|
||||
int tEven = EVEN_TOTAL_SUBSET[group];
|
||||
int gSum = GSUM[group];
|
||||
int value = vOdd * tEven + vEven + gSum;
|
||||
|
||||
return new DataCharacter(value, checksumPortion);
|
||||
}
|
||||
|
||||
private static boolean isNotA1left(FinderPattern pattern, boolean isOddPattern, boolean leftChar) {
|
||||
// A1: pattern.getValue is 0 (A), and it's an oddPattern, and it is a left char
|
||||
return !(pattern.getValue() == 0 && isOddPattern && leftChar);
|
||||
}
|
||||
|
||||
private void adjustOddEvenCounts(int numModules) throws NotFoundException {
|
||||
|
||||
int oddSum = MathUtils.sum(this.getOddCounts());
|
||||
int evenSum = MathUtils.sum(this.getEvenCounts());
|
||||
|
||||
boolean incrementOdd = false;
|
||||
boolean decrementOdd = false;
|
||||
|
||||
if (oddSum > 13) {
|
||||
decrementOdd = true;
|
||||
} else if (oddSum < 4) {
|
||||
incrementOdd = true;
|
||||
}
|
||||
boolean incrementEven = false;
|
||||
boolean decrementEven = false;
|
||||
if (evenSum > 13) {
|
||||
decrementEven = true;
|
||||
} else if (evenSum < 4) {
|
||||
incrementEven = true;
|
||||
}
|
||||
|
||||
int mismatch = oddSum + evenSum - numModules;
|
||||
boolean oddParityBad = (oddSum & 0x01) == 1;
|
||||
boolean evenParityBad = (evenSum & 0x01) == 0;
|
||||
switch (mismatch) {
|
||||
case 1:
|
||||
if (oddParityBad) {
|
||||
if (evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
decrementOdd = true;
|
||||
} else {
|
||||
if (!evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
decrementEven = true;
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
if (oddParityBad) {
|
||||
if (evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
incrementOdd = true;
|
||||
} else {
|
||||
if (!evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
incrementEven = true;
|
||||
}
|
||||
break;
|
||||
case 0:
|
||||
if (oddParityBad) {
|
||||
if (!evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
// Both bad
|
||||
if (oddSum < evenSum) {
|
||||
incrementOdd = true;
|
||||
decrementEven = true;
|
||||
} else {
|
||||
decrementOdd = true;
|
||||
incrementEven = true;
|
||||
}
|
||||
} else {
|
||||
if (evenParityBad) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
// Nothing to do!
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
if (incrementOdd) {
|
||||
if (decrementOdd) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
increment(this.getOddCounts(), this.getOddRoundingErrors());
|
||||
}
|
||||
if (decrementOdd) {
|
||||
decrement(this.getOddCounts(), this.getOddRoundingErrors());
|
||||
}
|
||||
if (incrementEven) {
|
||||
if (decrementEven) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
increment(this.getEvenCounts(), this.getOddRoundingErrors());
|
||||
}
|
||||
if (decrementEven) {
|
||||
decrement(this.getEvenCounts(), this.getEvenRoundingErrors());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
*/
|
||||
final class AI013103decoder extends AI013x0xDecoder {
|
||||
|
||||
AI013103decoder(BitArray information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addWeightCode(StringBuilder buf, int weight) {
|
||||
buf.append("(3103)");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int checkWeight(int weight) {
|
||||
return weight;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
*/
|
||||
final class AI01320xDecoder extends AI013x0xDecoder {
|
||||
|
||||
AI01320xDecoder(BitArray information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addWeightCode(StringBuilder buf, int weight) {
|
||||
if (weight < 10000) {
|
||||
buf.append("(3202)");
|
||||
} else {
|
||||
buf.append("(3203)");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int checkWeight(int weight) {
|
||||
if (weight < 10000) {
|
||||
return weight;
|
||||
}
|
||||
return weight - 10000;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
*/
|
||||
final class AI01392xDecoder extends AI01decoder {
|
||||
|
||||
private static final int HEADER_SIZE = 5 + 1 + 2;
|
||||
private static final int LAST_DIGIT_SIZE = 2;
|
||||
|
||||
AI01392xDecoder(BitArray information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parseInformation() throws NotFoundException, FormatException {
|
||||
if (this.getInformation().getSize() < HEADER_SIZE + GTIN_SIZE) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
encodeCompressedGtin(buf, HEADER_SIZE);
|
||||
|
||||
int lastAIdigit =
|
||||
this.getGeneralDecoder().extractNumericValueFromBitArray(HEADER_SIZE + GTIN_SIZE, LAST_DIGIT_SIZE);
|
||||
buf.append("(392");
|
||||
buf.append(lastAIdigit);
|
||||
buf.append(')');
|
||||
|
||||
DecodedInformation decodedInformation =
|
||||
this.getGeneralDecoder().decodeGeneralPurposeField(HEADER_SIZE + GTIN_SIZE + LAST_DIGIT_SIZE, null);
|
||||
buf.append(decodedInformation.getNewString());
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
*/
|
||||
final class AI01393xDecoder extends AI01decoder {
|
||||
|
||||
private static final int HEADER_SIZE = 5 + 1 + 2;
|
||||
private static final int LAST_DIGIT_SIZE = 2;
|
||||
private static final int FIRST_THREE_DIGITS_SIZE = 10;
|
||||
|
||||
AI01393xDecoder(BitArray information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parseInformation() throws NotFoundException, FormatException {
|
||||
if (this.getInformation().getSize() < HEADER_SIZE + GTIN_SIZE) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
encodeCompressedGtin(buf, HEADER_SIZE);
|
||||
|
||||
int lastAIdigit =
|
||||
this.getGeneralDecoder().extractNumericValueFromBitArray(HEADER_SIZE + GTIN_SIZE, LAST_DIGIT_SIZE);
|
||||
|
||||
buf.append("(393");
|
||||
buf.append(lastAIdigit);
|
||||
buf.append(')');
|
||||
|
||||
int firstThreeDigits = this.getGeneralDecoder().extractNumericValueFromBitArray(
|
||||
HEADER_SIZE + GTIN_SIZE + LAST_DIGIT_SIZE, FIRST_THREE_DIGITS_SIZE);
|
||||
if (firstThreeDigits / 100 == 0) {
|
||||
buf.append('0');
|
||||
}
|
||||
if (firstThreeDigits / 10 == 0) {
|
||||
buf.append('0');
|
||||
}
|
||||
buf.append(firstThreeDigits);
|
||||
|
||||
DecodedInformation generalInformation = this.getGeneralDecoder().decodeGeneralPurposeField(
|
||||
HEADER_SIZE + GTIN_SIZE + LAST_DIGIT_SIZE + FIRST_THREE_DIGITS_SIZE, null);
|
||||
buf.append(generalInformation.getNewString());
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,108 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class AI013x0x1xDecoder extends AI01weightDecoder {
|
||||
|
||||
private static final int HEADER_SIZE = 7 + 1;
|
||||
private static final int WEIGHT_SIZE = 20;
|
||||
private static final int DATE_SIZE = 16;
|
||||
|
||||
private final String dateCode;
|
||||
private final String firstAIdigits;
|
||||
|
||||
AI013x0x1xDecoder(BitArray information, String firstAIdigits, String dateCode) {
|
||||
super(information);
|
||||
this.dateCode = dateCode;
|
||||
this.firstAIdigits = firstAIdigits;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parseInformation() throws NotFoundException {
|
||||
if (this.getInformation().getSize() != HEADER_SIZE + GTIN_SIZE + WEIGHT_SIZE + DATE_SIZE) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
encodeCompressedGtin(buf, HEADER_SIZE);
|
||||
encodeCompressedWeight(buf, HEADER_SIZE + GTIN_SIZE, WEIGHT_SIZE);
|
||||
encodeCompressedDate(buf, HEADER_SIZE + GTIN_SIZE + WEIGHT_SIZE);
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
private void encodeCompressedDate(StringBuilder buf, int currentPos) {
|
||||
int numericDate = this.getGeneralDecoder().extractNumericValueFromBitArray(currentPos, DATE_SIZE);
|
||||
if (numericDate == 38400) {
|
||||
return;
|
||||
}
|
||||
|
||||
buf.append('(');
|
||||
buf.append(this.dateCode);
|
||||
buf.append(')');
|
||||
|
||||
int day = numericDate % 32;
|
||||
numericDate /= 32;
|
||||
int month = numericDate % 12 + 1;
|
||||
numericDate /= 12;
|
||||
int year = numericDate;
|
||||
|
||||
if (year / 10 == 0) {
|
||||
buf.append('0');
|
||||
}
|
||||
buf.append(year);
|
||||
if (month / 10 == 0) {
|
||||
buf.append('0');
|
||||
}
|
||||
buf.append(month);
|
||||
if (day / 10 == 0) {
|
||||
buf.append('0');
|
||||
}
|
||||
buf.append(day);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addWeightCode(StringBuilder buf, int weight) {
|
||||
buf.append('(');
|
||||
buf.append(this.firstAIdigits);
|
||||
buf.append(weight / 100000);
|
||||
buf.append(')');
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int checkWeight(int weight) {
|
||||
return weight % 100000;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
*/
|
||||
abstract class AI013x0xDecoder extends AI01weightDecoder {
|
||||
|
||||
private static final int HEADER_SIZE = 4 + 1;
|
||||
private static final int WEIGHT_SIZE = 15;
|
||||
|
||||
AI013x0xDecoder(BitArray information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parseInformation() throws NotFoundException {
|
||||
if (this.getInformation().getSize() != HEADER_SIZE + GTIN_SIZE + WEIGHT_SIZE) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
StringBuilder buf = new StringBuilder();
|
||||
|
||||
encodeCompressedGtin(buf, HEADER_SIZE);
|
||||
encodeCompressedWeight(buf, HEADER_SIZE + GTIN_SIZE, WEIGHT_SIZE);
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class AI01AndOtherAIs extends AI01decoder {
|
||||
|
||||
private static final int HEADER_SIZE = 1 + 1 + 2; //first bit encodes the linkage flag,
|
||||
//the second one is the encodation method, and the other two are for the variable length
|
||||
AI01AndOtherAIs(BitArray information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parseInformation() throws NotFoundException, FormatException {
|
||||
StringBuilder buff = new StringBuilder();
|
||||
|
||||
buff.append("(01)");
|
||||
int initialGtinPosition = buff.length();
|
||||
int firstGtinDigit = this.getGeneralDecoder().extractNumericValueFromBitArray(HEADER_SIZE, 4);
|
||||
buff.append(firstGtinDigit);
|
||||
|
||||
this.encodeCompressedGtinWithoutAI(buff, HEADER_SIZE + 4, initialGtinPosition);
|
||||
|
||||
return this.getGeneralDecoder().decodeAllCodes(buff, HEADER_SIZE + 44);
|
||||
}
|
||||
}
|
||||
@@ -1,81 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
abstract class AI01decoder extends AbstractExpandedDecoder {
|
||||
|
||||
static final int GTIN_SIZE = 40;
|
||||
|
||||
AI01decoder(BitArray information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
final void encodeCompressedGtin(StringBuilder buf, int currentPos) {
|
||||
buf.append("(01)");
|
||||
int initialPosition = buf.length();
|
||||
buf.append('9');
|
||||
|
||||
encodeCompressedGtinWithoutAI(buf, currentPos, initialPosition);
|
||||
}
|
||||
|
||||
final void encodeCompressedGtinWithoutAI(StringBuilder buf, int currentPos, int initialBufferPosition) {
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
int currentBlock = this.getGeneralDecoder().extractNumericValueFromBitArray(currentPos + 10 * i, 10);
|
||||
if (currentBlock / 100 == 0) {
|
||||
buf.append('0');
|
||||
}
|
||||
if (currentBlock / 10 == 0) {
|
||||
buf.append('0');
|
||||
}
|
||||
buf.append(currentBlock);
|
||||
}
|
||||
|
||||
appendCheckDigit(buf, initialBufferPosition);
|
||||
}
|
||||
|
||||
private static void appendCheckDigit(StringBuilder buf, int currentPos) {
|
||||
int checkDigit = 0;
|
||||
for (int i = 0; i < 13; i++) {
|
||||
int digit = buf.charAt(i + currentPos) - '0';
|
||||
checkDigit += (i & 0x01) == 0 ? 3 * digit : digit;
|
||||
}
|
||||
|
||||
checkDigit = 10 - (checkDigit % 10);
|
||||
if (checkDigit == 10) {
|
||||
checkDigit = 0;
|
||||
}
|
||||
|
||||
buf.append(checkDigit);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,60 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
*/
|
||||
abstract class AI01weightDecoder extends AI01decoder {
|
||||
|
||||
AI01weightDecoder(BitArray information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
final void encodeCompressedWeight(StringBuilder buf, int currentPos, int weightSize) {
|
||||
int originalWeightNumeric = this.getGeneralDecoder().extractNumericValueFromBitArray(currentPos, weightSize);
|
||||
addWeightCode(buf, originalWeightNumeric);
|
||||
|
||||
int weightNumeric = checkWeight(originalWeightNumeric);
|
||||
|
||||
int currentDivisor = 100000;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
if (weightNumeric / currentDivisor == 0) {
|
||||
buf.append('0');
|
||||
}
|
||||
currentDivisor /= 10;
|
||||
}
|
||||
buf.append(weightNumeric);
|
||||
}
|
||||
|
||||
protected abstract void addWeightCode(StringBuilder buf, int weight);
|
||||
|
||||
protected abstract int checkWeight(int weight);
|
||||
|
||||
}
|
||||
@@ -1,93 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
public abstract class AbstractExpandedDecoder {
|
||||
|
||||
private final BitArray information;
|
||||
private final GeneralAppIdDecoder generalDecoder;
|
||||
|
||||
AbstractExpandedDecoder(BitArray information) {
|
||||
this.information = information;
|
||||
this.generalDecoder = new GeneralAppIdDecoder(information);
|
||||
}
|
||||
|
||||
protected final BitArray getInformation() {
|
||||
return information;
|
||||
}
|
||||
|
||||
protected final GeneralAppIdDecoder getGeneralDecoder() {
|
||||
return generalDecoder;
|
||||
}
|
||||
|
||||
public abstract String parseInformation() throws NotFoundException, FormatException;
|
||||
|
||||
public static AbstractExpandedDecoder createDecoder(BitArray information) {
|
||||
if (information.get(1)) {
|
||||
return new AI01AndOtherAIs(information);
|
||||
}
|
||||
if (!information.get(2)) {
|
||||
return new AnyAIDecoder(information);
|
||||
}
|
||||
|
||||
int fourBitEncodationMethod = GeneralAppIdDecoder.extractNumericValueFromBitArray(information, 1, 4);
|
||||
|
||||
switch (fourBitEncodationMethod) {
|
||||
case 4: return new AI013103decoder(information);
|
||||
case 5: return new AI01320xDecoder(information);
|
||||
}
|
||||
|
||||
int fiveBitEncodationMethod = GeneralAppIdDecoder.extractNumericValueFromBitArray(information, 1, 5);
|
||||
switch (fiveBitEncodationMethod) {
|
||||
case 12: return new AI01392xDecoder(information);
|
||||
case 13: return new AI01393xDecoder(information);
|
||||
}
|
||||
|
||||
int sevenBitEncodationMethod = GeneralAppIdDecoder.extractNumericValueFromBitArray(information, 1, 7);
|
||||
switch (sevenBitEncodationMethod) {
|
||||
case 56: return new AI013x0x1xDecoder(information, "310", "11");
|
||||
case 57: return new AI013x0x1xDecoder(information, "320", "11");
|
||||
case 58: return new AI013x0x1xDecoder(information, "310", "13");
|
||||
case 59: return new AI013x0x1xDecoder(information, "320", "13");
|
||||
case 60: return new AI013x0x1xDecoder(information, "310", "15");
|
||||
case 61: return new AI013x0x1xDecoder(information, "320", "15");
|
||||
case 62: return new AI013x0x1xDecoder(information, "310", "17");
|
||||
case 63: return new AI013x0x1xDecoder(information, "320", "17");
|
||||
}
|
||||
|
||||
throw new IllegalStateException("unknown decoder: " + information);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,50 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class AnyAIDecoder extends AbstractExpandedDecoder {
|
||||
|
||||
private static final int HEADER_SIZE = 2 + 1 + 2;
|
||||
|
||||
AnyAIDecoder(BitArray information) {
|
||||
super(information);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parseInformation() throws NotFoundException, FormatException {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
return this.getGeneralDecoder().decodeAllCodes(buf, HEADER_SIZE);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class BlockParsedResult {
|
||||
|
||||
private final DecodedInformation decodedInformation;
|
||||
private final boolean finished;
|
||||
|
||||
BlockParsedResult() {
|
||||
this(null, false);
|
||||
}
|
||||
|
||||
BlockParsedResult(DecodedInformation information, boolean finished) {
|
||||
this.finished = finished;
|
||||
this.decodedInformation = information;
|
||||
}
|
||||
|
||||
DecodedInformation getDecodedInformation() {
|
||||
return this.decodedInformation;
|
||||
}
|
||||
|
||||
boolean isFinished() {
|
||||
return this.finished;
|
||||
}
|
||||
}
|
||||
@@ -1,83 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
*/
|
||||
final class CurrentParsingState {
|
||||
|
||||
private int position;
|
||||
private State encoding;
|
||||
|
||||
private enum State {
|
||||
NUMERIC,
|
||||
ALPHA,
|
||||
ISO_IEC_646
|
||||
}
|
||||
|
||||
CurrentParsingState() {
|
||||
this.position = 0;
|
||||
this.encoding = State.NUMERIC;
|
||||
}
|
||||
|
||||
int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
void setPosition(int position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
void incrementPosition(int delta) {
|
||||
position += delta;
|
||||
}
|
||||
|
||||
boolean isAlpha() {
|
||||
return this.encoding == State.ALPHA;
|
||||
}
|
||||
|
||||
boolean isNumeric() {
|
||||
return this.encoding == State.NUMERIC;
|
||||
}
|
||||
|
||||
boolean isIsoIec646() {
|
||||
return this.encoding == State.ISO_IEC_646;
|
||||
}
|
||||
|
||||
void setNumeric() {
|
||||
this.encoding = State.NUMERIC;
|
||||
}
|
||||
|
||||
void setAlpha() {
|
||||
this.encoding = State.ALPHA;
|
||||
}
|
||||
|
||||
void setIsoIec646() {
|
||||
this.encoding = State.ISO_IEC_646;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class DecodedChar extends DecodedObject {
|
||||
|
||||
private final char value;
|
||||
|
||||
static final char FNC1 = '$'; // It's not in Alphanumeric neither in ISO/IEC 646 charset
|
||||
|
||||
DecodedChar(int newPosition, char value) {
|
||||
super(newPosition);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
char getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
boolean isFNC1() {
|
||||
return this.value == FNC1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,64 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class DecodedInformation extends DecodedObject {
|
||||
|
||||
private final String newString;
|
||||
private final int remainingValue;
|
||||
private final boolean remaining;
|
||||
|
||||
DecodedInformation(int newPosition, String newString) {
|
||||
super(newPosition);
|
||||
this.newString = newString;
|
||||
this.remaining = false;
|
||||
this.remainingValue = 0;
|
||||
}
|
||||
|
||||
DecodedInformation(int newPosition, String newString, int remainingValue) {
|
||||
super(newPosition);
|
||||
this.remaining = true;
|
||||
this.remainingValue = remainingValue;
|
||||
this.newString = newString;
|
||||
}
|
||||
|
||||
String getNewString() {
|
||||
return this.newString;
|
||||
}
|
||||
|
||||
boolean isRemaining() {
|
||||
return this.remaining;
|
||||
}
|
||||
|
||||
int getRemainingValue() {
|
||||
return this.remainingValue;
|
||||
}
|
||||
}
|
||||
@@ -1,73 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.FormatException;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class DecodedNumeric extends DecodedObject {
|
||||
|
||||
private final int firstDigit;
|
||||
private final int secondDigit;
|
||||
|
||||
static final int FNC1 = 10;
|
||||
|
||||
DecodedNumeric(int newPosition, int firstDigit, int secondDigit) throws FormatException {
|
||||
super(newPosition);
|
||||
|
||||
if (firstDigit < 0 || firstDigit > 10 || secondDigit < 0 || secondDigit > 10) {
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
|
||||
this.firstDigit = firstDigit;
|
||||
this.secondDigit = secondDigit;
|
||||
}
|
||||
|
||||
int getFirstDigit() {
|
||||
return this.firstDigit;
|
||||
}
|
||||
|
||||
int getSecondDigit() {
|
||||
return this.secondDigit;
|
||||
}
|
||||
|
||||
int getValue() {
|
||||
return this.firstDigit * 10 + this.secondDigit;
|
||||
}
|
||||
|
||||
boolean isFirstDigitFNC1() {
|
||||
return this.firstDigit == FNC1;
|
||||
}
|
||||
|
||||
boolean isSecondDigitFNC1() {
|
||||
return this.secondDigit == FNC1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
*/
|
||||
abstract class DecodedObject {
|
||||
|
||||
private final int newPosition;
|
||||
|
||||
DecodedObject(int newPosition) {
|
||||
this.newPosition = newPosition;
|
||||
}
|
||||
|
||||
final int getNewPosition() {
|
||||
return this.newPosition;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,239 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.NotFoundException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class FieldParser {
|
||||
|
||||
private static final Map<String,DataLength> TWO_DIGIT_DATA_LENGTH = new HashMap<>();
|
||||
static {
|
||||
TWO_DIGIT_DATA_LENGTH.put("00", DataLength.fixed(18));
|
||||
TWO_DIGIT_DATA_LENGTH.put("01", DataLength.fixed(14));
|
||||
TWO_DIGIT_DATA_LENGTH.put("02", DataLength.fixed(14));
|
||||
TWO_DIGIT_DATA_LENGTH.put("10", DataLength.variable(20));
|
||||
TWO_DIGIT_DATA_LENGTH.put("11", DataLength.fixed(6));
|
||||
TWO_DIGIT_DATA_LENGTH.put("12", DataLength.fixed(6));
|
||||
TWO_DIGIT_DATA_LENGTH.put("13", DataLength.fixed(6));
|
||||
TWO_DIGIT_DATA_LENGTH.put("15", DataLength.fixed(6));
|
||||
TWO_DIGIT_DATA_LENGTH.put("17", DataLength.fixed(6));
|
||||
TWO_DIGIT_DATA_LENGTH.put("20", DataLength.fixed(2));
|
||||
TWO_DIGIT_DATA_LENGTH.put("21", DataLength.variable(20));
|
||||
TWO_DIGIT_DATA_LENGTH.put("22", DataLength.variable(29));
|
||||
TWO_DIGIT_DATA_LENGTH.put("30", DataLength.variable(8));
|
||||
TWO_DIGIT_DATA_LENGTH.put("37", DataLength.variable(8));
|
||||
//internal company codes
|
||||
for (int i = 90; i <= 99; i++) {
|
||||
TWO_DIGIT_DATA_LENGTH.put(String.valueOf(i), DataLength.variable(30));
|
||||
}
|
||||
}
|
||||
|
||||
private static final Map<String,DataLength> THREE_DIGIT_DATA_LENGTH = new HashMap<>();
|
||||
static {
|
||||
THREE_DIGIT_DATA_LENGTH.put("240", DataLength.variable(30));
|
||||
THREE_DIGIT_DATA_LENGTH.put("241", DataLength.variable(30));
|
||||
THREE_DIGIT_DATA_LENGTH.put("242", DataLength.variable(6));
|
||||
THREE_DIGIT_DATA_LENGTH.put("250", DataLength.variable(30));
|
||||
THREE_DIGIT_DATA_LENGTH.put("251", DataLength.variable(30));
|
||||
THREE_DIGIT_DATA_LENGTH.put("253", DataLength.variable(17));
|
||||
THREE_DIGIT_DATA_LENGTH.put("254", DataLength.variable(20));
|
||||
THREE_DIGIT_DATA_LENGTH.put("400", DataLength.variable(30));
|
||||
THREE_DIGIT_DATA_LENGTH.put("401", DataLength.variable(30));
|
||||
THREE_DIGIT_DATA_LENGTH.put("402", DataLength.fixed(17));
|
||||
THREE_DIGIT_DATA_LENGTH.put("403", DataLength.variable(30));
|
||||
THREE_DIGIT_DATA_LENGTH.put("410", DataLength.fixed(13));
|
||||
THREE_DIGIT_DATA_LENGTH.put("411", DataLength.fixed(13));
|
||||
THREE_DIGIT_DATA_LENGTH.put("412", DataLength.fixed(13));
|
||||
THREE_DIGIT_DATA_LENGTH.put("413", DataLength.fixed(13));
|
||||
THREE_DIGIT_DATA_LENGTH.put("414", DataLength.fixed(13));
|
||||
THREE_DIGIT_DATA_LENGTH.put("420", DataLength.variable(20));
|
||||
THREE_DIGIT_DATA_LENGTH.put("421", DataLength.variable(15));
|
||||
THREE_DIGIT_DATA_LENGTH.put("422", DataLength.fixed(3));
|
||||
THREE_DIGIT_DATA_LENGTH.put("423", DataLength.variable(15));
|
||||
THREE_DIGIT_DATA_LENGTH.put("424", DataLength.fixed(3));
|
||||
THREE_DIGIT_DATA_LENGTH.put("425", DataLength.fixed(3));
|
||||
THREE_DIGIT_DATA_LENGTH.put("426", DataLength.fixed(3));
|
||||
}
|
||||
|
||||
private static final Map<String,DataLength> THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH = new HashMap<>();
|
||||
static {
|
||||
for (int i = 310; i <= 316; i++) {
|
||||
THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.put(String.valueOf(i), DataLength.fixed(6));
|
||||
}
|
||||
for (int i = 320; i <= 336; i++) {
|
||||
THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.put(String.valueOf(i), DataLength.fixed(6));
|
||||
}
|
||||
for (int i = 340; i <= 357; i++) {
|
||||
THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.put(String.valueOf(i), DataLength.fixed(6));
|
||||
}
|
||||
for (int i = 360; i <= 369; i++) {
|
||||
THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.put(String.valueOf(i), DataLength.fixed(6));
|
||||
}
|
||||
THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.put("390", DataLength.variable(15));
|
||||
THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.put("391", DataLength.variable(18));
|
||||
THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.put("392", DataLength.variable(15));
|
||||
THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.put("393", DataLength.variable(18));
|
||||
THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.put("703", DataLength.variable(30));
|
||||
}
|
||||
|
||||
private static final Map<String,DataLength> FOUR_DIGIT_DATA_LENGTH = new HashMap<>();
|
||||
static {
|
||||
FOUR_DIGIT_DATA_LENGTH.put("7001", DataLength.fixed(13));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("7002", DataLength.variable(30));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("7003", DataLength.fixed(10));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8001", DataLength.fixed(14));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8002", DataLength.variable(20));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8003", DataLength.variable(30));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8004", DataLength.variable(30));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8005", DataLength.fixed(6));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8006", DataLength.fixed(18));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8007", DataLength.variable(30));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8008", DataLength.variable(12));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8018", DataLength.fixed(18));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8020", DataLength.variable(25));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8100", DataLength.fixed(6));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8101", DataLength.fixed(10));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8102", DataLength.fixed(2));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8110", DataLength.variable(70));
|
||||
FOUR_DIGIT_DATA_LENGTH.put("8200", DataLength.variable(70));
|
||||
}
|
||||
|
||||
private FieldParser() {
|
||||
}
|
||||
|
||||
static String parseFieldsInGeneralPurpose(String rawInformation) throws NotFoundException {
|
||||
if (rawInformation.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Processing 2-digit AIs
|
||||
|
||||
if (rawInformation.length() < 2) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
DataLength twoDigitDataLength = TWO_DIGIT_DATA_LENGTH.get(rawInformation.substring(0, 2));
|
||||
if (twoDigitDataLength != null) {
|
||||
if (twoDigitDataLength.variable) {
|
||||
return processVariableAI(2, twoDigitDataLength.length, rawInformation);
|
||||
}
|
||||
return processFixedAI(2, twoDigitDataLength.length, rawInformation);
|
||||
}
|
||||
|
||||
if (rawInformation.length() < 3) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
String firstThreeDigits = rawInformation.substring(0, 3);
|
||||
DataLength threeDigitDataLength = THREE_DIGIT_DATA_LENGTH.get(firstThreeDigits);
|
||||
if (threeDigitDataLength != null) {
|
||||
if (threeDigitDataLength.variable) {
|
||||
return processVariableAI(3, threeDigitDataLength.length, rawInformation);
|
||||
}
|
||||
return processFixedAI(3, threeDigitDataLength.length, rawInformation);
|
||||
}
|
||||
|
||||
if (rawInformation.length() < 4) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
DataLength threeDigitPlusDigitDataLength = THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH.get(firstThreeDigits);
|
||||
if (threeDigitPlusDigitDataLength != null) {
|
||||
if (threeDigitPlusDigitDataLength.variable) {
|
||||
return processVariableAI(4, threeDigitPlusDigitDataLength.length, rawInformation);
|
||||
}
|
||||
return processFixedAI(4, threeDigitPlusDigitDataLength.length, rawInformation);
|
||||
}
|
||||
|
||||
DataLength firstFourDigitLength = FOUR_DIGIT_DATA_LENGTH.get(rawInformation.substring(0, 4));
|
||||
if (firstFourDigitLength != null) {
|
||||
if (firstFourDigitLength.variable) {
|
||||
return processVariableAI(4, firstFourDigitLength.length, rawInformation);
|
||||
}
|
||||
return processFixedAI(4, firstFourDigitLength.length, rawInformation);
|
||||
}
|
||||
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
private static String processFixedAI(int aiSize, int fieldSize, String rawInformation) throws NotFoundException {
|
||||
if (rawInformation.length() < aiSize) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
String ai = rawInformation.substring(0, aiSize);
|
||||
|
||||
if (rawInformation.length() < aiSize + fieldSize) {
|
||||
throw NotFoundException.getNotFoundInstance();
|
||||
}
|
||||
|
||||
String field = rawInformation.substring(aiSize, aiSize + fieldSize);
|
||||
String remaining = rawInformation.substring(aiSize + fieldSize);
|
||||
String result = '(' + ai + ')' + field;
|
||||
String parsedAI = parseFieldsInGeneralPurpose(remaining);
|
||||
return parsedAI == null ? result : result + parsedAI;
|
||||
}
|
||||
|
||||
private static String processVariableAI(int aiSize, int variableFieldSize, String rawInformation)
|
||||
throws NotFoundException {
|
||||
String ai = rawInformation.substring(0, aiSize);
|
||||
int maxSize = Math.min(rawInformation.length(), aiSize + variableFieldSize);
|
||||
String field = rawInformation.substring(aiSize, maxSize);
|
||||
String remaining = rawInformation.substring(maxSize);
|
||||
String result = '(' + ai + ')' + field;
|
||||
String parsedAI = parseFieldsInGeneralPurpose(remaining);
|
||||
return parsedAI == null ? result : result + parsedAI;
|
||||
}
|
||||
|
||||
private static final class DataLength {
|
||||
|
||||
final boolean variable;
|
||||
final int length;
|
||||
|
||||
private DataLength(boolean variable, int length) {
|
||||
this.variable = variable;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
static DataLength fixed(int length) {
|
||||
return new DataLength(false, length);
|
||||
}
|
||||
|
||||
static DataLength variable(int length) {
|
||||
return new DataLength(true, length);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,470 +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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* These authors would like to acknowledge the Spanish Ministry of Industry,
|
||||
* Tourism and Trade, for the support in the project TSI020301-2008-2
|
||||
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
|
||||
* Mobile Dynamic Environments", led by Treelogic
|
||||
* ( http://www.treelogic.com/ ):
|
||||
*
|
||||
* http://www.piramidepse.com/
|
||||
*/
|
||||
|
||||
package com.google.zxing.oned.rss.expanded.decoders;
|
||||
|
||||
import com.google.zxing.FormatException;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.common.BitArray;
|
||||
|
||||
/**
|
||||
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
|
||||
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
|
||||
*/
|
||||
final class GeneralAppIdDecoder {
|
||||
|
||||
private final BitArray information;
|
||||
private final CurrentParsingState current = new CurrentParsingState();
|
||||
private final StringBuilder buffer = new StringBuilder();
|
||||
|
||||
GeneralAppIdDecoder(BitArray information) {
|
||||
this.information = information;
|
||||
}
|
||||
|
||||
String decodeAllCodes(StringBuilder buff, int initialPosition) throws NotFoundException, FormatException {
|
||||
int currentPosition = initialPosition;
|
||||
String remaining = null;
|
||||
do {
|
||||
DecodedInformation info = this.decodeGeneralPurposeField(currentPosition, remaining);
|
||||
String parsedFields = FieldParser.parseFieldsInGeneralPurpose(info.getNewString());
|
||||
if (parsedFields != null) {
|
||||
buff.append(parsedFields);
|
||||
}
|
||||
if (info.isRemaining()) {
|
||||
remaining = String.valueOf(info.getRemainingValue());
|
||||
} else {
|
||||
remaining = null;
|
||||
}
|
||||
|
||||
if (currentPosition == info.getNewPosition()) { // No step forward!
|
||||
break;
|
||||
}
|
||||
currentPosition = info.getNewPosition();
|
||||
} while (true);
|
||||
|
||||
return buff.toString();
|
||||
}
|
||||
|
||||
private boolean isStillNumeric(int pos) {
|
||||
// It's numeric if it still has 7 positions
|
||||
// and one of the first 4 bits is "1".
|
||||
if (pos + 7 > this.information.getSize()) {
|
||||
return pos + 4 <= this.information.getSize();
|
||||
}
|
||||
|
||||
for (int i = pos; i < pos + 3; ++i) {
|
||||
if (this.information.get(i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return this.information.get(pos + 3);
|
||||
}
|
||||
|
||||
private DecodedNumeric decodeNumeric(int pos) throws FormatException {
|
||||
if (pos + 7 > this.information.getSize()) {
|
||||
int numeric = extractNumericValueFromBitArray(pos, 4);
|
||||
if (numeric == 0) {
|
||||
return new DecodedNumeric(this.information.getSize(), DecodedNumeric.FNC1, DecodedNumeric.FNC1);
|
||||
}
|
||||
return new DecodedNumeric(this.information.getSize(), numeric - 1, DecodedNumeric.FNC1);
|
||||
}
|
||||
int numeric = extractNumericValueFromBitArray(pos, 7);
|
||||
|
||||
int digit1 = (numeric - 8) / 11;
|
||||
int digit2 = (numeric - 8) % 11;
|
||||
|
||||
return new DecodedNumeric(pos + 7, digit1, digit2);
|
||||
}
|
||||
|
||||
int extractNumericValueFromBitArray(int pos, int bits) {
|
||||
return extractNumericValueFromBitArray(this.information, pos, bits);
|
||||
}
|
||||
|
||||
static int extractNumericValueFromBitArray(BitArray information, int pos, int bits) {
|
||||
int value = 0;
|
||||
for (int i = 0; i < bits; ++i) {
|
||||
if (information.get(pos + i)) {
|
||||
value |= 1 << (bits - i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
DecodedInformation decodeGeneralPurposeField(int pos, String remaining) throws FormatException {
|
||||
this.buffer.setLength(0);
|
||||
|
||||
if (remaining != null) {
|
||||
this.buffer.append(remaining);
|
||||
}
|
||||
|
||||
this.current.setPosition(pos);
|
||||
|
||||
DecodedInformation lastDecoded = parseBlocks();
|
||||
if (lastDecoded != null && lastDecoded.isRemaining()) {
|
||||
return new DecodedInformation(this.current.getPosition(),
|
||||
this.buffer.toString(), lastDecoded.getRemainingValue());
|
||||
}
|
||||
return new DecodedInformation(this.current.getPosition(), this.buffer.toString());
|
||||
}
|
||||
|
||||
private DecodedInformation parseBlocks() throws FormatException {
|
||||
boolean isFinished;
|
||||
BlockParsedResult result;
|
||||
do {
|
||||
int initialPosition = current.getPosition();
|
||||
|
||||
if (current.isAlpha()) {
|
||||
result = parseAlphaBlock();
|
||||
isFinished = result.isFinished();
|
||||
} else if (current.isIsoIec646()) {
|
||||
result = parseIsoIec646Block();
|
||||
isFinished = result.isFinished();
|
||||
} else { // it must be numeric
|
||||
result = parseNumericBlock();
|
||||
isFinished = result.isFinished();
|
||||
}
|
||||
|
||||
boolean positionChanged = initialPosition != current.getPosition();
|
||||
if (!positionChanged && !isFinished) {
|
||||
break;
|
||||
}
|
||||
} while (!isFinished);
|
||||
|
||||
return result.getDecodedInformation();
|
||||
}
|
||||
|
||||
private BlockParsedResult parseNumericBlock() throws FormatException {
|
||||
while (isStillNumeric(current.getPosition())) {
|
||||
DecodedNumeric numeric = decodeNumeric(current.getPosition());
|
||||
current.setPosition(numeric.getNewPosition());
|
||||
|
||||
if (numeric.isFirstDigitFNC1()) {
|
||||
DecodedInformation information;
|
||||
if (numeric.isSecondDigitFNC1()) {
|
||||
information = new DecodedInformation(current.getPosition(), buffer.toString());
|
||||
} else {
|
||||
information = new DecodedInformation(current.getPosition(), buffer.toString(), numeric.getSecondDigit());
|
||||
}
|
||||
return new BlockParsedResult(information, true);
|
||||
}
|
||||
buffer.append(numeric.getFirstDigit());
|
||||
|
||||
if (numeric.isSecondDigitFNC1()) {
|
||||
DecodedInformation information = new DecodedInformation(current.getPosition(), buffer.toString());
|
||||
return new BlockParsedResult(information, true);
|
||||
}
|
||||
buffer.append(numeric.getSecondDigit());
|
||||
}
|
||||
|
||||
if (isNumericToAlphaNumericLatch(current.getPosition())) {
|
||||
current.setAlpha();
|
||||
current.incrementPosition(4);
|
||||
}
|
||||
return new BlockParsedResult();
|
||||
}
|
||||
|
||||
private BlockParsedResult parseIsoIec646Block() throws FormatException {
|
||||
while (isStillIsoIec646(current.getPosition())) {
|
||||
DecodedChar iso = decodeIsoIec646(current.getPosition());
|
||||
current.setPosition(iso.getNewPosition());
|
||||
|
||||
if (iso.isFNC1()) {
|
||||
DecodedInformation information = new DecodedInformation(current.getPosition(), buffer.toString());
|
||||
return new BlockParsedResult(information, true);
|
||||
}
|
||||
buffer.append(iso.getValue());
|
||||
}
|
||||
|
||||
if (isAlphaOr646ToNumericLatch(current.getPosition())) {
|
||||
current.incrementPosition(3);
|
||||
current.setNumeric();
|
||||
} else if (isAlphaTo646ToAlphaLatch(current.getPosition())) {
|
||||
if (current.getPosition() + 5 < this.information.getSize()) {
|
||||
current.incrementPosition(5);
|
||||
} else {
|
||||
current.setPosition(this.information.getSize());
|
||||
}
|
||||
|
||||
current.setAlpha();
|
||||
}
|
||||
return new BlockParsedResult();
|
||||
}
|
||||
|
||||
private BlockParsedResult parseAlphaBlock() {
|
||||
while (isStillAlpha(current.getPosition())) {
|
||||
DecodedChar alpha = decodeAlphanumeric(current.getPosition());
|
||||
current.setPosition(alpha.getNewPosition());
|
||||
|
||||
if (alpha.isFNC1()) {
|
||||
DecodedInformation information = new DecodedInformation(current.getPosition(), buffer.toString());
|
||||
return new BlockParsedResult(information, true); //end of the char block
|
||||
}
|
||||
|
||||
buffer.append(alpha.getValue());
|
||||
}
|
||||
|
||||
if (isAlphaOr646ToNumericLatch(current.getPosition())) {
|
||||
current.incrementPosition(3);
|
||||
current.setNumeric();
|
||||
} else if (isAlphaTo646ToAlphaLatch(current.getPosition())) {
|
||||
if (current.getPosition() + 5 < this.information.getSize()) {
|
||||
current.incrementPosition(5);
|
||||
} else {
|
||||
current.setPosition(this.information.getSize());
|
||||
}
|
||||
|
||||
current.setIsoIec646();
|
||||
}
|
||||
return new BlockParsedResult();
|
||||
}
|
||||
|
||||
private boolean isStillIsoIec646(int pos) {
|
||||
if (pos + 5 > this.information.getSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
|
||||
if (fiveBitValue >= 5 && fiveBitValue < 16) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pos + 7 > this.information.getSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int sevenBitValue = extractNumericValueFromBitArray(pos, 7);
|
||||
if (sevenBitValue >= 64 && sevenBitValue < 116) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pos + 8 > this.information.getSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int eightBitValue = extractNumericValueFromBitArray(pos, 8);
|
||||
return eightBitValue >= 232 && eightBitValue < 253;
|
||||
|
||||
}
|
||||
|
||||
private DecodedChar decodeIsoIec646(int pos) throws FormatException {
|
||||
int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
|
||||
if (fiveBitValue == 15) {
|
||||
return new DecodedChar(pos + 5, DecodedChar.FNC1);
|
||||
}
|
||||
|
||||
if (fiveBitValue >= 5 && fiveBitValue < 15) {
|
||||
return new DecodedChar(pos + 5, (char) ('0' + fiveBitValue - 5));
|
||||
}
|
||||
|
||||
int sevenBitValue = extractNumericValueFromBitArray(pos, 7);
|
||||
|
||||
if (sevenBitValue >= 64 && sevenBitValue < 90) {
|
||||
return new DecodedChar(pos + 7, (char) (sevenBitValue + 1));
|
||||
}
|
||||
|
||||
if (sevenBitValue >= 90 && sevenBitValue < 116) {
|
||||
return new DecodedChar(pos + 7, (char) (sevenBitValue + 7));
|
||||
}
|
||||
|
||||
int eightBitValue = extractNumericValueFromBitArray(pos, 8);
|
||||
char c;
|
||||
switch (eightBitValue) {
|
||||
case 232:
|
||||
c = '!';
|
||||
break;
|
||||
case 233:
|
||||
c = '"';
|
||||
break;
|
||||
case 234:
|
||||
c = '%';
|
||||
break;
|
||||
case 235:
|
||||
c = '&';
|
||||
break;
|
||||
case 236:
|
||||
c = '\'';
|
||||
break;
|
||||
case 237:
|
||||
c = '(';
|
||||
break;
|
||||
case 238:
|
||||
c = ')';
|
||||
break;
|
||||
case 239:
|
||||
c = '*';
|
||||
break;
|
||||
case 240:
|
||||
c = '+';
|
||||
break;
|
||||
case 241:
|
||||
c = ',';
|
||||
break;
|
||||
case 242:
|
||||
c = '-';
|
||||
break;
|
||||
case 243:
|
||||
c = '.';
|
||||
break;
|
||||
case 244:
|
||||
c = '/';
|
||||
break;
|
||||
case 245:
|
||||
c = ':';
|
||||
break;
|
||||
case 246:
|
||||
c = ';';
|
||||
break;
|
||||
case 247:
|
||||
c = '<';
|
||||
break;
|
||||
case 248:
|
||||
c = '=';
|
||||
break;
|
||||
case 249:
|
||||
c = '>';
|
||||
break;
|
||||
case 250:
|
||||
c = '?';
|
||||
break;
|
||||
case 251:
|
||||
c = '_';
|
||||
break;
|
||||
case 252:
|
||||
c = ' ';
|
||||
break;
|
||||
default:
|
||||
throw FormatException.getFormatInstance();
|
||||
}
|
||||
return new DecodedChar(pos + 8, c);
|
||||
}
|
||||
|
||||
private boolean isStillAlpha(int pos) {
|
||||
if (pos + 5 > this.information.getSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We now check if it's a valid 5-bit value (0..9 and FNC1)
|
||||
int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
|
||||
if (fiveBitValue >= 5 && fiveBitValue < 16) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pos + 6 > this.information.getSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int sixBitValue = extractNumericValueFromBitArray(pos, 6);
|
||||
return sixBitValue >= 16 && sixBitValue < 63; // 63 not included
|
||||
}
|
||||
|
||||
private DecodedChar decodeAlphanumeric(int pos) {
|
||||
int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
|
||||
if (fiveBitValue == 15) {
|
||||
return new DecodedChar(pos + 5, DecodedChar.FNC1);
|
||||
}
|
||||
|
||||
if (fiveBitValue >= 5 && fiveBitValue < 15) {
|
||||
return new DecodedChar(pos + 5, (char) ('0' + fiveBitValue - 5));
|
||||
}
|
||||
|
||||
int sixBitValue = extractNumericValueFromBitArray(pos, 6);
|
||||
|
||||
if (sixBitValue >= 32 && sixBitValue < 58) {
|
||||
return new DecodedChar(pos + 6, (char) (sixBitValue + 33));
|
||||
}
|
||||
|
||||
char c;
|
||||
switch (sixBitValue) {
|
||||
case 58:
|
||||
c = '*';
|
||||
break;
|
||||
case 59:
|
||||
c = ',';
|
||||
break;
|
||||
case 60:
|
||||
c = '-';
|
||||
break;
|
||||
case 61:
|
||||
c = '.';
|
||||
break;
|
||||
case 62:
|
||||
c = '/';
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Decoding invalid alphanumeric value: " + sixBitValue);
|
||||
}
|
||||
return new DecodedChar(pos + 6, c);
|
||||
}
|
||||
|
||||
private boolean isAlphaTo646ToAlphaLatch(int pos) {
|
||||
if (pos + 1 > this.information.getSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5 && i + pos < this.information.getSize(); ++i) {
|
||||
if (i == 2) {
|
||||
if (!this.information.get(pos + 2)) {
|
||||
return false;
|
||||
}
|
||||
} else if (this.information.get(pos + i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isAlphaOr646ToNumericLatch(int pos) {
|
||||
// Next is alphanumeric if there are 3 positions and they are all zeros
|
||||
if (pos + 3 > this.information.getSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = pos; i < pos + 3; ++i) {
|
||||
if (this.information.get(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isNumericToAlphaNumericLatch(int pos) {
|
||||
// Next is alphanumeric if there are 4 positions and they are all zeros, or
|
||||
// if there is a subset of this just before the end of the symbol
|
||||
if (pos + 1 > this.information.getSize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4 && i + pos < this.information.getSize(); ++i) {
|
||||
if (this.information.get(pos + i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user