diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..c6c8b3621 --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/core/src/main/java/com/google/zxing/pdf417/PDF417ResultMetadata.java b/core/src/main/java/com/google/zxing/pdf417/PDF417ResultMetadata.java index 2e1c4ee26..9c51959ba 100644 --- a/core/src/main/java/com/google/zxing/pdf417/PDF417ResultMetadata.java +++ b/core/src/main/java/com/google/zxing/pdf417/PDF417ResultMetadata.java @@ -23,9 +23,21 @@ public final class PDF417ResultMetadata { private int segmentIndex; private String fileId; - private int[] optionalData; 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() { return segmentIndex; } @@ -34,6 +46,11 @@ public final class PDF417ResultMetadata { this.segmentIndex = segmentIndex; } + /** + * Is the same for each related PDF417 symbol + * + * @return File ID + */ public String getFileId() { return fileId; } @@ -42,14 +59,27 @@ public final class PDF417ResultMetadata { this.fileId = fileId; } + /** + * @return always null + * @deprecated use dedicated already parsed fields + */ + @Deprecated public int[] getOptionalData() { return optionalData; } + /** + * @deprecated parse and use new fields + */ + @Deprecated public void setOptionalData(int[] optionalData) { this.optionalData = optionalData; } + + /** + * @return true if it is the last segment + */ public boolean isLastSegment() { return lastSegment; } @@ -58,4 +88,83 @@ public final class PDF417ResultMetadata { 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; + } + } diff --git a/core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java b/core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java index 94a7b9db5..d4dc585b3 100644 --- a/core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java +++ b/core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java @@ -57,6 +57,14 @@ final class DecodedBitStreamParser { private static final int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; 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 LL = 27; private static final int AS = 27; @@ -76,6 +84,7 @@ final class DecodedBitStreamParser { * This is used in the numeric compaction decode algorithm. */ private static final BigInteger[] EXP900; + static { EXP900 = new BigInteger[16]; EXP900[0] = BigInteger.ONE; @@ -124,7 +133,7 @@ final class DecodedBitStreamParser { break; case ECI_USER_DEFINED: // Can't do anything with user ECI; skip its 1 character - codeIndex ++; + codeIndex++; break; case BEGIN_MACRO_PDF417_CONTROL_BLOCK: codeIndex = decodeMacroBlock(codewords, codeIndex, resultMetadata); @@ -155,7 +164,7 @@ final class DecodedBitStreamParser { return decoderResult; } - private static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata) + static int decodeMacroBlock(int[] codewords, int codeIndex, PDF417ResultMetadata resultMetadata) throws FormatException { if (codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0]) { // we must have at least two bytes left for the segment index @@ -172,35 +181,72 @@ final class DecodedBitStreamParser { codeIndex = textCompaction(codewords, codeIndex, fileId); resultMetadata.setFileId(fileId.toString()); - switch (codewords[codeIndex]) { - case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: - codeIndex++; - int[] additionalOptionCodeWords = new int[codewords[0] - codeIndex]; - int additionalOptionCodeWordsIndex = 0; + int optionalFieldsStart = -1; + if (codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) { + optionalFieldsStart = codeIndex + 1; + } - boolean end = false; - while ((codeIndex < codewords[0]) && !end) { - int code = codewords[codeIndex++]; - if (code < TEXT_COMPACTION_MODE_LATCH) { - additionalOptionCodeWords[additionalOptionCodeWordsIndex++] = code; - } else { - switch (code) { - case MACRO_PDF417_TERMINATOR: - resultMetadata.setLastSegment(true); - codeIndex++; - end = true; - break; - default: - throw FormatException.getFormatInstance(); - } + while (codeIndex < codewords[0]) { + switch (codewords[codeIndex]) { + case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: + codeIndex++; + switch (codewords[codeIndex]) { + case MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME: + StringBuilder fileName = new StringBuilder(); + codeIndex = textCompaction(codewords, codeIndex + 1, fileName); + resultMetadata.setFileName(fileName.toString()); + break; + case MACRO_PDF417_OPTIONAL_FIELD_SENDER: + StringBuilder sender = new StringBuilder(); + codeIndex = textCompaction(codewords, codeIndex + 1, sender); + 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(); } - } - resultMetadata.setOptionalData(Arrays.copyOf(additionalOptionCodeWords, additionalOptionCodeWordsIndex)); - break; - case MACRO_PDF417_TERMINATOR: - resultMetadata.setLastSegment(true); - codeIndex++; - break; + break; + case MACRO_PDF417_TERMINATOR: + codeIndex++; + resultMetadata.setLastSegment(true); + break; + default: + 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.copyOfRange(codewords, optionalFieldsStart, optionalFieldsStart + optionalFieldsLength)); } return codeIndex; diff --git a/core/src/test/java/com/google/zxing/pdf417/PDF417BlackBox3TestCase.java b/core/src/test/java/com/google/zxing/pdf417/PDF417BlackBox3TestCase.java index 9c1b928a8..cd565029e 100644 --- a/core/src/test/java/com/google/zxing/pdf417/PDF417BlackBox3TestCase.java +++ b/core/src/test/java/com/google/zxing/pdf417/PDF417BlackBox3TestCase.java @@ -24,8 +24,8 @@ public final class PDF417BlackBox3TestCase extends AbstractBlackBoxTestCase { public PDF417BlackBox3TestCase() { super("src/test/resources/blackbox/pdf417-3", new MultiFormatReader(), BarcodeFormat.PDF_417); - addTest(18, 18, 0, 0, 0.0f); - addTest(18, 18, 0, 0, 180.0f); + addTest(19, 19, 0, 0, 0.0f); + addTest(19, 19, 0, 0, 180.0f); } } diff --git a/core/src/test/java/com/google/zxing/pdf417/PDF417BlackBox4TestCase.java b/core/src/test/java/com/google/zxing/pdf417/PDF417BlackBox4TestCase.java index d15d3d1ba..d27a22e46 100644 --- a/core/src/test/java/com/google/zxing/pdf417/PDF417BlackBox4TestCase.java +++ b/core/src/test/java/com/google/zxing/pdf417/PDF417BlackBox4TestCase.java @@ -64,7 +64,7 @@ public final class PDF417BlackBox4TestCase extends AbstractBlackBoxTestCase { public PDF417BlackBox4TestCase() { 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 diff --git a/core/src/test/java/com/google/zxing/pdf417/decoder/PDF471DecoderTestCase.java b/core/src/test/java/com/google/zxing/pdf417/decoder/PDF471DecoderTestCase.java new file mode 100644 index 000000000..0d66a4028 --- /dev/null +++ b/core/src/test/java/com/google/zxing/pdf417/decoder/PDF471DecoderTestCase.java @@ -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()); + + } + +} diff --git a/core/src/test/resources/blackbox/pdf417-3/19.png b/core/src/test/resources/blackbox/pdf417-3/19.png new file mode 100644 index 000000000..fcf4b6582 Binary files /dev/null and b/core/src/test/resources/blackbox/pdf417-3/19.png differ diff --git a/core/src/test/resources/blackbox/pdf417-3/19.txt b/core/src/test/resources/blackbox/pdf417-3/19.txt new file mode 100644 index 000000000..49db7a0da --- /dev/null +++ b/core/src/test/resources/blackbox/pdf417-3/19.txt @@ -0,0 +1 @@ +0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123 \ No newline at end of file diff --git a/core/src/test/resources/blackbox/pdf417-4/04-01.png b/core/src/test/resources/blackbox/pdf417-4/04-01.png new file mode 100644 index 000000000..5a301e157 Binary files /dev/null and b/core/src/test/resources/blackbox/pdf417-4/04-01.png differ diff --git a/core/src/test/resources/blackbox/pdf417-4/04.txt b/core/src/test/resources/blackbox/pdf417-4/04.txt new file mode 100644 index 000000000..9da7d8c0d --- /dev/null +++ b/core/src/test/resources/blackbox/pdf417-4/04.txt @@ -0,0 +1 @@ +01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 \ No newline at end of file