Symbology Identifier support (#1372)

* decoder support for symbology identifier metadata

Co-authored-by: Daniel Dehnhard <daniel@dehnhard.it>
This commit is contained in:
dehnhard 2021-04-02 14:21:33 +02:00 committed by GitHub
parent 3bf945e4cf
commit ef498941bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 175 additions and 24 deletions

View file

@ -94,4 +94,8 @@ public enum ResultMetadataType {
*/
STRUCTURED_APPEND_PARITY,
/**
* Barcode Symbology Identifier.
*/
SYMBOLOGY_IDENTIFIER,
}

View file

@ -110,6 +110,7 @@ public final class AztecReader implements Reader {
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]z" + decoderResult.getSymbologyModifier());
return result;
}

View file

@ -37,12 +37,21 @@ public final class DecoderResult {
private Object other;
private final int structuredAppendParity;
private final int structuredAppendSequenceNumber;
private final int symbologyModifier;
public DecoderResult(byte[] rawBytes,
String text,
List<byte[]> byteSegments,
String ecLevel) {
this(rawBytes, text, byteSegments, ecLevel, -1, -1);
this(rawBytes, text, byteSegments, ecLevel, -1, -1, 0);
}
public DecoderResult(byte[] rawBytes,
String text,
List<byte[]> byteSegments,
String ecLevel,
int symbologyModifier) {
this(rawBytes, text, byteSegments, ecLevel, -1, -1, symbologyModifier);
}
public DecoderResult(byte[] rawBytes,
@ -51,6 +60,16 @@ public final class DecoderResult {
String ecLevel,
int saSequence,
int saParity) {
this(rawBytes, text, byteSegments, ecLevel, saSequence, saParity, 0);
}
public DecoderResult(byte[] rawBytes,
String text,
List<byte[]> byteSegments,
String ecLevel,
int saSequence,
int saParity,
int symbologyModifier) {
this.rawBytes = rawBytes;
this.numBits = rawBytes == null ? 0 : 8 * rawBytes.length;
this.text = text;
@ -58,6 +77,7 @@ public final class DecoderResult {
this.ecLevel = ecLevel;
this.structuredAppendParity = saParity;
this.structuredAppendSequenceNumber = saSequence;
this.symbologyModifier = symbologyModifier;
}
/**
@ -149,4 +169,8 @@ public final class DecoderResult {
return structuredAppendSequenceNumber;
}
public int getSymbologyModifier() {
return symbologyModifier;
}
}

View file

@ -83,6 +83,7 @@ public final class DataMatrixReader implements Reader {
if (ecLevel != null) {
result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
}
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]d" + decoderResult.getSymbologyModifier());
return result;
}

View file

@ -23,7 +23,9 @@ import com.google.zxing.common.DecoderResult;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* <p>Data Matrix Codes can encode text as bits in one of several modes, and can use multiple modes
@ -43,7 +45,8 @@ final class DecodedBitStreamParser {
TEXT_ENCODE,
ANSIX12_ENCODE,
EDIFACT_ENCODE,
BASE256_ENCODE
BASE256_ENCODE,
ECI_ENCODE
}
/**
@ -87,17 +90,20 @@ final class DecodedBitStreamParser {
StringBuilder result = new StringBuilder(100);
StringBuilder resultTrailer = new StringBuilder(0);
List<byte[]> byteSegments = new ArrayList<>(1);
int symbologyModifier = 0;
Mode mode = Mode.ASCII_ENCODE;
Set<Integer> fnc1Positions = new HashSet<Integer>(); // Would be replaceable by looking directly at 'bytes', if we're sure to not having to account for multi byte values.
boolean isECIencoded = false;
do {
if (mode == Mode.ASCII_ENCODE) {
mode = decodeAsciiSegment(bits, result, resultTrailer);
mode = decodeAsciiSegment(bits, result, resultTrailer, fnc1Positions);
} else {
switch (mode) {
case C40_ENCODE:
decodeC40Segment(bits, result);
decodeC40Segment(bits, result, fnc1Positions);
break;
case TEXT_ENCODE:
decodeTextSegment(bits, result);
decodeTextSegment(bits, result, fnc1Positions);
break;
case ANSIX12_ENCODE:
decodeAnsiX12Segment(bits, result);
@ -108,6 +114,9 @@ final class DecodedBitStreamParser {
case BASE256_ENCODE:
decodeBase256Segment(bits, result, byteSegments);
break;
case ECI_ENCODE:
isECIencoded = true; // ECI detection only, atm continue decoding as ASCII
break;
default:
throw FormatException.getFormatInstance();
}
@ -117,7 +126,27 @@ final class DecodedBitStreamParser {
if (resultTrailer.length() > 0) {
result.append(resultTrailer);
}
return new DecoderResult(bytes, result.toString(), byteSegments.isEmpty() ? null : byteSegments, null);
if (isECIencoded) {
// Examples for this numbers can be found in this documentation of a hardware barcode scanner:
// https://honeywellaidc.force.com/supportppr/s/article/List-of-barcode-symbology-AIM-Identifiers
if (fnc1Positions.contains(0) || fnc1Positions.contains(4)) {
symbologyModifier = 5;
} else if (fnc1Positions.contains(1) || fnc1Positions.contains(5)) {
symbologyModifier = 6;
} else {
symbologyModifier = 4;
}
} else {
if (fnc1Positions.contains(0) || fnc1Positions.contains(4)) {
symbologyModifier = 2;
} else if (fnc1Positions.contains(1) || fnc1Positions.contains(5)) {
symbologyModifier = 3;
} else {
symbologyModifier = 1;
}
}
return new DecoderResult(bytes, result.toString(), byteSegments.isEmpty() ? null : byteSegments, null, symbologyModifier);
}
/**
@ -125,7 +154,8 @@ final class DecodedBitStreamParser {
*/
private static Mode decodeAsciiSegment(BitSource bits,
StringBuilder result,
StringBuilder resultTrailer) throws FormatException {
StringBuilder resultTrailer,
Set<Integer> fnc1positions) throws FormatException {
boolean upperShift = false;
do {
int oneByte = bits.readBits(8);
@ -153,6 +183,7 @@ final class DecodedBitStreamParser {
case 231: // Latch to Base 256 encodation
return Mode.BASE256_ENCODE;
case 232: // FNC1
fnc1positions.add(result.length());
result.append((char) 29); // translate as ASCII 29
break;
case 233: // Structured Append
@ -178,10 +209,7 @@ final class DecodedBitStreamParser {
case 240: // Latch to EDIFACT encodation
return Mode.EDIFACT_ENCODE;
case 241: // ECI Character
// TODO(bbrown): I think we need to support ECI
//throw ReaderException.getInstance();
// Ignore this symbol for now
break;
return Mode.ECI_ENCODE;
default:
// Not to be used in ASCII encodation
// but work around encoders that end with 254, latch back to ASCII
@ -198,7 +226,7 @@ final class DecodedBitStreamParser {
/**
* See ISO 16022:2006, 5.2.5 and Annex C, Table C.1
*/
private static void decodeC40Segment(BitSource bits, StringBuilder result) throws FormatException {
private static void decodeC40Segment(BitSource bits, StringBuilder result, Set<Integer> fnc1positions) throws FormatException {
// Three C40 values are encoded in a 16-bit value as
// (1600 * C1) + (40 * C2) + C3 + 1
// TODO(bbrown): The Upper Shift with C40 doesn't work in the 4 value scenario all the time
@ -258,6 +286,7 @@ final class DecodedBitStreamParser {
} else {
switch (cValue) {
case 27: // FNC1
fnc1positions.add(result.length());
result.append((char) 29); // translate as ASCII 29
break;
case 30: // Upper Shift
@ -288,7 +317,7 @@ final class DecodedBitStreamParser {
/**
* See ISO 16022:2006, 5.2.6 and Annex C, Table C.2
*/
private static void decodeTextSegment(BitSource bits, StringBuilder result) throws FormatException {
private static void decodeTextSegment(BitSource bits, StringBuilder result, Set<Integer> fnc1positions) throws FormatException {
// Three Text values are encoded in a 16-bit value as
// (1600 * C1) + (40 * C2) + C3 + 1
// TODO(bbrown): The Upper Shift with Text doesn't work in the 4 value scenario all the time
@ -348,6 +377,7 @@ final class DecodedBitStreamParser {
} else {
switch (cValue) {
case 27: // FNC1
fnc1positions.add(result.length());
result.append((char) 29); // translate as ASCII 29
break;
case 30: // Upper Shift

View file

@ -20,6 +20,7 @@ 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;
@ -152,13 +153,16 @@ public final class CodaBarReader extends OneDReader {
runningCount += counters[i];
}
float right = runningCount;
return new Result(
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 {

View file

@ -22,6 +22,7 @@ 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;
@ -238,6 +239,8 @@ public final class Code128Reader extends OneDReader {
boolean convertFNC1 = hints != null && hints.containsKey(DecodeHintType.ASSUME_GS1);
int symbologyModifier = 0;
int[] startPatternInfo = findStartPattern(row);
int startCode = startPatternInfo[2];
@ -339,6 +342,11 @@ public final class Code128Reader extends OneDReader {
}
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
@ -351,6 +359,8 @@ public final class Code128Reader extends OneDReader {
}
break;
case CODE_FNC_2:
symbologyModifier = 4;
break;
case CODE_FNC_3:
// do nothing?
break;
@ -395,6 +405,11 @@ public final class Code128Reader extends OneDReader {
}
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
@ -407,6 +422,8 @@ public final class Code128Reader extends OneDReader {
}
break;
case CODE_FNC_2:
symbologyModifier = 4;
break;
case CODE_FNC_3:
// do nothing?
break;
@ -449,6 +466,11 @@ public final class Code128Reader extends OneDReader {
}
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
@ -460,6 +482,9 @@ public final class Code128Reader extends OneDReader {
}
}
break;
case CODE_FNC_2:
symbologyModifier = 4;
break;
case CODE_CODE_A:
codeSet = CODE_CODE_A;
break;
@ -525,14 +550,15 @@ public final class Code128Reader extends OneDReader {
for (int i = 0; i < rawCodesSize; i++) {
rawBytes[i] = rawCodes.get(i);
}
return new Result(
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;
}

View file

@ -22,6 +22,7 @@ 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;
@ -165,14 +166,16 @@ public final class Code39Reader extends OneDReader {
float left = (start[1] + start[0]) / 2.0f;
float right = lastStart + lastPatternSize / 2.0f;
return new Result(
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 {

View file

@ -22,6 +22,7 @@ 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;
@ -118,14 +119,16 @@ public final class Code93Reader extends OneDReader {
float left = (start[1] + start[0]) / 2.0f;
float right = lastStart + lastPatternSize / 2.0f;
return new Result(
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 {

View file

@ -153,6 +153,7 @@ public abstract class UPCEANReader extends OneDReader {
ResultPointCallback resultPointCallback = hints == null ? null :
(ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
int symbologyIdentifier = 0;
if (resultPointCallback != null) {
resultPointCallback.foundPossibleResultPoint(new ResultPoint(
@ -239,6 +240,11 @@ public abstract class UPCEANReader extends OneDReader {
decodeResult.putMetadata(ResultMetadataType.POSSIBLE_COUNTRY, countryID);
}
}
if (format == BarcodeFormat.EAN_8) {
symbologyIdentifier = 4;
}
decodeResult.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]E" + symbologyIdentifier);
return decodeResult;
}

View file

@ -20,6 +20,7 @@ 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;
@ -131,11 +132,13 @@ public final class RSS14Reader extends AbstractRSSReader {
ResultPoint[] leftPoints = leftPair.getFinderPattern().getResultPoints();
ResultPoint[] rightPoints = rightPair.getFinderPattern().getResultPoints();
return new Result(
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) {

View file

@ -31,6 +31,7 @@ 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;
@ -355,12 +356,14 @@ public final class RSSExpandedReader extends AbstractRSSReader {
ResultPoint[] firstPoints = pairs.get(0).getFinderPattern().getResultPoints();
ResultPoint[] lastPoints = pairs.get(pairs.size() - 1).getFinderPattern().getResultPoints();
return new Result(
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() {

View file

@ -81,7 +81,7 @@ public final class PDF417Reader implements Reader, MultipleBarcodeReader {
}
}
private static Result[] decode(BinaryBitmap image, Map<DecodeHintType, ?> hints, boolean multiple)
private static Result[] decode(BinaryBitmap image, Map<DecodeHintType, ?> hints, boolean multiple)
throws NotFoundException, FormatException, ChecksumException {
List<Result> results = new ArrayList<>();
PDF417DetectorResult detectorResult = Detector.detect(image, hints, multiple);
@ -94,6 +94,7 @@ public final class PDF417Reader implements Reader, MultipleBarcodeReader {
if (pdf417ResultMetadata != null) {
result.putMetadata(ResultMetadataType.PDF417_EXTRA_METADATA, pdf417ResultMetadata);
}
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]L" + decoderResult.getSymbologyModifier());
results.add(result);
}
return results.toArray(EMPTY_RESULT_ARRAY);

View file

@ -99,6 +99,7 @@ public class QRCodeReader implements Reader {
result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
decoderResult.getStructuredAppendParity());
}
result.putMetadata(ResultMetadataType.SYMBOLOGY_IDENTIFIER, "]Q" + decoderResult.getSymbologyModifier());
return result;
}

View file

@ -58,10 +58,13 @@ final class DecodedBitStreamParser {
List<byte[]> byteSegments = new ArrayList<>(1);
int symbolSequence = -1;
int parityData = -1;
int symbologyModifier = 1;
try {
CharacterSetECI currentCharacterSetECI = null;
boolean fc1InEffect = false;
boolean hasFNC1first = false;
boolean hasFNC1second = false;
Mode mode;
do {
// While still another segment to read...
@ -75,7 +78,12 @@ final class DecodedBitStreamParser {
case TERMINATOR:
break;
case FNC1_FIRST_POSITION:
hasFNC1first = true; // symbology detection
// We do little with FNC1 except alter the parsed result a bit according to the spec
fc1InEffect = true;
break;
case FNC1_SECOND_POSITION:
hasFNC1second = true; // symbology detection
// We do little with FNC1 except alter the parsed result a bit according to the spec
fc1InEffect = true;
break;
@ -128,6 +136,25 @@ final class DecodedBitStreamParser {
break;
}
} while (mode != Mode.TERMINATOR);
if (currentCharacterSetECI != null) {
if (hasFNC1first) {
symbologyModifier = 4;
} else if (hasFNC1second) {
symbologyModifier = 6;
} else {
symbologyModifier = 2;
}
} else {
if (hasFNC1first) {
symbologyModifier = 3;
} else if (hasFNC1second) {
symbologyModifier = 5;
} else {
symbologyModifier = 1;
}
}
} catch (IllegalArgumentException iae) {
// from readBits() calls
throw FormatException.getFormatInstance();
@ -138,7 +165,8 @@ final class DecodedBitStreamParser {
byteSegments.isEmpty() ? null : byteSegments,
ecLevel == null ? null : ecLevel.toString(),
symbolSequence,
parityData);
parityData,
symbologyModifier);
}
/**

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]z0

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]F0

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]C1

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]C0

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]A0

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]G0

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]d1

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]E0

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]E4

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]I0

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]L0

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]Q1

View file

@ -0,0 +1 @@
SYMBOLOGY_IDENTIFIER=]Q1