Improve support for Macro PDF417 (#973)

* Improve support for Macro PDF417

* cleanup and move license to it's proper place

* add .editorconfig + proper indentation / add optionalFields array

* rename variables and use Arrays instead of System

* replaced length with to

* restore spaces & switch to package private
This commit is contained in:
Florian Schrag 2018-03-15 20:44:55 +01:00 committed by Sean Owen
parent a3bbebccba
commit dbfd5520e9
10 changed files with 309 additions and 33 deletions

9
.editorconfig Normal file
View file

@ -0,0 +1,9 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View file

@ -23,9 +23,21 @@ public final class PDF417ResultMetadata {
private int segmentIndex; private int segmentIndex;
private String fileId; private String fileId;
private int[] optionalData;
private boolean lastSegment; private boolean lastSegment;
private int segmentCount = -1;
private String sender;
private String addressee;
private String fileName;
private long fileSize = -1;
private long timestamp = -1;
private int checksum = -1;
private int[] optionalData;
/**
* The Segment ID represents the segment of the whole file distributed over different symbols.
*
* @return File segment index
*/
public int getSegmentIndex() { public int getSegmentIndex() {
return segmentIndex; return segmentIndex;
} }
@ -34,6 +46,11 @@ public final class PDF417ResultMetadata {
this.segmentIndex = segmentIndex; this.segmentIndex = segmentIndex;
} }
/**
* Is the same for each related PDF417 symbol
*
* @return File ID
*/
public String getFileId() { public String getFileId() {
return fileId; return fileId;
} }
@ -42,14 +59,27 @@ public final class PDF417ResultMetadata {
this.fileId = fileId; this.fileId = fileId;
} }
/**
* @return always null
* @deprecated use dedicated already parsed fields
*/
@Deprecated
public int[] getOptionalData() { public int[] getOptionalData() {
return optionalData; return optionalData;
} }
/**
* @deprecated parse and use new fields
*/
@Deprecated
public void setOptionalData(int[] optionalData) { public void setOptionalData(int[] optionalData) {
this.optionalData = optionalData; this.optionalData = optionalData;
} }
/**
* @return true if it is the last segment
*/
public boolean isLastSegment() { public boolean isLastSegment() {
return lastSegment; return lastSegment;
} }
@ -58,4 +88,83 @@ public final class PDF417ResultMetadata {
this.lastSegment = lastSegment; this.lastSegment = lastSegment;
} }
/**
* @return count of segments, -1 if not set
*/
public int getSegmentCount() {
return segmentCount;
}
public void setSegmentCount(int segmentCount) {
this.segmentCount = segmentCount;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getAddressee() {
return addressee;
}
public void setAddressee(String addressee) {
this.addressee = addressee;
}
/**
* Filename of the encoded file
*
* @return filename
*/
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* filesize in bytes of the encoded file
*
* @return filesize in bytes, -1 if not set
*/
public long getFileSize() {
return fileSize;
}
public void setFileSize(long fileSize) {
this.fileSize = fileSize;
}
/**
* 16-bit CRC checksum using CCITT-16
*
* @return crc checksum, -1 if not set
*/
public int getChecksum() {
return checksum;
}
public void setChecksum(int checksum) {
this.checksum = checksum;
}
/**
* unix epock timestamp, elapsed seconds since 1970-01-01
*
* @return elapsed seconds, -1 if not set
*/
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
} }

View file

@ -57,6 +57,14 @@ final class DecodedBitStreamParser {
private static final int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; private static final int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913;
private static final int MAX_NUMERIC_CODEWORDS = 15; private static final int MAX_NUMERIC_CODEWORDS = 15;
private static final int MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME = 0;
private static final int MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT = 1;
private static final int MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP = 2;
private static final int MACRO_PDF417_OPTIONAL_FIELD_SENDER = 3;
private static final int MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE = 4;
private static final int MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE = 5;
private static final int MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM = 6;
private static final int PL = 25; private static final int PL = 25;
private static final int LL = 27; private static final int LL = 27;
private static final int AS = 27; private static final int AS = 27;
@ -76,6 +84,7 @@ final class DecodedBitStreamParser {
* This is used in the numeric compaction decode algorithm. * This is used in the numeric compaction decode algorithm.
*/ */
private static final BigInteger[] EXP900; private static final BigInteger[] EXP900;
static { static {
EXP900 = new BigInteger[16]; EXP900 = new BigInteger[16];
EXP900[0] = BigInteger.ONE; EXP900[0] = BigInteger.ONE;
@ -124,7 +133,7 @@ final class DecodedBitStreamParser {
break; break;
case ECI_USER_DEFINED: case ECI_USER_DEFINED:
// Can't do anything with user ECI; skip its 1 character // Can't do anything with user ECI; skip its 1 character
codeIndex ++; codeIndex++;
break; break;
case BEGIN_MACRO_PDF417_CONTROL_BLOCK: case BEGIN_MACRO_PDF417_CONTROL_BLOCK:
codeIndex = decodeMacroBlock(codewords, codeIndex, resultMetadata); codeIndex = decodeMacroBlock(codewords, codeIndex, resultMetadata);
@ -155,7 +164,7 @@ final class DecodedBitStreamParser {
return decoderResult; return decoderResult;
} }
private static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata) static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata)
throws FormatException { throws FormatException {
if (codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0]) { if (codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0]) {
// we must have at least two bytes left for the segment index // we must have at least two bytes left for the segment index
@ -172,35 +181,72 @@ final class DecodedBitStreamParser {
codeIndex = textCompaction(codewords, codeIndex, fileId); codeIndex = textCompaction(codewords, codeIndex, fileId);
resultMetadata.setFileId(fileId.toString()); resultMetadata.setFileId(fileId.toString());
int optionalFieldsStart = -1;
if (codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) {
optionalFieldsStart = codeIndex + 1;
}
while (codeIndex < codewords[0]) {
switch (codewords[codeIndex]) { switch (codewords[codeIndex]) {
case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
codeIndex++; codeIndex++;
int[] additionalOptionCodeWords = new int[codewords[0] - codeIndex]; switch (codewords[codeIndex]) {
int additionalOptionCodeWordsIndex = 0; case MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME:
StringBuilder fileName = new StringBuilder();
boolean end = false; codeIndex = textCompaction(codewords, codeIndex + 1, fileName);
while ((codeIndex < codewords[0]) && !end) { resultMetadata.setFileName(fileName.toString());
int code = codewords[codeIndex++]; break;
if (code < TEXT_COMPACTION_MODE_LATCH) { case MACRO_PDF417_OPTIONAL_FIELD_SENDER:
additionalOptionCodeWords[additionalOptionCodeWordsIndex++] = code; StringBuilder sender = new StringBuilder();
} else { codeIndex = textCompaction(codewords, codeIndex + 1, sender);
switch (code) { resultMetadata.setSender(sender.toString());
break;
case MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE:
StringBuilder addressee = new StringBuilder();
codeIndex = textCompaction(codewords, codeIndex + 1, addressee);
resultMetadata.setAddressee(addressee.toString());
break;
case MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT:
StringBuilder segmentCount = new StringBuilder();
codeIndex = numericCompaction(codewords, codeIndex + 1, segmentCount);
resultMetadata.setSegmentCount(Integer.parseInt(segmentCount.toString()));
break;
case MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP:
StringBuilder timestamp = new StringBuilder();
codeIndex = numericCompaction(codewords, codeIndex + 1, timestamp);
resultMetadata.setTimestamp(Long.parseLong(timestamp.toString()));
break;
case MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM:
StringBuilder checksum = new StringBuilder();
codeIndex = numericCompaction(codewords, codeIndex + 1, checksum);
resultMetadata.setChecksum(Integer.parseInt(checksum.toString()));
break;
case MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE:
StringBuilder fileSize = new StringBuilder();
codeIndex = numericCompaction(codewords, codeIndex + 1, fileSize);
resultMetadata.setFileSize(Long.parseLong(fileSize.toString()));
break;
default:
throw FormatException.getFormatInstance();
}
break;
case MACRO_PDF417_TERMINATOR: case MACRO_PDF417_TERMINATOR:
resultMetadata.setLastSegment(true);
codeIndex++; codeIndex++;
end = true; resultMetadata.setLastSegment(true);
break; break;
default: default:
throw FormatException.getFormatInstance(); throw FormatException.getFormatInstance();
} }
} }
// copy optional fields to additional options
if (optionalFieldsStart != -1) {
int optionalFieldsLength = codeIndex - optionalFieldsStart;
if (resultMetadata.isLastSegment()) {
// do not include terminator
optionalFieldsLength--;
} }
resultMetadata.setOptionalData(Arrays.copyOf(additionalOptionCodeWords, additionalOptionCodeWordsIndex)); resultMetadata.setOptionalData(Arrays.copyOfRange(codewords, optionalFieldsStart, optionalFieldsStart + optionalFieldsLength));
break;
case MACRO_PDF417_TERMINATOR:
resultMetadata.setLastSegment(true);
codeIndex++;
break;
} }
return codeIndex; return codeIndex;

View file

@ -24,8 +24,8 @@ public final class PDF417BlackBox3TestCase extends AbstractBlackBoxTestCase {
public PDF417BlackBox3TestCase() { public PDF417BlackBox3TestCase() {
super("src/test/resources/blackbox/pdf417-3", new MultiFormatReader(), BarcodeFormat.PDF_417); super("src/test/resources/blackbox/pdf417-3", new MultiFormatReader(), BarcodeFormat.PDF_417);
addTest(18, 18, 0, 0, 0.0f); addTest(19, 19, 0, 0, 0.0f);
addTest(18, 18, 0, 0, 180.0f); addTest(19, 19, 0, 0, 180.0f);
} }
} }

View file

@ -64,7 +64,7 @@ public final class PDF417BlackBox4TestCase extends AbstractBlackBoxTestCase {
public PDF417BlackBox4TestCase() { public PDF417BlackBox4TestCase() {
super("src/test/resources/blackbox/pdf417-4", null, BarcodeFormat.PDF_417); super("src/test/resources/blackbox/pdf417-4", null, BarcodeFormat.PDF_417);
testResults.add(new TestResult(2, 2, 0, 0, 0.0f)); testResults.add(new TestResult(3, 3, 0, 0, 0.0f));
} }
@Test @Test

View file

@ -0,0 +1,110 @@
/*
* 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.pdf417.decoder;
import com.google.zxing.FormatException;
import com.google.zxing.pdf417.PDF417ResultMetadata;
import org.junit.Test;
import static org.junit.Assert.*;
public class PDF471DecoderTestCase {
/**
* Tests the first sample given in ISO/IEC 15438:2015(E) - Annex H.4
*/
@Test
public void testStandardSample1() throws FormatException {
PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata();
int[] sampleCodes = {20, 928, 111, 100, 17, 53, 923, 1, 111, 104, 923, 3, 64, 416, 34, 923, 4, 258, 446, 67,
// we should never reach these
1000, 1000, 1000};
DecodedBitStreamParser.decodeMacroBlock(sampleCodes, 2, resultMetadata);
assertEquals(0, resultMetadata.getSegmentIndex());
assertEquals("ARBX", resultMetadata.getFileId());
assertFalse(resultMetadata.isLastSegment());
assertEquals(4, resultMetadata.getSegmentCount());
assertEquals("CEN BE", resultMetadata.getSender());
assertEquals("ISO CH", resultMetadata.getAddressee());
int[] optionalData = resultMetadata.getOptionalData();
assertEquals("first element of optional array should be the first field identifier", 1, optionalData[0]);
assertEquals("last element of optional array should be the last codeword of the last field", 67, optionalData[optionalData.length - 1]);
}
/**
* Tests the second given in ISO/IEC 15438:2015(E) - Annex H.4
*/
@Test
public void testStandardSample2() throws FormatException {
PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata();
int[] sampleCodes = {11, 928, 111, 103, 17, 53, 923, 1, 111, 104, 922,
// we should never reach these
1000, 1000, 1000};
DecodedBitStreamParser.decodeMacroBlock(sampleCodes, 2, resultMetadata);
assertEquals(3, resultMetadata.getSegmentIndex());
assertEquals("ARBX", resultMetadata.getFileId());
assertTrue(resultMetadata.isLastSegment());
assertEquals(4, resultMetadata.getSegmentCount());
assertNull(resultMetadata.getAddressee());
assertNull(resultMetadata.getSender());
int[] optionalData = resultMetadata.getOptionalData();
assertEquals("first element of optional array should be the first field identifier", 1, optionalData[0]);
assertEquals("last element of optional array should be the last codeword of the last field", 104, optionalData[optionalData.length - 1]);
}
@Test
public void testSampleWithFilename() throws FormatException {
int[] sampleCodes = {23, 477, 928, 111, 100, 0, 252, 21, 86, 923, 0, 815, 251, 133, 12, 148, 537, 593, 599, 923, 1, 111, 102, 98, 311, 355, 522, 920, 779, 40, 628, 33, 749, 267, 506, 213, 928, 465, 248, 493, 72, 780, 699, 780, 493, 755, 84, 198, 628, 368, 156, 198, 809, 19, 113};
PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata();
DecodedBitStreamParser.decodeMacroBlock(sampleCodes, 3, resultMetadata);
assertEquals(0, resultMetadata.getSegmentIndex());
assertEquals("AAIMAVC ", resultMetadata.getFileId());
assertFalse(resultMetadata.isLastSegment());
assertEquals(2, resultMetadata.getSegmentCount());
assertNull(resultMetadata.getAddressee());
assertNull(resultMetadata.getSender());
assertEquals("filename.txt", resultMetadata.getFileName());
}
@Test
public void testSampleWithNumericValues() throws FormatException {
int[] sampleCodes = {25, 477, 928, 111, 100, 0, 252, 21, 86, 923, 2, 2, 0, 1, 0, 0, 0, 923, 5, 130, 923, 6, 1, 500, 13, 0};
PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata();
DecodedBitStreamParser.decodeMacroBlock(sampleCodes, 3, resultMetadata);
assertEquals(0, resultMetadata.getSegmentIndex());
assertEquals("AAIMAVC ", resultMetadata.getFileId());
assertFalse(resultMetadata.isLastSegment());
assertEquals(180980729000000L, resultMetadata.getTimestamp());
assertEquals(30, resultMetadata.getFileSize());
assertEquals(260013, resultMetadata.getChecksum());
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

View file

@ -0,0 +1 @@
0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View file

@ -0,0 +1 @@
01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789