From dd1853cab3ae2a63adcc4bc24e90e577fbecd7ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Daniel=20Mart=C3=AD?=
Date: Tue, 31 Mar 2015 17:39:07 +0200
Subject: [PATCH] Replace zxing submodule by checked in code
---
.gitmodules | 4 -
extern/zxing-core | 1 -
extern/zxing-core/.classpath | 9 +
extern/zxing-core/.project | 33 +
extern/zxing-core/AndroidManifest.xml | 5 +
extern/zxing-core/build.gradle | 3 +
extern/zxing-core/project.properties | 7 +
.../java/com/google/zxing/BarcodeFormat.java | 77 ++
.../main/java/com/google/zxing/Binarizer.java | 87 ++
.../java/com/google/zxing/BinaryBitmap.java | 150 ++++
.../com/google/zxing/ChecksumException.java | 37 +
.../java/com/google/zxing/DecodeHintType.java | 122 +++
.../main/java/com/google/zxing/Dimension.java | 62 ++
.../java/com/google/zxing/EncodeHintType.java | 86 ++
.../com/google/zxing/FormatException.java | 38 +
.../google/zxing/InvertedLuminanceSource.java | 88 ++
.../com/google/zxing/LuminanceSource.java | 157 ++++
.../com/google/zxing/MultiFormatReader.java | 180 ++++
.../com/google/zxing/MultiFormatWriter.java | 97 +++
.../com/google/zxing/NotFoundException.java | 37 +
.../zxing/PlanarYUVLuminanceSource.java | 169 ++++
.../com/google/zxing/RGBLuminanceSource.java | 142 ++++
.../main/java/com/google/zxing/Reader.java | 69 ++
.../com/google/zxing/ReaderException.java | 40 +
.../main/java/com/google/zxing/Result.java | 133 +++
.../com/google/zxing/ResultMetadataType.java | 97 +++
.../java/com/google/zxing/ResultPoint.java | 138 +++
.../com/google/zxing/ResultPointCallback.java | 29 +
.../main/java/com/google/zxing/Writer.java | 59 ++
.../com/google/zxing/WriterException.java | 38 +
.../zxing/aztec/AztecDetectorResult.java | 52 ++
.../com/google/zxing/aztec/AztecReader.java | 117 +++
.../com/google/zxing/aztec/AztecWriter.java | 88 ++
.../google/zxing/aztec/decoder/Decoder.java | 335 ++++++++
.../google/zxing/aztec/detector/Detector.java | 600 +++++++++++++
.../google/zxing/aztec/encoder/AztecCode.java | 89 ++
.../zxing/aztec/encoder/BinaryShiftToken.java | 60 ++
.../google/zxing/aztec/encoder/Encoder.java | 346 ++++++++
.../zxing/aztec/encoder/HighLevelEncoder.java | 307 +++++++
.../zxing/aztec/encoder/SimpleToken.java | 45 +
.../com/google/zxing/aztec/encoder/State.java | 169 ++++
.../com/google/zxing/aztec/encoder/Token.java | 46 +
.../result/AbstractDoCoMoResultParser.java | 39 +
.../result/AddressBookAUResultParser.java | 91 ++
.../result/AddressBookDoCoMoResultParser.java | 92 ++
.../result/AddressBookParsedResult.java | 208 +++++
.../client/result/BizcardResultParser.java | 100 +++
.../result/BookmarkDoCoMoResultParser.java | 41 +
.../client/result/CalendarParsedResult.java | 246 ++++++
.../result/EmailAddressParsedResult.java | 65 ++
.../result/EmailAddressResultParser.java | 63 ++
.../result/EmailDoCoMoResultParser.java | 63 ++
.../result/ExpandedProductParsedResult.java | 204 +++++
.../result/ExpandedProductResultParser.java | 218 +++++
.../zxing/client/result/GeoParsedResult.java | 101 +++
.../zxing/client/result/GeoResultParser.java | 73 ++
.../zxing/client/result/ISBNParsedResult.java | 40 +
.../zxing/client/result/ISBNResultParser.java | 50 ++
.../zxing/client/result/ParsedResult.java | 67 ++
.../zxing/client/result/ParsedResultType.java | 40 +
.../client/result/ProductParsedResult.java | 50 ++
.../client/result/ProductResultParser.java | 55 ++
.../zxing/client/result/ResultParser.java | 247 ++++++
.../client/result/SMSMMSResultParser.java | 109 +++
.../zxing/client/result/SMSParsedResult.java | 111 +++
.../client/result/SMSTOMMSTOResultParser.java | 52 ++
.../zxing/client/result/SMTPResultParser.java | 51 ++
.../zxing/client/result/TelParsedResult.java | 55 ++
.../zxing/client/result/TelResultParser.java | 42 +
.../zxing/client/result/TextParsedResult.java | 49 ++
.../zxing/client/result/URIParsedResult.java | 92 ++
.../zxing/client/result/URIResultParser.java | 62 ++
.../client/result/URLTOResultParser.java | 45 +
.../client/result/VCardResultParser.java | 357 ++++++++
.../client/result/VEventResultParser.java | 119 +++
.../zxing/client/result/VINParsedResult.java | 104 +++
.../zxing/client/result/VINResultParser.java | 209 +++++
.../zxing/client/result/WifiParsedResult.java | 67 ++
.../zxing/client/result/WifiResultParser.java | 51 ++
.../com/google/zxing/common/BitArray.java | 375 +++++++++
.../com/google/zxing/common/BitMatrix.java | 335 ++++++++
.../com/google/zxing/common/BitSource.java | 111 +++
.../google/zxing/common/CharacterSetECI.java | 118 +++
.../google/zxing/common/DecoderResult.java | 113 +++
.../zxing/common/DefaultGridSampler.java | 88 ++
.../google/zxing/common/DetectorResult.java | 46 +
.../common/GlobalHistogramBinarizer.java | 196 +++++
.../com/google/zxing/common/GridSampler.java | 173 ++++
.../google/zxing/common/HybridBinarizer.java | 237 ++++++
.../zxing/common/PerspectiveTransform.java | 156 ++++
.../com/google/zxing/common/StringUtils.java | 213 +++++
.../zxing/common/detector/MathUtils.java | 47 ++
.../detector/MonochromeRectangleDetector.java | 215 +++++
.../detector/WhiteRectangleDetector.java | 342 ++++++++
.../zxing/common/reedsolomon/GenericGF.java | 196 +++++
.../common/reedsolomon/GenericGFPoly.java | 264 ++++++
.../reedsolomon/ReedSolomonDecoder.java | 190 +++++
.../reedsolomon/ReedSolomonEncoder.java | 74 ++
.../reedsolomon/ReedSolomonException.java | 31 +
.../zxing/datamatrix/DataMatrixReader.java | 161 ++++
.../zxing/datamatrix/DataMatrixWriter.java | 178 ++++
.../datamatrix/decoder/BitMatrixParser.java | 440 ++++++++++
.../zxing/datamatrix/decoder/DataBlock.java | 117 +++
.../decoder/DecodedBitStreamParser.java | 494 +++++++++++
.../zxing/datamatrix/decoder/Decoder.java | 136 +++
.../zxing/datamatrix/decoder/Version.java | 237 ++++++
.../zxing/datamatrix/detector/Detector.java | 440 ++++++++++
.../datamatrix/encoder/ASCIIEncoder.java | 82 ++
.../datamatrix/encoder/Base256Encoder.java | 74 ++
.../zxing/datamatrix/encoder/C40Encoder.java | 180 ++++
.../encoder/DataMatrixSymbolInfo144.java | 35 +
.../datamatrix/encoder/DefaultPlacement.java | 198 +++++
.../datamatrix/encoder/EdifactEncoder.java | 137 +++
.../zxing/datamatrix/encoder/Encoder.java | 25 +
.../datamatrix/encoder/EncoderContext.java | 134 +++
.../datamatrix/encoder/ErrorCorrection.java | 184 ++++
.../datamatrix/encoder/HighLevelEncoder.java | 448 ++++++++++
.../zxing/datamatrix/encoder/SymbolInfo.java | 240 ++++++
.../datamatrix/encoder/SymbolShapeHint.java | 29 +
.../zxing/datamatrix/encoder/TextEncoder.java | 85 ++
.../zxing/datamatrix/encoder/X12Encoder.java | 88 ++
.../google/zxing/maxicode/MaxiCodeReader.java | 126 +++
.../maxicode/decoder/BitMatrixParser.java | 88 ++
.../decoder/DecodedBitStreamParser.java | 193 +++++
.../zxing/maxicode/decoder/Decoder.java | 114 +++
.../google/zxing/multi/ByQuadrantReader.java | 100 +++
.../multi/GenericMultipleBarcodeReader.java | 170 ++++
.../zxing/multi/MultipleBarcodeReader.java | 39 +
.../zxing/multi/qrcode/QRCodeMultiReader.java | 181 ++++
.../multi/qrcode/detector/MultiDetector.java | 73 ++
.../detector/MultiFinderPatternFinder.java | 311 +++++++
.../com/google/zxing/oned/CodaBarReader.java | 346 ++++++++
.../com/google/zxing/oned/CodaBarWriter.java | 118 +++
.../com/google/zxing/oned/Code128Reader.java | 539 ++++++++++++
.../com/google/zxing/oned/Code128Writer.java | 200 +++++
.../com/google/zxing/oned/Code39Reader.java | 323 +++++++
.../com/google/zxing/oned/Code39Writer.java | 89 ++
.../com/google/zxing/oned/Code93Reader.java | 281 +++++++
.../com/google/zxing/oned/EAN13Reader.java | 138 +++
.../com/google/zxing/oned/EAN13Writer.java | 94 +++
.../com/google/zxing/oned/EAN8Reader.java | 75 ++
.../com/google/zxing/oned/EAN8Writer.java | 84 ++
.../zxing/oned/EANManufacturerOrgSupport.java | 171 ++++
.../java/com/google/zxing/oned/ITFReader.java | 358 ++++++++
.../java/com/google/zxing/oned/ITFWriter.java | 76 ++
.../zxing/oned/MultiFormatOneDReader.java | 112 +++
.../zxing/oned/MultiFormatUPCEANReader.java | 124 +++
.../com/google/zxing/oned/OneDReader.java | 305 +++++++
.../zxing/oned/OneDimensionalCodeWriter.java | 132 +++
.../com/google/zxing/oned/UPCAReader.java | 86 ++
.../com/google/zxing/oned/UPCAWriter.java | 73 ++
.../zxing/oned/UPCEANExtension2Support.java | 112 +++
.../zxing/oned/UPCEANExtension5Support.java | 181 ++++
.../zxing/oned/UPCEANExtensionSupport.java | 40 +
.../com/google/zxing/oned/UPCEANReader.java | 395 +++++++++
.../com/google/zxing/oned/UPCEANWriter.java | 34 +
.../com/google/zxing/oned/UPCEReader.java | 155 ++++
.../zxing/oned/rss/AbstractRSSReader.java | 133 +++
.../google/zxing/oned/rss/DataCharacter.java | 56 ++
.../google/zxing/oned/rss/FinderPattern.java | 62 ++
.../java/com/google/zxing/oned/rss/Pair.java | 41 +
.../google/zxing/oned/rss/RSS14Reader.java | 477 +++++++++++
.../com/google/zxing/oned/rss/RSSUtils.java | 159 ++++
.../oned/rss/expanded/BitArrayBuilder.java | 85 ++
.../zxing/oned/rss/expanded/ExpandedPair.java | 104 +++
.../zxing/oned/rss/expanded/ExpandedRow.java | 76 ++
.../oned/rss/expanded/RSSExpandedReader.java | 787 ++++++++++++++++++
.../expanded/decoders/AI013103decoder.java | 49 ++
.../expanded/decoders/AI01320xDecoder.java | 57 ++
.../expanded/decoders/AI01392xDecoder.java | 68 ++
.../expanded/decoders/AI01393xDecoder.java | 78 ++
.../expanded/decoders/AI013x0x1xDecoder.java | 109 +++
.../expanded/decoders/AI013x0xDecoder.java | 57 ++
.../expanded/decoders/AI01AndOtherAIs.java | 58 ++
.../rss/expanded/decoders/AI01decoder.java | 81 ++
.../expanded/decoders/AI01weightDecoder.java | 60 ++
.../decoders/AbstractExpandedDecoder.java | 93 +++
.../rss/expanded/decoders/AnyAIDecoder.java | 50 ++
.../expanded/decoders/BlockParsedResult.java | 54 ++
.../decoders/CurrentParsingState.java | 83 ++
.../rss/expanded/decoders/DecodedChar.java | 52 ++
.../expanded/decoders/DecodedInformation.java | 64 ++
.../rss/expanded/decoders/DecodedNumeric.java | 77 ++
.../rss/expanded/decoders/DecodedObject.java | 44 +
.../rss/expanded/decoders/FieldParser.java | 291 +++++++
.../decoders/GeneralAppIdDecoder.java | 469 +++++++++++
.../com/google/zxing/pdf417/PDF417Common.java | 482 +++++++++++
.../com/google/zxing/pdf417/PDF417Reader.java | 135 +++
.../zxing/pdf417/PDF417ResultMetadata.java | 61 ++
.../com/google/zxing/pdf417/PDF417Writer.java | 168 ++++
.../zxing/pdf417/decoder/BarcodeMetadata.java | 58 ++
.../zxing/pdf417/decoder/BarcodeValue.java | 68 ++
.../zxing/pdf417/decoder/BoundingBox.java | 178 ++++
.../google/zxing/pdf417/decoder/Codeword.java | 84 ++
.../decoder/DecodedBitStreamParser.java | 611 ++++++++++++++
.../zxing/pdf417/decoder/DetectionResult.java | 296 +++++++
.../pdf417/decoder/DetectionResultColumn.java | 96 +++
.../DetectionResultRowIndicatorColumn.java | 267 ++++++
.../pdf417/decoder/PDF417CodewordDecoder.java | 117 +++
.../pdf417/decoder/PDF417ScanningDecoder.java | 624 ++++++++++++++
.../pdf417/decoder/ec/ErrorCorrection.java | 185 ++++
.../zxing/pdf417/decoder/ec/ModulusGF.java | 112 +++
.../zxing/pdf417/decoder/ec/ModulusPoly.java | 260 ++++++
.../zxing/pdf417/detector/Detector.java | 350 ++++++++
.../pdf417/detector/PDF417DetectorResult.java | 45 +
.../zxing/pdf417/encoder/BarcodeMatrix.java | 82 ++
.../zxing/pdf417/encoder/BarcodeRow.java | 85 ++
.../zxing/pdf417/encoder/Compaction.java | 26 +
.../zxing/pdf417/encoder/Dimensions.java | 54 ++
.../google/zxing/pdf417/encoder/PDF417.java | 769 +++++++++++++++++
.../pdf417/encoder/PDF417ErrorCorrection.java | 204 +++++
.../encoder/PDF417HighLevelEncoder.java | 616 ++++++++++++++
.../com/google/zxing/qrcode/QRCodeReader.java | 215 +++++
.../com/google/zxing/qrcode/QRCodeWriter.java | 120 +++
.../zxing/qrcode/decoder/BitMatrixParser.java | 245 ++++++
.../zxing/qrcode/decoder/DataBlock.java | 122 +++
.../google/zxing/qrcode/decoder/DataMask.java | 163 ++++
.../decoder/DecodedBitStreamParser.java | 354 ++++++++
.../google/zxing/qrcode/decoder/Decoder.java | 203 +++++
.../qrcode/decoder/ErrorCorrectionLevel.java | 60 ++
.../qrcode/decoder/FormatInformation.java | 172 ++++
.../com/google/zxing/qrcode/decoder/Mode.java | 102 +++
.../qrcode/decoder/QRCodeDecoderMetaData.java | 57 ++
.../google/zxing/qrcode/decoder/Version.java | 578 +++++++++++++
.../qrcode/detector/AlignmentPattern.java | 59 ++
.../detector/AlignmentPatternFinder.java | 277 ++++++
.../zxing/qrcode/detector/Detector.java | 405 +++++++++
.../zxing/qrcode/detector/FinderPattern.java | 82 ++
.../qrcode/detector/FinderPatternFinder.java | 680 +++++++++++++++
.../qrcode/detector/FinderPatternInfo.java | 49 ++
.../zxing/qrcode/encoder/BlockPair.java | 37 +
.../zxing/qrcode/encoder/ByteMatrix.java | 98 +++
.../google/zxing/qrcode/encoder/Encoder.java | 578 +++++++++++++
.../google/zxing/qrcode/encoder/MaskUtil.java | 217 +++++
.../zxing/qrcode/encoder/MatrixUtil.java | 482 +++++++++++
.../google/zxing/qrcode/encoder/QRCode.java | 108 +++
236 files changed, 37239 insertions(+), 5 deletions(-)
delete mode 160000 extern/zxing-core
create mode 100644 extern/zxing-core/.classpath
create mode 100644 extern/zxing-core/.project
create mode 100644 extern/zxing-core/AndroidManifest.xml
create mode 100644 extern/zxing-core/build.gradle
create mode 100644 extern/zxing-core/project.properties
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/BarcodeFormat.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/Binarizer.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/BinaryBitmap.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/ChecksumException.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/DecodeHintType.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/Dimension.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/EncodeHintType.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/FormatException.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/InvertedLuminanceSource.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/LuminanceSource.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/MultiFormatReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/MultiFormatWriter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/NotFoundException.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/PlanarYUVLuminanceSource.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/RGBLuminanceSource.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/Reader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/ReaderException.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/Result.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/ResultMetadataType.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/ResultPoint.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/ResultPointCallback.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/Writer.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/WriterException.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecDetectorResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecWriter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/decoder/Decoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/detector/Detector.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/AztecCode.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/BinaryShiftToken.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/Encoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/HighLevelEncoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/SimpleToken.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/State.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/Token.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/AbstractDoCoMoResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookAUResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookDoCoMoResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/BizcardResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/BookmarkDoCoMoResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/CalendarParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailAddressParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailAddressResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/ExpandedProductParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/ExpandedProductResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/GeoParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/GeoResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/ISBNParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/ISBNResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/ParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/ParsedResultType.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/ProductParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/ProductResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/ResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSMMSResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSTOMMSTOResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/SMTPResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/TelParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/TelResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/TextParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/URIParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/URIResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/URLTOResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/VCardResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/VEventResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/VINParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/VINResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/WifiParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/client/result/WifiResultParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/BitArray.java
create mode 100755 extern/zxing-core/src/main/java/com/google/zxing/common/BitMatrix.java
create mode 100755 extern/zxing-core/src/main/java/com/google/zxing/common/BitSource.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/CharacterSetECI.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/DecoderResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/DefaultGridSampler.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/DetectorResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/GlobalHistogramBinarizer.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/GridSampler.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/HybridBinarizer.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/PerspectiveTransform.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/StringUtils.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/detector/MathUtils.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/detector/MonochromeRectangleDetector.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/detector/WhiteRectangleDetector.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonException.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/DataMatrixReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/DataMatrixWriter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/BitMatrixParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/DataBlock.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/Decoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/Version.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/detector/Detector.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/ASCIIEncoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/Base256Encoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/C40Encoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/DataMatrixSymbolInfo144.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/DefaultPlacement.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/EdifactEncoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/Encoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/EncoderContext.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/ErrorCorrection.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/HighLevelEncoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/SymbolInfo.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/SymbolShapeHint.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/TextEncoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/X12Encoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/maxicode/MaxiCodeReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/BitMatrixParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/Decoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/multi/ByQuadrantReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/multi/GenericMultipleBarcodeReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/multi/MultipleBarcodeReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/QRCodeMultiReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/detector/MultiDetector.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/CodaBarReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/CodaBarWriter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/Code128Reader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/Code128Writer.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/Code39Reader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/Code39Writer.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/Code93Reader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/EAN13Reader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/EAN13Writer.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/EAN8Reader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/EAN8Writer.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/EANManufacturerOrgSupport.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/ITFReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/ITFWriter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/MultiFormatOneDReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/MultiFormatUPCEANReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/OneDReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/OneDimensionalCodeWriter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/UPCAReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/UPCAWriter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtension2Support.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtension5Support.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtensionSupport.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANWriter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/AbstractRSSReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/DataCharacter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/FinderPattern.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/Pair.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/RSS14Reader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/RSSUtils.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/BitArrayBuilder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/ExpandedPair.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/ExpandedRow.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/RSSExpandedReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013103decoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01320xDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01392xDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01393xDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013x0x1xDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013x0xDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01AndOtherAIs.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01decoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01weightDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AbstractExpandedDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AnyAIDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/BlockParsedResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/CurrentParsingState.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedChar.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedInformation.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedNumeric.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedObject.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/FieldParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/GeneralAppIdDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Common.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Reader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417ResultMetadata.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Writer.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BarcodeMetadata.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BarcodeValue.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BoundingBox.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/Codeword.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultColumn.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultRowIndicatorColumn.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/PDF417CodewordDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/PDF417ScanningDecoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ErrorCorrection.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusGF.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusPoly.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/detector/Detector.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/detector/PDF417DetectorResult.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/BarcodeMatrix.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/BarcodeRow.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/Compaction.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/Dimensions.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417ErrorCorrection.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/QRCodeReader.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/QRCodeWriter.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/BitMatrixParser.java
create mode 100755 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DataBlock.java
create mode 100755 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Decoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/FormatInformation.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Mode.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java
create mode 100755 extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Version.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/AlignmentPattern.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/Detector.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPattern.java
create mode 100755 extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternInfo.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/BlockPair.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/ByteMatrix.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/MatrixUtil.java
create mode 100644 extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/QRCode.java
diff --git a/.gitmodules b/.gitmodules
index ae7580289..713ef074c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -38,7 +38,3 @@
path = extern/android-support-v4-preferencefragment
url = https://github.com/kolavar/android-support-v4-preferencefragment.git
ignore = dirty
-[submodule "extern/zxing-core"]
- path = extern/zxing-core
- url = https://gitlab.com/fdroid/zxing-core.git
- ignore = dirty
diff --git a/extern/zxing-core b/extern/zxing-core
deleted file mode 160000
index 6890fe32b..000000000
--- a/extern/zxing-core
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 6890fe32b4e55e936f693b217fcc03492b7325a5
diff --git a/extern/zxing-core/.classpath b/extern/zxing-core/.classpath
new file mode 100644
index 000000000..6fcaabf36
--- /dev/null
+++ b/extern/zxing-core/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/extern/zxing-core/.project b/extern/zxing-core/.project
new file mode 100644
index 000000000..c349cdf5a
--- /dev/null
+++ b/extern/zxing-core/.project
@@ -0,0 +1,33 @@
+
+
+ zxing-core
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/extern/zxing-core/AndroidManifest.xml b/extern/zxing-core/AndroidManifest.xml
new file mode 100644
index 000000000..021edce5a
--- /dev/null
+++ b/extern/zxing-core/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
diff --git a/extern/zxing-core/build.gradle b/extern/zxing-core/build.gradle
new file mode 100644
index 000000000..4d4a19643
--- /dev/null
+++ b/extern/zxing-core/build.gradle
@@ -0,0 +1,3 @@
+apply plugin: 'java'
+
+sourceCompatibility = 1.7
diff --git a/extern/zxing-core/project.properties b/extern/zxing-core/project.properties
new file mode 100644
index 000000000..9da7c8431
--- /dev/null
+++ b/extern/zxing-core/project.properties
@@ -0,0 +1,7 @@
+# Dummy value
+target=android-19
+android.library=true
+
+source.dir=src/main/java
+java.target=1.7
+java.source=1.7
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/BarcodeFormat.java b/extern/zxing-core/src/main/java/com/google/zxing/BarcodeFormat.java
new file mode 100644
index 000000000..7f6a0ef58
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/BarcodeFormat.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * Enumerates barcode formats known to this package. Please keep alphabetized.
+ *
+ * @author Sean Owen
+ */
+public enum BarcodeFormat {
+
+ /** Aztec 2D barcode format. */
+ AZTEC,
+
+ /** CODABAR 1D format. */
+ CODABAR,
+
+ /** Code 39 1D format. */
+ CODE_39,
+
+ /** Code 93 1D format. */
+ CODE_93,
+
+ /** Code 128 1D format. */
+ CODE_128,
+
+ /** Data Matrix 2D barcode format. */
+ DATA_MATRIX,
+
+ /** EAN-8 1D format. */
+ EAN_8,
+
+ /** EAN-13 1D format. */
+ EAN_13,
+
+ /** ITF (Interleaved Two of Five) 1D format. */
+ ITF,
+
+ /** MaxiCode 2D barcode format. */
+ MAXICODE,
+
+ /** PDF417 format. */
+ PDF_417,
+
+ /** QR Code 2D barcode format. */
+ QR_CODE,
+
+ /** RSS 14 */
+ RSS_14,
+
+ /** RSS EXPANDED */
+ RSS_EXPANDED,
+
+ /** UPC-A 1D format. */
+ UPC_A,
+
+ /** UPC-E 1D format. */
+ UPC_E,
+
+ /** UPC/EAN extension format. Not a stand-alone format. */
+ UPC_EAN_EXTENSION
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/Binarizer.java b/extern/zxing-core/src/main/java/com/google/zxing/Binarizer.java
new file mode 100644
index 000000000..02af0832f
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/Binarizer.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+import com.google.zxing.common.BitArray;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * This class hierarchy provides a set of methods to convert luminance data to 1 bit data.
+ * It allows the algorithm to vary polymorphically, for example allowing a very expensive
+ * thresholding technique for servers and a fast one for mobile. It also permits the implementation
+ * to vary, e.g. a JNI version for Android and a Java fallback version for other platforms.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public abstract class Binarizer {
+
+ private final LuminanceSource source;
+
+ protected Binarizer(LuminanceSource source) {
+ this.source = source;
+ }
+
+ public final LuminanceSource getLuminanceSource() {
+ return source;
+ }
+
+ /**
+ * Converts one row of luminance data to 1 bit data. May actually do the conversion, or return
+ * cached data. Callers should assume this method is expensive and call it as seldom as possible.
+ * This method is intended for decoding 1D barcodes and may choose to apply sharpening.
+ * For callers which only examine one row of pixels at a time, the same BitArray should be reused
+ * and passed in with each call for performance. However it is legal to keep more than one row
+ * at a time if needed.
+ *
+ * @param y The row to fetch, which must be in [0, bitmap height)
+ * @param row An optional preallocated array. If null or too small, it will be ignored.
+ * If used, the Binarizer will call BitArray.clear(). Always use the returned object.
+ * @return The array of bits for this row (true means black).
+ * @throws NotFoundException if row can't be binarized
+ */
+ public abstract BitArray getBlackRow(int y, BitArray row) throws NotFoundException;
+
+ /**
+ * Converts a 2D array of luminance data to 1 bit data. As above, assume this method is expensive
+ * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or
+ * may not apply sharpening. Therefore, a row from this matrix may not be identical to one
+ * fetched using getBlackRow(), so don't mix and match between them.
+ *
+ * @return The 2D array of bits for the image (true means black).
+ * @throws NotFoundException if image can't be binarized to make a matrix
+ */
+ public abstract BitMatrix getBlackMatrix() throws NotFoundException;
+
+ /**
+ * Creates a new object with the same type as this Binarizer implementation, but with pristine
+ * state. This is needed because Binarizer implementations may be stateful, e.g. keeping a cache
+ * of 1 bit data. See Effective Java for why we can't use Java's clone() method.
+ *
+ * @param source The LuminanceSource this Binarizer will operate on.
+ * @return A new concrete Binarizer implementation object.
+ */
+ public abstract Binarizer createBinarizer(LuminanceSource source);
+
+ public final int getWidth() {
+ return source.getWidth();
+ }
+
+ public final int getHeight() {
+ return source.getHeight();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/BinaryBitmap.java b/extern/zxing-core/src/main/java/com/google/zxing/BinaryBitmap.java
new file mode 100644
index 000000000..c1ef8a13e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/BinaryBitmap.java
@@ -0,0 +1,150 @@
+/*
+ * 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;
+
+import com.google.zxing.common.BitArray;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * This class is the core bitmap class used by ZXing to represent 1 bit data. Reader objects
+ * accept a BinaryBitmap and attempt to decode it.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class BinaryBitmap {
+
+ private final Binarizer binarizer;
+ private BitMatrix matrix;
+
+ public BinaryBitmap(Binarizer binarizer) {
+ if (binarizer == null) {
+ throw new IllegalArgumentException("Binarizer must be non-null.");
+ }
+ this.binarizer = binarizer;
+ }
+
+ /**
+ * @return The width of the bitmap.
+ */
+ public int getWidth() {
+ return binarizer.getWidth();
+ }
+
+ /**
+ * @return The height of the bitmap.
+ */
+ public int getHeight() {
+ return binarizer.getHeight();
+ }
+
+ /**
+ * Converts one row of luminance data to 1 bit data. May actually do the conversion, or return
+ * cached data. Callers should assume this method is expensive and call it as seldom as possible.
+ * This method is intended for decoding 1D barcodes and may choose to apply sharpening.
+ *
+ * @param y The row to fetch, which must be in [0, bitmap height)
+ * @param row An optional preallocated array. If null or too small, it will be ignored.
+ * If used, the Binarizer will call BitArray.clear(). Always use the returned object.
+ * @return The array of bits for this row (true means black).
+ * @throws NotFoundException if row can't be binarized
+ */
+ public BitArray getBlackRow(int y, BitArray row) throws NotFoundException {
+ return binarizer.getBlackRow(y, row);
+ }
+
+ /**
+ * Converts a 2D array of luminance data to 1 bit. As above, assume this method is expensive
+ * and do not call it repeatedly. This method is intended for decoding 2D barcodes and may or
+ * may not apply sharpening. Therefore, a row from this matrix may not be identical to one
+ * fetched using getBlackRow(), so don't mix and match between them.
+ *
+ * @return The 2D array of bits for the image (true means black).
+ * @throws NotFoundException if image can't be binarized to make a matrix
+ */
+ public BitMatrix getBlackMatrix() throws NotFoundException {
+ // The matrix is created on demand the first time it is requested, then cached. There are two
+ // reasons for this:
+ // 1. This work will never be done if the caller only installs 1D Reader objects, or if a
+ // 1D Reader finds a barcode before the 2D Readers run.
+ // 2. This work will only be done once even if the caller installs multiple 2D Readers.
+ if (matrix == null) {
+ matrix = binarizer.getBlackMatrix();
+ }
+ return matrix;
+ }
+
+ /**
+ * @return Whether this bitmap can be cropped.
+ */
+ public boolean isCropSupported() {
+ return binarizer.getLuminanceSource().isCropSupported();
+ }
+
+ /**
+ * Returns a new object with cropped image data. Implementations may keep a reference to the
+ * original data rather than a copy. Only callable if isCropSupported() is true.
+ *
+ * @param left The left coordinate, which must be in [0,getWidth())
+ * @param top The top coordinate, which must be in [0,getHeight())
+ * @param width The width of the rectangle to crop.
+ * @param height The height of the rectangle to crop.
+ * @return A cropped version of this object.
+ */
+ public BinaryBitmap crop(int left, int top, int width, int height) {
+ LuminanceSource newSource = binarizer.getLuminanceSource().crop(left, top, width, height);
+ return new BinaryBitmap(binarizer.createBinarizer(newSource));
+ }
+
+ /**
+ * @return Whether this bitmap supports counter-clockwise rotation.
+ */
+ public boolean isRotateSupported() {
+ return binarizer.getLuminanceSource().isRotateSupported();
+ }
+
+ /**
+ * Returns a new object with rotated image data by 90 degrees counterclockwise.
+ * Only callable if {@link #isRotateSupported()} is true.
+ *
+ * @return A rotated version of this object.
+ */
+ public BinaryBitmap rotateCounterClockwise() {
+ LuminanceSource newSource = binarizer.getLuminanceSource().rotateCounterClockwise();
+ return new BinaryBitmap(binarizer.createBinarizer(newSource));
+ }
+
+ /**
+ * Returns a new object with rotated image data by 45 degrees counterclockwise.
+ * Only callable if {@link #isRotateSupported()} is true.
+ *
+ * @return A rotated version of this object.
+ */
+ public BinaryBitmap rotateCounterClockwise45() {
+ LuminanceSource newSource = binarizer.getLuminanceSource().rotateCounterClockwise45();
+ return new BinaryBitmap(binarizer.createBinarizer(newSource));
+ }
+
+ @Override
+ public String toString() {
+ try {
+ return getBlackMatrix().toString();
+ } catch (NotFoundException e) {
+ return "";
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/ChecksumException.java b/extern/zxing-core/src/main/java/com/google/zxing/ChecksumException.java
new file mode 100644
index 000000000..dedb4be99
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/ChecksumException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * Thrown when a barcode was successfully detected and decoded, but
+ * was not returned because its checksum feature failed.
+ *
+ * @author Sean Owen
+ */
+public final class ChecksumException extends ReaderException {
+
+ private static final ChecksumException instance = new ChecksumException();
+
+ private ChecksumException() {
+ // do nothing
+ }
+
+ public static ChecksumException getChecksumInstance() {
+ return instance;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/DecodeHintType.java b/extern/zxing-core/src/main/java/com/google/zxing/DecodeHintType.java
new file mode 100644
index 000000000..a6658695e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/DecodeHintType.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2007 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;
+
+import java.util.List;
+
+/**
+ * Encapsulates a type of hint that a caller may pass to a barcode reader to help it
+ * more quickly or accurately decode it. It is up to implementations to decide what,
+ * if anything, to do with the information that is supplied.
+ *
+ * @author Sean Owen
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @see Reader#decode(BinaryBitmap,java.util.Map)
+ */
+public enum DecodeHintType {
+
+ /**
+ * Unspecified, application-specific hint. Maps to an unspecified {@link Object}.
+ */
+ OTHER(Object.class),
+
+ /**
+ * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to;
+ * use {@link Boolean#TRUE}.
+ */
+ PURE_BARCODE(Void.class),
+
+ /**
+ * Image is known to be of one of a few possible formats.
+ * Maps to a {@link List} of {@link BarcodeFormat}s.
+ */
+ POSSIBLE_FORMATS(List.class),
+
+ /**
+ * Spend more time to try to find a barcode; optimize for accuracy, not speed.
+ * Doesn't matter what it maps to; use {@link Boolean#TRUE}.
+ */
+ TRY_HARDER(Void.class),
+
+ /**
+ * Specifies what character encoding to use when decoding, where applicable (type String)
+ */
+ CHARACTER_SET(String.class),
+
+ /**
+ * Allowed lengths of encoded data -- reject anything else. Maps to an {@code int[]}.
+ */
+ ALLOWED_LENGTHS(int[].class),
+
+ /**
+ * Assume Code 39 codes employ a check digit. Doesn't matter what it maps to;
+ * use {@link Boolean#TRUE}.
+ */
+ ASSUME_CODE_39_CHECK_DIGIT(Void.class),
+
+ /**
+ * Assume the barcode is being processed as a GS1 barcode, and modify behavior as needed.
+ * For example this affects FNC1 handling for Code 128 (aka GS1-128). Doesn't matter what it maps to;
+ * use {@link Boolean#TRUE}.
+ */
+ ASSUME_GS1(Void.class),
+
+ /**
+ * If true, return the start and end digits in a Codabar barcode instead of stripping them. They
+ * are alpha, whereas the rest are numeric. By default, they are stripped, but this causes them
+ * to not be. Doesn't matter what it maps to; use {@link Boolean#TRUE}.
+ */
+ RETURN_CODABAR_START_END(Void.class),
+
+ /**
+ * The caller needs to be notified via callback when a possible {@link ResultPoint}
+ * is found. Maps to a {@link ResultPointCallback}.
+ */
+ NEED_RESULT_POINT_CALLBACK(ResultPointCallback.class),
+
+
+ /**
+ * Allowed extension lengths for EAN or UPC barcodes. Other formats will ignore this.
+ * Maps to an {@code int[]} of the allowed extension lengths, for example [2], [5], or [2, 5].
+ * If it is optional to have an extension, do not set this hint. If this is set,
+ * and a UPC or EAN barcode is found but an extension is not, then no result will be returned
+ * at all.
+ */
+ ALLOWED_EAN_EXTENSIONS(int[].class),
+
+ // End of enumeration values.
+ ;
+
+ /**
+ * Data type the hint is expecting.
+ * Among the possible values the {@link Void} stands out as being used for
+ * hints that do not expect a value to be supplied (flag hints). Such hints
+ * will possibly have their value ignored, or replaced by a
+ * {@link Boolean#TRUE}. Hint suppliers should probably use
+ * {@link Boolean#TRUE} as directed by the actual hint documentation.
+ */
+ private final Class> valueType;
+
+ DecodeHintType(Class> valueType) {
+ this.valueType = valueType;
+ }
+
+ public Class> getValueType() {
+ return valueType;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/Dimension.java b/extern/zxing-core/src/main/java/com/google/zxing/Dimension.java
new file mode 100644
index 000000000..b3a2486e6
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/Dimension.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 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;
+
+/**
+ * Simply encapsulates a width and height.
+ */
+public final class Dimension {
+
+ private final int width;
+ private final int height;
+
+ public Dimension(int width, int height) {
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException();
+ }
+ this.width = width;
+ this.height = height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof Dimension) {
+ Dimension d = (Dimension) other;
+ return width == d.width && height == d.height;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return width * 32713 + height;
+ }
+
+ @Override
+ public String toString() {
+ return width + "x" + height;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/EncodeHintType.java b/extern/zxing-core/src/main/java/com/google/zxing/EncodeHintType.java
new file mode 100644
index 000000000..5b1961937
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/EncodeHintType.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+/**
+ * These are a set of hints that you may pass to Writers to specify their behavior.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public enum EncodeHintType {
+
+ /**
+ * Specifies what degree of error correction to use, for example in QR Codes.
+ * Type depends on the encoder. For example for QR codes it's type
+ * {@link com.google.zxing.qrcode.decoder.ErrorCorrectionLevel ErrorCorrectionLevel}.
+ * For Aztec it is of type {@link Integer}, representing the minimal percentage of error correction words.
+ * Note: an Aztec symbol should have a minimum of 25% EC words.
+ */
+ ERROR_CORRECTION,
+
+ /**
+ * Specifies what character encoding to use where applicable (type {@link String})
+ */
+ CHARACTER_SET,
+
+ /**
+ * Specifies the matrix shape for Data Matrix (type {@link com.google.zxing.datamatrix.encoder.SymbolShapeHint})
+ */
+ DATA_MATRIX_SHAPE,
+
+ /**
+ * Specifies a minimum barcode size (type {@link Dimension}). Only applicable to Data Matrix now.
+ */
+ MIN_SIZE,
+
+ /**
+ * Specifies a maximum barcode size (type {@link Dimension}). Only applicable to Data Matrix now.
+ */
+ MAX_SIZE,
+
+ /**
+ * Specifies margin, in pixels, to use when generating the barcode. The meaning can vary
+ * by format; for example it controls margin before and after the barcode horizontally for
+ * most 1D formats. (Type {@link Integer}).
+ */
+ MARGIN,
+
+ /**
+ * Specifies whether to use compact mode for PDF417 (type {@link Boolean}).
+ */
+ PDF417_COMPACT,
+
+ /**
+ * Specifies what compaction mode to use for PDF417 (type
+ * {@link com.google.zxing.pdf417.encoder.Compaction Compaction}).
+ */
+ PDF417_COMPACTION,
+
+ /**
+ * Specifies the minimum and maximum number of rows and columns for PDF417 (type
+ * {@link com.google.zxing.pdf417.encoder.Dimensions Dimensions}).
+ */
+ PDF417_DIMENSIONS,
+
+ /**
+ * Specifies the required number of layers for an Aztec code:
+ * a negative number (-1, -2, -3, -4) specifies a compact Aztec code
+ * 0 indicates to use the minimum number of layers (the default)
+ * a positive number (1, 2, .. 32) specifies a normaol (non-compact) Aztec code
+ */
+ AZTEC_LAYERS,
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/FormatException.java b/extern/zxing-core/src/main/java/com/google/zxing/FormatException.java
new file mode 100644
index 000000000..6967e93de
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/FormatException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * Thrown when a barcode was successfully detected, but some aspect of
+ * the content did not conform to the barcode's format rules. This could have
+ * been due to a mis-detection.
+ *
+ * @author Sean Owen
+ */
+public final class FormatException extends ReaderException {
+
+ private static final FormatException instance = new FormatException();
+
+ private FormatException() {
+ // do nothing
+ }
+
+ public static FormatException getFormatInstance() {
+ return instance;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/InvertedLuminanceSource.java b/extern/zxing-core/src/main/java/com/google/zxing/InvertedLuminanceSource.java
new file mode 100644
index 000000000..edaa119bd
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/InvertedLuminanceSource.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 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;
+
+/**
+ * A wrapper implementation of {@link LuminanceSource} which inverts the luminances it returns -- black becomes
+ * white and vice versa, and each value becomes (255-value).
+ *
+ * @author Sean Owen
+ */
+public final class InvertedLuminanceSource extends LuminanceSource {
+
+ private final LuminanceSource delegate;
+
+ public InvertedLuminanceSource(LuminanceSource delegate) {
+ super(delegate.getWidth(), delegate.getHeight());
+ this.delegate = delegate;
+ }
+
+ @Override
+ public byte[] getRow(int y, byte[] row) {
+ row = delegate.getRow(y, row);
+ int width = getWidth();
+ for (int i = 0; i < width; i++) {
+ row[i] = (byte) (255 - (row[i] & 0xFF));
+ }
+ return row;
+ }
+
+ @Override
+ public byte[] getMatrix() {
+ byte[] matrix = delegate.getMatrix();
+ int length = getWidth() * getHeight();
+ byte[] invertedMatrix = new byte[length];
+ for (int i = 0; i < length; i++) {
+ invertedMatrix[i] = (byte) (255 - (matrix[i] & 0xFF));
+ }
+ return invertedMatrix;
+ }
+
+ @Override
+ public boolean isCropSupported() {
+ return delegate.isCropSupported();
+ }
+
+ @Override
+ public LuminanceSource crop(int left, int top, int width, int height) {
+ return new InvertedLuminanceSource(delegate.crop(left, top, width, height));
+ }
+
+ @Override
+ public boolean isRotateSupported() {
+ return delegate.isRotateSupported();
+ }
+
+ /**
+ * @return original delegate {@link LuminanceSource} since invert undoes itself
+ */
+ @Override
+ public LuminanceSource invert() {
+ return delegate;
+ }
+
+ @Override
+ public LuminanceSource rotateCounterClockwise() {
+ return new InvertedLuminanceSource(delegate.rotateCounterClockwise());
+ }
+
+ @Override
+ public LuminanceSource rotateCounterClockwise45() {
+ return new InvertedLuminanceSource(delegate.rotateCounterClockwise45());
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/LuminanceSource.java b/extern/zxing-core/src/main/java/com/google/zxing/LuminanceSource.java
new file mode 100644
index 000000000..1946d023c
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/LuminanceSource.java
@@ -0,0 +1,157 @@
+/*
+ * 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;
+
+/**
+ * The purpose of this class hierarchy is to abstract different bitmap implementations across
+ * platforms into a standard interface for requesting greyscale luminance values. The interface
+ * only provides immutable methods; therefore crop and rotation create copies. This is to ensure
+ * that one Reader does not modify the original luminance source and leave it in an unknown state
+ * for other Readers in the chain.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public abstract class LuminanceSource {
+
+ private final int width;
+ private final int height;
+
+ protected LuminanceSource(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ /**
+ * Fetches one row of luminance data from the underlying platform's bitmap. Values range from
+ * 0 (black) to 255 (white). Because Java does not have an unsigned byte type, callers will have
+ * to bitwise and with 0xff for each value. It is preferable for implementations of this method
+ * to only fetch this row rather than the whole image, since no 2D Readers may be installed and
+ * getMatrix() may never be called.
+ *
+ * @param y The row to fetch, which must be in [0,getHeight())
+ * @param row An optional preallocated array. If null or too small, it will be ignored.
+ * Always use the returned object, and ignore the .length of the array.
+ * @return An array containing the luminance data.
+ */
+ public abstract byte[] getRow(int y, byte[] row);
+
+ /**
+ * Fetches luminance data for the underlying bitmap. Values should be fetched using:
+ * {@code int luminance = array[y * width + x] & 0xff}
+ *
+ * @return A row-major 2D array of luminance values. Do not use result.length as it may be
+ * larger than width * height bytes on some platforms. Do not modify the contents
+ * of the result.
+ */
+ public abstract byte[] getMatrix();
+
+ /**
+ * @return The width of the bitmap.
+ */
+ public final int getWidth() {
+ return width;
+ }
+
+ /**
+ * @return The height of the bitmap.
+ */
+ public final int getHeight() {
+ return height;
+ }
+
+ /**
+ * @return Whether this subclass supports cropping.
+ */
+ public boolean isCropSupported() {
+ return false;
+ }
+
+ /**
+ * Returns a new object with cropped image data. Implementations may keep a reference to the
+ * original data rather than a copy. Only callable if isCropSupported() is true.
+ *
+ * @param left The left coordinate, which must be in [0,getWidth())
+ * @param top The top coordinate, which must be in [0,getHeight())
+ * @param width The width of the rectangle to crop.
+ * @param height The height of the rectangle to crop.
+ * @return A cropped version of this object.
+ */
+ public LuminanceSource crop(int left, int top, int width, int height) {
+ throw new UnsupportedOperationException("This luminance source does not support cropping.");
+ }
+
+ /**
+ * @return Whether this subclass supports counter-clockwise rotation.
+ */
+ public boolean isRotateSupported() {
+ return false;
+ }
+
+ /**
+ * @return a wrapper of this {@code LuminanceSource} which inverts the luminances it returns -- black becomes
+ * white and vice versa, and each value becomes (255-value).
+ */
+ public LuminanceSource invert() {
+ return new InvertedLuminanceSource(this);
+ }
+
+ /**
+ * Returns a new object with rotated image data by 90 degrees counterclockwise.
+ * Only callable if {@link #isRotateSupported()} is true.
+ *
+ * @return A rotated version of this object.
+ */
+ public LuminanceSource rotateCounterClockwise() {
+ throw new UnsupportedOperationException("This luminance source does not support rotation by 90 degrees.");
+ }
+
+ /**
+ * Returns a new object with rotated image data by 45 degrees counterclockwise.
+ * Only callable if {@link #isRotateSupported()} is true.
+ *
+ * @return A rotated version of this object.
+ */
+ public LuminanceSource rotateCounterClockwise45() {
+ throw new UnsupportedOperationException("This luminance source does not support rotation by 45 degrees.");
+ }
+
+ @Override
+ public final String toString() {
+ byte[] row = new byte[width];
+ StringBuilder result = new StringBuilder(height * (width + 1));
+ for (int y = 0; y < height; y++) {
+ row = getRow(y, row);
+ for (int x = 0; x < width; x++) {
+ int luminance = row[x] & 0xFF;
+ char c;
+ if (luminance < 0x40) {
+ c = '#';
+ } else if (luminance < 0x80) {
+ c = '+';
+ } else if (luminance < 0xC0) {
+ c = '.';
+ } else {
+ c = ' ';
+ }
+ result.append(c);
+ }
+ result.append('\n');
+ }
+ return result.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/MultiFormatReader.java b/extern/zxing-core/src/main/java/com/google/zxing/MultiFormatReader.java
new file mode 100644
index 000000000..fe6f4ce5a
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/MultiFormatReader.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2007 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;
+
+import com.google.zxing.aztec.AztecReader;
+import com.google.zxing.datamatrix.DataMatrixReader;
+import com.google.zxing.maxicode.MaxiCodeReader;
+import com.google.zxing.oned.MultiFormatOneDReader;
+import com.google.zxing.pdf417.PDF417Reader;
+import com.google.zxing.qrcode.QRCodeReader;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * MultiFormatReader is a convenience class and the main entry point into the library for most uses.
+ * By default it attempts to decode all barcode formats that the library supports. Optionally, you
+ * can provide a hints object to request different behavior, for example only decoding QR codes.
+ *
+ * @author Sean Owen
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class MultiFormatReader implements Reader {
+
+ private Map hints;
+ private Reader[] readers;
+
+ /**
+ * This version of decode honors the intent of Reader.decode(BinaryBitmap) in that it
+ * passes null as a hint to the decoders. However, that makes it inefficient to call repeatedly.
+ * Use setHints() followed by decodeWithState() for continuous scan applications.
+ *
+ * @param image The pixel data to decode
+ * @return The contents of the image
+ * @throws NotFoundException Any errors which occurred
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException {
+ setHints(null);
+ return decodeInternal(image);
+ }
+
+ /**
+ * Decode an image using the hints provided. Does not honor existing state.
+ *
+ * @param image The pixel data to decode
+ * @param hints The hints to use, clearing the previous state.
+ * @return The contents of the image
+ * @throws NotFoundException Any errors which occurred
+ */
+ @Override
+ public Result decode(BinaryBitmap image, Map hints) throws NotFoundException {
+ setHints(hints);
+ return decodeInternal(image);
+ }
+
+ /**
+ * Decode an image using the state set up by calling setHints() previously. Continuous scan
+ * clients will get a large speed increase by using this instead of decode().
+ *
+ * @param image The pixel data to decode
+ * @return The contents of the image
+ * @throws NotFoundException Any errors which occurred
+ */
+ public Result decodeWithState(BinaryBitmap image) throws NotFoundException {
+ // Make sure to set up the default state so we don't crash
+ if (readers == null) {
+ setHints(null);
+ }
+ return decodeInternal(image);
+ }
+
+ /**
+ * This method adds state to the MultiFormatReader. By setting the hints once, subsequent calls
+ * to decodeWithState(image) can reuse the same set of readers without reallocating memory. This
+ * is important for performance in continuous scan clients.
+ *
+ * @param hints The set of hints to use for subsequent calls to decode(image)
+ */
+ public void setHints(Map hints) {
+ this.hints = hints;
+
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ @SuppressWarnings("unchecked")
+ Collection formats =
+ hints == null ? null : (Collection) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ Collection readers = new ArrayList<>();
+ if (formats != null) {
+ boolean addOneDReader =
+ formats.contains(BarcodeFormat.UPC_A) ||
+ formats.contains(BarcodeFormat.UPC_E) ||
+ formats.contains(BarcodeFormat.EAN_13) ||
+ formats.contains(BarcodeFormat.EAN_8) ||
+ formats.contains(BarcodeFormat.CODABAR) ||
+ formats.contains(BarcodeFormat.CODE_39) ||
+ formats.contains(BarcodeFormat.CODE_93) ||
+ formats.contains(BarcodeFormat.CODE_128) ||
+ formats.contains(BarcodeFormat.ITF) ||
+ formats.contains(BarcodeFormat.RSS_14) ||
+ formats.contains(BarcodeFormat.RSS_EXPANDED);
+ // Put 1D readers upfront in "normal" mode
+ if (addOneDReader && !tryHarder) {
+ readers.add(new MultiFormatOneDReader(hints));
+ }
+ if (formats.contains(BarcodeFormat.QR_CODE)) {
+ readers.add(new QRCodeReader());
+ }
+ if (formats.contains(BarcodeFormat.DATA_MATRIX)) {
+ readers.add(new DataMatrixReader());
+ }
+ if (formats.contains(BarcodeFormat.AZTEC)) {
+ readers.add(new AztecReader());
+ }
+ if (formats.contains(BarcodeFormat.PDF_417)) {
+ readers.add(new PDF417Reader());
+ }
+ if (formats.contains(BarcodeFormat.MAXICODE)) {
+ readers.add(new MaxiCodeReader());
+ }
+ // At end in "try harder" mode
+ if (addOneDReader && tryHarder) {
+ readers.add(new MultiFormatOneDReader(hints));
+ }
+ }
+ if (readers.isEmpty()) {
+ if (!tryHarder) {
+ readers.add(new MultiFormatOneDReader(hints));
+ }
+
+ readers.add(new QRCodeReader());
+ readers.add(new DataMatrixReader());
+ readers.add(new AztecReader());
+ readers.add(new PDF417Reader());
+ readers.add(new MaxiCodeReader());
+
+ if (tryHarder) {
+ readers.add(new MultiFormatOneDReader(hints));
+ }
+ }
+ this.readers = readers.toArray(new Reader[readers.size()]);
+ }
+
+ @Override
+ public void reset() {
+ if (readers != null) {
+ for (Reader reader : readers) {
+ reader.reset();
+ }
+ }
+ }
+
+ private Result decodeInternal(BinaryBitmap image) throws NotFoundException {
+ if (readers != null) {
+ for (Reader reader : readers) {
+ try {
+ return reader.decode(image, hints);
+ } catch (ReaderException re) {
+ // continue
+ }
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/MultiFormatWriter.java b/extern/zxing-core/src/main/java/com/google/zxing/MultiFormatWriter.java
new file mode 100644
index 000000000..c980eb666
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/MultiFormatWriter.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import com.google.zxing.aztec.AztecWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.datamatrix.DataMatrixWriter;
+import com.google.zxing.oned.CodaBarWriter;
+import com.google.zxing.oned.Code128Writer;
+import com.google.zxing.oned.Code39Writer;
+import com.google.zxing.oned.EAN13Writer;
+import com.google.zxing.oned.EAN8Writer;
+import com.google.zxing.oned.ITFWriter;
+import com.google.zxing.oned.UPCAWriter;
+import com.google.zxing.pdf417.PDF417Writer;
+import com.google.zxing.qrcode.QRCodeWriter;
+
+import java.util.Map;
+
+/**
+ * This is a factory class which finds the appropriate Writer subclass for the BarcodeFormat
+ * requested and encodes the barcode with the supplied contents.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class MultiFormatWriter implements Writer {
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height) throws WriterException {
+ return encode(contents, format, width, height, null);
+ }
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width, int height,
+ Map hints) throws WriterException {
+
+ Writer writer;
+ switch (format) {
+ case EAN_8:
+ writer = new EAN8Writer();
+ break;
+ case EAN_13:
+ writer = new EAN13Writer();
+ break;
+ case UPC_A:
+ writer = new UPCAWriter();
+ break;
+ case QR_CODE:
+ writer = new QRCodeWriter();
+ break;
+ case CODE_39:
+ writer = new Code39Writer();
+ break;
+ case CODE_128:
+ writer = new Code128Writer();
+ break;
+ case ITF:
+ writer = new ITFWriter();
+ break;
+ case PDF_417:
+ writer = new PDF417Writer();
+ break;
+ case CODABAR:
+ writer = new CodaBarWriter();
+ break;
+ case DATA_MATRIX:
+ writer = new DataMatrixWriter();
+ break;
+ case AZTEC:
+ writer = new AztecWriter();
+ break;
+ default:
+ throw new IllegalArgumentException("No encoder available for format " + format);
+ }
+ return writer.encode(contents, format, width, height, hints);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/NotFoundException.java b/extern/zxing-core/src/main/java/com/google/zxing/NotFoundException.java
new file mode 100644
index 000000000..dedab8dfc
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/NotFoundException.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * Thrown when a barcode was not found in the image. It might have been
+ * partially detected but could not be confirmed.
+ *
+ * @author Sean Owen
+ */
+public final class NotFoundException extends ReaderException {
+
+ private static final NotFoundException instance = new NotFoundException();
+
+ private NotFoundException() {
+ // do nothing
+ }
+
+ public static NotFoundException getNotFoundInstance() {
+ return instance;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/PlanarYUVLuminanceSource.java b/extern/zxing-core/src/main/java/com/google/zxing/PlanarYUVLuminanceSource.java
new file mode 100644
index 000000000..2049e5930
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/PlanarYUVLuminanceSource.java
@@ -0,0 +1,169 @@
+/*
+ * 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;
+
+/**
+ * This object extends LuminanceSource around an array of YUV data returned from the camera driver,
+ * with the option to crop to a rectangle within the full data. This can be used to exclude
+ * superfluous pixels around the perimeter and speed up decoding.
+ *
+ * It works for any pixel format where the Y channel is planar and appears first, including
+ * YCbCr_420_SP and YCbCr_422_SP.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class PlanarYUVLuminanceSource extends LuminanceSource {
+
+ private static final int THUMBNAIL_SCALE_FACTOR = 2;
+
+ private final byte[] yuvData;
+ private final int dataWidth;
+ private final int dataHeight;
+ private final int left;
+ private final int top;
+
+ public PlanarYUVLuminanceSource(byte[] yuvData,
+ int dataWidth,
+ int dataHeight,
+ int left,
+ int top,
+ int width,
+ int height,
+ boolean reverseHorizontal) {
+ super(width, height);
+
+ if (left + width > dataWidth || top + height > dataHeight) {
+ throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
+ }
+
+ this.yuvData = yuvData;
+ this.dataWidth = dataWidth;
+ this.dataHeight = dataHeight;
+ this.left = left;
+ this.top = top;
+ if (reverseHorizontal) {
+ reverseHorizontal(width, height);
+ }
+ }
+
+ @Override
+ public byte[] getRow(int y, byte[] row) {
+ if (y < 0 || y >= getHeight()) {
+ throw new IllegalArgumentException("Requested row is outside the image: " + y);
+ }
+ int width = getWidth();
+ if (row == null || row.length < width) {
+ row = new byte[width];
+ }
+ int offset = (y + top) * dataWidth + left;
+ System.arraycopy(yuvData, offset, row, 0, width);
+ return row;
+ }
+
+ @Override
+ public byte[] getMatrix() {
+ int width = getWidth();
+ int height = getHeight();
+
+ // If the caller asks for the entire underlying image, save the copy and give them the
+ // original data. The docs specifically warn that result.length must be ignored.
+ if (width == dataWidth && height == dataHeight) {
+ return yuvData;
+ }
+
+ int area = width * height;
+ byte[] matrix = new byte[area];
+ int inputOffset = top * dataWidth + left;
+
+ // If the width matches the full width of the underlying data, perform a single copy.
+ if (width == dataWidth) {
+ System.arraycopy(yuvData, inputOffset, matrix, 0, area);
+ return matrix;
+ }
+
+ // Otherwise copy one cropped row at a time.
+ byte[] yuv = yuvData;
+ for (int y = 0; y < height; y++) {
+ int outputOffset = y * width;
+ System.arraycopy(yuv, inputOffset, matrix, outputOffset, width);
+ inputOffset += dataWidth;
+ }
+ return matrix;
+ }
+
+ @Override
+ public boolean isCropSupported() {
+ return true;
+ }
+
+ @Override
+ public LuminanceSource crop(int left, int top, int width, int height) {
+ return new PlanarYUVLuminanceSource(yuvData,
+ dataWidth,
+ dataHeight,
+ this.left + left,
+ this.top + top,
+ width,
+ height,
+ false);
+ }
+
+ public int[] renderThumbnail() {
+ int width = getWidth() / THUMBNAIL_SCALE_FACTOR;
+ int height = getHeight() / THUMBNAIL_SCALE_FACTOR;
+ int[] pixels = new int[width * height];
+ byte[] yuv = yuvData;
+ int inputOffset = top * dataWidth + left;
+
+ for (int y = 0; y < height; y++) {
+ int outputOffset = y * width;
+ for (int x = 0; x < width; x++) {
+ int grey = yuv[inputOffset + x * THUMBNAIL_SCALE_FACTOR] & 0xff;
+ pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
+ }
+ inputOffset += dataWidth * THUMBNAIL_SCALE_FACTOR;
+ }
+ return pixels;
+ }
+
+ /**
+ * @return width of image from {@link #renderThumbnail()}
+ */
+ public int getThumbnailWidth() {
+ return getWidth() / THUMBNAIL_SCALE_FACTOR;
+ }
+
+ /**
+ * @return height of image from {@link #renderThumbnail()}
+ */
+ public int getThumbnailHeight() {
+ return getHeight() / THUMBNAIL_SCALE_FACTOR;
+ }
+
+ private void reverseHorizontal(int width, int height) {
+ byte[] yuvData = this.yuvData;
+ for (int y = 0, rowStart = top * dataWidth + left; y < height; y++, rowStart += dataWidth) {
+ int middle = rowStart + width / 2;
+ for (int x1 = rowStart, x2 = rowStart + width - 1; x1 < middle; x1++, x2--) {
+ byte temp = yuvData[x1];
+ yuvData[x1] = yuvData[x2];
+ yuvData[x2] = temp;
+ }
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/RGBLuminanceSource.java b/extern/zxing-core/src/main/java/com/google/zxing/RGBLuminanceSource.java
new file mode 100644
index 000000000..3f4870744
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/RGBLuminanceSource.java
@@ -0,0 +1,142 @@
+/*
+ * 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;
+
+/**
+ * This class is used to help decode images from files which arrive as RGB data from
+ * an ARGB pixel array. It does not support rotation.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Betaminos
+ */
+public final class RGBLuminanceSource extends LuminanceSource {
+
+ private final byte[] luminances;
+ private final int dataWidth;
+ private final int dataHeight;
+ private final int left;
+ private final int top;
+
+ public RGBLuminanceSource(int width, int height, int[] pixels) {
+ super(width, height);
+
+ dataWidth = width;
+ dataHeight = height;
+ left = 0;
+ top = 0;
+
+ // In order to measure pure decoding speed, we convert the entire image to a greyscale array
+ // up front, which is the same as the Y channel of the YUVLuminanceSource in the real app.
+ luminances = new byte[width * height];
+ for (int y = 0; y < height; y++) {
+ int offset = y * width;
+ for (int x = 0; x < width; x++) {
+ int pixel = pixels[offset + x];
+ int r = (pixel >> 16) & 0xff;
+ int g = (pixel >> 8) & 0xff;
+ int b = pixel & 0xff;
+ if (r == g && g == b) {
+ // Image is already greyscale, so pick any channel.
+ luminances[offset + x] = (byte) r;
+ } else {
+ // Calculate luminance cheaply, favoring green.
+ luminances[offset + x] = (byte) ((r + g + g + b) >> 2);
+ }
+ }
+ }
+ }
+
+ private RGBLuminanceSource(byte[] pixels,
+ int dataWidth,
+ int dataHeight,
+ int left,
+ int top,
+ int width,
+ int height) {
+ super(width, height);
+ if (left + width > dataWidth || top + height > dataHeight) {
+ throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
+ }
+ this.luminances = pixels;
+ this.dataWidth = dataWidth;
+ this.dataHeight = dataHeight;
+ this.left = left;
+ this.top = top;
+ }
+
+ @Override
+ public byte[] getRow(int y, byte[] row) {
+ if (y < 0 || y >= getHeight()) {
+ throw new IllegalArgumentException("Requested row is outside the image: " + y);
+ }
+ int width = getWidth();
+ if (row == null || row.length < width) {
+ row = new byte[width];
+ }
+ int offset = (y + top) * dataWidth + left;
+ System.arraycopy(luminances, offset, row, 0, width);
+ return row;
+ }
+
+ @Override
+ public byte[] getMatrix() {
+ int width = getWidth();
+ int height = getHeight();
+
+ // If the caller asks for the entire underlying image, save the copy and give them the
+ // original data. The docs specifically warn that result.length must be ignored.
+ if (width == dataWidth && height == dataHeight) {
+ return luminances;
+ }
+
+ int area = width * height;
+ byte[] matrix = new byte[area];
+ int inputOffset = top * dataWidth + left;
+
+ // If the width matches the full width of the underlying data, perform a single copy.
+ if (width == dataWidth) {
+ System.arraycopy(luminances, inputOffset, matrix, 0, area);
+ return matrix;
+ }
+
+ // Otherwise copy one cropped row at a time.
+ byte[] rgb = luminances;
+ for (int y = 0; y < height; y++) {
+ int outputOffset = y * width;
+ System.arraycopy(rgb, inputOffset, matrix, outputOffset, width);
+ inputOffset += dataWidth;
+ }
+ return matrix;
+ }
+
+ @Override
+ public boolean isCropSupported() {
+ return true;
+ }
+
+ @Override
+ public LuminanceSource crop(int left, int top, int width, int height) {
+ return new RGBLuminanceSource(luminances,
+ dataWidth,
+ dataHeight,
+ this.left + left,
+ this.top + top,
+ width,
+ height);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/Reader.java b/extern/zxing-core/src/main/java/com/google/zxing/Reader.java
new file mode 100644
index 000000000..bd3732e02
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/Reader.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2007 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;
+
+import java.util.Map;
+
+/**
+ * Implementations of this interface can decode an image of a barcode in some format into
+ * the String it encodes. For example, {@link com.google.zxing.qrcode.QRCodeReader} can
+ * decode a QR code. The decoder may optionally receive hints from the caller which may help
+ * it decode more quickly or accurately.
+ *
+ * See {@link com.google.zxing.MultiFormatReader}, which attempts to determine what barcode
+ * format is present within the image as well, and then decodes it accordingly.
+ *
+ * @author Sean Owen
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public interface Reader {
+
+ /**
+ * Locates and decodes a barcode in some format within an image.
+ *
+ * @param image image of barcode to decode
+ * @return String which the barcode encodes
+ * @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
+ */
+ Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException;
+
+ /**
+ * Locates and decodes a barcode in some format within an image. This method also accepts
+ * hints, each possibly associated to some data, which may help the implementation decode.
+ *
+ * @param image image of barcode to decode
+ * @param hints passed as a {@link java.util.Map} from {@link com.google.zxing.DecodeHintType}
+ * to arbitrary data. The
+ * meaning of the data depends upon the hint type. The implementation may or may not do
+ * anything with these hints.
+ * @return String which the barcode encodes
+ * @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
+ */
+ Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, ChecksumException, FormatException;
+
+ /**
+ * Resets any internal state the implementation has after a decode, to prepare it
+ * for reuse.
+ */
+ void reset();
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/ReaderException.java b/extern/zxing-core/src/main/java/com/google/zxing/ReaderException.java
new file mode 100644
index 000000000..9bb0dd4b9
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/ReaderException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2007 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;
+
+/**
+ * The general exception class throw when something goes wrong during decoding of a barcode.
+ * This includes, but is not limited to, failing checksums / error correction algorithms, being
+ * unable to locate finder timing patterns, and so on.
+ *
+ * @author Sean Owen
+ */
+public abstract class ReaderException extends Exception {
+
+ ReaderException() {
+ // do nothing
+ }
+
+ // Prevent stack traces from being taken
+ // srowen says: huh, my IDE is saying this is not an override. native methods can't be overridden?
+ // This, at least, does not hurt. Because we use a singleton pattern here, it doesn't matter anyhow.
+ @Override
+ public final Throwable fillInStackTrace() {
+ return null;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/Result.java b/extern/zxing-core/src/main/java/com/google/zxing/Result.java
new file mode 100644
index 000000000..7c98006de
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/Result.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2007 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;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * Encapsulates the result of decoding a barcode within an image.
+ *
+ * @author Sean Owen
+ */
+public final class Result {
+
+ private final String text;
+ private final byte[] rawBytes;
+ private ResultPoint[] resultPoints;
+ private final BarcodeFormat format;
+ private Map resultMetadata;
+ private final long timestamp;
+
+ public Result(String text,
+ byte[] rawBytes,
+ ResultPoint[] resultPoints,
+ BarcodeFormat format) {
+ this(text, rawBytes, resultPoints, format, System.currentTimeMillis());
+ }
+
+ public Result(String text,
+ byte[] rawBytes,
+ ResultPoint[] resultPoints,
+ BarcodeFormat format,
+ long timestamp) {
+ this.text = text;
+ this.rawBytes = rawBytes;
+ this.resultPoints = resultPoints;
+ this.format = format;
+ this.resultMetadata = null;
+ this.timestamp = timestamp;
+ }
+
+ /**
+ * @return raw text encoded by the barcode
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * @return raw bytes encoded by the barcode, if applicable, otherwise {@code null}
+ */
+ public byte[] getRawBytes() {
+ return rawBytes;
+ }
+
+ /**
+ * @return points related to the barcode in the image. These are typically points
+ * identifying finder patterns or the corners of the barcode. The exact meaning is
+ * specific to the type of barcode that was decoded.
+ */
+ public ResultPoint[] getResultPoints() {
+ return resultPoints;
+ }
+
+ /**
+ * @return {@link BarcodeFormat} representing the format of the barcode that was decoded
+ */
+ public BarcodeFormat getBarcodeFormat() {
+ return format;
+ }
+
+ /**
+ * @return {@link Map} mapping {@link ResultMetadataType} keys to values. May be
+ * {@code null}. This contains optional metadata about what was detected about the barcode,
+ * like orientation.
+ */
+ public Map getResultMetadata() {
+ return resultMetadata;
+ }
+
+ public void putMetadata(ResultMetadataType type, Object value) {
+ if (resultMetadata == null) {
+ resultMetadata = new EnumMap<>(ResultMetadataType.class);
+ }
+ resultMetadata.put(type, value);
+ }
+
+ public void putAllMetadata(Map metadata) {
+ if (metadata != null) {
+ if (resultMetadata == null) {
+ resultMetadata = metadata;
+ } else {
+ resultMetadata.putAll(metadata);
+ }
+ }
+ }
+
+ public void addResultPoints(ResultPoint[] newPoints) {
+ ResultPoint[] oldPoints = resultPoints;
+ if (oldPoints == null) {
+ resultPoints = newPoints;
+ } else if (newPoints != null && newPoints.length > 0) {
+ ResultPoint[] allPoints = new ResultPoint[oldPoints.length + newPoints.length];
+ System.arraycopy(oldPoints, 0, allPoints, 0, oldPoints.length);
+ System.arraycopy(newPoints, 0, allPoints, oldPoints.length, newPoints.length);
+ resultPoints = allPoints;
+ }
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public String toString() {
+ return text;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/ResultMetadataType.java b/extern/zxing-core/src/main/java/com/google/zxing/ResultMetadataType.java
new file mode 100644
index 000000000..eac968b1e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/ResultMetadataType.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+/**
+ * Represents some type of metadata about the result of the decoding that the decoder
+ * wishes to communicate back to the caller.
+ *
+ * @author Sean Owen
+ */
+public enum ResultMetadataType {
+
+ /**
+ * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
+ */
+ OTHER,
+
+ /**
+ * Denotes the likely approximate orientation of the barcode in the image. This value
+ * is given as degrees rotated clockwise from the normal, upright orientation.
+ * For example a 1D barcode which was found by reading top-to-bottom would be
+ * said to have orientation "90". This key maps to an {@link Integer} whose
+ * value is in the range [0,360).
+ */
+ ORIENTATION,
+
+ /**
+ * 2D barcode formats typically encode text, but allow for a sort of 'byte mode'
+ * which is sometimes used to encode binary data. While {@link Result} makes available
+ * the complete raw bytes in the barcode for these formats, it does not offer the bytes
+ * from the byte segments alone.
+ *
+ * This maps to a {@link java.util.List} of byte arrays corresponding to the
+ * raw bytes in the byte segments in the barcode, in order.
+ */
+ BYTE_SEGMENTS,
+
+ /**
+ * Error correction level used, if applicable. The value type depends on the
+ * format, but is typically a String.
+ */
+ ERROR_CORRECTION_LEVEL,
+
+ /**
+ * For some periodicals, indicates the issue number as an {@link Integer}.
+ */
+ ISSUE_NUMBER,
+
+ /**
+ * For some products, indicates the suggested retail price in the barcode as a
+ * formatted {@link String}.
+ */
+ SUGGESTED_PRICE ,
+
+ /**
+ * For some products, the possible country of manufacture as a {@link String} denoting the
+ * ISO country code. Some map to multiple possible countries, like "US/CA".
+ */
+ POSSIBLE_COUNTRY,
+
+ /**
+ * For some products, the extension text
+ */
+ UPC_EAN_EXTENSION,
+
+ /**
+ * PDF417-specific metadata
+ */
+ PDF417_EXTRA_METADATA,
+
+ /**
+ * If the code format supports structured append and the current scanned code is part of one then the
+ * sequence number is given with it.
+ */
+ STRUCTURED_APPEND_SEQUENCE,
+
+ /**
+ * If the code format supports structured append and the current scanned code is part of one then the
+ * parity is given with it.
+ */
+ STRUCTURED_APPEND_PARITY,
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/ResultPoint.java b/extern/zxing-core/src/main/java/com/google/zxing/ResultPoint.java
new file mode 100644
index 000000000..e6a6fef40
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/ResultPoint.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2007 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;
+
+import com.google.zxing.common.detector.MathUtils;
+
+/**
+ * Encapsulates a point of interest in an image containing a barcode. Typically, this
+ * would be the location of a finder pattern or the corner of the barcode, for example.
+ *
+ * @author Sean Owen
+ */
+public class ResultPoint {
+
+ private final float x;
+ private final float y;
+
+ public ResultPoint(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public final float getX() {
+ return x;
+ }
+
+ public final float getY() {
+ return y;
+ }
+
+ @Override
+ public final boolean equals(Object other) {
+ if (other instanceof ResultPoint) {
+ ResultPoint otherPoint = (ResultPoint) other;
+ return x == otherPoint.x && y == otherPoint.y;
+ }
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return 31 * Float.floatToIntBits(x) + Float.floatToIntBits(y);
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder result = new StringBuilder(25);
+ result.append('(');
+ result.append(x);
+ result.append(',');
+ result.append(y);
+ result.append(')');
+ return result.toString();
+ }
+
+ /**
+ * Orders an array of three ResultPoints in an order [A,B,C] such that AB is less than AC
+ * and BC is less than AC, and the angle between BC and BA is less than 180 degrees.
+ *
+ * @param patterns array of three {@link ResultPoint} to order
+ */
+ public static void orderBestPatterns(ResultPoint[] patterns) {
+
+ // Find distances between pattern centers
+ float zeroOneDistance = distance(patterns[0], patterns[1]);
+ float oneTwoDistance = distance(patterns[1], patterns[2]);
+ float zeroTwoDistance = distance(patterns[0], patterns[2]);
+
+ ResultPoint pointA;
+ ResultPoint pointB;
+ ResultPoint pointC;
+ // Assume one closest to other two is B; A and C will just be guesses at first
+ if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) {
+ pointB = patterns[0];
+ pointA = patterns[1];
+ pointC = patterns[2];
+ } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) {
+ pointB = patterns[1];
+ pointA = patterns[0];
+ pointC = patterns[2];
+ } else {
+ pointB = patterns[2];
+ pointA = patterns[0];
+ pointC = patterns[1];
+ }
+
+ // Use cross product to figure out whether A and C are correct or flipped.
+ // This asks whether BC x BA has a positive z component, which is the arrangement
+ // we want for A, B, C. If it's negative, then we've got it flipped around and
+ // should swap A and C.
+ if (crossProductZ(pointA, pointB, pointC) < 0.0f) {
+ ResultPoint temp = pointA;
+ pointA = pointC;
+ pointC = temp;
+ }
+
+ patterns[0] = pointA;
+ patterns[1] = pointB;
+ patterns[2] = pointC;
+ }
+
+
+ /**
+ * @param pattern1 first pattern
+ * @param pattern2 second pattern
+ * @return distance between two points
+ */
+ public static float distance(ResultPoint pattern1, ResultPoint pattern2) {
+ return MathUtils.distance(pattern1.x, pattern1.y, pattern2.x, pattern2.y);
+ }
+
+ /**
+ * Returns the z component of the cross product between vectors BC and BA.
+ */
+ private static float crossProductZ(ResultPoint pointA,
+ ResultPoint pointB,
+ ResultPoint pointC) {
+ float bX = pointB.x;
+ float bY = pointB.y;
+ return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX));
+ }
+
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/ResultPointCallback.java b/extern/zxing-core/src/main/java/com/google/zxing/ResultPointCallback.java
new file mode 100644
index 000000000..0c85410bc
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/ResultPointCallback.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+/**
+ * Callback which is invoked when a possible result point (significant
+ * point in the barcode image such as a corner) is found.
+ *
+ * @see DecodeHintType#NEED_RESULT_POINT_CALLBACK
+ */
+public interface ResultPointCallback {
+
+ void foundPossibleResultPoint(ResultPoint point);
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/Writer.java b/extern/zxing-core/src/main/java/com/google/zxing/Writer.java
new file mode 100644
index 000000000..f405fd844
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/Writer.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * The base class for all objects which encode/generate a barcode image.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public interface Writer {
+
+ /**
+ * Encode a barcode using the default settings.
+ *
+ * @param contents The contents to encode in the barcode
+ * @param format The barcode format to generate
+ * @param width The preferred width in pixels
+ * @param height The preferred height in pixels
+ * @return {@link BitMatrix} representing encoded barcode image
+ * @throws WriterException if contents cannot be encoded legally in a format
+ */
+ BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
+ throws WriterException;
+
+ /**
+ * @param contents The contents to encode in the barcode
+ * @param format The barcode format to generate
+ * @param width The preferred width in pixels
+ * @param height The preferred height in pixels
+ * @param hints Additional parameters to supply to the encoder
+ * @return {@link BitMatrix} representing encoded barcode image
+ * @throws WriterException if contents cannot be encoded legally in a format
+ */
+ BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints)
+ throws WriterException;
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/WriterException.java b/extern/zxing-core/src/main/java/com/google/zxing/WriterException.java
new file mode 100644
index 000000000..c61800b93
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/WriterException.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * A base class which covers the range of exceptions which may occur when encoding a barcode using
+ * the Writer framework.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class WriterException extends Exception {
+
+ public WriterException() {
+ }
+
+ public WriterException(String message) {
+ super(message);
+ }
+
+ public WriterException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecDetectorResult.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecDetectorResult.java
new file mode 100644
index 000000000..f262e0e6d
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecDetectorResult.java
@@ -0,0 +1,52 @@
+/*
+ * 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.aztec;
+
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DetectorResult;
+
+public final class AztecDetectorResult extends DetectorResult {
+
+ private final boolean compact;
+ private final int nbDatablocks;
+ private final int nbLayers;
+
+ public AztecDetectorResult(BitMatrix bits,
+ ResultPoint[] points,
+ boolean compact,
+ int nbDatablocks,
+ int nbLayers) {
+ super(bits, points);
+ this.compact = compact;
+ this.nbDatablocks = nbDatablocks;
+ this.nbLayers = nbLayers;
+ }
+
+ public int getNbLayers() {
+ return nbLayers;
+ }
+
+ public int getNbDatablocks() {
+ return nbDatablocks;
+ }
+
+ public boolean isCompact() {
+ return compact;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecReader.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecReader.java
new file mode 100644
index 000000000..d56e3f6c6
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecReader.java
@@ -0,0 +1,117 @@
+/*
+ * 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.aztec;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.aztec.decoder.Decoder;
+import com.google.zxing.aztec.detector.Detector;
+import com.google.zxing.common.DecoderResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode Aztec codes in an image.
+ *
+ * @author David Olivier
+ */
+public final class AztecReader implements Reader {
+
+ /**
+ * Locates and decodes a Data Matrix code in an image.
+ *
+ * @return a String representing the content encoded by the Data Matrix code
+ * @throws NotFoundException if a Data Matrix code cannot be found
+ * @throws FormatException if a Data Matrix code cannot be decoded
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, FormatException {
+
+ NotFoundException notFoundException = null;
+ FormatException formatException = null;
+ Detector detector = new Detector(image.getBlackMatrix());
+ ResultPoint[] points = null;
+ DecoderResult decoderResult = null;
+ try {
+ AztecDetectorResult detectorResult = detector.detect(false);
+ points = detectorResult.getPoints();
+ decoderResult = new Decoder().decode(detectorResult);
+ } catch (NotFoundException e) {
+ notFoundException = e;
+ } catch (FormatException e) {
+ formatException = e;
+ }
+ if (decoderResult == null) {
+ try {
+ AztecDetectorResult detectorResult = detector.detect(true);
+ points = detectorResult.getPoints();
+ decoderResult = new Decoder().decode(detectorResult);
+ } catch (NotFoundException | FormatException e) {
+ if (notFoundException != null) {
+ throw notFoundException;
+ }
+ if (formatException != null) {
+ throw formatException;
+ }
+ throw e;
+ }
+ }
+
+ if (hints != null) {
+ ResultPointCallback rpcb = (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+ if (rpcb != null) {
+ for (ResultPoint point : points) {
+ rpcb.foundPossibleResultPoint(point);
+ }
+ }
+ }
+
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.AZTEC);
+
+ List byteSegments = decoderResult.getByteSegments();
+ if (byteSegments != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
+ }
+ String ecLevel = decoderResult.getECLevel();
+ if (ecLevel != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ // do nothing
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecWriter.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecWriter.java
new file mode 100644
index 000000000..afa73ef83
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/AztecWriter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 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.aztec;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.Writer;
+import com.google.zxing.aztec.encoder.AztecCode;
+import com.google.zxing.aztec.encoder.Encoder;
+import com.google.zxing.common.BitMatrix;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+
+public final class AztecWriter implements Writer {
+
+ private static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
+
+ @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 hints) {
+ String charset = hints == null ? null : (String) hints.get(EncodeHintType.CHARACTER_SET);
+ Number eccPercent = hints == null ? null : (Number) hints.get(EncodeHintType.ERROR_CORRECTION);
+ Number layers = hints == null ? null : (Number) hints.get(EncodeHintType.AZTEC_LAYERS);
+ return encode(contents,
+ format,
+ width,
+ height,
+ charset == null ? DEFAULT_CHARSET : Charset.forName(charset),
+ eccPercent == null ? Encoder.DEFAULT_EC_PERCENT : eccPercent.intValue(),
+ layers == null ? Encoder.DEFAULT_AZTEC_LAYERS : layers.intValue());
+ }
+
+ private static BitMatrix encode(String contents, BarcodeFormat format,
+ int width, int height,
+ Charset charset, int eccPercent, int layers) {
+ if (format != BarcodeFormat.AZTEC) {
+ throw new IllegalArgumentException("Can only encode AZTEC, but got " + format);
+ }
+ AztecCode aztec = Encoder.encode(contents.getBytes(charset), eccPercent, layers);
+ return renderResult(aztec, width, height);
+ }
+
+ private static BitMatrix renderResult(AztecCode code, int width, int height) {
+ BitMatrix input = code.getMatrix();
+ if (input == null) {
+ throw new IllegalStateException();
+ }
+ int inputWidth = input.getWidth();
+ int inputHeight = input.getHeight();
+ int outputWidth = Math.max(width, inputWidth);
+ int outputHeight = Math.max(height, inputHeight);
+
+ int multiple = Math.min(outputWidth / inputWidth, outputHeight / inputHeight);
+ int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
+ int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
+
+ BitMatrix output = new BitMatrix(outputWidth, outputHeight);
+
+ for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
+ // Write the contents of this row of the barcode
+ for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
+ if (input.get(inputX, inputY)) {
+ output.setRegion(outputX, outputY, multiple, multiple);
+ }
+ }
+ }
+ return output;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/decoder/Decoder.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/decoder/Decoder.java
new file mode 100644
index 000000000..eae9af913
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/decoder/Decoder.java
@@ -0,0 +1,335 @@
+/*
+ * 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.aztec.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.aztec.AztecDetectorResult;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+import java.util.Arrays;
+
+/**
+ * The main class which implements Aztec Code decoding -- as opposed to locating and extracting
+ * the Aztec Code from an image.
+ *
+ * @author David Olivier
+ */
+public final class Decoder {
+
+ private enum Table {
+ UPPER,
+ LOWER,
+ MIXED,
+ DIGIT,
+ PUNCT,
+ BINARY
+ }
+
+ private static final String[] UPPER_TABLE = {
+ "CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
+ "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS"
+ };
+
+ private static final String[] LOWER_TABLE = {
+ "CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
+ "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS"
+ };
+
+ private static final String[] MIXED_TABLE = {
+ "CTRL_PS", " ", "\1", "\2", "\3", "\4", "\5", "\6", "\7", "\b", "\t", "\n",
+ "\13", "\f", "\r", "\33", "\34", "\35", "\36", "\37", "@", "\\", "^", "_",
+ "`", "|", "~", "\177", "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS"
+ };
+
+ private static final String[] PUNCT_TABLE = {
+ "", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
+ "*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL"
+ };
+
+ private static final String[] DIGIT_TABLE = {
+ "CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US"
+ };
+
+ private AztecDetectorResult ddata;
+
+ public DecoderResult decode(AztecDetectorResult detectorResult) throws FormatException {
+ ddata = detectorResult;
+ BitMatrix matrix = detectorResult.getBits();
+ boolean[] rawbits = extractBits(matrix);
+ boolean[] correctedBits = correctBits(rawbits);
+ String result = getEncodedData(correctedBits);
+ return new DecoderResult(null, result, null, null);
+ }
+
+ // This method is used for testing the high-level encoder
+ public static String highLevelDecode(boolean[] correctedBits) {
+ return getEncodedData(correctedBits);
+ }
+
+ /**
+ * Gets the string encoded in the aztec code bits
+ *
+ * @return the decoded string
+ */
+ private static String getEncodedData(boolean[] correctedBits) {
+ int endIndex = correctedBits.length;
+ Table latchTable = Table.UPPER; // table most recently latched to
+ Table shiftTable = Table.UPPER; // table to use for the next read
+ StringBuilder result = new StringBuilder(20);
+ int index = 0;
+ while (index < endIndex) {
+ if (shiftTable == Table.BINARY) {
+ if (endIndex - index < 5) {
+ break;
+ }
+ int length = readCode(correctedBits, index, 5);
+ index += 5;
+ if (length == 0) {
+ if (endIndex - index < 11) {
+ break;
+ }
+ length = readCode(correctedBits, index, 11) + 31;
+ index += 11;
+ }
+ for (int charCount = 0; charCount < length; charCount++) {
+ if (endIndex - index < 8) {
+ index = endIndex; // Force outer loop to exit
+ break;
+ }
+ int code = readCode(correctedBits, index, 8);
+ result.append((char) code);
+ index += 8;
+ }
+ // Go back to whatever mode we had been in
+ shiftTable = latchTable;
+ } else {
+ int size = shiftTable == Table.DIGIT ? 4 : 5;
+ if (endIndex - index < size) {
+ break;
+ }
+ int code = readCode(correctedBits, index, size);
+ index += size;
+ String str = getCharacter(shiftTable, code);
+ if (str.startsWith("CTRL_")) {
+ // Table changes
+ shiftTable = getTable(str.charAt(5));
+ if (str.charAt(6) == 'L') {
+ latchTable = shiftTable;
+ }
+ } else {
+ result.append(str);
+ // Go back to whatever mode we had been in
+ shiftTable = latchTable;
+ }
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * gets the table corresponding to the char passed
+ */
+ private static Table getTable(char t) {
+ switch (t) {
+ case 'L':
+ return Table.LOWER;
+ case 'P':
+ return Table.PUNCT;
+ case 'M':
+ return Table.MIXED;
+ case 'D':
+ return Table.DIGIT;
+ case 'B':
+ return Table.BINARY;
+ case 'U':
+ default:
+ return Table.UPPER;
+ }
+ }
+
+ /**
+ * Gets the character (or string) corresponding to the passed code in the given table
+ *
+ * @param table the table used
+ * @param code the code of the character
+ */
+ private static String getCharacter(Table table, int code) {
+ switch (table) {
+ case UPPER:
+ return UPPER_TABLE[code];
+ case LOWER:
+ return LOWER_TABLE[code];
+ case MIXED:
+ return MIXED_TABLE[code];
+ case PUNCT:
+ return PUNCT_TABLE[code];
+ case DIGIT:
+ return DIGIT_TABLE[code];
+ default:
+ // Should not reach here.
+ throw new IllegalStateException("Bad table");
+ }
+ }
+
+ /**
+ * Performs RS error correction on an array of bits.
+ *
+ * @return the corrected array
+ * @throws FormatException if the input contains too many errors
+ */
+ private boolean[] correctBits(boolean[] rawbits) throws FormatException {
+ GenericGF gf;
+ int codewordSize;
+
+ if (ddata.getNbLayers() <= 2) {
+ codewordSize = 6;
+ gf = GenericGF.AZTEC_DATA_6;
+ } else if (ddata.getNbLayers() <= 8) {
+ codewordSize = 8;
+ gf = GenericGF.AZTEC_DATA_8;
+ } else if (ddata.getNbLayers() <= 22) {
+ codewordSize = 10;
+ gf = GenericGF.AZTEC_DATA_10;
+ } else {
+ codewordSize = 12;
+ gf = GenericGF.AZTEC_DATA_12;
+ }
+
+ int numDataCodewords = ddata.getNbDatablocks();
+ int numCodewords = rawbits.length / codewordSize;
+ int offset = rawbits.length % codewordSize;
+ int numECCodewords = numCodewords - numDataCodewords;
+
+ int[] dataWords = new int[numCodewords];
+ for (int i = 0; i < numCodewords; i++, offset += codewordSize) {
+ dataWords[i] = readCode(rawbits, offset, codewordSize);
+ }
+
+ try {
+ ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(gf);
+ rsDecoder.decode(dataWords, numECCodewords);
+ } catch (ReedSolomonException ignored) {
+ throw FormatException.getFormatInstance();
+ }
+
+ // Now perform the unstuffing operation.
+ // First, count how many bits are going to be thrown out as stuffing
+ int mask = (1 << codewordSize) - 1;
+ int stuffedBits = 0;
+ for (int i = 0; i < numDataCodewords; i++) {
+ int dataWord = dataWords[i];
+ if (dataWord == 0 || dataWord == mask) {
+ throw FormatException.getFormatInstance();
+ } else if (dataWord == 1 || dataWord == mask - 1) {
+ stuffedBits++;
+ }
+ }
+ // Now, actually unpack the bits and remove the stuffing
+ boolean[] correctedBits = new boolean[numDataCodewords * codewordSize - stuffedBits];
+ int index = 0;
+ for (int i = 0; i < numDataCodewords; i++) {
+ int dataWord = dataWords[i];
+ if (dataWord == 1 || dataWord == mask - 1) {
+ // next codewordSize-1 bits are all zeros or all ones
+ Arrays.fill(correctedBits, index, index + codewordSize - 1, dataWord > 1);
+ index += codewordSize - 1;
+ } else {
+ for (int bit = codewordSize - 1; bit >= 0; --bit) {
+ correctedBits[index++] = (dataWord & (1 << bit)) != 0;
+ }
+ }
+ }
+ return correctedBits;
+ }
+
+ /**
+ * Gets the array of bits from an Aztec Code matrix
+ *
+ * @return the array of bits
+ */
+ boolean[] extractBits(BitMatrix matrix) {
+ boolean compact = ddata.isCompact();
+ int layers = ddata.getNbLayers();
+ int baseMatrixSize = compact ? 11 + layers * 4 : 14 + layers * 4; // not including alignment lines
+ int[] alignmentMap = new int[baseMatrixSize];
+ boolean[] rawbits = new boolean[totalBitsInLayer(layers, compact)];
+
+ if (compact) {
+ for (int i = 0; i < alignmentMap.length; i++) {
+ alignmentMap[i] = i;
+ }
+ } else {
+ int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
+ int origCenter = baseMatrixSize / 2;
+ int center = matrixSize / 2;
+ for (int i = 0; i < origCenter; i++) {
+ int newOffset = i + i / 15;
+ alignmentMap[origCenter - i - 1] = center - newOffset - 1;
+ alignmentMap[origCenter + i] = center + newOffset + 1;
+ }
+ }
+ for (int i = 0, rowOffset = 0; i < layers; i++) {
+ int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
+ // The top-left most point of this layer is (not including alignment lines)
+ int low = i * 2;
+ // The bottom-right most point of this layer is (not including alignment lines)
+ int high = baseMatrixSize - 1 - low;
+ // We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
+ for (int j = 0; j < rowSize; j++) {
+ int columnOffset = j * 2;
+ for (int k = 0; k < 2; k++) {
+ // left column
+ rawbits[rowOffset + columnOffset + k] =
+ matrix.get(alignmentMap[low + k], alignmentMap[low + j]);
+ // bottom row
+ rawbits[rowOffset + 2 * rowSize + columnOffset + k] =
+ matrix.get(alignmentMap[low + j], alignmentMap[high - k]);
+ // right column
+ rawbits[rowOffset + 4 * rowSize + columnOffset + k] =
+ matrix.get(alignmentMap[high - k], alignmentMap[high - j]);
+ // top row
+ rawbits[rowOffset + 6 * rowSize + columnOffset + k] =
+ matrix.get(alignmentMap[high - j], alignmentMap[low + k]);
+ }
+ }
+ rowOffset += rowSize * 8;
+ }
+ return rawbits;
+ }
+
+ /**
+ * Reads a code of given length and at given index in an array of bits
+ */
+ private static int readCode(boolean[] rawbits, int startIndex, int length) {
+ int res = 0;
+ for (int i = startIndex; i < startIndex + length; i++) {
+ res <<= 1;
+ if (rawbits[i]) {
+ res++;
+ }
+ }
+ return res;
+ }
+
+ private static int totalBitsInLayer(int layers, boolean compact) {
+ return ((compact ? 88 : 112) + 16 * layers) * layers;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/detector/Detector.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/detector/Detector.java
new file mode 100644
index 000000000..5cc5c3669
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/detector/Detector.java
@@ -0,0 +1,600 @@
+/*
+ * 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.aztec.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.aztec.AztecDetectorResult;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.GridSampler;
+import com.google.zxing.common.detector.MathUtils;
+import com.google.zxing.common.detector.WhiteRectangleDetector;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+/**
+ * Encapsulates logic that can detect an Aztec Code in an image, even if the Aztec Code
+ * is rotated or skewed, or partially obscured.
+ *
+ * @author David Olivier
+ * @author Frank Yellin
+ */
+public final class Detector {
+
+ private final BitMatrix image;
+
+ private boolean compact;
+ private int nbLayers;
+ private int nbDataBlocks;
+ private int nbCenterLayers;
+ private int shift;
+
+ public Detector(BitMatrix image) {
+ this.image = image;
+ }
+
+ public AztecDetectorResult detect() throws NotFoundException {
+ return detect(false);
+ }
+
+ /**
+ * Detects an Aztec Code in an image.
+ *
+ * @param isMirror if true, image is a mirror-image of original
+ * @return {@link AztecDetectorResult} encapsulating results of detecting an Aztec Code
+ * @throws NotFoundException if no Aztec Code can be found
+ */
+ public AztecDetectorResult detect(boolean isMirror) throws NotFoundException {
+
+ // 1. Get the center of the aztec matrix
+ Point pCenter = getMatrixCenter();
+
+ // 2. Get the center points of the four diagonal points just outside the bull's eye
+ // [topRight, bottomRight, bottomLeft, topLeft]
+ ResultPoint[] bullsEyeCorners = getBullsEyeCorners(pCenter);
+
+ if (isMirror) {
+ ResultPoint temp = bullsEyeCorners[0];
+ bullsEyeCorners[0] = bullsEyeCorners[2];
+ bullsEyeCorners[2] = temp;
+ }
+
+ // 3. Get the size of the matrix and other parameters from the bull's eye
+ extractParameters(bullsEyeCorners);
+
+ // 4. Sample the grid
+ BitMatrix bits = sampleGrid(image,
+ bullsEyeCorners[shift % 4],
+ bullsEyeCorners[(shift + 1) % 4],
+ bullsEyeCorners[(shift + 2) % 4],
+ bullsEyeCorners[(shift + 3) % 4]);
+
+ // 5. Get the corners of the matrix.
+ ResultPoint[] corners = getMatrixCornerPoints(bullsEyeCorners);
+
+ return new AztecDetectorResult(bits, corners, compact, nbDataBlocks, nbLayers);
+ }
+
+ /**
+ * Extracts the number of data layers and data blocks from the layer around the bull's eye.
+ *
+ * @param bullsEyeCorners the array of bull's eye corners
+ * @throws NotFoundException in case of too many errors or invalid parameters
+ */
+ private void extractParameters(ResultPoint[] bullsEyeCorners) throws NotFoundException {
+ if (!isValid(bullsEyeCorners[0]) || !isValid(bullsEyeCorners[1]) ||
+ !isValid(bullsEyeCorners[2]) || !isValid(bullsEyeCorners[3])) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int length = 2 * nbCenterLayers;
+ // Get the bits around the bull's eye
+ int[] sides = {
+ sampleLine(bullsEyeCorners[0], bullsEyeCorners[1], length), // Right side
+ sampleLine(bullsEyeCorners[1], bullsEyeCorners[2], length), // Bottom
+ sampleLine(bullsEyeCorners[2], bullsEyeCorners[3], length), // Left side
+ sampleLine(bullsEyeCorners[3], bullsEyeCorners[0], length) // Top
+ };
+
+ // bullsEyeCorners[shift] is the corner of the bulls'eye that has three
+ // orientation marks.
+ // sides[shift] is the row/column that goes from the corner with three
+ // orientation marks to the corner with two.
+ shift = getRotation(sides, length);
+
+ // Flatten the parameter bits into a single 28- or 40-bit long
+ long parameterData = 0;
+ for (int i = 0; i < 4; i++) {
+ int side = sides[(shift + i) % 4];
+ if (compact) {
+ // Each side of the form ..XXXXXXX. where Xs are parameter data
+ parameterData <<= 7;
+ parameterData += (side >> 1) & 0x7F;
+ } else {
+ // Each side of the form ..XXXXX.XXXXX. where Xs are parameter data
+ parameterData <<= 10;
+ parameterData += ((side >> 2) & (0x1f << 5)) + ((side >> 1) & 0x1F);
+ }
+ }
+
+ // Corrects parameter data using RS. Returns just the data portion
+ // without the error correction.
+ int correctedData = getCorrectedParameterData(parameterData, compact);
+
+ if (compact) {
+ // 8 bits: 2 bits layers and 6 bits data blocks
+ nbLayers = (correctedData >> 6) + 1;
+ nbDataBlocks = (correctedData & 0x3F) + 1;
+ } else {
+ // 16 bits: 5 bits layers and 11 bits data blocks
+ nbLayers = (correctedData >> 11) + 1;
+ nbDataBlocks = (correctedData & 0x7FF) + 1;
+ }
+ }
+
+ private static final int[] EXPECTED_CORNER_BITS = {
+ 0xee0, // 07340 XXX .XX X.. ...
+ 0x1dc, // 00734 ... XXX .XX X..
+ 0x83b, // 04073 X.. ... XXX .XX
+ 0x707, // 03407 .XX X.. ... XXX
+ };
+
+ private static int getRotation(int[] sides, int length) throws NotFoundException {
+ // In a normal pattern, we expect to See
+ // ** .* D A
+ // * *
+ //
+ // . *
+ // .. .. C B
+ //
+ // Grab the 3 bits from each of the sides the form the locator pattern and concatenate
+ // into a 12-bit integer. Start with the bit at A
+ int cornerBits = 0;
+ for (int side : sides) {
+ // XX......X where X's are orientation marks
+ int t = ((side >> (length - 2)) << 1) + (side & 1);
+ cornerBits = (cornerBits << 3) + t;
+ }
+ // Mov the bottom bit to the top, so that the three bits of the locator pattern at A are
+ // together. cornerBits is now:
+ // 3 orientation bits at A || 3 orientation bits at B || ... || 3 orientation bits at D
+ cornerBits = ((cornerBits & 1) << 11) + (cornerBits >> 1);
+ // The result shift indicates which element of BullsEyeCorners[] goes into the top-left
+ // corner. Since the four rotation values have a Hamming distance of 8, we
+ // can easily tolerate two errors.
+ for (int shift = 0; shift < 4; shift++) {
+ if (Integer.bitCount(cornerBits ^ EXPECTED_CORNER_BITS[shift]) <= 2) {
+ return shift;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /**
+ * Corrects the parameter bits using Reed-Solomon algorithm.
+ *
+ * @param parameterData parameter bits
+ * @param compact true if this is a compact Aztec code
+ * @throws NotFoundException if the array contains too many errors
+ */
+ private static int getCorrectedParameterData(long parameterData, boolean compact) throws NotFoundException {
+ int numCodewords;
+ int numDataCodewords;
+
+ if (compact) {
+ numCodewords = 7;
+ numDataCodewords = 2;
+ } else {
+ numCodewords = 10;
+ numDataCodewords = 4;
+ }
+
+ int numECCodewords = numCodewords - numDataCodewords;
+ int[] parameterWords = new int[numCodewords];
+ for (int i = numCodewords - 1; i >= 0; --i) {
+ parameterWords[i] = (int) parameterData & 0xF;
+ parameterData >>= 4;
+ }
+ try {
+ ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM);
+ rsDecoder.decode(parameterWords, numECCodewords);
+ } catch (ReedSolomonException ignored) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ // Toss the error correction. Just return the data as an integer
+ int result = 0;
+ for (int i = 0; i < numDataCodewords; i++) {
+ result = (result << 4) + parameterWords[i];
+ }
+ return result;
+ }
+
+ /**
+ * Finds the corners of a bull-eye centered on the passed point.
+ * This returns the centers of the diagonal points just outside the bull's eye
+ * Returns [topRight, bottomRight, bottomLeft, topLeft]
+ *
+ * @param pCenter Center point
+ * @return The corners of the bull-eye
+ * @throws NotFoundException If no valid bull-eye can be found
+ */
+ private ResultPoint[] getBullsEyeCorners(Point pCenter) throws NotFoundException {
+
+ Point pina = pCenter;
+ Point pinb = pCenter;
+ Point pinc = pCenter;
+ Point pind = pCenter;
+
+ boolean color = true;
+
+ for (nbCenterLayers = 1; nbCenterLayers < 9; nbCenterLayers++) {
+ Point pouta = getFirstDifferent(pina, color, 1, -1);
+ Point poutb = getFirstDifferent(pinb, color, 1, 1);
+ Point poutc = getFirstDifferent(pinc, color, -1, 1);
+ Point poutd = getFirstDifferent(pind, color, -1, -1);
+
+ //d a
+ //
+ //c b
+
+ if (nbCenterLayers > 2) {
+ float q = distance(poutd, pouta) * nbCenterLayers / (distance(pind, pina) * (nbCenterLayers + 2));
+ if (q < 0.75 || q > 1.25 || !isWhiteOrBlackRectangle(pouta, poutb, poutc, poutd)) {
+ break;
+ }
+ }
+
+ pina = pouta;
+ pinb = poutb;
+ pinc = poutc;
+ pind = poutd;
+
+ color = !color;
+ }
+
+ if (nbCenterLayers != 5 && nbCenterLayers != 7) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ compact = nbCenterLayers == 5;
+
+ // Expand the square by .5 pixel in each direction so that we're on the border
+ // between the white square and the black square
+ ResultPoint pinax = new ResultPoint(pina.getX() + 0.5f, pina.getY() - 0.5f);
+ ResultPoint pinbx = new ResultPoint(pinb.getX() + 0.5f, pinb.getY() + 0.5f);
+ ResultPoint pincx = new ResultPoint(pinc.getX() - 0.5f, pinc.getY() + 0.5f);
+ ResultPoint pindx = new ResultPoint(pind.getX() - 0.5f, pind.getY() - 0.5f);
+
+ // Expand the square so that its corners are the centers of the points
+ // just outside the bull's eye.
+ return expandSquare(new ResultPoint[]{pinax, pinbx, pincx, pindx},
+ 2 * nbCenterLayers - 3,
+ 2 * nbCenterLayers);
+ }
+
+ /**
+ * Finds a candidate center point of an Aztec code from an image
+ *
+ * @return the center point
+ */
+ private Point getMatrixCenter() {
+
+ ResultPoint pointA;
+ ResultPoint pointB;
+ ResultPoint pointC;
+ ResultPoint pointD;
+
+ //Get a white rectangle that can be the border of the matrix in center bull's eye or
+ try {
+
+ ResultPoint[] cornerPoints = new WhiteRectangleDetector(image).detect();
+ pointA = cornerPoints[0];
+ pointB = cornerPoints[1];
+ pointC = cornerPoints[2];
+ pointD = cornerPoints[3];
+
+ } catch (NotFoundException e) {
+
+ // This exception can be in case the initial rectangle is white
+ // In that case, surely in the bull's eye, we try to expand the rectangle.
+ int cx = image.getWidth() / 2;
+ int cy = image.getHeight() / 2;
+ pointA = getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint();
+ pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
+ pointC = getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint();
+ pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
+
+ }
+
+ //Compute the center of the rectangle
+ int cx = MathUtils.round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX()) / 4.0f);
+ int cy = MathUtils.round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY()) / 4.0f);
+
+ // Redetermine the white rectangle starting from previously computed center.
+ // This will ensure that we end up with a white rectangle in center bull's eye
+ // in order to compute a more accurate center.
+ try {
+ ResultPoint[] cornerPoints = new WhiteRectangleDetector(image, 15, cx, cy).detect();
+ pointA = cornerPoints[0];
+ pointB = cornerPoints[1];
+ pointC = cornerPoints[2];
+ pointD = cornerPoints[3];
+ } catch (NotFoundException e) {
+ // This exception can be in case the initial rectangle is white
+ // In that case we try to expand the rectangle.
+ pointA = getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint();
+ pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
+ pointC = getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint();
+ pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
+ }
+
+ // Recompute the center of the rectangle
+ cx = MathUtils.round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX()) / 4.0f);
+ cy = MathUtils.round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY()) / 4.0f);
+
+ return new Point(cx, cy);
+ }
+
+ /**
+ * Gets the Aztec code corners from the bull's eye corners and the parameters.
+ *
+ * @param bullsEyeCorners the array of bull's eye corners
+ * @return the array of aztec code corners
+ */
+ private ResultPoint[] getMatrixCornerPoints(ResultPoint[] bullsEyeCorners) {
+ return expandSquare(bullsEyeCorners, 2 * nbCenterLayers, getDimension());
+ }
+
+ /**
+ * Creates a BitMatrix by sampling the provided image.
+ * topLeft, topRight, bottomRight, and bottomLeft are the centers of the squares on the
+ * diagonal just outside the bull's eye.
+ */
+ private BitMatrix sampleGrid(BitMatrix image,
+ ResultPoint topLeft,
+ ResultPoint topRight,
+ ResultPoint bottomRight,
+ ResultPoint bottomLeft) throws NotFoundException {
+
+ GridSampler sampler = GridSampler.getInstance();
+ int dimension = getDimension();
+
+ float low = dimension / 2.0f - nbCenterLayers;
+ float high = dimension / 2.0f + nbCenterLayers;
+
+ return sampler.sampleGrid(image,
+ dimension,
+ dimension,
+ low, low, // topleft
+ high, low, // topright
+ high, high, // bottomright
+ low, high, // bottomleft
+ topLeft.getX(), topLeft.getY(),
+ topRight.getX(), topRight.getY(),
+ bottomRight.getX(), bottomRight.getY(),
+ bottomLeft.getX(), bottomLeft.getY());
+ }
+
+ /**
+ * Samples a line.
+ *
+ * @param p1 start point (inclusive)
+ * @param p2 end point (exclusive)
+ * @param size number of bits
+ * @return the array of bits as an int (first bit is high-order bit of result)
+ */
+ private int sampleLine(ResultPoint p1, ResultPoint p2, int size) {
+ int result = 0;
+
+ float d = distance(p1, p2);
+ float moduleSize = d / size;
+ float px = p1.getX();
+ float py = p1.getY();
+ float dx = moduleSize * (p2.getX() - p1.getX()) / d;
+ float dy = moduleSize * (p2.getY() - p1.getY()) / d;
+ for (int i = 0; i < size; i++) {
+ if (image.get(MathUtils.round(px + i * dx), MathUtils.round(py + i * dy))) {
+ result |= 1 << (size - i - 1);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return true if the border of the rectangle passed in parameter is compound of white points only
+ * or black points only
+ */
+ private boolean isWhiteOrBlackRectangle(Point p1,
+ Point p2,
+ Point p3,
+ Point p4) {
+
+ int corr = 3;
+
+ p1 = new Point(p1.getX() - corr, p1.getY() + corr);
+ p2 = new Point(p2.getX() - corr, p2.getY() - corr);
+ p3 = new Point(p3.getX() + corr, p3.getY() - corr);
+ p4 = new Point(p4.getX() + corr, p4.getY() + corr);
+
+ int cInit = getColor(p4, p1);
+
+ if (cInit == 0) {
+ return false;
+ }
+
+ int c = getColor(p1, p2);
+
+ if (c != cInit) {
+ return false;
+ }
+
+ c = getColor(p2, p3);
+
+ if (c != cInit) {
+ return false;
+ }
+
+ c = getColor(p3, p4);
+
+ return c == cInit;
+
+ }
+
+ /**
+ * Gets the color of a segment
+ *
+ * @return 1 if segment more than 90% black, -1 if segment is more than 90% white, 0 else
+ */
+ private int getColor(Point p1, Point p2) {
+ float d = distance(p1, p2);
+ float dx = (p2.getX() - p1.getX()) / d;
+ float dy = (p2.getY() - p1.getY()) / d;
+ int error = 0;
+
+ float px = p1.getX();
+ float py = p1.getY();
+
+ boolean colorModel = image.get(p1.getX(), p1.getY());
+
+ for (int i = 0; i < d; i++) {
+ px += dx;
+ py += dy;
+ if (image.get(MathUtils.round(px), MathUtils.round(py)) != colorModel) {
+ error++;
+ }
+ }
+
+ float errRatio = error / d;
+
+ if (errRatio > 0.1f && errRatio < 0.9f) {
+ return 0;
+ }
+
+ return (errRatio <= 0.1f) == colorModel ? 1 : -1;
+ }
+
+ /**
+ * Gets the coordinate of the first point with a different color in the given direction
+ */
+ private Point getFirstDifferent(Point init, boolean color, int dx, int dy) {
+ int x = init.getX() + dx;
+ int y = init.getY() + dy;
+
+ while (isValid(x, y) && image.get(x, y) == color) {
+ x += dx;
+ y += dy;
+ }
+
+ x -= dx;
+ y -= dy;
+
+ while (isValid(x, y) && image.get(x, y) == color) {
+ x += dx;
+ }
+ x -= dx;
+
+ while (isValid(x, y) && image.get(x, y) == color) {
+ y += dy;
+ }
+ y -= dy;
+
+ return new Point(x, y);
+ }
+
+ /**
+ * Expand the square represented by the corner points by pushing out equally in all directions
+ *
+ * @param cornerPoints the corners of the square, which has the bull's eye at its center
+ * @param oldSide the original length of the side of the square in the target bit matrix
+ * @param newSide the new length of the size of the square in the target bit matrix
+ * @return the corners of the expanded square
+ */
+ private static ResultPoint[] expandSquare(ResultPoint[] cornerPoints, float oldSide, float newSide) {
+ float ratio = newSide / (2 * oldSide);
+ float dx = cornerPoints[0].getX() - cornerPoints[2].getX();
+ float dy = cornerPoints[0].getY() - cornerPoints[2].getY();
+ float centerx = (cornerPoints[0].getX() + cornerPoints[2].getX()) / 2.0f;
+ float centery = (cornerPoints[0].getY() + cornerPoints[2].getY()) / 2.0f;
+
+ ResultPoint result0 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy);
+ ResultPoint result2 = new ResultPoint(centerx - ratio * dx, centery - ratio * dy);
+
+ dx = cornerPoints[1].getX() - cornerPoints[3].getX();
+ dy = cornerPoints[1].getY() - cornerPoints[3].getY();
+ centerx = (cornerPoints[1].getX() + cornerPoints[3].getX()) / 2.0f;
+ centery = (cornerPoints[1].getY() + cornerPoints[3].getY()) / 2.0f;
+ ResultPoint result1 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy);
+ ResultPoint result3 = new ResultPoint(centerx - ratio * dx, centery - ratio * dy);
+
+ return new ResultPoint[]{result0, result1, result2, result3};
+ }
+
+ private boolean isValid(int x, int y) {
+ return x >= 0 && x < image.getWidth() && y > 0 && y < image.getHeight();
+ }
+
+ private boolean isValid(ResultPoint point) {
+ int x = MathUtils.round(point.getX());
+ int y = MathUtils.round(point.getY());
+ return isValid(x, y);
+ }
+
+ private static float distance(Point a, Point b) {
+ return MathUtils.distance(a.getX(), a.getY(), b.getX(), b.getY());
+ }
+
+ private static float distance(ResultPoint a, ResultPoint b) {
+ return MathUtils.distance(a.getX(), a.getY(), b.getX(), b.getY());
+ }
+
+ private int getDimension() {
+ if (compact) {
+ return 4 * nbLayers + 11;
+ }
+ if (nbLayers <= 4) {
+ return 4 * nbLayers + 15;
+ }
+ return 4 * nbLayers + 2 * ((nbLayers - 4) / 8 + 1) + 15;
+ }
+
+ static final class Point {
+ private final int x;
+ private final int y;
+
+ ResultPoint toResultPoint() {
+ return new ResultPoint(getX(), getY());
+ }
+
+ Point(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ int getX() {
+ return x;
+ }
+
+ int getY() {
+ return y;
+ }
+
+ @Override
+ public String toString() {
+ return "<" + x + ' ' + y + '>';
+ }
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/AztecCode.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/AztecCode.java
new file mode 100644
index 000000000..e813b6bfa
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/AztecCode.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013 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.aztec.encoder;
+
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * Aztec 2D code representation
+ *
+ * @author Rustam Abdullaev
+ */
+public final class AztecCode {
+
+ private boolean compact;
+ private int size;
+ private int layers;
+ private int codeWords;
+ private BitMatrix matrix;
+
+ /**
+ * @return {@code true} if compact instead of full mode
+ */
+ public boolean isCompact() {
+ return compact;
+ }
+
+ public void setCompact(boolean compact) {
+ this.compact = compact;
+ }
+
+ /**
+ * @return size in pixels (width and height)
+ */
+ public int getSize() {
+ return size;
+ }
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ /**
+ * @return number of levels
+ */
+ public int getLayers() {
+ return layers;
+ }
+
+ public void setLayers(int layers) {
+ this.layers = layers;
+ }
+
+ /**
+ * @return number of data codewords
+ */
+ public int getCodeWords() {
+ return codeWords;
+ }
+
+ public void setCodeWords(int codeWords) {
+ this.codeWords = codeWords;
+ }
+
+ /**
+ * @return the symbol image
+ */
+ public BitMatrix getMatrix() {
+ return matrix;
+ }
+
+ public void setMatrix(BitMatrix matrix) {
+ this.matrix = matrix;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/BinaryShiftToken.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/BinaryShiftToken.java
new file mode 100644
index 000000000..637ba63a5
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/BinaryShiftToken.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 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.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+
+final class BinaryShiftToken extends Token {
+
+ private final short binaryShiftStart;
+ private final short binaryShiftByteCount;
+
+ BinaryShiftToken(Token previous,
+ int binaryShiftStart,
+ int binaryShiftByteCount) {
+ super(previous);
+ this.binaryShiftStart = (short) binaryShiftStart;
+ this.binaryShiftByteCount = (short) binaryShiftByteCount;
+ }
+
+ @Override
+ public void appendTo(BitArray bitArray, byte[] text) {
+ for (int i = 0; i < binaryShiftByteCount; i++) {
+ if (i == 0 || (i == 31 && binaryShiftByteCount <= 62)) {
+ // We need a header before the first character, and before
+ // character 31 when the total byte code is <= 62
+ bitArray.appendBits(31, 5); // BINARY_SHIFT
+ if (binaryShiftByteCount > 62) {
+ bitArray.appendBits(binaryShiftByteCount - 31, 16);
+ } else if (i == 0) {
+ // 1 <= binaryShiftByteCode <= 62
+ bitArray.appendBits(Math.min(binaryShiftByteCount, 31), 5);
+ } else {
+ // 32 <= binaryShiftCount <= 62 and i == 31
+ bitArray.appendBits(binaryShiftByteCount - 31, 5);
+ }
+ }
+ bitArray.appendBits(text[binaryShiftStart + i], 8);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "<" + binaryShiftStart + "::" + (binaryShiftStart + binaryShiftByteCount - 1) + '>';
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/Encoder.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/Encoder.java
new file mode 100644
index 000000000..d4fd24c6e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/Encoder.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2013 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.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
+
+/**
+ * Generates Aztec 2D barcodes.
+ *
+ * @author Rustam Abdullaev
+ */
+public final class Encoder {
+
+ public static final int DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words
+ public static final int DEFAULT_AZTEC_LAYERS = 0;
+ private static final int MAX_NB_BITS = 32;
+ private static final int MAX_NB_BITS_COMPACT = 4;
+
+ private static final int[] WORD_SIZE = {
+ 4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12
+ };
+
+ private Encoder() {
+ }
+
+ /**
+ * Encodes the given binary content as an Aztec symbol
+ *
+ * @param data input data string
+ * @return Aztec symbol matrix with metadata
+ */
+ public static AztecCode encode(byte[] data) {
+ return encode(data, DEFAULT_EC_PERCENT, DEFAULT_AZTEC_LAYERS);
+ }
+
+ /**
+ * Encodes the given binary content as an Aztec symbol
+ *
+ * @param data input data string
+ * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
+ * a minimum of 23% + 3 words is recommended)
+ * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
+ * @return Aztec symbol matrix with metadata
+ */
+ public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers) {
+ // High-level encode
+ BitArray bits = new HighLevelEncoder(data).encode();
+
+ // stuff bits and choose symbol size
+ int eccBits = bits.getSize() * minECCPercent / 100 + 11;
+ int totalSizeBits = bits.getSize() + eccBits;
+ boolean compact;
+ int layers;
+ int totalBitsInLayer;
+ int wordSize;
+ BitArray stuffedBits;
+ if (userSpecifiedLayers != DEFAULT_AZTEC_LAYERS) {
+ compact = userSpecifiedLayers < 0;
+ layers = Math.abs(userSpecifiedLayers);
+ if (layers > (compact ? MAX_NB_BITS_COMPACT : MAX_NB_BITS)) {
+ throw new IllegalArgumentException(
+ String.format("Illegal value %s for layers", userSpecifiedLayers));
+ }
+ totalBitsInLayer = totalBitsInLayer(layers, compact);
+ wordSize = WORD_SIZE[layers];
+ int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
+ stuffedBits = stuffBits(bits, wordSize);
+ if (stuffedBits.getSize() + eccBits > usableBitsInLayers) {
+ throw new IllegalArgumentException("Data to large for user specified layer");
+ }
+ if (compact && stuffedBits.getSize() > wordSize * 64) {
+ // Compact format only allows 64 data words, though C4 can hold more words than that
+ throw new IllegalArgumentException("Data to large for user specified layer");
+ }
+ } else {
+ wordSize = 0;
+ stuffedBits = null;
+ // We look at the possible table sizes in the order Compact1, Compact2, Compact3,
+ // Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1)
+ // is the same size, but has more data.
+ for (int i = 0; ; i++) {
+ if (i > MAX_NB_BITS) {
+ throw new IllegalArgumentException("Data too large for an Aztec code");
+ }
+ compact = i <= 3;
+ layers = compact ? i + 1 : i;
+ totalBitsInLayer = totalBitsInLayer(layers, compact);
+ if (totalSizeBits > totalBitsInLayer) {
+ continue;
+ }
+ // [Re]stuff the bits if this is the first opportunity, or if the
+ // wordSize has changed
+ if (wordSize != WORD_SIZE[layers]) {
+ wordSize = WORD_SIZE[layers];
+ stuffedBits = stuffBits(bits, wordSize);
+ }
+ int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
+ if (compact && stuffedBits.getSize() > wordSize * 64) {
+ // Compact format only allows 64 data words, though C4 can hold more words than that
+ continue;
+ }
+ if (stuffedBits.getSize() + eccBits <= usableBitsInLayers) {
+ break;
+ }
+ }
+ }
+ BitArray messageBits = generateCheckWords(stuffedBits, totalBitsInLayer, wordSize);
+
+ // generate mode message
+ int messageSizeInWords = stuffedBits.getSize() / wordSize;
+ BitArray modeMessage = generateModeMessage(compact, layers, messageSizeInWords);
+
+ // allocate symbol
+ int baseMatrixSize = compact ? 11 + layers * 4 : 14 + layers * 4; // not including alignment lines
+ int[] alignmentMap = new int[baseMatrixSize];
+ int matrixSize;
+ if (compact) {
+ // no alignment marks in compact mode, alignmentMap is a no-op
+ matrixSize = baseMatrixSize;
+ for (int i = 0; i < alignmentMap.length; i++) {
+ alignmentMap[i] = i;
+ }
+ } else {
+ matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
+ int origCenter = baseMatrixSize / 2;
+ int center = matrixSize / 2;
+ for (int i = 0; i < origCenter; i++) {
+ int newOffset = i + i / 15;
+ alignmentMap[origCenter - i - 1] = center - newOffset - 1;
+ alignmentMap[origCenter + i] = center + newOffset + 1;
+ }
+ }
+ BitMatrix matrix = new BitMatrix(matrixSize);
+
+ // draw data bits
+ for (int i = 0, rowOffset = 0; i < layers; i++) {
+ int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
+ for (int j = 0; j < rowSize; j++) {
+ int columnOffset = j * 2;
+ for (int k = 0; k < 2; k++) {
+ if (messageBits.get(rowOffset + columnOffset + k)) {
+ matrix.set(alignmentMap[i * 2 + k], alignmentMap[i * 2 + j]);
+ }
+ if (messageBits.get(rowOffset + rowSize * 2 + columnOffset + k)) {
+ matrix.set(alignmentMap[i * 2 + j], alignmentMap[baseMatrixSize - 1 - i * 2 - k]);
+ }
+ if (messageBits.get(rowOffset + rowSize * 4 + columnOffset + k)) {
+ matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - k], alignmentMap[baseMatrixSize - 1 - i * 2 - j]);
+ }
+ if (messageBits.get(rowOffset + rowSize * 6 + columnOffset + k)) {
+ matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - j], alignmentMap[i * 2 + k]);
+ }
+ }
+ }
+ rowOffset += rowSize * 8;
+ }
+
+ // draw mode message
+ drawModeMessage(matrix, compact, matrixSize, modeMessage);
+
+ // draw alignment marks
+ if (compact) {
+ drawBullsEye(matrix, matrixSize / 2, 5);
+ } else {
+ drawBullsEye(matrix, matrixSize / 2, 7);
+ for (int i = 0, j = 0; i < baseMatrixSize / 2 - 1; i += 15, j += 16) {
+ for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) {
+ matrix.set(matrixSize / 2 - j, k);
+ matrix.set(matrixSize / 2 + j, k);
+ matrix.set(k, matrixSize / 2 - j);
+ matrix.set(k, matrixSize / 2 + j);
+ }
+ }
+ }
+
+ AztecCode aztec = new AztecCode();
+ aztec.setCompact(compact);
+ aztec.setSize(matrixSize);
+ aztec.setLayers(layers);
+ aztec.setCodeWords(messageSizeInWords);
+ aztec.setMatrix(matrix);
+ return aztec;
+ }
+
+ private static void drawBullsEye(BitMatrix matrix, int center, int size) {
+ for (int i = 0; i < size; i += 2) {
+ for (int j = center - i; j <= center + i; j++) {
+ matrix.set(j, center - i);
+ matrix.set(j, center + i);
+ matrix.set(center - i, j);
+ matrix.set(center + i, j);
+ }
+ }
+ matrix.set(center - size, center - size);
+ matrix.set(center - size + 1, center - size);
+ matrix.set(center - size, center - size + 1);
+ matrix.set(center + size, center - size);
+ matrix.set(center + size, center - size + 1);
+ matrix.set(center + size, center + size - 1);
+ }
+
+ static BitArray generateModeMessage(boolean compact, int layers, int messageSizeInWords) {
+ BitArray modeMessage = new BitArray();
+ if (compact) {
+ modeMessage.appendBits(layers - 1, 2);
+ modeMessage.appendBits(messageSizeInWords - 1, 6);
+ modeMessage = generateCheckWords(modeMessage, 28, 4);
+ } else {
+ modeMessage.appendBits(layers - 1, 5);
+ modeMessage.appendBits(messageSizeInWords - 1, 11);
+ modeMessage = generateCheckWords(modeMessage, 40, 4);
+ }
+ return modeMessage;
+ }
+
+ private static void drawModeMessage(BitMatrix matrix, boolean compact, int matrixSize, BitArray modeMessage) {
+ int center = matrixSize / 2;
+ if (compact) {
+ for (int i = 0; i < 7; i++) {
+ int offset = center - 3 + i;
+ if (modeMessage.get(i)) {
+ matrix.set(offset, center - 5);
+ }
+ if (modeMessage.get(i + 7)) {
+ matrix.set(center + 5, offset);
+ }
+ if (modeMessage.get(20 - i)) {
+ matrix.set(offset, center + 5);
+ }
+ if (modeMessage.get(27 - i)) {
+ matrix.set(center - 5, offset);
+ }
+ }
+ } else {
+ for (int i = 0; i < 10; i++) {
+ int offset = center - 5 + i + i / 5;
+ if (modeMessage.get(i)) {
+ matrix.set(offset, center - 7);
+ }
+ if (modeMessage.get(i + 10)) {
+ matrix.set(center + 7, offset);
+ }
+ if (modeMessage.get(29 - i)) {
+ matrix.set(offset, center + 7);
+ }
+ if (modeMessage.get(39 - i)) {
+ matrix.set(center - 7, offset);
+ }
+ }
+ }
+ }
+
+ private static BitArray generateCheckWords(BitArray bitArray, int totalBits, int wordSize) {
+ // bitArray is guaranteed to be a multiple of the wordSize, so no padding needed
+ int messageSizeInWords = bitArray.getSize() / wordSize;
+ ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
+ int totalWords = totalBits / wordSize;
+ int[] messageWords = bitsToWords(bitArray, wordSize, totalWords);
+ rs.encode(messageWords, totalWords - messageSizeInWords);
+ int startPad = totalBits % wordSize;
+ BitArray messageBits = new BitArray();
+ messageBits.appendBits(0, startPad);
+ for (int messageWord : messageWords) {
+ messageBits.appendBits(messageWord, wordSize);
+ }
+ return messageBits;
+ }
+
+ private static int[] bitsToWords(BitArray stuffedBits, int wordSize, int totalWords) {
+ int[] message = new int[totalWords];
+ int i;
+ int n;
+ for (i = 0, n = stuffedBits.getSize() / wordSize; i < n; i++) {
+ int value = 0;
+ for (int j = 0; j < wordSize; j++) {
+ value |= stuffedBits.get(i * wordSize + j) ? (1 << wordSize - j - 1) : 0;
+ }
+ message[i] = value;
+ }
+ return message;
+ }
+
+ private static GenericGF getGF(int wordSize) {
+ switch (wordSize) {
+ case 4:
+ return GenericGF.AZTEC_PARAM;
+ case 6:
+ return GenericGF.AZTEC_DATA_6;
+ case 8:
+ return GenericGF.AZTEC_DATA_8;
+ case 10:
+ return GenericGF.AZTEC_DATA_10;
+ case 12:
+ return GenericGF.AZTEC_DATA_12;
+ default:
+ return null;
+ }
+ }
+
+ static BitArray stuffBits(BitArray bits, int wordSize) {
+ BitArray out = new BitArray();
+
+ int n = bits.getSize();
+ int mask = (1 << wordSize) - 2;
+ for (int i = 0; i < n; i += wordSize) {
+ int word = 0;
+ for (int j = 0; j < wordSize; j++) {
+ if (i + j >= n || bits.get(i + j)) {
+ word |= 1 << (wordSize - 1 - j);
+ }
+ }
+ if ((word & mask) == mask) {
+ out.appendBits(word & mask, wordSize);
+ i--;
+ } else if ((word & mask) == 0) {
+ out.appendBits(word | 1, wordSize);
+ i--;
+ } else {
+ out.appendBits(word, wordSize);
+ }
+ }
+ return out;
+ }
+
+ private static int totalBitsInLayer(int layers, boolean compact) {
+ return ((compact ? 88 : 112) + 16 * layers) * layers;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/HighLevelEncoder.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/HighLevelEncoder.java
new file mode 100644
index 000000000..b4a21dd5f
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/HighLevelEncoder.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2013 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.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * This produces nearly optimal encodings of text into the first-level of
+ * encoding used by Aztec code.
+ *
+ * It uses a dynamic algorithm. For each prefix of the string, it determines
+ * a set of encodings that could lead to this prefix. We repeatedly add a
+ * character and generate a new set of optimal encodings until we have read
+ * through the entire input.
+ *
+ * @author Frank Yellin
+ * @author Rustam Abdullaev
+ */
+public final class HighLevelEncoder {
+
+ static final String[] MODE_NAMES = {"UPPER", "LOWER", "DIGIT", "MIXED", "PUNCT"};
+
+ static final int MODE_UPPER = 0; // 5 bits
+ static final int MODE_LOWER = 1; // 5 bits
+ static final int MODE_DIGIT = 2; // 4 bits
+ static final int MODE_MIXED = 3; // 5 bits
+ static final int MODE_PUNCT = 4; // 5 bits
+
+ // The Latch Table shows, for each pair of Modes, the optimal method for
+ // getting from one mode to another. In the worst possible case, this can
+ // be up to 14 bits. In the best possible case, we are already there!
+ // The high half-word of each entry gives the number of bits.
+ // The low half-word of each entry are the actual bits necessary to change
+ static final int[][] LATCH_TABLE = {
+ {
+ 0,
+ (5 << 16) + 28, // UPPER -> LOWER
+ (5 << 16) + 30, // UPPER -> DIGIT
+ (5 << 16) + 29, // UPPER -> MIXED
+ (10 << 16) + (29 << 5) + 30, // UPPER -> MIXED -> PUNCT
+ },
+ {
+ (9 << 16) + (30 << 4) + 14, // LOWER -> DIGIT -> UPPER
+ 0,
+ (5 << 16) + 30, // LOWER -> DIGIT
+ (5 << 16) + 29, // LOWER -> MIXED
+ (10 << 16) + (29 << 5) + 30, // LOWER -> MIXED -> PUNCT
+ },
+ {
+ (4 << 16) + 14, // DIGIT -> UPPER
+ (9 << 16) + (14 << 5) + 28, // DIGIT -> UPPER -> LOWER
+ 0,
+ (9 << 16) + (14 << 5) + 29, // DIGIT -> UPPER -> MIXED
+ (14 << 16) + (14 << 10) + (29 << 5) + 30,
+ // DIGIT -> UPPER -> MIXED -> PUNCT
+ },
+ {
+ (5 << 16) + 29, // MIXED -> UPPER
+ (5 << 16) + 28, // MIXED -> LOWER
+ (10 << 16) + (29 << 5) + 30, // MIXED -> UPPER -> DIGIT
+ 0,
+ (5 << 16) + 30, // MIXED -> PUNCT
+ },
+ {
+ (5 << 16) + 31, // PUNCT -> UPPER
+ (10 << 16) + (31 << 5) + 28, // PUNCT -> UPPER -> LOWER
+ (10 << 16) + (31 << 5) + 30, // PUNCT -> UPPER -> DIGIT
+ (10 << 16) + (31 << 5) + 29, // PUNCT -> UPPER -> MIXED
+ 0,
+ },
+ };
+
+ // A reverse mapping from [mode][char] to the encoding for that character
+ // in that mode. An entry of 0 indicates no mapping exists.
+ private static final int[][] CHAR_MAP = new int[5][256];
+ static {
+ CHAR_MAP[MODE_UPPER][' '] = 1;
+ for (int c = 'A'; c <= 'Z'; c++) {
+ CHAR_MAP[MODE_UPPER][c] = c - 'A' + 2;
+ }
+ CHAR_MAP[MODE_LOWER][' '] = 1;
+ for (int c = 'a'; c <= 'z'; c++) {
+ CHAR_MAP[MODE_LOWER][c] = c - 'a' + 2;
+ }
+ CHAR_MAP[MODE_DIGIT][' '] = 1;
+ for (int c = '0'; c <= '9'; c++) {
+ CHAR_MAP[MODE_DIGIT][c] = c - '0' + 2;
+ }
+ CHAR_MAP[MODE_DIGIT][','] = 12;
+ CHAR_MAP[MODE_DIGIT]['.'] = 13;
+ int[] mixedTable = {
+ '\0', ' ', '\1', '\2', '\3', '\4', '\5', '\6', '\7', '\b', '\t', '\n',
+ '\13', '\f', '\r', '\33', '\34', '\35', '\36', '\37', '@', '\\', '^',
+ '_', '`', '|', '~', '\177'
+ };
+ for (int i = 0; i < mixedTable.length; i++) {
+ CHAR_MAP[MODE_MIXED][mixedTable[i]] = i;
+ }
+ int[] punctTable = {
+ '\0', '\r', '\0', '\0', '\0', '\0', '!', '\'', '#', '$', '%', '&', '\'',
+ '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?',
+ '[', ']', '{', '}'
+ };
+ for (int i = 0; i < punctTable.length; i++) {
+ if (punctTable[i] > 0) {
+ CHAR_MAP[MODE_PUNCT][punctTable[i]] = i;
+ }
+ }
+ }
+
+ // A map showing the available shift codes. (The shifts to BINARY are not
+ // shown
+ static final int[][] SHIFT_TABLE = new int[6][6]; // mode shift codes, per table
+ static {
+ for (int[] table : SHIFT_TABLE) {
+ Arrays.fill(table, -1);
+ }
+ SHIFT_TABLE[MODE_UPPER][MODE_PUNCT] = 0;
+
+ SHIFT_TABLE[MODE_LOWER][MODE_PUNCT] = 0;
+ SHIFT_TABLE[MODE_LOWER][MODE_UPPER] = 28;
+
+ SHIFT_TABLE[MODE_MIXED][MODE_PUNCT] = 0;
+
+ SHIFT_TABLE[MODE_DIGIT][MODE_PUNCT] = 0;
+ SHIFT_TABLE[MODE_DIGIT][MODE_UPPER] = 15;
+ }
+
+ private final byte[] text;
+
+ public HighLevelEncoder(byte[] text) {
+ this.text = text;
+ }
+
+ /**
+ * @return text represented by this encoder encoded as a {@link BitArray}
+ */
+ public BitArray encode() {
+ Collection states = Collections.singletonList(State.INITIAL_STATE);
+ for (int index = 0; index < text.length; index++) {
+ int pairCode;
+ int nextChar = index + 1 < text.length ? text[index + 1] : 0;
+ switch (text[index]) {
+ case '\r':
+ pairCode = nextChar == '\n' ? 2 : 0;
+ break;
+ case '.' :
+ pairCode = nextChar == ' ' ? 3 : 0;
+ break;
+ case ',' :
+ pairCode = nextChar == ' ' ? 4 : 0;
+ break;
+ case ':' :
+ pairCode = nextChar == ' ' ? 5 : 0;
+ break;
+ default:
+ pairCode = 0;
+ }
+ if (pairCode > 0) {
+ // We have one of the four special PUNCT pairs. Treat them specially.
+ // Get a new set of states for the two new characters.
+ states = updateStateListForPair(states, index, pairCode);
+ index++;
+ } else {
+ // Get a new set of states for the new character.
+ states = updateStateListForChar(states, index);
+ }
+ }
+ // We are left with a set of states. Find the shortest one.
+ State minState = Collections.min(states, new Comparator() {
+ @Override
+ public int compare(State a, State b) {
+ return a.getBitCount() - b.getBitCount();
+ }
+ });
+ // Convert it to a bit array, and return.
+ return minState.toBitArray(text);
+ }
+
+ // We update a set of states for a new character by updating each state
+ // for the new character, merging the results, and then removing the
+ // non-optimal states.
+ private Collection updateStateListForChar(Iterable states, int index) {
+ Collection result = new LinkedList<>();
+ for (State state : states) {
+ updateStateForChar(state, index, result);
+ }
+ return simplifyStates(result);
+ }
+
+ // Return a set of states that represent the possible ways of updating this
+ // state for the next character. The resulting set of states are added to
+ // the "result" list.
+ private void updateStateForChar(State state, int index, Collection result) {
+ char ch = (char) (text[index] & 0xFF);
+ boolean charInCurrentTable = CHAR_MAP[state.getMode()][ch] > 0;
+ State stateNoBinary = null;
+ for (int mode = 0; mode <= MODE_PUNCT; mode++) {
+ int charInMode = CHAR_MAP[mode][ch];
+ if (charInMode > 0) {
+ if (stateNoBinary == null) {
+ // Only create stateNoBinary the first time it's required.
+ stateNoBinary = state.endBinaryShift(index);
+ }
+ // Try generating the character by latching to its mode
+ if (!charInCurrentTable || mode == state.getMode() || mode == MODE_DIGIT) {
+ // If the character is in the current table, we don't want to latch to
+ // any other mode except possibly digit (which uses only 4 bits). Any
+ // other latch would be equally successful *after* this character, and
+ // so wouldn't save any bits.
+ State latch_state = stateNoBinary.latchAndAppend(mode, charInMode);
+ result.add(latch_state);
+ }
+ // Try generating the character by switching to its mode.
+ if (!charInCurrentTable && SHIFT_TABLE[state.getMode()][mode] >= 0) {
+ // It never makes sense to temporarily shift to another mode if the
+ // character exists in the current mode. That can never save bits.
+ State shift_state = stateNoBinary.shiftAndAppend(mode, charInMode);
+ result.add(shift_state);
+ }
+ }
+ }
+ if (state.getBinaryShiftByteCount() > 0 || CHAR_MAP[state.getMode()][ch] == 0) {
+ // It's never worthwhile to go into binary shift mode if you're not already
+ // in binary shift mode, and the character exists in your current mode.
+ // That can never save bits over just outputting the char in the current mode.
+ State binaryState = state.addBinaryShiftChar(index);
+ result.add(binaryState);
+ }
+ }
+
+ private static Collection updateStateListForPair(Iterable states, int index, int pairCode) {
+ Collection result = new LinkedList<>();
+ for (State state : states) {
+ updateStateForPair(state, index, pairCode, result);
+ }
+ return simplifyStates(result);
+ }
+
+ private static void updateStateForPair(State state, int index, int pairCode, Collection result) {
+ State stateNoBinary = state.endBinaryShift(index);
+ // Possibility 1. Latch to MODE_PUNCT, and then append this code
+ result.add(stateNoBinary.latchAndAppend(MODE_PUNCT, pairCode));
+ if (state.getMode() != MODE_PUNCT) {
+ // Possibility 2. Shift to MODE_PUNCT, and then append this code.
+ // Every state except MODE_PUNCT (handled above) can shift
+ result.add(stateNoBinary.shiftAndAppend(MODE_PUNCT, pairCode));
+ }
+ if (pairCode == 3 || pairCode == 4) {
+ // both characters are in DIGITS. Sometimes better to just add two digits
+ State digit_state = stateNoBinary
+ .latchAndAppend(MODE_DIGIT, 16 - pairCode) // period or comma in DIGIT
+ .latchAndAppend(MODE_DIGIT, 1); // space in DIGIT
+ result.add(digit_state);
+ }
+ if (state.getBinaryShiftByteCount() > 0) {
+ // It only makes sense to do the characters as binary if we're already
+ // in binary mode.
+ State binaryState = state.addBinaryShiftChar(index).addBinaryShiftChar(index + 1);
+ result.add(binaryState);
+ }
+ }
+
+ private static Collection simplifyStates(Iterable states) {
+ List result = new LinkedList<>();
+ for (State newState : states) {
+ boolean add = true;
+ for (Iterator iterator = result.iterator(); iterator.hasNext(); ) {
+ State oldState = iterator.next();
+ if (oldState.isBetterThanOrEqualTo(newState)) {
+ add = false;
+ break;
+ }
+ if (newState.isBetterThanOrEqualTo(oldState)) {
+ iterator.remove();
+ }
+ }
+ if (add) {
+ result.add(newState);
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/SimpleToken.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/SimpleToken.java
new file mode 100644
index 000000000..047d962ad
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/SimpleToken.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 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.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+
+final class SimpleToken extends Token {
+
+ // For normal words, indicates value and bitCount
+ private final short value;
+ private final short bitCount;
+
+ SimpleToken(Token previous, int value, int bitCount) {
+ super(previous);
+ this.value = (short) value;
+ this.bitCount = (short) bitCount;
+ }
+
+ @Override
+ void appendTo(BitArray bitArray, byte[] text) {
+ bitArray.appendBits(value, bitCount);
+ }
+
+ @Override
+ public String toString() {
+ int value = this.value & ((1 << bitCount) - 1);
+ value |= 1 << bitCount;
+ return '<' + Integer.toBinaryString(value | (1 << bitCount)).substring(1) + '>';
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/State.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/State.java
new file mode 100644
index 000000000..927da63a9
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/State.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2013 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.aztec.encoder;
+
+import java.util.Deque;
+import java.util.LinkedList;
+
+import com.google.zxing.common.BitArray;
+
+/**
+ * State represents all information about a sequence necessary to generate the current output.
+ * Note that a state is immutable.
+ */
+final class State {
+
+ static final State INITIAL_STATE = new State(Token.EMPTY, HighLevelEncoder.MODE_UPPER, 0, 0);
+
+ // The current mode of the encoding (or the mode to which we'll return if
+ // we're in Binary Shift mode.
+ private final int mode;
+ // The list of tokens that we output. If we are in Binary Shift mode, this
+ // token list does *not* yet included the token for those bytes
+ private final Token token;
+ // If non-zero, the number of most recent bytes that should be output
+ // in Binary Shift mode.
+ private final int binaryShiftByteCount;
+ // The total number of bits generated (including Binary Shift).
+ private final int bitCount;
+
+ private State(Token token, int mode, int binaryBytes, int bitCount) {
+ this.token = token;
+ this.mode = mode;
+ this.binaryShiftByteCount = binaryBytes;
+ this.bitCount = bitCount;
+ // Make sure we match the token
+ //int binaryShiftBitCount = (binaryShiftByteCount * 8) +
+ // (binaryShiftByteCount == 0 ? 0 :
+ // binaryShiftByteCount <= 31 ? 10 :
+ // binaryShiftByteCount <= 62 ? 20 : 21);
+ //assert this.bitCount == token.getTotalBitCount() + binaryShiftBitCount;
+ }
+
+ int getMode() {
+ return mode;
+ }
+
+ Token getToken() {
+ return token;
+ }
+
+ int getBinaryShiftByteCount() {
+ return binaryShiftByteCount;
+ }
+
+ int getBitCount() {
+ return bitCount;
+ }
+
+ // Create a new state representing this state with a latch to a (not
+ // necessary different) mode, and then a code.
+ State latchAndAppend(int mode, int value) {
+ //assert binaryShiftByteCount == 0;
+ int bitCount = this.bitCount;
+ Token token = this.token;
+ if (mode != this.mode) {
+ int latch = HighLevelEncoder.LATCH_TABLE[this.mode][mode];
+ token = token.add(latch & 0xFFFF, latch >> 16);
+ bitCount += latch >> 16;
+ }
+ int latchModeBitCount = mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5;
+ token = token.add(value, latchModeBitCount);
+ return new State(token, mode, 0, bitCount + latchModeBitCount);
+ }
+
+ // Create a new state representing this state, with a temporary shift
+ // to a different mode to output a single value.
+ State shiftAndAppend(int mode, int value) {
+ //assert binaryShiftByteCount == 0 && this.mode != mode;
+ Token token = this.token;
+ int thisModeBitCount = this.mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5;
+ // Shifts exist only to UPPER and PUNCT, both with tokens size 5.
+ token = token.add(HighLevelEncoder.SHIFT_TABLE[this.mode][mode], thisModeBitCount);
+ token = token.add(value, 5);
+ return new State(token, this.mode, 0, this.bitCount + thisModeBitCount + 5);
+ }
+
+ // Create a new state representing this state, but an additional character
+ // output in Binary Shift mode.
+ State addBinaryShiftChar(int index) {
+ Token token = this.token;
+ int mode = this.mode;
+ int bitCount = this.bitCount;
+ if (this.mode == HighLevelEncoder.MODE_PUNCT || this.mode == HighLevelEncoder.MODE_DIGIT) {
+ //assert binaryShiftByteCount == 0;
+ int latch = HighLevelEncoder.LATCH_TABLE[mode][HighLevelEncoder.MODE_UPPER];
+ token = token.add(latch & 0xFFFF, latch >> 16);
+ bitCount += latch >> 16;
+ mode = HighLevelEncoder.MODE_UPPER;
+ }
+ int deltaBitCount =
+ (binaryShiftByteCount == 0 || binaryShiftByteCount == 31) ? 18 :
+ (binaryShiftByteCount == 62) ? 9 : 8;
+ State result = new State(token, mode, binaryShiftByteCount + 1, bitCount + deltaBitCount);
+ if (result.binaryShiftByteCount == 2047 + 31) {
+ // The string is as long as it's allowed to be. We should end it.
+ result = result.endBinaryShift(index + 1);
+ }
+ return result;
+ }
+
+ // Create the state identical to this one, but we are no longer in
+ // Binary Shift mode.
+ State endBinaryShift(int index) {
+ if (binaryShiftByteCount == 0) {
+ return this;
+ }
+ Token token = this.token;
+ token = token.addBinaryShift(index - binaryShiftByteCount, binaryShiftByteCount);
+ //assert token.getTotalBitCount() == this.bitCount;
+ return new State(token, mode, 0, this.bitCount);
+ }
+
+ // Returns true if "this" state is better (or equal) to be in than "that"
+ // state under all possible circumstances.
+ boolean isBetterThanOrEqualTo(State other) {
+ int mySize = this.bitCount + (HighLevelEncoder.LATCH_TABLE[this.mode][other.mode] >> 16);
+ if (other.binaryShiftByteCount > 0 &&
+ (this.binaryShiftByteCount == 0 || this.binaryShiftByteCount > other.binaryShiftByteCount)) {
+ mySize += 10; // Cost of entering Binary Shift mode.
+ }
+ return mySize <= other.bitCount;
+ }
+
+ BitArray toBitArray(byte[] text) {
+ // Reverse the tokens, so that they are in the order that they should
+ // be output
+ Deque symbols = new LinkedList<>();
+ for (Token token = endBinaryShift(text.length).token; token != null; token = token.getPrevious()) {
+ symbols.addFirst(token);
+ }
+ BitArray bitArray = new BitArray();
+ // Add each token to the result.
+ for (Token symbol : symbols) {
+ symbol.appendTo(bitArray, text);
+ }
+ //assert bitArray.getSize() == this.bitCount;
+ return bitArray;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s bits=%d bytes=%d", HighLevelEncoder.MODE_NAMES[mode], bitCount, binaryShiftByteCount);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/Token.java b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/Token.java
new file mode 100644
index 000000000..62d336e9c
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/aztec/encoder/Token.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 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.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+
+abstract class Token {
+
+ static final Token EMPTY = new SimpleToken(null, 0, 0);
+
+ private final Token previous;
+
+ Token(Token previous) {
+ this.previous = previous;
+ }
+
+ final Token getPrevious() {
+ return previous;
+ }
+
+ final Token add(int value, int bitCount) {
+ return new SimpleToken(this, value, bitCount);
+ }
+
+ final Token addBinaryShift(int start, int byteCount) {
+ //int bitCount = (byteCount * 8) + (byteCount <= 31 ? 10 : byteCount <= 62 ? 20 : 21);
+ return new BinaryShiftToken(this, start, byteCount);
+ }
+
+ abstract void appendTo(BitArray bitArray, byte[] text);
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/AbstractDoCoMoResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/AbstractDoCoMoResultParser.java
new file mode 100644
index 000000000..3d2c1d2a7
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/AbstractDoCoMoResultParser.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2007 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.client.result;
+
+/**
+ * See
+ *
+ * DoCoMo's documentation about the result types represented by subclasses of this class.
+ *
+ * Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
+ * on exception-based mechanisms during parsing.
+ *
+ * @author Sean Owen
+ */
+abstract class AbstractDoCoMoResultParser extends ResultParser {
+
+ static String[] matchDoCoMoPrefixedField(String prefix, String rawText, boolean trim) {
+ return matchPrefixedField(prefix, rawText, ';', trim);
+ }
+
+ static String matchSingleDoCoMoPrefixedField(String prefix, String rawText, boolean trim) {
+ return matchSinglePrefixedField(prefix, rawText, ';', trim);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookAUResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookAUResultParser.java
new file mode 100644
index 000000000..cf7f96e87
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookAUResultParser.java
@@ -0,0 +1,91 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements KDDI AU's address book format. See
+ *
+ * http://www.au.kddi.com/ezfactory/tec/two_dimensions/index.html.
+ * (Thanks to Yuzo for translating!)
+ *
+ * @author Sean Owen
+ */
+public final class AddressBookAUResultParser extends ResultParser {
+
+ @Override
+ public AddressBookParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ // MEMORY is mandatory; seems like a decent indicator, as does end-of-record separator CR/LF
+ if (!rawText.contains("MEMORY") || !rawText.contains("\r\n")) {
+ return null;
+ }
+
+ // NAME1 and NAME2 have specific uses, namely written name and pronunciation, respectively.
+ // Therefore we treat them specially instead of as an array of names.
+ String name = matchSinglePrefixedField("NAME1:", rawText, '\r', true);
+ String pronunciation = matchSinglePrefixedField("NAME2:", rawText, '\r', true);
+
+ String[] phoneNumbers = matchMultipleValuePrefix("TEL", 3, rawText, true);
+ String[] emails = matchMultipleValuePrefix("MAIL", 3, rawText, true);
+ String note = matchSinglePrefixedField("MEMORY:", rawText, '\r', false);
+ String address = matchSinglePrefixedField("ADD:", rawText, '\r', true);
+ String[] addresses = address == null ? null : new String[] {address};
+ return new AddressBookParsedResult(maybeWrap(name),
+ null,
+ pronunciation,
+ phoneNumbers,
+ null,
+ emails,
+ null,
+ null,
+ note,
+ addresses,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ private static String[] matchMultipleValuePrefix(String prefix,
+ int max,
+ String rawText,
+ boolean trim) {
+ List values = null;
+ for (int i = 1; i <= max; i++) {
+ String value = matchSinglePrefixedField(prefix + i + ':', rawText, '\r', trim);
+ if (value == null) {
+ break;
+ }
+ if (values == null) {
+ values = new ArrayList<>(max); // lazy init
+ }
+ values.add(value);
+ }
+ if (values == null) {
+ return null;
+ }
+ return values.toArray(new String[values.size()]);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookDoCoMoResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookDoCoMoResultParser.java
new file mode 100644
index 000000000..cb2e19b26
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookDoCoMoResultParser.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2007 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.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Implements the "MECARD" address book entry format.
+ *
+ * Supported keys: N, SOUND, TEL, EMAIL, NOTE, ADR, BDAY, URL, plus ORG
+ * Unsupported keys: TEL-AV, NICKNAME
+ *
+ * Except for TEL, multiple values for keys are also not supported;
+ * the first one found takes precedence.
+ *
+ * Our understanding of the MECARD format is based on this document:
+ *
+ * http://www.mobicode.org.tw/files/OMIA%20Mobile%20Bar%20Code%20Standard%20v3.2.1.doc
+ *
+ * @author Sean Owen
+ */
+public final class AddressBookDoCoMoResultParser extends AbstractDoCoMoResultParser {
+
+ @Override
+ public AddressBookParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("MECARD:")) {
+ return null;
+ }
+ String[] rawName = matchDoCoMoPrefixedField("N:", rawText, true);
+ if (rawName == null) {
+ return null;
+ }
+ String name = parseName(rawName[0]);
+ String pronunciation = matchSingleDoCoMoPrefixedField("SOUND:", rawText, true);
+ String[] phoneNumbers = matchDoCoMoPrefixedField("TEL:", rawText, true);
+ String[] emails = matchDoCoMoPrefixedField("EMAIL:", rawText, true);
+ String note = matchSingleDoCoMoPrefixedField("NOTE:", rawText, false);
+ String[] addresses = matchDoCoMoPrefixedField("ADR:", rawText, true);
+ String birthday = matchSingleDoCoMoPrefixedField("BDAY:", rawText, true);
+ if (!isStringOfDigits(birthday, 8)) {
+ // No reason to throw out the whole card because the birthday is formatted wrong.
+ birthday = null;
+ }
+ String[] urls = matchDoCoMoPrefixedField("URL:", rawText, true);
+
+ // Although ORG may not be strictly legal in MECARD, it does exist in VCARD and we might as well
+ // honor it when found in the wild.
+ String org = matchSingleDoCoMoPrefixedField("ORG:", rawText, true);
+
+ return new AddressBookParsedResult(maybeWrap(name),
+ null,
+ pronunciation,
+ phoneNumbers,
+ null,
+ emails,
+ null,
+ null,
+ note,
+ addresses,
+ null,
+ org,
+ birthday,
+ null,
+ urls,
+ null);
+ }
+
+ private static String parseName(String name) {
+ int comma = name.indexOf((int) ',');
+ if (comma >= 0) {
+ // Format may be last,first; switch it around
+ return name.substring(comma + 1) + ' ' + name.substring(0, comma);
+ }
+ return name;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookParsedResult.java
new file mode 100644
index 000000000..291ebfccf
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/AddressBookParsedResult.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2007 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.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class AddressBookParsedResult extends ParsedResult {
+
+ private final String[] names;
+ private final String[] nicknames;
+ private final String pronunciation;
+ private final String[] phoneNumbers;
+ private final String[] phoneTypes;
+ private final String[] emails;
+ private final String[] emailTypes;
+ private final String instantMessenger;
+ private final String note;
+ private final String[] addresses;
+ private final String[] addressTypes;
+ private final String org;
+ private final String birthday;
+ private final String title;
+ private final String[] urls;
+ private final String[] geo;
+
+ public AddressBookParsedResult(String[] names,
+ String[] phoneNumbers,
+ String[] phoneTypes,
+ String[] emails,
+ String[] emailTypes,
+ String[] addresses,
+ String[] addressTypes) {
+ this(names,
+ null,
+ null,
+ phoneNumbers,
+ phoneTypes,
+ emails,
+ emailTypes,
+ null,
+ null,
+ addresses,
+ addressTypes,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ public AddressBookParsedResult(String[] names,
+ String[] nicknames,
+ String pronunciation,
+ String[] phoneNumbers,
+ String[] phoneTypes,
+ String[] emails,
+ String[] emailTypes,
+ String instantMessenger,
+ String note,
+ String[] addresses,
+ String[] addressTypes,
+ String org,
+ String birthday,
+ String title,
+ String[] urls,
+ String[] geo) {
+ super(ParsedResultType.ADDRESSBOOK);
+ this.names = names;
+ this.nicknames = nicknames;
+ this.pronunciation = pronunciation;
+ this.phoneNumbers = phoneNumbers;
+ this.phoneTypes = phoneTypes;
+ this.emails = emails;
+ this.emailTypes = emailTypes;
+ this.instantMessenger = instantMessenger;
+ this.note = note;
+ this.addresses = addresses;
+ this.addressTypes = addressTypes;
+ this.org = org;
+ this.birthday = birthday;
+ this.title = title;
+ this.urls = urls;
+ this.geo = geo;
+ }
+
+ public String[] getNames() {
+ return names;
+ }
+
+ public String[] getNicknames() {
+ return nicknames;
+ }
+
+ /**
+ * In Japanese, the name is written in kanji, which can have multiple readings. Therefore a hint
+ * is often provided, called furigana, which spells the name phonetically.
+ *
+ * @return The pronunciation of the getNames() field, often in hiragana or katakana.
+ */
+ public String getPronunciation() {
+ return pronunciation;
+ }
+
+ public String[] getPhoneNumbers() {
+ return phoneNumbers;
+ }
+
+ /**
+ * @return optional descriptions of the type of each phone number. It could be like "HOME", but,
+ * there is no guaranteed or standard format.
+ */
+ public String[] getPhoneTypes() {
+ return phoneTypes;
+ }
+
+ public String[] getEmails() {
+ return emails;
+ }
+
+ /**
+ * @return optional descriptions of the type of each e-mail. It could be like "WORK", but,
+ * there is no guaranteed or standard format.
+ */
+ public String[] getEmailTypes() {
+ return emailTypes;
+ }
+
+ public String getInstantMessenger() {
+ return instantMessenger;
+ }
+
+ public String getNote() {
+ return note;
+ }
+
+ public String[] getAddresses() {
+ return addresses;
+ }
+
+ /**
+ * @return optional descriptions of the type of each e-mail. It could be like "WORK", but,
+ * there is no guaranteed or standard format.
+ */
+ public String[] getAddressTypes() {
+ return addressTypes;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getOrg() {
+ return org;
+ }
+
+ public String[] getURLs() {
+ return urls;
+ }
+
+ /**
+ * @return birthday formatted as yyyyMMdd (e.g. 19780917)
+ */
+ public String getBirthday() {
+ return birthday;
+ }
+
+ /**
+ * @return a location as a latitude/longitude pair
+ */
+ public String[] getGeo() {
+ return geo;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(100);
+ maybeAppend(names, result);
+ maybeAppend(nicknames, result);
+ maybeAppend(pronunciation, result);
+ maybeAppend(title, result);
+ maybeAppend(org, result);
+ maybeAppend(addresses, result);
+ maybeAppend(phoneNumbers, result);
+ maybeAppend(emails, result);
+ maybeAppend(instantMessenger, result);
+ maybeAppend(urls, result);
+ maybeAppend(birthday, result);
+ maybeAppend(geo, result);
+ maybeAppend(note, result);
+ return result.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/BizcardResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/BizcardResultParser.java
new file mode 100644
index 000000000..1d588d4c2
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/BizcardResultParser.java
@@ -0,0 +1,100 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements the "BIZCARD" address book entry format, though this has been
+ * largely reverse-engineered from examples observed in the wild -- still
+ * looking for a definitive reference.
+ *
+ * @author Sean Owen
+ */
+public final class BizcardResultParser extends AbstractDoCoMoResultParser {
+
+ // Yes, we extend AbstractDoCoMoResultParser since the format is very much
+ // like the DoCoMo MECARD format, but this is not technically one of
+ // DoCoMo's proposed formats
+
+ @Override
+ public AddressBookParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("BIZCARD:")) {
+ return null;
+ }
+ String firstName = matchSingleDoCoMoPrefixedField("N:", rawText, true);
+ String lastName = matchSingleDoCoMoPrefixedField("X:", rawText, true);
+ String fullName = buildName(firstName, lastName);
+ String title = matchSingleDoCoMoPrefixedField("T:", rawText, true);
+ String org = matchSingleDoCoMoPrefixedField("C:", rawText, true);
+ String[] addresses = matchDoCoMoPrefixedField("A:", rawText, true);
+ String phoneNumber1 = matchSingleDoCoMoPrefixedField("B:", rawText, true);
+ String phoneNumber2 = matchSingleDoCoMoPrefixedField("M:", rawText, true);
+ String phoneNumber3 = matchSingleDoCoMoPrefixedField("F:", rawText, true);
+ String email = matchSingleDoCoMoPrefixedField("E:", rawText, true);
+
+ return new AddressBookParsedResult(maybeWrap(fullName),
+ null,
+ null,
+ buildPhoneNumbers(phoneNumber1, phoneNumber2, phoneNumber3),
+ null,
+ maybeWrap(email),
+ null,
+ null,
+ null,
+ addresses,
+ null,
+ org,
+ null,
+ title,
+ null,
+ null);
+ }
+
+ private static String[] buildPhoneNumbers(String number1,
+ String number2,
+ String number3) {
+ List numbers = new ArrayList<>(3);
+ if (number1 != null) {
+ numbers.add(number1);
+ }
+ if (number2 != null) {
+ numbers.add(number2);
+ }
+ if (number3 != null) {
+ numbers.add(number3);
+ }
+ int size = numbers.size();
+ if (size == 0) {
+ return null;
+ }
+ return numbers.toArray(new String[size]);
+ }
+
+ private static String buildName(String firstName, String lastName) {
+ if (firstName == null) {
+ return lastName;
+ } else {
+ return lastName == null ? firstName : firstName + ' ' + lastName;
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/BookmarkDoCoMoResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/BookmarkDoCoMoResultParser.java
new file mode 100644
index 000000000..a729239d8
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/BookmarkDoCoMoResultParser.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 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.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * @author Sean Owen
+ */
+public final class BookmarkDoCoMoResultParser extends AbstractDoCoMoResultParser {
+
+ @Override
+ public URIParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (!rawText.startsWith("MEBKM:")) {
+ return null;
+ }
+ String title = matchSingleDoCoMoPrefixedField("TITLE:", rawText, true);
+ String[] rawUri = matchDoCoMoPrefixedField("URL:", rawText, true);
+ if (rawUri == null) {
+ return null;
+ }
+ String uri = rawUri[0];
+ return URIResultParser.isBasicallyValidURI(uri) ? new URIParsedResult(uri, title) : null;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/CalendarParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/CalendarParsedResult.java
new file mode 100644
index 000000000..48b92a6bc
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/CalendarParsedResult.java
@@ -0,0 +1,246 @@
+/*
+ * 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.client.result;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Sean Owen
+ */
+public final class CalendarParsedResult extends ParsedResult {
+
+ private static final Pattern RFC2445_DURATION =
+ Pattern.compile("P(?:(\\d+)W)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?");
+ private static final long[] RFC2445_DURATION_FIELD_UNITS = {
+ 7 * 24 * 60 * 60 * 1000L, // 1 week
+ 24 * 60 * 60 * 1000L, // 1 day
+ 60 * 60 * 1000L, // 1 hour
+ 60 * 1000L, // 1 minute
+ 1000L, // 1 second
+ };
+
+ private static final Pattern DATE_TIME = Pattern.compile("[0-9]{8}(T[0-9]{6}Z?)?");
+
+ private final String summary;
+ private final Date start;
+ private final boolean startAllDay;
+ private final Date end;
+ private final boolean endAllDay;
+ private final String location;
+ private final String organizer;
+ private final String[] attendees;
+ private final String description;
+ private final double latitude;
+ private final double longitude;
+
+ public CalendarParsedResult(String summary,
+ String startString,
+ String endString,
+ String durationString,
+ String location,
+ String organizer,
+ String[] attendees,
+ String description,
+ double latitude,
+ double longitude) {
+ super(ParsedResultType.CALENDAR);
+ this.summary = summary;
+
+ try {
+ this.start = parseDate(startString);
+ } catch (ParseException pe) {
+ throw new IllegalArgumentException(pe.toString());
+ }
+
+ if (endString == null) {
+ long durationMS = parseDurationMS(durationString);
+ end = durationMS < 0L ? null : new Date(start.getTime() + durationMS);
+ } else {
+ try {
+ this.end = parseDate(endString);
+ } catch (ParseException pe) {
+ throw new IllegalArgumentException(pe.toString());
+ }
+ }
+
+ this.startAllDay = startString.length() == 8;
+ this.endAllDay = endString != null && endString.length() == 8;
+
+ this.location = location;
+ this.organizer = organizer;
+ this.attendees = attendees;
+ this.description = description;
+ this.latitude = latitude;
+ this.longitude = longitude;
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ /**
+ * @return start time
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * @return true if start time was specified as a whole day
+ */
+ public boolean isStartAllDay() {
+ return startAllDay;
+ }
+
+ /**
+ * @return event end {@link Date}, or {@code null} if event has no duration
+ * @see #getStart()
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ /**
+ * @return true if end time was specified as a whole day
+ */
+ public boolean isEndAllDay() {
+ return endAllDay;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public String getOrganizer() {
+ return organizer;
+ }
+
+ public String[] getAttendees() {
+ return attendees;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public double getLatitude() {
+ return latitude;
+ }
+
+ public double getLongitude() {
+ return longitude;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(100);
+ maybeAppend(summary, result);
+ maybeAppend(format(startAllDay, start), result);
+ maybeAppend(format(endAllDay, end), result);
+ maybeAppend(location, result);
+ maybeAppend(organizer, result);
+ maybeAppend(attendees, result);
+ maybeAppend(description, result);
+ return result.toString();
+ }
+
+ /**
+ * Parses a string as a date. RFC 2445 allows the start and end fields to be of type DATE (e.g. 20081021)
+ * or DATE-TIME (e.g. 20081021T123000 for local time, or 20081021T123000Z for UTC).
+ *
+ * @param when The string to parse
+ * @throws ParseException if not able to parse as a date
+ */
+ private static Date parseDate(String when) throws ParseException {
+ if (!DATE_TIME.matcher(when).matches()) {
+ throw new ParseException(when, 0);
+ }
+ if (when.length() == 8) {
+ // Show only year/month/day
+ return buildDateFormat().parse(when);
+ } else {
+ // The when string can be local time, or UTC if it ends with a Z
+ Date date;
+ if (when.length() == 16 && when.charAt(15) == 'Z') {
+ date = buildDateTimeFormat().parse(when.substring(0, 15));
+ Calendar calendar = new GregorianCalendar();
+ long milliseconds = date.getTime();
+ // Account for time zone difference
+ milliseconds += calendar.get(Calendar.ZONE_OFFSET);
+ // Might need to correct for daylight savings time, but use target time since
+ // now might be in DST but not then, or vice versa
+ calendar.setTime(new Date(milliseconds));
+ milliseconds += calendar.get(Calendar.DST_OFFSET);
+ date = new Date(milliseconds);
+ } else {
+ date = buildDateTimeFormat().parse(when);
+ }
+ return date;
+ }
+ }
+
+ private static String format(boolean allDay, Date date) {
+ if (date == null) {
+ return null;
+ }
+ DateFormat format = allDay
+ ? DateFormat.getDateInstance(DateFormat.MEDIUM)
+ : DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
+ return format.format(date);
+ }
+
+ private static long parseDurationMS(CharSequence durationString) {
+ if (durationString == null) {
+ return -1L;
+ }
+ Matcher m = RFC2445_DURATION.matcher(durationString);
+ if (!m.matches()) {
+ return -1L;
+ }
+ long durationMS = 0L;
+ for (int i = 0; i < RFC2445_DURATION_FIELD_UNITS.length; i++) {
+ String fieldValue = m.group(i + 1);
+ if (fieldValue != null) {
+ durationMS += RFC2445_DURATION_FIELD_UNITS[i] * Integer.parseInt(fieldValue);
+ }
+ }
+ return durationMS;
+ }
+
+ private static DateFormat buildDateFormat() {
+ DateFormat format = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
+ // For dates without a time, for purposes of interacting with Android, the resulting timestamp
+ // needs to be midnight of that day in GMT. See:
+ // http://code.google.com/p/android/issues/detail?id=8330
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return format;
+ }
+
+ private static DateFormat buildDateTimeFormat() {
+ return new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailAddressParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailAddressParsedResult.java
new file mode 100644
index 000000000..f4af3f6ea
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailAddressParsedResult.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2007 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.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class EmailAddressParsedResult extends ParsedResult {
+
+ private final String emailAddress;
+ private final String subject;
+ private final String body;
+ private final String mailtoURI;
+
+ EmailAddressParsedResult(String emailAddress,
+ String subject,
+ String body,
+ String mailtoURI) {
+ super(ParsedResultType.EMAIL_ADDRESS);
+ this.emailAddress = emailAddress;
+ this.subject = subject;
+ this.body = body;
+ this.mailtoURI = mailtoURI;
+ }
+
+ public String getEmailAddress() {
+ return emailAddress;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public String getMailtoURI() {
+ return mailtoURI;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(30);
+ maybeAppend(emailAddress, result);
+ maybeAppend(subject, result);
+ maybeAppend(body, result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailAddressResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailAddressResultParser.java
new file mode 100644
index 000000000..b5117c12a
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailAddressResultParser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2007 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.Map;
+
+/**
+ * Represents a result that encodes an e-mail address, either as a plain address
+ * like "joe@example.org" or a mailto: URL like "mailto:joe@example.org".
+ *
+ * @author Sean Owen
+ */
+public final class EmailAddressResultParser extends ResultParser {
+
+ @Override
+ public EmailAddressParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ String emailAddress;
+ if (rawText.startsWith("mailto:") || rawText.startsWith("MAILTO:")) {
+ // If it starts with mailto:, assume it is definitely trying to be an email address
+ emailAddress = rawText.substring(7);
+ int queryStart = emailAddress.indexOf('?');
+ if (queryStart >= 0) {
+ emailAddress = emailAddress.substring(0, queryStart);
+ }
+ emailAddress = urlDecode(emailAddress);
+ Map nameValues = parseNameValuePairs(rawText);
+ String subject = null;
+ String body = null;
+ if (nameValues != null) {
+ if (emailAddress.isEmpty()) {
+ emailAddress = nameValues.get("to");
+ }
+ subject = nameValues.get("subject");
+ body = nameValues.get("body");
+ }
+ return new EmailAddressParsedResult(emailAddress, subject, body, rawText);
+ } else {
+ if (!EmailDoCoMoResultParser.isBasicallyValidEmailAddress(rawText)) {
+ return null;
+ }
+ emailAddress = rawText;
+ return new EmailAddressParsedResult(emailAddress, null, null, "mailto:" + emailAddress);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java
new file mode 100644
index 000000000..dfef4e1ab
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/EmailDoCoMoResultParser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2007 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.regex.Pattern;
+
+/**
+ * Implements the "MATMSG" email message entry format.
+ *
+ * Supported keys: TO, SUB, BODY
+ *
+ * @author Sean Owen
+ */
+public final class EmailDoCoMoResultParser extends AbstractDoCoMoResultParser {
+
+ private static final Pattern ATEXT_ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9@.!#$%&'*+\\-/=?^_`{|}~]+");
+
+ @Override
+ public EmailAddressParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("MATMSG:")) {
+ return null;
+ }
+ String[] rawTo = matchDoCoMoPrefixedField("TO:", rawText, true);
+ if (rawTo == null) {
+ return null;
+ }
+ String to = rawTo[0];
+ if (!isBasicallyValidEmailAddress(to)) {
+ return null;
+ }
+ String subject = matchSingleDoCoMoPrefixedField("SUB:", rawText, false);
+ String body = matchSingleDoCoMoPrefixedField("BODY:", rawText, false);
+ return new EmailAddressParsedResult(to, subject, body, "mailto:" + to);
+ }
+
+ /**
+ * This implements only the most basic checking for an email address's validity -- that it contains
+ * an '@' and contains no characters disallowed by RFC 2822. This is an overly lenient definition of
+ * validity. We want to generally be lenient here since this class is only intended to encapsulate what's
+ * in a barcode, not "judge" it.
+ */
+ static boolean isBasicallyValidEmailAddress(String email) {
+ return email != null && ATEXT_ALPHANUMERIC.matcher(email).matches() && email.indexOf('@') >= 0;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/ExpandedProductParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ExpandedProductParsedResult.java
new file mode 100644
index 000000000..6053100d4
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ExpandedProductParsedResult.java
@@ -0,0 +1,204 @@
+/*
+ * 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.client.result;
+
+import java.util.Map;
+
+/**
+ * @author Antonio Manuel Benjumea Conde, Servinform, S.A.
+ * @author AgustÃn Delgado, Servinform, S.A.
+ */
+public final class ExpandedProductParsedResult extends ParsedResult {
+
+ public static final String KILOGRAM = "KG";
+ public static final String POUND = "LB";
+
+ private final String rawText;
+ private final String productID;
+ private final String sscc;
+ private final String lotNumber;
+ private final String productionDate;
+ private final String packagingDate;
+ private final String bestBeforeDate;
+ private final String expirationDate;
+ private final String weight;
+ private final String weightType;
+ private final String weightIncrement;
+ private final String price;
+ private final String priceIncrement;
+ private final String priceCurrency;
+ // For AIS that not exist in this object
+ private final Map uncommonAIs;
+
+ public ExpandedProductParsedResult(String rawText,
+ String productID,
+ String sscc,
+ String lotNumber,
+ String productionDate,
+ String packagingDate,
+ String bestBeforeDate,
+ String expirationDate,
+ String weight,
+ String weightType,
+ String weightIncrement,
+ String price,
+ String priceIncrement,
+ String priceCurrency,
+ Map uncommonAIs) {
+ super(ParsedResultType.PRODUCT);
+ this.rawText = rawText;
+ this.productID = productID;
+ this.sscc = sscc;
+ this.lotNumber = lotNumber;
+ this.productionDate = productionDate;
+ this.packagingDate = packagingDate;
+ this.bestBeforeDate = bestBeforeDate;
+ this.expirationDate = expirationDate;
+ this.weight = weight;
+ this.weightType = weightType;
+ this.weightIncrement = weightIncrement;
+ this.price = price;
+ this.priceIncrement = priceIncrement;
+ this.priceCurrency = priceCurrency;
+ this.uncommonAIs = uncommonAIs;
+ }
+
+ @Override
+ public boolean equals(Object o){
+ if (!(o instanceof ExpandedProductParsedResult)) {
+ return false;
+ }
+
+ ExpandedProductParsedResult other = (ExpandedProductParsedResult)o;
+
+ return equalsOrNull(productID, other.productID)
+ && equalsOrNull(sscc, other.sscc)
+ && equalsOrNull(lotNumber, other.lotNumber)
+ && equalsOrNull(productionDate, other.productionDate)
+ && equalsOrNull(bestBeforeDate, other.bestBeforeDate)
+ && equalsOrNull(expirationDate, other.expirationDate)
+ && equalsOrNull(weight, other.weight)
+ && equalsOrNull(weightType, other.weightType)
+ && equalsOrNull(weightIncrement, other.weightIncrement)
+ && equalsOrNull(price, other.price)
+ && equalsOrNull(priceIncrement, other.priceIncrement)
+ && equalsOrNull(priceCurrency, other.priceCurrency)
+ && equalsOrNull(uncommonAIs, other.uncommonAIs);
+ }
+
+ private static boolean equalsOrNull(Object o1, Object o2) {
+ return o1 == null ? o2 == null : o1.equals(o2);
+ }
+
+ @Override
+ public int hashCode(){
+ int hash = 0;
+ hash ^= hashNotNull(productID);
+ hash ^= hashNotNull(sscc);
+ hash ^= hashNotNull(lotNumber);
+ hash ^= hashNotNull(productionDate);
+ hash ^= hashNotNull(bestBeforeDate);
+ hash ^= hashNotNull(expirationDate);
+ hash ^= hashNotNull(weight);
+ hash ^= hashNotNull(weightType);
+ hash ^= hashNotNull(weightIncrement);
+ hash ^= hashNotNull(price);
+ hash ^= hashNotNull(priceIncrement);
+ hash ^= hashNotNull(priceCurrency);
+ hash ^= hashNotNull(uncommonAIs);
+ return hash;
+ }
+
+ private static int hashNotNull(Object o) {
+ return o == null ? 0 : o.hashCode();
+ }
+
+ public String getRawText() {
+ return rawText;
+ }
+
+ public String getProductID() {
+ return productID;
+ }
+
+ public String getSscc() {
+ return sscc;
+ }
+
+ public String getLotNumber() {
+ return lotNumber;
+ }
+
+ public String getProductionDate() {
+ return productionDate;
+ }
+
+ public String getPackagingDate() {
+ return packagingDate;
+ }
+
+ public String getBestBeforeDate() {
+ return bestBeforeDate;
+ }
+
+ public String getExpirationDate() {
+ return expirationDate;
+ }
+
+ public String getWeight() {
+ return weight;
+ }
+
+ public String getWeightType() {
+ return weightType;
+ }
+
+ public String getWeightIncrement() {
+ return weightIncrement;
+ }
+
+ public String getPrice() {
+ return price;
+ }
+
+ public String getPriceIncrement() {
+ return priceIncrement;
+ }
+
+ public String getPriceCurrency() {
+ return priceCurrency;
+ }
+
+ public Map getUncommonAIs() {
+ return uncommonAIs;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ return String.valueOf(rawText);
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/ExpandedProductResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ExpandedProductResultParser.java
new file mode 100644
index 000000000..a64aec275
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ExpandedProductResultParser.java
@@ -0,0 +1,218 @@
+/*
+ * 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.client.result;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+
+/**
+ * Parses strings of digits that represent a RSS Extended code.
+ *
+ * @author Antonio Manuel Benjumea Conde, Servinform, S.A.
+ * @author AgustÃn Delgado, Servinform, S.A.
+ */
+public final class ExpandedProductResultParser extends ResultParser {
+
+ @Override
+ public ExpandedProductParsedResult parse(Result result) {
+ BarcodeFormat format = result.getBarcodeFormat();
+ if (format != BarcodeFormat.RSS_EXPANDED) {
+ // ExtendedProductParsedResult NOT created. Not a RSS Expanded barcode
+ return null;
+ }
+ String rawText = getMassagedText(result);
+
+ String productID = null;
+ String sscc = null;
+ String lotNumber = null;
+ String productionDate = null;
+ String packagingDate = null;
+ String bestBeforeDate = null;
+ String expirationDate = null;
+ String weight = null;
+ String weightType = null;
+ String weightIncrement = null;
+ String price = null;
+ String priceIncrement = null;
+ String priceCurrency = null;
+ Map uncommonAIs = new HashMap<>();
+
+ int i = 0;
+
+ while (i < rawText.length()) {
+ String ai = findAIvalue(i, rawText);
+ if (ai == null) {
+ // Error. Code doesn't match with RSS expanded pattern
+ // ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern
+ return null;
+ }
+ i += ai.length() + 2;
+ String value = findValue(i, rawText);
+ i += value.length();
+
+ switch (ai) {
+ case "00":
+ sscc = value;
+ break;
+ case "01":
+ productID = value;
+ break;
+ case "10":
+ lotNumber = value;
+ break;
+ case "11":
+ productionDate = value;
+ break;
+ case "13":
+ packagingDate = value;
+ break;
+ case "15":
+ bestBeforeDate = value;
+ break;
+ case "17":
+ expirationDate = value;
+ break;
+ case "3100":
+ case "3101":
+ case "3102":
+ case "3103":
+ case "3104":
+ case "3105":
+ case "3106":
+ case "3107":
+ case "3108":
+ case "3109":
+ weight = value;
+ weightType = ExpandedProductParsedResult.KILOGRAM;
+ weightIncrement = ai.substring(3);
+ break;
+ case "3200":
+ case "3201":
+ case "3202":
+ case "3203":
+ case "3204":
+ case "3205":
+ case "3206":
+ case "3207":
+ case "3208":
+ case "3209":
+ weight = value;
+ weightType = ExpandedProductParsedResult.POUND;
+ weightIncrement = ai.substring(3);
+ break;
+ case "3920":
+ case "3921":
+ case "3922":
+ case "3923":
+ price = value;
+ priceIncrement = ai.substring(3);
+ break;
+ case "3930":
+ case "3931":
+ case "3932":
+ case "3933":
+ if (value.length() < 4) {
+ // The value must have more of 3 symbols (3 for currency and
+ // 1 at least for the price)
+ // ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern
+ return null;
+ }
+ price = value.substring(3);
+ priceCurrency = value.substring(0, 3);
+ priceIncrement = ai.substring(3);
+ break;
+ default:
+ // No match with common AIs
+ uncommonAIs.put(ai, value);
+ break;
+ }
+ }
+
+ return new ExpandedProductParsedResult(rawText,
+ productID,
+ sscc,
+ lotNumber,
+ productionDate,
+ packagingDate,
+ bestBeforeDate,
+ expirationDate,
+ weight,
+ weightType,
+ weightIncrement,
+ price,
+ priceIncrement,
+ priceCurrency,
+ uncommonAIs);
+ }
+
+ private static String findAIvalue(int i, String rawText) {
+ char c = rawText.charAt(i);
+ // First character must be a open parenthesis.If not, ERROR
+ if (c != '(') {
+ return null;
+ }
+
+ CharSequence rawTextAux = rawText.substring(i + 1);
+
+ StringBuilder buf = new StringBuilder();
+ for (int index = 0; index < rawTextAux.length(); index++) {
+ char currentChar = rawTextAux.charAt(index);
+ if (currentChar == ')') {
+ return buf.toString();
+ } else if (currentChar >= '0' && currentChar <= '9') {
+ buf.append(currentChar);
+ } else {
+ return null;
+ }
+ }
+ return buf.toString();
+ }
+
+ private static String findValue(int i, String rawText) {
+ StringBuilder buf = new StringBuilder();
+ String rawTextAux = rawText.substring(i);
+
+ for (int index = 0; index < rawTextAux.length(); index++) {
+ char c = rawTextAux.charAt(index);
+ if (c == '(') {
+ // We look for a new AI. If it doesn't exist (ERROR), we coninue
+ // with the iteration
+ if (findAIvalue(index, rawTextAux) == null) {
+ buf.append('(');
+ } else {
+ break;
+ }
+ } else {
+ buf.append(c);
+ }
+ }
+ return buf.toString();
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/GeoParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/GeoParsedResult.java
new file mode 100644
index 000000000..6eaad1ebb
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/GeoParsedResult.java
@@ -0,0 +1,101 @@
+/*
+ * 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.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class GeoParsedResult extends ParsedResult {
+
+ private final double latitude;
+ private final double longitude;
+ private final double altitude;
+ private final String query;
+
+ GeoParsedResult(double latitude, double longitude, double altitude, String query) {
+ super(ParsedResultType.GEO);
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.altitude = altitude;
+ this.query = query;
+ }
+
+ public String getGeoURI() {
+ StringBuilder result = new StringBuilder();
+ result.append("geo:");
+ result.append(latitude);
+ result.append(',');
+ result.append(longitude);
+ if (altitude > 0) {
+ result.append(',');
+ result.append(altitude);
+ }
+ if (query != null) {
+ result.append('?');
+ result.append(query);
+ }
+ return result.toString();
+ }
+
+ /**
+ * @return latitude in degrees
+ */
+ public double getLatitude() {
+ return latitude;
+ }
+
+ /**
+ * @return longitude in degrees
+ */
+ public double getLongitude() {
+ return longitude;
+ }
+
+ /**
+ * @return altitude in meters. If not specified, in the geo URI, returns 0.0
+ */
+ public double getAltitude() {
+ return altitude;
+ }
+
+ /**
+ * @return query string associated with geo URI or null if none exists
+ */
+ public String getQuery() {
+ return query;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(20);
+ result.append(latitude);
+ result.append(", ");
+ result.append(longitude);
+ if (altitude > 0.0) {
+ result.append(", ");
+ result.append(altitude);
+ result.append('m');
+ }
+ if (query != null) {
+ result.append(" (");
+ result.append(query);
+ result.append(')');
+ }
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/GeoResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/GeoResultParser.java
new file mode 100644
index 000000000..fb9cb07d4
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/GeoResultParser.java
@@ -0,0 +1,73 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses a "geo:" URI result, which specifies a location on the surface of
+ * the Earth as well as an optional altitude above the surface. See
+ *
+ * http://tools.ietf.org/html/draft-mayrhofer-geo-uri-00.
+ *
+ * @author Sean Owen
+ */
+public final class GeoResultParser extends ResultParser {
+
+ private static final Pattern GEO_URL_PATTERN =
+ Pattern.compile("geo:([\\-0-9.]+),([\\-0-9.]+)(?:,([\\-0-9.]+))?(?:\\?(.*))?", Pattern.CASE_INSENSITIVE);
+
+ @Override
+ public GeoParsedResult parse(Result result) {
+ CharSequence rawText = getMassagedText(result);
+ Matcher matcher = GEO_URL_PATTERN.matcher(rawText);
+ if (!matcher.matches()) {
+ return null;
+ }
+
+ String query = matcher.group(4);
+
+ double latitude;
+ double longitude;
+ double altitude;
+ try {
+ latitude = Double.parseDouble(matcher.group(1));
+ if (latitude > 90.0 || latitude < -90.0) {
+ return null;
+ }
+ longitude = Double.parseDouble(matcher.group(2));
+ if (longitude > 180.0 || longitude < -180.0) {
+ return null;
+ }
+ if (matcher.group(3) == null) {
+ altitude = 0.0;
+ } else {
+ altitude = Double.parseDouble(matcher.group(3));
+ if (altitude < 0.0) {
+ return null;
+ }
+ }
+ } catch (NumberFormatException ignored) {
+ return null;
+ }
+ return new GeoParsedResult(latitude, longitude, altitude, query);
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/ISBNParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ISBNParsedResult.java
new file mode 100644
index 000000000..51b63ff82
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ISBNParsedResult.java
@@ -0,0 +1,40 @@
+/*
+ * 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.client.result;
+
+/**
+ * @author jbreiden@google.com (Jeff Breidenbach)
+ */
+public final class ISBNParsedResult extends ParsedResult {
+
+ private final String isbn;
+
+ ISBNParsedResult(String isbn) {
+ super(ParsedResultType.ISBN);
+ this.isbn = isbn;
+ }
+
+ public String getISBN() {
+ return isbn;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ return isbn;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/ISBNResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ISBNResultParser.java
new file mode 100644
index 000000000..e957dd02b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ISBNResultParser.java
@@ -0,0 +1,50 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+
+/**
+ * Parses strings of digits that represent a ISBN.
+ *
+ * @author jbreiden@google.com (Jeff Breidenbach)
+ */
+public final class ISBNResultParser extends ResultParser {
+
+ /**
+ * See ISBN-13 For Dummies
+ */
+ @Override
+ public ISBNParsedResult parse(Result result) {
+ BarcodeFormat format = result.getBarcodeFormat();
+ if (format != BarcodeFormat.EAN_13) {
+ return null;
+ }
+ String rawText = getMassagedText(result);
+ int length = rawText.length();
+ if (length != 13) {
+ return null;
+ }
+ if (!rawText.startsWith("978") && !rawText.startsWith("979")) {
+ return null;
+ }
+
+ return new ISBNParsedResult(rawText);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/ParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ParsedResult.java
new file mode 100644
index 000000000..17660e2e6
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ParsedResult.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2007 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.client.result;
+
+/**
+ * Abstract class representing the result of decoding a barcode, as more than
+ * a String -- as some type of structured data. This might be a subclass which represents
+ * a URL, or an e-mail address. {@link ResultParser#parseResult(com.google.zxing.Result)} will turn a raw
+ * decoded string into the most appropriate type of structured representation.
+ *
+ * Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
+ * on exception-based mechanisms during parsing.
+ *
+ * @author Sean Owen
+ */
+public abstract class ParsedResult {
+
+ private final ParsedResultType type;
+
+ protected ParsedResult(ParsedResultType type) {
+ this.type = type;
+ }
+
+ public final ParsedResultType getType() {
+ return type;
+ }
+
+ public abstract String getDisplayResult();
+
+ @Override
+ public final String toString() {
+ return getDisplayResult();
+ }
+
+ public static void maybeAppend(String value, StringBuilder result) {
+ if (value != null && !value.isEmpty()) {
+ // Don't add a newline before the first value
+ if (result.length() > 0) {
+ result.append('\n');
+ }
+ result.append(value);
+ }
+ }
+
+ public static void maybeAppend(String[] values, StringBuilder result) {
+ if (values != null) {
+ for (String value : values) {
+ maybeAppend(value, result);
+ }
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/ParsedResultType.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ParsedResultType.java
new file mode 100644
index 000000000..c74d54594
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ParsedResultType.java
@@ -0,0 +1,40 @@
+/*
+ * 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.client.result;
+
+/**
+ * Represents the type of data encoded by a barcode -- from plain text, to a
+ * URI, to an e-mail address, etc.
+ *
+ * @author Sean Owen
+ */
+public enum ParsedResultType {
+
+ ADDRESSBOOK,
+ EMAIL_ADDRESS,
+ PRODUCT,
+ URI,
+ TEXT,
+ GEO,
+ TEL,
+ SMS,
+ CALENDAR,
+ WIFI,
+ ISBN,
+ VIN,
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/ProductParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ProductParsedResult.java
new file mode 100644
index 000000000..95cfad016
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ProductParsedResult.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 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.client.result;
+
+/**
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class ProductParsedResult extends ParsedResult {
+
+ private final String productID;
+ private final String normalizedProductID;
+
+ ProductParsedResult(String productID) {
+ this(productID, productID);
+ }
+
+ ProductParsedResult(String productID, String normalizedProductID) {
+ super(ParsedResultType.PRODUCT);
+ this.productID = productID;
+ this.normalizedProductID = normalizedProductID;
+ }
+
+ public String getProductID() {
+ return productID;
+ }
+
+ public String getNormalizedProductID() {
+ return normalizedProductID;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ return productID;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/ProductResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ProductResultParser.java
new file mode 100644
index 000000000..bed1a7dc1
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ProductResultParser.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 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.client.result;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+import com.google.zxing.oned.UPCEReader;
+
+/**
+ * Parses strings of digits that represent a UPC code.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class ProductResultParser extends ResultParser {
+
+ // Treat all UPC and EAN variants as UPCs, in the sense that they are all product barcodes.
+ @Override
+ public ProductParsedResult parse(Result result) {
+ BarcodeFormat format = result.getBarcodeFormat();
+ if (!(format == BarcodeFormat.UPC_A || format == BarcodeFormat.UPC_E ||
+ format == BarcodeFormat.EAN_8 || format == BarcodeFormat.EAN_13)) {
+ return null;
+ }
+ String rawText = getMassagedText(result);
+ if (!isStringOfDigits(rawText, rawText.length())) {
+ return null;
+ }
+ // Not actually checking the checksum again here
+
+ String normalizedProductID;
+ // Expand UPC-E for purposes of searching
+ if (format == BarcodeFormat.UPC_E && rawText.length() == 8) {
+ normalizedProductID = UPCEReader.convertUPCEtoUPCA(rawText);
+ } else {
+ normalizedProductID = rawText;
+ }
+
+ return new ProductParsedResult(rawText, normalizedProductID);
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/ResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ResultParser.java
new file mode 100644
index 000000000..7cc1d4862
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/ResultParser.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright 2007 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Abstract class representing the result of decoding a barcode, as more than
+ * a String -- as some type of structured data. This might be a subclass which represents
+ * a URL, or an e-mail address. {@link #parseResult(Result)} will turn a raw
+ * decoded string into the most appropriate type of structured representation.
+ *
+ * Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
+ * on exception-based mechanisms during parsing.
+ *
+ * @author Sean Owen
+ */
+public abstract class ResultParser {
+
+ private static final ResultParser[] PARSERS = {
+ new BookmarkDoCoMoResultParser(),
+ new AddressBookDoCoMoResultParser(),
+ new EmailDoCoMoResultParser(),
+ new AddressBookAUResultParser(),
+ new VCardResultParser(),
+ new BizcardResultParser(),
+ new VEventResultParser(),
+ new EmailAddressResultParser(),
+ new SMTPResultParser(),
+ new TelResultParser(),
+ new SMSMMSResultParser(),
+ new SMSTOMMSTOResultParser(),
+ new GeoResultParser(),
+ new WifiResultParser(),
+ new URLTOResultParser(),
+ new URIResultParser(),
+ new ISBNResultParser(),
+ new ProductResultParser(),
+ new ExpandedProductResultParser(),
+ new VINResultParser(),
+ };
+
+ private static final Pattern DIGITS = Pattern.compile("\\d+");
+ private static final Pattern AMPERSAND = Pattern.compile("&");
+ private static final Pattern EQUALS = Pattern.compile("=");
+ private static final String BYTE_ORDER_MARK = "\ufeff";
+
+ /**
+ * Attempts to parse the raw {@link Result}'s contents as a particular type
+ * of information (email, URL, etc.) and return a {@link ParsedResult} encapsulating
+ * the result of parsing.
+ *
+ * @param theResult the raw {@link Result} to parse
+ * @return {@link ParsedResult} encapsulating the parsing result
+ */
+ public abstract ParsedResult parse(Result theResult);
+
+ protected static String getMassagedText(Result result) {
+ String text = result.getText();
+ if (text.startsWith(BYTE_ORDER_MARK)) {
+ text = text.substring(1);
+ }
+ return text;
+ }
+
+ public static ParsedResult parseResult(Result theResult) {
+ for (ResultParser parser : PARSERS) {
+ ParsedResult result = parser.parse(theResult);
+ if (result != null) {
+ return result;
+ }
+ }
+ return new TextParsedResult(theResult.getText(), null);
+ }
+
+ protected static void maybeAppend(String value, StringBuilder result) {
+ if (value != null) {
+ result.append('\n');
+ result.append(value);
+ }
+ }
+
+ protected static void maybeAppend(String[] value, StringBuilder result) {
+ if (value != null) {
+ for (String s : value) {
+ result.append('\n');
+ result.append(s);
+ }
+ }
+ }
+
+ protected static String[] maybeWrap(String value) {
+ return value == null ? null : new String[] { value };
+ }
+
+ protected static String unescapeBackslash(String escaped) {
+ int backslash = escaped.indexOf((int) '\\');
+ if (backslash < 0) {
+ return escaped;
+ }
+ int max = escaped.length();
+ StringBuilder unescaped = new StringBuilder(max - 1);
+ unescaped.append(escaped.toCharArray(), 0, backslash);
+ boolean nextIsEscaped = false;
+ for (int i = backslash; i < max; i++) {
+ char c = escaped.charAt(i);
+ if (nextIsEscaped || c != '\\') {
+ unescaped.append(c);
+ nextIsEscaped = false;
+ } else {
+ nextIsEscaped = true;
+ }
+ }
+ return unescaped.toString();
+ }
+
+ protected static int parseHexDigit(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ }
+ if (c >= 'a' && c <= 'f') {
+ return 10 + (c - 'a');
+ }
+ if (c >= 'A' && c <= 'F') {
+ return 10 + (c - 'A');
+ }
+ return -1;
+ }
+
+ protected static boolean isStringOfDigits(CharSequence value, int length) {
+ return value != null && length > 0 && length == value.length() && DIGITS.matcher(value).matches();
+ }
+
+ protected static boolean isSubstringOfDigits(CharSequence value, int offset, int length) {
+ if (value == null || length <= 0) {
+ return false;
+ }
+ int max = offset + length;
+ return value.length() >= max && DIGITS.matcher(value.subSequence(offset, max)).matches();
+ }
+
+ static Map parseNameValuePairs(String uri) {
+ int paramStart = uri.indexOf('?');
+ if (paramStart < 0) {
+ return null;
+ }
+ Map result = new HashMap<>(3);
+ for (String keyValue : AMPERSAND.split(uri.substring(paramStart + 1))) {
+ appendKeyValue(keyValue, result);
+ }
+ return result;
+ }
+
+ private static void appendKeyValue(CharSequence keyValue, Map result) {
+ String[] keyValueTokens = EQUALS.split(keyValue, 2);
+ if (keyValueTokens.length == 2) {
+ String key = keyValueTokens[0];
+ String value = keyValueTokens[1];
+ try {
+ value = urlDecode(value);
+ result.put(key, value);
+ } catch (IllegalArgumentException iae) {
+ // continue; invalid data such as an escape like %0t
+ }
+ }
+ }
+
+ static String urlDecode(String encoded) {
+ try {
+ return URLDecoder.decode(encoded, "UTF-8");
+ } catch (UnsupportedEncodingException uee) {
+ throw new IllegalStateException(uee); // can't happen
+ }
+ }
+
+ static String[] matchPrefixedField(String prefix, String rawText, char endChar, boolean trim) {
+ List matches = null;
+ int i = 0;
+ int max = rawText.length();
+ while (i < max) {
+ i = rawText.indexOf(prefix, i);
+ if (i < 0) {
+ break;
+ }
+ i += prefix.length(); // Skip past this prefix we found to start
+ int start = i; // Found the start of a match here
+ boolean more = true;
+ while (more) {
+ i = rawText.indexOf((int) endChar, i);
+ if (i < 0) {
+ // No terminating end character? uh, done. Set i such that loop terminates and break
+ i = rawText.length();
+ more = false;
+ } else if (rawText.charAt(i - 1) == '\\') {
+ // semicolon was escaped so continue
+ i++;
+ } else {
+ // found a match
+ if (matches == null) {
+ matches = new ArrayList<>(3); // lazy init
+ }
+ String element = unescapeBackslash(rawText.substring(start, i));
+ if (trim) {
+ element = element.trim();
+ }
+ if (!element.isEmpty()) {
+ matches.add(element);
+ }
+ i++;
+ more = false;
+ }
+ }
+ }
+ if (matches == null || matches.isEmpty()) {
+ return null;
+ }
+ return matches.toArray(new String[matches.size()]);
+ }
+
+ static String matchSinglePrefixedField(String prefix, String rawText, char endChar, boolean trim) {
+ String[] matches = matchPrefixedField(prefix, rawText, endChar, trim);
+ return matches == null ? null : matches[0];
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSMMSResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSMMSResultParser.java
new file mode 100644
index 000000000..a8d65d315
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSMMSResultParser.java
@@ -0,0 +1,109 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Parses an "sms:" URI result, which specifies a number to SMS.
+ * See RFC 5724 on this.
+ *
+ * This class supports "via" syntax for numbers, which is not part of the spec.
+ * For example "+12125551212;via=+12124440101" may appear as a number.
+ * It also supports a "subject" query parameter, which is not mentioned in the spec.
+ * These are included since they were mentioned in earlier IETF drafts and might be
+ * used.
+ *
+ * This actually also parses URIs starting with "mms:" and treats them all the same way,
+ * and effectively converts them to an "sms:" URI for purposes of forwarding to the platform.
+ *
+ * @author Sean Owen
+ */
+public final class SMSMMSResultParser extends ResultParser {
+
+ @Override
+ public SMSParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!(rawText.startsWith("sms:") || rawText.startsWith("SMS:") ||
+ rawText.startsWith("mms:") || rawText.startsWith("MMS:"))) {
+ return null;
+ }
+
+ // Check up front if this is a URI syntax string with query arguments
+ Map nameValuePairs = parseNameValuePairs(rawText);
+ String subject = null;
+ String body = null;
+ boolean querySyntax = false;
+ if (nameValuePairs != null && !nameValuePairs.isEmpty()) {
+ subject = nameValuePairs.get("subject");
+ body = nameValuePairs.get("body");
+ querySyntax = true;
+ }
+
+ // Drop sms, query portion
+ int queryStart = rawText.indexOf('?', 4);
+ String smsURIWithoutQuery;
+ // If it's not query syntax, the question mark is part of the subject or message
+ if (queryStart < 0 || !querySyntax) {
+ smsURIWithoutQuery = rawText.substring(4);
+ } else {
+ smsURIWithoutQuery = rawText.substring(4, queryStart);
+ }
+
+ int lastComma = -1;
+ int comma;
+ List numbers = new ArrayList<>(1);
+ List vias = new ArrayList<>(1);
+ while ((comma = smsURIWithoutQuery.indexOf(',', lastComma + 1)) > lastComma) {
+ String numberPart = smsURIWithoutQuery.substring(lastComma + 1, comma);
+ addNumberVia(numbers, vias, numberPart);
+ lastComma = comma;
+ }
+ addNumberVia(numbers, vias, smsURIWithoutQuery.substring(lastComma + 1));
+
+ return new SMSParsedResult(numbers.toArray(new String[numbers.size()]),
+ vias.toArray(new String[vias.size()]),
+ subject,
+ body);
+ }
+
+ private static void addNumberVia(Collection numbers,
+ Collection vias,
+ String numberPart) {
+ int numberEnd = numberPart.indexOf(';');
+ if (numberEnd < 0) {
+ numbers.add(numberPart);
+ vias.add(null);
+ } else {
+ numbers.add(numberPart.substring(0, numberEnd));
+ String maybeVia = numberPart.substring(numberEnd + 1);
+ String via;
+ if (maybeVia.startsWith("via=")) {
+ via = maybeVia.substring(4);
+ } else {
+ via = null;
+ }
+ vias.add(via);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSParsedResult.java
new file mode 100644
index 000000000..69c806c84
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSParsedResult.java
@@ -0,0 +1,111 @@
+/*
+ * 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.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class SMSParsedResult extends ParsedResult {
+
+ private final String[] numbers;
+ private final String[] vias;
+ private final String subject;
+ private final String body;
+
+ public SMSParsedResult(String number,
+ String via,
+ String subject,
+ String body) {
+ super(ParsedResultType.SMS);
+ this.numbers = new String[] {number};
+ this.vias = new String[] {via};
+ this.subject = subject;
+ this.body = body;
+ }
+
+ public SMSParsedResult(String[] numbers,
+ String[] vias,
+ String subject,
+ String body) {
+ super(ParsedResultType.SMS);
+ this.numbers = numbers;
+ this.vias = vias;
+ this.subject = subject;
+ this.body = body;
+ }
+
+ public String getSMSURI() {
+ StringBuilder result = new StringBuilder();
+ result.append("sms:");
+ boolean first = true;
+ for (int i = 0; i < numbers.length; i++) {
+ if (first) {
+ first = false;
+ } else {
+ result.append(',');
+ }
+ result.append(numbers[i]);
+ if (vias != null && vias[i] != null) {
+ result.append(";via=");
+ result.append(vias[i]);
+ }
+ }
+ boolean hasBody = body != null;
+ boolean hasSubject = subject != null;
+ if (hasBody || hasSubject) {
+ result.append('?');
+ if (hasBody) {
+ result.append("body=");
+ result.append(body);
+ }
+ if (hasSubject) {
+ if (hasBody) {
+ result.append('&');
+ }
+ result.append("subject=");
+ result.append(subject);
+ }
+ }
+ return result.toString();
+ }
+
+ public String[] getNumbers() {
+ return numbers;
+ }
+
+ public String[] getVias() {
+ return vias;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(100);
+ maybeAppend(numbers, result);
+ maybeAppend(subject, result);
+ maybeAppend(body, result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSTOMMSTOResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSTOMMSTOResultParser.java
new file mode 100644
index 000000000..076056587
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMSTOMMSTOResultParser.java
@@ -0,0 +1,52 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses an "smsto:" URI result, whose format is not standardized but appears to be like:
+ * {@code smsto:number(:body)}.
+ *
+ * This actually also parses URIs starting with "smsto:", "mmsto:", "SMSTO:", and
+ * "MMSTO:", and treats them all the same way, and effectively converts them to an "sms:" URI
+ * for purposes of forwarding to the platform.
+ *
+ * @author Sean Owen
+ */
+public final class SMSTOMMSTOResultParser extends ResultParser {
+
+ @Override
+ public SMSParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!(rawText.startsWith("smsto:") || rawText.startsWith("SMSTO:") ||
+ rawText.startsWith("mmsto:") || rawText.startsWith("MMSTO:"))) {
+ return null;
+ }
+ // Thanks to dominik.wild for suggesting this enhancement to support
+ // smsto:number:body URIs
+ String number = rawText.substring(6);
+ String body = null;
+ int bodyStart = number.indexOf(':');
+ if (bodyStart >= 0) {
+ body = number.substring(bodyStart + 1);
+ number = number.substring(0, bodyStart);
+ }
+ return new SMSParsedResult(number, null, null, body);
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMTPResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMTPResultParser.java
new file mode 100644
index 000000000..7c69696c0
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/SMTPResultParser.java
@@ -0,0 +1,51 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses an "smtp:" URI result, whose format is not standardized but appears to be like:
+ * {@code smtp[:subject[:body]]}.
+ *
+ * @author Sean Owen
+ */
+public final class SMTPResultParser extends ResultParser {
+
+ @Override
+ public EmailAddressParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!(rawText.startsWith("smtp:") || rawText.startsWith("SMTP:"))) {
+ return null;
+ }
+ String emailAddress = rawText.substring(5);
+ String subject = null;
+ String body = null;
+ int colon = emailAddress.indexOf(':');
+ if (colon >= 0) {
+ subject = emailAddress.substring(colon + 1);
+ emailAddress = emailAddress.substring(0, colon);
+ colon = subject.indexOf(':');
+ if (colon >= 0) {
+ body = subject.substring(colon + 1);
+ subject = subject.substring(0, colon);
+ }
+ }
+ String mailtoURI = "mailto:" + emailAddress;
+ return new EmailAddressParsedResult(emailAddress, subject, body, mailtoURI);
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/TelParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/TelParsedResult.java
new file mode 100644
index 000000000..8b6c6f879
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/TelParsedResult.java
@@ -0,0 +1,55 @@
+/*
+ * 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.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class TelParsedResult extends ParsedResult {
+
+ private final String number;
+ private final String telURI;
+ private final String title;
+
+ public TelParsedResult(String number, String telURI, String title) {
+ super(ParsedResultType.TEL);
+ this.number = number;
+ this.telURI = telURI;
+ this.title = title;
+ }
+
+ public String getNumber() {
+ return number;
+ }
+
+ public String getTelURI() {
+ return telURI;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(20);
+ maybeAppend(number, result);
+ maybeAppend(title, result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/TelResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/TelResultParser.java
new file mode 100644
index 000000000..e4bca1fba
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/TelResultParser.java
@@ -0,0 +1,42 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses a "tel:" URI result, which specifies a phone number.
+ *
+ * @author Sean Owen
+ */
+public final class TelResultParser extends ResultParser {
+
+ @Override
+ public TelParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("tel:") && !rawText.startsWith("TEL:")) {
+ return null;
+ }
+ // Normalize "TEL:" to "tel:"
+ String telURI = rawText.startsWith("TEL:") ? "tel:" + rawText.substring(4) : rawText;
+ // Drop tel, query portion
+ int queryStart = rawText.indexOf('?', 4);
+ String number = queryStart < 0 ? rawText.substring(4) : rawText.substring(4, queryStart);
+ return new TelParsedResult(number, telURI, null);
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/TextParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/TextParsedResult.java
new file mode 100644
index 000000000..9cc408ec6
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/TextParsedResult.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 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.client.result;
+
+/**
+ * A simple result type encapsulating a string that has no further
+ * interpretation.
+ *
+ * @author Sean Owen
+ */
+public final class TextParsedResult extends ParsedResult {
+
+ private final String text;
+ private final String language;
+
+ public TextParsedResult(String text, String language) {
+ super(ParsedResultType.TEXT);
+ this.text = text;
+ this.language = language;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ return text;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/URIParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/URIParsedResult.java
new file mode 100644
index 000000000..d36c6500b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/URIParsedResult.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2007 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.client.result;
+
+import java.util.regex.Pattern;
+
+/**
+ * @author Sean Owen
+ */
+public final class URIParsedResult extends ParsedResult {
+
+ private static final Pattern USER_IN_HOST = Pattern.compile(":/*([^/@]+)@[^/]+");
+
+ private final String uri;
+ private final String title;
+
+ public URIParsedResult(String uri, String title) {
+ super(ParsedResultType.URI);
+ this.uri = massageURI(uri);
+ this.title = title;
+ }
+
+ public String getURI() {
+ return uri;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * @return true if the URI contains suspicious patterns that may suggest it intends to
+ * mislead the user about its true nature. At the moment this looks for the presence
+ * of user/password syntax in the host/authority portion of a URI which may be used
+ * in attempts to make the URI's host appear to be other than it is. Example:
+ * http://yourbank.com@phisher.com This URI connects to phisher.com but may appear
+ * to connect to yourbank.com at first glance.
+ */
+ public boolean isPossiblyMaliciousURI() {
+ return USER_IN_HOST.matcher(uri).find();
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(30);
+ maybeAppend(title, result);
+ maybeAppend(uri, result);
+ return result.toString();
+ }
+
+ /**
+ * Transforms a string that represents a URI into something more proper, by adding or canonicalizing
+ * the protocol.
+ */
+ private static String massageURI(String uri) {
+ uri = uri.trim();
+ int protocolEnd = uri.indexOf(':');
+ if (protocolEnd < 0) {
+ // No protocol, assume http
+ uri = "http://" + uri;
+ } else if (isColonFollowedByPortNumber(uri, protocolEnd)) {
+ // Found a colon, but it looks like it is after the host, so the protocol is still missing
+ uri = "http://" + uri;
+ }
+ return uri;
+ }
+
+ private static boolean isColonFollowedByPortNumber(String uri, int protocolEnd) {
+ int start = protocolEnd + 1;
+ int nextSlash = uri.indexOf('/', start);
+ if (nextSlash < 0) {
+ nextSlash = uri.length();
+ }
+ return ResultParser.isSubstringOfDigits(uri, start, nextSlash - start);
+ }
+
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/URIResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/URIResultParser.java
new file mode 100644
index 000000000..56839822a
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/URIResultParser.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2007 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tries to parse results that are a URI of some kind.
+ *
+ * @author Sean Owen
+ */
+public final class URIResultParser extends ResultParser {
+
+ private static final Pattern URL_WITH_PROTOCOL_PATTERN = Pattern.compile("[a-zA-Z0-9]{2,}:");
+ private static final Pattern URL_WITHOUT_PROTOCOL_PATTERN = Pattern.compile(
+ "([a-zA-Z0-9\\-]+\\.)+[a-zA-Z]{2,}" + // host name elements
+ "(:\\d{1,5})?" + // maybe port
+ "(/|\\?|$)"); // query, path or nothing
+
+ @Override
+ public URIParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ // We specifically handle the odd "URL" scheme here for simplicity and add "URI" for fun
+ // Assume anything starting this way really means to be a URI
+ if (rawText.startsWith("URL:") || rawText.startsWith("URI:")) {
+ return new URIParsedResult(rawText.substring(4).trim(), null);
+ }
+ rawText = rawText.trim();
+ return isBasicallyValidURI(rawText) ? new URIParsedResult(rawText, null) : null;
+ }
+
+ static boolean isBasicallyValidURI(String uri) {
+ if (uri.contains(" ")) {
+ // Quick hack check for a common case
+ return false;
+ }
+ Matcher m = URL_WITH_PROTOCOL_PATTERN.matcher(uri);
+ if (m.find() && m.start() == 0) { // match at start only
+ return true;
+ }
+ m = URL_WITHOUT_PROTOCOL_PATTERN.matcher(uri);
+ return m.find() && m.start() == 0;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/URLTOResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/URLTOResultParser.java
new file mode 100644
index 000000000..fd259007e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/URLTOResultParser.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 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.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses the "URLTO" result format, which is of the form "URLTO:[title]:[url]".
+ * This seems to be used sometimes, but I am not able to find documentation
+ * on its origin or official format?
+ *
+ * @author Sean Owen
+ */
+public final class URLTOResultParser extends ResultParser {
+
+ @Override
+ public URIParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("urlto:") && !rawText.startsWith("URLTO:")) {
+ return null;
+ }
+ int titleEnd = rawText.indexOf(':', 6);
+ if (titleEnd < 0) {
+ return null;
+ }
+ String title = titleEnd <= 6 ? null : rawText.substring(6, titleEnd);
+ String uri = rawText.substring(titleEnd + 1);
+ return new URIParsedResult(uri, title);
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/VCardResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/VCardResultParser.java
new file mode 100644
index 000000000..98078b3fb
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/VCardResultParser.java
@@ -0,0 +1,357 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses contact information formatted according to the VCard (2.1) format. This is not a complete
+ * implementation but should parse information as commonly encoded in 2D barcodes.
+ *
+ * @author Sean Owen
+ */
+public final class VCardResultParser extends ResultParser {
+
+ private static final Pattern BEGIN_VCARD = Pattern.compile("BEGIN:VCARD", Pattern.CASE_INSENSITIVE);
+ private static final Pattern VCARD_LIKE_DATE = Pattern.compile("\\d{4}-?\\d{2}-?\\d{2}");
+ private static final Pattern CR_LF_SPACE_TAB = Pattern.compile("\r\n[ \t]");
+ private static final Pattern NEWLINE_ESCAPE = Pattern.compile("\\\\[nN]");
+ private static final Pattern VCARD_ESCAPES = Pattern.compile("\\\\([,;\\\\])");
+ private static final Pattern EQUALS = Pattern.compile("=");
+ private static final Pattern SEMICOLON = Pattern.compile(";");
+ private static final Pattern UNESCAPED_SEMICOLONS = Pattern.compile("(?> names = matchVCardPrefixedField("FN", rawText, true, false);
+ if (names == null) {
+ // If no display names found, look for regular name fields and format them
+ names = matchVCardPrefixedField("N", rawText, true, false);
+ formatNames(names);
+ }
+ List nicknameString = matchSingleVCardPrefixedField("NICKNAME", rawText, true, false);
+ String[] nicknames = nicknameString == null ? null : COMMA.split(nicknameString.get(0));
+ List> phoneNumbers = matchVCardPrefixedField("TEL", rawText, true, false);
+ List> emails = matchVCardPrefixedField("EMAIL", rawText, true, false);
+ List note = matchSingleVCardPrefixedField("NOTE", rawText, false, false);
+ List> addresses = matchVCardPrefixedField("ADR", rawText, true, true);
+ List org = matchSingleVCardPrefixedField("ORG", rawText, true, true);
+ List birthday = matchSingleVCardPrefixedField("BDAY", rawText, true, false);
+ if (birthday != null && !isLikeVCardDate(birthday.get(0))) {
+ birthday = null;
+ }
+ List title = matchSingleVCardPrefixedField("TITLE", rawText, true, false);
+ List> urls = matchVCardPrefixedField("URL", rawText, true, false);
+ List instantMessenger = matchSingleVCardPrefixedField("IMPP", rawText, true, false);
+ List geoString = matchSingleVCardPrefixedField("GEO", rawText, true, false);
+ String[] geo = geoString == null ? null : SEMICOLON_OR_COMMA.split(geoString.get(0));
+ if (geo != null && geo.length != 2) {
+ geo = null;
+ }
+ return new AddressBookParsedResult(toPrimaryValues(names),
+ nicknames,
+ null,
+ toPrimaryValues(phoneNumbers),
+ toTypes(phoneNumbers),
+ toPrimaryValues(emails),
+ toTypes(emails),
+ toPrimaryValue(instantMessenger),
+ toPrimaryValue(note),
+ toPrimaryValues(addresses),
+ toTypes(addresses),
+ toPrimaryValue(org),
+ toPrimaryValue(birthday),
+ toPrimaryValue(title),
+ toPrimaryValues(urls),
+ geo);
+ }
+
+ static List> matchVCardPrefixedField(CharSequence prefix,
+ String rawText,
+ boolean trim,
+ boolean parseFieldDivider) {
+ List> matches = null;
+ int i = 0;
+ int max = rawText.length();
+
+ while (i < max) {
+
+ // At start or after newline, match prefix, followed by optional metadata
+ // (led by ;) ultimately ending in colon
+ Matcher matcher = Pattern.compile("(?:^|\n)" + prefix + "(?:;([^:]*))?:",
+ Pattern.CASE_INSENSITIVE).matcher(rawText);
+ if (i > 0) {
+ i--; // Find from i-1 not i since looking at the preceding character
+ }
+ if (!matcher.find(i)) {
+ break;
+ }
+ i = matcher.end(0); // group 0 = whole pattern; end(0) is past final colon
+
+ String metadataString = matcher.group(1); // group 1 = metadata substring
+ List metadata = null;
+ boolean quotedPrintable = false;
+ String quotedPrintableCharset = null;
+ if (metadataString != null) {
+ for (String metadatum : SEMICOLON.split(metadataString)) {
+ if (metadata == null) {
+ metadata = new ArrayList<>(1);
+ }
+ metadata.add(metadatum);
+ String[] metadatumTokens = EQUALS.split(metadatum, 2);
+ if (metadatumTokens.length > 1) {
+ String key = metadatumTokens[0];
+ String value = metadatumTokens[1];
+ if ("ENCODING".equalsIgnoreCase(key) && "QUOTED-PRINTABLE".equalsIgnoreCase(value)) {
+ quotedPrintable = true;
+ } else if ("CHARSET".equalsIgnoreCase(key)) {
+ quotedPrintableCharset = value;
+ }
+ }
+ }
+ }
+
+ int matchStart = i; // Found the start of a match here
+
+ while ((i = rawText.indexOf((int) '\n', i)) >= 0) { // Really, end in \r\n
+ if (i < rawText.length() - 1 && // But if followed by tab or space,
+ (rawText.charAt(i+1) == ' ' || // this is only a continuation
+ rawText.charAt(i+1) == '\t')) {
+ i += 2; // Skip \n and continutation whitespace
+ } else if (quotedPrintable && // If preceded by = in quoted printable
+ ((i >= 1 && rawText.charAt(i-1) == '=') || // this is a continuation
+ (i >= 2 && rawText.charAt(i-2) == '='))) {
+ i++; // Skip \n
+ } else {
+ break;
+ }
+ }
+
+ if (i < 0) {
+ // No terminating end character? uh, done. Set i such that loop terminates and break
+ i = max;
+ } else if (i > matchStart) {
+ // found a match
+ if (matches == null) {
+ matches = new ArrayList<>(1); // lazy init
+ }
+ if (i >= 1 && rawText.charAt(i-1) == '\r') {
+ i--; // Back up over \r, which really should be there
+ }
+ String element = rawText.substring(matchStart, i);
+ if (trim) {
+ element = element.trim();
+ }
+ if (quotedPrintable) {
+ element = decodeQuotedPrintable(element, quotedPrintableCharset);
+ if (parseFieldDivider) {
+ element = UNESCAPED_SEMICOLONS.matcher(element).replaceAll("\n").trim();
+ }
+ } else {
+ if (parseFieldDivider) {
+ element = UNESCAPED_SEMICOLONS.matcher(element).replaceAll("\n").trim();
+ }
+ element = CR_LF_SPACE_TAB.matcher(element).replaceAll("");
+ element = NEWLINE_ESCAPE.matcher(element).replaceAll("\n");
+ element = VCARD_ESCAPES.matcher(element).replaceAll("$1");
+ }
+ if (metadata == null) {
+ List match = new ArrayList<>(1);
+ match.add(element);
+ matches.add(match);
+ } else {
+ metadata.add(0, element);
+ matches.add(metadata);
+ }
+ i++;
+ } else {
+ i++;
+ }
+
+ }
+
+ return matches;
+ }
+
+ private static String decodeQuotedPrintable(CharSequence value, String charset) {
+ int length = value.length();
+ StringBuilder result = new StringBuilder(length);
+ ByteArrayOutputStream fragmentBuffer = new ByteArrayOutputStream();
+ for (int i = 0; i < length; i++) {
+ char c = value.charAt(i);
+ switch (c) {
+ case '\r':
+ case '\n':
+ break;
+ case '=':
+ if (i < length - 2) {
+ char nextChar = value.charAt(i+1);
+ if (nextChar != '\r' && nextChar != '\n') {
+ char nextNextChar = value.charAt(i+2);
+ int firstDigit = parseHexDigit(nextChar);
+ int secondDigit = parseHexDigit(nextNextChar);
+ if (firstDigit >= 0 && secondDigit >= 0) {
+ fragmentBuffer.write((firstDigit << 4) + secondDigit);
+ } // else ignore it, assume it was incorrectly encoded
+ i += 2;
+ }
+ }
+ break;
+ default:
+ maybeAppendFragment(fragmentBuffer, charset, result);
+ result.append(c);
+ }
+ }
+ maybeAppendFragment(fragmentBuffer, charset, result);
+ return result.toString();
+ }
+
+ private static void maybeAppendFragment(ByteArrayOutputStream fragmentBuffer,
+ String charset,
+ StringBuilder result) {
+ if (fragmentBuffer.size() > 0) {
+ byte[] fragmentBytes = fragmentBuffer.toByteArray();
+ String fragment;
+ if (charset == null) {
+ fragment = new String(fragmentBytes, Charset.forName("UTF-8"));
+ } else {
+ try {
+ fragment = new String(fragmentBytes, charset);
+ } catch (UnsupportedEncodingException e) {
+ fragment = new String(fragmentBytes, Charset.forName("UTF-8"));
+ }
+ }
+ fragmentBuffer.reset();
+ result.append(fragment);
+ }
+ }
+
+ static List matchSingleVCardPrefixedField(CharSequence prefix,
+ String rawText,
+ boolean trim,
+ boolean parseFieldDivider) {
+ List> values = matchVCardPrefixedField(prefix, rawText, trim, parseFieldDivider);
+ return values == null || values.isEmpty() ? null : values.get(0);
+ }
+
+ private static String toPrimaryValue(List list) {
+ return list == null || list.isEmpty() ? null : list.get(0);
+ }
+
+ private static String[] toPrimaryValues(Collection> lists) {
+ if (lists == null || lists.isEmpty()) {
+ return null;
+ }
+ List result = new ArrayList<>(lists.size());
+ for (List list : lists) {
+ String value = list.get(0);
+ if (value != null && !value.isEmpty()) {
+ result.add(value);
+ }
+ }
+ return result.toArray(new String[lists.size()]);
+ }
+
+ private static String[] toTypes(Collection> lists) {
+ if (lists == null || lists.isEmpty()) {
+ return null;
+ }
+ List result = new ArrayList<>(lists.size());
+ for (List list : lists) {
+ String type = null;
+ for (int i = 1; i < list.size(); i++) {
+ String metadatum = list.get(i);
+ int equals = metadatum.indexOf('=');
+ if (equals < 0) {
+ // take the whole thing as a usable label
+ type = metadatum;
+ break;
+ }
+ if ("TYPE".equalsIgnoreCase(metadatum.substring(0, equals))) {
+ type = metadatum.substring(equals + 1);
+ break;
+ }
+ }
+ result.add(type);
+ }
+ return result.toArray(new String[lists.size()]);
+ }
+
+ private static boolean isLikeVCardDate(CharSequence value) {
+ return value == null || VCARD_LIKE_DATE.matcher(value).matches();
+ }
+
+ /**
+ * Formats name fields of the form "Public;John;Q.;Reverend;III" into a form like
+ * "Reverend John Q. Public III".
+ *
+ * @param names name values to format, in place
+ */
+ private static void formatNames(Iterable> names) {
+ if (names != null) {
+ for (List list : names) {
+ String name = list.get(0);
+ String[] components = new String[5];
+ int start = 0;
+ int end;
+ int componentIndex = 0;
+ while (componentIndex < components.length - 1 && (end = name.indexOf(';', start)) >= 0) {
+ components[componentIndex] = name.substring(start, end);
+ componentIndex++;
+ start = end + 1;
+ }
+ components[componentIndex] = name.substring(start);
+ StringBuilder newName = new StringBuilder(100);
+ maybeAppendComponent(components, 3, newName);
+ maybeAppendComponent(components, 1, newName);
+ maybeAppendComponent(components, 2, newName);
+ maybeAppendComponent(components, 0, newName);
+ maybeAppendComponent(components, 4, newName);
+ list.set(0, newName.toString().trim());
+ }
+ }
+ }
+
+ private static void maybeAppendComponent(String[] components, int i, StringBuilder newName) {
+ if (components[i] != null && !components[i].isEmpty()) {
+ if (newName.length() > 0) {
+ newName.append(' ');
+ }
+ newName.append(components[i]);
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/VEventResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/VEventResultParser.java
new file mode 100644
index 000000000..f6b2f398c
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/VEventResultParser.java
@@ -0,0 +1,119 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.List;
+
+/**
+ * Partially implements the iCalendar format's "VEVENT" format for specifying a
+ * calendar event. See RFC 2445. This supports SUMMARY, LOCATION, GEO, DTSTART and DTEND fields.
+ *
+ * @author Sean Owen
+ */
+public final class VEventResultParser extends ResultParser {
+
+ @Override
+ public CalendarParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ int vEventStart = rawText.indexOf("BEGIN:VEVENT");
+ if (vEventStart < 0) {
+ return null;
+ }
+
+ String summary = matchSingleVCardPrefixedField("SUMMARY", rawText, true);
+ String start = matchSingleVCardPrefixedField("DTSTART", rawText, true);
+ if (start == null) {
+ return null;
+ }
+ String end = matchSingleVCardPrefixedField("DTEND", rawText, true);
+ String duration = matchSingleVCardPrefixedField("DURATION", rawText, true);
+ String location = matchSingleVCardPrefixedField("LOCATION", rawText, true);
+ String organizer = stripMailto(matchSingleVCardPrefixedField("ORGANIZER", rawText, true));
+
+ String[] attendees = matchVCardPrefixedField("ATTENDEE", rawText, true);
+ if (attendees != null) {
+ for (int i = 0; i < attendees.length; i++) {
+ attendees[i] = stripMailto(attendees[i]);
+ }
+ }
+ String description = matchSingleVCardPrefixedField("DESCRIPTION", rawText, true);
+
+ String geoString = matchSingleVCardPrefixedField("GEO", rawText, true);
+ double latitude;
+ double longitude;
+ if (geoString == null) {
+ latitude = Double.NaN;
+ longitude = Double.NaN;
+ } else {
+ int semicolon = geoString.indexOf(';');
+ if (semicolon < 0) {
+ return null;
+ }
+ try {
+ latitude = Double.parseDouble(geoString.substring(0, semicolon));
+ longitude = Double.parseDouble(geoString.substring(semicolon + 1));
+ } catch (NumberFormatException ignored) {
+ return null;
+ }
+ }
+
+ try {
+ return new CalendarParsedResult(summary,
+ start,
+ end,
+ duration,
+ location,
+ organizer,
+ attendees,
+ description,
+ latitude,
+ longitude);
+ } catch (IllegalArgumentException ignored) {
+ return null;
+ }
+ }
+
+ private static String matchSingleVCardPrefixedField(CharSequence prefix,
+ String rawText,
+ boolean trim) {
+ List values = VCardResultParser.matchSingleVCardPrefixedField(prefix, rawText, trim, false);
+ return values == null || values.isEmpty() ? null : values.get(0);
+ }
+
+ private static String[] matchVCardPrefixedField(CharSequence prefix, String rawText, boolean trim) {
+ List> values = VCardResultParser.matchVCardPrefixedField(prefix, rawText, trim, false);
+ if (values == null || values.isEmpty()) {
+ return null;
+ }
+ int size = values.size();
+ String[] result = new String[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = values.get(i).get(0);
+ }
+ return result;
+ }
+
+ private static String stripMailto(String s) {
+ if (s != null && (s.startsWith("mailto:") || s.startsWith("MAILTO:"))) {
+ s = s.substring(7);
+ }
+ return s;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/VINParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/VINParsedResult.java
new file mode 100644
index 000000000..dddd7d022
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/VINParsedResult.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014 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.client.result;
+
+
+public final class VINParsedResult extends ParsedResult {
+
+ private final String vin;
+ private final String worldManufacturerID;
+ private final String vehicleDescriptorSection;
+ private final String vehicleIdentifierSection;
+ private final String countryCode;
+ private final String vehicleAttributes;
+ private final int modelYear;
+ private final char plantCode;
+ private final String sequentialNumber;
+
+ public VINParsedResult(String vin,
+ String worldManufacturerID,
+ String vehicleDescriptorSection,
+ String vehicleIdentifierSection,
+ String countryCode,
+ String vehicleAttributes,
+ int modelYear,
+ char plantCode,
+ String sequentialNumber) {
+ super(ParsedResultType.VIN);
+ this.vin = vin;
+ this.worldManufacturerID = worldManufacturerID;
+ this.vehicleDescriptorSection = vehicleDescriptorSection;
+ this.vehicleIdentifierSection = vehicleIdentifierSection;
+ this.countryCode = countryCode;
+ this.vehicleAttributes = vehicleAttributes;
+ this.modelYear = modelYear;
+ this.plantCode = plantCode;
+ this.sequentialNumber = sequentialNumber;
+ }
+
+ public String getVIN() {
+ return vin;
+ }
+
+ public String getWorldManufacturerID() {
+ return worldManufacturerID;
+ }
+
+ public String getVehicleDescriptorSection() {
+ return vehicleDescriptorSection;
+ }
+
+ public String getVehicleIdentifierSection() {
+ return vehicleIdentifierSection;
+ }
+
+ public String getCountryCode() {
+ return countryCode;
+ }
+
+ public String getVehicleAttributes() {
+ return vehicleAttributes;
+ }
+
+ public int getModelYear() {
+ return modelYear;
+ }
+
+ public char getPlantCode() {
+ return plantCode;
+ }
+
+ public String getSequentialNumber() {
+ return sequentialNumber;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(50);
+ result.append(worldManufacturerID).append(' ');
+ result.append(vehicleDescriptorSection).append(' ');
+ result.append(vehicleIdentifierSection).append('\n');
+ if (countryCode != null) {
+ result.append(countryCode).append(' ');
+ }
+ result.append(modelYear).append(' ');
+ result.append(plantCode).append(' ');
+ result.append(sequentialNumber).append('\n');
+ return result.toString();
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/VINResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/VINResultParser.java
new file mode 100644
index 000000000..0d1314cb6
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/VINResultParser.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2014 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.client.result;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+
+import java.util.regex.Pattern;
+
+/**
+ * Detects a result that is likely a vehicle identification number.
+ *
+ * @author Sean Owen
+ */
+public final class VINResultParser extends ResultParser {
+
+ private static final Pattern IOQ = Pattern.compile("[IOQ]");
+ private static final Pattern AZ09 = Pattern.compile("[A-Z0-9]{17}");
+
+ @Override
+ public VINParsedResult parse(Result result) {
+ if (result.getBarcodeFormat() != BarcodeFormat.CODE_39) {
+ return null;
+ }
+ String rawText = result.getText();
+ rawText = IOQ.matcher(rawText).replaceAll("").trim();
+ if (!AZ09.matcher(rawText).matches()) {
+ return null;
+ }
+ try {
+ if (!checkChecksum(rawText)) {
+ return null;
+ }
+ String wmi = rawText.substring(0, 3);
+ return new VINParsedResult(rawText,
+ wmi,
+ rawText.substring(3, 9),
+ rawText.substring(9, 17),
+ countryCode(wmi),
+ rawText.substring(3, 8),
+ modelYear(rawText.charAt(9)),
+ rawText.charAt(10),
+ rawText.substring(11));
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ }
+
+ private static boolean checkChecksum(CharSequence vin) {
+ int sum = 0;
+ for (int i = 0; i < vin.length(); i++) {
+ sum += vinPositionWeight(i + 1) * vinCharValue(vin.charAt(i));
+ }
+ char checkChar = vin.charAt(8);
+ char expectedCheckChar = checkChar(sum % 11);
+ return checkChar == expectedCheckChar;
+ }
+
+ private static int vinCharValue(char c) {
+ if (c >= 'A' && c <= 'I') {
+ return (c - 'A') + 1;
+ }
+ if (c >= 'J' && c <= 'R') {
+ return (c - 'J') + 1;
+ }
+ if (c >= 'S' && c <= 'Z') {
+ return (c - 'S') + 2;
+ }
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static int vinPositionWeight(int position) {
+ if (position >= 1 && position <= 7) {
+ return 9 - position;
+ }
+ if (position == 8) {
+ return 10;
+ }
+ if (position == 9) {
+ return 0;
+ }
+ if (position >= 10 && position <= 17) {
+ return 19 - position;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static char checkChar(int remainder) {
+ if (remainder < 10) {
+ return (char) ('0' + remainder);
+ }
+ if (remainder == 10) {
+ return 'X';
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static int modelYear(char c) {
+ if (c >= 'E' && c <= 'H') {
+ return (c - 'E') + 1984;
+ }
+ if (c >= 'J' && c <= 'N') {
+ return (c - 'J') + 1988;
+ }
+ if (c == 'P') {
+ return 1993;
+ }
+ if (c >= 'R' && c <= 'T') {
+ return (c - 'R') + 1994;
+ }
+ if (c >= 'V' && c <= 'Y') {
+ return (c - 'V') + 1997;
+ }
+ if (c >= '1' && c <= '9') {
+ return (c - '1') + 2001;
+ }
+ if (c >= 'A' && c <= 'D') {
+ return (c - 'A') + 2010;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static String countryCode(CharSequence wmi) {
+ char c1 = wmi.charAt(0);
+ char c2 = wmi.charAt(1);
+ switch (c1) {
+ case '1':
+ case '4':
+ case '5':
+ return "US";
+ case '2':
+ return "CA";
+ case '3':
+ if (c2 >= 'A' && c2 <= 'W') {
+ return "MX";
+ }
+ break;
+ case '9':
+ if ((c2 >= 'A' && c2 <= 'E') || (c2 >= '3' && c2 <= '9')) {
+ return "BR";
+ }
+ break;
+ case 'J':
+ if (c2 >= 'A' && c2 <= 'T') {
+ return "JP";
+ }
+ break;
+ case 'K':
+ if (c2 >= 'L' && c2 <= 'R') {
+ return "KO";
+ }
+ break;
+ case 'L':
+ return "CN";
+ case 'M':
+ if (c2 >= 'A' && c2 <= 'E') {
+ return "IN";
+ }
+ break;
+ case 'S':
+ if (c2 >= 'A' && c2 <= 'M') {
+ return "UK";
+ }
+ if (c2 >= 'N' && c2 <= 'T') {
+ return "DE";
+ }
+ break;
+ case 'V':
+ if (c2 >= 'F' && c2 <= 'R') {
+ return "FR";
+ }
+ if (c2 >= 'S' && c2 <= 'W') {
+ return "ES";
+ }
+ break;
+ case 'W':
+ return "DE";
+ case 'X':
+ if (c2 == '0' || (c2 >= '3' && c2 <= '9')) {
+ return "RU";
+ }
+ break;
+ case 'Z':
+ if (c2 >= 'A' && c2 <= 'R') {
+ return "IT";
+ }
+ break;
+ }
+ return null;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/WifiParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/WifiParsedResult.java
new file mode 100644
index 000000000..39dec351d
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/WifiParsedResult.java
@@ -0,0 +1,67 @@
+/*
+ * 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.client.result;
+
+/**
+ * @author Vikram Aggarwal
+ */
+public final class WifiParsedResult extends ParsedResult {
+
+ private final String ssid;
+ private final String networkEncryption;
+ private final String password;
+ private final boolean hidden;
+
+ public WifiParsedResult(String networkEncryption, String ssid, String password) {
+ this(networkEncryption, ssid, password, false);
+ }
+
+ public WifiParsedResult(String networkEncryption, String ssid, String password, boolean hidden) {
+ super(ParsedResultType.WIFI);
+ this.ssid = ssid;
+ this.networkEncryption = networkEncryption;
+ this.password = password;
+ this.hidden = hidden;
+ }
+
+ public String getSsid() {
+ return ssid;
+ }
+
+ public String getNetworkEncryption() {
+ return networkEncryption;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(80);
+ maybeAppend(ssid, result);
+ maybeAppend(networkEncryption, result);
+ maybeAppend(password, result);
+ maybeAppend(Boolean.toString(hidden), result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/client/result/WifiResultParser.java b/extern/zxing-core/src/main/java/com/google/zxing/client/result/WifiResultParser.java
new file mode 100644
index 000000000..b62151c72
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/client/result/WifiResultParser.java
@@ -0,0 +1,51 @@
+/*
+ * 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.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses a WIFI configuration string. Strings will be of the form:
+ *
+ * {@code WIFI:T:[network type];S:[network SSID];P:[network password];H:[hidden?];;}
+ *
+ * The fields can appear in any order. Only "S:" is required.
+ *
+ * @author Vikram Aggarwal
+ * @author Sean Owen
+ */
+public final class WifiResultParser extends ResultParser {
+
+ @Override
+ public WifiParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("WIFI:")) {
+ return null;
+ }
+ String ssid = matchSinglePrefixedField("S:", rawText, ';', false);
+ if (ssid == null || ssid.isEmpty()) {
+ return null;
+ }
+ String pass = matchSinglePrefixedField("P:", rawText, ';', false);
+ String type = matchSinglePrefixedField("T:", rawText, ';', false);
+ if (type == null) {
+ type = "nopass";
+ }
+ boolean hidden = Boolean.parseBoolean(matchSinglePrefixedField("H:", rawText, ';', false));
+ return new WifiParsedResult(type, ssid, pass, hidden);
+ }
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/BitArray.java b/extern/zxing-core/src/main/java/com/google/zxing/common/BitArray.java
new file mode 100644
index 000000000..0af7dd72c
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/BitArray.java
@@ -0,0 +1,375 @@
+/*
+ * Copyright 2007 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.common;
+
+import java.util.Arrays;
+
+/**
+ * A simple, fast array of bits, represented compactly by an array of ints internally.
+ *
+ * @author Sean Owen
+ */
+public final class BitArray implements Cloneable {
+
+ private int[] bits;
+ private int size;
+
+ public BitArray() {
+ this.size = 0;
+ this.bits = new int[1];
+ }
+
+ public BitArray(int size) {
+ this.size = size;
+ this.bits = makeArray(size);
+ }
+
+ // For testing only
+ BitArray(int[] bits, int size) {
+ this.bits = bits;
+ this.size = size;
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public int getSizeInBytes() {
+ return (size + 7) / 8;
+ }
+
+ private void ensureCapacity(int size) {
+ if (size > bits.length * 32) {
+ int[] newBits = makeArray(size);
+ System.arraycopy(bits, 0, newBits, 0, bits.length);
+ this.bits = newBits;
+ }
+ }
+
+ /**
+ * @param i bit to get
+ * @return true iff bit i is set
+ */
+ public boolean get(int i) {
+ return (bits[i / 32] & (1 << (i & 0x1F))) != 0;
+ }
+
+ /**
+ * Sets bit i.
+ *
+ * @param i bit to set
+ */
+ public void set(int i) {
+ bits[i / 32] |= 1 << (i & 0x1F);
+ }
+
+ /**
+ * Flips bit i.
+ *
+ * @param i bit to set
+ */
+ public void flip(int i) {
+ bits[i / 32] ^= 1 << (i & 0x1F);
+ }
+
+ /**
+ * @param from first bit to check
+ * @return index of first bit that is set, starting from the given index, or size if none are set
+ * at or beyond this given index
+ * @see #getNextUnset(int)
+ */
+ public int getNextSet(int from) {
+ if (from >= size) {
+ return size;
+ }
+ int bitsOffset = from / 32;
+ int currentBits = bits[bitsOffset];
+ // mask off lesser bits first
+ currentBits &= ~((1 << (from & 0x1F)) - 1);
+ while (currentBits == 0) {
+ if (++bitsOffset == bits.length) {
+ return size;
+ }
+ currentBits = bits[bitsOffset];
+ }
+ int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits);
+ return result > size ? size : result;
+ }
+
+ /**
+ * @param from index to start looking for unset bit
+ * @return index of next unset bit, or {@code size} if none are unset until the end
+ * @see #getNextSet(int)
+ */
+ public int getNextUnset(int from) {
+ if (from >= size) {
+ return size;
+ }
+ int bitsOffset = from / 32;
+ int currentBits = ~bits[bitsOffset];
+ // mask off lesser bits first
+ currentBits &= ~((1 << (from & 0x1F)) - 1);
+ while (currentBits == 0) {
+ if (++bitsOffset == bits.length) {
+ return size;
+ }
+ currentBits = ~bits[bitsOffset];
+ }
+ int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits);
+ return result > size ? size : result;
+ }
+
+ /**
+ * Sets a block of 32 bits, starting at bit i.
+ *
+ * @param i first bit to set
+ * @param newBits the new value of the next 32 bits. Note again that the least-significant bit
+ * corresponds to bit i, the next-least-significant to i+1, and so on.
+ */
+ public void setBulk(int i, int newBits) {
+ bits[i / 32] = newBits;
+ }
+
+ /**
+ * Sets a range of bits.
+ *
+ * @param start start of range, inclusive.
+ * @param end end of range, exclusive
+ */
+ public void setRange(int start, int end) {
+ if (end < start) {
+ throw new IllegalArgumentException();
+ }
+ if (end == start) {
+ return;
+ }
+ end--; // will be easier to treat this as the last actually set bit -- inclusive
+ int firstInt = start / 32;
+ int lastInt = end / 32;
+ for (int i = firstInt; i <= lastInt; i++) {
+ int firstBit = i > firstInt ? 0 : start & 0x1F;
+ int lastBit = i < lastInt ? 31 : end & 0x1F;
+ int mask;
+ if (firstBit == 0 && lastBit == 31) {
+ mask = -1;
+ } else {
+ mask = 0;
+ for (int j = firstBit; j <= lastBit; j++) {
+ mask |= 1 << j;
+ }
+ }
+ bits[i] |= mask;
+ }
+ }
+
+ /**
+ * Clears all bits (sets to false).
+ */
+ public void clear() {
+ int max = bits.length;
+ for (int i = 0; i < max; i++) {
+ bits[i] = 0;
+ }
+ }
+
+ /**
+ * Efficient method to check if a range of bits is set, or not set.
+ *
+ * @param start start of range, inclusive.
+ * @param end end of range, exclusive
+ * @param value if true, checks that bits in range are set, otherwise checks that they are not set
+ * @return true iff all bits are set or not set in range, according to value argument
+ * @throws IllegalArgumentException if end is less than or equal to start
+ */
+ public boolean isRange(int start, int end, boolean value) {
+ if (end < start) {
+ throw new IllegalArgumentException();
+ }
+ if (end == start) {
+ return true; // empty range matches
+ }
+ end--; // will be easier to treat this as the last actually set bit -- inclusive
+ int firstInt = start / 32;
+ int lastInt = end / 32;
+ for (int i = firstInt; i <= lastInt; i++) {
+ int firstBit = i > firstInt ? 0 : start & 0x1F;
+ int lastBit = i < lastInt ? 31 : end & 0x1F;
+ int mask;
+ if (firstBit == 0 && lastBit == 31) {
+ mask = -1;
+ } else {
+ mask = 0;
+ for (int j = firstBit; j <= lastBit; j++) {
+ mask |= 1 << j;
+ }
+ }
+
+ // Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is,
+ // equals the mask, or we're looking for 0s and the masked portion is not all 0s
+ if ((bits[i] & mask) != (value ? mask : 0)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void appendBit(boolean bit) {
+ ensureCapacity(size + 1);
+ if (bit) {
+ bits[size / 32] |= 1 << (size & 0x1F);
+ }
+ size++;
+ }
+
+ /**
+ * Appends the least-significant bits, from value, in order from most-significant to
+ * least-significant. For example, appending 6 bits from 0x000001E will append the bits
+ * 0, 1, 1, 1, 1, 0 in that order.
+ *
+ * @param value {@code int} containing bits to append
+ * @param numBits bits from value to append
+ */
+ public void appendBits(int value, int numBits) {
+ if (numBits < 0 || numBits > 32) {
+ throw new IllegalArgumentException("Num bits must be between 0 and 32");
+ }
+ ensureCapacity(size + numBits);
+ for (int numBitsLeft = numBits; numBitsLeft > 0; numBitsLeft--) {
+ appendBit(((value >> (numBitsLeft - 1)) & 0x01) == 1);
+ }
+ }
+
+ public void appendBitArray(BitArray other) {
+ int otherSize = other.size;
+ ensureCapacity(size + otherSize);
+ for (int i = 0; i < otherSize; i++) {
+ appendBit(other.get(i));
+ }
+ }
+
+ public void xor(BitArray other) {
+ if (bits.length != other.bits.length) {
+ throw new IllegalArgumentException("Sizes don't match");
+ }
+ for (int i = 0; i < bits.length; i++) {
+ // The last byte could be incomplete (i.e. not have 8 bits in
+ // it) but there is no problem since 0 XOR 0 == 0.
+ bits[i] ^= other.bits[i];
+ }
+ }
+
+ /**
+ *
+ * @param bitOffset first bit to start writing
+ * @param array array to write into. Bytes are written most-significant byte first. This is the opposite
+ * of the internal representation, which is exposed by {@link #getBitArray()}
+ * @param offset position in array to start writing
+ * @param numBytes how many bytes to write
+ */
+ public void toBytes(int bitOffset, byte[] array, int offset, int numBytes) {
+ for (int i = 0; i < numBytes; i++) {
+ int theByte = 0;
+ for (int j = 0; j < 8; j++) {
+ if (get(bitOffset)) {
+ theByte |= 1 << (7 - j);
+ }
+ bitOffset++;
+ }
+ array[offset + i] = (byte) theByte;
+ }
+ }
+
+ /**
+ * @return underlying array of ints. The first element holds the first 32 bits, and the least
+ * significant bit is bit 0.
+ */
+ public int[] getBitArray() {
+ return bits;
+ }
+
+ /**
+ * Reverses all bits in the array.
+ */
+ public void reverse() {
+ int[] newBits = new int[bits.length];
+ // reverse all int's first
+ int len = ((size-1) / 32);
+ int oldBitsLen = len + 1;
+ for (int i = 0; i < oldBitsLen; i++) {
+ long x = (long) bits[i];
+ x = ((x >> 1) & 0x55555555L) | ((x & 0x55555555L) << 1);
+ x = ((x >> 2) & 0x33333333L) | ((x & 0x33333333L) << 2);
+ x = ((x >> 4) & 0x0f0f0f0fL) | ((x & 0x0f0f0f0fL) << 4);
+ x = ((x >> 8) & 0x00ff00ffL) | ((x & 0x00ff00ffL) << 8);
+ x = ((x >> 16) & 0x0000ffffL) | ((x & 0x0000ffffL) << 16);
+ newBits[len - i] = (int) x;
+ }
+ // now correct the int's if the bit size isn't a multiple of 32
+ if (size != oldBitsLen * 32) {
+ int leftOffset = oldBitsLen * 32 - size;
+ int mask = 1;
+ for (int i = 0; i < 31 - leftOffset; i++) {
+ mask = (mask << 1) | 1;
+ }
+ int currentInt = (newBits[0] >> leftOffset) & mask;
+ for (int i = 1; i < oldBitsLen; i++) {
+ int nextInt = newBits[i];
+ currentInt |= nextInt << (32 - leftOffset);
+ newBits[i - 1] = currentInt;
+ currentInt = (nextInt >> leftOffset) & mask;
+ }
+ newBits[oldBitsLen - 1] = currentInt;
+ }
+ bits = newBits;
+ }
+
+ private static int[] makeArray(int size) {
+ return new int[(size + 31) / 32];
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BitArray)) {
+ return false;
+ }
+ BitArray other = (BitArray) o;
+ return size == other.size && Arrays.equals(bits, other.bits);
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * size + Arrays.hashCode(bits);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder(size);
+ for (int i = 0; i < size; i++) {
+ if ((i & 0x07) == 0) {
+ result.append(' ');
+ }
+ result.append(get(i) ? 'X' : '.');
+ }
+ return result.toString();
+ }
+
+ @Override
+ public BitArray clone() {
+ return new BitArray(bits.clone(), size);
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/BitMatrix.java b/extern/zxing-core/src/main/java/com/google/zxing/common/BitMatrix.java
new file mode 100755
index 000000000..d4f0d9b7b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/BitMatrix.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2007 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.common;
+
+import java.util.Arrays;
+
+/**
+ * Represents a 2D matrix of bits. In function arguments below, and throughout the common
+ * module, x is the column position, and y is the row position. The ordering is always x, y.
+ * The origin is at the top-left.
+ *
+ * Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins
+ * with a new int. This is done intentionally so that we can copy out a row into a BitArray very
+ * efficiently.
+ *
+ * The ordering of bits is row-major. Within each int, the least significant bits are used first,
+ * meaning they represent lower x values. This is compatible with BitArray's implementation.
+ *
+ * @author Sean Owen
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class BitMatrix implements Cloneable {
+
+ private final int width;
+ private final int height;
+ private final int rowSize;
+ private final int[] bits;
+
+ // A helper to construct a square matrix.
+ public BitMatrix(int dimension) {
+ this(dimension, dimension);
+ }
+
+ public BitMatrix(int width, int height) {
+ if (width < 1 || height < 1) {
+ throw new IllegalArgumentException("Both dimensions must be greater than 0");
+ }
+ this.width = width;
+ this.height = height;
+ this.rowSize = (width + 31) >> 5;
+ bits = new int[rowSize * height];
+ }
+
+ private BitMatrix(int width, int height, int rowSize, int[] bits) {
+ this.width = width;
+ this.height = height;
+ this.rowSize = rowSize;
+ this.bits = bits;
+ }
+
+ /**
+ * Gets the requested bit, where true means black.
+ *
+ * @param x The horizontal component (i.e. which column)
+ * @param y The vertical component (i.e. which row)
+ * @return value of given bit in matrix
+ */
+ public boolean get(int x, int y) {
+ int offset = y * rowSize + (x >> 5);
+ return ((bits[offset] >>> (x & 0x1f)) & 1) != 0;
+ }
+
+ /**
+ * Sets the given bit to true.
+ *
+ * @param x The horizontal component (i.e. which column)
+ * @param y The vertical component (i.e. which row)
+ */
+ public void set(int x, int y) {
+ int offset = y * rowSize + (x >> 5);
+ bits[offset] |= 1 << (x & 0x1f);
+ }
+
+ /**
+ * Flips the given bit.
+ *
+ * @param x The horizontal component (i.e. which column)
+ * @param y The vertical component (i.e. which row)
+ */
+ public void flip(int x, int y) {
+ int offset = y * rowSize + (x >> 5);
+ bits[offset] ^= 1 << (x & 0x1f);
+ }
+
+ /**
+ * Clears all bits (sets to false).
+ */
+ public void clear() {
+ int max = bits.length;
+ for (int i = 0; i < max; i++) {
+ bits[i] = 0;
+ }
+ }
+
+ /**
+ * Sets a square region of the bit matrix to true.
+ *
+ * @param left The horizontal position to begin at (inclusive)
+ * @param top The vertical position to begin at (inclusive)
+ * @param width The width of the region
+ * @param height The height of the region
+ */
+ public void setRegion(int left, int top, int width, int height) {
+ if (top < 0 || left < 0) {
+ throw new IllegalArgumentException("Left and top must be nonnegative");
+ }
+ if (height < 1 || width < 1) {
+ throw new IllegalArgumentException("Height and width must be at least 1");
+ }
+ int right = left + width;
+ int bottom = top + height;
+ if (bottom > this.height || right > this.width) {
+ throw new IllegalArgumentException("The region must fit inside the matrix");
+ }
+ for (int y = top; y < bottom; y++) {
+ int offset = y * rowSize;
+ for (int x = left; x < right; x++) {
+ bits[offset + (x >> 5)] |= 1 << (x & 0x1f);
+ }
+ }
+ }
+
+ /**
+ * A fast method to retrieve one row of data from the matrix as a BitArray.
+ *
+ * @param y The row to retrieve
+ * @param row An optional caller-allocated BitArray, will be allocated if null or too small
+ * @return The resulting BitArray - this reference should always be used even when passing
+ * your own row
+ */
+ public BitArray getRow(int y, BitArray row) {
+ if (row == null || row.getSize() < width) {
+ row = new BitArray(width);
+ } else {
+ row.clear();
+ }
+ int offset = y * rowSize;
+ for (int x = 0; x < rowSize; x++) {
+ row.setBulk(x << 5, bits[offset + x]);
+ }
+ return row;
+ }
+
+ /**
+ * @param y row to set
+ * @param row {@link BitArray} to copy from
+ */
+ public void setRow(int y, BitArray row) {
+ System.arraycopy(row.getBitArray(), 0, bits, y * rowSize, rowSize);
+ }
+
+ /**
+ * Modifies this {@code BitMatrix} to represent the same but rotated 180 degrees
+ */
+ public void rotate180() {
+ int width = getWidth();
+ int height = getHeight();
+ BitArray topRow = new BitArray(width);
+ BitArray bottomRow = new BitArray(width);
+ for (int i = 0; i < (height+1) / 2; i++) {
+ topRow = getRow(i, topRow);
+ bottomRow = getRow(height - 1 - i, bottomRow);
+ topRow.reverse();
+ bottomRow.reverse();
+ setRow(i, bottomRow);
+ setRow(height - 1 - i, topRow);
+ }
+ }
+
+ /**
+ * This is useful in detecting the enclosing rectangle of a 'pure' barcode.
+ *
+ * @return {@code left,top,width,height} enclosing rectangle of all 1 bits, or null if it is all white
+ */
+ public int[] getEnclosingRectangle() {
+ int left = width;
+ int top = height;
+ int right = -1;
+ int bottom = -1;
+
+ for (int y = 0; y < height; y++) {
+ for (int x32 = 0; x32 < rowSize; x32++) {
+ int theBits = bits[y * rowSize + x32];
+ if (theBits != 0) {
+ if (y < top) {
+ top = y;
+ }
+ if (y > bottom) {
+ bottom = y;
+ }
+ if (x32 * 32 < left) {
+ int bit = 0;
+ while ((theBits << (31 - bit)) == 0) {
+ bit++;
+ }
+ if ((x32 * 32 + bit) < left) {
+ left = x32 * 32 + bit;
+ }
+ }
+ if (x32 * 32 + 31 > right) {
+ int bit = 31;
+ while ((theBits >>> bit) == 0) {
+ bit--;
+ }
+ if ((x32 * 32 + bit) > right) {
+ right = x32 * 32 + bit;
+ }
+ }
+ }
+ }
+ }
+
+ int width = right - left;
+ int height = bottom - top;
+
+ if (width < 0 || height < 0) {
+ return null;
+ }
+
+ return new int[] {left, top, width, height};
+ }
+
+ /**
+ * This is useful in detecting a corner of a 'pure' barcode.
+ *
+ * @return {@code x,y} coordinate of top-left-most 1 bit, or null if it is all white
+ */
+ public int[] getTopLeftOnBit() {
+ int bitsOffset = 0;
+ while (bitsOffset < bits.length && bits[bitsOffset] == 0) {
+ bitsOffset++;
+ }
+ if (bitsOffset == bits.length) {
+ return null;
+ }
+ int y = bitsOffset / rowSize;
+ int x = (bitsOffset % rowSize) << 5;
+
+ int theBits = bits[bitsOffset];
+ int bit = 0;
+ while ((theBits << (31-bit)) == 0) {
+ bit++;
+ }
+ x += bit;
+ return new int[] {x, y};
+ }
+
+ public int[] getBottomRightOnBit() {
+ int bitsOffset = bits.length - 1;
+ while (bitsOffset >= 0 && bits[bitsOffset] == 0) {
+ bitsOffset--;
+ }
+ if (bitsOffset < 0) {
+ return null;
+ }
+
+ int y = bitsOffset / rowSize;
+ int x = (bitsOffset % rowSize) << 5;
+
+ int theBits = bits[bitsOffset];
+ int bit = 31;
+ while ((theBits >>> bit) == 0) {
+ bit--;
+ }
+ x += bit;
+
+ return new int[] {x, y};
+ }
+
+ /**
+ * @return The width of the matrix
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * @return The height of the matrix
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BitMatrix)) {
+ return false;
+ }
+ BitMatrix other = (BitMatrix) o;
+ return width == other.width && height == other.height && rowSize == other.rowSize &&
+ Arrays.equals(bits, other.bits);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = width;
+ hash = 31 * hash + width;
+ hash = 31 * hash + height;
+ hash = 31 * hash + rowSize;
+ hash = 31 * hash + Arrays.hashCode(bits);
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder(height * (width + 1));
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ result.append(get(x, y) ? "X " : " ");
+ }
+ result.append('\n');
+ }
+ return result.toString();
+ }
+
+ @Override
+ public BitMatrix clone() {
+ return new BitMatrix(width, height, rowSize, bits.clone());
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/BitSource.java b/extern/zxing-core/src/main/java/com/google/zxing/common/BitSource.java
new file mode 100755
index 000000000..45a3858ef
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/BitSource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2007 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.common;
+
+/**
+ * This provides an easy abstraction to read bits at a time from a sequence of bytes, where the
+ * number of bits read is not often a multiple of 8.
+ *
+ * This class is thread-safe but not reentrant -- unless the caller modifies the bytes array
+ * it passed in, in which case all bets are off.
+ *
+ * @author Sean Owen
+ */
+public final class BitSource {
+
+ private final byte[] bytes;
+ private int byteOffset;
+ private int bitOffset;
+
+ /**
+ * @param bytes bytes from which this will read bits. Bits will be read from the first byte first.
+ * Bits are read within a byte from most-significant to least-significant bit.
+ */
+ public BitSource(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ /**
+ * @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}.
+ */
+ public int getBitOffset() {
+ return bitOffset;
+ }
+
+ /**
+ * @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}.
+ */
+ public int getByteOffset() {
+ return byteOffset;
+ }
+
+ /**
+ * @param numBits number of bits to read
+ * @return int representing the bits read. The bits will appear as the least-significant
+ * bits of the int
+ * @throws IllegalArgumentException if numBits isn't in [1,32] or more than is available
+ */
+ public int readBits(int numBits) {
+ if (numBits < 1 || numBits > 32 || numBits > available()) {
+ throw new IllegalArgumentException(String.valueOf(numBits));
+ }
+
+ int result = 0;
+
+ // First, read remainder from current byte
+ if (bitOffset > 0) {
+ int bitsLeft = 8 - bitOffset;
+ int toRead = numBits < bitsLeft ? numBits : bitsLeft;
+ int bitsToNotRead = bitsLeft - toRead;
+ int mask = (0xFF >> (8 - toRead)) << bitsToNotRead;
+ result = (bytes[byteOffset] & mask) >> bitsToNotRead;
+ numBits -= toRead;
+ bitOffset += toRead;
+ if (bitOffset == 8) {
+ bitOffset = 0;
+ byteOffset++;
+ }
+ }
+
+ // Next read whole bytes
+ if (numBits > 0) {
+ while (numBits >= 8) {
+ result = (result << 8) | (bytes[byteOffset] & 0xFF);
+ byteOffset++;
+ numBits -= 8;
+ }
+
+ // Finally read a partial byte
+ if (numBits > 0) {
+ int bitsToNotRead = 8 - numBits;
+ int mask = (0xFF >> bitsToNotRead) << bitsToNotRead;
+ result = (result << numBits) | ((bytes[byteOffset] & mask) >> bitsToNotRead);
+ bitOffset += numBits;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * @return number of bits that can be read successfully
+ */
+ public int available() {
+ return 8 * (bytes.length - byteOffset) - bitOffset;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/CharacterSetECI.java b/extern/zxing-core/src/main/java/com/google/zxing/common/CharacterSetECI.java
new file mode 100644
index 000000000..141bee866
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/CharacterSetECI.java
@@ -0,0 +1,118 @@
+/*
+ * 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.common;
+
+import com.google.zxing.FormatException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1
+ * of ISO 18004.
+ *
+ * @author Sean Owen
+ */
+public enum CharacterSetECI {
+
+ // Enum name is a Java encoding valid for java.lang and java.io
+ Cp437(new int[]{0,2}),
+ ISO8859_1(new int[]{1,3}, "ISO-8859-1"),
+ ISO8859_2(4, "ISO-8859-2"),
+ ISO8859_3(5, "ISO-8859-3"),
+ ISO8859_4(6, "ISO-8859-4"),
+ ISO8859_5(7, "ISO-8859-5"),
+ ISO8859_6(8, "ISO-8859-6"),
+ ISO8859_7(9, "ISO-8859-7"),
+ ISO8859_8(10, "ISO-8859-8"),
+ ISO8859_9(11, "ISO-8859-9"),
+ ISO8859_10(12, "ISO-8859-10"),
+ ISO8859_11(13, "ISO-8859-11"),
+ ISO8859_13(15, "ISO-8859-13"),
+ ISO8859_14(16, "ISO-8859-14"),
+ ISO8859_15(17, "ISO-8859-15"),
+ ISO8859_16(18, "ISO-8859-16"),
+ SJIS(20, "Shift_JIS"),
+ Cp1250(21, "windows-1250"),
+ Cp1251(22, "windows-1251"),
+ Cp1252(23, "windows-1252"),
+ Cp1256(24, "windows-1256"),
+ UnicodeBigUnmarked(25, "UTF-16BE", "UnicodeBig"),
+ UTF8(26, "UTF-8"),
+ ASCII(new int[] {27, 170}, "US-ASCII"),
+ Big5(28),
+ GB18030(29, "GB2312", "EUC_CN", "GBK"),
+ EUC_KR(30, "EUC-KR");
+
+ private static final Map VALUE_TO_ECI = new HashMap<>();
+ private static final Map NAME_TO_ECI = new HashMap<>();
+ static {
+ for (CharacterSetECI eci : values()) {
+ for (int value : eci.values) {
+ VALUE_TO_ECI.put(value, eci);
+ }
+ NAME_TO_ECI.put(eci.name(), eci);
+ for (String name : eci.otherEncodingNames) {
+ NAME_TO_ECI.put(name, eci);
+ }
+ }
+ }
+
+ private final int[] values;
+ private final String[] otherEncodingNames;
+
+ CharacterSetECI(int value) {
+ this(new int[] {value});
+ }
+
+ CharacterSetECI(int value, String... otherEncodingNames) {
+ this.values = new int[] {value};
+ this.otherEncodingNames = otherEncodingNames;
+ }
+
+ CharacterSetECI(int[] values, String... otherEncodingNames) {
+ this.values = values;
+ this.otherEncodingNames = otherEncodingNames;
+ }
+
+ public int getValue() {
+ return values[0];
+ }
+
+ /**
+ * @param value character set ECI value
+ * @return {@link CharacterSetECI} representing ECI of given value, or null if it is legal but
+ * unsupported
+ * @throws FormatException if ECI value is invalid
+ */
+ public static CharacterSetECI getCharacterSetECIByValue(int value) throws FormatException {
+ if (value < 0 || value >= 900) {
+ throw FormatException.getFormatInstance();
+ }
+ return VALUE_TO_ECI.get(value);
+ }
+
+ /**
+ * @param name character set ECI encoding name
+ * @return CharacterSetECI representing ECI for character encoding, or null if it is legal
+ * but unsupported
+ */
+ public static CharacterSetECI getCharacterSetECIByName(String name) {
+ return NAME_TO_ECI.get(name);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/DecoderResult.java b/extern/zxing-core/src/main/java/com/google/zxing/common/DecoderResult.java
new file mode 100644
index 000000000..bfeacedc8
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/DecoderResult.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2007 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.common;
+
+import java.util.List;
+
+/**
+ * Encapsulates the result of decoding a matrix of bits. This typically
+ * applies to 2D barcode formats. For now it contains the raw bytes obtained,
+ * as well as a String interpretation of those bytes, if applicable.
+ *
+ * @author Sean Owen
+ */
+public final class DecoderResult {
+
+ private final byte[] rawBytes;
+ private final String text;
+ private final List byteSegments;
+ private final String ecLevel;
+ private Integer errorsCorrected;
+ private Integer erasures;
+ private Object other;
+ private final int structuredAppendParity;
+ private final int structuredAppendSequenceNumber;
+
+ public DecoderResult(byte[] rawBytes,
+ String text,
+ List byteSegments,
+ String ecLevel) {
+ this(rawBytes, text, byteSegments, ecLevel, -1, -1);
+ }
+
+ public DecoderResult(byte[] rawBytes,
+ String text,
+ List byteSegments,
+ String ecLevel,
+ int saSequence,
+ int saParity) {
+ this.rawBytes = rawBytes;
+ this.text = text;
+ this.byteSegments = byteSegments;
+ this.ecLevel = ecLevel;
+ this.structuredAppendParity = saParity;
+ this.structuredAppendSequenceNumber = saSequence;
+ }
+
+ public byte[] getRawBytes() {
+ return rawBytes;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public List getByteSegments() {
+ return byteSegments;
+ }
+
+ public String getECLevel() {
+ return ecLevel;
+ }
+
+ public Integer getErrorsCorrected() {
+ return errorsCorrected;
+ }
+
+ public void setErrorsCorrected(Integer errorsCorrected) {
+ this.errorsCorrected = errorsCorrected;
+ }
+
+ public Integer getErasures() {
+ return erasures;
+ }
+
+ public void setErasures(Integer erasures) {
+ this.erasures = erasures;
+ }
+
+ public Object getOther() {
+ return other;
+ }
+
+ public void setOther(Object other) {
+ this.other = other;
+ }
+
+ public boolean hasStructuredAppend() {
+ return structuredAppendParity >= 0 && structuredAppendSequenceNumber >= 0;
+ }
+
+ public int getStructuredAppendParity() {
+ return structuredAppendParity;
+ }
+
+ public int getStructuredAppendSequenceNumber() {
+ return structuredAppendSequenceNumber;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/DefaultGridSampler.java b/extern/zxing-core/src/main/java/com/google/zxing/common/DefaultGridSampler.java
new file mode 100644
index 000000000..4ab43fd68
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/DefaultGridSampler.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2007 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.common;
+
+import com.google.zxing.NotFoundException;
+
+/**
+ * @author Sean Owen
+ */
+public final class DefaultGridSampler extends GridSampler {
+
+ @Override
+ public BitMatrix sampleGrid(BitMatrix image,
+ int dimensionX,
+ int dimensionY,
+ float p1ToX, float p1ToY,
+ float p2ToX, float p2ToY,
+ float p3ToX, float p3ToY,
+ float p4ToX, float p4ToY,
+ float p1FromX, float p1FromY,
+ float p2FromX, float p2FromY,
+ float p3FromX, float p3FromY,
+ float p4FromX, float p4FromY) throws NotFoundException {
+
+ PerspectiveTransform transform = PerspectiveTransform.quadrilateralToQuadrilateral(
+ p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY,
+ p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY);
+
+ return sampleGrid(image, dimensionX, dimensionY, transform);
+ }
+
+ @Override
+ public BitMatrix sampleGrid(BitMatrix image,
+ int dimensionX,
+ int dimensionY,
+ PerspectiveTransform transform) throws NotFoundException {
+ if (dimensionX <= 0 || dimensionY <= 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ BitMatrix bits = new BitMatrix(dimensionX, dimensionY);
+ float[] points = new float[dimensionX << 1];
+ for (int y = 0; y < dimensionY; y++) {
+ int max = points.length;
+ float iValue = (float) y + 0.5f;
+ for (int x = 0; x < max; x += 2) {
+ points[x] = (float) (x >> 1) + 0.5f;
+ points[x + 1] = iValue;
+ }
+ transform.transformPoints(points);
+ // Quick check to see if points transformed to something inside the image;
+ // sufficient to check the endpoints
+ checkAndNudgePoints(image, points);
+ try {
+ for (int x = 0; x < max; x += 2) {
+ if (image.get((int) points[x], (int) points[x + 1])) {
+ // Black(-ish) pixel
+ bits.set(x >> 1, y);
+ }
+ }
+ } catch (ArrayIndexOutOfBoundsException aioobe) {
+ // This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting
+ // transform gets "twisted" such that it maps a straight line of points to a set of points
+ // whose endpoints are in bounds, but others are not. There is probably some mathematical
+ // way to detect this about the transformation that I don't know yet.
+ // This results in an ugly runtime exception despite our clever checks above -- can't have
+ // that. We could check each point's coordinates but that feels duplicative. We settle for
+ // catching and wrapping ArrayIndexOutOfBoundsException.
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+ return bits;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/DetectorResult.java b/extern/zxing-core/src/main/java/com/google/zxing/common/DetectorResult.java
new file mode 100644
index 000000000..0f3cf15e7
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/DetectorResult.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2007 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.common;
+
+import com.google.zxing.ResultPoint;
+
+/**
+ * Encapsulates the result of detecting a barcode in an image. This includes the raw
+ * matrix of black/white pixels corresponding to the barcode, and possibly points of interest
+ * in the image, like the location of finder patterns or corners of the barcode in the image.
+ *
+ * @author Sean Owen
+ */
+public class DetectorResult {
+
+ private final BitMatrix bits;
+ private final ResultPoint[] points;
+
+ public DetectorResult(BitMatrix bits, ResultPoint[] points) {
+ this.bits = bits;
+ this.points = points;
+ }
+
+ public final BitMatrix getBits() {
+ return bits;
+ }
+
+ public final ResultPoint[] getPoints() {
+ return points;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/GlobalHistogramBinarizer.java b/extern/zxing-core/src/main/java/com/google/zxing/common/GlobalHistogramBinarizer.java
new file mode 100644
index 000000000..f9676468b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/GlobalHistogramBinarizer.java
@@ -0,0 +1,196 @@
+/*
+ * 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.common;
+
+import com.google.zxing.Binarizer;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.NotFoundException;
+
+/**
+ * This Binarizer implementation uses the old ZXing global histogram approach. It is suitable
+ * for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
+ * algorithm. However, because it picks a global black point, it cannot handle difficult shadows
+ * and gradients.
+ *
+ * Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ */
+public class GlobalHistogramBinarizer extends Binarizer {
+
+ private static final int LUMINANCE_BITS = 5;
+ private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
+ private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
+ private static final byte[] EMPTY = new byte[0];
+
+ private byte[] luminances;
+ private final int[] buckets;
+
+ public GlobalHistogramBinarizer(LuminanceSource source) {
+ super(source);
+ luminances = EMPTY;
+ buckets = new int[LUMINANCE_BUCKETS];
+ }
+
+ // Applies simple sharpening to the row data to improve performance of the 1D Readers.
+ @Override
+ public BitArray getBlackRow(int y, BitArray row) throws NotFoundException {
+ LuminanceSource source = getLuminanceSource();
+ int width = source.getWidth();
+ if (row == null || row.getSize() < width) {
+ row = new BitArray(width);
+ } else {
+ row.clear();
+ }
+
+ initArrays(width);
+ byte[] localLuminances = source.getRow(y, luminances);
+ int[] localBuckets = buckets;
+ for (int x = 0; x < width; x++) {
+ int pixel = localLuminances[x] & 0xff;
+ localBuckets[pixel >> LUMINANCE_SHIFT]++;
+ }
+ int blackPoint = estimateBlackPoint(localBuckets);
+
+ int left = localLuminances[0] & 0xff;
+ int center = localLuminances[1] & 0xff;
+ for (int x = 1; x < width - 1; x++) {
+ int right = localLuminances[x + 1] & 0xff;
+ // A simple -1 4 -1 box filter with a weight of 2.
+ int luminance = ((center << 2) - left - right) >> 1;
+ if (luminance < blackPoint) {
+ row.set(x);
+ }
+ left = center;
+ center = right;
+ }
+ return row;
+ }
+
+ // Does not sharpen the data, as this call is intended to only be used by 2D Readers.
+ @Override
+ public BitMatrix getBlackMatrix() throws NotFoundException {
+ LuminanceSource source = getLuminanceSource();
+ int width = source.getWidth();
+ int height = source.getHeight();
+ BitMatrix matrix = new BitMatrix(width, height);
+
+ // Quickly calculates the histogram by sampling four rows from the image. This proved to be
+ // more robust on the blackbox tests than sampling a diagonal as we used to do.
+ initArrays(width);
+ int[] localBuckets = buckets;
+ for (int y = 1; y < 5; y++) {
+ int row = height * y / 5;
+ byte[] localLuminances = source.getRow(row, luminances);
+ int right = (width << 2) / 5;
+ for (int x = width / 5; x < right; x++) {
+ int pixel = localLuminances[x] & 0xff;
+ localBuckets[pixel >> LUMINANCE_SHIFT]++;
+ }
+ }
+ int blackPoint = estimateBlackPoint(localBuckets);
+
+ // We delay reading the entire image luminance until the black point estimation succeeds.
+ // Although we end up reading four rows twice, it is consistent with our motto of
+ // "fail quickly" which is necessary for continuous scanning.
+ byte[] localLuminances = source.getMatrix();
+ for (int y = 0; y < height; y++) {
+ int offset = y * width;
+ for (int x = 0; x< width; x++) {
+ int pixel = localLuminances[offset + x] & 0xff;
+ if (pixel < blackPoint) {
+ matrix.set(x, y);
+ }
+ }
+ }
+
+ return matrix;
+ }
+
+ @Override
+ public Binarizer createBinarizer(LuminanceSource source) {
+ return new GlobalHistogramBinarizer(source);
+ }
+
+ private void initArrays(int luminanceSize) {
+ if (luminances.length < luminanceSize) {
+ luminances = new byte[luminanceSize];
+ }
+ for (int x = 0; x < LUMINANCE_BUCKETS; x++) {
+ buckets[x] = 0;
+ }
+ }
+
+ private static int estimateBlackPoint(int[] buckets) throws NotFoundException {
+ // Find the tallest peak in the histogram.
+ int numBuckets = buckets.length;
+ int maxBucketCount = 0;
+ int firstPeak = 0;
+ int firstPeakSize = 0;
+ for (int x = 0; x < numBuckets; x++) {
+ if (buckets[x] > firstPeakSize) {
+ firstPeak = x;
+ firstPeakSize = buckets[x];
+ }
+ if (buckets[x] > maxBucketCount) {
+ maxBucketCount = buckets[x];
+ }
+ }
+
+ // Find the second-tallest peak which is somewhat far from the tallest peak.
+ int secondPeak = 0;
+ int secondPeakScore = 0;
+ for (int x = 0; x < numBuckets; x++) {
+ int distanceToBiggest = x - firstPeak;
+ // Encourage more distant second peaks by multiplying by square of distance.
+ int score = buckets[x] * distanceToBiggest * distanceToBiggest;
+ if (score > secondPeakScore) {
+ secondPeak = x;
+ secondPeakScore = score;
+ }
+ }
+
+ // Make sure firstPeak corresponds to the black peak.
+ if (firstPeak > secondPeak) {
+ int temp = firstPeak;
+ firstPeak = secondPeak;
+ secondPeak = temp;
+ }
+
+ // If there is too little contrast in the image to pick a meaningful black point, throw rather
+ // than waste time trying to decode the image, and risk false positives.
+ if (secondPeak - firstPeak <= numBuckets >> 4) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Find a valley between them that is low and closer to the white peak.
+ int bestValley = secondPeak - 1;
+ int bestValleyScore = -1;
+ for (int x = secondPeak - 1; x > firstPeak; x--) {
+ int fromFirst = x - firstPeak;
+ int score = fromFirst * fromFirst * (secondPeak - x) * (maxBucketCount - buckets[x]);
+ if (score > bestValleyScore) {
+ bestValley = x;
+ bestValleyScore = score;
+ }
+ }
+
+ return bestValley << LUMINANCE_SHIFT;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/GridSampler.java b/extern/zxing-core/src/main/java/com/google/zxing/common/GridSampler.java
new file mode 100644
index 000000000..849588f4e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/GridSampler.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2007 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.common;
+
+import com.google.zxing.NotFoundException;
+
+/**
+ * Implementations of this class can, given locations of finder patterns for a QR code in an
+ * image, sample the right points in the image to reconstruct the QR code, accounting for
+ * perspective distortion. It is abstracted since it is relatively expensive and should be allowed
+ * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced
+ * Imaging library, but which may not be available in other environments such as J2ME, and vice
+ * versa.
+ *
+ * The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)}
+ * with an instance of a class which implements this interface.
+ *
+ * @author Sean Owen
+ */
+public abstract class GridSampler {
+
+ private static GridSampler gridSampler = new DefaultGridSampler();
+
+ /**
+ * Sets the implementation of GridSampler used by the library. One global
+ * instance is stored, which may sound problematic. But, the implementation provided
+ * ought to be appropriate for the entire platform, and all uses of this library
+ * in the whole lifetime of the JVM. For instance, an Android activity can swap in
+ * an implementation that takes advantage of native platform libraries.
+ *
+ * @param newGridSampler The platform-specific object to install.
+ */
+ public static void setGridSampler(GridSampler newGridSampler) {
+ gridSampler = newGridSampler;
+ }
+
+ /**
+ * @return the current implementation of GridSampler
+ */
+ public static GridSampler getInstance() {
+ return gridSampler;
+ }
+
+ /**
+ * Samples an image for a rectangular matrix of bits of the given dimension. The sampling
+ * transformation is determined by the coordinates of 4 points, in the original and transformed
+ * image space.
+ *
+ * @param image image to sample
+ * @param dimensionX width of {@link BitMatrix} to sample from image
+ * @param dimensionY height of {@link BitMatrix} to sample from image
+ * @param p1ToX point 1 preimage X
+ * @param p1ToY point 1 preimage Y
+ * @param p2ToX point 2 preimage X
+ * @param p2ToY point 2 preimage Y
+ * @param p3ToX point 3 preimage X
+ * @param p3ToY point 3 preimage Y
+ * @param p4ToX point 4 preimage X
+ * @param p4ToY point 4 preimage Y
+ * @param p1FromX point 1 image X
+ * @param p1FromY point 1 image Y
+ * @param p2FromX point 2 image X
+ * @param p2FromY point 2 image Y
+ * @param p3FromX point 3 image X
+ * @param p3FromY point 3 image Y
+ * @param p4FromX point 4 image X
+ * @param p4FromY point 4 image Y
+ * @return {@link BitMatrix} representing a grid of points sampled from the image within a region
+ * defined by the "from" parameters
+ * @throws NotFoundException if image can't be sampled, for example, if the transformation defined
+ * by the given points is invalid or results in sampling outside the image boundaries
+ */
+ public abstract BitMatrix sampleGrid(BitMatrix image,
+ int dimensionX,
+ int dimensionY,
+ float p1ToX, float p1ToY,
+ float p2ToX, float p2ToY,
+ float p3ToX, float p3ToY,
+ float p4ToX, float p4ToY,
+ float p1FromX, float p1FromY,
+ float p2FromX, float p2FromY,
+ float p3FromX, float p3FromY,
+ float p4FromX, float p4FromY) throws NotFoundException;
+
+ public abstract BitMatrix sampleGrid(BitMatrix image,
+ int dimensionX,
+ int dimensionY,
+ PerspectiveTransform transform) throws NotFoundException;
+
+ /**
+ * Checks a set of points that have been transformed to sample points on an image against
+ * the image's dimensions to see if the point are even within the image.
+ *
+ * This method will actually "nudge" the endpoints back onto the image if they are found to be
+ * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder
+ * patterns in an image where the QR Code runs all the way to the image border.
+ *
+ * For efficiency, the method will check points from either end of the line until one is found
+ * to be within the image. Because the set of points are assumed to be linear, this is valid.
+ *
+ * @param image image into which the points should map
+ * @param points actual points in x1,y1,...,xn,yn form
+ * @throws NotFoundException if an endpoint is lies outside the image boundaries
+ */
+ protected static void checkAndNudgePoints(BitMatrix image,
+ float[] points) throws NotFoundException {
+ int width = image.getWidth();
+ int height = image.getHeight();
+ // Check and nudge points from start until we see some that are OK:
+ boolean nudged = true;
+ for (int offset = 0; offset < points.length && nudged; offset += 2) {
+ int x = (int) points[offset];
+ int y = (int) points[offset + 1];
+ if (x < -1 || x > width || y < -1 || y > height) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ nudged = false;
+ if (x == -1) {
+ points[offset] = 0.0f;
+ nudged = true;
+ } else if (x == width) {
+ points[offset] = width - 1;
+ nudged = true;
+ }
+ if (y == -1) {
+ points[offset + 1] = 0.0f;
+ nudged = true;
+ } else if (y == height) {
+ points[offset + 1] = height - 1;
+ nudged = true;
+ }
+ }
+ // Check and nudge points from end:
+ nudged = true;
+ for (int offset = points.length - 2; offset >= 0 && nudged; offset -= 2) {
+ int x = (int) points[offset];
+ int y = (int) points[offset + 1];
+ if (x < -1 || x > width || y < -1 || y > height) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ nudged = false;
+ if (x == -1) {
+ points[offset] = 0.0f;
+ nudged = true;
+ } else if (x == width) {
+ points[offset] = width - 1;
+ nudged = true;
+ }
+ if (y == -1) {
+ points[offset + 1] = 0.0f;
+ nudged = true;
+ } else if (y == height) {
+ points[offset + 1] = height - 1;
+ nudged = true;
+ }
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/HybridBinarizer.java b/extern/zxing-core/src/main/java/com/google/zxing/common/HybridBinarizer.java
new file mode 100644
index 000000000..ba56db7d0
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/HybridBinarizer.java
@@ -0,0 +1,237 @@
+/*
+ * 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.common;
+
+import com.google.zxing.Binarizer;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.NotFoundException;
+
+/**
+ * This class implements a local thresholding algorithm, which while slower than the
+ * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for
+ * high frequency images of barcodes with black data on white backgrounds. For this application,
+ * it does a much better job than a global blackpoint with severe shadows and gradients.
+ * However it tends to produce artifacts on lower frequency images and is therefore not
+ * a good general purpose binarizer for uses outside ZXing.
+ *
+ * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers,
+ * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already
+ * inherently local, and only fails for horizontal gradients. We can revisit that problem later,
+ * but for now it was not a win to use local blocks for 1D.
+ *
+ * This Binarizer is the default for the unit tests and the recommended class for library users.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class HybridBinarizer extends GlobalHistogramBinarizer {
+
+ // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
+ // So this is the smallest dimension in each axis we can accept.
+ private static final int BLOCK_SIZE_POWER = 3;
+ private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00
+ private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11
+ private static final int MINIMUM_DIMENSION = BLOCK_SIZE * 5;
+ private static final int MIN_DYNAMIC_RANGE = 24;
+
+ private BitMatrix matrix;
+
+ public HybridBinarizer(LuminanceSource source) {
+ super(source);
+ }
+
+ /**
+ * Calculates the final BitMatrix once for all requests. This could be called once from the
+ * constructor instead, but there are some advantages to doing it lazily, such as making
+ * profiling easier, and not doing heavy lifting when callers don't expect it.
+ */
+ @Override
+ public BitMatrix getBlackMatrix() throws NotFoundException {
+ if (matrix != null) {
+ return matrix;
+ }
+ LuminanceSource source = getLuminanceSource();
+ int width = source.getWidth();
+ int height = source.getHeight();
+ if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) {
+ byte[] luminances = source.getMatrix();
+ int subWidth = width >> BLOCK_SIZE_POWER;
+ if ((width & BLOCK_SIZE_MASK) != 0) {
+ subWidth++;
+ }
+ int subHeight = height >> BLOCK_SIZE_POWER;
+ if ((height & BLOCK_SIZE_MASK) != 0) {
+ subHeight++;
+ }
+ int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height);
+
+ BitMatrix newMatrix = new BitMatrix(width, height);
+ calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, newMatrix);
+ matrix = newMatrix;
+ } else {
+ // If the image is too small, fall back to the global histogram approach.
+ matrix = super.getBlackMatrix();
+ }
+ return matrix;
+ }
+
+ @Override
+ public Binarizer createBinarizer(LuminanceSource source) {
+ return new HybridBinarizer(source);
+ }
+
+ /**
+ * For each block in the image, calculate the average black point using a 5x5 grid
+ * of the blocks around it. Also handles the corner cases (fractional blocks are computed based
+ * on the last pixels in the row/column which are also used in the previous block).
+ */
+ private static void calculateThresholdForBlock(byte[] luminances,
+ int subWidth,
+ int subHeight,
+ int width,
+ int height,
+ int[][] blackPoints,
+ BitMatrix matrix) {
+ for (int y = 0; y < subHeight; y++) {
+ int yoffset = y << BLOCK_SIZE_POWER;
+ int maxYOffset = height - BLOCK_SIZE;
+ if (yoffset > maxYOffset) {
+ yoffset = maxYOffset;
+ }
+ for (int x = 0; x < subWidth; x++) {
+ int xoffset = x << BLOCK_SIZE_POWER;
+ int maxXOffset = width - BLOCK_SIZE;
+ if (xoffset > maxXOffset) {
+ xoffset = maxXOffset;
+ }
+ int left = cap(x, 2, subWidth - 3);
+ int top = cap(y, 2, subHeight - 3);
+ int sum = 0;
+ for (int z = -2; z <= 2; z++) {
+ int[] blackRow = blackPoints[top + z];
+ sum += blackRow[left - 2] + blackRow[left - 1] + blackRow[left] + blackRow[left + 1] + blackRow[left + 2];
+ }
+ int average = sum / 25;
+ thresholdBlock(luminances, xoffset, yoffset, average, width, matrix);
+ }
+ }
+ }
+
+ private static int cap(int value, int min, int max) {
+ return value < min ? min : value > max ? max : value;
+ }
+
+ /**
+ * Applies a single threshold to a block of pixels.
+ */
+ private static void thresholdBlock(byte[] luminances,
+ int xoffset,
+ int yoffset,
+ int threshold,
+ int stride,
+ BitMatrix matrix) {
+ for (int y = 0, offset = yoffset * stride + xoffset; y < BLOCK_SIZE; y++, offset += stride) {
+ for (int x = 0; x < BLOCK_SIZE; x++) {
+ // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0.
+ if ((luminances[offset + x] & 0xFF) <= threshold) {
+ matrix.set(xoffset + x, yoffset + y);
+ }
+ }
+ }
+ }
+
+ /**
+ * Calculates a single black point for each block of pixels and saves it away.
+ * See the following thread for a discussion of this algorithm:
+ * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0
+ */
+ private static int[][] calculateBlackPoints(byte[] luminances,
+ int subWidth,
+ int subHeight,
+ int width,
+ int height) {
+ int[][] blackPoints = new int[subHeight][subWidth];
+ for (int y = 0; y < subHeight; y++) {
+ int yoffset = y << BLOCK_SIZE_POWER;
+ int maxYOffset = height - BLOCK_SIZE;
+ if (yoffset > maxYOffset) {
+ yoffset = maxYOffset;
+ }
+ for (int x = 0; x < subWidth; x++) {
+ int xoffset = x << BLOCK_SIZE_POWER;
+ int maxXOffset = width - BLOCK_SIZE;
+ if (xoffset > maxXOffset) {
+ xoffset = maxXOffset;
+ }
+ int sum = 0;
+ int min = 0xFF;
+ int max = 0;
+ for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width) {
+ for (int xx = 0; xx < BLOCK_SIZE; xx++) {
+ int pixel = luminances[offset + xx] & 0xFF;
+ sum += pixel;
+ // still looking for good contrast
+ if (pixel < min) {
+ min = pixel;
+ }
+ if (pixel > max) {
+ max = pixel;
+ }
+ }
+ // short-circuit min/max tests once dynamic range is met
+ if (max - min > MIN_DYNAMIC_RANGE) {
+ // finish the rest of the rows quickly
+ for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width) {
+ for (int xx = 0; xx < BLOCK_SIZE; xx++) {
+ sum += luminances[offset + xx] & 0xFF;
+ }
+ }
+ }
+ }
+
+ // The default estimate is the average of the values in the block.
+ int average = sum >> (BLOCK_SIZE_POWER * 2);
+ if (max - min <= MIN_DYNAMIC_RANGE) {
+ // If variation within the block is low, assume this is a block with only light or only
+ // dark pixels. In that case we do not want to use the average, as it would divide this
+ // low contrast area into black and white pixels, essentially creating data out of noise.
+ //
+ // The default assumption is that the block is light/background. Since no estimate for
+ // the level of dark pixels exists locally, use half the min for the block.
+ average = min >> 1;
+
+ if (y > 0 && x > 0) {
+ // Correct the "white background" assumption for blocks that have neighbors by comparing
+ // the pixels in this block to the previously calculated black points. This is based on
+ // the fact that dark barcode symbology is always surrounded by some amount of light
+ // background for which reasonable black point estimates were made. The bp estimated at
+ // the boundaries is used for the interior.
+
+ // The (min < bp) is arbitrary but works better than other heuristics that were tried.
+ int averageNeighborBlackPoint = (blackPoints[y - 1][x] + (2 * blackPoints[y][x - 1]) +
+ blackPoints[y - 1][x - 1]) >> 2;
+ if (min < averageNeighborBlackPoint) {
+ average = averageNeighborBlackPoint;
+ }
+ }
+ }
+ blackPoints[y][x] = average;
+ }
+ }
+ return blackPoints;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/PerspectiveTransform.java b/extern/zxing-core/src/main/java/com/google/zxing/common/PerspectiveTransform.java
new file mode 100644
index 000000000..8ddfa7f24
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/PerspectiveTransform.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2007 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.common;
+
+/**
+ * This class implements a perspective transform in two dimensions. Given four source and four
+ * destination points, it will compute the transformation implied between them. The code is based
+ * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.
+ *
+ * @author Sean Owen
+ */
+public final class PerspectiveTransform {
+
+ private final float a11;
+ private final float a12;
+ private final float a13;
+ private final float a21;
+ private final float a22;
+ private final float a23;
+ private final float a31;
+ private final float a32;
+ private final float a33;
+
+ private PerspectiveTransform(float a11, float a21, float a31,
+ float a12, float a22, float a32,
+ float a13, float a23, float a33) {
+ this.a11 = a11;
+ this.a12 = a12;
+ this.a13 = a13;
+ this.a21 = a21;
+ this.a22 = a22;
+ this.a23 = a23;
+ this.a31 = a31;
+ this.a32 = a32;
+ this.a33 = a33;
+ }
+
+ public static PerspectiveTransform quadrilateralToQuadrilateral(float x0, float y0,
+ float x1, float y1,
+ float x2, float y2,
+ float x3, float y3,
+ float x0p, float y0p,
+ float x1p, float y1p,
+ float x2p, float y2p,
+ float x3p, float y3p) {
+
+ PerspectiveTransform qToS = quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
+ PerspectiveTransform sToQ = squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
+ return sToQ.times(qToS);
+ }
+
+ public void transformPoints(float[] points) {
+ int max = points.length;
+ float a11 = this.a11;
+ float a12 = this.a12;
+ float a13 = this.a13;
+ float a21 = this.a21;
+ float a22 = this.a22;
+ float a23 = this.a23;
+ float a31 = this.a31;
+ float a32 = this.a32;
+ float a33 = this.a33;
+ for (int i = 0; i < max; i += 2) {
+ float x = points[i];
+ float y = points[i + 1];
+ float denominator = a13 * x + a23 * y + a33;
+ points[i] = (a11 * x + a21 * y + a31) / denominator;
+ points[i + 1] = (a12 * x + a22 * y + a32) / denominator;
+ }
+ }
+
+ public void transformPoints(float[] xValues, float[] yValues) {
+ int n = xValues.length;
+ for (int i = 0; i < n; i ++) {
+ float x = xValues[i];
+ float y = yValues[i];
+ float denominator = a13 * x + a23 * y + a33;
+ xValues[i] = (a11 * x + a21 * y + a31) / denominator;
+ yValues[i] = (a12 * x + a22 * y + a32) / denominator;
+ }
+ }
+
+ public static PerspectiveTransform squareToQuadrilateral(float x0, float y0,
+ float x1, float y1,
+ float x2, float y2,
+ float x3, float y3) {
+ float dx3 = x0 - x1 + x2 - x3;
+ float dy3 = y0 - y1 + y2 - y3;
+ if (dx3 == 0.0f && dy3 == 0.0f) {
+ // Affine
+ return new PerspectiveTransform(x1 - x0, x2 - x1, x0,
+ y1 - y0, y2 - y1, y0,
+ 0.0f, 0.0f, 1.0f);
+ } else {
+ float dx1 = x1 - x2;
+ float dx2 = x3 - x2;
+ float dy1 = y1 - y2;
+ float dy2 = y3 - y2;
+ float denominator = dx1 * dy2 - dx2 * dy1;
+ float a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
+ float a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
+ return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0,
+ y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0,
+ a13, a23, 1.0f);
+ }
+ }
+
+ public static PerspectiveTransform quadrilateralToSquare(float x0, float y0,
+ float x1, float y1,
+ float x2, float y2,
+ float x3, float y3) {
+ // Here, the adjoint serves as the inverse:
+ return squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();
+ }
+
+ PerspectiveTransform buildAdjoint() {
+ // Adjoint is the transpose of the cofactor matrix:
+ return new PerspectiveTransform(a22 * a33 - a23 * a32,
+ a23 * a31 - a21 * a33,
+ a21 * a32 - a22 * a31,
+ a13 * a32 - a12 * a33,
+ a11 * a33 - a13 * a31,
+ a12 * a31 - a11 * a32,
+ a12 * a23 - a13 * a22,
+ a13 * a21 - a11 * a23,
+ a11 * a22 - a12 * a21);
+ }
+
+ PerspectiveTransform times(PerspectiveTransform other) {
+ return new PerspectiveTransform(a11 * other.a11 + a21 * other.a12 + a31 * other.a13,
+ a11 * other.a21 + a21 * other.a22 + a31 * other.a23,
+ a11 * other.a31 + a21 * other.a32 + a31 * other.a33,
+ a12 * other.a11 + a22 * other.a12 + a32 * other.a13,
+ a12 * other.a21 + a22 * other.a22 + a32 * other.a23,
+ a12 * other.a31 + a22 * other.a32 + a32 * other.a33,
+ a13 * other.a11 + a23 * other.a12 + a33 * other.a13,
+ a13 * other.a21 + a23 * other.a22 + a33 * other.a23,
+ a13 * other.a31 + a23 * other.a32 + a33 * other.a33);
+
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/StringUtils.java b/extern/zxing-core/src/main/java/com/google/zxing/common/StringUtils.java
new file mode 100644
index 000000000..9ea1d822e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/StringUtils.java
@@ -0,0 +1,213 @@
+/*
+ * 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.common;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+
+import com.google.zxing.DecodeHintType;
+
+/**
+ * Common string-related functions.
+ *
+ * @author Sean Owen
+ * @author Alex Dupre
+ */
+public final class StringUtils {
+
+ private static final String PLATFORM_DEFAULT_ENCODING = Charset.defaultCharset().name();
+ public static final String SHIFT_JIS = "SJIS";
+ public static final String GB2312 = "GB2312";
+ private static final String EUC_JP = "EUC_JP";
+ private static final String UTF8 = "UTF8";
+ private static final String ISO88591 = "ISO8859_1";
+ private static final boolean ASSUME_SHIFT_JIS =
+ SHIFT_JIS.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING) ||
+ EUC_JP.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING);
+
+ private StringUtils() {}
+
+ /**
+ * @param bytes bytes encoding a string, whose encoding should be guessed
+ * @param hints decode hints if applicable
+ * @return name of guessed encoding; at the moment will only guess one of:
+ * {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform
+ * default encoding if none of these can possibly be correct
+ */
+ public static String guessEncoding(byte[] bytes, Map hints) {
+ if (hints != null) {
+ String characterSet = (String) hints.get(DecodeHintType.CHARACTER_SET);
+ if (characterSet != null) {
+ return characterSet;
+ }
+ }
+ // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
+ // which should be by far the most common encodings.
+ int length = bytes.length;
+ boolean canBeISO88591 = true;
+ boolean canBeShiftJIS = true;
+ boolean canBeUTF8 = true;
+ int utf8BytesLeft = 0;
+ //int utf8LowChars = 0;
+ int utf2BytesChars = 0;
+ int utf3BytesChars = 0;
+ int utf4BytesChars = 0;
+ int sjisBytesLeft = 0;
+ //int sjisLowChars = 0;
+ int sjisKatakanaChars = 0;
+ //int sjisDoubleBytesChars = 0;
+ int sjisCurKatakanaWordLength = 0;
+ int sjisCurDoubleBytesWordLength = 0;
+ int sjisMaxKatakanaWordLength = 0;
+ int sjisMaxDoubleBytesWordLength = 0;
+ //int isoLowChars = 0;
+ //int isoHighChars = 0;
+ int isoHighOther = 0;
+
+ boolean utf8bom = bytes.length > 3 &&
+ bytes[0] == (byte) 0xEF &&
+ bytes[1] == (byte) 0xBB &&
+ bytes[2] == (byte) 0xBF;
+
+ for (int i = 0;
+ i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8);
+ i++) {
+
+ int value = bytes[i] & 0xFF;
+
+ // UTF-8 stuff
+ if (canBeUTF8) {
+ if (utf8BytesLeft > 0) {
+ if ((value & 0x80) == 0) {
+ canBeUTF8 = false;
+ } else {
+ utf8BytesLeft--;
+ }
+ } else if ((value & 0x80) != 0) {
+ if ((value & 0x40) == 0) {
+ canBeUTF8 = false;
+ } else {
+ utf8BytesLeft++;
+ if ((value & 0x20) == 0) {
+ utf2BytesChars++;
+ } else {
+ utf8BytesLeft++;
+ if ((value & 0x10) == 0) {
+ utf3BytesChars++;
+ } else {
+ utf8BytesLeft++;
+ if ((value & 0x08) == 0) {
+ utf4BytesChars++;
+ } else {
+ canBeUTF8 = false;
+ }
+ }
+ }
+ }
+ } //else {
+ //utf8LowChars++;
+ //}
+ }
+
+ // ISO-8859-1 stuff
+ if (canBeISO88591) {
+ if (value > 0x7F && value < 0xA0) {
+ canBeISO88591 = false;
+ } else if (value > 0x9F) {
+ if (value < 0xC0 || value == 0xD7 || value == 0xF7) {
+ isoHighOther++;
+ } //else {
+ //isoHighChars++;
+ //}
+ } //else {
+ //isoLowChars++;
+ //}
+ }
+
+ // Shift_JIS stuff
+ if (canBeShiftJIS) {
+ if (sjisBytesLeft > 0) {
+ if (value < 0x40 || value == 0x7F || value > 0xFC) {
+ canBeShiftJIS = false;
+ } else {
+ sjisBytesLeft--;
+ }
+ } else if (value == 0x80 || value == 0xA0 || value > 0xEF) {
+ canBeShiftJIS = false;
+ } else if (value > 0xA0 && value < 0xE0) {
+ sjisKatakanaChars++;
+ sjisCurDoubleBytesWordLength = 0;
+ sjisCurKatakanaWordLength++;
+ if (sjisCurKatakanaWordLength > sjisMaxKatakanaWordLength) {
+ sjisMaxKatakanaWordLength = sjisCurKatakanaWordLength;
+ }
+ } else if (value > 0x7F) {
+ sjisBytesLeft++;
+ //sjisDoubleBytesChars++;
+ sjisCurKatakanaWordLength = 0;
+ sjisCurDoubleBytesWordLength++;
+ if (sjisCurDoubleBytesWordLength > sjisMaxDoubleBytesWordLength) {
+ sjisMaxDoubleBytesWordLength = sjisCurDoubleBytesWordLength;
+ }
+ } else {
+ //sjisLowChars++;
+ sjisCurKatakanaWordLength = 0;
+ sjisCurDoubleBytesWordLength = 0;
+ }
+ }
+ }
+
+ if (canBeUTF8 && utf8BytesLeft > 0) {
+ canBeUTF8 = false;
+ }
+ if (canBeShiftJIS && sjisBytesLeft > 0) {
+ canBeShiftJIS = false;
+ }
+
+ // Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done
+ if (canBeUTF8 && (utf8bom || utf2BytesChars + utf3BytesChars + utf4BytesChars > 0)) {
+ return UTF8;
+ }
+ // Easy -- if assuming Shift_JIS or at least 3 valid consecutive not-ascii characters (and no evidence it can't be), done
+ if (canBeShiftJIS && (ASSUME_SHIFT_JIS || sjisMaxKatakanaWordLength >= 3 || sjisMaxDoubleBytesWordLength >= 3)) {
+ return SHIFT_JIS;
+ }
+ // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is:
+ // - If we saw
+ // - only two consecutive katakana chars in the whole text, or
+ // - at least 10% of bytes that could be "upper" not-alphanumeric Latin1,
+ // - then we conclude Shift_JIS, else ISO-8859-1
+ if (canBeISO88591 && canBeShiftJIS) {
+ return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= length
+ ? SHIFT_JIS : ISO88591;
+ }
+
+ // Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding
+ if (canBeISO88591) {
+ return ISO88591;
+ }
+ if (canBeShiftJIS) {
+ return SHIFT_JIS;
+ }
+ if (canBeUTF8) {
+ return UTF8;
+ }
+ // Otherwise, we take a wild guess with platform encoding
+ return PLATFORM_DEFAULT_ENCODING;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/detector/MathUtils.java b/extern/zxing-core/src/main/java/com/google/zxing/common/detector/MathUtils.java
new file mode 100644
index 000000000..307d6d30e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/detector/MathUtils.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 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.common.detector;
+
+public final class MathUtils {
+
+ private MathUtils() {
+ }
+
+ /**
+ * Ends up being a bit faster than {@link Math#round(float)}. This merely rounds its
+ * argument to the nearest int, where x.5 rounds up to x+1.
+ *
+ * @param d real value to round
+ * @return nearest {@code int}
+ */
+ public static int round(float d) {
+ return (int) (d + 0.5f);
+ }
+
+ public static float distance(float aX, float aY, float bX, float bY) {
+ float xDiff = aX - bX;
+ float yDiff = aY - bY;
+ return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+ }
+
+ public static float distance(int aX, int aY, int bX, int bY) {
+ int xDiff = aX - bX;
+ int yDiff = aY - bY;
+ return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/detector/MonochromeRectangleDetector.java b/extern/zxing-core/src/main/java/com/google/zxing/common/detector/MonochromeRectangleDetector.java
new file mode 100644
index 000000000..5746b88df
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/detector/MonochromeRectangleDetector.java
@@ -0,0 +1,215 @@
+/*
+ * 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.common.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * A somewhat generic detector that looks for a barcode-like rectangular region within an image.
+ * It looks within a mostly white region of an image for a region of black and white, but mostly
+ * black. It returns the four corners of the region, as best it can determine.
+ *
+ * @author Sean Owen
+ */
+public final class MonochromeRectangleDetector {
+
+ private static final int MAX_MODULES = 32;
+
+ private final BitMatrix image;
+
+ public MonochromeRectangleDetector(BitMatrix image) {
+ this.image = image;
+ }
+
+ /**
+ * Detects a rectangular region of black and white -- mostly black -- with a region of mostly
+ * white, in an image.
+ *
+ * @return {@link ResultPoint}[] describing the corners of the rectangular region. The first and
+ * last points are opposed on the diagonal, as are the second and third. The first point will be
+ * the topmost point and the last, the bottommost. The second point will be leftmost and the
+ * third, the rightmost
+ * @throws NotFoundException if no Data Matrix Code can be found
+ */
+ public ResultPoint[] detect() throws NotFoundException {
+ int height = image.getHeight();
+ int width = image.getWidth();
+ int halfHeight = height >> 1;
+ int halfWidth = width >> 1;
+ int deltaY = Math.max(1, height / (MAX_MODULES << 3));
+ int deltaX = Math.max(1, width / (MAX_MODULES << 3));
+
+ int top = 0;
+ int bottom = height;
+ int left = 0;
+ int right = width;
+ ResultPoint pointA = findCornerFromCenter(halfWidth, 0, left, right,
+ halfHeight, -deltaY, top, bottom, halfWidth >> 1);
+ top = (int) pointA.getY() - 1;
+ ResultPoint pointB = findCornerFromCenter(halfWidth, -deltaX, left, right,
+ halfHeight, 0, top, bottom, halfHeight >> 1);
+ left = (int) pointB.getX() - 1;
+ ResultPoint pointC = findCornerFromCenter(halfWidth, deltaX, left, right,
+ halfHeight, 0, top, bottom, halfHeight >> 1);
+ right = (int) pointC.getX() + 1;
+ ResultPoint pointD = findCornerFromCenter(halfWidth, 0, left, right,
+ halfHeight, deltaY, top, bottom, halfWidth >> 1);
+ bottom = (int) pointD.getY() + 1;
+
+ // Go try to find point A again with better information -- might have been off at first.
+ pointA = findCornerFromCenter(halfWidth, 0, left, right,
+ halfHeight, -deltaY, top, bottom, halfWidth >> 2);
+
+ return new ResultPoint[] { pointA, pointB, pointC, pointD };
+ }
+
+ /**
+ * Attempts to locate a corner of the barcode by scanning up, down, left or right from a center
+ * point which should be within the barcode.
+ *
+ * @param centerX center's x component (horizontal)
+ * @param deltaX same as deltaY but change in x per step instead
+ * @param left minimum value of x
+ * @param right maximum value of x
+ * @param centerY center's y component (vertical)
+ * @param deltaY change in y per step. If scanning up this is negative; down, positive;
+ * left or right, 0
+ * @param top minimum value of y to search through (meaningless when di == 0)
+ * @param bottom maximum value of y
+ * @param maxWhiteRun maximum run of white pixels that can still be considered to be within
+ * the barcode
+ * @return a {@link com.google.zxing.ResultPoint} encapsulating the corner that was found
+ * @throws NotFoundException if such a point cannot be found
+ */
+ private ResultPoint findCornerFromCenter(int centerX,
+ int deltaX,
+ int left,
+ int right,
+ int centerY,
+ int deltaY,
+ int top,
+ int bottom,
+ int maxWhiteRun) throws NotFoundException {
+ int[] lastRange = null;
+ for (int y = centerY, x = centerX;
+ y < bottom && y >= top && x < right && x >= left;
+ y += deltaY, x += deltaX) {
+ int[] range;
+ if (deltaX == 0) {
+ // horizontal slices, up and down
+ range = blackWhiteRange(y, maxWhiteRun, left, right, true);
+ } else {
+ // vertical slices, left and right
+ range = blackWhiteRange(x, maxWhiteRun, top, bottom, false);
+ }
+ if (range == null) {
+ if (lastRange == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ // lastRange was found
+ if (deltaX == 0) {
+ int lastY = y - deltaY;
+ if (lastRange[0] < centerX) {
+ if (lastRange[1] > centerX) {
+ // straddle, choose one or the other based on direction
+ return new ResultPoint(deltaY > 0 ? lastRange[0] : lastRange[1], lastY);
+ }
+ return new ResultPoint(lastRange[0], lastY);
+ } else {
+ return new ResultPoint(lastRange[1], lastY);
+ }
+ } else {
+ int lastX = x - deltaX;
+ if (lastRange[0] < centerY) {
+ if (lastRange[1] > centerY) {
+ return new ResultPoint(lastX, deltaX < 0 ? lastRange[0] : lastRange[1]);
+ }
+ return new ResultPoint(lastX, lastRange[0]);
+ } else {
+ return new ResultPoint(lastX, lastRange[1]);
+ }
+ }
+ }
+ lastRange = range;
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /**
+ * Computes the start and end of a region of pixels, either horizontally or vertically, that could
+ * be part of a Data Matrix barcode.
+ *
+ * @param fixedDimension if scanning horizontally, this is the row (the fixed vertical location)
+ * where we are scanning. If scanning vertically it's the column, the fixed horizontal location
+ * @param maxWhiteRun largest run of white pixels that can still be considered part of the
+ * barcode region
+ * @param minDim minimum pixel location, horizontally or vertically, to consider
+ * @param maxDim maximum pixel location, horizontally or vertically, to consider
+ * @param horizontal if true, we're scanning left-right, instead of up-down
+ * @return int[] with start and end of found range, or null if no such range is found
+ * (e.g. only white was found)
+ */
+ private int[] blackWhiteRange(int fixedDimension, int maxWhiteRun, int minDim, int maxDim, boolean horizontal) {
+
+ int center = (minDim + maxDim) >> 1;
+
+ // Scan left/up first
+ int start = center;
+ while (start >= minDim) {
+ if (horizontal ? image.get(start, fixedDimension) : image.get(fixedDimension, start)) {
+ start--;
+ } else {
+ int whiteRunStart = start;
+ do {
+ start--;
+ } while (start >= minDim && !(horizontal ? image.get(start, fixedDimension) :
+ image.get(fixedDimension, start)));
+ int whiteRunSize = whiteRunStart - start;
+ if (start < minDim || whiteRunSize > maxWhiteRun) {
+ start = whiteRunStart;
+ break;
+ }
+ }
+ }
+ start++;
+
+ // Then try right/down
+ int end = center;
+ while (end < maxDim) {
+ if (horizontal ? image.get(end, fixedDimension) : image.get(fixedDimension, end)) {
+ end++;
+ } else {
+ int whiteRunStart = end;
+ do {
+ end++;
+ } while (end < maxDim && !(horizontal ? image.get(end, fixedDimension) :
+ image.get(fixedDimension, end)));
+ int whiteRunSize = end - whiteRunStart;
+ if (end >= maxDim || whiteRunSize > maxWhiteRun) {
+ end = whiteRunStart;
+ break;
+ }
+ }
+ }
+ end--;
+
+ return end > start ? new int[]{start, end} : null;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/detector/WhiteRectangleDetector.java b/extern/zxing-core/src/main/java/com/google/zxing/common/detector/WhiteRectangleDetector.java
new file mode 100644
index 000000000..9e0cce0fc
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/detector/WhiteRectangleDetector.java
@@ -0,0 +1,342 @@
+/*
+ * 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.common.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ *
+ * Detects a candidate barcode-like rectangular region within an image. It
+ * starts around the center of the image, increases the size of the candidate
+ * region until it finds a white rectangular region. By keeping track of the
+ * last black points it encountered, it determines the corners of the barcode.
+ *
+ *
+ * @author David Olivier
+ */
+public final class WhiteRectangleDetector {
+
+ private static final int INIT_SIZE = 10;
+ private static final int CORR = 1;
+
+ private final BitMatrix image;
+ private final int height;
+ private final int width;
+ private final int leftInit;
+ private final int rightInit;
+ private final int downInit;
+ private final int upInit;
+
+ public WhiteRectangleDetector(BitMatrix image) throws NotFoundException {
+ this(image, INIT_SIZE, image.getWidth() / 2, image.getHeight() / 2);
+ }
+
+ /**
+ * @param image barcode image to find a rectangle in
+ * @param initSize initial size of search area around center
+ * @param x x position of search center
+ * @param y y position of search center
+ * @throws NotFoundException if image is too small to accommodate {@code initSize}
+ */
+ public WhiteRectangleDetector(BitMatrix image, int initSize, int x, int y) throws NotFoundException {
+ this.image = image;
+ height = image.getHeight();
+ width = image.getWidth();
+ int halfsize = initSize / 2;
+ leftInit = x - halfsize;
+ rightInit = x + halfsize;
+ upInit = y - halfsize;
+ downInit = y + halfsize;
+ if (upInit < 0 || leftInit < 0 || downInit >= height || rightInit >= width) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ /**
+ *
+ * Detects a candidate barcode-like rectangular region within an image. It
+ * starts around the center of the image, increases the size of the candidate
+ * region until it finds a white rectangular region.
+ *
+ *
+ * @return {@link ResultPoint}[] describing the corners of the rectangular
+ * region. The first and last points are opposed on the diagonal, as
+ * are the second and third. The first point will be the topmost
+ * point and the last, the bottommost. The second point will be
+ * leftmost and the third, the rightmost
+ * @throws NotFoundException if no Data Matrix Code can be found
+ */
+ public ResultPoint[] detect() throws NotFoundException {
+
+ int left = leftInit;
+ int right = rightInit;
+ int up = upInit;
+ int down = downInit;
+ boolean sizeExceeded = false;
+ boolean aBlackPointFoundOnBorder = true;
+ boolean atLeastOneBlackPointFoundOnBorder = false;
+
+ boolean atLeastOneBlackPointFoundOnRight = false;
+ boolean atLeastOneBlackPointFoundOnBottom = false;
+ boolean atLeastOneBlackPointFoundOnLeft = false;
+ boolean atLeastOneBlackPointFoundOnTop = false;
+
+ while (aBlackPointFoundOnBorder) {
+
+ aBlackPointFoundOnBorder = false;
+
+ // .....
+ // . |
+ // .....
+ boolean rightBorderNotWhite = true;
+ while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < width) {
+ rightBorderNotWhite = containsBlackPoint(up, down, right, false);
+ if (rightBorderNotWhite) {
+ right++;
+ aBlackPointFoundOnBorder = true;
+ atLeastOneBlackPointFoundOnRight = true;
+ } else if (!atLeastOneBlackPointFoundOnRight) {
+ right++;
+ }
+ }
+
+ if (right >= width) {
+ sizeExceeded = true;
+ break;
+ }
+
+ // .....
+ // . .
+ // .___.
+ boolean bottomBorderNotWhite = true;
+ while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < height) {
+ bottomBorderNotWhite = containsBlackPoint(left, right, down, true);
+ if (bottomBorderNotWhite) {
+ down++;
+ aBlackPointFoundOnBorder = true;
+ atLeastOneBlackPointFoundOnBottom = true;
+ } else if (!atLeastOneBlackPointFoundOnBottom) {
+ down++;
+ }
+ }
+
+ if (down >= height) {
+ sizeExceeded = true;
+ break;
+ }
+
+ // .....
+ // | .
+ // .....
+ boolean leftBorderNotWhite = true;
+ while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) {
+ leftBorderNotWhite = containsBlackPoint(up, down, left, false);
+ if (leftBorderNotWhite) {
+ left--;
+ aBlackPointFoundOnBorder = true;
+ atLeastOneBlackPointFoundOnLeft = true;
+ } else if (!atLeastOneBlackPointFoundOnLeft) {
+ left--;
+ }
+ }
+
+ if (left < 0) {
+ sizeExceeded = true;
+ break;
+ }
+
+ // .___.
+ // . .
+ // .....
+ boolean topBorderNotWhite = true;
+ while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) {
+ topBorderNotWhite = containsBlackPoint(left, right, up, true);
+ if (topBorderNotWhite) {
+ up--;
+ aBlackPointFoundOnBorder = true;
+ atLeastOneBlackPointFoundOnTop = true;
+ } else if (!atLeastOneBlackPointFoundOnTop) {
+ up--;
+ }
+ }
+
+ if (up < 0) {
+ sizeExceeded = true;
+ break;
+ }
+
+ if (aBlackPointFoundOnBorder) {
+ atLeastOneBlackPointFoundOnBorder = true;
+ }
+
+ }
+
+ if (!sizeExceeded && atLeastOneBlackPointFoundOnBorder) {
+
+ int maxSize = right - left;
+
+ ResultPoint z = null;
+ for (int i = 1; i < maxSize; i++) {
+ z = getBlackPointOnSegment(left, down - i, left + i, down);
+ if (z != null) {
+ break;
+ }
+ }
+
+ if (z == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ ResultPoint t = null;
+ //go down right
+ for (int i = 1; i < maxSize; i++) {
+ t = getBlackPointOnSegment(left, up + i, left + i, up);
+ if (t != null) {
+ break;
+ }
+ }
+
+ if (t == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ ResultPoint x = null;
+ //go down left
+ for (int i = 1; i < maxSize; i++) {
+ x = getBlackPointOnSegment(right, up + i, right - i, up);
+ if (x != null) {
+ break;
+ }
+ }
+
+ if (x == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ ResultPoint y = null;
+ //go up left
+ for (int i = 1; i < maxSize; i++) {
+ y = getBlackPointOnSegment(right, down - i, right - i, down);
+ if (y != null) {
+ break;
+ }
+ }
+
+ if (y == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ return centerEdges(y, z, x, t);
+
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ private ResultPoint getBlackPointOnSegment(float aX, float aY, float bX, float bY) {
+ int dist = MathUtils.round(MathUtils.distance(aX, aY, bX, bY));
+ float xStep = (bX - aX) / dist;
+ float yStep = (bY - aY) / dist;
+
+ for (int i = 0; i < dist; i++) {
+ int x = MathUtils.round(aX + i * xStep);
+ int y = MathUtils.round(aY + i * yStep);
+ if (image.get(x, y)) {
+ return new ResultPoint(x, y);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * recenters the points of a constant distance towards the center
+ *
+ * @param y bottom most point
+ * @param z left most point
+ * @param x right most point
+ * @param t top most point
+ * @return {@link ResultPoint}[] describing the corners of the rectangular
+ * region. The first and last points are opposed on the diagonal, as
+ * are the second and third. The first point will be the topmost
+ * point and the last, the bottommost. The second point will be
+ * leftmost and the third, the rightmost
+ */
+ private ResultPoint[] centerEdges(ResultPoint y, ResultPoint z,
+ ResultPoint x, ResultPoint t) {
+
+ //
+ // t t
+ // z x
+ // x OR z
+ // y y
+ //
+
+ float yi = y.getX();
+ float yj = y.getY();
+ float zi = z.getX();
+ float zj = z.getY();
+ float xi = x.getX();
+ float xj = x.getY();
+ float ti = t.getX();
+ float tj = t.getY();
+
+ if (yi < width / 2.0f) {
+ return new ResultPoint[]{
+ new ResultPoint(ti - CORR, tj + CORR),
+ new ResultPoint(zi + CORR, zj + CORR),
+ new ResultPoint(xi - CORR, xj - CORR),
+ new ResultPoint(yi + CORR, yj - CORR)};
+ } else {
+ return new ResultPoint[]{
+ new ResultPoint(ti + CORR, tj + CORR),
+ new ResultPoint(zi + CORR, zj - CORR),
+ new ResultPoint(xi - CORR, xj + CORR),
+ new ResultPoint(yi - CORR, yj - CORR)};
+ }
+ }
+
+ /**
+ * Determines whether a segment contains a black point
+ *
+ * @param a min value of the scanned coordinate
+ * @param b max value of the scanned coordinate
+ * @param fixed value of fixed coordinate
+ * @param horizontal set to true if scan must be horizontal, false if vertical
+ * @return true if a black point has been found, else false.
+ */
+ private boolean containsBlackPoint(int a, int b, int fixed, boolean horizontal) {
+
+ if (horizontal) {
+ for (int x = a; x <= b; x++) {
+ if (image.get(x, fixed)) {
+ return true;
+ }
+ }
+ } else {
+ for (int y = a; y <= b; y++) {
+ if (image.get(fixed, y)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java
new file mode 100644
index 000000000..ba6d798c3
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/GenericGF.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2007 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.common.reedsolomon;
+
+/**
+ * This class contains utility methods for performing mathematical operations over
+ * the Galois Fields. Operations use a given primitive polynomial in calculations.
+ *
+ * Throughout this package, elements of the GF are represented as an {@code int}
+ * for convenience and speed (but at the cost of memory).
+ *
+ *
+ * @author Sean Owen
+ * @author David Olivier
+ */
+public final class GenericGF {
+
+ public static final GenericGF AZTEC_DATA_12 = new GenericGF(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1
+ public static final GenericGF AZTEC_DATA_10 = new GenericGF(0x409, 1024, 1); // x^10 + x^3 + 1
+ public static final GenericGF AZTEC_DATA_6 = new GenericGF(0x43, 64, 1); // x^6 + x + 1
+ public static final GenericGF AZTEC_PARAM = new GenericGF(0x13, 16, 1); // x^4 + x + 1
+ public static final GenericGF QR_CODE_FIELD_256 = new GenericGF(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1
+ public static final GenericGF DATA_MATRIX_FIELD_256 = new GenericGF(0x012D, 256, 1); // x^8 + x^5 + x^3 + x^2 + 1
+ public static final GenericGF AZTEC_DATA_8 = DATA_MATRIX_FIELD_256;
+ public static final GenericGF MAXICODE_FIELD_64 = AZTEC_DATA_6;
+
+ private static final int INITIALIZATION_THRESHOLD = 0;
+
+ private int[] expTable;
+ private int[] logTable;
+ private GenericGFPoly zero;
+ private GenericGFPoly one;
+ private final int size;
+ private final int primitive;
+ private final int generatorBase;
+ private boolean initialized = false;
+
+ /**
+ * Create a representation of GF(size) using the given primitive polynomial.
+ *
+ * @param primitive irreducible polynomial whose coefficients are represented by
+ * the bits of an int, where the least-significant bit represents the constant
+ * coefficient
+ * @param size the size of the field
+ * @param b the factor b in the generator polynomial can be 0- or 1-based
+ * (g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))).
+ * In most cases it should be 1, but for QR code it is 0.
+ */
+ public GenericGF(int primitive, int size, int b) {
+ this.primitive = primitive;
+ this.size = size;
+ this.generatorBase = b;
+
+ if (size <= INITIALIZATION_THRESHOLD) {
+ initialize();
+ }
+ }
+
+ private void initialize() {
+ expTable = new int[size];
+ logTable = new int[size];
+ int x = 1;
+ for (int i = 0; i < size; i++) {
+ expTable[i] = x;
+ x <<= 1; // x = x * 2; we're assuming the generator alpha is 2
+ if (x >= size) {
+ x ^= primitive;
+ x &= size-1;
+ }
+ }
+ for (int i = 0; i < size-1; i++) {
+ logTable[expTable[i]] = i;
+ }
+ // logTable[0] == 0 but this should never be used
+ zero = new GenericGFPoly(this, new int[]{0});
+ one = new GenericGFPoly(this, new int[]{1});
+ initialized = true;
+ }
+
+ private void checkInit() {
+ if (!initialized) {
+ initialize();
+ }
+ }
+
+ GenericGFPoly getZero() {
+ checkInit();
+
+ return zero;
+ }
+
+ GenericGFPoly getOne() {
+ checkInit();
+
+ return one;
+ }
+
+ /**
+ * @return the monomial representing coefficient * x^degree
+ */
+ GenericGFPoly buildMonomial(int degree, int coefficient) {
+ checkInit();
+
+ if (degree < 0) {
+ throw new IllegalArgumentException();
+ }
+ if (coefficient == 0) {
+ return zero;
+ }
+ int[] coefficients = new int[degree + 1];
+ coefficients[0] = coefficient;
+ return new GenericGFPoly(this, coefficients);
+ }
+
+ /**
+ * Implements both addition and subtraction -- they are the same in GF(size).
+ *
+ * @return sum/difference of a and b
+ */
+ static int addOrSubtract(int a, int b) {
+ return a ^ b;
+ }
+
+ /**
+ * @return 2 to the power of a in GF(size)
+ */
+ int exp(int a) {
+ checkInit();
+
+ return expTable[a];
+ }
+
+ /**
+ * @return base 2 log of a in GF(size)
+ */
+ int log(int a) {
+ checkInit();
+
+ if (a == 0) {
+ throw new IllegalArgumentException();
+ }
+ return logTable[a];
+ }
+
+ /**
+ * @return multiplicative inverse of a
+ */
+ int inverse(int a) {
+ checkInit();
+
+ if (a == 0) {
+ throw new ArithmeticException();
+ }
+ return expTable[size - logTable[a] - 1];
+ }
+
+ /**
+ * @return product of a and b in GF(size)
+ */
+ int multiply(int a, int b) {
+ checkInit();
+
+ if (a == 0 || b == 0) {
+ return 0;
+ }
+ return expTable[(logTable[a] + logTable[b]) % (size - 1)];
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public int getGeneratorBase() {
+ return generatorBase;
+ }
+
+ @Override
+ public String toString() {
+ return "GF(0x" + Integer.toHexString(primitive) + ',' + size + ')';
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java
new file mode 100644
index 000000000..1ccc0358d
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/GenericGFPoly.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2007 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.common.reedsolomon;
+
+/**
+ * Represents a polynomial whose coefficients are elements of a GF.
+ * Instances of this class are immutable.
+ *
+ * Much credit is due to William Rucklidge since portions of this code are an indirect
+ * port of his C++ Reed-Solomon implementation.
+ *
+ * @author Sean Owen
+ */
+final class GenericGFPoly {
+
+ private final GenericGF field;
+ private final int[] coefficients;
+
+ /**
+ * @param field the {@link GenericGF} instance representing the field to use
+ * to perform computations
+ * @param coefficients coefficients as ints representing elements of GF(size), arranged
+ * from most significant (highest-power term) coefficient to least significant
+ * @throws IllegalArgumentException if argument is null or empty,
+ * or if leading coefficient is 0 and this is not a
+ * constant polynomial (that is, it is not the monomial "0")
+ */
+ GenericGFPoly(GenericGF field, int[] coefficients) {
+ if (coefficients.length == 0) {
+ throw new IllegalArgumentException();
+ }
+ this.field = field;
+ int coefficientsLength = coefficients.length;
+ if (coefficientsLength > 1 && coefficients[0] == 0) {
+ // Leading term must be non-zero for anything except the constant polynomial "0"
+ int firstNonZero = 1;
+ while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) {
+ firstNonZero++;
+ }
+ if (firstNonZero == coefficientsLength) {
+ this.coefficients = field.getZero().coefficients;
+ } else {
+ this.coefficients = new int[coefficientsLength - firstNonZero];
+ System.arraycopy(coefficients,
+ firstNonZero,
+ this.coefficients,
+ 0,
+ this.coefficients.length);
+ }
+ } else {
+ this.coefficients = coefficients;
+ }
+ }
+
+ int[] getCoefficients() {
+ return coefficients;
+ }
+
+ /**
+ * @return degree of this polynomial
+ */
+ int getDegree() {
+ return coefficients.length - 1;
+ }
+
+ /**
+ * @return true iff this polynomial is the monomial "0"
+ */
+ boolean isZero() {
+ return coefficients[0] == 0;
+ }
+
+ /**
+ * @return coefficient of x^degree term in this polynomial
+ */
+ int getCoefficient(int degree) {
+ return coefficients[coefficients.length - 1 - degree];
+ }
+
+ /**
+ * @return evaluation of this polynomial at a given point
+ */
+ int evaluateAt(int a) {
+ if (a == 0) {
+ // Just return the x^0 coefficient
+ return getCoefficient(0);
+ }
+ int size = coefficients.length;
+ if (a == 1) {
+ // Just the sum of the coefficients
+ int result = 0;
+ for (int coefficient : coefficients) {
+ result = GenericGF.addOrSubtract(result, coefficient);
+ }
+ return result;
+ }
+ int result = coefficients[0];
+ for (int i = 1; i < size; i++) {
+ result = GenericGF.addOrSubtract(field.multiply(a, result), coefficients[i]);
+ }
+ return result;
+ }
+
+ GenericGFPoly addOrSubtract(GenericGFPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
+ }
+ if (isZero()) {
+ return other;
+ }
+ if (other.isZero()) {
+ return this;
+ }
+
+ int[] smallerCoefficients = this.coefficients;
+ int[] largerCoefficients = other.coefficients;
+ if (smallerCoefficients.length > largerCoefficients.length) {
+ int[] temp = smallerCoefficients;
+ smallerCoefficients = largerCoefficients;
+ largerCoefficients = temp;
+ }
+ int[] sumDiff = new int[largerCoefficients.length];
+ int lengthDiff = largerCoefficients.length - smallerCoefficients.length;
+ // Copy high-order terms only found in higher-degree polynomial's coefficients
+ System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff);
+
+ for (int i = lengthDiff; i < largerCoefficients.length; i++) {
+ sumDiff[i] = GenericGF.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
+ }
+
+ return new GenericGFPoly(field, sumDiff);
+ }
+
+ GenericGFPoly multiply(GenericGFPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
+ }
+ if (isZero() || other.isZero()) {
+ return field.getZero();
+ }
+ int[] aCoefficients = this.coefficients;
+ int aLength = aCoefficients.length;
+ int[] bCoefficients = other.coefficients;
+ int bLength = bCoefficients.length;
+ int[] product = new int[aLength + bLength - 1];
+ for (int i = 0; i < aLength; i++) {
+ int aCoeff = aCoefficients[i];
+ for (int j = 0; j < bLength; j++) {
+ product[i + j] = GenericGF.addOrSubtract(product[i + j],
+ field.multiply(aCoeff, bCoefficients[j]));
+ }
+ }
+ return new GenericGFPoly(field, product);
+ }
+
+ GenericGFPoly multiply(int scalar) {
+ if (scalar == 0) {
+ return field.getZero();
+ }
+ if (scalar == 1) {
+ return this;
+ }
+ int size = coefficients.length;
+ int[] product = new int[size];
+ for (int i = 0; i < size; i++) {
+ product[i] = field.multiply(coefficients[i], scalar);
+ }
+ return new GenericGFPoly(field, product);
+ }
+
+ GenericGFPoly multiplyByMonomial(int degree, int coefficient) {
+ if (degree < 0) {
+ throw new IllegalArgumentException();
+ }
+ if (coefficient == 0) {
+ return field.getZero();
+ }
+ int size = coefficients.length;
+ int[] product = new int[size + degree];
+ for (int i = 0; i < size; i++) {
+ product[i] = field.multiply(coefficients[i], coefficient);
+ }
+ return new GenericGFPoly(field, product);
+ }
+
+ GenericGFPoly[] divide(GenericGFPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
+ }
+ if (other.isZero()) {
+ throw new IllegalArgumentException("Divide by 0");
+ }
+
+ GenericGFPoly quotient = field.getZero();
+ GenericGFPoly remainder = this;
+
+ int denominatorLeadingTerm = other.getCoefficient(other.getDegree());
+ int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm);
+
+ while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) {
+ int degreeDifference = remainder.getDegree() - other.getDegree();
+ int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm);
+ GenericGFPoly term = other.multiplyByMonomial(degreeDifference, scale);
+ GenericGFPoly iterationQuotient = field.buildMonomial(degreeDifference, scale);
+ quotient = quotient.addOrSubtract(iterationQuotient);
+ remainder = remainder.addOrSubtract(term);
+ }
+
+ return new GenericGFPoly[] { quotient, remainder };
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder(8 * getDegree());
+ for (int degree = getDegree(); degree >= 0; degree--) {
+ int coefficient = getCoefficient(degree);
+ if (coefficient != 0) {
+ if (coefficient < 0) {
+ result.append(" - ");
+ coefficient = -coefficient;
+ } else {
+ if (result.length() > 0) {
+ result.append(" + ");
+ }
+ }
+ if (degree == 0 || coefficient != 1) {
+ int alphaPower = field.log(coefficient);
+ if (alphaPower == 0) {
+ result.append('1');
+ } else if (alphaPower == 1) {
+ result.append('a');
+ } else {
+ result.append("a^");
+ result.append(alphaPower);
+ }
+ }
+ if (degree != 0) {
+ if (degree == 1) {
+ result.append('x');
+ } else {
+ result.append("x^");
+ result.append(degree);
+ }
+ }
+ }
+ }
+ return result.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
new file mode 100644
index 000000000..19127e0c6
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2007 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.common.reedsolomon;
+
+/**
+ * Implements Reed-Solomon decoding, as the name implies.
+ *
+ * The algorithm will not be explained here, but the following references were helpful
+ * in creating this implementation:
+ *
+ *
+ *
+ * Much credit is due to William Rucklidge since portions of this code are an indirect
+ * port of his C++ Reed-Solomon implementation.
+ *
+ * @author Sean Owen
+ * @author William Rucklidge
+ * @author sanfordsquires
+ */
+public final class ReedSolomonDecoder {
+
+ private final GenericGF field;
+
+ public ReedSolomonDecoder(GenericGF field) {
+ this.field = field;
+ }
+
+ /**
+ * Decodes given set of received codewords, which include both data and error-correction
+ * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
+ * in the input.
+ *
+ * @param received data and error-correction codewords
+ * @param twoS number of error-correction codewords available
+ * @throws ReedSolomonException if decoding fails for any reason
+ */
+ public void decode(int[] received, int twoS) throws ReedSolomonException {
+ GenericGFPoly poly = new GenericGFPoly(field, received);
+ int[] syndromeCoefficients = new int[twoS];
+ boolean noError = true;
+ for (int i = 0; i < twoS; i++) {
+ int eval = poly.evaluateAt(field.exp(i + field.getGeneratorBase()));
+ syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval;
+ if (eval != 0) {
+ noError = false;
+ }
+ }
+ if (noError) {
+ return;
+ }
+ GenericGFPoly syndrome = new GenericGFPoly(field, syndromeCoefficients);
+ GenericGFPoly[] sigmaOmega =
+ runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS);
+ GenericGFPoly sigma = sigmaOmega[0];
+ GenericGFPoly omega = sigmaOmega[1];
+ int[] errorLocations = findErrorLocations(sigma);
+ int[] errorMagnitudes = findErrorMagnitudes(omega, errorLocations);
+ for (int i = 0; i < errorLocations.length; i++) {
+ int position = received.length - 1 - field.log(errorLocations[i]);
+ if (position < 0) {
+ throw new ReedSolomonException("Bad error location");
+ }
+ received[position] = GenericGF.addOrSubtract(received[position], errorMagnitudes[i]);
+ }
+ }
+
+ private GenericGFPoly[] runEuclideanAlgorithm(GenericGFPoly a, GenericGFPoly b, int R)
+ throws ReedSolomonException {
+ // Assume a's degree is >= b's
+ if (a.getDegree() < b.getDegree()) {
+ GenericGFPoly temp = a;
+ a = b;
+ b = temp;
+ }
+
+ GenericGFPoly rLast = a;
+ GenericGFPoly r = b;
+ GenericGFPoly tLast = field.getZero();
+ GenericGFPoly t = field.getOne();
+
+ // Run Euclidean algorithm until r's degree is less than R/2
+ while (r.getDegree() >= R / 2) {
+ GenericGFPoly rLastLast = rLast;
+ GenericGFPoly tLastLast = tLast;
+ rLast = r;
+ tLast = t;
+
+ // Divide rLastLast by rLast, with quotient in q and remainder in r
+ if (rLast.isZero()) {
+ // Oops, Euclidean algorithm already terminated?
+ throw new ReedSolomonException("r_{i-1} was zero");
+ }
+ r = rLastLast;
+ GenericGFPoly q = field.getZero();
+ int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree());
+ int dltInverse = field.inverse(denominatorLeadingTerm);
+ while (r.getDegree() >= rLast.getDegree() && !r.isZero()) {
+ int degreeDiff = r.getDegree() - rLast.getDegree();
+ int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse);
+ q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale));
+ r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale));
+ }
+
+ t = q.multiply(tLast).addOrSubtract(tLastLast);
+
+ if (r.getDegree() >= rLast.getDegree()) {
+ throw new IllegalStateException("Division algorithm failed to reduce polynomial?");
+ }
+ }
+
+ int sigmaTildeAtZero = t.getCoefficient(0);
+ if (sigmaTildeAtZero == 0) {
+ throw new ReedSolomonException("sigmaTilde(0) was zero");
+ }
+
+ int inverse = field.inverse(sigmaTildeAtZero);
+ GenericGFPoly sigma = t.multiply(inverse);
+ GenericGFPoly omega = r.multiply(inverse);
+ return new GenericGFPoly[]{sigma, omega};
+ }
+
+ private int[] findErrorLocations(GenericGFPoly errorLocator) throws ReedSolomonException {
+ // This is a direct application of Chien's search
+ int numErrors = errorLocator.getDegree();
+ if (numErrors == 1) { // shortcut
+ return new int[] { errorLocator.getCoefficient(1) };
+ }
+ int[] result = new int[numErrors];
+ int e = 0;
+ for (int i = 1; i < field.getSize() && e < numErrors; i++) {
+ if (errorLocator.evaluateAt(i) == 0) {
+ result[e] = field.inverse(i);
+ e++;
+ }
+ }
+ if (e != numErrors) {
+ throw new ReedSolomonException("Error locator degree does not match number of roots");
+ }
+ return result;
+ }
+
+ private int[] findErrorMagnitudes(GenericGFPoly errorEvaluator, int[] errorLocations) {
+ // This is directly applying Forney's Formula
+ int s = errorLocations.length;
+ int[] result = new int[s];
+ for (int i = 0; i < s; i++) {
+ int xiInverse = field.inverse(errorLocations[i]);
+ int denominator = 1;
+ for (int j = 0; j < s; j++) {
+ if (i != j) {
+ //denominator = field.multiply(denominator,
+ // GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse)));
+ // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
+ // Below is a funny-looking workaround from Steven Parkes
+ int term = field.multiply(errorLocations[j], xiInverse);
+ int termPlus1 = (term & 0x1) == 0 ? term | 1 : term & ~1;
+ denominator = field.multiply(denominator, termPlus1);
+ }
+ }
+ result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse),
+ field.inverse(denominator));
+ if (field.getGeneratorBase() != 0) {
+ result[i] = field.multiply(result[i], xiInverse);
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java
new file mode 100644
index 000000000..ff813cb1f
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java
@@ -0,0 +1,74 @@
+/*
+ * 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.common.reedsolomon;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements Reed-Solomon enbcoding, as the name implies.
+ *
+ * @author Sean Owen
+ * @author William Rucklidge
+ */
+public final class ReedSolomonEncoder {
+
+ private final GenericGF field;
+ private final List cachedGenerators;
+
+ public ReedSolomonEncoder(GenericGF field) {
+ this.field = field;
+ this.cachedGenerators = new ArrayList<>();
+ cachedGenerators.add(new GenericGFPoly(field, new int[]{1}));
+ }
+
+ private GenericGFPoly buildGenerator(int degree) {
+ if (degree >= cachedGenerators.size()) {
+ GenericGFPoly lastGenerator = cachedGenerators.get(cachedGenerators.size() - 1);
+ for (int d = cachedGenerators.size(); d <= degree; d++) {
+ GenericGFPoly nextGenerator = lastGenerator.multiply(
+ new GenericGFPoly(field, new int[] { 1, field.exp(d - 1 + field.getGeneratorBase()) }));
+ cachedGenerators.add(nextGenerator);
+ lastGenerator = nextGenerator;
+ }
+ }
+ return cachedGenerators.get(degree);
+ }
+
+ public void encode(int[] toEncode, int ecBytes) {
+ if (ecBytes == 0) {
+ throw new IllegalArgumentException("No error correction bytes");
+ }
+ int dataBytes = toEncode.length - ecBytes;
+ if (dataBytes <= 0) {
+ throw new IllegalArgumentException("No data bytes provided");
+ }
+ GenericGFPoly generator = buildGenerator(ecBytes);
+ int[] infoCoefficients = new int[dataBytes];
+ System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes);
+ GenericGFPoly info = new GenericGFPoly(field, infoCoefficients);
+ info = info.multiplyByMonomial(ecBytes, 1);
+ GenericGFPoly remainder = info.divide(generator)[1];
+ int[] coefficients = remainder.getCoefficients();
+ int numZeroCoefficients = ecBytes - coefficients.length;
+ for (int i = 0; i < numZeroCoefficients; i++) {
+ toEncode[dataBytes + i] = 0;
+ }
+ System.arraycopy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.length);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonException.java b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonException.java
new file mode 100644
index 000000000..d5b45a612
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/common/reedsolomon/ReedSolomonException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2007 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.common.reedsolomon;
+
+/**
+ * Thrown when an exception occurs during Reed-Solomon decoding, such as when
+ * there are too many errors to correct.
+ *
+ * @author Sean Owen
+ */
+public final class ReedSolomonException extends Exception {
+
+ public ReedSolomonException(String message) {
+ super(message);
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/DataMatrixReader.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/DataMatrixReader.java
new file mode 100644
index 000000000..f66be4af3
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/DataMatrixReader.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2007 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.datamatrix;
+
+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.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.datamatrix.decoder.Decoder;
+import com.google.zxing.datamatrix.detector.Detector;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode Data Matrix codes in an image.
+ *
+ * @author bbrown@google.com (Brian Brown)
+ */
+public final class DataMatrixReader implements Reader {
+
+ private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
+
+ private final Decoder decoder = new Decoder();
+
+ /**
+ * Locates and decodes a Data Matrix code in an image.
+ *
+ * @return a String representing the content encoded by the Data Matrix code
+ * @throws NotFoundException if a Data Matrix code cannot be found
+ * @throws FormatException if a Data Matrix code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+ DecoderResult decoderResult;
+ ResultPoint[] points;
+ if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
+ BitMatrix bits = extractPureBits(image.getBlackMatrix());
+ decoderResult = decoder.decode(bits);
+ points = NO_POINTS;
+ } else {
+ DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect();
+ decoderResult = decoder.decode(detectorResult.getBits());
+ points = detectorResult.getPoints();
+ }
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points,
+ BarcodeFormat.DATA_MATRIX);
+ List byteSegments = decoderResult.getByteSegments();
+ if (byteSegments != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
+ }
+ String ecLevel = decoderResult.getECLevel();
+ if (ecLevel != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ // do nothing
+ }
+
+ /**
+ * This method detects a code in a "pure" image -- that is, pure monochrome image
+ * which contains only an unrotated, unskewed, image of a code, with some white border
+ * around it. This is a specialized method that works exceptionally fast in this special
+ * case.
+ *
+ * @see com.google.zxing.qrcode.QRCodeReader#extractPureBits(BitMatrix)
+ */
+ private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException {
+
+ int[] leftTopBlack = image.getTopLeftOnBit();
+ int[] rightBottomBlack = image.getBottomRightOnBit();
+ if (leftTopBlack == null || rightBottomBlack == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int moduleSize = moduleSize(leftTopBlack, image);
+
+ int top = leftTopBlack[1];
+ int bottom = rightBottomBlack[1];
+ int left = leftTopBlack[0];
+ int right = rightBottomBlack[0];
+
+ int matrixWidth = (right - left + 1) / moduleSize;
+ int matrixHeight = (bottom - top + 1) / moduleSize;
+ if (matrixWidth <= 0 || matrixHeight <= 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Push in the "border" by half the module width so that we start
+ // sampling in the middle of the module. Just in case the image is a
+ // little off, this will help recover.
+ int nudge = moduleSize >> 1;
+ top += nudge;
+ left += nudge;
+
+ // Now just read off the bits
+ BitMatrix bits = new BitMatrix(matrixWidth, matrixHeight);
+ for (int y = 0; y < matrixHeight; y++) {
+ int iOffset = top + y * moduleSize;
+ for (int x = 0; x < matrixWidth; x++) {
+ if (image.get(left + x * moduleSize, iOffset)) {
+ bits.set(x, y);
+ }
+ }
+ }
+ return bits;
+ }
+
+ private static int moduleSize(int[] leftTopBlack, BitMatrix image) throws NotFoundException {
+ int width = image.getWidth();
+ int x = leftTopBlack[0];
+ int y = leftTopBlack[1];
+ while (x < width && image.get(x, y)) {
+ x++;
+ }
+ if (x == width) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int moduleSize = x - leftTopBlack[0];
+ if (moduleSize == 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ return moduleSize;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/DataMatrixWriter.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/DataMatrixWriter.java
new file mode 100644
index 000000000..1560f68f1
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/DataMatrixWriter.java
@@ -0,0 +1,178 @@
+/*
+ * 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.datamatrix;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.Writer;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.datamatrix.encoder.DefaultPlacement;
+import com.google.zxing.Dimension;
+import com.google.zxing.datamatrix.encoder.ErrorCorrection;
+import com.google.zxing.datamatrix.encoder.HighLevelEncoder;
+import com.google.zxing.datamatrix.encoder.SymbolInfo;
+import com.google.zxing.datamatrix.encoder.SymbolShapeHint;
+import com.google.zxing.qrcode.encoder.ByteMatrix;
+
+import java.util.Map;
+
+/**
+ * This object renders a Data Matrix code as a BitMatrix 2D array of greyscale values.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Guillaume Le Biller Added to zxing lib.
+ */
+public final class DataMatrixWriter implements Writer {
+
+ @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 hints) {
+
+ if (contents.isEmpty()) {
+ throw new IllegalArgumentException("Found empty contents");
+ }
+
+ if (format != BarcodeFormat.DATA_MATRIX) {
+ throw new IllegalArgumentException("Can only encode DATA_MATRIX, but got " + format);
+ }
+
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' + height);
+ }
+
+ // Try to get force shape & min / max size
+ SymbolShapeHint shape = SymbolShapeHint.FORCE_NONE;
+ Dimension minSize = null;
+ Dimension maxSize = null;
+ if (hints != null) {
+ SymbolShapeHint requestedShape = (SymbolShapeHint) hints.get(EncodeHintType.DATA_MATRIX_SHAPE);
+ if (requestedShape != null) {
+ shape = requestedShape;
+ }
+ Dimension requestedMinSize = (Dimension) hints.get(EncodeHintType.MIN_SIZE);
+ if (requestedMinSize != null) {
+ minSize = requestedMinSize;
+ }
+ Dimension requestedMaxSize = (Dimension) hints.get(EncodeHintType.MAX_SIZE);
+ if (requestedMaxSize != null) {
+ maxSize = requestedMaxSize;
+ }
+ }
+
+
+ //1. step: Data encodation
+ String encoded = HighLevelEncoder.encodeHighLevel(contents, shape, minSize, maxSize);
+
+ SymbolInfo symbolInfo = SymbolInfo.lookup(encoded.length(), shape, minSize, maxSize, true);
+
+ //2. step: ECC generation
+ String codewords = ErrorCorrection.encodeECC200(encoded, symbolInfo);
+
+ //3. step: Module placement in Matrix
+ DefaultPlacement placement =
+ new DefaultPlacement(codewords, symbolInfo.getSymbolDataWidth(), symbolInfo.getSymbolDataHeight());
+ placement.place();
+
+ //4. step: low-level encoding
+ return encodeLowLevel(placement, symbolInfo);
+ }
+
+ /**
+ * Encode the given symbol info to a bit matrix.
+ *
+ * @param placement The DataMatrix placement.
+ * @param symbolInfo The symbol info to encode.
+ * @return The bit matrix generated.
+ */
+ private static BitMatrix encodeLowLevel(DefaultPlacement placement, SymbolInfo symbolInfo) {
+ int symbolWidth = symbolInfo.getSymbolDataWidth();
+ int symbolHeight = symbolInfo.getSymbolDataHeight();
+
+ ByteMatrix matrix = new ByteMatrix(symbolInfo.getSymbolWidth(), symbolInfo.getSymbolHeight());
+
+ int matrixY = 0;
+
+ for (int y = 0; y < symbolHeight; y++) {
+ // Fill the top edge with alternate 0 / 1
+ int matrixX;
+ if ((y % symbolInfo.matrixHeight) == 0) {
+ matrixX = 0;
+ for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) {
+ matrix.set(matrixX, matrixY, (x % 2) == 0);
+ matrixX++;
+ }
+ matrixY++;
+ }
+ matrixX = 0;
+ for (int x = 0; x < symbolWidth; x++) {
+ // Fill the right edge with full 1
+ if ((x % symbolInfo.matrixWidth) == 0) {
+ matrix.set(matrixX, matrixY, true);
+ matrixX++;
+ }
+ matrix.set(matrixX, matrixY, placement.getBit(x, y));
+ matrixX++;
+ // Fill the right edge with alternate 0 / 1
+ if ((x % symbolInfo.matrixWidth) == symbolInfo.matrixWidth - 1) {
+ matrix.set(matrixX, matrixY, (y % 2) == 0);
+ matrixX++;
+ }
+ }
+ matrixY++;
+ // Fill the bottom edge with full 1
+ if ((y % symbolInfo.matrixHeight) == symbolInfo.matrixHeight - 1) {
+ matrixX = 0;
+ for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) {
+ matrix.set(matrixX, matrixY, true);
+ matrixX++;
+ }
+ matrixY++;
+ }
+ }
+
+ return convertByteMatrixToBitMatrix(matrix);
+ }
+
+ /**
+ * Convert the ByteMatrix to BitMatrix.
+ *
+ * @param matrix The input matrix.
+ * @return The output matrix.
+ */
+ private static BitMatrix convertByteMatrixToBitMatrix(ByteMatrix matrix) {
+ int matrixWidgth = matrix.getWidth();
+ int matrixHeight = matrix.getHeight();
+
+ BitMatrix output = new BitMatrix(matrixWidgth, matrixHeight);
+ output.clear();
+ for (int i = 0; i < matrixWidgth; i++) {
+ for (int j = 0; j < matrixHeight; j++) {
+ // Zero is white in the bytematrix
+ if (matrix.get(i, j) == 1) {
+ output.set(i, j);
+ }
+ }
+ }
+
+ return output;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/BitMatrixParser.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/BitMatrixParser.java
new file mode 100644
index 000000000..f04f21937
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/BitMatrixParser.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2007 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.datamatrix.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * @author bbrown@google.com (Brian Brown)
+ */
+final class BitMatrixParser {
+
+ private final BitMatrix mappingBitMatrix;
+ private final BitMatrix readMappingMatrix;
+ private final Version version;
+
+ /**
+ * @param bitMatrix {@link BitMatrix} to parse
+ * @throws FormatException if dimension is < 8 or > 144 or not 0 mod 2
+ */
+ BitMatrixParser(BitMatrix bitMatrix) throws FormatException {
+ int dimension = bitMatrix.getHeight();
+ if (dimension < 8 || dimension > 144 || (dimension & 0x01) != 0) {
+ throw FormatException.getFormatInstance();
+ }
+
+ version = readVersion(bitMatrix);
+ this.mappingBitMatrix = extractDataRegion(bitMatrix);
+ this.readMappingMatrix = new BitMatrix(this.mappingBitMatrix.getWidth(), this.mappingBitMatrix.getHeight());
+ }
+
+ Version getVersion() {
+ return version;
+ }
+
+ /**
+ * Creates the version object based on the dimension of the original bit matrix from
+ * the datamatrix code.
+ *
+ * See ISO 16022:2006 Table 7 - ECC 200 symbol attributes
+ *
+ * @param bitMatrix Original {@link BitMatrix} including alignment patterns
+ * @return {@link Version} encapsulating the Data Matrix Code's "version"
+ * @throws FormatException if the dimensions of the mapping matrix are not valid
+ * Data Matrix dimensions.
+ */
+ private static Version readVersion(BitMatrix bitMatrix) throws FormatException {
+ int numRows = bitMatrix.getHeight();
+ int numColumns = bitMatrix.getWidth();
+ return Version.getVersionForDimensions(numRows, numColumns);
+ }
+
+ /**
+ * Reads the bits in the {@link BitMatrix} representing the mapping matrix (No alignment patterns)
+ * in the correct order in order to reconstitute the codewords bytes contained within the
+ * Data Matrix Code.
+ *
+ * @return bytes encoded within the Data Matrix Code
+ * @throws FormatException if the exact number of bytes expected is not read
+ */
+ byte[] readCodewords() throws FormatException {
+
+ byte[] result = new byte[version.getTotalCodewords()];
+ int resultOffset = 0;
+
+ int row = 4;
+ int column = 0;
+
+ int numRows = mappingBitMatrix.getHeight();
+ int numColumns = mappingBitMatrix.getWidth();
+
+ boolean corner1Read = false;
+ boolean corner2Read = false;
+ boolean corner3Read = false;
+ boolean corner4Read = false;
+
+ // Read all of the codewords
+ do {
+ // Check the four corner cases
+ if ((row == numRows) && (column == 0) && !corner1Read) {
+ result[resultOffset++] = (byte) readCorner1(numRows, numColumns);
+ row -= 2;
+ column +=2;
+ corner1Read = true;
+ } else if ((row == numRows-2) && (column == 0) && ((numColumns & 0x03) != 0) && !corner2Read) {
+ result[resultOffset++] = (byte) readCorner2(numRows, numColumns);
+ row -= 2;
+ column +=2;
+ corner2Read = true;
+ } else if ((row == numRows+4) && (column == 2) && ((numColumns & 0x07) == 0) && !corner3Read) {
+ result[resultOffset++] = (byte) readCorner3(numRows, numColumns);
+ row -= 2;
+ column +=2;
+ corner3Read = true;
+ } else if ((row == numRows-2) && (column == 0) && ((numColumns & 0x07) == 4) && !corner4Read) {
+ result[resultOffset++] = (byte) readCorner4(numRows, numColumns);
+ row -= 2;
+ column +=2;
+ corner4Read = true;
+ } else {
+ // Sweep upward diagonally to the right
+ do {
+ if ((row < numRows) && (column >= 0) && !readMappingMatrix.get(column, row)) {
+ result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns);
+ }
+ row -= 2;
+ column +=2;
+ } while ((row >= 0) && (column < numColumns));
+ row += 1;
+ column +=3;
+
+ // Sweep downward diagonally to the left
+ do {
+ if ((row >= 0) && (column < numColumns) && !readMappingMatrix.get(column, row)) {
+ result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns);
+ }
+ row += 2;
+ column -=2;
+ } while ((row < numRows) && (column >= 0));
+ row += 3;
+ column +=1;
+ }
+ } while ((row < numRows) || (column < numColumns));
+
+ if (resultOffset != version.getTotalCodewords()) {
+ throw FormatException.getFormatInstance();
+ }
+ return result;
+ }
+
+ /**
+ * Reads a bit of the mapping matrix accounting for boundary wrapping.
+ *
+ * @param row Row to read in the mapping matrix
+ * @param column Column to read in the mapping matrix
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return value of the given bit in the mapping matrix
+ */
+ boolean readModule(int row, int column, int numRows, int numColumns) {
+ // Adjust the row and column indices based on boundary wrapping
+ if (row < 0) {
+ row += numRows;
+ column += 4 - ((numRows + 4) & 0x07);
+ }
+ if (column < 0) {
+ column += numColumns;
+ row += 4 - ((numColumns + 4) & 0x07);
+ }
+ readMappingMatrix.set(column, row);
+ return mappingBitMatrix.get(column, row);
+ }
+
+ /**
+ * Reads the 8 bits of the standard Utah-shaped pattern.
+ *
+ * See ISO 16022:2006, 5.8.1 Figure 6
+ *
+ * @param row Current row in the mapping matrix, anchored at the 8th bit (LSB) of the pattern
+ * @param column Current column in the mapping matrix, anchored at the 8th bit (LSB) of the pattern
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the utah shape
+ */
+ int readUtah(int row, int column, int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(row - 2, column - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row - 2, column - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row - 1, column - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row - 1, column - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row - 1, column, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row, column - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row, column - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row, column, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Reads the 8 bits of the special corner condition 1.
+ *
+ * See ISO 16022:2006, Figure F.3
+ *
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the Corner condition 1
+ */
+ int readCorner1(int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(numRows - 1, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(2, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(3, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Reads the 8 bits of the special corner condition 2.
+ *
+ * See ISO 16022:2006, Figure F.4
+ *
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the Corner condition 2
+ */
+ int readCorner2(int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(numRows - 3, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 2, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 4, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 3, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Reads the 8 bits of the special corner condition 3.
+ *
+ * See ISO 16022:2006, Figure F.5
+ *
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the Corner condition 3
+ */
+ int readCorner3(int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(numRows - 1, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 3, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 3, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Reads the 8 bits of the special corner condition 4.
+ *
+ * See ISO 16022:2006, Figure F.6
+ *
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the Corner condition 4
+ */
+ int readCorner4(int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(numRows - 3, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 2, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(2, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(3, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Extracts the data region from a {@link BitMatrix} that contains
+ * alignment patterns.
+ *
+ * @param bitMatrix Original {@link BitMatrix} with alignment patterns
+ * @return BitMatrix that has the alignment patterns removed
+ */
+ BitMatrix extractDataRegion(BitMatrix bitMatrix) {
+ int symbolSizeRows = version.getSymbolSizeRows();
+ int symbolSizeColumns = version.getSymbolSizeColumns();
+
+ if (bitMatrix.getHeight() != symbolSizeRows) {
+ throw new IllegalArgumentException("Dimension of bitMarix must match the version size");
+ }
+
+ int dataRegionSizeRows = version.getDataRegionSizeRows();
+ int dataRegionSizeColumns = version.getDataRegionSizeColumns();
+
+ int numDataRegionsRow = symbolSizeRows / dataRegionSizeRows;
+ int numDataRegionsColumn = symbolSizeColumns / dataRegionSizeColumns;
+
+ int sizeDataRegionRow = numDataRegionsRow * dataRegionSizeRows;
+ int sizeDataRegionColumn = numDataRegionsColumn * dataRegionSizeColumns;
+
+ BitMatrix bitMatrixWithoutAlignment = new BitMatrix(sizeDataRegionColumn, sizeDataRegionRow);
+ for (int dataRegionRow = 0; dataRegionRow < numDataRegionsRow; ++dataRegionRow) {
+ int dataRegionRowOffset = dataRegionRow * dataRegionSizeRows;
+ for (int dataRegionColumn = 0; dataRegionColumn < numDataRegionsColumn; ++dataRegionColumn) {
+ int dataRegionColumnOffset = dataRegionColumn * dataRegionSizeColumns;
+ for (int i = 0; i < dataRegionSizeRows; ++i) {
+ int readRowOffset = dataRegionRow * (dataRegionSizeRows + 2) + 1 + i;
+ int writeRowOffset = dataRegionRowOffset + i;
+ for (int j = 0; j < dataRegionSizeColumns; ++j) {
+ int readColumnOffset = dataRegionColumn * (dataRegionSizeColumns + 2) + 1 + j;
+ if (bitMatrix.get(readColumnOffset, readRowOffset)) {
+ int writeColumnOffset = dataRegionColumnOffset + j;
+ bitMatrixWithoutAlignment.set(writeColumnOffset, writeRowOffset);
+ }
+ }
+ }
+ }
+ }
+ return bitMatrixWithoutAlignment;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/DataBlock.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/DataBlock.java
new file mode 100644
index 000000000..a91b72a7b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/DataBlock.java
@@ -0,0 +1,117 @@
+/*
+ * 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.datamatrix.decoder;
+
+/**
+ * Encapsulates a block of data within a Data Matrix Code. Data Matrix Codes may split their data into
+ * multiple blocks, each of which is a unit of data and error-correction codewords. Each
+ * is represented by an instance of this class.
+ *
+ * @author bbrown@google.com (Brian Brown)
+ */
+final class DataBlock {
+
+ private final int numDataCodewords;
+ private final byte[] codewords;
+
+ private DataBlock(int numDataCodewords, byte[] codewords) {
+ this.numDataCodewords = numDataCodewords;
+ this.codewords = codewords;
+ }
+
+ /**
+ * When Data Matrix Codes use multiple data blocks, they actually interleave the bytes of each of them.
+ * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This
+ * method will separate the data into original blocks.
+ *
+ * @param rawCodewords bytes as read directly from the Data Matrix Code
+ * @param version version of the Data Matrix Code
+ * @return DataBlocks containing original bytes, "de-interleaved" from representation in the
+ * Data Matrix Code
+ */
+ static DataBlock[] getDataBlocks(byte[] rawCodewords,
+ Version version) {
+ // Figure out the number and size of data blocks used by this version
+ Version.ECBlocks ecBlocks = version.getECBlocks();
+
+ // First count the total number of data blocks
+ int totalBlocks = 0;
+ Version.ECB[] ecBlockArray = ecBlocks.getECBlocks();
+ for (Version.ECB ecBlock : ecBlockArray) {
+ totalBlocks += ecBlock.getCount();
+ }
+
+ // Now establish DataBlocks of the appropriate size and number of data codewords
+ DataBlock[] result = new DataBlock[totalBlocks];
+ int numResultBlocks = 0;
+ for (Version.ECB ecBlock : ecBlockArray) {
+ for (int i = 0; i < ecBlock.getCount(); i++) {
+ int numDataCodewords = ecBlock.getDataCodewords();
+ int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords;
+ result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]);
+ }
+ }
+
+ // All blocks have the same amount of data, except that the last n
+ // (where n may be 0) have 1 less byte. Figure out where these start.
+ // TODO(bbrown): There is only one case where there is a difference for Data Matrix for size 144
+ int longerBlocksTotalCodewords = result[0].codewords.length;
+ //int shorterBlocksTotalCodewords = longerBlocksTotalCodewords - 1;
+
+ int longerBlocksNumDataCodewords = longerBlocksTotalCodewords - ecBlocks.getECCodewords();
+ int shorterBlocksNumDataCodewords = longerBlocksNumDataCodewords - 1;
+ // The last elements of result may be 1 element shorter for 144 matrix
+ // first fill out as many elements as all of them have minus 1
+ int rawCodewordsOffset = 0;
+ for (int i = 0; i < shorterBlocksNumDataCodewords; i++) {
+ for (int j = 0; j < numResultBlocks; j++) {
+ result[j].codewords[i] = rawCodewords[rawCodewordsOffset++];
+ }
+ }
+
+ // Fill out the last data block in the longer ones
+ boolean specialVersion = version.getVersionNumber() == 24;
+ int numLongerBlocks = specialVersion ? 8 : numResultBlocks;
+ for (int j = 0; j < numLongerBlocks; j++) {
+ result[j].codewords[longerBlocksNumDataCodewords - 1] = rawCodewords[rawCodewordsOffset++];
+ }
+
+ // Now add in error correction blocks
+ int max = result[0].codewords.length;
+ for (int i = longerBlocksNumDataCodewords; i < max; i++) {
+ for (int j = 0; j < numResultBlocks; j++) {
+ int iOffset = specialVersion && j > 7 ? i - 1 : i;
+ result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++];
+ }
+ }
+
+ if (rawCodewordsOffset != rawCodewords.length) {
+ throw new IllegalArgumentException();
+ }
+
+ return result;
+ }
+
+ int getNumDataCodewords() {
+ return numDataCodewords;
+ }
+
+ byte[] getCodewords() {
+ return codewords;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java
new file mode 100644
index 000000000..8b0816a92
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java
@@ -0,0 +1,494 @@
+/*
+ * 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.datamatrix.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitSource;
+import com.google.zxing.common.DecoderResult;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Data Matrix Codes can encode text as bits in one of several modes, and can use multiple modes
+ * in one Data Matrix Code. This class decodes the bits back into text.
+ *
+ * See ISO 16022:2006, 5.2.1 - 5.2.9.2
+ *
+ * @author bbrown@google.com (Brian Brown)
+ * @author Sean Owen
+ */
+final class DecodedBitStreamParser {
+
+ private enum Mode {
+ PAD_ENCODE, // Not really a mode
+ ASCII_ENCODE,
+ C40_ENCODE,
+ TEXT_ENCODE,
+ ANSIX12_ENCODE,
+ EDIFACT_ENCODE,
+ BASE256_ENCODE
+ }
+
+ /**
+ * See ISO 16022:2006, Annex C Table C.1
+ * The C40 Basic Character Set (*'s used for placeholders for the shift values)
+ */
+ private static final char[] C40_BASIC_SET_CHARS = {
+ '*', '*', '*', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'
+ };
+
+ private static final char[] C40_SHIFT2_SET_CHARS = {
+ '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.',
+ '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '^', '_'
+ };
+
+ /**
+ * See ISO 16022:2006, Annex C Table C.2
+ * The Text Basic Character Set (*'s used for placeholders for the shift values)
+ */
+ private static final char[] TEXT_BASIC_SET_CHARS = {
+ '*', '*', '*', ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
+ };
+
+ private static final char[] TEXT_SHIFT3_SET_CHARS = {
+ '`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '{', '|', '}', '~', (char) 127
+ };
+
+ private DecodedBitStreamParser() {
+ }
+
+ static DecoderResult decode(byte[] bytes) throws FormatException {
+ BitSource bits = new BitSource(bytes);
+ StringBuilder result = new StringBuilder(100);
+ StringBuilder resultTrailer = new StringBuilder(0);
+ List byteSegments = new ArrayList<>(1);
+ Mode mode = Mode.ASCII_ENCODE;
+ do {
+ if (mode == Mode.ASCII_ENCODE) {
+ mode = decodeAsciiSegment(bits, result, resultTrailer);
+ } else {
+ switch (mode) {
+ case C40_ENCODE:
+ decodeC40Segment(bits, result);
+ break;
+ case TEXT_ENCODE:
+ decodeTextSegment(bits, result);
+ break;
+ case ANSIX12_ENCODE:
+ decodeAnsiX12Segment(bits, result);
+ break;
+ case EDIFACT_ENCODE:
+ decodeEdifactSegment(bits, result);
+ break;
+ case BASE256_ENCODE:
+ decodeBase256Segment(bits, result, byteSegments);
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+ mode = Mode.ASCII_ENCODE;
+ }
+ } while (mode != Mode.PAD_ENCODE && bits.available() > 0);
+ if (resultTrailer.length() > 0) {
+ result.append(resultTrailer);
+ }
+ return new DecoderResult(bytes, result.toString(), byteSegments.isEmpty() ? null : byteSegments, null);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.3 and Annex C, Table C.2
+ */
+ private static Mode decodeAsciiSegment(BitSource bits,
+ StringBuilder result,
+ StringBuilder resultTrailer) throws FormatException {
+ boolean upperShift = false;
+ do {
+ int oneByte = bits.readBits(8);
+ if (oneByte == 0) {
+ throw FormatException.getFormatInstance();
+ } else if (oneByte <= 128) { // ASCII data (ASCII value + 1)
+ if (upperShift) {
+ oneByte += 128;
+ //upperShift = false;
+ }
+ result.append((char) (oneByte - 1));
+ return Mode.ASCII_ENCODE;
+ } else if (oneByte == 129) { // Pad
+ return Mode.PAD_ENCODE;
+ } else if (oneByte <= 229) { // 2-digit data 00-99 (Numeric Value + 130)
+ int value = oneByte - 130;
+ if (value < 10) { // padd with '0' for single digit values
+ result.append('0');
+ }
+ result.append(value);
+ } else if (oneByte == 230) { // Latch to C40 encodation
+ return Mode.C40_ENCODE;
+ } else if (oneByte == 231) { // Latch to Base 256 encodation
+ return Mode.BASE256_ENCODE;
+ } else if (oneByte == 232) {
+ // FNC1
+ result.append((char) 29); // translate as ASCII 29
+ } else if (oneByte == 233 || oneByte == 234) {
+ // Structured Append, Reader Programming
+ // Ignore these symbols for now
+ //throw ReaderException.getInstance();
+ } else if (oneByte == 235) { // Upper Shift (shift to Extended ASCII)
+ upperShift = true;
+ } else if (oneByte == 236) { // 05 Macro
+ result.append("[)>\u001E05\u001D");
+ resultTrailer.insert(0, "\u001E\u0004");
+ } else if (oneByte == 237) { // 06 Macro
+ result.append("[)>\u001E06\u001D");
+ resultTrailer.insert(0, "\u001E\u0004");
+ } else if (oneByte == 238) { // Latch to ANSI X12 encodation
+ return Mode.ANSIX12_ENCODE;
+ } else if (oneByte == 239) { // Latch to Text encodation
+ return Mode.TEXT_ENCODE;
+ } else if (oneByte == 240) { // Latch to EDIFACT encodation
+ return Mode.EDIFACT_ENCODE;
+ } else if (oneByte == 241) { // ECI Character
+ // TODO(bbrown): I think we need to support ECI
+ //throw ReaderException.getInstance();
+ // Ignore this symbol for now
+ } else if (oneByte >= 242) { // Not to be used in ASCII encodation
+ // ... but work around encoders that end with 254, latch back to ASCII
+ if (oneByte != 254 || bits.available() != 0) {
+ throw FormatException.getFormatInstance();
+ }
+ }
+ } while (bits.available() > 0);
+ return Mode.ASCII_ENCODE;
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.5 and Annex C, Table C.1
+ */
+ private static void decodeC40Segment(BitSource bits, StringBuilder result) 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
+ boolean upperShift = false;
+
+ int[] cValues = new int[3];
+ int shift = 0;
+
+ do {
+ // If there is only one byte left then it will be encoded as ASCII
+ if (bits.available() == 8) {
+ return;
+ }
+ int firstByte = bits.readBits(8);
+ if (firstByte == 254) { // Unlatch codeword
+ return;
+ }
+
+ parseTwoBytes(firstByte, bits.readBits(8), cValues);
+
+ for (int i = 0; i < 3; i++) {
+ int cValue = cValues[i];
+ switch (shift) {
+ case 0:
+ if (cValue < 3) {
+ shift = cValue + 1;
+ } else if (cValue < C40_BASIC_SET_CHARS.length) {
+ char c40char = C40_BASIC_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (c40char + 128));
+ upperShift = false;
+ } else {
+ result.append(c40char);
+ }
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case 1:
+ if (upperShift) {
+ result.append((char) (cValue + 128));
+ upperShift = false;
+ } else {
+ result.append((char) cValue);
+ }
+ shift = 0;
+ break;
+ case 2:
+ if (cValue < C40_SHIFT2_SET_CHARS.length) {
+ char c40char = C40_SHIFT2_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (c40char + 128));
+ upperShift = false;
+ } else {
+ result.append(c40char);
+ }
+ } else if (cValue == 27) { // FNC1
+ result.append((char) 29); // translate as ASCII 29
+ } else if (cValue == 30) { // Upper Shift
+ upperShift = true;
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ shift = 0;
+ break;
+ case 3:
+ if (upperShift) {
+ result.append((char) (cValue + 224));
+ upperShift = false;
+ } else {
+ result.append((char) (cValue + 96));
+ }
+ shift = 0;
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+ }
+ } while (bits.available() > 0);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.6 and Annex C, Table C.2
+ */
+ private static void decodeTextSegment(BitSource bits, StringBuilder result) 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
+ boolean upperShift = false;
+
+ int[] cValues = new int[3];
+ int shift = 0;
+ do {
+ // If there is only one byte left then it will be encoded as ASCII
+ if (bits.available() == 8) {
+ return;
+ }
+ int firstByte = bits.readBits(8);
+ if (firstByte == 254) { // Unlatch codeword
+ return;
+ }
+
+ parseTwoBytes(firstByte, bits.readBits(8), cValues);
+
+ for (int i = 0; i < 3; i++) {
+ int cValue = cValues[i];
+ switch (shift) {
+ case 0:
+ if (cValue < 3) {
+ shift = cValue + 1;
+ } else if (cValue < TEXT_BASIC_SET_CHARS.length) {
+ char textChar = TEXT_BASIC_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (textChar + 128));
+ upperShift = false;
+ } else {
+ result.append(textChar);
+ }
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case 1:
+ if (upperShift) {
+ result.append((char) (cValue + 128));
+ upperShift = false;
+ } else {
+ result.append((char) cValue);
+ }
+ shift = 0;
+ break;
+ case 2:
+ // Shift 2 for Text is the same encoding as C40
+ if (cValue < C40_SHIFT2_SET_CHARS.length) {
+ char c40char = C40_SHIFT2_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (c40char + 128));
+ upperShift = false;
+ } else {
+ result.append(c40char);
+ }
+ } else if (cValue == 27) { // FNC1
+ result.append((char) 29); // translate as ASCII 29
+ } else if (cValue == 30) { // Upper Shift
+ upperShift = true;
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ shift = 0;
+ break;
+ case 3:
+ if (cValue < TEXT_SHIFT3_SET_CHARS.length) {
+ char textChar = TEXT_SHIFT3_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (textChar + 128));
+ upperShift = false;
+ } else {
+ result.append(textChar);
+ }
+ shift = 0;
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+ }
+ } while (bits.available() > 0);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.7
+ */
+ private static void decodeAnsiX12Segment(BitSource bits,
+ StringBuilder result) throws FormatException {
+ // Three ANSI X12 values are encoded in a 16-bit value as
+ // (1600 * C1) + (40 * C2) + C3 + 1
+
+ int[] cValues = new int[3];
+ do {
+ // If there is only one byte left then it will be encoded as ASCII
+ if (bits.available() == 8) {
+ return;
+ }
+ int firstByte = bits.readBits(8);
+ if (firstByte == 254) { // Unlatch codeword
+ return;
+ }
+
+ parseTwoBytes(firstByte, bits.readBits(8), cValues);
+
+ for (int i = 0; i < 3; i++) {
+ int cValue = cValues[i];
+ if (cValue == 0) { // X12 segment terminator
+ result.append('\r');
+ } else if (cValue == 1) { // X12 segment separator *
+ result.append('*');
+ } else if (cValue == 2) { // X12 sub-element separator >
+ result.append('>');
+ } else if (cValue == 3) { // space
+ result.append(' ');
+ } else if (cValue < 14) { // 0 - 9
+ result.append((char) (cValue + 44));
+ } else if (cValue < 40) { // A - Z
+ result.append((char) (cValue + 51));
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ }
+ } while (bits.available() > 0);
+ }
+
+ private static void parseTwoBytes(int firstByte, int secondByte, int[] result) {
+ int fullBitValue = (firstByte << 8) + secondByte - 1;
+ int temp = fullBitValue / 1600;
+ result[0] = temp;
+ fullBitValue -= temp * 1600;
+ temp = fullBitValue / 40;
+ result[1] = temp;
+ result[2] = fullBitValue - temp * 40;
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.8 and Annex C Table C.3
+ */
+ private static void decodeEdifactSegment(BitSource bits, StringBuilder result) {
+ do {
+ // If there is only two or less bytes left then it will be encoded as ASCII
+ if (bits.available() <= 16) {
+ return;
+ }
+
+ for (int i = 0; i < 4; i++) {
+ int edifactValue = bits.readBits(6);
+
+ // Check for the unlatch character
+ if (edifactValue == 0x1F) { // 011111
+ // Read rest of byte, which should be 0, and stop
+ int bitsLeft = 8 - bits.getBitOffset();
+ if (bitsLeft != 8) {
+ bits.readBits(bitsLeft);
+ }
+ return;
+ }
+
+ if ((edifactValue & 0x20) == 0) { // no 1 in the leading (6th) bit
+ edifactValue |= 0x40; // Add a leading 01 to the 6 bit binary value
+ }
+ result.append((char) edifactValue);
+ }
+ } while (bits.available() > 0);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.9 and Annex B, B.2
+ */
+ private static void decodeBase256Segment(BitSource bits,
+ StringBuilder result,
+ Collection byteSegments)
+ throws FormatException {
+ // Figure out how long the Base 256 Segment is.
+ int codewordPosition = 1 + bits.getByteOffset(); // position is 1-indexed
+ int d1 = unrandomize255State(bits.readBits(8), codewordPosition++);
+ int count;
+ if (d1 == 0) { // Read the remainder of the symbol
+ count = bits.available() / 8;
+ } else if (d1 < 250) {
+ count = d1;
+ } else {
+ count = 250 * (d1 - 249) + unrandomize255State(bits.readBits(8), codewordPosition++);
+ }
+
+ // We're seeing NegativeArraySizeException errors from users.
+ if (count < 0) {
+ throw FormatException.getFormatInstance();
+ }
+
+ byte[] bytes = new byte[count];
+ for (int i = 0; i < count; i++) {
+ // Have seen this particular error in the wild, such as at
+ // http://www.bcgen.com/demo/IDAutomationStreamingDataMatrix.aspx?MODE=3&D=Fred&PFMT=3&PT=F&X=0.3&O=0&LM=0.2
+ if (bits.available() < 8) {
+ throw FormatException.getFormatInstance();
+ }
+ bytes[i] = (byte) unrandomize255State(bits.readBits(8), codewordPosition++);
+ }
+ byteSegments.add(bytes);
+ try {
+ result.append(new String(bytes, "ISO8859_1"));
+ } catch (UnsupportedEncodingException uee) {
+ throw new IllegalStateException("Platform does not support required encoding: " + uee);
+ }
+ }
+
+ /**
+ * See ISO 16022:2006, Annex B, B.2
+ */
+ private static int unrandomize255State(int randomizedBase256Codeword,
+ int base256CodewordPosition) {
+ int pseudoRandomNumber = ((149 * base256CodewordPosition) % 255) + 1;
+ int tempVariable = randomizedBase256Codeword - pseudoRandomNumber;
+ return tempVariable >= 0 ? tempVariable : tempVariable + 256;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/Decoder.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/Decoder.java
new file mode 100644
index 000000000..7a1ad6b93
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/Decoder.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2007 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.datamatrix.decoder;
+
+import com.google.zxing.ChecksumException;
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+/**
+ * The main class which implements Data Matrix Code decoding -- as opposed to locating and extracting
+ * the Data Matrix Code from an image.
+ *
+ * @author bbrown@google.com (Brian Brown)
+ */
+public final class Decoder {
+
+ private final ReedSolomonDecoder rsDecoder;
+
+ public Decoder() {
+ rsDecoder = new ReedSolomonDecoder(GenericGF.DATA_MATRIX_FIELD_256);
+ }
+
+ /**
+ * Convenience method that can decode a Data Matrix Code represented as a 2D array of booleans.
+ * "true" is taken to mean a black module.
+ *
+ * @param image booleans representing white/black Data Matrix Code modules
+ * @return text and bytes encoded within the Data Matrix Code
+ * @throws FormatException if the Data Matrix Code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ public DecoderResult decode(boolean[][] image) throws FormatException, ChecksumException {
+ int dimension = image.length;
+ BitMatrix bits = new BitMatrix(dimension);
+ for (int i = 0; i < dimension; i++) {
+ for (int j = 0; j < dimension; j++) {
+ if (image[i][j]) {
+ bits.set(j, i);
+ }
+ }
+ }
+ return decode(bits);
+ }
+
+ /**
+ * Decodes a Data Matrix Code represented as a {@link BitMatrix}. A 1 or "true" is taken
+ * to mean a black module.
+ *
+ * @param bits booleans representing white/black Data Matrix Code modules
+ * @return text and bytes encoded within the Data Matrix Code
+ * @throws FormatException if the Data Matrix Code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ public DecoderResult decode(BitMatrix bits) throws FormatException, ChecksumException {
+
+ // Construct a parser and read version, error-correction level
+ BitMatrixParser parser = new BitMatrixParser(bits);
+ Version version = parser.getVersion();
+
+ // Read codewords
+ byte[] codewords = parser.readCodewords();
+ // Separate into data blocks
+ DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version);
+
+ int dataBlocksCount = dataBlocks.length;
+
+ // Count total number of data bytes
+ int totalBytes = 0;
+ for (DataBlock db : dataBlocks) {
+ totalBytes += db.getNumDataCodewords();
+ }
+ byte[] resultBytes = new byte[totalBytes];
+
+ // Error-correct and copy data blocks together into a stream of bytes
+ for (int j = 0; j < dataBlocksCount; j++) {
+ DataBlock dataBlock = dataBlocks[j];
+ byte[] codewordBytes = dataBlock.getCodewords();
+ int numDataCodewords = dataBlock.getNumDataCodewords();
+ correctErrors(codewordBytes, numDataCodewords);
+ for (int i = 0; i < numDataCodewords; i++) {
+ // De-interlace data blocks.
+ resultBytes[i * dataBlocksCount + j] = codewordBytes[i];
+ }
+ }
+
+ // Decode the contents of that stream of bytes
+ return DecodedBitStreamParser.decode(resultBytes);
+ }
+
+ /**
+ * Given data and error-correction codewords received, possibly corrupted by errors, attempts to
+ * correct the errors in-place using Reed-Solomon error correction.
+ *
+ * @param codewordBytes data and error correction codewords
+ * @param numDataCodewords number of codewords that are data bytes
+ * @throws ChecksumException if error correction fails
+ */
+ private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {
+ int numCodewords = codewordBytes.length;
+ // First read into an array of ints
+ int[] codewordsInts = new int[numCodewords];
+ for (int i = 0; i < numCodewords; i++) {
+ codewordsInts[i] = codewordBytes[i] & 0xFF;
+ }
+ int numECCodewords = codewordBytes.length - numDataCodewords;
+ try {
+ rsDecoder.decode(codewordsInts, numECCodewords);
+ } catch (ReedSolomonException ignored) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ // Copy back into array of bytes -- only need to worry about the bytes that were data
+ // We don't care about errors in the error-correction codewords
+ for (int i = 0; i < numDataCodewords; i++) {
+ codewordBytes[i] = (byte) codewordsInts[i];
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/Version.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/Version.java
new file mode 100644
index 000000000..fff9e1798
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/decoder/Version.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2007 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.datamatrix.decoder;
+
+import com.google.zxing.FormatException;
+
+/**
+ * The Version object encapsulates attributes about a particular
+ * size Data Matrix Code.
+ *
+ * @author bbrown@google.com (Brian Brown)
+ */
+public final class Version {
+
+ private static final Version[] VERSIONS = buildVersions();
+
+ private final int versionNumber;
+ private final int symbolSizeRows;
+ private final int symbolSizeColumns;
+ private final int dataRegionSizeRows;
+ private final int dataRegionSizeColumns;
+ private final ECBlocks ecBlocks;
+ private final int totalCodewords;
+
+ private Version(int versionNumber,
+ int symbolSizeRows,
+ int symbolSizeColumns,
+ int dataRegionSizeRows,
+ int dataRegionSizeColumns,
+ ECBlocks ecBlocks) {
+ this.versionNumber = versionNumber;
+ this.symbolSizeRows = symbolSizeRows;
+ this.symbolSizeColumns = symbolSizeColumns;
+ this.dataRegionSizeRows = dataRegionSizeRows;
+ this.dataRegionSizeColumns = dataRegionSizeColumns;
+ this.ecBlocks = ecBlocks;
+
+ // Calculate the total number of codewords
+ int total = 0;
+ int ecCodewords = ecBlocks.getECCodewords();
+ ECB[] ecbArray = ecBlocks.getECBlocks();
+ for (ECB ecBlock : ecbArray) {
+ total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords);
+ }
+ this.totalCodewords = total;
+ }
+
+ public int getVersionNumber() {
+ return versionNumber;
+ }
+
+ public int getSymbolSizeRows() {
+ return symbolSizeRows;
+ }
+
+ public int getSymbolSizeColumns() {
+ return symbolSizeColumns;
+ }
+
+ public int getDataRegionSizeRows() {
+ return dataRegionSizeRows;
+ }
+
+ public int getDataRegionSizeColumns() {
+ return dataRegionSizeColumns;
+ }
+
+ public int getTotalCodewords() {
+ return totalCodewords;
+ }
+
+ ECBlocks getECBlocks() {
+ return ecBlocks;
+ }
+
+ /**
+ * Deduces version information from Data Matrix dimensions.
+ *
+ * @param numRows Number of rows in modules
+ * @param numColumns Number of columns in modules
+ * @return Version for a Data Matrix Code of those dimensions
+ * @throws FormatException if dimensions do correspond to a valid Data Matrix size
+ */
+ public static Version getVersionForDimensions(int numRows, int numColumns) throws FormatException {
+ if ((numRows & 0x01) != 0 || (numColumns & 0x01) != 0) {
+ throw FormatException.getFormatInstance();
+ }
+
+ for (Version version : VERSIONS) {
+ if (version.symbolSizeRows == numRows && version.symbolSizeColumns == numColumns) {
+ return version;
+ }
+ }
+
+ throw FormatException.getFormatInstance();
+ }
+
+ /**
+ * Encapsulates a set of error-correction blocks in one symbol version. Most versions will
+ * use blocks of differing sizes within one version, so, this encapsulates the parameters for
+ * each set of blocks. It also holds the number of error-correction codewords per block since it
+ * will be the same across all blocks within one version.
+ */
+ static final class ECBlocks {
+ private final int ecCodewords;
+ private final ECB[] ecBlocks;
+
+ private ECBlocks(int ecCodewords, ECB ecBlocks) {
+ this.ecCodewords = ecCodewords;
+ this.ecBlocks = new ECB[] { ecBlocks };
+ }
+
+ private ECBlocks(int ecCodewords, ECB ecBlocks1, ECB ecBlocks2) {
+ this.ecCodewords = ecCodewords;
+ this.ecBlocks = new ECB[] { ecBlocks1, ecBlocks2 };
+ }
+
+ int getECCodewords() {
+ return ecCodewords;
+ }
+
+ ECB[] getECBlocks() {
+ return ecBlocks;
+ }
+ }
+
+ /**
+ * Encapsualtes the parameters for one error-correction block in one symbol version.
+ * This includes the number of data codewords, and the number of times a block with these
+ * parameters is used consecutively in the Data Matrix code version's format.
+ */
+ static final class ECB {
+ private final int count;
+ private final int dataCodewords;
+
+ private ECB(int count, int dataCodewords) {
+ this.count = count;
+ this.dataCodewords = dataCodewords;
+ }
+
+ int getCount() {
+ return count;
+ }
+
+ int getDataCodewords() {
+ return dataCodewords;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(versionNumber);
+ }
+
+ /**
+ * See ISO 16022:2006 5.5.1 Table 7
+ */
+ private static Version[] buildVersions() {
+ return new Version[]{
+ new Version(1, 10, 10, 8, 8,
+ new ECBlocks(5, new ECB(1, 3))),
+ new Version(2, 12, 12, 10, 10,
+ new ECBlocks(7, new ECB(1, 5))),
+ new Version(3, 14, 14, 12, 12,
+ new ECBlocks(10, new ECB(1, 8))),
+ new Version(4, 16, 16, 14, 14,
+ new ECBlocks(12, new ECB(1, 12))),
+ new Version(5, 18, 18, 16, 16,
+ new ECBlocks(14, new ECB(1, 18))),
+ new Version(6, 20, 20, 18, 18,
+ new ECBlocks(18, new ECB(1, 22))),
+ new Version(7, 22, 22, 20, 20,
+ new ECBlocks(20, new ECB(1, 30))),
+ new Version(8, 24, 24, 22, 22,
+ new ECBlocks(24, new ECB(1, 36))),
+ new Version(9, 26, 26, 24, 24,
+ new ECBlocks(28, new ECB(1, 44))),
+ new Version(10, 32, 32, 14, 14,
+ new ECBlocks(36, new ECB(1, 62))),
+ new Version(11, 36, 36, 16, 16,
+ new ECBlocks(42, new ECB(1, 86))),
+ new Version(12, 40, 40, 18, 18,
+ new ECBlocks(48, new ECB(1, 114))),
+ new Version(13, 44, 44, 20, 20,
+ new ECBlocks(56, new ECB(1, 144))),
+ new Version(14, 48, 48, 22, 22,
+ new ECBlocks(68, new ECB(1, 174))),
+ new Version(15, 52, 52, 24, 24,
+ new ECBlocks(42, new ECB(2, 102))),
+ new Version(16, 64, 64, 14, 14,
+ new ECBlocks(56, new ECB(2, 140))),
+ new Version(17, 72, 72, 16, 16,
+ new ECBlocks(36, new ECB(4, 92))),
+ new Version(18, 80, 80, 18, 18,
+ new ECBlocks(48, new ECB(4, 114))),
+ new Version(19, 88, 88, 20, 20,
+ new ECBlocks(56, new ECB(4, 144))),
+ new Version(20, 96, 96, 22, 22,
+ new ECBlocks(68, new ECB(4, 174))),
+ new Version(21, 104, 104, 24, 24,
+ new ECBlocks(56, new ECB(6, 136))),
+ new Version(22, 120, 120, 18, 18,
+ new ECBlocks(68, new ECB(6, 175))),
+ new Version(23, 132, 132, 20, 20,
+ new ECBlocks(62, new ECB(8, 163))),
+ new Version(24, 144, 144, 22, 22,
+ new ECBlocks(62, new ECB(8, 156), new ECB(2, 155))),
+ new Version(25, 8, 18, 6, 16,
+ new ECBlocks(7, new ECB(1, 5))),
+ new Version(26, 8, 32, 6, 14,
+ new ECBlocks(11, new ECB(1, 10))),
+ new Version(27, 12, 26, 10, 24,
+ new ECBlocks(14, new ECB(1, 16))),
+ new Version(28, 12, 36, 10, 16,
+ new ECBlocks(18, new ECB(1, 22))),
+ new Version(29, 16, 36, 14, 16,
+ new ECBlocks(24, new ECB(1, 32))),
+ new Version(30, 16, 48, 14, 22,
+ new ECBlocks(28, new ECB(1, 49)))
+ };
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/detector/Detector.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/detector/Detector.java
new file mode 100644
index 000000000..4e4ccf1e4
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/detector/Detector.java
@@ -0,0 +1,440 @@
+/*
+ * 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.datamatrix.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.common.GridSampler;
+import com.google.zxing.common.detector.MathUtils;
+import com.google.zxing.common.detector.WhiteRectangleDetector;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code
+ * is rotated or skewed, or partially obscured.
+ *
+ * @author Sean Owen
+ */
+public final class Detector {
+
+ private final BitMatrix image;
+ private final WhiteRectangleDetector rectangleDetector;
+
+ public Detector(BitMatrix image) throws NotFoundException {
+ this.image = image;
+ rectangleDetector = new WhiteRectangleDetector(image);
+ }
+
+ /**
+ * Detects a Data Matrix Code in an image.
+ *
+ * @return {@link DetectorResult} encapsulating results of detecting a Data Matrix Code
+ * @throws NotFoundException if no Data Matrix Code can be found
+ */
+ public DetectorResult detect() throws NotFoundException {
+
+ ResultPoint[] cornerPoints = rectangleDetector.detect();
+ ResultPoint pointA = cornerPoints[0];
+ ResultPoint pointB = cornerPoints[1];
+ ResultPoint pointC = cornerPoints[2];
+ ResultPoint pointD = cornerPoints[3];
+
+ // Point A and D are across the diagonal from one another,
+ // as are B and C. Figure out which are the solid black lines
+ // by counting transitions
+ List transitions = new ArrayList<>(4);
+ transitions.add(transitionsBetween(pointA, pointB));
+ transitions.add(transitionsBetween(pointA, pointC));
+ transitions.add(transitionsBetween(pointB, pointD));
+ transitions.add(transitionsBetween(pointC, pointD));
+ Collections.sort(transitions, new ResultPointsAndTransitionsComparator());
+
+ // Sort by number of transitions. First two will be the two solid sides; last two
+ // will be the two alternating black/white sides
+ ResultPointsAndTransitions lSideOne = transitions.get(0);
+ ResultPointsAndTransitions lSideTwo = transitions.get(1);
+
+ // Figure out which point is their intersection by tallying up the number of times we see the
+ // endpoints in the four endpoints. One will show up twice.
+ Map pointCount = new HashMap<>();
+ increment(pointCount, lSideOne.getFrom());
+ increment(pointCount, lSideOne.getTo());
+ increment(pointCount, lSideTwo.getFrom());
+ increment(pointCount, lSideTwo.getTo());
+
+ ResultPoint maybeTopLeft = null;
+ ResultPoint bottomLeft = null;
+ ResultPoint maybeBottomRight = null;
+ for (Map.Entry entry : pointCount.entrySet()) {
+ ResultPoint point = entry.getKey();
+ Integer value = entry.getValue();
+ if (value == 2) {
+ bottomLeft = point; // this is definitely the bottom left, then -- end of two L sides
+ } else {
+ // Otherwise it's either top left or bottom right -- just assign the two arbitrarily now
+ if (maybeTopLeft == null) {
+ maybeTopLeft = point;
+ } else {
+ maybeBottomRight = point;
+ }
+ }
+ }
+
+ if (maybeTopLeft == null || bottomLeft == null || maybeBottomRight == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Bottom left is correct but top left and bottom right might be switched
+ ResultPoint[] corners = { maybeTopLeft, bottomLeft, maybeBottomRight };
+ // Use the dot product trick to sort them out
+ ResultPoint.orderBestPatterns(corners);
+
+ // Now we know which is which:
+ ResultPoint bottomRight = corners[0];
+ bottomLeft = corners[1];
+ ResultPoint topLeft = corners[2];
+
+ // Which point didn't we find in relation to the "L" sides? that's the top right corner
+ ResultPoint topRight;
+ if (!pointCount.containsKey(pointA)) {
+ topRight = pointA;
+ } else if (!pointCount.containsKey(pointB)) {
+ topRight = pointB;
+ } else if (!pointCount.containsKey(pointC)) {
+ topRight = pointC;
+ } else {
+ topRight = pointD;
+ }
+
+ // Next determine the dimension by tracing along the top or right side and counting black/white
+ // transitions. Since we start inside a black module, we should see a number of transitions
+ // equal to 1 less than the code dimension. Well, actually 2 less, because we are going to
+ // end on a black module:
+
+ // The top right point is actually the corner of a module, which is one of the two black modules
+ // adjacent to the white module at the top right. Tracing to that corner from either the top left
+ // or bottom right should work here.
+
+ int dimensionTop = transitionsBetween(topLeft, topRight).getTransitions();
+ int dimensionRight = transitionsBetween(bottomRight, topRight).getTransitions();
+
+ if ((dimensionTop & 0x01) == 1) {
+ // it can't be odd, so, round... up?
+ dimensionTop++;
+ }
+ dimensionTop += 2;
+
+ if ((dimensionRight & 0x01) == 1) {
+ // it can't be odd, so, round... up?
+ dimensionRight++;
+ }
+ dimensionRight += 2;
+
+ BitMatrix bits;
+ ResultPoint correctedTopRight;
+
+ // Rectanguar symbols are 6x16, 6x28, 10x24, 10x32, 14x32, or 14x44. If one dimension is more
+ // than twice the other, it's certainly rectangular, but to cut a bit more slack we accept it as
+ // rectangular if the bigger side is at least 7/4 times the other:
+ if (4 * dimensionTop >= 7 * dimensionRight || 4 * dimensionRight >= 7 * dimensionTop) {
+ // The matrix is rectangular
+
+ correctedTopRight =
+ correctTopRightRectangular(bottomLeft, bottomRight, topLeft, topRight, dimensionTop, dimensionRight);
+ if (correctedTopRight == null){
+ correctedTopRight = topRight;
+ }
+
+ dimensionTop = transitionsBetween(topLeft, correctedTopRight).getTransitions();
+ dimensionRight = transitionsBetween(bottomRight, correctedTopRight).getTransitions();
+
+ if ((dimensionTop & 0x01) == 1) {
+ // it can't be odd, so, round... up?
+ dimensionTop++;
+ }
+
+ if ((dimensionRight & 0x01) == 1) {
+ // it can't be odd, so, round... up?
+ dimensionRight++;
+ }
+
+ bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, correctedTopRight, dimensionTop, dimensionRight);
+
+ } else {
+ // The matrix is square
+
+ int dimension = Math.min(dimensionRight, dimensionTop);
+ // correct top right point to match the white module
+ correctedTopRight = correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension);
+ if (correctedTopRight == null){
+ correctedTopRight = topRight;
+ }
+
+ // Redetermine the dimension using the corrected top right point
+ int dimensionCorrected = Math.max(transitionsBetween(topLeft, correctedTopRight).getTransitions(),
+ transitionsBetween(bottomRight, correctedTopRight).getTransitions());
+ dimensionCorrected++;
+ if ((dimensionCorrected & 0x01) == 1) {
+ dimensionCorrected++;
+ }
+
+ bits = sampleGrid(image,
+ topLeft,
+ bottomLeft,
+ bottomRight,
+ correctedTopRight,
+ dimensionCorrected,
+ dimensionCorrected);
+ }
+
+ return new DetectorResult(bits, new ResultPoint[]{topLeft, bottomLeft, bottomRight, correctedTopRight});
+ }
+
+ /**
+ * Calculates the position of the white top right module using the output of the rectangle detector
+ * for a rectangular matrix
+ */
+ private ResultPoint correctTopRightRectangular(ResultPoint bottomLeft,
+ ResultPoint bottomRight,
+ ResultPoint topLeft,
+ ResultPoint topRight,
+ int dimensionTop,
+ int dimensionRight) {
+
+ float corr = distance(bottomLeft, bottomRight) / (float)dimensionTop;
+ int norm = distance(topLeft, topRight);
+ float cos = (topRight.getX() - topLeft.getX()) / norm;
+ float sin = (topRight.getY() - topLeft.getY()) / norm;
+
+ ResultPoint c1 = new ResultPoint(topRight.getX()+corr*cos, topRight.getY()+corr*sin);
+
+ corr = distance(bottomLeft, topLeft) / (float)dimensionRight;
+ norm = distance(bottomRight, topRight);
+ cos = (topRight.getX() - bottomRight.getX()) / norm;
+ sin = (topRight.getY() - bottomRight.getY()) / norm;
+
+ ResultPoint c2 = new ResultPoint(topRight.getX()+corr*cos, topRight.getY()+corr*sin);
+
+ if (!isValid(c1)) {
+ if (isValid(c2)) {
+ return c2;
+ }
+ return null;
+ }
+ if (!isValid(c2)){
+ return c1;
+ }
+
+ int l1 = Math.abs(dimensionTop - transitionsBetween(topLeft, c1).getTransitions()) +
+ Math.abs(dimensionRight - transitionsBetween(bottomRight, c1).getTransitions());
+ int l2 = Math.abs(dimensionTop - transitionsBetween(topLeft, c2).getTransitions()) +
+ Math.abs(dimensionRight - transitionsBetween(bottomRight, c2).getTransitions());
+
+ if (l1 <= l2){
+ return c1;
+ }
+
+ return c2;
+ }
+
+ /**
+ * Calculates the position of the white top right module using the output of the rectangle detector
+ * for a square matrix
+ */
+ private ResultPoint correctTopRight(ResultPoint bottomLeft,
+ ResultPoint bottomRight,
+ ResultPoint topLeft,
+ ResultPoint topRight,
+ int dimension) {
+
+ float corr = distance(bottomLeft, bottomRight) / (float) dimension;
+ int norm = distance(topLeft, topRight);
+ float cos = (topRight.getX() - topLeft.getX()) / norm;
+ float sin = (topRight.getY() - topLeft.getY()) / norm;
+
+ ResultPoint c1 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin);
+
+ corr = distance(bottomLeft, topLeft) / (float) dimension;
+ norm = distance(bottomRight, topRight);
+ cos = (topRight.getX() - bottomRight.getX()) / norm;
+ sin = (topRight.getY() - bottomRight.getY()) / norm;
+
+ ResultPoint c2 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin);
+
+ if (!isValid(c1)) {
+ if (isValid(c2)) {
+ return c2;
+ }
+ return null;
+ }
+ if (!isValid(c2)) {
+ return c1;
+ }
+
+ int l1 = Math.abs(transitionsBetween(topLeft, c1).getTransitions() -
+ transitionsBetween(bottomRight, c1).getTransitions());
+ int l2 = Math.abs(transitionsBetween(topLeft, c2).getTransitions() -
+ transitionsBetween(bottomRight, c2).getTransitions());
+
+ return l1 <= l2 ? c1 : c2;
+ }
+
+ private boolean isValid(ResultPoint p) {
+ return p.getX() >= 0 && p.getX() < image.getWidth() && p.getY() > 0 && p.getY() < image.getHeight();
+ }
+
+ private static int distance(ResultPoint a, ResultPoint b) {
+ return MathUtils.round(ResultPoint.distance(a, b));
+ }
+
+ /**
+ * Increments the Integer associated with a key by one.
+ */
+ private static void increment(Map table, ResultPoint key) {
+ Integer value = table.get(key);
+ table.put(key, value == null ? 1 : value + 1);
+ }
+
+ private static BitMatrix sampleGrid(BitMatrix image,
+ ResultPoint topLeft,
+ ResultPoint bottomLeft,
+ ResultPoint bottomRight,
+ ResultPoint topRight,
+ int dimensionX,
+ int dimensionY) throws NotFoundException {
+
+ GridSampler sampler = GridSampler.getInstance();
+
+ return sampler.sampleGrid(image,
+ dimensionX,
+ dimensionY,
+ 0.5f,
+ 0.5f,
+ dimensionX - 0.5f,
+ 0.5f,
+ dimensionX - 0.5f,
+ dimensionY - 0.5f,
+ 0.5f,
+ dimensionY - 0.5f,
+ topLeft.getX(),
+ topLeft.getY(),
+ topRight.getX(),
+ topRight.getY(),
+ bottomRight.getX(),
+ bottomRight.getY(),
+ bottomLeft.getX(),
+ bottomLeft.getY());
+ }
+
+ /**
+ * Counts the number of black/white transitions between two points, using something like Bresenham's algorithm.
+ */
+ private ResultPointsAndTransitions transitionsBetween(ResultPoint from, ResultPoint to) {
+ // See QR Code Detector, sizeOfBlackWhiteBlackRun()
+ int fromX = (int) from.getX();
+ int fromY = (int) from.getY();
+ int toX = (int) to.getX();
+ int toY = (int) to.getY();
+ boolean steep = Math.abs(toY - fromY) > Math.abs(toX - fromX);
+ if (steep) {
+ int temp = fromX;
+ fromX = fromY;
+ fromY = temp;
+ temp = toX;
+ toX = toY;
+ toY = temp;
+ }
+
+ int dx = Math.abs(toX - fromX);
+ int dy = Math.abs(toY - fromY);
+ int error = -dx >> 1;
+ int ystep = fromY < toY ? 1 : -1;
+ int xstep = fromX < toX ? 1 : -1;
+ int transitions = 0;
+ boolean inBlack = image.get(steep ? fromY : fromX, steep ? fromX : fromY);
+ for (int x = fromX, y = fromY; x != toX; x += xstep) {
+ boolean isBlack = image.get(steep ? y : x, steep ? x : y);
+ if (isBlack != inBlack) {
+ transitions++;
+ inBlack = isBlack;
+ }
+ error += dy;
+ if (error > 0) {
+ if (y == toY) {
+ break;
+ }
+ y += ystep;
+ error -= dx;
+ }
+ }
+ return new ResultPointsAndTransitions(from, to, transitions);
+ }
+
+ /**
+ * Simply encapsulates two points and a number of transitions between them.
+ */
+ private static final class ResultPointsAndTransitions {
+
+ private final ResultPoint from;
+ private final ResultPoint to;
+ private final int transitions;
+
+ private ResultPointsAndTransitions(ResultPoint from, ResultPoint to, int transitions) {
+ this.from = from;
+ this.to = to;
+ this.transitions = transitions;
+ }
+
+ ResultPoint getFrom() {
+ return from;
+ }
+
+ ResultPoint getTo() {
+ return to;
+ }
+
+ public int getTransitions() {
+ return transitions;
+ }
+
+ @Override
+ public String toString() {
+ return from + "/" + to + '/' + transitions;
+ }
+ }
+
+ /**
+ * Orders ResultPointsAndTransitions by number of transitions, ascending.
+ */
+ private static final class ResultPointsAndTransitionsComparator
+ implements Comparator, Serializable {
+ @Override
+ public int compare(ResultPointsAndTransitions o1, ResultPointsAndTransitions o2) {
+ return o1.getTransitions() - o2.getTransitions();
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/ASCIIEncoder.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/ASCIIEncoder.java
new file mode 100644
index 000000000..b9cb72bd2
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/ASCIIEncoder.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+final class ASCIIEncoder implements Encoder {
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.ASCII_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ //step B
+ int n = HighLevelEncoder.determineConsecutiveDigitCount(context.getMessage(), context.pos);
+ if (n >= 2) {
+ context.writeCodeword(encodeASCIIDigits(context.getMessage().charAt(context.pos),
+ context.getMessage().charAt(context.pos + 1)));
+ context.pos += 2;
+ } else {
+ char c = context.getCurrentChar();
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ switch (newMode) {
+ case HighLevelEncoder.BASE256_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_BASE256);
+ context.signalEncoderChange(HighLevelEncoder.BASE256_ENCODATION);
+ return;
+ case HighLevelEncoder.C40_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_C40);
+ context.signalEncoderChange(HighLevelEncoder.C40_ENCODATION);
+ return;
+ case HighLevelEncoder.X12_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_ANSIX12);
+ context.signalEncoderChange(HighLevelEncoder.X12_ENCODATION);
+ break;
+ case HighLevelEncoder.TEXT_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_TEXT);
+ context.signalEncoderChange(HighLevelEncoder.TEXT_ENCODATION);
+ break;
+ case HighLevelEncoder.EDIFACT_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_EDIFACT);
+ context.signalEncoderChange(HighLevelEncoder.EDIFACT_ENCODATION);
+ break;
+ default:
+ throw new IllegalStateException("Illegal mode: " + newMode);
+ }
+ } else if (HighLevelEncoder.isExtendedASCII(c)) {
+ context.writeCodeword(HighLevelEncoder.UPPER_SHIFT);
+ context.writeCodeword((char) (c - 128 + 1));
+ context.pos++;
+ } else {
+ context.writeCodeword((char) (c + 1));
+ context.pos++;
+ }
+
+ }
+ }
+
+ private static char encodeASCIIDigits(char digit1, char digit2) {
+ if (HighLevelEncoder.isDigit(digit1) && HighLevelEncoder.isDigit(digit2)) {
+ int num = (digit1 - 48) * 10 + (digit2 - 48);
+ return (char) (num + 130);
+ }
+ throw new IllegalArgumentException("not digits: " + digit1 + digit2);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/Base256Encoder.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/Base256Encoder.java
new file mode 100644
index 000000000..b66e48551
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/Base256Encoder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+final class Base256Encoder implements Encoder {
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.BASE256_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append('\0'); //Initialize length field
+ while (context.hasMoreCharacters()) {
+ char c = context.getCurrentChar();
+ buffer.append(c);
+
+ context.pos++;
+
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ context.signalEncoderChange(newMode);
+ break;
+ }
+ }
+ int dataCount = buffer.length() - 1;
+ int lengthFieldSize = 1;
+ int currentSize = context.getCodewordCount() + dataCount + lengthFieldSize;
+ context.updateSymbolInfo(currentSize);
+ boolean mustPad = (context.getSymbolInfo().getDataCapacity() - currentSize) > 0;
+ if (context.hasMoreCharacters() || mustPad) {
+ if (dataCount <= 249) {
+ buffer.setCharAt(0, (char) dataCount);
+ } else if (dataCount > 249 && dataCount <= 1555) {
+ buffer.setCharAt(0, (char) ((dataCount / 250) + 249));
+ buffer.insert(1, (char) (dataCount % 250));
+ } else {
+ throw new IllegalStateException(
+ "Message length not in valid ranges: " + dataCount);
+ }
+ }
+ for (int i = 0, c = buffer.length(); i < c; i++) {
+ context.writeCodeword(randomize255State(
+ buffer.charAt(i), context.getCodewordCount() + 1));
+ }
+ }
+
+ private static char randomize255State(char ch, int codewordPosition) {
+ int pseudoRandom = ((149 * codewordPosition) % 255) + 1;
+ int tempVariable = ch + pseudoRandom;
+ if (tempVariable <= 255) {
+ return (char) tempVariable;
+ } else {
+ return (char) (tempVariable - 256);
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/C40Encoder.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/C40Encoder.java
new file mode 100644
index 000000000..acd02f6c8
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/C40Encoder.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+class C40Encoder implements Encoder {
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.C40_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ //step C
+ StringBuilder buffer = new StringBuilder();
+ while (context.hasMoreCharacters()) {
+ char c = context.getCurrentChar();
+ context.pos++;
+
+ int lastCharSize = encodeChar(c, buffer);
+
+ int unwritten = (buffer.length() / 3) * 2;
+
+ int curCodewordCount = context.getCodewordCount() + unwritten;
+ context.updateSymbolInfo(curCodewordCount);
+ int available = context.getSymbolInfo().getDataCapacity() - curCodewordCount;
+
+ if (!context.hasMoreCharacters()) {
+ //Avoid having a single C40 value in the last triplet
+ StringBuilder removed = new StringBuilder();
+ if ((buffer.length() % 3) == 2) {
+ if (available < 2 || available > 2) {
+ lastCharSize = backtrackOneCharacter(context, buffer, removed,
+ lastCharSize);
+ }
+ }
+ while ((buffer.length() % 3) == 1
+ && ((lastCharSize <= 3 && available != 1) || lastCharSize > 3)) {
+ lastCharSize = backtrackOneCharacter(context, buffer, removed, lastCharSize);
+ }
+ break;
+ }
+
+ int count = buffer.length();
+ if ((count % 3) == 0) {
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ context.signalEncoderChange(newMode);
+ break;
+ }
+ }
+ }
+ handleEOD(context, buffer);
+ }
+
+ private int backtrackOneCharacter(EncoderContext context,
+ StringBuilder buffer, StringBuilder removed, int lastCharSize) {
+ int count = buffer.length();
+ buffer.delete(count - lastCharSize, count);
+ context.pos--;
+ char c = context.getCurrentChar();
+ lastCharSize = encodeChar(c, removed);
+ context.resetSymbolInfo(); //Deal with possible reduction in symbol size
+ return lastCharSize;
+ }
+
+ static void writeNextTriplet(EncoderContext context, StringBuilder buffer) {
+ context.writeCodewords(encodeToCodewords(buffer, 0));
+ buffer.delete(0, 3);
+ }
+
+ /**
+ * Handle "end of data" situations
+ *
+ * @param context the encoder context
+ * @param buffer the buffer with the remaining encoded characters
+ */
+ void handleEOD(EncoderContext context, StringBuilder buffer) {
+ int unwritten = (buffer.length() / 3) * 2;
+ int rest = buffer.length() % 3;
+
+ int curCodewordCount = context.getCodewordCount() + unwritten;
+ context.updateSymbolInfo(curCodewordCount);
+ int available = context.getSymbolInfo().getDataCapacity() - curCodewordCount;
+
+ if (rest == 2) {
+ buffer.append('\0'); //Shift 1
+ while (buffer.length() >= 3) {
+ writeNextTriplet(context, buffer);
+ }
+ if (context.hasMoreCharacters()) {
+ context.writeCodeword(HighLevelEncoder.C40_UNLATCH);
+ }
+ } else if (available == 1 && rest == 1) {
+ while (buffer.length() >= 3) {
+ writeNextTriplet(context, buffer);
+ }
+ if (context.hasMoreCharacters()) {
+ context.writeCodeword(HighLevelEncoder.C40_UNLATCH);
+ }
+ // else no unlatch
+ context.pos--;
+ } else if (rest == 0) {
+ while (buffer.length() >= 3) {
+ writeNextTriplet(context, buffer);
+ }
+ if (available > 0 || context.hasMoreCharacters()) {
+ context.writeCodeword(HighLevelEncoder.C40_UNLATCH);
+ }
+ } else {
+ throw new IllegalStateException("Unexpected case. Please report!");
+ }
+ context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION);
+ }
+
+ int encodeChar(char c, StringBuilder sb) {
+ if (c == ' ') {
+ sb.append('\3');
+ return 1;
+ } else if (c >= '0' && c <= '9') {
+ sb.append((char) (c - 48 + 4));
+ return 1;
+ } else if (c >= 'A' && c <= 'Z') {
+ sb.append((char) (c - 65 + 14));
+ return 1;
+ } else if (c >= '\0' && c <= '\u001f') {
+ sb.append('\0'); //Shift 1 Set
+ sb.append(c);
+ return 2;
+ } else if (c >= '!' && c <= '/') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 33));
+ return 2;
+ } else if (c >= ':' && c <= '@') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 58 + 15));
+ return 2;
+ } else if (c >= '[' && c <= '_') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 91 + 22));
+ return 2;
+ } else if (c >= '\u0060' && c <= '\u007f') {
+ sb.append('\2'); //Shift 3 Set
+ sb.append((char) (c - 96));
+ return 2;
+ } else if (c >= '\u0080') {
+ sb.append("\1\u001e"); //Shift 2, Upper Shift
+ int len = 2;
+ len += encodeChar((char) (c - 128), sb);
+ return len;
+ } else {
+ throw new IllegalArgumentException("Illegal character: " + c);
+ }
+ }
+
+ private static String encodeToCodewords(CharSequence sb, int startPos) {
+ char c1 = sb.charAt(startPos);
+ char c2 = sb.charAt(startPos + 1);
+ char c3 = sb.charAt(startPos + 2);
+ int v = (1600 * c1) + (40 * c2) + c3 + 1;
+ char cw1 = (char) (v / 256);
+ char cw2 = (char) (v % 256);
+ return new String(new char[] {cw1, cw2});
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/DataMatrixSymbolInfo144.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/DataMatrixSymbolInfo144.java
new file mode 100644
index 000000000..31463539a
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/DataMatrixSymbolInfo144.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2006 Jeremias Maerki
+ *
+ * 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.datamatrix.encoder;
+
+final class DataMatrixSymbolInfo144 extends SymbolInfo {
+
+ DataMatrixSymbolInfo144() {
+ super(false, 1558, 620, 22, 22, 36, -1, 62);
+ }
+
+ @Override
+ public int getInterleavedBlockCount() {
+ return 10;
+ }
+
+ @Override
+ public int getDataLengthForInterleavedBlock(int index) {
+ return (index <= 8) ? 156 : 155;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/DefaultPlacement.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/DefaultPlacement.java
new file mode 100644
index 000000000..5efb5a580
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/DefaultPlacement.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2006 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+import java.util.Arrays;
+
+/**
+ * Symbol Character Placement Program. Adapted from Annex M.1 in ISO/IEC 16022:2000(E).
+ */
+public class DefaultPlacement {
+
+ private final CharSequence codewords;
+ private final int numrows;
+ private final int numcols;
+ private final byte[] bits;
+
+ /**
+ * Main constructor
+ *
+ * @param codewords the codewords to place
+ * @param numcols the number of columns
+ * @param numrows the number of rows
+ */
+ public DefaultPlacement(CharSequence codewords, int numcols, int numrows) {
+ this.codewords = codewords;
+ this.numcols = numcols;
+ this.numrows = numrows;
+ this.bits = new byte[numcols * numrows];
+ Arrays.fill(this.bits, (byte) -1); //Initialize with "not set" value
+ }
+
+ final int getNumrows() {
+ return numrows;
+ }
+
+ final int getNumcols() {
+ return numcols;
+ }
+
+ final byte[] getBits() {
+ return bits;
+ }
+
+ public final boolean getBit(int col, int row) {
+ return bits[row * numcols + col] == 1;
+ }
+
+ final void setBit(int col, int row, boolean bit) {
+ bits[row * numcols + col] = bit ? (byte) 1 : (byte) 0;
+ }
+
+ final boolean hasBit(int col, int row) {
+ return bits[row * numcols + col] >= 0;
+ }
+
+ public final void place() {
+ int pos = 0;
+ int row = 4;
+ int col = 0;
+
+ do {
+ /* repeatedly first check for one of the special corner cases, then... */
+ if ((row == numrows) && (col == 0)) {
+ corner1(pos++);
+ }
+ if ((row == numrows - 2) && (col == 0) && ((numcols % 4) != 0)) {
+ corner2(pos++);
+ }
+ if ((row == numrows - 2) && (col == 0) && (numcols % 8 == 4)) {
+ corner3(pos++);
+ }
+ if ((row == numrows + 4) && (col == 2) && ((numcols % 8) == 0)) {
+ corner4(pos++);
+ }
+ /* sweep upward diagonally, inserting successive characters... */
+ do {
+ if ((row < numrows) && (col >= 0) && !hasBit(col, row)) {
+ utah(row, col, pos++);
+ }
+ row -= 2;
+ col += 2;
+ } while (row >= 0 && (col < numcols));
+ row++;
+ col += 3;
+
+ /* and then sweep downward diagonally, inserting successive characters, ... */
+ do {
+ if ((row >= 0) && (col < numcols) && !hasBit(col, row)) {
+ utah(row, col, pos++);
+ }
+ row += 2;
+ col -= 2;
+ } while ((row < numrows) && (col >= 0));
+ row += 3;
+ col++;
+
+ /* ...until the entire array is scanned */
+ } while ((row < numrows) || (col < numcols));
+
+ /* Lastly, if the lower righthand corner is untouched, fill in fixed pattern */
+ if (!hasBit(numcols - 1, numrows - 1)) {
+ setBit(numcols - 1, numrows - 1, true);
+ setBit(numcols - 2, numrows - 2, true);
+ }
+ }
+
+ private void module(int row, int col, int pos, int bit) {
+ if (row < 0) {
+ row += numrows;
+ col += 4 - ((numrows + 4) % 8);
+ }
+ if (col < 0) {
+ col += numcols;
+ row += 4 - ((numcols + 4) % 8);
+ }
+ // Note the conversion:
+ int v = codewords.charAt(pos);
+ v &= 1 << (8 - bit);
+ setBit(col, row, v != 0);
+ }
+
+ /**
+ * Places the 8 bits of a utah-shaped symbol character in ECC200.
+ *
+ * @param row the row
+ * @param col the column
+ * @param pos character position
+ */
+ private void utah(int row, int col, int pos) {
+ module(row - 2, col - 2, pos, 1);
+ module(row - 2, col - 1, pos, 2);
+ module(row - 1, col - 2, pos, 3);
+ module(row - 1, col - 1, pos, 4);
+ module(row - 1, col, pos, 5);
+ module(row, col - 2, pos, 6);
+ module(row, col - 1, pos, 7);
+ module(row, col, pos, 8);
+ }
+
+ private void corner1(int pos) {
+ module(numrows - 1, 0, pos, 1);
+ module(numrows - 1, 1, pos, 2);
+ module(numrows - 1, 2, pos, 3);
+ module(0, numcols - 2, pos, 4);
+ module(0, numcols - 1, pos, 5);
+ module(1, numcols - 1, pos, 6);
+ module(2, numcols - 1, pos, 7);
+ module(3, numcols - 1, pos, 8);
+ }
+
+ private void corner2(int pos) {
+ module(numrows - 3, 0, pos, 1);
+ module(numrows - 2, 0, pos, 2);
+ module(numrows - 1, 0, pos, 3);
+ module(0, numcols - 4, pos, 4);
+ module(0, numcols - 3, pos, 5);
+ module(0, numcols - 2, pos, 6);
+ module(0, numcols - 1, pos, 7);
+ module(1, numcols - 1, pos, 8);
+ }
+
+ private void corner3(int pos) {
+ module(numrows - 3, 0, pos, 1);
+ module(numrows - 2, 0, pos, 2);
+ module(numrows - 1, 0, pos, 3);
+ module(0, numcols - 2, pos, 4);
+ module(0, numcols - 1, pos, 5);
+ module(1, numcols - 1, pos, 6);
+ module(2, numcols - 1, pos, 7);
+ module(3, numcols - 1, pos, 8);
+ }
+
+ private void corner4(int pos) {
+ module(numrows - 1, 0, pos, 1);
+ module(numrows - 1, numcols - 1, pos, 2);
+ module(0, numcols - 3, pos, 3);
+ module(0, numcols - 2, pos, 4);
+ module(0, numcols - 1, pos, 5);
+ module(1, numcols - 3, pos, 6);
+ module(1, numcols - 2, pos, 7);
+ module(1, numcols - 1, pos, 8);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/EdifactEncoder.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/EdifactEncoder.java
new file mode 100644
index 000000000..dbb3f130e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/EdifactEncoder.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+final class EdifactEncoder implements Encoder {
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.EDIFACT_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ //step F
+ StringBuilder buffer = new StringBuilder();
+ while (context.hasMoreCharacters()) {
+ char c = context.getCurrentChar();
+ encodeChar(c, buffer);
+ context.pos++;
+
+ int count = buffer.length();
+ if (count >= 4) {
+ context.writeCodewords(encodeToCodewords(buffer, 0));
+ buffer.delete(0, 4);
+
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION);
+ break;
+ }
+ }
+ }
+ buffer.append((char) 31); //Unlatch
+ handleEOD(context, buffer);
+ }
+
+ /**
+ * Handle "end of data" situations
+ *
+ * @param context the encoder context
+ * @param buffer the buffer with the remaining encoded characters
+ */
+ private static void handleEOD(EncoderContext context, CharSequence buffer) {
+ try {
+ int count = buffer.length();
+ if (count == 0) {
+ return; //Already finished
+ }
+ if (count == 1) {
+ //Only an unlatch at the end
+ context.updateSymbolInfo();
+ int available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount();
+ int remaining = context.getRemainingCharacters();
+ if (remaining == 0 && available <= 2) {
+ return; //No unlatch
+ }
+ }
+
+ if (count > 4) {
+ throw new IllegalStateException("Count must not exceed 4");
+ }
+ int restChars = count - 1;
+ String encoded = encodeToCodewords(buffer, 0);
+ boolean endOfSymbolReached = !context.hasMoreCharacters();
+ boolean restInAscii = endOfSymbolReached && restChars <= 2;
+
+ if (restChars <= 2) {
+ context.updateSymbolInfo(context.getCodewordCount() + restChars);
+ int available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount();
+ if (available >= 3) {
+ restInAscii = false;
+ context.updateSymbolInfo(context.getCodewordCount() + encoded.length());
+ //available = context.symbolInfo.dataCapacity - context.getCodewordCount();
+ }
+ }
+
+ if (restInAscii) {
+ context.resetSymbolInfo();
+ context.pos -= restChars;
+ } else {
+ context.writeCodewords(encoded);
+ }
+ } finally {
+ context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION);
+ }
+ }
+
+ private static void encodeChar(char c, StringBuilder sb) {
+ if (c >= ' ' && c <= '?') {
+ sb.append(c);
+ } else if (c >= '@' && c <= '^') {
+ sb.append((char) (c - 64));
+ } else {
+ HighLevelEncoder.illegalCharacter(c);
+ }
+ }
+
+ private static String encodeToCodewords(CharSequence sb, int startPos) {
+ int len = sb.length() - startPos;
+ if (len == 0) {
+ throw new IllegalStateException("StringBuilder must not be empty");
+ }
+ char c1 = sb.charAt(startPos);
+ char c2 = len >= 2 ? sb.charAt(startPos + 1) : 0;
+ char c3 = len >= 3 ? sb.charAt(startPos + 2) : 0;
+ char c4 = len >= 4 ? sb.charAt(startPos + 3) : 0;
+
+ int v = (c1 << 18) + (c2 << 12) + (c3 << 6) + c4;
+ char cw1 = (char) ((v >> 16) & 255);
+ char cw2 = (char) ((v >> 8) & 255);
+ char cw3 = (char) (v & 255);
+ StringBuilder res = new StringBuilder(3);
+ res.append(cw1);
+ if (len >= 2) {
+ res.append(cw2);
+ }
+ if (len >= 3) {
+ res.append(cw3);
+ }
+ return res.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/Encoder.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/Encoder.java
new file mode 100644
index 000000000..7a91e1e41
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/Encoder.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+interface Encoder {
+
+ int getEncodingMode();
+
+ void encode(EncoderContext context);
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/EncoderContext.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/EncoderContext.java
new file mode 100644
index 000000000..ba92278a3
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/EncoderContext.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+import com.google.zxing.Dimension;
+
+import java.nio.charset.Charset;
+
+final class EncoderContext {
+
+ private final String msg;
+ private SymbolShapeHint shape;
+ private Dimension minSize;
+ private Dimension maxSize;
+ private final StringBuilder codewords;
+ int pos;
+ private int newEncoding;
+ private SymbolInfo symbolInfo;
+ private int skipAtEnd;
+
+ EncoderContext(String msg) {
+ //From this point on Strings are not Unicode anymore!
+ byte[] msgBinary = msg.getBytes(Charset.forName("ISO-8859-1"));
+ StringBuilder sb = new StringBuilder(msgBinary.length);
+ for (int i = 0, c = msgBinary.length; i < c; i++) {
+ char ch = (char) (msgBinary[i] & 0xff);
+ if (ch == '?' && msg.charAt(i) != '?') {
+ throw new IllegalArgumentException("Message contains characters outside ISO-8859-1 encoding.");
+ }
+ sb.append(ch);
+ }
+ this.msg = sb.toString(); //Not Unicode here!
+ shape = SymbolShapeHint.FORCE_NONE;
+ this.codewords = new StringBuilder(msg.length());
+ newEncoding = -1;
+ }
+
+ public void setSymbolShape(SymbolShapeHint shape) {
+ this.shape = shape;
+ }
+
+ public void setSizeConstraints(Dimension minSize, Dimension maxSize) {
+ this.minSize = minSize;
+ this.maxSize = maxSize;
+ }
+
+ public String getMessage() {
+ return this.msg;
+ }
+
+ public void setSkipAtEnd(int count) {
+ this.skipAtEnd = count;
+ }
+
+ public char getCurrentChar() {
+ return msg.charAt(pos);
+ }
+
+ public char getCurrent() {
+ return msg.charAt(pos);
+ }
+
+ public StringBuilder getCodewords() {
+ return codewords;
+ }
+
+ public void writeCodewords(String codewords) {
+ this.codewords.append(codewords);
+ }
+
+ public void writeCodeword(char codeword) {
+ this.codewords.append(codeword);
+ }
+
+ public int getCodewordCount() {
+ return this.codewords.length();
+ }
+
+ public int getNewEncoding() {
+ return newEncoding;
+ }
+
+ public void signalEncoderChange(int encoding) {
+ this.newEncoding = encoding;
+ }
+
+ public void resetEncoderSignal() {
+ this.newEncoding = -1;
+ }
+
+ public boolean hasMoreCharacters() {
+ return pos < getTotalMessageCharCount();
+ }
+
+ private int getTotalMessageCharCount() {
+ return msg.length() - skipAtEnd;
+ }
+
+ public int getRemainingCharacters() {
+ return getTotalMessageCharCount() - pos;
+ }
+
+ public SymbolInfo getSymbolInfo() {
+ return symbolInfo;
+ }
+
+ public void updateSymbolInfo() {
+ updateSymbolInfo(getCodewordCount());
+ }
+
+ public void updateSymbolInfo(int len) {
+ if (this.symbolInfo == null || len > this.symbolInfo.getDataCapacity()) {
+ this.symbolInfo = SymbolInfo.lookup(len, shape, minSize, maxSize, true);
+ }
+ }
+
+ public void resetSymbolInfo() {
+ this.symbolInfo = null;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/ErrorCorrection.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/ErrorCorrection.java
new file mode 100644
index 000000000..8d6d5ff40
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/ErrorCorrection.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2006 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+/**
+ * Error Correction Code for ECC200.
+ */
+public final class ErrorCorrection {
+
+ /**
+ * Lookup table which factors to use for which number of error correction codewords.
+ * See FACTORS.
+ */
+ private static final int[] FACTOR_SETS
+ = {5, 7, 10, 11, 12, 14, 18, 20, 24, 28, 36, 42, 48, 56, 62, 68};
+
+ /**
+ * Precomputed polynomial factors for ECC 200.
+ */
+ private static final int[][] FACTORS = {
+ {228, 48, 15, 111, 62},
+ {23, 68, 144, 134, 240, 92, 254},
+ {28, 24, 185, 166, 223, 248, 116, 255, 110, 61},
+ {175, 138, 205, 12, 194, 168, 39, 245, 60, 97, 120},
+ {41, 153, 158, 91, 61, 42, 142, 213, 97, 178, 100, 242},
+ {156, 97, 192, 252, 95, 9, 157, 119, 138, 45, 18, 186, 83, 185},
+ {83, 195, 100, 39, 188, 75, 66, 61, 241, 213, 109, 129, 94, 254, 225, 48, 90, 188},
+ {15, 195, 244, 9, 233, 71, 168, 2, 188, 160, 153, 145, 253, 79, 108, 82, 27, 174, 186, 172},
+ {52, 190, 88, 205, 109, 39, 176, 21, 155, 197, 251, 223, 155, 21, 5, 172,
+ 254, 124, 12, 181, 184, 96, 50, 193},
+ {211, 231, 43, 97, 71, 96, 103, 174, 37, 151, 170, 53, 75, 34, 249, 121,
+ 17, 138, 110, 213, 141, 136, 120, 151, 233, 168, 93, 255},
+ {245, 127, 242, 218, 130, 250, 162, 181, 102, 120, 84, 179, 220, 251, 80, 182,
+ 229, 18, 2, 4, 68, 33, 101, 137, 95, 119, 115, 44, 175, 184, 59, 25,
+ 225, 98, 81, 112},
+ {77, 193, 137, 31, 19, 38, 22, 153, 247, 105, 122, 2, 245, 133, 242, 8,
+ 175, 95, 100, 9, 167, 105, 214, 111, 57, 121, 21, 1, 253, 57, 54, 101,
+ 248, 202, 69, 50, 150, 177, 226, 5, 9, 5},
+ {245, 132, 172, 223, 96, 32, 117, 22, 238, 133, 238, 231, 205, 188, 237, 87,
+ 191, 106, 16, 147, 118, 23, 37, 90, 170, 205, 131, 88, 120, 100, 66, 138,
+ 186, 240, 82, 44, 176, 87, 187, 147, 160, 175, 69, 213, 92, 253, 225, 19},
+ {175, 9, 223, 238, 12, 17, 220, 208, 100, 29, 175, 170, 230, 192, 215, 235,
+ 150, 159, 36, 223, 38, 200, 132, 54, 228, 146, 218, 234, 117, 203, 29, 232,
+ 144, 238, 22, 150, 201, 117, 62, 207, 164, 13, 137, 245, 127, 67, 247, 28,
+ 155, 43, 203, 107, 233, 53, 143, 46},
+ {242, 93, 169, 50, 144, 210, 39, 118, 202, 188, 201, 189, 143, 108, 196, 37,
+ 185, 112, 134, 230, 245, 63, 197, 190, 250, 106, 185, 221, 175, 64, 114, 71,
+ 161, 44, 147, 6, 27, 218, 51, 63, 87, 10, 40, 130, 188, 17, 163, 31,
+ 176, 170, 4, 107, 232, 7, 94, 166, 224, 124, 86, 47, 11, 204},
+ {220, 228, 173, 89, 251, 149, 159, 56, 89, 33, 147, 244, 154, 36, 73, 127,
+ 213, 136, 248, 180, 234, 197, 158, 177, 68, 122, 93, 213, 15, 160, 227, 236,
+ 66, 139, 153, 185, 202, 167, 179, 25, 220, 232, 96, 210, 231, 136, 223, 239,
+ 181, 241, 59, 52, 172, 25, 49, 232, 211, 189, 64, 54, 108, 153, 132, 63,
+ 96, 103, 82, 186}};
+
+ private static final int MODULO_VALUE = 0x12D;
+
+ private static final int[] LOG;
+ private static final int[] ALOG;
+
+ static {
+ //Create log and antilog table
+ LOG = new int[256];
+ ALOG = new int[255];
+
+ int p = 1;
+ for (int i = 0; i < 255; i++) {
+ ALOG[i] = p;
+ LOG[p] = i;
+ p <<= 1;
+ if (p >= 256) {
+ p ^= MODULO_VALUE;
+ }
+ }
+ }
+
+ private ErrorCorrection() {
+ }
+
+ /**
+ * Creates the ECC200 error correction for an encoded message.
+ *
+ * @param codewords the codewords
+ * @param symbolInfo information about the symbol to be encoded
+ * @return the codewords with interleaved error correction.
+ */
+ public static String encodeECC200(String codewords, SymbolInfo symbolInfo) {
+ if (codewords.length() != symbolInfo.getDataCapacity()) {
+ throw new IllegalArgumentException(
+ "The number of codewords does not match the selected symbol");
+ }
+ StringBuilder sb = new StringBuilder(symbolInfo.getDataCapacity() + symbolInfo.getErrorCodewords());
+ sb.append(codewords);
+ int blockCount = symbolInfo.getInterleavedBlockCount();
+ if (blockCount == 1) {
+ String ecc = createECCBlock(codewords, symbolInfo.getErrorCodewords());
+ sb.append(ecc);
+ } else {
+ sb.setLength(sb.capacity());
+ int[] dataSizes = new int[blockCount];
+ int[] errorSizes = new int[blockCount];
+ int[] startPos = new int[blockCount];
+ for (int i = 0; i < blockCount; i++) {
+ dataSizes[i] = symbolInfo.getDataLengthForInterleavedBlock(i + 1);
+ errorSizes[i] = symbolInfo.getErrorLengthForInterleavedBlock(i + 1);
+ startPos[i] = 0;
+ if (i > 0) {
+ startPos[i] = startPos[i - 1] + dataSizes[i];
+ }
+ }
+ for (int block = 0; block < blockCount; block++) {
+ StringBuilder temp = new StringBuilder(dataSizes[block]);
+ for (int d = block; d < symbolInfo.getDataCapacity(); d += blockCount) {
+ temp.append(codewords.charAt(d));
+ }
+ String ecc = createECCBlock(temp.toString(), errorSizes[block]);
+ int pos = 0;
+ for (int e = block; e < errorSizes[block] * blockCount; e += blockCount) {
+ sb.setCharAt(symbolInfo.getDataCapacity() + e, ecc.charAt(pos++));
+ }
+ }
+ }
+ return sb.toString();
+
+ }
+
+ private static String createECCBlock(CharSequence codewords, int numECWords) {
+ return createECCBlock(codewords, 0, codewords.length(), numECWords);
+ }
+
+ private static String createECCBlock(CharSequence codewords, int start, int len, int numECWords) {
+ int table = -1;
+ for (int i = 0; i < FACTOR_SETS.length; i++) {
+ if (FACTOR_SETS[i] == numECWords) {
+ table = i;
+ break;
+ }
+ }
+ if (table < 0) {
+ throw new IllegalArgumentException(
+ "Illegal number of error correction codewords specified: " + numECWords);
+ }
+ int[] poly = FACTORS[table];
+ char[] ecc = new char[numECWords];
+ for (int i = 0; i < numECWords; i++) {
+ ecc[i] = 0;
+ }
+ for (int i = start; i < start + len; i++) {
+ int m = ecc[numECWords - 1] ^ codewords.charAt(i);
+ for (int k = numECWords - 1; k > 0; k--) {
+ if (m != 0 && poly[k] != 0) {
+ ecc[k] = (char) (ecc[k - 1] ^ ALOG[(LOG[m] + LOG[poly[k]]) % 255]);
+ } else {
+ ecc[k] = ecc[k - 1];
+ }
+ }
+ if (m != 0 && poly[0] != 0) {
+ ecc[0] = (char) ALOG[(LOG[m] + LOG[poly[0]]) % 255];
+ } else {
+ ecc[0] = 0;
+ }
+ }
+ char[] eccReversed = new char[numECWords];
+ for (int i = 0; i < numECWords; i++) {
+ eccReversed[i] = ecc[numECWords - i - 1];
+ }
+ return String.valueOf(eccReversed);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/HighLevelEncoder.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/HighLevelEncoder.java
new file mode 100644
index 000000000..caec10970
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/HighLevelEncoder.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+import com.google.zxing.Dimension;
+
+import java.util.Arrays;
+
+/**
+ * DataMatrix ECC 200 data encoder following the algorithm described in ISO/IEC 16022:200(E) in
+ * annex S.
+ */
+public final class HighLevelEncoder {
+
+ /**
+ * Padding character
+ */
+ private static final char PAD = 129;
+ /**
+ * mode latch to C40 encodation mode
+ */
+ static final char LATCH_TO_C40 = 230;
+ /**
+ * mode latch to Base 256 encodation mode
+ */
+ static final char LATCH_TO_BASE256 = 231;
+ /**
+ * FNC1 Codeword
+ */
+ //private static final char FNC1 = 232;
+ /**
+ * Structured Append Codeword
+ */
+ //private static final char STRUCTURED_APPEND = 233;
+ /**
+ * Reader Programming
+ */
+ //private static final char READER_PROGRAMMING = 234;
+ /**
+ * Upper Shift
+ */
+ static final char UPPER_SHIFT = 235;
+ /**
+ * 05 Macro
+ */
+ private static final char MACRO_05 = 236;
+ /**
+ * 06 Macro
+ */
+ private static final char MACRO_06 = 237;
+ /**
+ * mode latch to ANSI X.12 encodation mode
+ */
+ static final char LATCH_TO_ANSIX12 = 238;
+ /**
+ * mode latch to Text encodation mode
+ */
+ static final char LATCH_TO_TEXT = 239;
+ /**
+ * mode latch to EDIFACT encodation mode
+ */
+ static final char LATCH_TO_EDIFACT = 240;
+ /**
+ * ECI character (Extended Channel Interpretation)
+ */
+ //private static final char ECI = 241;
+
+ /**
+ * Unlatch from C40 encodation
+ */
+ static final char C40_UNLATCH = 254;
+ /**
+ * Unlatch from X12 encodation
+ */
+ static final char X12_UNLATCH = 254;
+
+ /**
+ * 05 Macro header
+ */
+ private static final String MACRO_05_HEADER = "[)>\u001E05\u001D";
+ /**
+ * 06 Macro header
+ */
+ private static final String MACRO_06_HEADER = "[)>\u001E06\u001D";
+ /**
+ * Macro trailer
+ */
+ private static final String MACRO_TRAILER = "\u001E\u0004";
+
+ static final int ASCII_ENCODATION = 0;
+ static final int C40_ENCODATION = 1;
+ static final int TEXT_ENCODATION = 2;
+ static final int X12_ENCODATION = 3;
+ static final int EDIFACT_ENCODATION = 4;
+ static final int BASE256_ENCODATION = 5;
+
+ private HighLevelEncoder() {
+ }
+
+ /*
+ * Converts the message to a byte array using the default encoding (cp437) as defined by the
+ * specification
+ *
+ * @param msg the message
+ * @return the byte array of the message
+ */
+
+ /*
+ public static byte[] getBytesForMessage(String msg) {
+ return msg.getBytes(Charset.forName("cp437")); //See 4.4.3 and annex B of ISO/IEC 15438:2001(E)
+ }
+ */
+
+ private static char randomize253State(char ch, int codewordPosition) {
+ int pseudoRandom = ((149 * codewordPosition) % 253) + 1;
+ int tempVariable = ch + pseudoRandom;
+ return tempVariable <= 254 ? (char) tempVariable : (char) (tempVariable - 254);
+ }
+
+ /**
+ * Performs message encoding of a DataMatrix message using the algorithm described in annex P
+ * of ISO/IEC 16022:2000(E).
+ *
+ * @param msg the message
+ * @return the encoded message (the char values range from 0 to 255)
+ */
+ public static String encodeHighLevel(String msg) {
+ return encodeHighLevel(msg, SymbolShapeHint.FORCE_NONE, null, null);
+ }
+
+ /**
+ * Performs message encoding of a DataMatrix message using the algorithm described in annex P
+ * of ISO/IEC 16022:2000(E).
+ *
+ * @param msg the message
+ * @param shape requested shape. May be {@code SymbolShapeHint.FORCE_NONE},
+ * {@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}.
+ * @param minSize the minimum symbol size constraint or null for no constraint
+ * @param maxSize the maximum symbol size constraint or null for no constraint
+ * @return the encoded message (the char values range from 0 to 255)
+ */
+ public static String encodeHighLevel(String msg,
+ SymbolShapeHint shape,
+ Dimension minSize,
+ Dimension maxSize) {
+ //the codewords 0..255 are encoded as Unicode characters
+ Encoder[] encoders = {
+ new ASCIIEncoder(), new C40Encoder(), new TextEncoder(),
+ new X12Encoder(), new EdifactEncoder(), new Base256Encoder()
+ };
+
+ EncoderContext context = new EncoderContext(msg);
+ context.setSymbolShape(shape);
+ context.setSizeConstraints(minSize, maxSize);
+
+ if (msg.startsWith(MACRO_05_HEADER) && msg.endsWith(MACRO_TRAILER)) {
+ context.writeCodeword(MACRO_05);
+ context.setSkipAtEnd(2);
+ context.pos += MACRO_05_HEADER.length();
+ } else if (msg.startsWith(MACRO_06_HEADER) && msg.endsWith(MACRO_TRAILER)) {
+ context.writeCodeword(MACRO_06);
+ context.setSkipAtEnd(2);
+ context.pos += MACRO_06_HEADER.length();
+ }
+
+ int encodingMode = ASCII_ENCODATION; //Default mode
+ while (context.hasMoreCharacters()) {
+ encoders[encodingMode].encode(context);
+ if (context.getNewEncoding() >= 0) {
+ encodingMode = context.getNewEncoding();
+ context.resetEncoderSignal();
+ }
+ }
+ int len = context.getCodewordCount();
+ context.updateSymbolInfo();
+ int capacity = context.getSymbolInfo().getDataCapacity();
+ if (len < capacity) {
+ if (encodingMode != ASCII_ENCODATION && encodingMode != BASE256_ENCODATION) {
+ context.writeCodeword('\u00fe'); //Unlatch (254)
+ }
+ }
+ //Padding
+ StringBuilder codewords = context.getCodewords();
+ if (codewords.length() < capacity) {
+ codewords.append(PAD);
+ }
+ while (codewords.length() < capacity) {
+ codewords.append(randomize253State(PAD, codewords.length() + 1));
+ }
+
+ return context.getCodewords().toString();
+ }
+
+ static int lookAheadTest(CharSequence msg, int startpos, int currentMode) {
+ if (startpos >= msg.length()) {
+ return currentMode;
+ }
+ float[] charCounts;
+ //step J
+ if (currentMode == ASCII_ENCODATION) {
+ charCounts = new float[]{0, 1, 1, 1, 1, 1.25f};
+ } else {
+ charCounts = new float[]{1, 2, 2, 2, 2, 2.25f};
+ charCounts[currentMode] = 0;
+ }
+
+ int charsProcessed = 0;
+ while (true) {
+ //step K
+ if ((startpos + charsProcessed) == msg.length()) {
+ int min = Integer.MAX_VALUE;
+ byte[] mins = new byte[6];
+ int[] intCharCounts = new int[6];
+ min = findMinimums(charCounts, intCharCounts, min, mins);
+ int minCount = getMinimumCount(mins);
+
+ if (intCharCounts[ASCII_ENCODATION] == min) {
+ return ASCII_ENCODATION;
+ }
+ if (minCount == 1 && mins[BASE256_ENCODATION] > 0) {
+ return BASE256_ENCODATION;
+ }
+ if (minCount == 1 && mins[EDIFACT_ENCODATION] > 0) {
+ return EDIFACT_ENCODATION;
+ }
+ if (minCount == 1 && mins[TEXT_ENCODATION] > 0) {
+ return TEXT_ENCODATION;
+ }
+ if (minCount == 1 && mins[X12_ENCODATION] > 0) {
+ return X12_ENCODATION;
+ }
+ return C40_ENCODATION;
+ }
+
+ char c = msg.charAt(startpos + charsProcessed);
+ charsProcessed++;
+
+ //step L
+ if (isDigit(c)) {
+ charCounts[ASCII_ENCODATION] += 0.5;
+ } else if (isExtendedASCII(c)) {
+ charCounts[ASCII_ENCODATION] = (int) Math.ceil(charCounts[ASCII_ENCODATION]);
+ charCounts[ASCII_ENCODATION] += 2;
+ } else {
+ charCounts[ASCII_ENCODATION] = (int) Math.ceil(charCounts[ASCII_ENCODATION]);
+ charCounts[ASCII_ENCODATION]++;
+ }
+
+ //step M
+ if (isNativeC40(c)) {
+ charCounts[C40_ENCODATION] += 2.0f / 3.0f;
+ } else if (isExtendedASCII(c)) {
+ charCounts[C40_ENCODATION] += 8.0f / 3.0f;
+ } else {
+ charCounts[C40_ENCODATION] += 4.0f / 3.0f;
+ }
+
+ //step N
+ if (isNativeText(c)) {
+ charCounts[TEXT_ENCODATION] += 2.0f / 3.0f;
+ } else if (isExtendedASCII(c)) {
+ charCounts[TEXT_ENCODATION] += 8.0f / 3.0f;
+ } else {
+ charCounts[TEXT_ENCODATION] += 4.0f / 3.0f;
+ }
+
+ //step O
+ if (isNativeX12(c)) {
+ charCounts[X12_ENCODATION] += 2.0f / 3.0f;
+ } else if (isExtendedASCII(c)) {
+ charCounts[X12_ENCODATION] += 13.0f / 3.0f;
+ } else {
+ charCounts[X12_ENCODATION] += 10.0f / 3.0f;
+ }
+
+ //step P
+ if (isNativeEDIFACT(c)) {
+ charCounts[EDIFACT_ENCODATION] += 3.0f / 4.0f;
+ } else if (isExtendedASCII(c)) {
+ charCounts[EDIFACT_ENCODATION] += 17.0f / 4.0f;
+ } else {
+ charCounts[EDIFACT_ENCODATION] += 13.0f / 4.0f;
+ }
+
+ // step Q
+ if (isSpecialB256(c)) {
+ charCounts[BASE256_ENCODATION] += 4;
+ } else {
+ charCounts[BASE256_ENCODATION]++;
+ }
+
+ //step R
+ if (charsProcessed >= 4) {
+ int[] intCharCounts = new int[6];
+ byte[] mins = new byte[6];
+ findMinimums(charCounts, intCharCounts, Integer.MAX_VALUE, mins);
+ int minCount = getMinimumCount(mins);
+
+ if (intCharCounts[ASCII_ENCODATION] < intCharCounts[BASE256_ENCODATION]
+ && intCharCounts[ASCII_ENCODATION] < intCharCounts[C40_ENCODATION]
+ && intCharCounts[ASCII_ENCODATION] < intCharCounts[TEXT_ENCODATION]
+ && intCharCounts[ASCII_ENCODATION] < intCharCounts[X12_ENCODATION]
+ && intCharCounts[ASCII_ENCODATION] < intCharCounts[EDIFACT_ENCODATION]) {
+ return ASCII_ENCODATION;
+ }
+ if (intCharCounts[BASE256_ENCODATION] < intCharCounts[ASCII_ENCODATION]
+ || (mins[C40_ENCODATION] + mins[TEXT_ENCODATION] + mins[X12_ENCODATION] + mins[EDIFACT_ENCODATION]) == 0) {
+ return BASE256_ENCODATION;
+ }
+ if (minCount == 1 && mins[EDIFACT_ENCODATION] > 0) {
+ return EDIFACT_ENCODATION;
+ }
+ if (minCount == 1 && mins[TEXT_ENCODATION] > 0) {
+ return TEXT_ENCODATION;
+ }
+ if (minCount == 1 && mins[X12_ENCODATION] > 0) {
+ return X12_ENCODATION;
+ }
+ if (intCharCounts[C40_ENCODATION] + 1 < intCharCounts[ASCII_ENCODATION]
+ && intCharCounts[C40_ENCODATION] + 1 < intCharCounts[BASE256_ENCODATION]
+ && intCharCounts[C40_ENCODATION] + 1 < intCharCounts[EDIFACT_ENCODATION]
+ && intCharCounts[C40_ENCODATION] + 1 < intCharCounts[TEXT_ENCODATION]) {
+ if (intCharCounts[C40_ENCODATION] < intCharCounts[X12_ENCODATION]) {
+ return C40_ENCODATION;
+ }
+ if (intCharCounts[C40_ENCODATION] == intCharCounts[X12_ENCODATION]) {
+ int p = startpos + charsProcessed + 1;
+ while (p < msg.length()) {
+ char tc = msg.charAt(p);
+ if (isX12TermSep(tc)) {
+ return X12_ENCODATION;
+ }
+ if (!isNativeX12(tc)) {
+ break;
+ }
+ p++;
+ }
+ return C40_ENCODATION;
+ }
+ }
+ }
+ }
+ }
+
+ private static int findMinimums(float[] charCounts, int[] intCharCounts, int min, byte[] mins) {
+ Arrays.fill(mins, (byte) 0);
+ for (int i = 0; i < 6; i++) {
+ intCharCounts[i] = (int) Math.ceil(charCounts[i]);
+ int current = intCharCounts[i];
+ if (min > current) {
+ min = current;
+ Arrays.fill(mins, (byte) 0);
+ }
+ if (min == current) {
+ mins[i]++;
+
+ }
+ }
+ return min;
+ }
+
+ private static int getMinimumCount(byte[] mins) {
+ int minCount = 0;
+ for (int i = 0; i < 6; i++) {
+ minCount += mins[i];
+ }
+ return minCount;
+ }
+
+ static boolean isDigit(char ch) {
+ return ch >= '0' && ch <= '9';
+ }
+
+ static boolean isExtendedASCII(char ch) {
+ return ch >= 128 && ch <= 255;
+ }
+
+ private static boolean isNativeC40(char ch) {
+ return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z');
+ }
+
+ private static boolean isNativeText(char ch) {
+ return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z');
+ }
+
+ private static boolean isNativeX12(char ch) {
+ return isX12TermSep(ch) || (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z');
+ }
+
+ private static boolean isX12TermSep(char ch) {
+ return (ch == '\r') //CR
+ || (ch == '*')
+ || (ch == '>');
+ }
+
+ private static boolean isNativeEDIFACT(char ch) {
+ return ch >= ' ' && ch <= '^';
+ }
+
+ private static boolean isSpecialB256(char ch) {
+ return false; //TODO NOT IMPLEMENTED YET!!!
+ }
+
+ /**
+ * Determines the number of consecutive characters that are encodable using numeric compaction.
+ *
+ * @param msg the message
+ * @param startpos the start position within the message
+ * @return the requested character count
+ */
+ public static int determineConsecutiveDigitCount(CharSequence msg, int startpos) {
+ int count = 0;
+ int len = msg.length();
+ int idx = startpos;
+ if (idx < len) {
+ char ch = msg.charAt(idx);
+ while (isDigit(ch) && idx < len) {
+ count++;
+ idx++;
+ if (idx < len) {
+ ch = msg.charAt(idx);
+ }
+ }
+ }
+ return count;
+ }
+
+ static void illegalCharacter(char c) {
+ String hex = Integer.toHexString(c);
+ hex = "0000".substring(0, 4 - hex.length()) + hex;
+ throw new IllegalArgumentException("Illegal character: " + c + " (0x" + hex + ')');
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/SymbolInfo.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/SymbolInfo.java
new file mode 100644
index 000000000..2cb956cfa
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/SymbolInfo.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright 2006 Jeremias Maerki
+ *
+ * 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.datamatrix.encoder;
+
+import com.google.zxing.Dimension;
+
+/**
+ * Symbol info table for DataMatrix.
+ *
+ * @version $Id$
+ */
+public class SymbolInfo {
+
+ static final SymbolInfo[] PROD_SYMBOLS = {
+ new SymbolInfo(false, 3, 5, 8, 8, 1),
+ new SymbolInfo(false, 5, 7, 10, 10, 1),
+ /*rect*/new SymbolInfo(true, 5, 7, 16, 6, 1),
+ new SymbolInfo(false, 8, 10, 12, 12, 1),
+ /*rect*/new SymbolInfo(true, 10, 11, 14, 6, 2),
+ new SymbolInfo(false, 12, 12, 14, 14, 1),
+ /*rect*/new SymbolInfo(true, 16, 14, 24, 10, 1),
+
+ new SymbolInfo(false, 18, 14, 16, 16, 1),
+ new SymbolInfo(false, 22, 18, 18, 18, 1),
+ /*rect*/new SymbolInfo(true, 22, 18, 16, 10, 2),
+ new SymbolInfo(false, 30, 20, 20, 20, 1),
+ /*rect*/new SymbolInfo(true, 32, 24, 16, 14, 2),
+ new SymbolInfo(false, 36, 24, 22, 22, 1),
+ new SymbolInfo(false, 44, 28, 24, 24, 1),
+ /*rect*/new SymbolInfo(true, 49, 28, 22, 14, 2),
+
+ new SymbolInfo(false, 62, 36, 14, 14, 4),
+ new SymbolInfo(false, 86, 42, 16, 16, 4),
+ new SymbolInfo(false, 114, 48, 18, 18, 4),
+ new SymbolInfo(false, 144, 56, 20, 20, 4),
+ new SymbolInfo(false, 174, 68, 22, 22, 4),
+
+ new SymbolInfo(false, 204, 84, 24, 24, 4, 102, 42),
+ new SymbolInfo(false, 280, 112, 14, 14, 16, 140, 56),
+ new SymbolInfo(false, 368, 144, 16, 16, 16, 92, 36),
+ new SymbolInfo(false, 456, 192, 18, 18, 16, 114, 48),
+ new SymbolInfo(false, 576, 224, 20, 20, 16, 144, 56),
+ new SymbolInfo(false, 696, 272, 22, 22, 16, 174, 68),
+ new SymbolInfo(false, 816, 336, 24, 24, 16, 136, 56),
+ new SymbolInfo(false, 1050, 408, 18, 18, 36, 175, 68),
+ new SymbolInfo(false, 1304, 496, 20, 20, 36, 163, 62),
+ new DataMatrixSymbolInfo144(),
+ };
+
+ private static SymbolInfo[] symbols = PROD_SYMBOLS;
+
+ /**
+ * Overrides the symbol info set used by this class. Used for testing purposes.
+ *
+ * @param override the symbol info set to use
+ */
+ public static void overrideSymbolSet(SymbolInfo[] override) {
+ symbols = override;
+ }
+
+ private final boolean rectangular;
+ private final int dataCapacity;
+ private final int errorCodewords;
+ public final int matrixWidth;
+ public final int matrixHeight;
+ private final int dataRegions;
+ private final int rsBlockData;
+ private final int rsBlockError;
+
+ public SymbolInfo(boolean rectangular, int dataCapacity, int errorCodewords,
+ int matrixWidth, int matrixHeight, int dataRegions) {
+ this(rectangular, dataCapacity, errorCodewords, matrixWidth, matrixHeight, dataRegions,
+ dataCapacity, errorCodewords);
+ }
+
+ SymbolInfo(boolean rectangular, int dataCapacity, int errorCodewords,
+ int matrixWidth, int matrixHeight, int dataRegions,
+ int rsBlockData, int rsBlockError) {
+ this.rectangular = rectangular;
+ this.dataCapacity = dataCapacity;
+ this.errorCodewords = errorCodewords;
+ this.matrixWidth = matrixWidth;
+ this.matrixHeight = matrixHeight;
+ this.dataRegions = dataRegions;
+ this.rsBlockData = rsBlockData;
+ this.rsBlockError = rsBlockError;
+ }
+
+ public static SymbolInfo lookup(int dataCodewords) {
+ return lookup(dataCodewords, SymbolShapeHint.FORCE_NONE, true);
+ }
+
+ public static SymbolInfo lookup(int dataCodewords, SymbolShapeHint shape) {
+ return lookup(dataCodewords, shape, true);
+ }
+
+ public static SymbolInfo lookup(int dataCodewords, boolean allowRectangular, boolean fail) {
+ SymbolShapeHint shape = allowRectangular
+ ? SymbolShapeHint.FORCE_NONE : SymbolShapeHint.FORCE_SQUARE;
+ return lookup(dataCodewords, shape, fail);
+ }
+
+ private static SymbolInfo lookup(int dataCodewords, SymbolShapeHint shape, boolean fail) {
+ return lookup(dataCodewords, shape, null, null, fail);
+ }
+
+ public static SymbolInfo lookup(int dataCodewords,
+ SymbolShapeHint shape,
+ Dimension minSize,
+ Dimension maxSize,
+ boolean fail) {
+ for (SymbolInfo symbol : symbols) {
+ if (shape == SymbolShapeHint.FORCE_SQUARE && symbol.rectangular) {
+ continue;
+ }
+ if (shape == SymbolShapeHint.FORCE_RECTANGLE && !symbol.rectangular) {
+ continue;
+ }
+ if (minSize != null
+ && (symbol.getSymbolWidth() < minSize.getWidth()
+ || symbol.getSymbolHeight() < minSize.getHeight())) {
+ continue;
+ }
+ if (maxSize != null
+ && (symbol.getSymbolWidth() > maxSize.getWidth()
+ || symbol.getSymbolHeight() > maxSize.getHeight())) {
+ continue;
+ }
+ if (dataCodewords <= symbol.dataCapacity) {
+ return symbol;
+ }
+ }
+ if (fail) {
+ throw new IllegalArgumentException(
+ "Can't find a symbol arrangement that matches the message. Data codewords: "
+ + dataCodewords);
+ }
+ return null;
+ }
+
+ final int getHorizontalDataRegions() {
+ switch (dataRegions) {
+ case 1:
+ return 1;
+ case 2:
+ return 2;
+ case 4:
+ return 2;
+ case 16:
+ return 4;
+ case 36:
+ return 6;
+ default:
+ throw new IllegalStateException("Cannot handle this number of data regions");
+ }
+ }
+
+ final int getVerticalDataRegions() {
+ switch (dataRegions) {
+ case 1:
+ return 1;
+ case 2:
+ return 1;
+ case 4:
+ return 2;
+ case 16:
+ return 4;
+ case 36:
+ return 6;
+ default:
+ throw new IllegalStateException("Cannot handle this number of data regions");
+ }
+ }
+
+ public final int getSymbolDataWidth() {
+ return getHorizontalDataRegions() * matrixWidth;
+ }
+
+ public final int getSymbolDataHeight() {
+ return getVerticalDataRegions() * matrixHeight;
+ }
+
+ public final int getSymbolWidth() {
+ return getSymbolDataWidth() + (getHorizontalDataRegions() * 2);
+ }
+
+ public final int getSymbolHeight() {
+ return getSymbolDataHeight() + (getVerticalDataRegions() * 2);
+ }
+
+ public int getCodewordCount() {
+ return dataCapacity + errorCodewords;
+ }
+
+ public int getInterleavedBlockCount() {
+ return dataCapacity / rsBlockData;
+ }
+
+ public final int getDataCapacity() {
+ return dataCapacity;
+ }
+
+ public final int getErrorCodewords() {
+ return errorCodewords;
+ }
+
+ public int getDataLengthForInterleavedBlock(int index) {
+ return rsBlockData;
+ }
+
+ public final int getErrorLengthForInterleavedBlock(int index) {
+ return rsBlockError;
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(rectangular ? "Rectangular Symbol:" : "Square Symbol:");
+ sb.append(" data region ").append(matrixWidth).append('x').append(matrixHeight);
+ sb.append(", symbol size ").append(getSymbolWidth()).append('x').append(getSymbolHeight());
+ sb.append(", symbol data size ").append(getSymbolDataWidth()).append('x').append(getSymbolDataHeight());
+ sb.append(", codewords ").append(dataCapacity).append('+').append(errorCodewords);
+ return sb.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/SymbolShapeHint.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/SymbolShapeHint.java
new file mode 100644
index 000000000..58739b8b9
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/SymbolShapeHint.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+/**
+ * Enumeration for DataMatrix symbol shape hint. It can be used to force square or rectangular
+ * symbols.
+ */
+public enum SymbolShapeHint {
+
+ FORCE_NONE,
+ FORCE_SQUARE,
+ FORCE_RECTANGLE,
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/TextEncoder.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/TextEncoder.java
new file mode 100644
index 000000000..19af016b7
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/TextEncoder.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+final class TextEncoder extends C40Encoder {
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.TEXT_ENCODATION;
+ }
+
+ @Override
+ int encodeChar(char c, StringBuilder sb) {
+ if (c == ' ') {
+ sb.append('\3');
+ return 1;
+ }
+ if (c >= '0' && c <= '9') {
+ sb.append((char) (c - 48 + 4));
+ return 1;
+ }
+ if (c >= 'a' && c <= 'z') {
+ sb.append((char) (c - 97 + 14));
+ return 1;
+ }
+ if (c >= '\0' && c <= '\u001f') {
+ sb.append('\0'); //Shift 1 Set
+ sb.append(c);
+ return 2;
+ }
+ if (c >= '!' && c <= '/') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 33));
+ return 2;
+ }
+ if (c >= ':' && c <= '@') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 58 + 15));
+ return 2;
+ }
+ if (c >= '[' && c <= '_') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 91 + 22));
+ return 2;
+ }
+ if (c == '\u0060') {
+ sb.append('\2'); //Shift 3 Set
+ sb.append((char) (c - 96));
+ return 2;
+ }
+ if (c >= 'A' && c <= 'Z') {
+ sb.append('\2'); //Shift 3 Set
+ sb.append((char) (c - 65 + 1));
+ return 2;
+ }
+ if (c >= '{' && c <= '\u007f') {
+ sb.append('\2'); //Shift 3 Set
+ sb.append((char) (c - 123 + 27));
+ return 2;
+ }
+ if (c >= '\u0080') {
+ sb.append("\1\u001e"); //Shift 2, Upper Shift
+ int len = 2;
+ len += encodeChar((char) (c - 128), sb);
+ return len;
+ }
+ HighLevelEncoder.illegalCharacter(c);
+ return -1;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/X12Encoder.java b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/X12Encoder.java
new file mode 100644
index 000000000..fca29f250
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/datamatrix/encoder/X12Encoder.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * 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.datamatrix.encoder;
+
+final class X12Encoder extends C40Encoder {
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.X12_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ //step C
+ StringBuilder buffer = new StringBuilder();
+ while (context.hasMoreCharacters()) {
+ char c = context.getCurrentChar();
+ context.pos++;
+
+ encodeChar(c, buffer);
+
+ int count = buffer.length();
+ if ((count % 3) == 0) {
+ writeNextTriplet(context, buffer);
+
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ context.signalEncoderChange(newMode);
+ break;
+ }
+ }
+ }
+ handleEOD(context, buffer);
+ }
+
+ @Override
+ int encodeChar(char c, StringBuilder sb) {
+ if (c == '\r') {
+ sb.append('\0');
+ } else if (c == '*') {
+ sb.append('\1');
+ } else if (c == '>') {
+ sb.append('\2');
+ } else if (c == ' ') {
+ sb.append('\3');
+ } else if (c >= '0' && c <= '9') {
+ sb.append((char) (c - 48 + 4));
+ } else if (c >= 'A' && c <= 'Z') {
+ sb.append((char) (c - 65 + 14));
+ } else {
+ HighLevelEncoder.illegalCharacter(c);
+ }
+ return 1;
+ }
+
+ @Override
+ void handleEOD(EncoderContext context, StringBuilder buffer) {
+ context.updateSymbolInfo();
+ int available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount();
+ int count = buffer.length();
+ if (count == 2) {
+ context.writeCodeword(HighLevelEncoder.X12_UNLATCH);
+ context.pos -= 2;
+ context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION);
+ } else if (count == 1) {
+ context.pos--;
+ if (available > 1) {
+ context.writeCodeword(HighLevelEncoder.X12_UNLATCH);
+ }
+ //NOP - No unlatch necessary
+ context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION);
+ }
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/maxicode/MaxiCodeReader.java b/extern/zxing-core/src/main/java/com/google/zxing/maxicode/MaxiCodeReader.java
new file mode 100644
index 000000000..6eb3b959e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/maxicode/MaxiCodeReader.java
@@ -0,0 +1,126 @@
+/*
+ * 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.maxicode;
+
+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.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.maxicode.decoder.Decoder;
+
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode a MaxiCode in an image.
+ */
+public final class MaxiCodeReader implements Reader {
+
+ private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
+ private static final int MATRIX_WIDTH = 30;
+ private static final int MATRIX_HEIGHT = 33;
+
+ private final Decoder decoder = new Decoder();
+
+ /*
+ Decoder getDecoder() {
+ return decoder;
+ }
+ */
+
+ /**
+ * Locates and decodes a MaxiCode in an image.
+ *
+ * @return a String representing the content encoded by the MaxiCode
+ * @throws NotFoundException if a MaxiCode cannot be found
+ * @throws FormatException if a MaxiCode cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+ DecoderResult decoderResult;
+ if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
+ BitMatrix bits = extractPureBits(image.getBlackMatrix());
+ decoderResult = decoder.decode(bits, hints);
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ ResultPoint[] points = NO_POINTS;
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.MAXICODE);
+
+ String ecLevel = decoderResult.getECLevel();
+ if (ecLevel != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ // do nothing
+ }
+
+ /**
+ * This method detects a code in a "pure" image -- that is, pure monochrome image
+ * which contains only an unrotated, unskewed, image of a code, with some white border
+ * around it. This is a specialized method that works exceptionally fast in this special
+ * case.
+ *
+ * @see com.google.zxing.datamatrix.DataMatrixReader#extractPureBits(BitMatrix)
+ * @see com.google.zxing.qrcode.QRCodeReader#extractPureBits(BitMatrix)
+ */
+ private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException {
+
+ int[] enclosingRectangle = image.getEnclosingRectangle();
+ if (enclosingRectangle == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int left = enclosingRectangle[0];
+ int top = enclosingRectangle[1];
+ int width = enclosingRectangle[2];
+ int height = enclosingRectangle[3];
+
+ // Now just read off the bits
+ BitMatrix bits = new BitMatrix(MATRIX_WIDTH, MATRIX_HEIGHT);
+ for (int y = 0; y < MATRIX_HEIGHT; y++) {
+ int iy = top + (y * height + height / 2) / MATRIX_HEIGHT;
+ for (int x = 0; x < MATRIX_WIDTH; x++) {
+ int ix = left + (x * width + width / 2 + (y & 0x01) * width / 2) / MATRIX_WIDTH;
+ if (image.get(ix, iy)) {
+ bits.set(x, y);
+ }
+ }
+ }
+ return bits;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/BitMatrixParser.java b/extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/BitMatrixParser.java
new file mode 100644
index 000000000..be18a7e2a
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/BitMatrixParser.java
@@ -0,0 +1,88 @@
+/*
+ * 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.maxicode.decoder;
+
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * @author mike32767
+ * @author Manuel Kasten
+ */
+final class BitMatrixParser {
+
+ private static final int[][] BITNR = {
+ {121,120,127,126,133,132,139,138,145,144,151,150,157,156,163,162,169,168,175,174,181,180,187,186,193,192,199,198, -2, -2},
+ {123,122,129,128,135,134,141,140,147,146,153,152,159,158,165,164,171,170,177,176,183,182,189,188,195,194,201,200,816, -3},
+ {125,124,131,130,137,136,143,142,149,148,155,154,161,160,167,166,173,172,179,178,185,184,191,190,197,196,203,202,818,817},
+ {283,282,277,276,271,270,265,264,259,258,253,252,247,246,241,240,235,234,229,228,223,222,217,216,211,210,205,204,819, -3},
+ {285,284,279,278,273,272,267,266,261,260,255,254,249,248,243,242,237,236,231,230,225,224,219,218,213,212,207,206,821,820},
+ {287,286,281,280,275,274,269,268,263,262,257,256,251,250,245,244,239,238,233,232,227,226,221,220,215,214,209,208,822, -3},
+ {289,288,295,294,301,300,307,306,313,312,319,318,325,324,331,330,337,336,343,342,349,348,355,354,361,360,367,366,824,823},
+ {291,290,297,296,303,302,309,308,315,314,321,320,327,326,333,332,339,338,345,344,351,350,357,356,363,362,369,368,825, -3},
+ {293,292,299,298,305,304,311,310,317,316,323,322,329,328,335,334,341,340,347,346,353,352,359,358,365,364,371,370,827,826},
+ {409,408,403,402,397,396,391,390, 79, 78, -2, -2, 13, 12, 37, 36, 2, -1, 44, 43,109,108,385,384,379,378,373,372,828, -3},
+ {411,410,405,404,399,398,393,392, 81, 80, 40, -2, 15, 14, 39, 38, 3, -1, -1, 45,111,110,387,386,381,380,375,374,830,829},
+ {413,412,407,406,401,400,395,394, 83, 82, 41, -3, -3, -3, -3, -3, 5, 4, 47, 46,113,112,389,388,383,382,377,376,831, -3},
+ {415,414,421,420,427,426,103,102, 55, 54, 16, -3, -3, -3, -3, -3, -3, -3, 20, 19, 85, 84,433,432,439,438,445,444,833,832},
+ {417,416,423,422,429,428,105,104, 57, 56, -3, -3, -3, -3, -3, -3, -3, -3, 22, 21, 87, 86,435,434,441,440,447,446,834, -3},
+ {419,418,425,424,431,430,107,106, 59, 58, -3, -3, -3, -3, -3, -3, -3, -3, -3, 23, 89, 88,437,436,443,442,449,448,836,835},
+ {481,480,475,474,469,468, 48, -2, 30, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, 0, 53, 52,463,462,457,456,451,450,837, -3},
+ {483,482,477,476,471,470, 49, -1, -2, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -1,465,464,459,458,453,452,839,838},
+ {485,484,479,478,473,472, 51, 50, 31, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, 1, -2, 42,467,466,461,460,455,454,840, -3},
+ {487,486,493,492,499,498, 97, 96, 61, 60, -3, -3, -3, -3, -3, -3, -3, -3, -3, 26, 91, 90,505,504,511,510,517,516,842,841},
+ {489,488,495,494,501,500, 99, 98, 63, 62, -3, -3, -3, -3, -3, -3, -3, -3, 28, 27, 93, 92,507,506,513,512,519,518,843, -3},
+ {491,490,497,496,503,502,101,100, 65, 64, 17, -3, -3, -3, -3, -3, -3, -3, 18, 29, 95, 94,509,508,515,514,521,520,845,844},
+ {559,558,553,552,547,546,541,540, 73, 72, 32, -3, -3, -3, -3, -3, -3, 10, 67, 66,115,114,535,534,529,528,523,522,846, -3},
+ {561,560,555,554,549,548,543,542, 75, 74, -2, -1, 7, 6, 35, 34, 11, -2, 69, 68,117,116,537,536,531,530,525,524,848,847},
+ {563,562,557,556,551,550,545,544, 77, 76, -2, 33, 9, 8, 25, 24, -1, -2, 71, 70,119,118,539,538,533,532,527,526,849, -3},
+ {565,564,571,570,577,576,583,582,589,588,595,594,601,600,607,606,613,612,619,618,625,624,631,630,637,636,643,642,851,850},
+ {567,566,573,572,579,578,585,584,591,590,597,596,603,602,609,608,615,614,621,620,627,626,633,632,639,638,645,644,852, -3},
+ {569,568,575,574,581,580,587,586,593,592,599,598,605,604,611,610,617,616,623,622,629,628,635,634,641,640,647,646,854,853},
+ {727,726,721,720,715,714,709,708,703,702,697,696,691,690,685,684,679,678,673,672,667,666,661,660,655,654,649,648,855, -3},
+ {729,728,723,722,717,716,711,710,705,704,699,698,693,692,687,686,681,680,675,674,669,668,663,662,657,656,651,650,857,856},
+ {731,730,725,724,719,718,713,712,707,706,701,700,695,694,689,688,683,682,677,676,671,670,665,664,659,658,653,652,858, -3},
+ {733,732,739,738,745,744,751,750,757,756,763,762,769,768,775,774,781,780,787,786,793,792,799,798,805,804,811,810,860,859},
+ {735,734,741,740,747,746,753,752,759,758,765,764,771,770,777,776,783,782,789,788,795,794,801,800,807,806,813,812,861, -3},
+ {737,736,743,742,749,748,755,754,761,760,767,766,773,772,779,778,785,784,791,790,797,796,803,802,809,808,815,814,863,862}
+ };
+
+ private final BitMatrix bitMatrix;
+
+ /**
+ * @param bitMatrix {@link BitMatrix} to parse
+ */
+ BitMatrixParser(BitMatrix bitMatrix) {
+ this.bitMatrix = bitMatrix;
+ }
+
+ byte[] readCodewords() {
+ byte[] result = new byte[144];
+ int height = bitMatrix.getHeight();
+ int width = bitMatrix.getWidth();
+ for (int y = 0; y < height; y++) {
+ int[] bitnrRow = BITNR[y];
+ for (int x = 0; x < width; x++) {
+ int bit = bitnrRow[x];
+ if (bit >= 0 && bitMatrix.get(x, y)) {
+ result[bit / 6] |= (byte) (1 << (5 - (bit % 6)));
+ }
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java b/extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java
new file mode 100644
index 000000000..f372b49e2
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java
@@ -0,0 +1,193 @@
+/*
+ * 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.maxicode.decoder;
+
+import com.google.zxing.common.DecoderResult;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+/**
+ * MaxiCodes can encode text or structured information as bits in one of several modes,
+ * with multiple character sets in one code. This class decodes the bits back into text.
+ *
+ * @author mike32767
+ * @author Manuel Kasten
+ */
+final class DecodedBitStreamParser {
+
+ private static final char SHIFTA = '\uFFF0';
+ private static final char SHIFTB = '\uFFF1';
+ private static final char SHIFTC = '\uFFF2';
+ private static final char SHIFTD = '\uFFF3';
+ private static final char SHIFTE = '\uFFF4';
+ private static final char TWOSHIFTA = '\uFFF5';
+ private static final char THREESHIFTA = '\uFFF6';
+ private static final char LATCHA = '\uFFF7';
+ private static final char LATCHB = '\uFFF8';
+ private static final char LOCK = '\uFFF9';
+ private static final char ECI = '\uFFFA';
+ private static final char NS = '\uFFFB';
+ private static final char PAD = '\uFFFC';
+ private static final char FS = '\u001C';
+ private static final char GS = '\u001D';
+ private static final char RS = '\u001E';
+ private static final NumberFormat NINE_DIGITS = new DecimalFormat("000000000");
+ private static final NumberFormat THREE_DIGITS = new DecimalFormat("000");
+
+ private static final String[] SETS = {
+ "\nABCDEFGHIJKLMNOPQRSTUVWXYZ"+ECI+FS+GS+RS+NS+' '+PAD+"\"#$%&'()*+,-./0123456789:"+SHIFTB+SHIFTC+SHIFTD+SHIFTE+LATCHB,
+ "`abcdefghijklmnopqrstuvwxyz"+ECI+FS+GS+RS+NS+'{'+PAD+"}~\u007F;<=>?[\\]^_ ,./:@!|"+PAD+TWOSHIFTA+THREESHIFTA+PAD+SHIFTA+SHIFTC+SHIFTD+SHIFTE+LATCHA,
+ "\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA"+ECI+FS+GS+RS+"\u00DB\u00DC\u00DD\u00DE\u00DF\u00AA\u00AC\u00B1\u00B2\u00B3\u00B5\u00B9\u00BA\u00BC\u00BD\u00BE\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089"+LATCHA+' '+LOCK+SHIFTD+SHIFTE+LATCHB,
+ "\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA"+ECI+FS+GS+RS+NS+"\u00FB\u00FC\u00FD\u00FE\u00FF\u00A1\u00A8\u00AB\u00AF\u00B0\u00B4\u00B7\u00B8\u00BB\u00BF\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094"+LATCHA+' '+SHIFTC+LOCK+SHIFTE+LATCHB,
+ "\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"+ECI+PAD+PAD+'\u001B'+NS+FS+GS+RS+"\u001F\u009F\u00A0\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A9\u00AD\u00AE\u00B6\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E"+LATCHA+' '+SHIFTC+SHIFTD+LOCK+LATCHB,
+ "\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\u0020\u0021\"\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F"
+ };
+
+ private DecodedBitStreamParser() {
+ }
+
+ static DecoderResult decode(byte[] bytes, int mode) {
+ StringBuilder result = new StringBuilder(144);
+ switch (mode) {
+ case 2:
+ case 3:
+ String postcode;
+ if (mode == 2) {
+ int pc = getPostCode2(bytes);
+ NumberFormat df = new DecimalFormat("0000000000".substring(0, getPostCode2Length(bytes)));
+ postcode = df.format(pc);
+ } else {
+ postcode = getPostCode3(bytes);
+ }
+ String country = THREE_DIGITS.format(getCountry(bytes));
+ String service = THREE_DIGITS.format(getServiceClass(bytes));
+ result.append(getMessage(bytes, 10, 84));
+ if (result.toString().startsWith("[)>"+RS+"01"+GS)) {
+ result.insert(9, postcode + GS + country + GS + service + GS);
+ } else {
+ result.insert(0, postcode + GS + country + GS + service + GS);
+ }
+ break;
+ case 4:
+ result.append(getMessage(bytes, 1, 93));
+ break;
+ case 5:
+ result.append(getMessage(bytes, 1, 77));
+ break;
+ }
+ return new DecoderResult(bytes, result.toString(), null, String.valueOf(mode));
+ }
+
+ private static int getBit(int bit, byte[] bytes) {
+ bit--;
+ return (bytes[bit / 6] & (1 << (5 - (bit % 6)))) == 0 ? 0 : 1;
+ }
+
+ private static int getInt(byte[] bytes, byte[] x) {
+ int val = 0;
+ for (int i = 0; i < x.length; i++) {
+ val += getBit(x[i], bytes) << (x.length - i - 1);
+ }
+ return val;
+ }
+
+ private static int getCountry(byte[] bytes) {
+ return getInt(bytes, new byte[] {53, 54, 43, 44, 45, 46, 47, 48, 37, 38});
+ }
+
+ private static int getServiceClass(byte[] bytes) {
+ return getInt(bytes, new byte[] {55, 56, 57, 58, 59, 60, 49, 50, 51, 52});
+ }
+
+ private static int getPostCode2Length(byte[] bytes) {
+ return getInt(bytes, new byte[] {39, 40, 41, 42, 31, 32});
+ }
+
+ private static int getPostCode2(byte[] bytes) {
+ return getInt(bytes, new byte[] {33, 34, 35, 36, 25, 26, 27, 28, 29, 30, 19,
+ 20, 21, 22, 23, 24, 13, 14, 15, 16, 17, 18, 7, 8, 9, 10, 11, 12, 1, 2});
+ }
+
+ private static String getPostCode3(byte[] bytes) {
+ return String.valueOf(
+ new char[] {
+ SETS[0].charAt(getInt(bytes, new byte[] {39, 40, 41, 42, 31, 32})),
+ SETS[0].charAt(getInt(bytes, new byte[] {33, 34, 35, 36, 25, 26})),
+ SETS[0].charAt(getInt(bytes, new byte[] {27, 28, 29, 30, 19, 20})),
+ SETS[0].charAt(getInt(bytes, new byte[] {21, 22, 23, 24, 13, 14})),
+ SETS[0].charAt(getInt(bytes, new byte[] {15, 16, 17, 18, 7, 8})),
+ SETS[0].charAt(getInt(bytes, new byte[] { 9, 10, 11, 12, 1, 2})),
+ }
+ );
+ }
+
+ private static String getMessage(byte[] bytes, int start, int len) {
+ StringBuilder sb = new StringBuilder();
+ int shift = -1;
+ int set = 0;
+ int lastset = 0;
+ for (int i = start; i < start + len; i++) {
+ char c = SETS[set].charAt(bytes[i]);
+ switch (c) {
+ case LATCHA:
+ set = 0;
+ shift = -1;
+ break;
+ case LATCHB:
+ set = 1;
+ shift = -1;
+ break;
+ case SHIFTA:
+ case SHIFTB:
+ case SHIFTC:
+ case SHIFTD:
+ case SHIFTE:
+ lastset = set;
+ set = c - SHIFTA;
+ shift = 1;
+ break;
+ case TWOSHIFTA:
+ lastset = set;
+ set = 0;
+ shift = 2;
+ break;
+ case THREESHIFTA:
+ lastset = set;
+ set = 0;
+ shift = 3;
+ break;
+ case NS:
+ int nsval = (bytes[++i] << 24) + (bytes[++i] << 18) + (bytes[++i] << 12) + (bytes[++i] << 6) + bytes[++i];
+ sb.append(NINE_DIGITS.format(nsval));
+ break;
+ case LOCK:
+ shift = -1;
+ break;
+ default:
+ sb.append(c);
+ }
+ if (shift-- == 0) {
+ set = lastset;
+ }
+ }
+ while (sb.length() > 0 && sb.charAt(sb.length() - 1) == PAD) {
+ sb.setLength(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/Decoder.java b/extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/Decoder.java
new file mode 100644
index 000000000..f7836f4e2
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/maxicode/decoder/Decoder.java
@@ -0,0 +1,114 @@
+/*
+ * 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.maxicode.decoder;
+
+import com.google.zxing.ChecksumException;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+import java.util.Map;
+
+/**
+ * The main class which implements MaxiCode decoding -- as opposed to locating and extracting
+ * the MaxiCode from an image.
+ *
+ * @author Manuel Kasten
+ */
+public final class Decoder {
+
+ private static final int ALL = 0;
+ private static final int EVEN = 1;
+ private static final int ODD = 2;
+
+ private final ReedSolomonDecoder rsDecoder;
+
+ public Decoder() {
+ rsDecoder = new ReedSolomonDecoder(GenericGF.MAXICODE_FIELD_64);
+ }
+
+ public DecoderResult decode(BitMatrix bits) throws ChecksumException, FormatException {
+ return decode(bits, null);
+ }
+
+ public DecoderResult decode(BitMatrix bits,
+ Map hints) throws FormatException, ChecksumException {
+ BitMatrixParser parser = new BitMatrixParser(bits);
+ byte[] codewords = parser.readCodewords();
+
+ correctErrors(codewords, 0, 10, 10, ALL);
+ int mode = codewords[0] & 0x0F;
+ byte[] datawords;
+ switch (mode) {
+ case 2:
+ case 3:
+ case 4:
+ correctErrors(codewords, 20, 84, 40, EVEN);
+ correctErrors(codewords, 20, 84, 40, ODD);
+ datawords = new byte[94];
+ break;
+ case 5:
+ correctErrors(codewords, 20, 68, 56, EVEN);
+ correctErrors(codewords, 20, 68, 56, ODD);
+ datawords = new byte[78];
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+
+ System.arraycopy(codewords, 0, datawords, 0, 10);
+ System.arraycopy(codewords, 20, datawords, 10, datawords.length - 10);
+
+ return DecodedBitStreamParser.decode(datawords, mode);
+ }
+
+ private void correctErrors(byte[] codewordBytes,
+ int start,
+ int dataCodewords,
+ int ecCodewords,
+ int mode) throws ChecksumException {
+ int codewords = dataCodewords + ecCodewords;
+
+ // in EVEN or ODD mode only half the codewords
+ int divisor = mode == ALL ? 1 : 2;
+
+ // First read into an array of ints
+ int[] codewordsInts = new int[codewords / divisor];
+ for (int i = 0; i < codewords; i++) {
+ if ((mode == ALL) || (i % 2 == (mode - 1))) {
+ codewordsInts[i / divisor] = codewordBytes[i + start] & 0xFF;
+ }
+ }
+ try {
+ rsDecoder.decode(codewordsInts, ecCodewords / divisor);
+ } catch (ReedSolomonException ignored) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ // Copy back into array of bytes -- only need to worry about the bytes that were data
+ // We don't care about errors in the error-correction codewords
+ for (int i = 0; i < dataCodewords; i++) {
+ if ((mode == ALL) || (i % 2 == (mode - 1))) {
+ codewordBytes[i + start] = (byte) codewordsInts[i / divisor];
+ }
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/multi/ByQuadrantReader.java b/extern/zxing-core/src/main/java/com/google/zxing/multi/ByQuadrantReader.java
new file mode 100644
index 000000000..74186dcc8
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/multi/ByQuadrantReader.java
@@ -0,0 +1,100 @@
+/*
+ * 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.multi;
+
+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.Result;
+
+import java.util.Map;
+
+/**
+ * This class attempts to decode a barcode from an image, not by scanning the whole image,
+ * but by scanning subsets of the image. This is important when there may be multiple barcodes in
+ * an image, and detecting a barcode may find parts of multiple barcode and fail to decode
+ * (e.g. QR Codes). Instead this scans the four quadrants of the image -- and also the center
+ * 'quadrant' to cover the case where a barcode is found in the center.
+ *
+ * @see GenericMultipleBarcodeReader
+ */
+public final class ByQuadrantReader implements Reader {
+
+ private final Reader delegate;
+
+ public ByQuadrantReader(Reader delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image)
+ throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int halfWidth = width / 2;
+ int halfHeight = height / 2;
+
+ BinaryBitmap topLeft = image.crop(0, 0, halfWidth, halfHeight);
+ try {
+ return delegate.decode(topLeft, hints);
+ } catch (NotFoundException re) {
+ // continue
+ }
+
+ BinaryBitmap topRight = image.crop(halfWidth, 0, halfWidth, halfHeight);
+ try {
+ return delegate.decode(topRight, hints);
+ } catch (NotFoundException re) {
+ // continue
+ }
+
+ BinaryBitmap bottomLeft = image.crop(0, halfHeight, halfWidth, halfHeight);
+ try {
+ return delegate.decode(bottomLeft, hints);
+ } catch (NotFoundException re) {
+ // continue
+ }
+
+ BinaryBitmap bottomRight = image.crop(halfWidth, halfHeight, halfWidth, halfHeight);
+ try {
+ return delegate.decode(bottomRight, hints);
+ } catch (NotFoundException re) {
+ // continue
+ }
+
+ int quarterWidth = halfWidth / 2;
+ int quarterHeight = halfHeight / 2;
+ BinaryBitmap center = image.crop(quarterWidth, quarterHeight, halfWidth, halfHeight);
+ return delegate.decode(center, hints);
+ }
+
+ @Override
+ public void reset() {
+ delegate.reset();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/multi/GenericMultipleBarcodeReader.java b/extern/zxing-core/src/main/java/com/google/zxing/multi/GenericMultipleBarcodeReader.java
new file mode 100644
index 000000000..276279bb7
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/multi/GenericMultipleBarcodeReader.java
@@ -0,0 +1,170 @@
+/*
+ * 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.multi;
+
+import com.google.zxing.BinaryBitmap;
+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.ResultPoint;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Attempts to locate multiple barcodes in an image by repeatedly decoding portion of the image.
+ * After one barcode is found, the areas left, above, right and below the barcode's
+ * {@link ResultPoint}s are scanned, recursively.
+ *
+ * A caller may want to also employ {@link ByQuadrantReader} when attempting to find multiple
+ * 2D barcodes, like QR Codes, in an image, where the presence of multiple barcodes might prevent
+ * detecting any one of them.
+ *
+ * That is, instead of passing a {@link Reader} a caller might pass
+ * {@code new ByQuadrantReader(reader)}.
+ *
+ * @author Sean Owen
+ */
+public final class GenericMultipleBarcodeReader implements MultipleBarcodeReader {
+
+ private static final int MIN_DIMENSION_TO_RECUR = 100;
+ private static final int MAX_DEPTH = 4;
+
+ private final Reader delegate;
+
+ public GenericMultipleBarcodeReader(Reader delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
+ return decodeMultiple(image, null);
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image, Map hints)
+ throws NotFoundException {
+ List results = new ArrayList<>();
+ doDecodeMultiple(image, hints, results, 0, 0, 0);
+ if (results.isEmpty()) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ return results.toArray(new Result[results.size()]);
+ }
+
+ private void doDecodeMultiple(BinaryBitmap image,
+ Map hints,
+ List results,
+ int xOffset,
+ int yOffset,
+ int currentDepth) {
+ if (currentDepth > MAX_DEPTH) {
+ return;
+ }
+
+ Result result;
+ try {
+ result = delegate.decode(image, hints);
+ } catch (ReaderException ignored) {
+ return;
+ }
+ boolean alreadyFound = false;
+ for (Result existingResult : results) {
+ if (existingResult.getText().equals(result.getText())) {
+ alreadyFound = true;
+ break;
+ }
+ }
+ if (!alreadyFound) {
+ results.add(translateResultPoints(result, xOffset, yOffset));
+ }
+ ResultPoint[] resultPoints = result.getResultPoints();
+ if (resultPoints == null || resultPoints.length == 0) {
+ return;
+ }
+ int width = image.getWidth();
+ int height = image.getHeight();
+ float minX = width;
+ float minY = height;
+ float maxX = 0.0f;
+ float maxY = 0.0f;
+ for (ResultPoint point : resultPoints) {
+ float x = point.getX();
+ float y = point.getY();
+ if (x < minX) {
+ minX = x;
+ }
+ if (y < minY) {
+ minY = y;
+ }
+ if (x > maxX) {
+ maxX = x;
+ }
+ if (y > maxY) {
+ maxY = y;
+ }
+ }
+
+ // Decode left of barcode
+ if (minX > MIN_DIMENSION_TO_RECUR) {
+ doDecodeMultiple(image.crop(0, 0, (int) minX, height),
+ hints, results,
+ xOffset, yOffset,
+ currentDepth + 1);
+ }
+ // Decode above barcode
+ if (minY > MIN_DIMENSION_TO_RECUR) {
+ doDecodeMultiple(image.crop(0, 0, width, (int) minY),
+ hints, results,
+ xOffset, yOffset,
+ currentDepth + 1);
+ }
+ // Decode right of barcode
+ if (maxX < width - MIN_DIMENSION_TO_RECUR) {
+ doDecodeMultiple(image.crop((int) maxX, 0, width - (int) maxX, height),
+ hints, results,
+ xOffset + (int) maxX, yOffset,
+ currentDepth + 1);
+ }
+ // Decode below barcode
+ if (maxY < height - MIN_DIMENSION_TO_RECUR) {
+ doDecodeMultiple(image.crop(0, (int) maxY, width, height - (int) maxY),
+ hints, results,
+ xOffset, yOffset + (int) maxY,
+ currentDepth + 1);
+ }
+ }
+
+ private static Result translateResultPoints(Result result, int xOffset, int yOffset) {
+ ResultPoint[] oldResultPoints = result.getResultPoints();
+ if (oldResultPoints == null) {
+ return result;
+ }
+ ResultPoint[] newResultPoints = new ResultPoint[oldResultPoints.length];
+ for (int i = 0; i < oldResultPoints.length; i++) {
+ ResultPoint oldPoint = oldResultPoints[i];
+ newResultPoints[i] = new ResultPoint(oldPoint.getX() + xOffset, oldPoint.getY() + yOffset);
+ }
+ Result newResult = new Result(result.getText(), result.getRawBytes(), newResultPoints, result.getBarcodeFormat());
+ newResult.putAllMetadata(result.getResultMetadata());
+ return newResult;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/multi/MultipleBarcodeReader.java b/extern/zxing-core/src/main/java/com/google/zxing/multi/MultipleBarcodeReader.java
new file mode 100644
index 000000000..a35872799
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/multi/MultipleBarcodeReader.java
@@ -0,0 +1,39 @@
+/*
+ * 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.multi;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Result;
+
+import java.util.Map;
+
+/**
+ * Implementation of this interface attempt to read several barcodes from one image.
+ *
+ * @see com.google.zxing.Reader
+ * @author Sean Owen
+ */
+public interface MultipleBarcodeReader {
+
+ Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException;
+
+ Result[] decodeMultiple(BinaryBitmap image,
+ Map hints) throws NotFoundException;
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/QRCodeMultiReader.java b/extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/QRCodeMultiReader.java
new file mode 100644
index 000000000..7f4a531bc
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/QRCodeMultiReader.java
@@ -0,0 +1,181 @@
+/*
+ * 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.multi.qrcode;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+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.common.DecoderResult;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.multi.MultipleBarcodeReader;
+import com.google.zxing.multi.qrcode.detector.MultiDetector;
+import com.google.zxing.qrcode.QRCodeReader;
+import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * This implementation can detect and decode multiple QR Codes in an image.
+ *
+ * @author Sean Owen
+ * @author Hannes Erven
+ */
+public final class QRCodeMultiReader extends QRCodeReader implements MultipleBarcodeReader {
+
+ private static final Result[] EMPTY_RESULT_ARRAY = new Result[0];
+ private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
+ return decodeMultiple(image, null);
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image, Map hints) throws NotFoundException {
+ List results = new ArrayList<>();
+ DetectorResult[] detectorResults = new MultiDetector(image.getBlackMatrix()).detectMulti(hints);
+ for (DetectorResult detectorResult : detectorResults) {
+ try {
+ DecoderResult decoderResult = getDecoder().decode(detectorResult.getBits(), hints);
+ ResultPoint[] points = detectorResult.getPoints();
+ // If the code was mirrored: swap the bottom-left and the top-right points.
+ if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
+ ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
+ }
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points,
+ BarcodeFormat.QR_CODE);
+ List byteSegments = decoderResult.getByteSegments();
+ if (byteSegments != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
+ }
+ String ecLevel = decoderResult.getECLevel();
+ if (ecLevel != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+ if (decoderResult.hasStructuredAppend()) {
+ result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
+ decoderResult.getStructuredAppendSequenceNumber());
+ result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
+ decoderResult.getStructuredAppendParity());
+ }
+ results.add(result);
+ } catch (ReaderException re) {
+ // ignore and continue
+ }
+ }
+ if (results.isEmpty()) {
+ return EMPTY_RESULT_ARRAY;
+ } else {
+ results = processStructuredAppend(results);
+ return results.toArray(new Result[results.size()]);
+ }
+ }
+
+ private static List processStructuredAppend(List results) {
+ boolean hasSA = false;
+
+ // first, check, if there is at least on SA result in the list
+ for (Result result : results) {
+ if (result.getResultMetadata().containsKey(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) {
+ hasSA = true;
+ break;
+ }
+ }
+ if (!hasSA) {
+ return results;
+ }
+
+ // it is, second, split the lists and built a new result list
+ List newResults = new ArrayList<>();
+ List saResults = new ArrayList<>();
+ for (Result result : results) {
+ newResults.add(result);
+ if (result.getResultMetadata().containsKey(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) {
+ saResults.add(result);
+ }
+ }
+ // sort and concatenate the SA list items
+ Collections.sort(saResults, new SAComparator());
+ StringBuilder concatedText = new StringBuilder();
+ int rawBytesLen = 0;
+ int byteSegmentLength = 0;
+ for (Result saResult : saResults) {
+ concatedText.append(saResult.getText());
+ rawBytesLen += saResult.getRawBytes().length;
+ if (saResult.getResultMetadata().containsKey(ResultMetadataType.BYTE_SEGMENTS)) {
+ @SuppressWarnings("unchecked")
+ Iterable byteSegments =
+ (Iterable) saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS);
+ for (byte[] segment : byteSegments) {
+ byteSegmentLength += segment.length;
+ }
+ }
+ }
+ byte[] newRawBytes = new byte[rawBytesLen];
+ byte[] newByteSegment = new byte[byteSegmentLength];
+ int newRawBytesIndex = 0;
+ int byteSegmentIndex = 0;
+ for (Result saResult : saResults) {
+ System.arraycopy(saResult.getRawBytes(), 0, newRawBytes, newRawBytesIndex, saResult.getRawBytes().length);
+ newRawBytesIndex += saResult.getRawBytes().length;
+ if (saResult.getResultMetadata().containsKey(ResultMetadataType.BYTE_SEGMENTS)) {
+ @SuppressWarnings("unchecked")
+ Iterable byteSegments =
+ (Iterable) saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS);
+ for (byte[] segment : byteSegments) {
+ System.arraycopy(segment, 0, newByteSegment, byteSegmentIndex, segment.length);
+ byteSegmentIndex += segment.length;
+ }
+ }
+ }
+ Result newResult = new Result(concatedText.toString(), newRawBytes, NO_POINTS, BarcodeFormat.QR_CODE);
+ if (byteSegmentLength > 0) {
+ Collection byteSegmentList = new ArrayList<>();
+ byteSegmentList.add(newByteSegment);
+ newResult.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegmentList);
+ }
+ newResults.add(newResult);
+ return newResults;
+ }
+
+ private static final class SAComparator implements Comparator, Serializable {
+ @Override
+ public int compare(Result a, Result b) {
+ int aNumber = (int) (a.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE));
+ int bNumber = (int) (b.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE));
+ if (aNumber < bNumber) {
+ return -1;
+ }
+ if (aNumber > bNumber) {
+ return 1;
+ }
+ return 0;
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/detector/MultiDetector.java b/extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/detector/MultiDetector.java
new file mode 100644
index 000000000..9b242104b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/detector/MultiDetector.java
@@ -0,0 +1,73 @@
+/*
+ * 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.multi.qrcode.detector;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ReaderException;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.qrcode.detector.Detector;
+import com.google.zxing.qrcode.detector.FinderPatternInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Encapsulates logic that can detect one or more QR Codes in an image, even if the QR Code
+ * is rotated or skewed, or partially obscured.
+ *
+ * @author Sean Owen
+ * @author Hannes Erven
+ */
+public final class MultiDetector extends Detector {
+
+ private static final DetectorResult[] EMPTY_DETECTOR_RESULTS = new DetectorResult[0];
+
+ public MultiDetector(BitMatrix image) {
+ super(image);
+ }
+
+ public DetectorResult[] detectMulti(Map hints) throws NotFoundException {
+ BitMatrix image = getImage();
+ ResultPointCallback resultPointCallback =
+ hints == null ? null : (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+ MultiFinderPatternFinder finder = new MultiFinderPatternFinder(image, resultPointCallback);
+ FinderPatternInfo[] infos = finder.findMulti(hints);
+
+ if (infos.length == 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ List result = new ArrayList<>();
+ for (FinderPatternInfo info : infos) {
+ try {
+ result.add(processFinderPatternInfo(info));
+ } catch (ReaderException e) {
+ // ignore
+ }
+ }
+ if (result.isEmpty()) {
+ return EMPTY_DETECTOR_RESULTS;
+ } else {
+ return result.toArray(new DetectorResult[result.size()]);
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java b/extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java
new file mode 100644
index 000000000..6ec4bc46a
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java
@@ -0,0 +1,311 @@
+/*
+ * 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.multi.qrcode.detector;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.detector.FinderPattern;
+import com.google.zxing.qrcode.detector.FinderPatternFinder;
+import com.google.zxing.qrcode.detector.FinderPatternInfo;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class attempts to find finder patterns in a QR Code. Finder patterns are the square
+ * markers at three corners of a QR Code.
+ *
+ * This class is thread-safe but not reentrant. Each thread must allocate its own object.
+ *
+ *
In contrast to {@link FinderPatternFinder}, this class will return an array of all possible
+ * QR code locations in the image.
+ *
+ * Use the TRY_HARDER hint to ask for a more thorough detection.
+ *
+ * @author Sean Owen
+ * @author Hannes Erven
+ */
+final class MultiFinderPatternFinder extends FinderPatternFinder {
+
+ private static final FinderPatternInfo[] EMPTY_RESULT_ARRAY = new FinderPatternInfo[0];
+
+ // TODO MIN_MODULE_COUNT and MAX_MODULE_COUNT would be great hints to ask the user for
+ // since it limits the number of regions to decode
+
+ // max. legal count of modules per QR code edge (177)
+ private static final float MAX_MODULE_COUNT_PER_EDGE = 180;
+ // min. legal count per modules per QR code edge (11)
+ private static final float MIN_MODULE_COUNT_PER_EDGE = 9;
+
+ /**
+ * More or less arbitrary cutoff point for determining if two finder patterns might belong
+ * to the same code if they differ less than DIFF_MODSIZE_CUTOFF_PERCENT percent in their
+ * estimated modules sizes.
+ */
+ private static final float DIFF_MODSIZE_CUTOFF_PERCENT = 0.05f;
+
+ /**
+ * More or less arbitrary cutoff point for determining if two finder patterns might belong
+ * to the same code if they differ less than DIFF_MODSIZE_CUTOFF pixels/module in their
+ * estimated modules sizes.
+ */
+ private static final float DIFF_MODSIZE_CUTOFF = 0.5f;
+
+
+ /**
+ * A comparator that orders FinderPatterns by their estimated module size.
+ */
+ private static final class ModuleSizeComparator implements Comparator, Serializable {
+ @Override
+ public int compare(FinderPattern center1, FinderPattern center2) {
+ float value = center2.getEstimatedModuleSize() - center1.getEstimatedModuleSize();
+ return value < 0.0 ? -1 : value > 0.0 ? 1 : 0;
+ }
+ }
+
+ /**
+ * Creates a finder that will search the image for three finder patterns.
+ *
+ * @param image image to search
+ */
+ MultiFinderPatternFinder(BitMatrix image) {
+ super(image);
+ }
+
+ MultiFinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) {
+ super(image, resultPointCallback);
+ }
+
+ /**
+ * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are
+ * those that have been detected at least {@link #CENTER_QUORUM} times, and whose module
+ * size differs from the average among those patterns the least
+ * @throws NotFoundException if 3 such finder patterns do not exist
+ */
+ private FinderPattern[][] selectMutipleBestPatterns() throws NotFoundException {
+ List possibleCenters = getPossibleCenters();
+ int size = possibleCenters.size();
+
+ if (size < 3) {
+ // Couldn't find enough finder patterns
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /*
+ * Begin HE modifications to safely detect multiple codes of equal size
+ */
+ if (size == 3) {
+ return new FinderPattern[][]{
+ new FinderPattern[]{
+ possibleCenters.get(0),
+ possibleCenters.get(1),
+ possibleCenters.get(2)
+ }
+ };
+ }
+
+ // Sort by estimated module size to speed up the upcoming checks
+ Collections.sort(possibleCenters, new ModuleSizeComparator());
+
+ /*
+ * Now lets start: build a list of tuples of three finder locations that
+ * - feature similar module sizes
+ * - are placed in a distance so the estimated module count is within the QR specification
+ * - have similar distance between upper left/right and left top/bottom finder patterns
+ * - form a triangle with 90° angle (checked by comparing top right/bottom left distance
+ * with pythagoras)
+ *
+ * Note: we allow each point to be used for more than one code region: this might seem
+ * counterintuitive at first, but the performance penalty is not that big. At this point,
+ * we cannot make a good quality decision whether the three finders actually represent
+ * a QR code, or are just by chance layouted so it looks like there might be a QR code there.
+ * So, if the layout seems right, lets have the decoder try to decode.
+ */
+
+ List results = new ArrayList<>(); // holder for the results
+
+ for (int i1 = 0; i1 < (size - 2); i1++) {
+ FinderPattern p1 = possibleCenters.get(i1);
+ if (p1 == null) {
+ continue;
+ }
+
+ for (int i2 = i1 + 1; i2 < (size - 1); i2++) {
+ FinderPattern p2 = possibleCenters.get(i2);
+ if (p2 == null) {
+ continue;
+ }
+
+ // Compare the expected module sizes; if they are really off, skip
+ float vModSize12 = (p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()) /
+ Math.min(p1.getEstimatedModuleSize(), p2.getEstimatedModuleSize());
+ float vModSize12A = Math.abs(p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize());
+ if (vModSize12A > DIFF_MODSIZE_CUTOFF && vModSize12 >= DIFF_MODSIZE_CUTOFF_PERCENT) {
+ // break, since elements are ordered by the module size deviation there cannot be
+ // any more interesting elements for the given p1.
+ break;
+ }
+
+ for (int i3 = i2 + 1; i3 < size; i3++) {
+ FinderPattern p3 = possibleCenters.get(i3);
+ if (p3 == null) {
+ continue;
+ }
+
+ // Compare the expected module sizes; if they are really off, skip
+ float vModSize23 = (p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()) /
+ Math.min(p2.getEstimatedModuleSize(), p3.getEstimatedModuleSize());
+ float vModSize23A = Math.abs(p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize());
+ if (vModSize23A > DIFF_MODSIZE_CUTOFF && vModSize23 >= DIFF_MODSIZE_CUTOFF_PERCENT) {
+ // break, since elements are ordered by the module size deviation there cannot be
+ // any more interesting elements for the given p1.
+ break;
+ }
+
+ FinderPattern[] test = {p1, p2, p3};
+ ResultPoint.orderBestPatterns(test);
+
+ // Calculate the distances: a = topleft-bottomleft, b=topleft-topright, c = diagonal
+ FinderPatternInfo info = new FinderPatternInfo(test);
+ float dA = ResultPoint.distance(info.getTopLeft(), info.getBottomLeft());
+ float dC = ResultPoint.distance(info.getTopRight(), info.getBottomLeft());
+ float dB = ResultPoint.distance(info.getTopLeft(), info.getTopRight());
+
+ // Check the sizes
+ float estimatedModuleCount = (dA + dB) / (p1.getEstimatedModuleSize() * 2.0f);
+ if (estimatedModuleCount > MAX_MODULE_COUNT_PER_EDGE ||
+ estimatedModuleCount < MIN_MODULE_COUNT_PER_EDGE) {
+ continue;
+ }
+
+ // Calculate the difference of the edge lengths in percent
+ float vABBC = Math.abs((dA - dB) / Math.min(dA, dB));
+ if (vABBC >= 0.1f) {
+ continue;
+ }
+
+ // Calculate the diagonal length by assuming a 90° angle at topleft
+ float dCpy = (float) Math.sqrt(dA * dA + dB * dB);
+ // Compare to the real distance in %
+ float vPyC = Math.abs((dC - dCpy) / Math.min(dC, dCpy));
+
+ if (vPyC >= 0.1f) {
+ continue;
+ }
+
+ // All tests passed!
+ results.add(test);
+ } // end iterate p3
+ } // end iterate p2
+ } // end iterate p1
+
+ if (!results.isEmpty()) {
+ return results.toArray(new FinderPattern[results.size()][]);
+ }
+
+ // Nothing found!
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ public FinderPatternInfo[] findMulti(Map hints) throws NotFoundException {
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ boolean pureBarcode = hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE);
+ BitMatrix image = getImage();
+ int maxI = image.getHeight();
+ int maxJ = image.getWidth();
+ // We are looking for black/white/black/white/black modules in
+ // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
+
+ // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the
+ // image, and then account for the center being 3 modules in size. This gives the smallest
+ // number of pixels the center could be, so skip this often. When trying harder, look for all
+ // QR versions regardless of how dense they are.
+ int iSkip = (int) (maxI / (MAX_MODULES * 4.0f) * 3);
+ if (iSkip < MIN_SKIP || tryHarder) {
+ iSkip = MIN_SKIP;
+ }
+
+ int[] stateCount = new int[5];
+ for (int i = iSkip - 1; i < maxI; i += iSkip) {
+ // Get a row of black/white values
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ int currentState = 0;
+ for (int j = 0; j < maxJ; j++) {
+ if (image.get(j, i)) {
+ // Black pixel
+ if ((currentState & 1) == 1) { // Counting white pixels
+ currentState++;
+ }
+ stateCount[currentState]++;
+ } else { // White pixel
+ if ((currentState & 1) == 0) { // Counting black pixels
+ if (currentState == 4) { // A winner?
+ if (foundPatternCross(stateCount) && handlePossibleCenter(stateCount, i, j, pureBarcode)) { // Yes
+ // Clear state to start looking again
+ currentState = 0;
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ } else { // No, shift counts back by two
+ stateCount[0] = stateCount[2];
+ stateCount[1] = stateCount[3];
+ stateCount[2] = stateCount[4];
+ stateCount[3] = 1;
+ stateCount[4] = 0;
+ currentState = 3;
+ }
+ } else {
+ stateCount[++currentState]++;
+ }
+ } else { // Counting white pixels
+ stateCount[currentState]++;
+ }
+ }
+ } // for j=...
+
+ if (foundPatternCross(stateCount)) {
+ handlePossibleCenter(stateCount, i, maxJ, pureBarcode);
+ } // end if foundPatternCross
+ } // for i=iSkip-1 ...
+ FinderPattern[][] patternInfo = selectMutipleBestPatterns();
+ List result = new ArrayList<>();
+ for (FinderPattern[] pattern : patternInfo) {
+ ResultPoint.orderBestPatterns(pattern);
+ result.add(new FinderPatternInfo(pattern));
+ }
+
+ if (result.isEmpty()) {
+ return EMPTY_RESULT_ARRAY;
+ } else {
+ return result.toArray(new FinderPatternInfo[result.size()]);
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/CodaBarReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/CodaBarReader.java
new file mode 100644
index 000000000..7c9cbd8ce
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/CodaBarReader.java
@@ -0,0 +1,346 @@
+/*
+ * 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.ResultPoint;
+import com.google.zxing.common.BitArray;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Decodes Codabar barcodes.
+ *
+ * @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 int MAX_ACCEPTABLE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 2.0f);
+ private static final int PADDING = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 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 (inclusing 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 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 = (float) runningCount;
+ for (int i = startOffset; i < nextStart - 1; i++) {
+ runningCount += counters[i];
+ }
+ float right = (float) runningCount;
+ return new Result(
+ decodeRowResult.toString(),
+ null,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODABAR);
+ }
+
+ 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; true; 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;
+ }
+ if (i >= end) {
+ break;
+ }
+ // We ignore the inter-character space - it could be of any size.
+ pos += 8;
+ }
+
+ // Calculate our allowable size thresholds using fixed-point math.
+ int[] maxes = new int[4];
+ int[] mins = new int[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; // Accept arbitrarily small "short" stripes.
+ mins[i + 2] = ((sizes[i] << INTEGER_MATH_SHIFT) / counts[i] +
+ (sizes[i + 2] << INTEGER_MATH_SHIFT) / counts[i + 2]) >> 1;
+ 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; true; 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] << INTEGER_MATH_SHIFT;
+ if (size < mins[category] || size > maxes[category]) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ pattern >>= 1;
+ }
+ if (i >= end) {
+ break;
+ }
+ 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) { // that is, exactly one is true
+ 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;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/CodaBarWriter.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/CodaBarWriter.java
new file mode 100644
index 000000000..39ac22924
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/CodaBarWriter.java
@@ -0,0 +1,118 @@
+/*
+ * 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 java.util.Arrays;
+
+/**
+ * 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 = {'/', ':', '+', '.'};
+
+ @Override
+ public boolean[] encode(String contents) {
+
+ if (contents.length() < 2) {
+ throw new IllegalArgumentException("Codabar should start/end with start/stop symbols");
+ }
+ // Verify input and calculate decoded length.
+ char firstChar = Character.toUpperCase(contents.charAt(0));
+ char lastChar = Character.toUpperCase(contents.charAt(contents.length() - 1));
+ boolean startsEndsNormal =
+ CodaBarReader.arrayContains(START_END_CHARS, firstChar) &&
+ CodaBarReader.arrayContains(START_END_CHARS, lastChar);
+ boolean startsEndsAlt =
+ CodaBarReader.arrayContains(ALT_START_END_CHARS, firstChar) &&
+ CodaBarReader.arrayContains(ALT_START_END_CHARS, lastChar);
+ if (!(startsEndsNormal || startsEndsAlt)) {
+ throw new IllegalArgumentException(
+ "Codabar should start/end with " + Arrays.toString(START_END_CHARS) +
+ ", or start/end with " + Arrays.toString(ALT_START_END_CHARS));
+ }
+
+ // 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;
+ }
+}
+
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/Code128Reader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code128Reader.java
new file mode 100644
index 000000000..82540c729
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code128Reader.java
@@ -0,0 +1,539 @@
+/*
+ * 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.ResultPoint;
+import com.google.zxing.common.BitArray;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Decodes Code 128 barcodes.
+ *
+ * @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 int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.25f);
+ private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 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) {
+ int bestVariance = MAX_AVG_VARIANCE;
+ int bestMatch = -1;
+ for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) {
+ int 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, patternLength - 2);
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 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);
+ int 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];
+ int 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 hints)
+ throws NotFoundException, FormatException, ChecksumException {
+
+ boolean convertFNC1 = hints != null && hints.containsKey(DecodeHintType.ASSUME_GS1);
+
+ int[] startPatternInfo = findStartPattern(row);
+ int startCode = startPatternInfo[2];
+
+ List 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 (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:
+ 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 (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:
+ 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 (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 = (float) (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);
+ }
+
+ return new Result(
+ result.toString(),
+ rawBytes,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODE_128);
+
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/Code128Writer.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code128Writer.java
new file mode 100644
index 000000000..ad149e4d0
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code128Writer.java
@@ -0,0 +1,200 @@
+/*
+ * 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.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.ArrayList;
+import java.util.Collection;
+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_B = 104;
+ private static final int CODE_START_C = 105;
+ 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_B = 100; // Code B
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.CODE_128) {
+ throw new IllegalArgumentException("Can only encode CODE_128, but got " + format);
+ }
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ @Override
+ public boolean[] encode(String contents) {
+ 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 content
+ for (int i = 0; i < length; i++) {
+ char c = contents.charAt(i);
+ if (c < ' ' || c > '~') {
+ switch (c) {
+ case ESCAPE_FNC_1:
+ case ESCAPE_FNC_2:
+ case ESCAPE_FNC_3:
+ case ESCAPE_FNC_4:
+ break;
+ default:
+ throw new IllegalArgumentException("Bad character in input: " + c);
+ }
+ }
+ }
+
+ Collection 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 requiredDigitCount = codeSet == CODE_CODE_C ? 2 : 4;
+ int newCodeSet;
+ if (isDigits(contents, position, requiredDigitCount)) {
+ newCodeSet = CODE_CODE_C;
+ } else {
+ newCodeSet = CODE_CODE_B;
+ }
+
+ //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:
+ patternIndex = CODE_FNC_4_B; // FIXME if this ever outputs Code A
+ break;
+ default:
+ // Then handle normal characters otherwise
+ if (codeSet == CODE_CODE_B) {
+ patternIndex = contents.charAt(position) - ' ';
+ } else { // CODE_CODE_C
+ patternIndex = Integer.parseInt(contents.substring(position, position + 2));
+ position++; // Also incremented below
+ }
+ }
+ 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
+ if (newCodeSet == CODE_CODE_B) {
+ patternIndex = CODE_START_B;
+ } else {
+ // CODE_CODE_C
+ patternIndex = CODE_START_C;
+ }
+ } 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++;
+ }
+ }
+
+ // 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 boolean isDigits(CharSequence value, int start, int length) {
+ int end = start + length;
+ int last = value.length();
+ for (int i = start; i < end && i < last; i++) {
+ char c = value.charAt(i);
+ if (c < '0' || c > '9') {
+ if (c != ESCAPE_FNC_1) {
+ return false;
+ }
+ end++; // ignore FNC_1
+ }
+ }
+ return end <= last; // end > last if we've run out of string
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/Code39Reader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code39Reader.java
new file mode 100644
index 000000000..685354de7
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code39Reader.java
@@ -0,0 +1,323 @@
+/*
+ * 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.ResultPoint;
+import com.google.zxing.common.BitArray;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Decodes Code 39 barcodes. This does not support "Full ASCII Code 39" yet.
+ *
+ * @author Sean Owen
+ * @see Code93Reader
+ */
+public final class Code39Reader extends OneDReader {
+
+ static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%";
+ 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,
+ * 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, 0x094, // U-*
+ 0x0A8, 0x0A2, 0x08A, 0x02A // $-%
+ };
+
+ private static final int ASTERISK_ENCODING = CHARACTER_ENCODINGS[39];
+
+ 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 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 << 1) < 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[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 = (float) (start[1] + start[0]) / 2.0f;
+ float right = lastStart + lastPatternSize / 2.0f;
+ return new Result(
+ resultString,
+ null,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODE_39);
+
+ }
+
+ 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) >> 1)), patternStart, false)) {
+ return new int[]{patternStart, i};
+ }
+ patternStart += counters[0] + counters[1];
+ System.arraycopy(counters, 2, counters, 0, patternLength - 2);
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 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 << 1) >= 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[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 == '+' || 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 <= 'W') {
+ decodedChar = (char) (next - 11);
+ } 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();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/Code39Writer.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code39Writer.java
new file mode 100644
index 000000000..8a498a0ab
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code39Writer.java
@@ -0,0 +1,89 @@
+/*
+ * 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.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * This object renders a CODE39 code as a {@link BitMatrix}.
+ *
+ * @author erik.barbara@gmail.com (Erik Barbara)
+ */
+public final class Code39Writer extends OneDimensionalCodeWriter {
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.CODE_39) {
+ throw new IllegalArgumentException("Can only encode CODE_39, but got " + format);
+ }
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ @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);
+ }
+
+ int[] widths = new int[9];
+ int codeWidth = 24 + 1 + length;
+ for (int i = 0; i < length; i++) {
+ int indexInString = Code39Reader.ALPHABET_STRING.indexOf(contents.charAt(i));
+ if (indexInString < 0) {
+ throw new IllegalArgumentException("Bad contents: " + contents);
+ }
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[indexInString], widths);
+ for (int width : widths) {
+ codeWidth += width;
+ }
+ }
+ boolean[] result = new boolean[codeWidth];
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[39], 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.CHARACTER_ENCODINGS[39], 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;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/Code93Reader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code93Reader.java
new file mode 100644
index 000000000..e79925867
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/Code93Reader.java
@@ -0,0 +1,281 @@
+/*
+ * 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.ResultPoint;
+import com.google.zxing.common.BitArray;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Decodes Code 93 barcodes.
+ *
+ * @author Sean Owen
+ * @see Code39Reader
+ */
+public final class Code93Reader extends OneDReader {
+
+ // Note that 'abcd' are dummy characters in place of control characters.
+ private 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.
+ */
+ private 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? $-*
+ };
+ private 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 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 = (float) (start[1] + start[0]) / 2.0f;
+ float right = lastStart + lastPatternSize / 2.0f;
+ return new Result(
+ resultString,
+ null,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODE_93);
+
+ }
+
+ 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, patternLength - 2);
+ theCounters[patternLength - 2] = 0;
+ theCounters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ theCounters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private static int toPattern(int[] counters) {
+ int max = counters.length;
+ int sum = 0;
+ for (int counter : counters) {
+ sum += counter;
+ }
+ int pattern = 0;
+ for (int i = 0; i < max; i++) {
+ int scaledShifted = (counters[i] << INTEGER_MATH_SHIFT) * 9 / sum;
+ int scaledUnshifted = scaledShifted >> INTEGER_MATH_SHIFT;
+ if ((scaledShifted & 0xFF) > 0x7F) {
+ scaledUnshifted++;
+ }
+ if (scaledUnshifted < 1 || scaledUnshifted > 4) {
+ return -1;
+ }
+ if ((i & 0x01) == 0) {
+ for (int j = 0; j < scaledUnshifted; j++) {
+ pattern = (pattern << 1) | 0x01;
+ }
+ } else {
+ pattern <<= scaledUnshifted;
+ }
+ }
+ 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':
+ // %A to %E map to control codes ESC to US
+ if (next >= 'A' && next <= 'E') {
+ decodedChar = (char) (next - 38);
+ } else if (next >= 'F' && next <= 'W') {
+ decodedChar = (char) (next - 11);
+ } 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();
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN13Reader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN13Reader.java
new file mode 100644
index 000000000..c537eac51
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN13Reader.java
@@ -0,0 +1,138 @@
+/*
+ * 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;
+
+/**
+ * Implements decoding of the EAN-13 format.
+ *
+ * @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();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN13Writer.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN13Writer.java
new file mode 100644
index 000000000..f3e947d69
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN13Writer.java
@@ -0,0 +1,94 @@
+/*
+ * 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.EncodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * 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
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.EAN_13) {
+ throw new IllegalArgumentException("Can only encode EAN_13, but got " + format);
+ }
+
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ @Override
+ public boolean[] encode(String contents) {
+ if (contents.length() != 13) {
+ throw new IllegalArgumentException(
+ "Requested contents should be 13 digits long, but got " + contents.length());
+ }
+ try {
+ if (!UPCEANReader.checkStandardUPCEANChecksum(contents)) {
+ throw new IllegalArgumentException("Contents do not pass checksum");
+ }
+ } catch (FormatException ignored) {
+ throw new IllegalArgumentException("Illegal contents");
+ }
+
+ int firstDigit = Integer.parseInt(contents.substring(0, 1));
+ 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 {@link #EAN13Reader} for a description of how the first digit & left bars are encoded
+ for (int i = 1; i <= 6; i++) {
+ int digit = Integer.parseInt(contents.substring(i, i + 1));
+ 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 = Integer.parseInt(contents.substring(i, i + 1));
+ pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], true);
+ }
+ appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
+
+ return result;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN8Reader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN8Reader.java
new file mode 100644
index 000000000..8d0b7e21e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN8Reader.java
@@ -0,0 +1,75 @@
+/*
+ * 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;
+
+/**
+ * Implements decoding of the EAN-8 format.
+ *
+ * @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;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN8Writer.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN8Writer.java
new file mode 100644
index 000000000..efd807d97
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/EAN8Writer.java
@@ -0,0 +1,84 @@
+/*
+ * 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.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * 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
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.EAN_8) {
+ throw new IllegalArgumentException("Can only encode EAN_8, but got "
+ + format);
+ }
+
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ /**
+ * @return a byte array of horizontal pixels (false = white, true = black)
+ */
+ @Override
+ public boolean[] encode(String contents) {
+ if (contents.length() != 8) {
+ throw new IllegalArgumentException(
+ "Requested contents should be 8 digits long, but got " + contents.length());
+ }
+
+ 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 = Integer.parseInt(contents.substring(i, i + 1));
+ 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 = Integer.parseInt(contents.substring(i, i + 1));
+ pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], true);
+ }
+ appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
+
+ return result;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/EANManufacturerOrgSupport.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/EANManufacturerOrgSupport.java
new file mode 100644
index 000000000..e5b439264
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/EANManufacturerOrgSupport.java
@@ -0,0 +1,171 @@
+/*
+ * 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
+ *
+ * http://en.wikipedia.org/wiki/List_of_GS1_country_codes.
+ *
+ * @author Sean Owen
+ */
+final class EANManufacturerOrgSupport {
+
+ private final List ranges = new ArrayList<>();
+ private final List 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");
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/ITFReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/ITFReader.java
new file mode 100644
index 000000000..004cd2777
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/ITFReader.java
@@ -0,0 +1,358 @@
+/*
+ * 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.ResultPoint;
+import com.google.zxing.common.BitArray;
+
+import java.util.Map;
+
+/**
+ * Implements decoding of the ITF format, or Interleaved Two of Five.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * http://en.wikipedia.org/wiki/Interleaved_2_of_5
+ * is a great reference for Interleaved 2 of 5 information.
+ *
+ * @author kevin.osullivan@sita.aero, SITA Lab.
+ */
+public final class ITFReader extends OneDReader {
+
+ private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);
+ private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.78f);
+
+ private static final int W = 3; // Pixel width of a 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};
+
+ /**
+ * Patterns of Wide / Narrow lines to indicate each digit
+ */
+ 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
+ public Result decodeRow(int rowNumber, BitArray row, Map 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();
+ }
+
+ return new Result(
+ resultString,
+ null, // no natural byte representation for these barcodes
+ new ResultPoint[] { new ResultPoint(startRange[1], (float) rowNumber),
+ new ResultPoint(endRange[0], (float) rowNumber)},
+ BarcodeFormat.ITF);
+ }
+
+ /**
+ * @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 = k << 1;
+ 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'
+ * @throws NotFoundException
+ */
+ 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]) >> 2;
+
+ 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, a ReaderException is thrown.
+ */
+ 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 = quietCount < startPattern ? 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'
+ * @throws NotFoundException
+ */
+ 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 = findGuardPattern(row, endStart, END_PATTERN_REVERSED);
+
+ // 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 {
+
+ // TODO: This is very similar to implementation in UPCEANReader. Consider if they can be
+ // merged to a single method.
+ 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, patternLength - 2);
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 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 {
+
+ int 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];
+ int variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
+ if (variance < bestVariance) {
+ bestVariance = variance;
+ bestMatch = i;
+ }
+ }
+ if (bestMatch >= 0) {
+ return bestMatch;
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/ITFWriter.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/ITFWriter.java
new file mode 100644
index 000000000..03c0287e4
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/ITFWriter.java
@@ -0,0 +1,76 @@
+/*
+ * 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.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * 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};
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.ITF) {
+ throw new IllegalArgumentException("Can only encode ITF, but got " + format);
+ }
+
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ @Override
+ public boolean[] encode(String contents) {
+ int length = contents.length();
+ if (length % 2 != 0) {
+ throw new IllegalArgumentException("The lenght of the input should be even");
+ }
+ if (length > 80) {
+ throw new IllegalArgumentException(
+ "Requested contents should be less than 80 digits long, but got " + length);
+ }
+ 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[18];
+ for (int j = 0; j < 5; j++) {
+ encoding[j << 1] = ITFReader.PATTERNS[one][j];
+ encoding[(j << 1) + 1] = ITFReader.PATTERNS[two][j];
+ }
+ pos += appendPattern(result, pos, encoding, true);
+ }
+ appendPattern(result, pos, END_PATTERN, true);
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/MultiFormatOneDReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/MultiFormatOneDReader.java
new file mode 100644
index 000000000..031bd1128
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/MultiFormatOneDReader.java
@@ -0,0 +1,112 @@
+/*
+ * 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 final OneDReader[] readers;
+
+ public MultiFormatOneDReader(Map hints) {
+ @SuppressWarnings("unchecked")
+ Collection possibleFormats = hints == null ? null :
+ (Collection) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ boolean useCode39CheckDigit = hints != null &&
+ hints.get(DecodeHintType.ASSUME_CODE_39_CHECK_DIGIT) != null;
+ Collection 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(new OneDReader[readers.size()]);
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ Map 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();
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/MultiFormatUPCEANReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/MultiFormatUPCEANReader.java
new file mode 100644
index 000000000..9449f155c
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/MultiFormatUPCEANReader.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+/**
+ * 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.
+ *
+ * @author Sean Owen
+ */
+public final class MultiFormatUPCEANReader extends OneDReader {
+
+ private final UPCEANReader[] readers;
+
+ public MultiFormatUPCEANReader(Map hints) {
+ @SuppressWarnings("unchecked")
+ Collection possibleFormats = hints == null ? null :
+ (Collection) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ Collection 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(new UPCEANReader[readers.size()]);
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ Map hints) throws NotFoundException {
+ // Compute this location once and reuse it on multiple implementations
+ int[] startGuardPattern = UPCEANReader.findStartGuardPattern(row);
+ for (UPCEANReader reader : readers) {
+ Result result;
+ try {
+ result = reader.decodeRow(rowNumber, row, startGuardPattern, hints);
+ } catch (ReaderException ignored) {
+ continue;
+ }
+ // 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 possibleFormats =
+ hints == null ? null : (Collection) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ boolean canReturnUPCA = possibleFormats == null || possibleFormats.contains(BarcodeFormat.UPC_A);
+
+ if (ean13MayBeUPCA && canReturnUPCA) {
+ // Transfer the metdata across
+ Result resultUPCA = new Result(result.getText().substring(1),
+ result.getRawBytes(),
+ result.getResultPoints(),
+ BarcodeFormat.UPC_A);
+ resultUPCA.putAllMetadata(result.getResultMetadata());
+ return resultUPCA;
+ }
+ return result;
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ @Override
+ public void reset() {
+ for (Reader reader : readers) {
+ reader.reset();
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/OneDReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/OneDReader.java
new file mode 100644
index 000000000..0ef3e14e1
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/OneDReader.java
@@ -0,0 +1,305 @@
+/*
+ * 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 {
+
+ protected static final int INTEGER_MATH_SHIFT = 8;
+ protected static final int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
+
+ @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 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 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 hints) throws NotFoundException {
+ int width = image.getWidth();
+ int height = image.getHeight();
+ BitArray row = new BitArray(width);
+
+ int middle = height >> 1;
+ 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
+ }
+
+ for (int x = 0; x < maxLines; x++) {
+
+ // Scanning from the middle out. Determine which row we're looking at next:
+ int rowStepsAboveOrBelow = (x + 1) >> 1;
+ 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 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) { // that is, exactly one is true
+ counters[counterPosition]++;
+ } else {
+ counterPosition++;
+ 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,
+ * where the ratio has been multiplied by 256. So, 0 means no variance (perfect match); 256 means
+ * the total variance between counters and patterns equals the pattern length, higher values mean
+ * even more variance
+ */
+ protected static int patternMatchVariance(int[] counters,
+ int[] pattern,
+ int 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 Integer.MAX_VALUE;
+ }
+ // We're going to fake floating-point math in integers. We just need to use more bits.
+ // Scale up patternLength so that intermediate values below like scaledCounter will have
+ // more "significant digits"
+ int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;
+ maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;
+
+ int totalVariance = 0;
+ for (int x = 0; x < numCounters; x++) {
+ int counter = counters[x] << INTEGER_MATH_SHIFT;
+ int scaledPattern = pattern[x] * unitBarWidth;
+ int variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
+ if (variance > maxIndividualVariance) {
+ return Integer.MAX_VALUE;
+ }
+ totalVariance += variance;
+ }
+ return totalVariance / total;
+ }
+
+ /**
+ * Attempts to decode a one-dimensional barcode format given a single row of
+ * an image.
+ *
+ * @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 hints)
+ throws NotFoundException, ChecksumException, FormatException;
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/OneDimensionalCodeWriter.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/OneDimensionalCodeWriter.java
new file mode 100644
index 000000000..c2dad999e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/OneDimensionalCodeWriter.java
@@ -0,0 +1,132 @@
+/*
+ * 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.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * Encapsulates functionality and implementation that is common to one-dimensional barcodes.
+ *
+ * @author dsbnatut@gmail.com (Kazuki Nishiura)
+ */
+public abstract class OneDimensionalCodeWriter implements Writer {
+
+ @Override
+ public final BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
+ throws WriterException {
+ 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 hints) throws WriterException {
+ 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);
+ }
+
+ int sidesMargin = getDefaultMargin();
+ if (hints != null) {
+ Integer sidesMarginInt = (Integer) hints.get(EncodeHintType.MARGIN);
+ if (sidesMarginInt != null) {
+ sidesMargin = sidesMarginInt;
+ }
+ }
+
+ boolean[] code = encode(contents);
+ return renderResult(code, width, height, sidesMargin);
+ }
+
+ /**
+ * @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 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;
+ }
+
+ /**
+ * 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);
+}
+
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCAReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCAReader.java
new file mode 100644
index 000000000..62f69cce3
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCAReader.java
@@ -0,0 +1,86 @@
+/*
+ * 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;
+
+/**
+ * Implements decoding of the UPC-A format.
+ *
+ * @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 hints)
+ throws NotFoundException, FormatException, ChecksumException {
+ return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, startGuardRange, hints));
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber, BitArray row, Map 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 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') {
+ return new Result(text.substring(1), null, result.getResultPoints(), BarcodeFormat.UPC_A);
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCAWriter.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCAWriter.java
new file mode 100644
index 000000000..64f777de4
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCAWriter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.WriterException;
+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)
+ throws WriterException {
+ return encode(contents, format, width, height, null);
+ }
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.UPC_A) {
+ throw new IllegalArgumentException("Can only encode UPC-A, but got " + format);
+ }
+ return subWriter.encode(preencode(contents), BarcodeFormat.EAN_13, width, height, hints);
+ }
+
+ /**
+ * Transform a UPC-A code into the equivalent EAN-13 code, and add a check digit if it is not
+ * already present.
+ */
+ private static String preencode(String contents) {
+ int length = contents.length();
+ if (length == 11) {
+ // No check digit present, calculate it and add it
+ int sum = 0;
+ for (int i = 0; i < 11; ++i) {
+ sum += (contents.charAt(i) - '0') * (i % 2 == 0 ? 3 : 1);
+ }
+ contents += (1000 - sum) % 10;
+ } else if (length != 12) {
+ throw new IllegalArgumentException(
+ "Requested contents should be 11 or 12 digits long, but got " + contents.length());
+ }
+ return '0' + contents;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtension2Support.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtension2Support.java
new file mode 100644
index 000000000..03cd35d4d
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtension2Support.java
@@ -0,0 +1,112 @@
+/*
+ * 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 extensionData = parseExtensionString(resultString);
+
+ Result extensionResult =
+ new Result(resultString,
+ null,
+ new ResultPoint[] {
+ new ResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0f, (float) rowNumber),
+ new ResultPoint((float) end, (float) rowNumber),
+ },
+ BarcodeFormat.UPC_EAN_EXTENSION);
+ if (extensionData != null) {
+ extensionResult.putAllMetadata(extensionData);
+ }
+ return extensionResult;
+ }
+
+ 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 parseExtensionString(String raw) {
+ if (raw.length() != 2) {
+ return null;
+ }
+ Map result = new EnumMap<>(ResultMetadataType.class);
+ result.put(ResultMetadataType.ISSUE_NUMBER, Integer.valueOf(raw));
+ return result;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtension5Support.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtension5Support.java
new file mode 100644
index 000000000..ecd421e0d
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtension5Support.java
@@ -0,0 +1,181 @@
+/*
+ * 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 extensionData = parseExtensionString(resultString);
+
+ Result extensionResult =
+ new Result(resultString,
+ null,
+ new ResultPoint[] {
+ new ResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0f, (float) rowNumber),
+ new ResultPoint((float) end, (float) rowNumber),
+ },
+ BarcodeFormat.UPC_EAN_EXTENSION);
+ if (extensionData != null) {
+ extensionResult.putAllMetadata(extensionData);
+ }
+ return extensionResult;
+ }
+
+ 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 += (int) s.charAt(i) - (int) '0';
+ }
+ sum *= 3;
+ for (int i = length - 1; i >= 0; i -= 2) {
+ sum += (int) s.charAt(i) - (int) '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 parseExtensionString(String raw) {
+ if (raw.length() != 5) {
+ return null;
+ }
+ Object value = parseExtension5String(raw);
+ if (value == null) {
+ return null;
+ }
+ Map 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
+ if ("90000".equals(raw)) {
+ // No suggested retail price
+ return null;
+ }
+ if ("99991".equals(raw)) {
+ // Complementary
+ return "0.00";
+ }
+ if ("99990".equals(raw)) {
+ 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;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtensionSupport.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtensionSupport.java
new file mode 100644
index 000000000..b36a58141
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANExtensionSupport.java
@@ -0,0 +1,40 @@
+/*
+ * 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);
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANReader.java
new file mode 100644
index 000000000..c9ddc32c7
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANReader.java
@@ -0,0 +1,395 @@
+/*
+ * 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;
+
+/**
+ * Encapsulates functionality and implementation that is common to UPC and EAN families
+ * of one-dimensional barcodes.
+ *
+ * @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 int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.48f);
+ private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 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};
+
+ /**
+ * "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 hints)
+ throws NotFoundException, ChecksumException, FormatException {
+ return decodeRow(rowNumber, row, findStartGuardPattern(row), hints);
+ }
+
+ /**
+ * Like {@link #decodeRow(int, BitArray, java.util.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.
+ *
+ * @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 hints)
+ throws NotFoundException, ChecksumException, FormatException {
+
+ ResultPointCallback resultPointCallback = hints == null ? null :
+ (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+
+ 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 = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;
+ float right = (float) (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, (float) rowNumber),
+ new ResultPoint(right, (float) 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);
+ }
+ }
+
+ 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 sum = 0;
+ for (int i = length - 2; i >= 0; i -= 2) {
+ int digit = (int) s.charAt(i) - (int) '0';
+ if (digit < 0 || digit > 9) {
+ throw FormatException.getFormatInstance();
+ }
+ sum += digit;
+ }
+ sum *= 3;
+ for (int i = length - 1; i >= 0; i -= 2) {
+ int digit = (int) s.charAt(i) - (int) '0';
+ if (digit < 0 || digit > 9) {
+ throw FormatException.getFormatInstance();
+ }
+ sum += digit;
+ }
+ return sum % 10 == 0;
+ }
+
+ 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 patternLength = pattern.length;
+ int width = row.getSize();
+ boolean isWhite = whiteFirst;
+ rowOffset = whiteFirst ? row.getNextUnset(rowOffset) : row.getNextSet(rowOffset);
+ 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, patternLength - 2);
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 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);
+ int 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];
+ int 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;
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANWriter.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANWriter.java
new file mode 100644
index 000000000..a39937dbb
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEANWriter.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * Encapsulates functionality and implementation that is common to UPC and EAN families
+ * of one-dimensional barcodes.
+ *
+ * @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 UPCEANReader.START_END_PATTERN.length;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEReader.java
new file mode 100644
index 000000000..ba86eb279
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/UPCEReader.java
@@ -0,0 +1,155 @@
+/*
+ * 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;
+
+/**
+ * Implements decoding of the UPC-E format.
+ * This is a great reference for
+ * UPC-E information.
+ *
+ * @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};
+
+ /**
+ * 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.
+ */
+ private 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;
+ }
+ result.append(upce.charAt(7));
+ return result.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/AbstractRSSReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/AbstractRSSReader.java
new file mode 100644
index 000000000..d23096131
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/AbstractRSSReader.java
@@ -0,0 +1,133 @@
+/*
+ * 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.oned.OneDReader;
+
+public abstract class AbstractRSSReader extends OneDReader {
+
+ private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.2f);
+ private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 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();
+ }
+
+ protected static int count(int[] array) {
+ int count = 0;
+ for (int a : array) {
+ count += a;
+ }
+ return count;
+ }
+
+ 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 = (float) 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;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/DataCharacter.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/DataCharacter.java
new file mode 100644
index 000000000..167930000
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/DataCharacter.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+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;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/FinderPattern.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/FinderPattern.java
new file mode 100644
index 000000000..492aec977
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/FinderPattern.java
@@ -0,0 +1,62 @@
+/*
+ * 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;
+
+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((float) start, (float) rowNumber),
+ new ResultPoint((float) end, (float) 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;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/Pair.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/Pair.java
new file mode 100644
index 000000000..e2371d29e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/Pair.java
@@ -0,0 +1,41 @@
+/*
+ * 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++;
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/RSS14Reader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/RSS14Reader.java
new file mode 100644
index 000000000..cb62673bc
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/RSS14Reader.java
@@ -0,0 +1,477 @@
+/*
+ * 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.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitArray;
+
+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 possibleLeftPairs;
+ private final List possibleRightPairs;
+
+ public RSS14Reader() {
+ possibleLeftPairs = new ArrayList<>();
+ possibleRightPairs = new ArrayList<>();
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ Map 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();
+ int lefSize = possibleLeftPairs.size();
+ for (int i = 0; i < lefSize; i++) {
+ Pair left = possibleLeftPairs.get(i);
+ if (left.getCount() > 1) {
+ int rightSize = possibleRightPairs.size();
+ for (int j = 0; j < rightSize; j++) {
+ Pair right = possibleRightPairs.get(j);
+ if (right.getCount() > 1) {
+ if (checkChecksum(left, right)) {
+ return constructResult(left, right);
+ }
+ }
+ }
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private static void addOrTally(Collection 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();
+ return new Result(
+ String.valueOf(buffer.toString()),
+ null,
+ new ResultPoint[] { leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1], },
+ BarcodeFormat.RSS_14);
+ }
+
+ private static boolean checkChecksum(Pair leftPair, Pair rightPair) {
+ //int leftFPValue = leftPair.getFinderPattern().getValue();
+ //int rightFPValue = rightPair.getFinderPattern().getValue();
+ //if ((leftFPValue == 0 && rightFPValue == 8) ||
+ // (leftFPValue == 8 && rightFPValue == 0)) {
+ //}
+ 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 hints) {
+ try {
+ int[] startEnd = findFinderPattern(row, 0, right);
+ FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd);
+
+ ResultPointCallback resultPointCallback = hints == null ? null :
+ (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+
+ if (resultPointCallback != null) {
+ float center = (startEnd[0] + startEnd[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();
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+ counters[4] = 0;
+ counters[5] = 0;
+ counters[6] = 0;
+ counters[7] = 0;
+
+ if (outsideChar) {
+ recordPatternInReverse(row, pattern.getStartEnd()[0], counters);
+ } else {
+ recordPattern(row, pattern.getStartEnd()[1] + 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 = (float) count(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 = (float) counters[i] / elementWidth;
+ int count = (int) (value + 0.5f); // Round
+ if (count < 1) {
+ count = 1;
+ } else if (count > 8) {
+ count = 8;
+ }
+ int offset = i >> 1;
+ 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, int rowOffset, 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;
+ 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 = count(getOddCounts());
+ int evenSum = count(getEvenCounts());
+ int mismatch = oddSum + evenSum - numModules;
+ boolean oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0);
+ boolean evenParityBad = (evenSum & 0x01) == 1;
+
+ 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;
+ }
+ }
+
+ /*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 */if (mismatch == 1) {
+ if (oddParityBad) {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decrementOdd = true;
+ } else {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decrementEven = true;
+ }
+ } else if (mismatch == -1) {
+ if (oddParityBad) {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ incrementOdd = true;
+ } else {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ incrementEven = true;
+ }
+ } else if (mismatch == 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!
+ }
+ } else {
+ 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());
+ }
+
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/RSSUtils.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/RSSUtils.java
new file mode 100644
index 000000000..f1fd1798c
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/RSSUtils.java
@@ -0,0 +1,159 @@
+/*
+ * 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() {}
+
+ /*
+ static int[] getRSSwidths(int val, int n, int elements, int maxWidth, boolean noNarrow) {
+ int[] widths = new int[elements];
+ int bar;
+ int narrowMask = 0;
+ for (bar = 0; bar < elements - 1; bar++) {
+ narrowMask |= 1 << bar;
+ int elmWidth = 1;
+ int subVal;
+ while (true) {
+ 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;
+ if (val < 0) {
+ break;
+ }
+ elmWidth++;
+ narrowMask &= ~(1 << bar);
+ }
+ val += subVal;
+ n -= elmWidth;
+ widths[bar] = elmWidth;
+ }
+ widths[bar] = n;
+ return widths;
+ }
+ */
+
+ public static int getRSSvalue(int[] widths, int maxWidth, boolean noNarrow) {
+ int elements = widths.length;
+ int n = 0;
+ for (int width : widths) {
+ n += width;
+ }
+ int val = 0;
+ int narrowMask = 0;
+ 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;
+ }
+
+ /*
+ static int[] elements(int[] eDist, int N, int K) {
+ int[] widths = new int[eDist.length + 2];
+ int twoK = K << 1;
+ widths[0] = 1;
+ int i;
+ int minEven = 10;
+ int barSum = 1;
+ for (i = 1; i < twoK - 2; i += 2) {
+ widths[i] = eDist[i - 1] - widths[i - 1];
+ widths[i + 1] = eDist[i] - widths[i];
+ barSum += widths[i] + widths[i + 1];
+ if (widths[i] < minEven) {
+ minEven = widths[i];
+ }
+ }
+ widths[twoK - 1] = N - barSum;
+ if (widths[twoK - 1] < minEven) {
+ minEven = widths[twoK - 1];
+ }
+ if (minEven > 1) {
+ for (i = 0; i < twoK; i += 2) {
+ widths[i] += minEven - 1;
+ widths[i + 1] -= minEven - 1;
+ }
+ }
+ return widths;
+ }
+ */
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/BitArrayBuilder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/BitArrayBuilder.java
new file mode 100644
index 000000000..5fe109c18
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/BitArrayBuilder.java
@@ -0,0 +1,85 @@
+/*
+ * 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 pairs) {
+ int charNumber = (pairs.size() << 1) - 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;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/ExpandedPair.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/ExpandedPair.java
new file mode 100644
index 000000000..8c2a501fa
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/ExpandedPair.java
@@ -0,0 +1,104 @@
+/*
+ * 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;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+final class ExpandedPair {
+
+ private final boolean mayBeLast;
+ private final DataCharacter leftChar;
+ private final DataCharacter rightChar;
+ private final FinderPattern finderPattern;
+
+ ExpandedPair(DataCharacter leftChar,
+ DataCharacter rightChar,
+ FinderPattern finderPattern,
+ boolean mayBeLast) {
+ this.leftChar = leftChar;
+ this.rightChar = rightChar;
+ this.finderPattern = finderPattern;
+ this.mayBeLast = mayBeLast;
+ }
+
+ boolean mayBeLast(){
+ return this.mayBeLast;
+ }
+
+ DataCharacter getLeftChar() {
+ return this.leftChar;
+ }
+
+ DataCharacter getRightChar() {
+ return this.rightChar;
+ }
+
+ FinderPattern getFinderPattern() {
+ return this.finderPattern;
+ }
+
+ public 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
+ equalsOrNull(leftChar, that.leftChar) &&
+ equalsOrNull(rightChar, that.rightChar) &&
+ equalsOrNull(finderPattern, that.finderPattern);
+ }
+
+ private static boolean equalsOrNull(Object o1, Object o2) {
+ return o1 == null ? o2 == null : o1.equals(o2);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashNotNull(leftChar) ^ hashNotNull(rightChar) ^ hashNotNull(finderPattern);
+ }
+
+ private static int hashNotNull(Object o) {
+ return o == null ? 0 : o.hashCode();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/ExpandedRow.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/ExpandedRow.java
new file mode 100644
index 000000000..037328e8f
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/ExpandedRow.java
@@ -0,0 +1,76 @@
+/*
+ * 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 pairs;
+ private final int rowNumber;
+ /** Did this row of the image have to be reversed (mirrored) to recognize the pairs? */
+ private final boolean wasReversed;
+
+ ExpandedRow(List pairs, int rowNumber, boolean wasReversed) {
+ this.pairs = new ArrayList<>(pairs);
+ this.rowNumber = rowNumber;
+ this.wasReversed = wasReversed;
+ }
+
+ List getPairs() {
+ return this.pairs;
+ }
+
+ int getRowNumber() {
+ return this.rowNumber;
+ }
+
+ boolean isReversed() {
+ return this.wasReversed;
+ }
+
+ boolean isEquivalent(List 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.getPairs()) && wasReversed == that.wasReversed;
+ }
+
+ @Override
+ public int hashCode() {
+ return pairs.hashCode() ^ Boolean.valueOf(wasReversed).hashCode();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/RSSExpandedReader.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/RSSExpandedReader.java
new file mode 100644
index 000000000..0d283a8d3
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/RSSExpandedReader.java
@@ -0,0 +1,787 @@
+/*
+ * 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.ResultPoint;
+import com.google.zxing.common.BitArray;
+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.ArrayList;
+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;
+
+ 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 LONGEST_SEQUENCE_SIZE = FINDER_PATTERN_SEQUENCES[FINDER_PATTERN_SEQUENCES.length - 1].length;
+
+ private static final int MAX_PAIRS = 11;
+
+ private final List pairs = new ArrayList<>(MAX_PAIRS);
+ private final List rows = new ArrayList<>();
+ private final int [] startEnd = new int[2];
+ //private final int [] currentSequence = new int[LONGEST_SEQUENCE_SIZE];
+ private boolean startFromEven = false;
+
+ @Override
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ Map 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 {
+ List pairs = decodeRow2pairs(rowNumber, row);
+ return constructResult(pairs);
+ } catch (NotFoundException e) {
+ // OK
+ }
+
+ this.pairs.clear();
+ this.startFromEven = true;
+ List pairs = decodeRow2pairs(rowNumber, row);
+ return constructResult(pairs);
+ }
+
+ @Override
+ public void reset() {
+ this.pairs.clear();
+ this.rows.clear();
+ }
+
+ // Not private for testing
+ List decodeRow2pairs(int rowNumber, BitArray row) throws NotFoundException {
+ try {
+ while (true){
+ ExpandedPair nextPair = retrieveNextPair(row, this.pairs, rowNumber);
+ this.pairs.add(nextPair);
+ //System.out.println(this.pairs.size()+" pairs found so far on row "+rowNumber+": "+this.pairs);
+ // exit this loop when retrieveNextPair() fails and throws
+ }
+ } catch (NotFoundException nfe) {
+ if (this.pairs.isEmpty()) {
+ throw nfe;
+ }
+ }
+
+ // TODO: verify sequence of finder patterns as in checkPairSequence()
+ if (checkChecksum()) {
+ return this.pairs;
+ }
+
+ boolean tryStackedDecode = !this.rows.isEmpty();
+ boolean wasReversed = false; // TODO: deal with reversed rows
+ storeRow(rowNumber, wasReversed);
+ if (tryStackedDecode) {
+ // When the image is 180-rotated, then rows are sorted in wrong dirrection.
+ // Try twice with both the directions.
+ List ps = checkRows(false);
+ if (ps != null) {
+ return ps;
+ }
+ ps = checkRows(true);
+ if (ps != null) {
+ return ps;
+ }
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private List 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 resonable 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 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 checkRows(List collectedRows, int currentRow) throws NotFoundException {
+ for (int i = currentRow; i < rows.size(); i++) {
+ ExpandedRow row = rows.get(i);
+ this.pairs.clear();
+ int size = collectedRows.size();
+ for (int j = 0; j < size; j++) {
+ this.pairs.addAll(collectedRows.get(j).getPairs());
+ }
+ this.pairs.addAll(row.getPairs());
+
+ if (!isValidSequence(this.pairs)) {
+ continue;
+ }
+
+ if (checkChecksum()) {
+ return this.pairs;
+ }
+
+ List rs = new ArrayList<>();
+ rs.addAll(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 seqience,
+ // either complete or a prefix
+ private static boolean isValidSequence(List pairs) {
+ for (int[] sequence : FINDER_PATTERN_SEQUENCES) {
+ if (pairs.size() > sequence.length) {
+ continue;
+ }
+
+ 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, boolean wasReversed) {
+ // 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 allready detected row
+ if (isPartialRow(this.pairs, this.rows)) {
+ return;
+ }
+
+ this.rows.add(insertPos, new ExpandedRow(this.pairs, rowNumber, wasReversed));
+
+ removePartialRows(this.pairs, this.rows);
+ }
+
+ // Remove all the rows that contains only specified pairs
+ private static void removePartialRows(List pairs, List rows) {
+ for (Iterator iterator = rows.iterator(); iterator.hasNext(); ) {
+ ExpandedRow r = iterator.next();
+ if (r.getPairs().size() == pairs.size()) {
+ continue;
+ }
+ boolean allFound = true;
+ for (ExpandedPair p : r.getPairs()) {
+ boolean found = false;
+ for (ExpandedPair pp : pairs) {
+ if (p.equals(pp)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ 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 pairs, Iterable 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 getRows() {
+ return this.rows;
+ }
+
+ // Not private for unit testing
+ static Result constructResult(List 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();
+
+ return new Result(
+ resultingString,
+ null,
+ new ResultPoint[]{firstPoints[0], firstPoints[1], lastPoints[0], lastPoints[1]},
+ BarcodeFormat.RSS_EXPANDED
+ );
+ }
+
+ 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 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;
+ }
+ boolean mayBeLast = true;
+ return new ExpandedPair(leftChar, rightChar, pattern, mayBeLast);
+ }
+
+ private void findNextPair(BitArray row, List 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();
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+ counters[4] = 0;
+ counters[5] = 0;
+ counters[6] = 0;
+ counters[7] = 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 = (float) count(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 >> 1;
+ 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;
+ //int evenSum = 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;
+ }
+ //evenSum += evenCounts[i];
+ }
+ 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 = count(this.getOddCounts());
+ int evenSum = count(this.getEvenCounts());
+ int mismatch = oddSum + evenSum - numModules;
+ boolean oddParityBad = (oddSum & 0x01) == 1;
+ boolean evenParityBad = (evenSum & 0x01) == 0;
+
+ 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;
+ }
+
+ if (mismatch == 1) {
+ if (oddParityBad) {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decrementOdd = true;
+ } else {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decrementEven = true;
+ }
+ } else if (mismatch == -1) {
+ if (oddParityBad) {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ incrementOdd = true;
+ } else {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ incrementEven = true;
+ }
+ } else if (mismatch == 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!
+ }
+ } else {
+ 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());
+ }
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013103decoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013103decoder.java
new file mode 100644
index 000000000..759cf96e0
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013103decoder.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01320xDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01320xDecoder.java
new file mode 100644
index 000000000..f4e807e04
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01320xDecoder.java
@@ -0,0 +1,57 @@
+/*
+ * 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;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01392xDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01392xDecoder.java
new file mode 100644
index 000000000..9ea5bba66
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01392xDecoder.java
@@ -0,0 +1,68 @@
+/*
+ * 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();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01393xDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01393xDecoder.java
new file mode 100644
index 000000000..649da3f37
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01393xDecoder.java
@@ -0,0 +1,78 @@
+/*
+ * 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();
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013x0x1xDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013x0x1xDecoder.java
new file mode 100644
index 000000000..9bcf3e8b3
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013x0x1xDecoder.java
@@ -0,0 +1,109 @@
+/*
+ * 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) {
+ int lastAI = weight / 100000;
+ buf.append('(');
+ buf.append(this.firstAIdigits);
+ buf.append(lastAI);
+ buf.append(')');
+ }
+
+ @Override
+ protected int checkWeight(int weight) {
+ return weight % 100000;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013x0xDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013x0xDecoder.java
new file mode 100644
index 000000000..54d32d6c9
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI013x0xDecoder.java
@@ -0,0 +1,57 @@
+/*
+ * 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();
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01AndOtherAIs.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01AndOtherAIs.java
new file mode 100644
index 000000000..27222a382
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01AndOtherAIs.java
@@ -0,0 +1,58 @@
+/*
+ * 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);
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01decoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01decoder.java
new file mode 100644
index 000000000..49bc9f9b1
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01decoder.java
@@ -0,0 +1,81 @@
+/*
+ * 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 {
+
+ protected static final int GTIN_SIZE = 40;
+
+ AI01decoder(BitArray information) {
+ super(information);
+ }
+
+ protected final void encodeCompressedGtin(StringBuilder buf, int currentPos) {
+ buf.append("(01)");
+ int initialPosition = buf.length();
+ buf.append('9');
+
+ encodeCompressedGtinWithoutAI(buf, currentPos, initialPosition);
+ }
+
+ protected 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);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01weightDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01weightDecoder.java
new file mode 100644
index 000000000..357a13dcb
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AI01weightDecoder.java
@@ -0,0 +1,60 @@
+/*
+ * 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);
+ }
+
+ protected 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);
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AbstractExpandedDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AbstractExpandedDecoder.java
new file mode 100644
index 000000000..bdb7e1085
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AbstractExpandedDecoder.java
@@ -0,0 +1,93 @@
+/*
+ * 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);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AnyAIDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AnyAIDecoder.java
new file mode 100644
index 000000000..2074d8f0b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/AnyAIDecoder.java
@@ -0,0 +1,50 @@
+/*
+ * 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);
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/BlockParsedResult.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/BlockParsedResult.java
new file mode 100644
index 000000000..a9593553e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/BlockParsedResult.java
@@ -0,0 +1,54 @@
+/*
+ * 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(boolean finished) {
+ this(null, finished);
+ }
+
+ BlockParsedResult(DecodedInformation information, boolean finished) {
+ this.finished = finished;
+ this.decodedInformation = information;
+ }
+
+ DecodedInformation getDecodedInformation() {
+ return this.decodedInformation;
+ }
+
+ boolean isFinished() {
+ return this.finished;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/CurrentParsingState.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/CurrentParsingState.java
new file mode 100644
index 000000000..463cee2bb
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/CurrentParsingState.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedChar.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedChar.java
new file mode 100644
index 000000000..790b6846e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedChar.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedInformation.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedInformation.java
new file mode 100644
index 000000000..cc2522592
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedInformation.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedNumeric.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedNumeric.java
new file mode 100644
index 000000000..29a7e6721
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedNumeric.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+ }
+
+ boolean isAnyFNC1(){
+ return this.firstDigit == FNC1 || this.secondDigit == FNC1;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedObject.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedObject.java
new file mode 100644
index 000000000..ea667aa5e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/DecodedObject.java
@@ -0,0 +1,44 @@
+/*
+ * 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;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/FieldParser.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/FieldParser.java
new file mode 100644
index 000000000..8de540b3d
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/FieldParser.java
@@ -0,0 +1,291 @@
+/*
+ * 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;
+
+/**
+ * @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 Object VARIABLE_LENGTH = new Object();
+
+ private static final Object [][] TWO_DIGIT_DATA_LENGTH = {
+ // "DIGITS", new Integer(LENGTH)
+ // or
+ // "DIGITS", VARIABLE_LENGTH, new Integer(MAX_SIZE)
+
+ { "00", 18},
+ { "01", 14},
+ { "02", 14},
+
+ { "10", VARIABLE_LENGTH, 20},
+ { "11", 6},
+ { "12", 6},
+ { "13", 6},
+ { "15", 6},
+ { "17", 6},
+
+ { "20", 2},
+ { "21", VARIABLE_LENGTH, 20},
+ { "22", VARIABLE_LENGTH, 29},
+
+ { "30", VARIABLE_LENGTH, 8},
+ { "37", VARIABLE_LENGTH, 8},
+
+ //internal company codes
+ { "90", VARIABLE_LENGTH, 30},
+ { "91", VARIABLE_LENGTH, 30},
+ { "92", VARIABLE_LENGTH, 30},
+ { "93", VARIABLE_LENGTH, 30},
+ { "94", VARIABLE_LENGTH, 30},
+ { "95", VARIABLE_LENGTH, 30},
+ { "96", VARIABLE_LENGTH, 30},
+ { "97", VARIABLE_LENGTH, 30},
+ { "98", VARIABLE_LENGTH, 30},
+ { "99", VARIABLE_LENGTH, 30},
+ };
+
+ private static final Object [][] THREE_DIGIT_DATA_LENGTH = {
+ // Same format as above
+
+ { "240", VARIABLE_LENGTH, 30},
+ { "241", VARIABLE_LENGTH, 30},
+ { "242", VARIABLE_LENGTH, 6},
+ { "250", VARIABLE_LENGTH, 30},
+ { "251", VARIABLE_LENGTH, 30},
+ { "253", VARIABLE_LENGTH, 17},
+ { "254", VARIABLE_LENGTH, 20},
+
+ { "400", VARIABLE_LENGTH, 30},
+ { "401", VARIABLE_LENGTH, 30},
+ { "402", 17},
+ { "403", VARIABLE_LENGTH, 30},
+ { "410", 13},
+ { "411", 13},
+ { "412", 13},
+ { "413", 13},
+ { "414", 13},
+ { "420", VARIABLE_LENGTH, 20},
+ { "421", VARIABLE_LENGTH, 15},
+ { "422", 3},
+ { "423", VARIABLE_LENGTH, 15},
+ { "424", 3},
+ { "425", 3},
+ { "426", 3},
+ };
+
+ private static final Object [][] THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH = {
+ // Same format as above
+
+ { "310", 6},
+ { "311", 6},
+ { "312", 6},
+ { "313", 6},
+ { "314", 6},
+ { "315", 6},
+ { "316", 6},
+ { "320", 6},
+ { "321", 6},
+ { "322", 6},
+ { "323", 6},
+ { "324", 6},
+ { "325", 6},
+ { "326", 6},
+ { "327", 6},
+ { "328", 6},
+ { "329", 6},
+ { "330", 6},
+ { "331", 6},
+ { "332", 6},
+ { "333", 6},
+ { "334", 6},
+ { "335", 6},
+ { "336", 6},
+ { "340", 6},
+ { "341", 6},
+ { "342", 6},
+ { "343", 6},
+ { "344", 6},
+ { "345", 6},
+ { "346", 6},
+ { "347", 6},
+ { "348", 6},
+ { "349", 6},
+ { "350", 6},
+ { "351", 6},
+ { "352", 6},
+ { "353", 6},
+ { "354", 6},
+ { "355", 6},
+ { "356", 6},
+ { "357", 6},
+ { "360", 6},
+ { "361", 6},
+ { "362", 6},
+ { "363", 6},
+ { "364", 6},
+ { "365", 6},
+ { "366", 6},
+ { "367", 6},
+ { "368", 6},
+ { "369", 6},
+ { "390", VARIABLE_LENGTH, 15},
+ { "391", VARIABLE_LENGTH, 18},
+ { "392", VARIABLE_LENGTH, 15},
+ { "393", VARIABLE_LENGTH, 18},
+ { "703", VARIABLE_LENGTH, 30}
+ };
+
+ private static final Object [][] FOUR_DIGIT_DATA_LENGTH = {
+ // Same format as above
+
+ { "7001", 13},
+ { "7002", VARIABLE_LENGTH, 30},
+ { "7003", 10},
+
+ { "8001", 14},
+ { "8002", VARIABLE_LENGTH, 20},
+ { "8003", VARIABLE_LENGTH, 30},
+ { "8004", VARIABLE_LENGTH, 30},
+ { "8005", 6},
+ { "8006", 18},
+ { "8007", VARIABLE_LENGTH, 30},
+ { "8008", VARIABLE_LENGTH, 12},
+ { "8018", 18},
+ { "8020", VARIABLE_LENGTH, 25},
+ { "8100", 6},
+ { "8101", 10},
+ { "8102", 2},
+ { "8110", VARIABLE_LENGTH, 70},
+ { "8200", VARIABLE_LENGTH, 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();
+ }
+
+ String firstTwoDigits = rawInformation.substring(0, 2);
+
+ for (Object[] dataLength : TWO_DIGIT_DATA_LENGTH) {
+ if (dataLength[0].equals(firstTwoDigits)) {
+ if (dataLength[1] == VARIABLE_LENGTH) {
+ return processVariableAI(2, (Integer) dataLength[2], rawInformation);
+ }
+ return processFixedAI(2, (Integer) dataLength[1], rawInformation);
+ }
+ }
+
+ if(rawInformation.length() < 3) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String firstThreeDigits = rawInformation.substring(0, 3);
+
+ for (Object[] dataLength : THREE_DIGIT_DATA_LENGTH) {
+ if (dataLength[0].equals(firstThreeDigits)) {
+ if (dataLength[1] == VARIABLE_LENGTH) {
+ return processVariableAI(3, (Integer) dataLength[2], rawInformation);
+ }
+ return processFixedAI(3, (Integer) dataLength[1], rawInformation);
+ }
+ }
+
+
+ for (Object[] dataLength : THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH) {
+ if (dataLength[0].equals(firstThreeDigits)) {
+ if (dataLength[1] == VARIABLE_LENGTH) {
+ return processVariableAI(4, (Integer) dataLength[2], rawInformation);
+ }
+ return processFixedAI(4, (Integer) dataLength[1], rawInformation);
+ }
+ }
+
+ if(rawInformation.length() < 4) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String firstFourDigits = rawInformation.substring(0, 4);
+
+ for (Object[] dataLength : FOUR_DIGIT_DATA_LENGTH) {
+ if (dataLength[0].equals(firstFourDigits)) {
+ if (dataLength[1] == VARIABLE_LENGTH) {
+ return processVariableAI(4, (Integer) dataLength[2], rawInformation);
+ }
+ return processFixedAI(4, (Integer) dataLength[1], 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;
+ if (rawInformation.length() < aiSize + variableFieldSize) {
+ maxSize = rawInformation.length();
+ } else {
+ maxSize = 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;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/GeneralAppIdDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/GeneralAppIdDecoder.java
new file mode 100644
index 000000000..916a708e8
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/oned/rss/expanded/decoders/GeneralAppIdDecoder.java
@@ -0,0 +1,469 @@
+/*
+ * 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(false);
+ }
+
+ 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(false);
+ }
+
+ 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(false);
+ }
+
+ 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;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Common.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Common.java
new file mode 100644
index 000000000..a784ef859
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Common.java
@@ -0,0 +1,482 @@
+/*
+ * 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;
+
+import java.util.Collection;
+
+/**
+ * @author SITA Lab (kevin.osullivan@sita.aero)
+ * @author Guenther Grau
+ */
+public final class PDF417Common {
+
+ public static final int NUMBER_OF_CODEWORDS = 929;
+ // Maximum Codewords (Data + Error).
+ public static final int MAX_CODEWORDS_IN_BARCODE = NUMBER_OF_CODEWORDS - 1;
+ public static final int MIN_ROWS_IN_BARCODE = 3;
+ public static final int MAX_ROWS_IN_BARCODE = 90;
+ // One left row indication column + max 30 data columns + one right row indicator column
+ //public static final int MAX_CODEWORDS_IN_ROW = 32;
+ public static final int MODULES_IN_CODEWORD = 17;
+ public static final int MODULES_IN_STOP_PATTERN = 18;
+ public static final int BARS_IN_MODULE = 8;
+
+ private static final int[] EMPTY_INT_ARRAY = {};
+
+ private PDF417Common() {
+ }
+
+ public static int getBitCountSum(int[] moduleBitCount) {
+ int bitCountSum = 0;
+ for (int count : moduleBitCount) {
+ bitCountSum += count;
+ }
+ return bitCountSum;
+ }
+
+ public static int[] toIntArray(Collection list) {
+ if (list == null || list.isEmpty()) {
+ return EMPTY_INT_ARRAY;
+ }
+ int[] result = new int[list.size()];
+ int i = 0;
+ for (Integer integer : list) {
+ result[i++] = integer;
+ }
+ return result;
+ }
+
+ /**
+ * @param symbol encoded symbol to translate to a codeword
+ * @return the codeword corresponding to the symbol.
+ */
+ public static int getCodeword(long symbol) {
+ long sym = symbol & 0x3FFFF;
+ int i = findCodewordIndex(sym);
+ if (i == -1) {
+ return -1;
+ }
+ return (CODEWORD_TABLE[i] - 1) % NUMBER_OF_CODEWORDS;
+ }
+
+ /**
+ * Use a binary search to find the index of the codeword corresponding to
+ * this symbol.
+ *
+ * @param symbol the symbol from the barcode.
+ * @return the index into the codeword table.
+ */
+ private static int findCodewordIndex(long symbol) {
+ int first = 0;
+ int upto = SYMBOL_TABLE.length;
+ while (first < upto) {
+ int mid = (first + upto) >>> 1; // Compute mid point.
+ if (symbol < SYMBOL_TABLE[mid]) {
+ upto = mid; // continue search in bottom half.
+ } else if (symbol > SYMBOL_TABLE[mid]) {
+ first = mid + 1; // continue search in top half.
+ } else {
+ return mid; // Found it. return position
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * The sorted table of all possible symbols. Extracted from the PDF417
+ * specification. The index of a symbol in this table corresponds to the
+ * index into the codeword table.
+ */
+ public static final int[] SYMBOL_TABLE = {
+ 0x1025e, 0x1027a, 0x1029e, 0x102bc, 0x102f2, 0x102f4, 0x1032e, 0x1034e, 0x1035c, 0x10396, 0x103a6, 0x103ac,
+ 0x10422, 0x10428, 0x10436, 0x10442, 0x10444, 0x10448, 0x10450, 0x1045e, 0x10466, 0x1046c, 0x1047a, 0x10482,
+ 0x1049e, 0x104a0, 0x104bc, 0x104c6, 0x104d8, 0x104ee, 0x104f2, 0x104f4, 0x10504, 0x10508, 0x10510, 0x1051e,
+ 0x10520, 0x1053c, 0x10540, 0x10578, 0x10586, 0x1058c, 0x10598, 0x105b0, 0x105be, 0x105ce, 0x105dc, 0x105e2,
+ 0x105e4, 0x105e8, 0x105f6, 0x1062e, 0x1064e, 0x1065c, 0x1068e, 0x1069c, 0x106b8, 0x106de, 0x106fa, 0x10716,
+ 0x10726, 0x1072c, 0x10746, 0x1074c, 0x10758, 0x1076e, 0x10792, 0x10794, 0x107a2, 0x107a4, 0x107a8, 0x107b6,
+ 0x10822, 0x10828, 0x10842, 0x10848, 0x10850, 0x1085e, 0x10866, 0x1086c, 0x1087a, 0x10882, 0x10884, 0x10890,
+ 0x1089e, 0x108a0, 0x108bc, 0x108c6, 0x108cc, 0x108d8, 0x108ee, 0x108f2, 0x108f4, 0x10902, 0x10908, 0x1091e,
+ 0x10920, 0x1093c, 0x10940, 0x10978, 0x10986, 0x10998, 0x109b0, 0x109be, 0x109ce, 0x109dc, 0x109e2, 0x109e4,
+ 0x109e8, 0x109f6, 0x10a08, 0x10a10, 0x10a1e, 0x10a20, 0x10a3c, 0x10a40, 0x10a78, 0x10af0, 0x10b06, 0x10b0c,
+ 0x10b18, 0x10b30, 0x10b3e, 0x10b60, 0x10b7c, 0x10b8e, 0x10b9c, 0x10bb8, 0x10bc2, 0x10bc4, 0x10bc8, 0x10bd0,
+ 0x10bde, 0x10be6, 0x10bec, 0x10c2e, 0x10c4e, 0x10c5c, 0x10c62, 0x10c64, 0x10c68, 0x10c76, 0x10c8e, 0x10c9c,
+ 0x10cb8, 0x10cc2, 0x10cc4, 0x10cc8, 0x10cd0, 0x10cde, 0x10ce6, 0x10cec, 0x10cfa, 0x10d0e, 0x10d1c, 0x10d38,
+ 0x10d70, 0x10d7e, 0x10d82, 0x10d84, 0x10d88, 0x10d90, 0x10d9e, 0x10da0, 0x10dbc, 0x10dc6, 0x10dcc, 0x10dd8,
+ 0x10dee, 0x10df2, 0x10df4, 0x10e16, 0x10e26, 0x10e2c, 0x10e46, 0x10e58, 0x10e6e, 0x10e86, 0x10e8c, 0x10e98,
+ 0x10eb0, 0x10ebe, 0x10ece, 0x10edc, 0x10f0a, 0x10f12, 0x10f14, 0x10f22, 0x10f28, 0x10f36, 0x10f42, 0x10f44,
+ 0x10f48, 0x10f50, 0x10f5e, 0x10f66, 0x10f6c, 0x10fb2, 0x10fb4, 0x11022, 0x11028, 0x11042, 0x11048, 0x11050,
+ 0x1105e, 0x1107a, 0x11082, 0x11084, 0x11090, 0x1109e, 0x110a0, 0x110bc, 0x110c6, 0x110cc, 0x110d8, 0x110ee,
+ 0x110f2, 0x110f4, 0x11102, 0x1111e, 0x11120, 0x1113c, 0x11140, 0x11178, 0x11186, 0x11198, 0x111b0, 0x111be,
+ 0x111ce, 0x111dc, 0x111e2, 0x111e4, 0x111e8, 0x111f6, 0x11208, 0x1121e, 0x11220, 0x11278, 0x112f0, 0x1130c,
+ 0x11330, 0x1133e, 0x11360, 0x1137c, 0x1138e, 0x1139c, 0x113b8, 0x113c2, 0x113c8, 0x113d0, 0x113de, 0x113e6,
+ 0x113ec, 0x11408, 0x11410, 0x1141e, 0x11420, 0x1143c, 0x11440, 0x11478, 0x114f0, 0x115e0, 0x1160c, 0x11618,
+ 0x11630, 0x1163e, 0x11660, 0x1167c, 0x116c0, 0x116f8, 0x1171c, 0x11738, 0x11770, 0x1177e, 0x11782, 0x11784,
+ 0x11788, 0x11790, 0x1179e, 0x117a0, 0x117bc, 0x117c6, 0x117cc, 0x117d8, 0x117ee, 0x1182e, 0x11834, 0x1184e,
+ 0x1185c, 0x11862, 0x11864, 0x11868, 0x11876, 0x1188e, 0x1189c, 0x118b8, 0x118c2, 0x118c8, 0x118d0, 0x118de,
+ 0x118e6, 0x118ec, 0x118fa, 0x1190e, 0x1191c, 0x11938, 0x11970, 0x1197e, 0x11982, 0x11984, 0x11990, 0x1199e,
+ 0x119a0, 0x119bc, 0x119c6, 0x119cc, 0x119d8, 0x119ee, 0x119f2, 0x119f4, 0x11a0e, 0x11a1c, 0x11a38, 0x11a70,
+ 0x11a7e, 0x11ae0, 0x11afc, 0x11b08, 0x11b10, 0x11b1e, 0x11b20, 0x11b3c, 0x11b40, 0x11b78, 0x11b8c, 0x11b98,
+ 0x11bb0, 0x11bbe, 0x11bce, 0x11bdc, 0x11be2, 0x11be4, 0x11be8, 0x11bf6, 0x11c16, 0x11c26, 0x11c2c, 0x11c46,
+ 0x11c4c, 0x11c58, 0x11c6e, 0x11c86, 0x11c98, 0x11cb0, 0x11cbe, 0x11cce, 0x11cdc, 0x11ce2, 0x11ce4, 0x11ce8,
+ 0x11cf6, 0x11d06, 0x11d0c, 0x11d18, 0x11d30, 0x11d3e, 0x11d60, 0x11d7c, 0x11d8e, 0x11d9c, 0x11db8, 0x11dc4,
+ 0x11dc8, 0x11dd0, 0x11dde, 0x11de6, 0x11dec, 0x11dfa, 0x11e0a, 0x11e12, 0x11e14, 0x11e22, 0x11e24, 0x11e28,
+ 0x11e36, 0x11e42, 0x11e44, 0x11e50, 0x11e5e, 0x11e66, 0x11e6c, 0x11e82, 0x11e84, 0x11e88, 0x11e90, 0x11e9e,
+ 0x11ea0, 0x11ebc, 0x11ec6, 0x11ecc, 0x11ed8, 0x11eee, 0x11f1a, 0x11f2e, 0x11f32, 0x11f34, 0x11f4e, 0x11f5c,
+ 0x11f62, 0x11f64, 0x11f68, 0x11f76, 0x12048, 0x1205e, 0x12082, 0x12084, 0x12090, 0x1209e, 0x120a0, 0x120bc,
+ 0x120d8, 0x120f2, 0x120f4, 0x12108, 0x1211e, 0x12120, 0x1213c, 0x12140, 0x12178, 0x12186, 0x12198, 0x121b0,
+ 0x121be, 0x121e2, 0x121e4, 0x121e8, 0x121f6, 0x12204, 0x12210, 0x1221e, 0x12220, 0x12278, 0x122f0, 0x12306,
+ 0x1230c, 0x12330, 0x1233e, 0x12360, 0x1237c, 0x1238e, 0x1239c, 0x123b8, 0x123c2, 0x123c8, 0x123d0, 0x123e6,
+ 0x123ec, 0x1241e, 0x12420, 0x1243c, 0x124f0, 0x125e0, 0x12618, 0x1263e, 0x12660, 0x1267c, 0x126c0, 0x126f8,
+ 0x12738, 0x12770, 0x1277e, 0x12782, 0x12784, 0x12790, 0x1279e, 0x127a0, 0x127bc, 0x127c6, 0x127cc, 0x127d8,
+ 0x127ee, 0x12820, 0x1283c, 0x12840, 0x12878, 0x128f0, 0x129e0, 0x12bc0, 0x12c18, 0x12c30, 0x12c3e, 0x12c60,
+ 0x12c7c, 0x12cc0, 0x12cf8, 0x12df0, 0x12e1c, 0x12e38, 0x12e70, 0x12e7e, 0x12ee0, 0x12efc, 0x12f04, 0x12f08,
+ 0x12f10, 0x12f20, 0x12f3c, 0x12f40, 0x12f78, 0x12f86, 0x12f8c, 0x12f98, 0x12fb0, 0x12fbe, 0x12fce, 0x12fdc,
+ 0x1302e, 0x1304e, 0x1305c, 0x13062, 0x13068, 0x1308e, 0x1309c, 0x130b8, 0x130c2, 0x130c8, 0x130d0, 0x130de,
+ 0x130ec, 0x130fa, 0x1310e, 0x13138, 0x13170, 0x1317e, 0x13182, 0x13184, 0x13190, 0x1319e, 0x131a0, 0x131bc,
+ 0x131c6, 0x131cc, 0x131d8, 0x131f2, 0x131f4, 0x1320e, 0x1321c, 0x13270, 0x1327e, 0x132e0, 0x132fc, 0x13308,
+ 0x1331e, 0x13320, 0x1333c, 0x13340, 0x13378, 0x13386, 0x13398, 0x133b0, 0x133be, 0x133ce, 0x133dc, 0x133e2,
+ 0x133e4, 0x133e8, 0x133f6, 0x1340e, 0x1341c, 0x13438, 0x13470, 0x1347e, 0x134e0, 0x134fc, 0x135c0, 0x135f8,
+ 0x13608, 0x13610, 0x1361e, 0x13620, 0x1363c, 0x13640, 0x13678, 0x136f0, 0x1370c, 0x13718, 0x13730, 0x1373e,
+ 0x13760, 0x1377c, 0x1379c, 0x137b8, 0x137c2, 0x137c4, 0x137c8, 0x137d0, 0x137de, 0x137e6, 0x137ec, 0x13816,
+ 0x13826, 0x1382c, 0x13846, 0x1384c, 0x13858, 0x1386e, 0x13874, 0x13886, 0x13898, 0x138b0, 0x138be, 0x138ce,
+ 0x138dc, 0x138e2, 0x138e4, 0x138e8, 0x13906, 0x1390c, 0x13930, 0x1393e, 0x13960, 0x1397c, 0x1398e, 0x1399c,
+ 0x139b8, 0x139c8, 0x139d0, 0x139de, 0x139e6, 0x139ec, 0x139fa, 0x13a06, 0x13a0c, 0x13a18, 0x13a30, 0x13a3e,
+ 0x13a60, 0x13a7c, 0x13ac0, 0x13af8, 0x13b0e, 0x13b1c, 0x13b38, 0x13b70, 0x13b7e, 0x13b88, 0x13b90, 0x13b9e,
+ 0x13ba0, 0x13bbc, 0x13bcc, 0x13bd8, 0x13bee, 0x13bf2, 0x13bf4, 0x13c12, 0x13c14, 0x13c22, 0x13c24, 0x13c28,
+ 0x13c36, 0x13c42, 0x13c48, 0x13c50, 0x13c5e, 0x13c66, 0x13c6c, 0x13c82, 0x13c84, 0x13c90, 0x13c9e, 0x13ca0,
+ 0x13cbc, 0x13cc6, 0x13ccc, 0x13cd8, 0x13cee, 0x13d02, 0x13d04, 0x13d08, 0x13d10, 0x13d1e, 0x13d20, 0x13d3c,
+ 0x13d40, 0x13d78, 0x13d86, 0x13d8c, 0x13d98, 0x13db0, 0x13dbe, 0x13dce, 0x13ddc, 0x13de4, 0x13de8, 0x13df6,
+ 0x13e1a, 0x13e2e, 0x13e32, 0x13e34, 0x13e4e, 0x13e5c, 0x13e62, 0x13e64, 0x13e68, 0x13e76, 0x13e8e, 0x13e9c,
+ 0x13eb8, 0x13ec2, 0x13ec4, 0x13ec8, 0x13ed0, 0x13ede, 0x13ee6, 0x13eec, 0x13f26, 0x13f2c, 0x13f3a, 0x13f46,
+ 0x13f4c, 0x13f58, 0x13f6e, 0x13f72, 0x13f74, 0x14082, 0x1409e, 0x140a0, 0x140bc, 0x14104, 0x14108, 0x14110,
+ 0x1411e, 0x14120, 0x1413c, 0x14140, 0x14178, 0x1418c, 0x14198, 0x141b0, 0x141be, 0x141e2, 0x141e4, 0x141e8,
+ 0x14208, 0x14210, 0x1421e, 0x14220, 0x1423c, 0x14240, 0x14278, 0x142f0, 0x14306, 0x1430c, 0x14318, 0x14330,
+ 0x1433e, 0x14360, 0x1437c, 0x1438e, 0x143c2, 0x143c4, 0x143c8, 0x143d0, 0x143e6, 0x143ec, 0x14408, 0x14410,
+ 0x1441e, 0x14420, 0x1443c, 0x14440, 0x14478, 0x144f0, 0x145e0, 0x1460c, 0x14618, 0x14630, 0x1463e, 0x14660,
+ 0x1467c, 0x146c0, 0x146f8, 0x1471c, 0x14738, 0x14770, 0x1477e, 0x14782, 0x14784, 0x14788, 0x14790, 0x147a0,
+ 0x147bc, 0x147c6, 0x147cc, 0x147d8, 0x147ee, 0x14810, 0x14820, 0x1483c, 0x14840, 0x14878, 0x148f0, 0x149e0,
+ 0x14bc0, 0x14c30, 0x14c3e, 0x14c60, 0x14c7c, 0x14cc0, 0x14cf8, 0x14df0, 0x14e38, 0x14e70, 0x14e7e, 0x14ee0,
+ 0x14efc, 0x14f04, 0x14f08, 0x14f10, 0x14f1e, 0x14f20, 0x14f3c, 0x14f40, 0x14f78, 0x14f86, 0x14f8c, 0x14f98,
+ 0x14fb0, 0x14fce, 0x14fdc, 0x15020, 0x15040, 0x15078, 0x150f0, 0x151e0, 0x153c0, 0x15860, 0x1587c, 0x158c0,
+ 0x158f8, 0x159f0, 0x15be0, 0x15c70, 0x15c7e, 0x15ce0, 0x15cfc, 0x15dc0, 0x15df8, 0x15e08, 0x15e10, 0x15e20,
+ 0x15e40, 0x15e78, 0x15ef0, 0x15f0c, 0x15f18, 0x15f30, 0x15f60, 0x15f7c, 0x15f8e, 0x15f9c, 0x15fb8, 0x1604e,
+ 0x1605c, 0x1608e, 0x1609c, 0x160b8, 0x160c2, 0x160c4, 0x160c8, 0x160de, 0x1610e, 0x1611c, 0x16138, 0x16170,
+ 0x1617e, 0x16184, 0x16188, 0x16190, 0x1619e, 0x161a0, 0x161bc, 0x161c6, 0x161cc, 0x161d8, 0x161f2, 0x161f4,
+ 0x1620e, 0x1621c, 0x16238, 0x16270, 0x1627e, 0x162e0, 0x162fc, 0x16304, 0x16308, 0x16310, 0x1631e, 0x16320,
+ 0x1633c, 0x16340, 0x16378, 0x16386, 0x1638c, 0x16398, 0x163b0, 0x163be, 0x163ce, 0x163dc, 0x163e2, 0x163e4,
+ 0x163e8, 0x163f6, 0x1640e, 0x1641c, 0x16438, 0x16470, 0x1647e, 0x164e0, 0x164fc, 0x165c0, 0x165f8, 0x16610,
+ 0x1661e, 0x16620, 0x1663c, 0x16640, 0x16678, 0x166f0, 0x16718, 0x16730, 0x1673e, 0x16760, 0x1677c, 0x1678e,
+ 0x1679c, 0x167b8, 0x167c2, 0x167c4, 0x167c8, 0x167d0, 0x167de, 0x167e6, 0x167ec, 0x1681c, 0x16838, 0x16870,
+ 0x168e0, 0x168fc, 0x169c0, 0x169f8, 0x16bf0, 0x16c10, 0x16c1e, 0x16c20, 0x16c3c, 0x16c40, 0x16c78, 0x16cf0,
+ 0x16de0, 0x16e18, 0x16e30, 0x16e3e, 0x16e60, 0x16e7c, 0x16ec0, 0x16ef8, 0x16f1c, 0x16f38, 0x16f70, 0x16f7e,
+ 0x16f84, 0x16f88, 0x16f90, 0x16f9e, 0x16fa0, 0x16fbc, 0x16fc6, 0x16fcc, 0x16fd8, 0x17026, 0x1702c, 0x17046,
+ 0x1704c, 0x17058, 0x1706e, 0x17086, 0x1708c, 0x17098, 0x170b0, 0x170be, 0x170ce, 0x170dc, 0x170e8, 0x17106,
+ 0x1710c, 0x17118, 0x17130, 0x1713e, 0x17160, 0x1717c, 0x1718e, 0x1719c, 0x171b8, 0x171c2, 0x171c4, 0x171c8,
+ 0x171d0, 0x171de, 0x171e6, 0x171ec, 0x171fa, 0x17206, 0x1720c, 0x17218, 0x17230, 0x1723e, 0x17260, 0x1727c,
+ 0x172c0, 0x172f8, 0x1730e, 0x1731c, 0x17338, 0x17370, 0x1737e, 0x17388, 0x17390, 0x1739e, 0x173a0, 0x173bc,
+ 0x173cc, 0x173d8, 0x173ee, 0x173f2, 0x173f4, 0x1740c, 0x17418, 0x17430, 0x1743e, 0x17460, 0x1747c, 0x174c0,
+ 0x174f8, 0x175f0, 0x1760e, 0x1761c, 0x17638, 0x17670, 0x1767e, 0x176e0, 0x176fc, 0x17708, 0x17710, 0x1771e,
+ 0x17720, 0x1773c, 0x17740, 0x17778, 0x17798, 0x177b0, 0x177be, 0x177dc, 0x177e2, 0x177e4, 0x177e8, 0x17822,
+ 0x17824, 0x17828, 0x17836, 0x17842, 0x17844, 0x17848, 0x17850, 0x1785e, 0x17866, 0x1786c, 0x17882, 0x17884,
+ 0x17888, 0x17890, 0x1789e, 0x178a0, 0x178bc, 0x178c6, 0x178cc, 0x178d8, 0x178ee, 0x178f2, 0x178f4, 0x17902,
+ 0x17904, 0x17908, 0x17910, 0x1791e, 0x17920, 0x1793c, 0x17940, 0x17978, 0x17986, 0x1798c, 0x17998, 0x179b0,
+ 0x179be, 0x179ce, 0x179dc, 0x179e2, 0x179e4, 0x179e8, 0x179f6, 0x17a04, 0x17a08, 0x17a10, 0x17a1e, 0x17a20,
+ 0x17a3c, 0x17a40, 0x17a78, 0x17af0, 0x17b06, 0x17b0c, 0x17b18, 0x17b30, 0x17b3e, 0x17b60, 0x17b7c, 0x17b8e,
+ 0x17b9c, 0x17bb8, 0x17bc4, 0x17bc8, 0x17bd0, 0x17bde, 0x17be6, 0x17bec, 0x17c2e, 0x17c32, 0x17c34, 0x17c4e,
+ 0x17c5c, 0x17c62, 0x17c64, 0x17c68, 0x17c76, 0x17c8e, 0x17c9c, 0x17cb8, 0x17cc2, 0x17cc4, 0x17cc8, 0x17cd0,
+ 0x17cde, 0x17ce6, 0x17cec, 0x17d0e, 0x17d1c, 0x17d38, 0x17d70, 0x17d82, 0x17d84, 0x17d88, 0x17d90, 0x17d9e,
+ 0x17da0, 0x17dbc, 0x17dc6, 0x17dcc, 0x17dd8, 0x17dee, 0x17e26, 0x17e2c, 0x17e3a, 0x17e46, 0x17e4c, 0x17e58,
+ 0x17e6e, 0x17e72, 0x17e74, 0x17e86, 0x17e8c, 0x17e98, 0x17eb0, 0x17ece, 0x17edc, 0x17ee2, 0x17ee4, 0x17ee8,
+ 0x17ef6, 0x1813a, 0x18172, 0x18174, 0x18216, 0x18226, 0x1823a, 0x1824c, 0x18258, 0x1826e, 0x18272, 0x18274,
+ 0x18298, 0x182be, 0x182e2, 0x182e4, 0x182e8, 0x182f6, 0x1835e, 0x1837a, 0x183ae, 0x183d6, 0x18416, 0x18426,
+ 0x1842c, 0x1843a, 0x18446, 0x18458, 0x1846e, 0x18472, 0x18474, 0x18486, 0x184b0, 0x184be, 0x184ce, 0x184dc,
+ 0x184e2, 0x184e4, 0x184e8, 0x184f6, 0x18506, 0x1850c, 0x18518, 0x18530, 0x1853e, 0x18560, 0x1857c, 0x1858e,
+ 0x1859c, 0x185b8, 0x185c2, 0x185c4, 0x185c8, 0x185d0, 0x185de, 0x185e6, 0x185ec, 0x185fa, 0x18612, 0x18614,
+ 0x18622, 0x18628, 0x18636, 0x18642, 0x18650, 0x1865e, 0x1867a, 0x18682, 0x18684, 0x18688, 0x18690, 0x1869e,
+ 0x186a0, 0x186bc, 0x186c6, 0x186cc, 0x186d8, 0x186ee, 0x186f2, 0x186f4, 0x1872e, 0x1874e, 0x1875c, 0x18796,
+ 0x187a6, 0x187ac, 0x187d2, 0x187d4, 0x18826, 0x1882c, 0x1883a, 0x18846, 0x1884c, 0x18858, 0x1886e, 0x18872,
+ 0x18874, 0x18886, 0x18898, 0x188b0, 0x188be, 0x188ce, 0x188dc, 0x188e2, 0x188e4, 0x188e8, 0x188f6, 0x1890c,
+ 0x18930, 0x1893e, 0x18960, 0x1897c, 0x1898e, 0x189b8, 0x189c2, 0x189c8, 0x189d0, 0x189de, 0x189e6, 0x189ec,
+ 0x189fa, 0x18a18, 0x18a30, 0x18a3e, 0x18a60, 0x18a7c, 0x18ac0, 0x18af8, 0x18b1c, 0x18b38, 0x18b70, 0x18b7e,
+ 0x18b82, 0x18b84, 0x18b88, 0x18b90, 0x18b9e, 0x18ba0, 0x18bbc, 0x18bc6, 0x18bcc, 0x18bd8, 0x18bee, 0x18bf2,
+ 0x18bf4, 0x18c22, 0x18c24, 0x18c28, 0x18c36, 0x18c42, 0x18c48, 0x18c50, 0x18c5e, 0x18c66, 0x18c7a, 0x18c82,
+ 0x18c84, 0x18c90, 0x18c9e, 0x18ca0, 0x18cbc, 0x18ccc, 0x18cf2, 0x18cf4, 0x18d04, 0x18d08, 0x18d10, 0x18d1e,
+ 0x18d20, 0x18d3c, 0x18d40, 0x18d78, 0x18d86, 0x18d98, 0x18dce, 0x18de2, 0x18de4, 0x18de8, 0x18e2e, 0x18e32,
+ 0x18e34, 0x18e4e, 0x18e5c, 0x18e62, 0x18e64, 0x18e68, 0x18e8e, 0x18e9c, 0x18eb8, 0x18ec2, 0x18ec4, 0x18ec8,
+ 0x18ed0, 0x18efa, 0x18f16, 0x18f26, 0x18f2c, 0x18f46, 0x18f4c, 0x18f58, 0x18f6e, 0x18f8a, 0x18f92, 0x18f94,
+ 0x18fa2, 0x18fa4, 0x18fa8, 0x18fb6, 0x1902c, 0x1903a, 0x19046, 0x1904c, 0x19058, 0x19072, 0x19074, 0x19086,
+ 0x19098, 0x190b0, 0x190be, 0x190ce, 0x190dc, 0x190e2, 0x190e8, 0x190f6, 0x19106, 0x1910c, 0x19130, 0x1913e,
+ 0x19160, 0x1917c, 0x1918e, 0x1919c, 0x191b8, 0x191c2, 0x191c8, 0x191d0, 0x191de, 0x191e6, 0x191ec, 0x191fa,
+ 0x19218, 0x1923e, 0x19260, 0x1927c, 0x192c0, 0x192f8, 0x19338, 0x19370, 0x1937e, 0x19382, 0x19384, 0x19390,
+ 0x1939e, 0x193a0, 0x193bc, 0x193c6, 0x193cc, 0x193d8, 0x193ee, 0x193f2, 0x193f4, 0x19430, 0x1943e, 0x19460,
+ 0x1947c, 0x194c0, 0x194f8, 0x195f0, 0x19638, 0x19670, 0x1967e, 0x196e0, 0x196fc, 0x19702, 0x19704, 0x19708,
+ 0x19710, 0x19720, 0x1973c, 0x19740, 0x19778, 0x19786, 0x1978c, 0x19798, 0x197b0, 0x197be, 0x197ce, 0x197dc,
+ 0x197e2, 0x197e4, 0x197e8, 0x19822, 0x19824, 0x19842, 0x19848, 0x19850, 0x1985e, 0x19866, 0x1987a, 0x19882,
+ 0x19884, 0x19890, 0x1989e, 0x198a0, 0x198bc, 0x198cc, 0x198f2, 0x198f4, 0x19902, 0x19908, 0x1991e, 0x19920,
+ 0x1993c, 0x19940, 0x19978, 0x19986, 0x19998, 0x199ce, 0x199e2, 0x199e4, 0x199e8, 0x19a08, 0x19a10, 0x19a1e,
+ 0x19a20, 0x19a3c, 0x19a40, 0x19a78, 0x19af0, 0x19b18, 0x19b3e, 0x19b60, 0x19b9c, 0x19bc2, 0x19bc4, 0x19bc8,
+ 0x19bd0, 0x19be6, 0x19c2e, 0x19c34, 0x19c4e, 0x19c5c, 0x19c62, 0x19c64, 0x19c68, 0x19c8e, 0x19c9c, 0x19cb8,
+ 0x19cc2, 0x19cc8, 0x19cd0, 0x19ce6, 0x19cfa, 0x19d0e, 0x19d1c, 0x19d38, 0x19d70, 0x19d7e, 0x19d82, 0x19d84,
+ 0x19d88, 0x19d90, 0x19da0, 0x19dcc, 0x19df2, 0x19df4, 0x19e16, 0x19e26, 0x19e2c, 0x19e46, 0x19e4c, 0x19e58,
+ 0x19e74, 0x19e86, 0x19e8c, 0x19e98, 0x19eb0, 0x19ebe, 0x19ece, 0x19ee2, 0x19ee4, 0x19ee8, 0x19f0a, 0x19f12,
+ 0x19f14, 0x19f22, 0x19f24, 0x19f28, 0x19f42, 0x19f44, 0x19f48, 0x19f50, 0x19f5e, 0x19f6c, 0x19f9a, 0x19fae,
+ 0x19fb2, 0x19fb4, 0x1a046, 0x1a04c, 0x1a072, 0x1a074, 0x1a086, 0x1a08c, 0x1a098, 0x1a0b0, 0x1a0be, 0x1a0e2,
+ 0x1a0e4, 0x1a0e8, 0x1a0f6, 0x1a106, 0x1a10c, 0x1a118, 0x1a130, 0x1a13e, 0x1a160, 0x1a17c, 0x1a18e, 0x1a19c,
+ 0x1a1b8, 0x1a1c2, 0x1a1c4, 0x1a1c8, 0x1a1d0, 0x1a1de, 0x1a1e6, 0x1a1ec, 0x1a218, 0x1a230, 0x1a23e, 0x1a260,
+ 0x1a27c, 0x1a2c0, 0x1a2f8, 0x1a31c, 0x1a338, 0x1a370, 0x1a37e, 0x1a382, 0x1a384, 0x1a388, 0x1a390, 0x1a39e,
+ 0x1a3a0, 0x1a3bc, 0x1a3c6, 0x1a3cc, 0x1a3d8, 0x1a3ee, 0x1a3f2, 0x1a3f4, 0x1a418, 0x1a430, 0x1a43e, 0x1a460,
+ 0x1a47c, 0x1a4c0, 0x1a4f8, 0x1a5f0, 0x1a61c, 0x1a638, 0x1a670, 0x1a67e, 0x1a6e0, 0x1a6fc, 0x1a702, 0x1a704,
+ 0x1a708, 0x1a710, 0x1a71e, 0x1a720, 0x1a73c, 0x1a740, 0x1a778, 0x1a786, 0x1a78c, 0x1a798, 0x1a7b0, 0x1a7be,
+ 0x1a7ce, 0x1a7dc, 0x1a7e2, 0x1a7e4, 0x1a7e8, 0x1a830, 0x1a860, 0x1a87c, 0x1a8c0, 0x1a8f8, 0x1a9f0, 0x1abe0,
+ 0x1ac70, 0x1ac7e, 0x1ace0, 0x1acfc, 0x1adc0, 0x1adf8, 0x1ae04, 0x1ae08, 0x1ae10, 0x1ae20, 0x1ae3c, 0x1ae40,
+ 0x1ae78, 0x1aef0, 0x1af06, 0x1af0c, 0x1af18, 0x1af30, 0x1af3e, 0x1af60, 0x1af7c, 0x1af8e, 0x1af9c, 0x1afb8,
+ 0x1afc4, 0x1afc8, 0x1afd0, 0x1afde, 0x1b042, 0x1b05e, 0x1b07a, 0x1b082, 0x1b084, 0x1b088, 0x1b090, 0x1b09e,
+ 0x1b0a0, 0x1b0bc, 0x1b0cc, 0x1b0f2, 0x1b0f4, 0x1b102, 0x1b104, 0x1b108, 0x1b110, 0x1b11e, 0x1b120, 0x1b13c,
+ 0x1b140, 0x1b178, 0x1b186, 0x1b198, 0x1b1ce, 0x1b1e2, 0x1b1e4, 0x1b1e8, 0x1b204, 0x1b208, 0x1b210, 0x1b21e,
+ 0x1b220, 0x1b23c, 0x1b240, 0x1b278, 0x1b2f0, 0x1b30c, 0x1b33e, 0x1b360, 0x1b39c, 0x1b3c2, 0x1b3c4, 0x1b3c8,
+ 0x1b3d0, 0x1b3e6, 0x1b410, 0x1b41e, 0x1b420, 0x1b43c, 0x1b440, 0x1b478, 0x1b4f0, 0x1b5e0, 0x1b618, 0x1b660,
+ 0x1b67c, 0x1b6c0, 0x1b738, 0x1b782, 0x1b784, 0x1b788, 0x1b790, 0x1b79e, 0x1b7a0, 0x1b7cc, 0x1b82e, 0x1b84e,
+ 0x1b85c, 0x1b88e, 0x1b89c, 0x1b8b8, 0x1b8c2, 0x1b8c4, 0x1b8c8, 0x1b8d0, 0x1b8e6, 0x1b8fa, 0x1b90e, 0x1b91c,
+ 0x1b938, 0x1b970, 0x1b97e, 0x1b982, 0x1b984, 0x1b988, 0x1b990, 0x1b99e, 0x1b9a0, 0x1b9cc, 0x1b9f2, 0x1b9f4,
+ 0x1ba0e, 0x1ba1c, 0x1ba38, 0x1ba70, 0x1ba7e, 0x1bae0, 0x1bafc, 0x1bb08, 0x1bb10, 0x1bb20, 0x1bb3c, 0x1bb40,
+ 0x1bb98, 0x1bbce, 0x1bbe2, 0x1bbe4, 0x1bbe8, 0x1bc16, 0x1bc26, 0x1bc2c, 0x1bc46, 0x1bc4c, 0x1bc58, 0x1bc72,
+ 0x1bc74, 0x1bc86, 0x1bc8c, 0x1bc98, 0x1bcb0, 0x1bcbe, 0x1bcce, 0x1bce2, 0x1bce4, 0x1bce8, 0x1bd06, 0x1bd0c,
+ 0x1bd18, 0x1bd30, 0x1bd3e, 0x1bd60, 0x1bd7c, 0x1bd9c, 0x1bdc2, 0x1bdc4, 0x1bdc8, 0x1bdd0, 0x1bde6, 0x1bdfa,
+ 0x1be12, 0x1be14, 0x1be22, 0x1be24, 0x1be28, 0x1be42, 0x1be44, 0x1be48, 0x1be50, 0x1be5e, 0x1be66, 0x1be82,
+ 0x1be84, 0x1be88, 0x1be90, 0x1be9e, 0x1bea0, 0x1bebc, 0x1becc, 0x1bef4, 0x1bf1a, 0x1bf2e, 0x1bf32, 0x1bf34,
+ 0x1bf4e, 0x1bf5c, 0x1bf62, 0x1bf64, 0x1bf68, 0x1c09a, 0x1c0b2, 0x1c0b4, 0x1c11a, 0x1c132, 0x1c134, 0x1c162,
+ 0x1c164, 0x1c168, 0x1c176, 0x1c1ba, 0x1c21a, 0x1c232, 0x1c234, 0x1c24e, 0x1c25c, 0x1c262, 0x1c264, 0x1c268,
+ 0x1c276, 0x1c28e, 0x1c2c2, 0x1c2c4, 0x1c2c8, 0x1c2d0, 0x1c2de, 0x1c2e6, 0x1c2ec, 0x1c2fa, 0x1c316, 0x1c326,
+ 0x1c33a, 0x1c346, 0x1c34c, 0x1c372, 0x1c374, 0x1c41a, 0x1c42e, 0x1c432, 0x1c434, 0x1c44e, 0x1c45c, 0x1c462,
+ 0x1c464, 0x1c468, 0x1c476, 0x1c48e, 0x1c49c, 0x1c4b8, 0x1c4c2, 0x1c4c8, 0x1c4d0, 0x1c4de, 0x1c4e6, 0x1c4ec,
+ 0x1c4fa, 0x1c51c, 0x1c538, 0x1c570, 0x1c57e, 0x1c582, 0x1c584, 0x1c588, 0x1c590, 0x1c59e, 0x1c5a0, 0x1c5bc,
+ 0x1c5c6, 0x1c5cc, 0x1c5d8, 0x1c5ee, 0x1c5f2, 0x1c5f4, 0x1c616, 0x1c626, 0x1c62c, 0x1c63a, 0x1c646, 0x1c64c,
+ 0x1c658, 0x1c66e, 0x1c672, 0x1c674, 0x1c686, 0x1c68c, 0x1c698, 0x1c6b0, 0x1c6be, 0x1c6ce, 0x1c6dc, 0x1c6e2,
+ 0x1c6e4, 0x1c6e8, 0x1c712, 0x1c714, 0x1c722, 0x1c728, 0x1c736, 0x1c742, 0x1c744, 0x1c748, 0x1c750, 0x1c75e,
+ 0x1c766, 0x1c76c, 0x1c77a, 0x1c7ae, 0x1c7d6, 0x1c7ea, 0x1c81a, 0x1c82e, 0x1c832, 0x1c834, 0x1c84e, 0x1c85c,
+ 0x1c862, 0x1c864, 0x1c868, 0x1c876, 0x1c88e, 0x1c89c, 0x1c8b8, 0x1c8c2, 0x1c8c8, 0x1c8d0, 0x1c8de, 0x1c8e6,
+ 0x1c8ec, 0x1c8fa, 0x1c90e, 0x1c938, 0x1c970, 0x1c97e, 0x1c982, 0x1c984, 0x1c990, 0x1c99e, 0x1c9a0, 0x1c9bc,
+ 0x1c9c6, 0x1c9cc, 0x1c9d8, 0x1c9ee, 0x1c9f2, 0x1c9f4, 0x1ca38, 0x1ca70, 0x1ca7e, 0x1cae0, 0x1cafc, 0x1cb02,
+ 0x1cb04, 0x1cb08, 0x1cb10, 0x1cb20, 0x1cb3c, 0x1cb40, 0x1cb78, 0x1cb86, 0x1cb8c, 0x1cb98, 0x1cbb0, 0x1cbbe,
+ 0x1cbce, 0x1cbdc, 0x1cbe2, 0x1cbe4, 0x1cbe8, 0x1cbf6, 0x1cc16, 0x1cc26, 0x1cc2c, 0x1cc3a, 0x1cc46, 0x1cc58,
+ 0x1cc72, 0x1cc74, 0x1cc86, 0x1ccb0, 0x1ccbe, 0x1ccce, 0x1cce2, 0x1cce4, 0x1cce8, 0x1cd06, 0x1cd0c, 0x1cd18,
+ 0x1cd30, 0x1cd3e, 0x1cd60, 0x1cd7c, 0x1cd9c, 0x1cdc2, 0x1cdc4, 0x1cdc8, 0x1cdd0, 0x1cdde, 0x1cde6, 0x1cdfa,
+ 0x1ce22, 0x1ce28, 0x1ce42, 0x1ce50, 0x1ce5e, 0x1ce66, 0x1ce7a, 0x1ce82, 0x1ce84, 0x1ce88, 0x1ce90, 0x1ce9e,
+ 0x1cea0, 0x1cebc, 0x1cecc, 0x1cef2, 0x1cef4, 0x1cf2e, 0x1cf32, 0x1cf34, 0x1cf4e, 0x1cf5c, 0x1cf62, 0x1cf64,
+ 0x1cf68, 0x1cf96, 0x1cfa6, 0x1cfac, 0x1cfca, 0x1cfd2, 0x1cfd4, 0x1d02e, 0x1d032, 0x1d034, 0x1d04e, 0x1d05c,
+ 0x1d062, 0x1d064, 0x1d068, 0x1d076, 0x1d08e, 0x1d09c, 0x1d0b8, 0x1d0c2, 0x1d0c4, 0x1d0c8, 0x1d0d0, 0x1d0de,
+ 0x1d0e6, 0x1d0ec, 0x1d0fa, 0x1d11c, 0x1d138, 0x1d170, 0x1d17e, 0x1d182, 0x1d184, 0x1d188, 0x1d190, 0x1d19e,
+ 0x1d1a0, 0x1d1bc, 0x1d1c6, 0x1d1cc, 0x1d1d8, 0x1d1ee, 0x1d1f2, 0x1d1f4, 0x1d21c, 0x1d238, 0x1d270, 0x1d27e,
+ 0x1d2e0, 0x1d2fc, 0x1d302, 0x1d304, 0x1d308, 0x1d310, 0x1d31e, 0x1d320, 0x1d33c, 0x1d340, 0x1d378, 0x1d386,
+ 0x1d38c, 0x1d398, 0x1d3b0, 0x1d3be, 0x1d3ce, 0x1d3dc, 0x1d3e2, 0x1d3e4, 0x1d3e8, 0x1d3f6, 0x1d470, 0x1d47e,
+ 0x1d4e0, 0x1d4fc, 0x1d5c0, 0x1d5f8, 0x1d604, 0x1d608, 0x1d610, 0x1d620, 0x1d640, 0x1d678, 0x1d6f0, 0x1d706,
+ 0x1d70c, 0x1d718, 0x1d730, 0x1d73e, 0x1d760, 0x1d77c, 0x1d78e, 0x1d79c, 0x1d7b8, 0x1d7c2, 0x1d7c4, 0x1d7c8,
+ 0x1d7d0, 0x1d7de, 0x1d7e6, 0x1d7ec, 0x1d826, 0x1d82c, 0x1d83a, 0x1d846, 0x1d84c, 0x1d858, 0x1d872, 0x1d874,
+ 0x1d886, 0x1d88c, 0x1d898, 0x1d8b0, 0x1d8be, 0x1d8ce, 0x1d8e2, 0x1d8e4, 0x1d8e8, 0x1d8f6, 0x1d90c, 0x1d918,
+ 0x1d930, 0x1d93e, 0x1d960, 0x1d97c, 0x1d99c, 0x1d9c2, 0x1d9c4, 0x1d9c8, 0x1d9d0, 0x1d9e6, 0x1d9fa, 0x1da0c,
+ 0x1da18, 0x1da30, 0x1da3e, 0x1da60, 0x1da7c, 0x1dac0, 0x1daf8, 0x1db38, 0x1db82, 0x1db84, 0x1db88, 0x1db90,
+ 0x1db9e, 0x1dba0, 0x1dbcc, 0x1dbf2, 0x1dbf4, 0x1dc22, 0x1dc42, 0x1dc44, 0x1dc48, 0x1dc50, 0x1dc5e, 0x1dc66,
+ 0x1dc7a, 0x1dc82, 0x1dc84, 0x1dc88, 0x1dc90, 0x1dc9e, 0x1dca0, 0x1dcbc, 0x1dccc, 0x1dcf2, 0x1dcf4, 0x1dd04,
+ 0x1dd08, 0x1dd10, 0x1dd1e, 0x1dd20, 0x1dd3c, 0x1dd40, 0x1dd78, 0x1dd86, 0x1dd98, 0x1ddce, 0x1dde2, 0x1dde4,
+ 0x1dde8, 0x1de2e, 0x1de32, 0x1de34, 0x1de4e, 0x1de5c, 0x1de62, 0x1de64, 0x1de68, 0x1de8e, 0x1de9c, 0x1deb8,
+ 0x1dec2, 0x1dec4, 0x1dec8, 0x1ded0, 0x1dee6, 0x1defa, 0x1df16, 0x1df26, 0x1df2c, 0x1df46, 0x1df4c, 0x1df58,
+ 0x1df72, 0x1df74, 0x1df8a, 0x1df92, 0x1df94, 0x1dfa2, 0x1dfa4, 0x1dfa8, 0x1e08a, 0x1e092, 0x1e094, 0x1e0a2,
+ 0x1e0a4, 0x1e0a8, 0x1e0b6, 0x1e0da, 0x1e10a, 0x1e112, 0x1e114, 0x1e122, 0x1e124, 0x1e128, 0x1e136, 0x1e142,
+ 0x1e144, 0x1e148, 0x1e150, 0x1e166, 0x1e16c, 0x1e17a, 0x1e19a, 0x1e1b2, 0x1e1b4, 0x1e20a, 0x1e212, 0x1e214,
+ 0x1e222, 0x1e224, 0x1e228, 0x1e236, 0x1e242, 0x1e248, 0x1e250, 0x1e25e, 0x1e266, 0x1e26c, 0x1e27a, 0x1e282,
+ 0x1e284, 0x1e288, 0x1e290, 0x1e2a0, 0x1e2bc, 0x1e2c6, 0x1e2cc, 0x1e2d8, 0x1e2ee, 0x1e2f2, 0x1e2f4, 0x1e31a,
+ 0x1e332, 0x1e334, 0x1e35c, 0x1e362, 0x1e364, 0x1e368, 0x1e3ba, 0x1e40a, 0x1e412, 0x1e414, 0x1e422, 0x1e428,
+ 0x1e436, 0x1e442, 0x1e448, 0x1e450, 0x1e45e, 0x1e466, 0x1e46c, 0x1e47a, 0x1e482, 0x1e484, 0x1e490, 0x1e49e,
+ 0x1e4a0, 0x1e4bc, 0x1e4c6, 0x1e4cc, 0x1e4d8, 0x1e4ee, 0x1e4f2, 0x1e4f4, 0x1e502, 0x1e504, 0x1e508, 0x1e510,
+ 0x1e51e, 0x1e520, 0x1e53c, 0x1e540, 0x1e578, 0x1e586, 0x1e58c, 0x1e598, 0x1e5b0, 0x1e5be, 0x1e5ce, 0x1e5dc,
+ 0x1e5e2, 0x1e5e4, 0x1e5e8, 0x1e5f6, 0x1e61a, 0x1e62e, 0x1e632, 0x1e634, 0x1e64e, 0x1e65c, 0x1e662, 0x1e668,
+ 0x1e68e, 0x1e69c, 0x1e6b8, 0x1e6c2, 0x1e6c4, 0x1e6c8, 0x1e6d0, 0x1e6e6, 0x1e6fa, 0x1e716, 0x1e726, 0x1e72c,
+ 0x1e73a, 0x1e746, 0x1e74c, 0x1e758, 0x1e772, 0x1e774, 0x1e792, 0x1e794, 0x1e7a2, 0x1e7a4, 0x1e7a8, 0x1e7b6,
+ 0x1e812, 0x1e814, 0x1e822, 0x1e824, 0x1e828, 0x1e836, 0x1e842, 0x1e844, 0x1e848, 0x1e850, 0x1e85e, 0x1e866,
+ 0x1e86c, 0x1e87a, 0x1e882, 0x1e884, 0x1e888, 0x1e890, 0x1e89e, 0x1e8a0, 0x1e8bc, 0x1e8c6, 0x1e8cc, 0x1e8d8,
+ 0x1e8ee, 0x1e8f2, 0x1e8f4, 0x1e902, 0x1e904, 0x1e908, 0x1e910, 0x1e920, 0x1e93c, 0x1e940, 0x1e978, 0x1e986,
+ 0x1e98c, 0x1e998, 0x1e9b0, 0x1e9be, 0x1e9ce, 0x1e9dc, 0x1e9e2, 0x1e9e4, 0x1e9e8, 0x1e9f6, 0x1ea04, 0x1ea08,
+ 0x1ea10, 0x1ea20, 0x1ea40, 0x1ea78, 0x1eaf0, 0x1eb06, 0x1eb0c, 0x1eb18, 0x1eb30, 0x1eb3e, 0x1eb60, 0x1eb7c,
+ 0x1eb8e, 0x1eb9c, 0x1ebb8, 0x1ebc2, 0x1ebc4, 0x1ebc8, 0x1ebd0, 0x1ebde, 0x1ebe6, 0x1ebec, 0x1ec1a, 0x1ec2e,
+ 0x1ec32, 0x1ec34, 0x1ec4e, 0x1ec5c, 0x1ec62, 0x1ec64, 0x1ec68, 0x1ec8e, 0x1ec9c, 0x1ecb8, 0x1ecc2, 0x1ecc4,
+ 0x1ecc8, 0x1ecd0, 0x1ece6, 0x1ecfa, 0x1ed0e, 0x1ed1c, 0x1ed38, 0x1ed70, 0x1ed7e, 0x1ed82, 0x1ed84, 0x1ed88,
+ 0x1ed90, 0x1ed9e, 0x1eda0, 0x1edcc, 0x1edf2, 0x1edf4, 0x1ee16, 0x1ee26, 0x1ee2c, 0x1ee3a, 0x1ee46, 0x1ee4c,
+ 0x1ee58, 0x1ee6e, 0x1ee72, 0x1ee74, 0x1ee86, 0x1ee8c, 0x1ee98, 0x1eeb0, 0x1eebe, 0x1eece, 0x1eedc, 0x1eee2,
+ 0x1eee4, 0x1eee8, 0x1ef12, 0x1ef22, 0x1ef24, 0x1ef28, 0x1ef36, 0x1ef42, 0x1ef44, 0x1ef48, 0x1ef50, 0x1ef5e,
+ 0x1ef66, 0x1ef6c, 0x1ef7a, 0x1efae, 0x1efb2, 0x1efb4, 0x1efd6, 0x1f096, 0x1f0a6, 0x1f0ac, 0x1f0ba, 0x1f0ca,
+ 0x1f0d2, 0x1f0d4, 0x1f116, 0x1f126, 0x1f12c, 0x1f13a, 0x1f146, 0x1f14c, 0x1f158, 0x1f16e, 0x1f172, 0x1f174,
+ 0x1f18a, 0x1f192, 0x1f194, 0x1f1a2, 0x1f1a4, 0x1f1a8, 0x1f1da, 0x1f216, 0x1f226, 0x1f22c, 0x1f23a, 0x1f246,
+ 0x1f258, 0x1f26e, 0x1f272, 0x1f274, 0x1f286, 0x1f28c, 0x1f298, 0x1f2b0, 0x1f2be, 0x1f2ce, 0x1f2dc, 0x1f2e2,
+ 0x1f2e4, 0x1f2e8, 0x1f2f6, 0x1f30a, 0x1f312, 0x1f314, 0x1f322, 0x1f328, 0x1f342, 0x1f344, 0x1f348, 0x1f350,
+ 0x1f35e, 0x1f366, 0x1f37a, 0x1f39a, 0x1f3ae, 0x1f3b2, 0x1f3b4, 0x1f416, 0x1f426, 0x1f42c, 0x1f43a, 0x1f446,
+ 0x1f44c, 0x1f458, 0x1f46e, 0x1f472, 0x1f474, 0x1f486, 0x1f48c, 0x1f498, 0x1f4b0, 0x1f4be, 0x1f4ce, 0x1f4dc,
+ 0x1f4e2, 0x1f4e4, 0x1f4e8, 0x1f4f6, 0x1f506, 0x1f50c, 0x1f518, 0x1f530, 0x1f53e, 0x1f560, 0x1f57c, 0x1f58e,
+ 0x1f59c, 0x1f5b8, 0x1f5c2, 0x1f5c4, 0x1f5c8, 0x1f5d0, 0x1f5de, 0x1f5e6, 0x1f5ec, 0x1f5fa, 0x1f60a, 0x1f612,
+ 0x1f614, 0x1f622, 0x1f624, 0x1f628, 0x1f636, 0x1f642, 0x1f644, 0x1f648, 0x1f650, 0x1f65e, 0x1f666, 0x1f67a,
+ 0x1f682, 0x1f684, 0x1f688, 0x1f690, 0x1f69e, 0x1f6a0, 0x1f6bc, 0x1f6cc, 0x1f6f2, 0x1f6f4, 0x1f71a, 0x1f72e,
+ 0x1f732, 0x1f734, 0x1f74e, 0x1f75c, 0x1f762, 0x1f764, 0x1f768, 0x1f776, 0x1f796, 0x1f7a6, 0x1f7ac, 0x1f7ba,
+ 0x1f7d2, 0x1f7d4, 0x1f89a, 0x1f8ae, 0x1f8b2, 0x1f8b4, 0x1f8d6, 0x1f8ea, 0x1f91a, 0x1f92e, 0x1f932, 0x1f934,
+ 0x1f94e, 0x1f95c, 0x1f962, 0x1f964, 0x1f968, 0x1f976, 0x1f996, 0x1f9a6, 0x1f9ac, 0x1f9ba, 0x1f9ca, 0x1f9d2,
+ 0x1f9d4, 0x1fa1a, 0x1fa2e, 0x1fa32, 0x1fa34, 0x1fa4e, 0x1fa5c, 0x1fa62, 0x1fa64, 0x1fa68, 0x1fa76, 0x1fa8e,
+ 0x1fa9c, 0x1fab8, 0x1fac2, 0x1fac4, 0x1fac8, 0x1fad0, 0x1fade, 0x1fae6, 0x1faec, 0x1fb16, 0x1fb26, 0x1fb2c,
+ 0x1fb3a, 0x1fb46, 0x1fb4c, 0x1fb58, 0x1fb6e, 0x1fb72, 0x1fb74, 0x1fb8a, 0x1fb92, 0x1fb94, 0x1fba2, 0x1fba4,
+ 0x1fba8, 0x1fbb6, 0x1fbda};
+
+ /**
+ * This table contains to codewords for all symbols.
+ */
+ private static final int[] CODEWORD_TABLE = {
+ 2627, 1819, 2622, 2621, 1813, 1812, 2729, 2724, 2723, 2779, 2774, 2773, 902, 896, 908, 868, 865, 861, 859, 2511,
+ 873, 871, 1780, 835, 2493, 825, 2491, 842, 837, 844, 1764, 1762, 811, 810, 809, 2483, 807, 2482, 806, 2480, 815,
+ 814, 813, 812, 2484, 817, 816, 1745, 1744, 1742, 1746, 2655, 2637, 2635, 2626, 2625, 2623, 2628, 1820, 2752,
+ 2739, 2737, 2728, 2727, 2725, 2730, 2785, 2783, 2778, 2777, 2775, 2780, 787, 781, 747, 739, 736, 2413, 754, 752,
+ 1719, 692, 689, 681, 2371, 678, 2369, 700, 697, 694, 703, 1688, 1686, 642, 638, 2343, 631, 2341, 627, 2338, 651,
+ 646, 643, 2345, 654, 652, 1652, 1650, 1647, 1654, 601, 599, 2322, 596, 2321, 594, 2319, 2317, 611, 610, 608, 606,
+ 2324, 603, 2323, 615, 614, 612, 1617, 1616, 1614, 1612, 616, 1619, 1618, 2575, 2538, 2536, 905, 901, 898, 909,
+ 2509, 2507, 2504, 870, 867, 864, 860, 2512, 875, 872, 1781, 2490, 2489, 2487, 2485, 1748, 836, 834, 832, 830,
+ 2494, 827, 2492, 843, 841, 839, 845, 1765, 1763, 2701, 2676, 2674, 2653, 2648, 2656, 2634, 2633, 2631, 2629,
+ 1821, 2638, 2636, 2770, 2763, 2761, 2750, 2745, 2753, 2736, 2735, 2733, 2731, 1848, 2740, 2738, 2786, 2784, 591,
+ 588, 576, 569, 566, 2296, 1590, 537, 534, 526, 2276, 522, 2274, 545, 542, 539, 548, 1572, 1570, 481, 2245, 466,
+ 2242, 462, 2239, 492, 485, 482, 2249, 496, 494, 1534, 1531, 1528, 1538, 413, 2196, 406, 2191, 2188, 425, 419,
+ 2202, 415, 2199, 432, 430, 427, 1472, 1467, 1464, 433, 1476, 1474, 368, 367, 2160, 365, 2159, 362, 2157, 2155,
+ 2152, 378, 377, 375, 2166, 372, 2165, 369, 2162, 383, 381, 379, 2168, 1419, 1418, 1416, 1414, 385, 1411, 384,
+ 1423, 1422, 1420, 1424, 2461, 802, 2441, 2439, 790, 786, 783, 794, 2409, 2406, 2403, 750, 742, 738, 2414, 756,
+ 753, 1720, 2367, 2365, 2362, 2359, 1663, 693, 691, 684, 2373, 680, 2370, 702, 699, 696, 704, 1690, 1687, 2337,
+ 2336, 2334, 2332, 1624, 2329, 1622, 640, 637, 2344, 634, 2342, 630, 2340, 650, 648, 645, 2346, 655, 653, 1653,
+ 1651, 1649, 1655, 2612, 2597, 2595, 2571, 2568, 2565, 2576, 2534, 2529, 2526, 1787, 2540, 2537, 907, 904, 900,
+ 910, 2503, 2502, 2500, 2498, 1768, 2495, 1767, 2510, 2508, 2506, 869, 866, 863, 2513, 876, 874, 1782, 2720, 2713,
+ 2711, 2697, 2694, 2691, 2702, 2672, 2670, 2664, 1828, 2678, 2675, 2647, 2646, 2644, 2642, 1823, 2639, 1822, 2654,
+ 2652, 2650, 2657, 2771, 1855, 2765, 2762, 1850, 1849, 2751, 2749, 2747, 2754, 353, 2148, 344, 342, 336, 2142,
+ 332, 2140, 345, 1375, 1373, 306, 2130, 299, 2128, 295, 2125, 319, 314, 311, 2132, 1354, 1352, 1349, 1356, 262,
+ 257, 2101, 253, 2096, 2093, 274, 273, 267, 2107, 263, 2104, 280, 278, 275, 1316, 1311, 1308, 1320, 1318, 2052,
+ 202, 2050, 2044, 2040, 219, 2063, 212, 2060, 208, 2055, 224, 221, 2066, 1260, 1258, 1252, 231, 1248, 229, 1266,
+ 1264, 1261, 1268, 155, 1998, 153, 1996, 1994, 1991, 1988, 165, 164, 2007, 162, 2006, 159, 2003, 2000, 172, 171,
+ 169, 2012, 166, 2010, 1186, 1184, 1182, 1179, 175, 1176, 173, 1192, 1191, 1189, 1187, 176, 1194, 1193, 2313,
+ 2307, 2305, 592, 589, 2294, 2292, 2289, 578, 572, 568, 2297, 580, 1591, 2272, 2267, 2264, 1547, 538, 536, 529,
+ 2278, 525, 2275, 547, 544, 541, 1574, 1571, 2237, 2235, 2229, 1493, 2225, 1489, 478, 2247, 470, 2244, 465, 2241,
+ 493, 488, 484, 2250, 498, 495, 1536, 1533, 1530, 1539, 2187, 2186, 2184, 2182, 1432, 2179, 1430, 2176, 1427, 414,
+ 412, 2197, 409, 2195, 405, 2193, 2190, 426, 424, 421, 2203, 418, 2201, 431, 429, 1473, 1471, 1469, 1466, 434,
+ 1477, 1475, 2478, 2472, 2470, 2459, 2457, 2454, 2462, 803, 2437, 2432, 2429, 1726, 2443, 2440, 792, 789, 785,
+ 2401, 2399, 2393, 1702, 2389, 1699, 2411, 2408, 2405, 745, 741, 2415, 758, 755, 1721, 2358, 2357, 2355, 2353,
+ 1661, 2350, 1660, 2347, 1657, 2368, 2366, 2364, 2361, 1666, 690, 687, 2374, 683, 2372, 701, 698, 705, 1691, 1689,
+ 2619, 2617, 2610, 2608, 2605, 2613, 2593, 2588, 2585, 1803, 2599, 2596, 2563, 2561, 2555, 1797, 2551, 1795, 2573,
+ 2570, 2567, 2577, 2525, 2524, 2522, 2520, 1786, 2517, 1785, 2514, 1783, 2535, 2533, 2531, 2528, 1788, 2541, 2539,
+ 906, 903, 911, 2721, 1844, 2715, 2712, 1838, 1836, 2699, 2696, 2693, 2703, 1827, 1826, 1824, 2673, 2671, 2669,
+ 2666, 1829, 2679, 2677, 1858, 1857, 2772, 1854, 1853, 1851, 1856, 2766, 2764, 143, 1987, 139, 1986, 135, 133,
+ 131, 1984, 128, 1983, 125, 1981, 138, 137, 136, 1985, 1133, 1132, 1130, 112, 110, 1974, 107, 1973, 104, 1971,
+ 1969, 122, 121, 119, 117, 1977, 114, 1976, 124, 1115, 1114, 1112, 1110, 1117, 1116, 84, 83, 1953, 81, 1952, 78,
+ 1950, 1948, 1945, 94, 93, 91, 1959, 88, 1958, 85, 1955, 99, 97, 95, 1961, 1086, 1085, 1083, 1081, 1078, 100,
+ 1090, 1089, 1087, 1091, 49, 47, 1917, 44, 1915, 1913, 1910, 1907, 59, 1926, 56, 1925, 53, 1922, 1919, 66, 64,
+ 1931, 61, 1929, 1042, 1040, 1038, 71, 1035, 70, 1032, 68, 1048, 1047, 1045, 1043, 1050, 1049, 12, 10, 1869, 1867,
+ 1864, 1861, 21, 1880, 19, 1877, 1874, 1871, 28, 1888, 25, 1886, 22, 1883, 982, 980, 977, 974, 32, 30, 991, 989,
+ 987, 984, 34, 995, 994, 992, 2151, 2150, 2147, 2146, 2144, 356, 355, 354, 2149, 2139, 2138, 2136, 2134, 1359,
+ 343, 341, 338, 2143, 335, 2141, 348, 347, 346, 1376, 1374, 2124, 2123, 2121, 2119, 1326, 2116, 1324, 310, 308,
+ 305, 2131, 302, 2129, 298, 2127, 320, 318, 316, 313, 2133, 322, 321, 1355, 1353, 1351, 1357, 2092, 2091, 2089,
+ 2087, 1276, 2084, 1274, 2081, 1271, 259, 2102, 256, 2100, 252, 2098, 2095, 272, 269, 2108, 266, 2106, 281, 279,
+ 277, 1317, 1315, 1313, 1310, 282, 1321, 1319, 2039, 2037, 2035, 2032, 1203, 2029, 1200, 1197, 207, 2053, 205,
+ 2051, 201, 2049, 2046, 2043, 220, 218, 2064, 215, 2062, 211, 2059, 228, 226, 223, 2069, 1259, 1257, 1254, 232,
+ 1251, 230, 1267, 1265, 1263, 2316, 2315, 2312, 2311, 2309, 2314, 2304, 2303, 2301, 2299, 1593, 2308, 2306, 590,
+ 2288, 2287, 2285, 2283, 1578, 2280, 1577, 2295, 2293, 2291, 579, 577, 574, 571, 2298, 582, 581, 1592, 2263, 2262,
+ 2260, 2258, 1545, 2255, 1544, 2252, 1541, 2273, 2271, 2269, 2266, 1550, 535, 532, 2279, 528, 2277, 546, 543, 549,
+ 1575, 1573, 2224, 2222, 2220, 1486, 2217, 1485, 2214, 1482, 1479, 2238, 2236, 2234, 2231, 1496, 2228, 1492, 480,
+ 477, 2248, 473, 2246, 469, 2243, 490, 487, 2251, 497, 1537, 1535, 1532, 2477, 2476, 2474, 2479, 2469, 2468, 2466,
+ 2464, 1730, 2473, 2471, 2453, 2452, 2450, 2448, 1729, 2445, 1728, 2460, 2458, 2456, 2463, 805, 804, 2428, 2427,
+ 2425, 2423, 1725, 2420, 1724, 2417, 1722, 2438, 2436, 2434, 2431, 1727, 2444, 2442, 793, 791, 788, 795, 2388,
+ 2386, 2384, 1697, 2381, 1696, 2378, 1694, 1692, 2402, 2400, 2398, 2395, 1703, 2392, 1701, 2412, 2410, 2407, 751,
+ 748, 744, 2416, 759, 757, 1807, 2620, 2618, 1806, 1805, 2611, 2609, 2607, 2614, 1802, 1801, 1799, 2594, 2592,
+ 2590, 2587, 1804, 2600, 2598, 1794, 1793, 1791, 1789, 2564, 2562, 2560, 2557, 1798, 2554, 1796, 2574, 2572, 2569,
+ 2578, 1847, 1846, 2722, 1843, 1842, 1840, 1845, 2716, 2714, 1835, 1834, 1832, 1830, 1839, 1837, 2700, 2698, 2695,
+ 2704, 1817, 1811, 1810, 897, 862, 1777, 829, 826, 838, 1760, 1758, 808, 2481, 1741, 1740, 1738, 1743, 2624, 1818,
+ 2726, 2776, 782, 740, 737, 1715, 686, 679, 695, 1682, 1680, 639, 628, 2339, 647, 644, 1645, 1643, 1640, 1648,
+ 602, 600, 597, 595, 2320, 593, 2318, 609, 607, 604, 1611, 1610, 1608, 1606, 613, 1615, 1613, 2328, 926, 924, 892,
+ 886, 899, 857, 850, 2505, 1778, 824, 823, 821, 819, 2488, 818, 2486, 833, 831, 828, 840, 1761, 1759, 2649, 2632,
+ 2630, 2746, 2734, 2732, 2782, 2781, 570, 567, 1587, 531, 527, 523, 540, 1566, 1564, 476, 467, 463, 2240, 486,
+ 483, 1524, 1521, 1518, 1529, 411, 403, 2192, 399, 2189, 423, 416, 1462, 1457, 1454, 428, 1468, 1465, 2210, 366,
+ 363, 2158, 360, 2156, 357, 2153, 376, 373, 370, 2163, 1410, 1409, 1407, 1405, 382, 1402, 380, 1417, 1415, 1412,
+ 1421, 2175, 2174, 777, 774, 771, 784, 732, 725, 722, 2404, 743, 1716, 676, 674, 668, 2363, 665, 2360, 685, 1684,
+ 1681, 626, 624, 622, 2335, 620, 2333, 617, 2330, 641, 635, 649, 1646, 1644, 1642, 2566, 928, 925, 2530, 2527,
+ 894, 891, 888, 2501, 2499, 2496, 858, 856, 854, 851, 1779, 2692, 2668, 2665, 2645, 2643, 2640, 2651, 2768, 2759,
+ 2757, 2744, 2743, 2741, 2748, 352, 1382, 340, 337, 333, 1371, 1369, 307, 300, 296, 2126, 315, 312, 1347, 1342,
+ 1350, 261, 258, 250, 2097, 246, 2094, 271, 268, 264, 1306, 1301, 1298, 276, 1312, 1309, 2115, 203, 2048, 195,
+ 2045, 191, 2041, 213, 209, 2056, 1246, 1244, 1238, 225, 1234, 222, 1256, 1253, 1249, 1262, 2080, 2079, 154, 1997,
+ 150, 1995, 147, 1992, 1989, 163, 160, 2004, 156, 2001, 1175, 1174, 1172, 1170, 1167, 170, 1164, 167, 1185, 1183,
+ 1180, 1177, 174, 1190, 1188, 2025, 2024, 2022, 587, 586, 564, 559, 556, 2290, 573, 1588, 520, 518, 512, 2268,
+ 508, 2265, 530, 1568, 1565, 461, 457, 2233, 450, 2230, 446, 2226, 479, 471, 489, 1526, 1523, 1520, 397, 395,
+ 2185, 392, 2183, 389, 2180, 2177, 410, 2194, 402, 422, 1463, 1461, 1459, 1456, 1470, 2455, 799, 2433, 2430, 779,
+ 776, 773, 2397, 2394, 2390, 734, 728, 724, 746, 1717, 2356, 2354, 2351, 2348, 1658, 677, 675, 673, 670, 667, 688,
+ 1685, 1683, 2606, 2589, 2586, 2559, 2556, 2552, 927, 2523, 2521, 2518, 2515, 1784, 2532, 895, 893, 890, 2718,
+ 2709, 2707, 2689, 2687, 2684, 2663, 2662, 2660, 2658, 1825, 2667, 2769, 1852, 2760, 2758, 142, 141, 1139, 1138,
+ 134, 132, 129, 126, 1982, 1129, 1128, 1126, 1131, 113, 111, 108, 105, 1972, 101, 1970, 120, 118, 115, 1109, 1108,
+ 1106, 1104, 123, 1113, 1111, 82, 79, 1951, 75, 1949, 72, 1946, 92, 89, 86, 1956, 1077, 1076, 1074, 1072, 98,
+ 1069, 96, 1084, 1082, 1079, 1088, 1968, 1967, 48, 45, 1916, 42, 1914, 39, 1911, 1908, 60, 57, 54, 1923, 50, 1920,
+ 1031, 1030, 1028, 1026, 67, 1023, 65, 1020, 62, 1041, 1039, 1036, 1033, 69, 1046, 1044, 1944, 1943, 1941, 11, 9,
+ 1868, 7, 1865, 1862, 1859, 20, 1878, 16, 1875, 13, 1872, 970, 968, 966, 963, 29, 960, 26, 23, 983, 981, 978, 975,
+ 33, 971, 31, 990, 988, 985, 1906, 1904, 1902, 993, 351, 2145, 1383, 331, 330, 328, 326, 2137, 323, 2135, 339,
+ 1372, 1370, 294, 293, 291, 289, 2122, 286, 2120, 283, 2117, 309, 303, 317, 1348, 1346, 1344, 245, 244, 242, 2090,
+ 239, 2088, 236, 2085, 2082, 260, 2099, 249, 270, 1307, 1305, 1303, 1300, 1314, 189, 2038, 186, 2036, 183, 2033,
+ 2030, 2026, 206, 198, 2047, 194, 216, 1247, 1245, 1243, 1240, 227, 1237, 1255, 2310, 2302, 2300, 2286, 2284,
+ 2281, 565, 563, 561, 558, 575, 1589, 2261, 2259, 2256, 2253, 1542, 521, 519, 517, 514, 2270, 511, 533, 1569,
+ 1567, 2223, 2221, 2218, 2215, 1483, 2211, 1480, 459, 456, 453, 2232, 449, 474, 491, 1527, 1525, 1522, 2475, 2467,
+ 2465, 2451, 2449, 2446, 801, 800, 2426, 2424, 2421, 2418, 1723, 2435, 780, 778, 775, 2387, 2385, 2382, 2379,
+ 1695, 2375, 1693, 2396, 735, 733, 730, 727, 749, 1718, 2616, 2615, 2604, 2603, 2601, 2584, 2583, 2581, 2579,
+ 1800, 2591, 2550, 2549, 2547, 2545, 1792, 2542, 1790, 2558, 929, 2719, 1841, 2710, 2708, 1833, 1831, 2690, 2688,
+ 2686, 1815, 1809, 1808, 1774, 1756, 1754, 1737, 1736, 1734, 1739, 1816, 1711, 1676, 1674, 633, 629, 1638, 1636,
+ 1633, 1641, 598, 1605, 1604, 1602, 1600, 605, 1609, 1607, 2327, 887, 853, 1775, 822, 820, 1757, 1755, 1584, 524,
+ 1560, 1558, 468, 464, 1514, 1511, 1508, 1519, 408, 404, 400, 1452, 1447, 1444, 417, 1458, 1455, 2208, 364, 361,
+ 358, 2154, 1401, 1400, 1398, 1396, 374, 1393, 371, 1408, 1406, 1403, 1413, 2173, 2172, 772, 726, 723, 1712, 672,
+ 669, 666, 682, 1678, 1675, 625, 623, 621, 618, 2331, 636, 632, 1639, 1637, 1635, 920, 918, 884, 880, 889, 849,
+ 848, 847, 846, 2497, 855, 852, 1776, 2641, 2742, 2787, 1380, 334, 1367, 1365, 301, 297, 1340, 1338, 1335, 1343,
+ 255, 251, 247, 1296, 1291, 1288, 265, 1302, 1299, 2113, 204, 196, 192, 2042, 1232, 1230, 1224, 214, 1220, 210,
+ 1242, 1239, 1235, 1250, 2077, 2075, 151, 148, 1993, 144, 1990, 1163, 1162, 1160, 1158, 1155, 161, 1152, 157,
+ 1173, 1171, 1168, 1165, 168, 1181, 1178, 2021, 2020, 2018, 2023, 585, 560, 557, 1585, 516, 509, 1562, 1559, 458,
+ 447, 2227, 472, 1516, 1513, 1510, 398, 396, 393, 390, 2181, 386, 2178, 407, 1453, 1451, 1449, 1446, 420, 1460,
+ 2209, 769, 764, 720, 712, 2391, 729, 1713, 664, 663, 661, 659, 2352, 656, 2349, 671, 1679, 1677, 2553, 922, 919,
+ 2519, 2516, 885, 883, 881, 2685, 2661, 2659, 2767, 2756, 2755, 140, 1137, 1136, 130, 127, 1125, 1124, 1122, 1127,
+ 109, 106, 102, 1103, 1102, 1100, 1098, 116, 1107, 1105, 1980, 80, 76, 73, 1947, 1068, 1067, 1065, 1063, 90, 1060,
+ 87, 1075, 1073, 1070, 1080, 1966, 1965, 46, 43, 40, 1912, 36, 1909, 1019, 1018, 1016, 1014, 58, 1011, 55, 1008,
+ 51, 1029, 1027, 1024, 1021, 63, 1037, 1034, 1940, 1939, 1937, 1942, 8, 1866, 4, 1863, 1, 1860, 956, 954, 952,
+ 949, 946, 17, 14, 969, 967, 964, 961, 27, 957, 24, 979, 976, 972, 1901, 1900, 1898, 1896, 986, 1905, 1903, 350,
+ 349, 1381, 329, 327, 324, 1368, 1366, 292, 290, 287, 284, 2118, 304, 1341, 1339, 1337, 1345, 243, 240, 237, 2086,
+ 233, 2083, 254, 1297, 1295, 1293, 1290, 1304, 2114, 190, 187, 184, 2034, 180, 2031, 177, 2027, 199, 1233, 1231,
+ 1229, 1226, 217, 1223, 1241, 2078, 2076, 584, 555, 554, 552, 550, 2282, 562, 1586, 507, 506, 504, 502, 2257, 499,
+ 2254, 515, 1563, 1561, 445, 443, 441, 2219, 438, 2216, 435, 2212, 460, 454, 475, 1517, 1515, 1512, 2447, 798,
+ 797, 2422, 2419, 770, 768, 766, 2383, 2380, 2376, 721, 719, 717, 714, 731, 1714, 2602, 2582, 2580, 2548, 2546,
+ 2543, 923, 921, 2717, 2706, 2705, 2683, 2682, 2680, 1771, 1752, 1750, 1733, 1732, 1731, 1735, 1814, 1707, 1670,
+ 1668, 1631, 1629, 1626, 1634, 1599, 1598, 1596, 1594, 1603, 1601, 2326, 1772, 1753, 1751, 1581, 1554, 1552, 1504,
+ 1501, 1498, 1509, 1442, 1437, 1434, 401, 1448, 1445, 2206, 1392, 1391, 1389, 1387, 1384, 359, 1399, 1397, 1394,
+ 1404, 2171, 2170, 1708, 1672, 1669, 619, 1632, 1630, 1628, 1773, 1378, 1363, 1361, 1333, 1328, 1336, 1286, 1281,
+ 1278, 248, 1292, 1289, 2111, 1218, 1216, 1210, 197, 1206, 193, 1228, 1225, 1221, 1236, 2073, 2071, 1151, 1150,
+ 1148, 1146, 152, 1143, 149, 1140, 145, 1161, 1159, 1156, 1153, 158, 1169, 1166, 2017, 2016, 2014, 2019, 1582,
+ 510, 1556, 1553, 452, 448, 1506, 1500, 394, 391, 387, 1443, 1441, 1439, 1436, 1450, 2207, 765, 716, 713, 1709,
+ 662, 660, 657, 1673, 1671, 916, 914, 879, 878, 877, 882, 1135, 1134, 1121, 1120, 1118, 1123, 1097, 1096, 1094,
+ 1092, 103, 1101, 1099, 1979, 1059, 1058, 1056, 1054, 77, 1051, 74, 1066, 1064, 1061, 1071, 1964, 1963, 1007,
+ 1006, 1004, 1002, 999, 41, 996, 37, 1017, 1015, 1012, 1009, 52, 1025, 1022, 1936, 1935, 1933, 1938, 942, 940,
+ 938, 935, 932, 5, 2, 955, 953, 950, 947, 18, 943, 15, 965, 962, 958, 1895, 1894, 1892, 1890, 973, 1899, 1897,
+ 1379, 325, 1364, 1362, 288, 285, 1334, 1332, 1330, 241, 238, 234, 1287, 1285, 1283, 1280, 1294, 2112, 188, 185,
+ 181, 178, 2028, 1219, 1217, 1215, 1212, 200, 1209, 1227, 2074, 2072, 583, 553, 551, 1583, 505, 503, 500, 513,
+ 1557, 1555, 444, 442, 439, 436, 2213, 455, 451, 1507, 1505, 1502, 796, 763, 762, 760, 767, 711, 710, 708, 706,
+ 2377, 718, 715, 1710, 2544, 917, 915, 2681, 1627, 1597, 1595, 2325, 1769, 1749, 1747, 1499, 1438, 1435, 2204,
+ 1390, 1388, 1385, 1395, 2169, 2167, 1704, 1665, 1662, 1625, 1623, 1620, 1770, 1329, 1282, 1279, 2109, 1214, 1207,
+ 1222, 2068, 2065, 1149, 1147, 1144, 1141, 146, 1157, 1154, 2013, 2011, 2008, 2015, 1579, 1549, 1546, 1495, 1487,
+ 1433, 1431, 1428, 1425, 388, 1440, 2205, 1705, 658, 1667, 1664, 1119, 1095, 1093, 1978, 1057, 1055, 1052, 1062,
+ 1962, 1960, 1005, 1003, 1000, 997, 38, 1013, 1010, 1932, 1930, 1927, 1934, 941, 939, 936, 933, 6, 930, 3, 951,
+ 948, 944, 1889, 1887, 1884, 1881, 959, 1893, 1891, 35, 1377, 1360, 1358, 1327, 1325, 1322, 1331, 1277, 1275,
+ 1272, 1269, 235, 1284, 2110, 1205, 1204, 1201, 1198, 182, 1195, 179, 1213, 2070, 2067, 1580, 501, 1551, 1548,
+ 440, 437, 1497, 1494, 1490, 1503, 761, 709, 707, 1706, 913, 912, 2198, 1386, 2164, 2161, 1621, 1766, 2103, 1208,
+ 2058, 2054, 1145, 1142, 2005, 2002, 1999, 2009, 1488, 1429, 1426, 2200, 1698, 1659, 1656, 1975, 1053, 1957, 1954,
+ 1001, 998, 1924, 1921, 1918, 1928, 937, 934, 931, 1879, 1876, 1873, 1870, 945, 1885, 1882, 1323, 1273, 1270,
+ 2105, 1202, 1199, 1196, 1211, 2061, 2057, 1576, 1543, 1540, 1484, 1481, 1478, 1491, 1700};
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Reader.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Reader.java
new file mode 100644
index 000000000..c0eeb288b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Reader.java
@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+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.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.multi.MultipleBarcodeReader;
+import com.google.zxing.pdf417.decoder.PDF417ScanningDecoder;
+import com.google.zxing.pdf417.detector.Detector;
+import com.google.zxing.pdf417.detector.PDF417DetectorResult;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode PDF417 codes in an image.
+ *
+ * @author Guenther Grau
+ */
+public final class PDF417Reader implements Reader, MultipleBarcodeReader {
+
+ /**
+ * Locates and decodes a PDF417 code in an image.
+ *
+ * @return a String representing the content encoded by the PDF417 code
+ * @throws NotFoundException if a PDF417 code cannot be found,
+ * @throws FormatException if a PDF417 cannot be decoded
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, FormatException, ChecksumException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints) throws NotFoundException, FormatException,
+ ChecksumException {
+ Result[] result = decode(image, hints, false);
+ if (result == null || result.length == 0 || result[0] == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ return result[0];
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
+ return decodeMultiple(image, null);
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image, Map hints) throws NotFoundException {
+ try {
+ return decode(image, hints, true);
+ } catch (FormatException | ChecksumException ignored) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ private static Result[] decode(BinaryBitmap image, Map hints, boolean multiple)
+ throws NotFoundException, FormatException, ChecksumException {
+ List results = new ArrayList<>();
+ PDF417DetectorResult detectorResult = Detector.detect(image, hints, multiple);
+ for (ResultPoint[] points : detectorResult.getPoints()) {
+ DecoderResult decoderResult = PDF417ScanningDecoder.decode(detectorResult.getBits(), points[4], points[5],
+ points[6], points[7], getMinCodewordWidth(points), getMaxCodewordWidth(points));
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.PDF_417);
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel());
+ PDF417ResultMetadata pdf417ResultMetadata = (PDF417ResultMetadata) decoderResult.getOther();
+ if (pdf417ResultMetadata != null) {
+ result.putMetadata(ResultMetadataType.PDF417_EXTRA_METADATA, pdf417ResultMetadata);
+ }
+ results.add(result);
+ }
+ return results.toArray(new Result[results.size()]);
+ }
+
+ private static int getMaxWidth(ResultPoint p1, ResultPoint p2) {
+ if (p1 == null || p2 == null) {
+ return 0;
+ }
+ return (int) Math.abs(p1.getX() - p2.getX());
+ }
+
+ private static int getMinWidth(ResultPoint p1, ResultPoint p2) {
+ if (p1 == null || p2 == null) {
+ return Integer.MAX_VALUE;
+ }
+ return (int) Math.abs(p1.getX() - p2.getX());
+ }
+
+ private static int getMaxCodewordWidth(ResultPoint[] p) {
+ return Math.max(
+ Math.max(getMaxWidth(p[0], p[4]), getMaxWidth(p[6], p[2]) * PDF417Common.MODULES_IN_CODEWORD /
+ PDF417Common.MODULES_IN_STOP_PATTERN),
+ Math.max(getMaxWidth(p[1], p[5]), getMaxWidth(p[7], p[3]) * PDF417Common.MODULES_IN_CODEWORD /
+ PDF417Common.MODULES_IN_STOP_PATTERN));
+ }
+
+ private static int getMinCodewordWidth(ResultPoint[] p) {
+ return Math.min(
+ Math.min(getMinWidth(p[0], p[4]), getMinWidth(p[6], p[2]) * PDF417Common.MODULES_IN_CODEWORD /
+ PDF417Common.MODULES_IN_STOP_PATTERN),
+ Math.min(getMinWidth(p[1], p[5]), getMinWidth(p[7], p[3]) * PDF417Common.MODULES_IN_CODEWORD /
+ PDF417Common.MODULES_IN_STOP_PATTERN));
+ }
+
+ @Override
+ public void reset() {
+ // nothing needs to be reset
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417ResultMetadata.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417ResultMetadata.java
new file mode 100644
index 000000000..2e1c4ee26
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417ResultMetadata.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 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;
+
+/**
+ * @author Guenther Grau
+ */
+public final class PDF417ResultMetadata {
+
+ private int segmentIndex;
+ private String fileId;
+ private int[] optionalData;
+ private boolean lastSegment;
+
+ public int getSegmentIndex() {
+ return segmentIndex;
+ }
+
+ public void setSegmentIndex(int segmentIndex) {
+ this.segmentIndex = segmentIndex;
+ }
+
+ public String getFileId() {
+ return fileId;
+ }
+
+ public void setFileId(String fileId) {
+ this.fileId = fileId;
+ }
+
+ public int[] getOptionalData() {
+ return optionalData;
+ }
+
+ public void setOptionalData(int[] optionalData) {
+ this.optionalData = optionalData;
+ }
+
+ public boolean isLastSegment() {
+ return lastSegment;
+ }
+
+ public void setLastSegment(boolean lastSegment) {
+ this.lastSegment = lastSegment;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Writer.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Writer.java
new file mode 100644
index 000000000..0b4526c47
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/PDF417Writer.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 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.pdf417;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.Writer;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.pdf417.encoder.Compaction;
+import com.google.zxing.pdf417.encoder.Dimensions;
+import com.google.zxing.pdf417.encoder.PDF417;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+
+/**
+ * @author Jacob Haynes
+ * @author qwandor@google.com (Andrew Walbran)
+ */
+public final class PDF417Writer implements Writer {
+
+ /**
+ * default white space (margin) around the code
+ */
+ static final int WHITE_SPACE = 30;
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.PDF_417) {
+ throw new IllegalArgumentException("Can only encode PDF_417, but got " + format);
+ }
+
+ PDF417 encoder = new PDF417();
+ int margin = WHITE_SPACE;
+
+ if (hints != null) {
+ if (hints.containsKey(EncodeHintType.PDF417_COMPACT)) {
+ encoder.setCompact((Boolean) hints.get(EncodeHintType.PDF417_COMPACT));
+ }
+ if (hints.containsKey(EncodeHintType.PDF417_COMPACTION)) {
+ encoder.setCompaction((Compaction) hints.get(EncodeHintType.PDF417_COMPACTION));
+ }
+ if (hints.containsKey(EncodeHintType.PDF417_DIMENSIONS)) {
+ Dimensions dimensions = (Dimensions) hints.get(EncodeHintType.PDF417_DIMENSIONS);
+ encoder.setDimensions(dimensions.getMaxCols(),
+ dimensions.getMinCols(),
+ dimensions.getMaxRows(),
+ dimensions.getMinRows());
+ }
+ if (hints.containsKey(EncodeHintType.MARGIN)) {
+ margin = ((Number) hints.get(EncodeHintType.MARGIN)).intValue();
+ }
+ if (hints.containsKey(EncodeHintType.CHARACTER_SET)) {
+ String encoding = (String) hints.get(EncodeHintType.CHARACTER_SET);
+ encoder.setEncoding(Charset.forName(encoding));
+ }
+ }
+
+ return bitMatrixFromEncoder(encoder, contents, width, height, margin);
+ }
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height) throws WriterException {
+ return encode(contents, format, width, height, null);
+ }
+
+ /**
+ * Takes encoder, accounts for width/height, and retrieves bit matrix
+ */
+ private static BitMatrix bitMatrixFromEncoder(PDF417 encoder,
+ String contents,
+ int width,
+ int height,
+ int margin) throws WriterException {
+ int errorCorrectionLevel = 2;
+ encoder.generateBarcodeLogic(contents, errorCorrectionLevel);
+
+ int lineThickness = 2;
+ int aspectRatio = 4;
+ byte[][] originalScale = encoder.getBarcodeMatrix().getScaledMatrix(lineThickness, aspectRatio * lineThickness);
+ boolean rotated = false;
+ if ((height > width) ^ (originalScale[0].length < originalScale.length)) {
+ originalScale = rotateArray(originalScale);
+ rotated = true;
+ }
+
+ int scaleX = width / originalScale[0].length;
+ int scaleY = height / originalScale.length;
+
+ int scale;
+ if (scaleX < scaleY) {
+ scale = scaleX;
+ } else {
+ scale = scaleY;
+ }
+
+ if (scale > 1) {
+ byte[][] scaledMatrix =
+ encoder.getBarcodeMatrix().getScaledMatrix(scale * lineThickness, scale * aspectRatio * lineThickness);
+ if (rotated) {
+ scaledMatrix = rotateArray(scaledMatrix);
+ }
+ return bitMatrixFrombitArray(scaledMatrix, margin);
+ }
+ return bitMatrixFrombitArray(originalScale, margin);
+ }
+
+ /**
+ * This takes an array holding the values of the PDF 417
+ *
+ * @param input a byte array of information with 0 is black, and 1 is white
+ * @param margin border around the barcode
+ * @return BitMatrix of the input
+ */
+ private static BitMatrix bitMatrixFrombitArray(byte[][] input, int margin) {
+ // Creates the bitmatrix with extra space for whitespace
+ BitMatrix output = new BitMatrix(input[0].length + 2 * margin, input.length + 2 * margin);
+ output.clear();
+ for (int y = 0, yOutput = output.getHeight() - margin - 1; y < input.length; y++, yOutput--) {
+ for (int x = 0; x < input[0].length; x++) {
+ // Zero is white in the bytematrix
+ if (input[y][x] == 1) {
+ output.set(x + margin, yOutput);
+ }
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Takes and rotates the it 90 degrees
+ */
+ private static byte[][] rotateArray(byte[][] bitarray) {
+ byte[][] temp = new byte[bitarray[0].length][bitarray.length];
+ for (int ii = 0; ii < bitarray.length; ii++) {
+ // This makes the direction consistent on screen when rotating the
+ // screen;
+ int inverseii = bitarray.length - ii - 1;
+ for (int jj = 0; jj < bitarray[0].length; jj++) {
+ temp[jj][inverseii] = bitarray[ii][jj];
+ }
+ }
+ return temp;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BarcodeMetadata.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BarcodeMetadata.java
new file mode 100644
index 000000000..9c1acbf7b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BarcodeMetadata.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 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;
+
+/**
+ * @author Guenther Grau
+ */
+final class BarcodeMetadata {
+
+ private final int columnCount;
+ private final int errorCorrectionLevel;
+ private final int rowCountUpperPart;
+ private final int rowCountLowerPart;
+ private final int rowCount;
+
+ BarcodeMetadata(int columnCount, int rowCountUpperPart, int rowCountLowerPart, int errorCorrectionLevel) {
+ this.columnCount = columnCount;
+ this.errorCorrectionLevel = errorCorrectionLevel;
+ this.rowCountUpperPart = rowCountUpperPart;
+ this.rowCountLowerPart = rowCountLowerPart;
+ this.rowCount = rowCountUpperPart + rowCountLowerPart;
+ }
+
+ int getColumnCount() {
+ return columnCount;
+ }
+
+ int getErrorCorrectionLevel() {
+ return errorCorrectionLevel;
+ }
+
+ int getRowCount() {
+ return rowCount;
+ }
+
+ int getRowCountUpperPart() {
+ return rowCountUpperPart;
+ }
+
+ int getRowCountLowerPart() {
+ return rowCountLowerPart;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BarcodeValue.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BarcodeValue.java
new file mode 100644
index 000000000..79a8d23af
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BarcodeValue.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2013 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.pdf417.PDF417Common;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/**
+ * @author Guenther Grau
+ */
+final class BarcodeValue {
+ private final Map values = new HashMap<>();
+
+ /**
+ * Add an occurrence of a value
+ */
+ void setValue(int value) {
+ Integer confidence = values.get(value);
+ if (confidence == null) {
+ confidence = 0;
+ }
+ confidence++;
+ values.put(value, confidence);
+ }
+
+ /**
+ * Determines the maximum occurrence of a set value and returns all values which were set with this occurrence.
+ * @return an array of int, containing the values with the highest occurrence, or null, if no value was set
+ */
+ int[] getValue() {
+ int maxConfidence = -1;
+ Collection result = new ArrayList<>();
+ for (Entry entry : values.entrySet()) {
+ if (entry.getValue() > maxConfidence) {
+ maxConfidence = entry.getValue();
+ result.clear();
+ result.add(entry.getKey());
+ } else if (entry.getValue() == maxConfidence) {
+ result.add(entry.getKey());
+ }
+ }
+ return PDF417Common.toIntArray(result);
+ }
+
+ public Integer getConfidence(int value) {
+ return values.get(value);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BoundingBox.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BoundingBox.java
new file mode 100644
index 000000000..7273e0c91
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/BoundingBox.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2013 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.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * @author Guenther Grau
+ */
+final class BoundingBox {
+
+ private BitMatrix image;
+ private ResultPoint topLeft;
+ private ResultPoint bottomLeft;
+ private ResultPoint topRight;
+ private ResultPoint bottomRight;
+ private int minX;
+ private int maxX;
+ private int minY;
+ private int maxY;
+
+ BoundingBox(BitMatrix image,
+ ResultPoint topLeft,
+ ResultPoint bottomLeft,
+ ResultPoint topRight,
+ ResultPoint bottomRight) throws NotFoundException {
+ if ((topLeft == null && topRight == null) ||
+ (bottomLeft == null && bottomRight == null) ||
+ (topLeft != null && bottomLeft == null) ||
+ (topRight != null && bottomRight == null)) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ init(image, topLeft, bottomLeft, topRight, bottomRight);
+ }
+
+ BoundingBox(BoundingBox boundingBox) {
+ init(boundingBox.image, boundingBox.topLeft, boundingBox.bottomLeft, boundingBox.topRight, boundingBox.bottomRight);
+ }
+
+ private void init(BitMatrix image,
+ ResultPoint topLeft,
+ ResultPoint bottomLeft,
+ ResultPoint topRight,
+ ResultPoint bottomRight) {
+ this.image = image;
+ this.topLeft = topLeft;
+ this.bottomLeft = bottomLeft;
+ this.topRight = topRight;
+ this.bottomRight = bottomRight;
+ calculateMinMaxValues();
+ }
+
+ static BoundingBox merge(BoundingBox leftBox, BoundingBox rightBox) throws NotFoundException {
+ if (leftBox == null) {
+ return rightBox;
+ }
+ if (rightBox == null) {
+ return leftBox;
+ }
+ return new BoundingBox(leftBox.image, leftBox.topLeft, leftBox.bottomLeft, rightBox.topRight, rightBox.bottomRight);
+ }
+
+ BoundingBox addMissingRows(int missingStartRows, int missingEndRows, boolean isLeft) throws NotFoundException {
+ ResultPoint newTopLeft = topLeft;
+ ResultPoint newBottomLeft = bottomLeft;
+ ResultPoint newTopRight = topRight;
+ ResultPoint newBottomRight = bottomRight;
+
+ if (missingStartRows > 0) {
+ ResultPoint top = isLeft ? topLeft : topRight;
+ int newMinY = (int) top.getY() - missingStartRows;
+ if (newMinY < 0) {
+ newMinY = 0;
+ }
+ // TODO use existing points to better interpolate the new x positions
+ ResultPoint newTop = new ResultPoint(top.getX(), newMinY);
+ if (isLeft) {
+ newTopLeft = newTop;
+ } else {
+ newTopRight = newTop;
+ }
+ }
+
+ if (missingEndRows > 0) {
+ ResultPoint bottom = isLeft ? bottomLeft : bottomRight;
+ int newMaxY = (int) bottom.getY() + missingEndRows;
+ if (newMaxY >= image.getHeight()) {
+ newMaxY = image.getHeight() - 1;
+ }
+ // TODO use existing points to better interpolate the new x positions
+ ResultPoint newBottom = new ResultPoint(bottom.getX(), newMaxY);
+ if (isLeft) {
+ newBottomLeft = newBottom;
+ } else {
+ newBottomRight = newBottom;
+ }
+ }
+
+ calculateMinMaxValues();
+ return new BoundingBox(image, newTopLeft, newBottomLeft, newTopRight, newBottomRight);
+ }
+
+ private void calculateMinMaxValues() {
+ if (topLeft == null) {
+ topLeft = new ResultPoint(0, topRight.getY());
+ bottomLeft = new ResultPoint(0, bottomRight.getY());
+ } else if (topRight == null) {
+ topRight = new ResultPoint(image.getWidth() - 1, topLeft.getY());
+ bottomRight = new ResultPoint(image.getWidth() - 1, bottomLeft.getY());
+ }
+
+ minX = (int) Math.min(topLeft.getX(), bottomLeft.getX());
+ maxX = (int) Math.max(topRight.getX(), bottomRight.getX());
+ minY = (int) Math.min(topLeft.getY(), topRight.getY());
+ maxY = (int) Math.max(bottomLeft.getY(), bottomRight.getY());
+ }
+
+ /*
+ void setTopRight(ResultPoint topRight) {
+ this.topRight = topRight;
+ calculateMinMaxValues();
+ }
+
+ void setBottomRight(ResultPoint bottomRight) {
+ this.bottomRight = bottomRight;
+ calculateMinMaxValues();
+ }
+ */
+
+ int getMinX() {
+ return minX;
+ }
+
+ int getMaxX() {
+ return maxX;
+ }
+
+ int getMinY() {
+ return minY;
+ }
+
+ int getMaxY() {
+ return maxY;
+ }
+
+ ResultPoint getTopLeft() {
+ return topLeft;
+ }
+
+ ResultPoint getTopRight() {
+ return topRight;
+ }
+
+ ResultPoint getBottomLeft() {
+ return bottomLeft;
+ }
+
+ ResultPoint getBottomRight() {
+ return bottomRight;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/Codeword.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/Codeword.java
new file mode 100644
index 000000000..bb5477da3
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/Codeword.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2013 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;
+
+/**
+ * @author Guenther Grau
+ */
+final class Codeword {
+
+ private static final int BARCODE_ROW_UNKNOWN = -1;
+
+ private final int startX;
+ private final int endX;
+ private final int bucket;
+ private final int value;
+ private int rowNumber = BARCODE_ROW_UNKNOWN;
+
+ Codeword(int startX, int endX, int bucket, int value) {
+ this.startX = startX;
+ this.endX = endX;
+ this.bucket = bucket;
+ this.value = value;
+ }
+
+ boolean hasValidRowNumber() {
+ return isValidRowNumber(rowNumber);
+ }
+
+ boolean isValidRowNumber(int rowNumber) {
+ return rowNumber != BARCODE_ROW_UNKNOWN && bucket == (rowNumber % 3) * 3;
+ }
+
+ void setRowNumberAsRowIndicatorColumn() {
+ rowNumber = (value / 30) * 3 + bucket / 3;
+ }
+
+ int getWidth() {
+ return endX - startX;
+ }
+
+ int getStartX() {
+ return startX;
+ }
+
+ int getEndX() {
+ return endX;
+ }
+
+ int getBucket() {
+ return bucket;
+ }
+
+ int getValue() {
+ return value;
+ }
+
+ int getRowNumber() {
+ return rowNumber;
+ }
+
+ void setRowNumber(int rowNumber) {
+ this.rowNumber = rowNumber;
+ }
+
+ @Override
+ public String toString() {
+ return rowNumber + "|" + value;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java
new file mode 100644
index 000000000..ab97a5597
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java
@@ -0,0 +1,611 @@
+/*
+ * 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.common.DecoderResult;
+import com.google.zxing.pdf417.PDF417ResultMetadata;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+
+/**
+ * This class contains the methods for decoding the PDF417 codewords.
+ *
+ * @author SITA Lab (kevin.osullivan@sita.aero)
+ * @author Guenther Grau
+ */
+final class DecodedBitStreamParser {
+
+ private enum Mode {
+ ALPHA,
+ LOWER,
+ MIXED,
+ PUNCT,
+ ALPHA_SHIFT,
+ PUNCT_SHIFT
+ }
+
+ private static final int TEXT_COMPACTION_MODE_LATCH = 900;
+ private static final int BYTE_COMPACTION_MODE_LATCH = 901;
+ private static final int NUMERIC_COMPACTION_MODE_LATCH = 902;
+ private static final int BYTE_COMPACTION_MODE_LATCH_6 = 924;
+ private static final int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928;
+ private static final int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923;
+ private static final int MACRO_PDF417_TERMINATOR = 922;
+ private static final int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913;
+ private static final int MAX_NUMERIC_CODEWORDS = 15;
+
+ private static final int PL = 25;
+ private static final int LL = 27;
+ private static final int AS = 27;
+ private static final int ML = 28;
+ private static final int AL = 28;
+ private static final int PS = 29;
+ private static final int PAL = 29;
+
+ private static final char[] PUNCT_CHARS = {
+ ';', '<', '>', '@', '[', '\\', '}', '_', '`', '~', '!',
+ '\r', '\t', ',', ':', '\n', '-', '.', '$', '/', '"', '|', '*',
+ '(', ')', '?', '{', '}', '\''};
+
+ private static final char[] MIXED_CHARS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '&',
+ '\r', '\t', ',', ':', '#', '-', '.', '$', '/', '+', '%', '*',
+ '=', '^'};
+
+ /**
+ * Table containing values for the exponent of 900.
+ * This is used in the numeric compaction decode algorithm.
+ */
+ private static final BigInteger[] EXP900;
+ static {
+ EXP900 = new BigInteger[16];
+ EXP900[0] = BigInteger.ONE;
+ BigInteger nineHundred = BigInteger.valueOf(900);
+ EXP900[1] = nineHundred;
+ for (int i = 2; i < EXP900.length; i++) {
+ EXP900[i] = EXP900[i - 1].multiply(nineHundred);
+ }
+ }
+
+ private static final int NUMBER_OF_SEQUENCE_CODEWORDS = 2;
+
+ private DecodedBitStreamParser() {
+ }
+
+ static DecoderResult decode(int[] codewords, String ecLevel) throws FormatException {
+ StringBuilder result = new StringBuilder(codewords.length * 2);
+ // Get compaction mode
+ int codeIndex = 1;
+ int code = codewords[codeIndex++];
+ PDF417ResultMetadata resultMetadata = new PDF417ResultMetadata();
+ while (codeIndex < codewords[0]) {
+ switch (code) {
+ case TEXT_COMPACTION_MODE_LATCH:
+ codeIndex = textCompaction(codewords, codeIndex, result);
+ break;
+ case BYTE_COMPACTION_MODE_LATCH:
+ case BYTE_COMPACTION_MODE_LATCH_6:
+ case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
+ codeIndex = byteCompaction(code, codewords, codeIndex, result);
+ break;
+ case NUMERIC_COMPACTION_MODE_LATCH:
+ codeIndex = numericCompaction(codewords, codeIndex, result);
+ break;
+ case BEGIN_MACRO_PDF417_CONTROL_BLOCK:
+ codeIndex = decodeMacroBlock(codewords, codeIndex, resultMetadata);
+ break;
+ case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
+ case MACRO_PDF417_TERMINATOR:
+ // Should not see these outside a macro block
+ throw FormatException.getFormatInstance();
+ default:
+ // Default to text compaction. During testing numerous barcodes
+ // appeared to be missing the starting mode. In these cases defaulting
+ // to text compaction seems to work.
+ codeIndex--;
+ codeIndex = textCompaction(codewords, codeIndex, result);
+ break;
+ }
+ if (codeIndex < codewords.length) {
+ code = codewords[codeIndex++];
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ }
+ if (result.length() == 0) {
+ throw FormatException.getFormatInstance();
+ }
+ DecoderResult decoderResult = new DecoderResult(null, result.toString(), null, ecLevel);
+ decoderResult.setOther(resultMetadata);
+ return decoderResult;
+ }
+
+ private 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
+ throw FormatException.getFormatInstance();
+ }
+ int[] segmentIndexArray = new int[NUMBER_OF_SEQUENCE_CODEWORDS];
+ for (int i = 0; i < NUMBER_OF_SEQUENCE_CODEWORDS; i++, codeIndex++) {
+ segmentIndexArray[i] = codewords[codeIndex];
+ }
+ resultMetadata.setSegmentIndex(Integer.parseInt(decodeBase900toBase10(segmentIndexArray,
+ NUMBER_OF_SEQUENCE_CODEWORDS)));
+
+ StringBuilder fileId = new StringBuilder();
+ codeIndex = textCompaction(codewords, codeIndex, fileId);
+ resultMetadata.setFileId(fileId.toString());
+
+ if (codewords[codeIndex] == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) {
+ codeIndex++;
+ int[] additionalOptionCodeWords = new int[codewords[0] - codeIndex];
+ int additionalOptionCodeWordsIndex = 0;
+
+ 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();
+ }
+ }
+ }
+
+ resultMetadata.setOptionalData(Arrays.copyOf(additionalOptionCodeWords, additionalOptionCodeWordsIndex));
+ } else if (codewords[codeIndex] == MACRO_PDF417_TERMINATOR) {
+ resultMetadata.setLastSegment(true);
+ codeIndex++;
+ }
+
+ return codeIndex;
+ }
+
+ /**
+ * Text Compaction mode (see 5.4.1.5) permits all printable ASCII characters to be
+ * encoded, i.e. values 32 - 126 inclusive in accordance with ISO/IEC 646 (IRV), as
+ * well as selected control characters.
+ *
+ * @param codewords The array of codewords (data + error)
+ * @param codeIndex The current index into the codeword array.
+ * @param result The decoded data is appended to the result.
+ * @return The next index into the codeword array.
+ */
+ private static int textCompaction(int[] codewords, int codeIndex, StringBuilder result) {
+ // 2 character per codeword
+ int[] textCompactionData = new int[(codewords[0] - codeIndex) << 1];
+ // Used to hold the byte compaction value if there is a mode shift
+ int[] byteCompactionData = new int[(codewords[0] - codeIndex) << 1];
+
+ int index = 0;
+ boolean end = false;
+ while ((codeIndex < codewords[0]) && !end) {
+ int code = codewords[codeIndex++];
+ if (code < TEXT_COMPACTION_MODE_LATCH) {
+ textCompactionData[index] = code / 30;
+ textCompactionData[index + 1] = code % 30;
+ index += 2;
+ } else {
+ switch (code) {
+ case TEXT_COMPACTION_MODE_LATCH:
+ // reinitialize text compaction mode to alpha sub mode
+ textCompactionData[index++] = TEXT_COMPACTION_MODE_LATCH;
+ break;
+ case BYTE_COMPACTION_MODE_LATCH:
+ case BYTE_COMPACTION_MODE_LATCH_6:
+ case NUMERIC_COMPACTION_MODE_LATCH:
+ case BEGIN_MACRO_PDF417_CONTROL_BLOCK:
+ case BEGIN_MACRO_PDF417_OPTIONAL_FIELD:
+ case MACRO_PDF417_TERMINATOR:
+ codeIndex--;
+ end = true;
+ break;
+ case MODE_SHIFT_TO_BYTE_COMPACTION_MODE:
+ // The Mode Shift codeword 913 shall cause a temporary
+ // switch from Text Compaction mode to Byte Compaction mode.
+ // This switch shall be in effect for only the next codeword,
+ // after which the mode shall revert to the prevailing sub-mode
+ // of the Text Compaction mode. Codeword 913 is only available
+ // in Text Compaction mode; its use is described in 5.4.2.4.
+ textCompactionData[index] = MODE_SHIFT_TO_BYTE_COMPACTION_MODE;
+ code = codewords[codeIndex++];
+ byteCompactionData[index] = code;
+ index++;
+ break;
+ }
+ }
+ }
+ decodeTextCompaction(textCompactionData, byteCompactionData, index, result);
+ return codeIndex;
+ }
+
+ /**
+ * The Text Compaction mode includes all the printable ASCII characters
+ * (i.e. values from 32 to 126) and three ASCII control characters: HT or tab
+ * (ASCII value 9), LF or line feed (ASCII value 10), and CR or carriage
+ * return (ASCII value 13). The Text Compaction mode also includes various latch
+ * and shift characters which are used exclusively within the mode. The Text
+ * Compaction mode encodes up to 2 characters per codeword. The compaction rules
+ * for converting data into PDF417 codewords are defined in 5.4.2.2. The sub-mode
+ * switches are defined in 5.4.2.3.
+ *
+ * @param textCompactionData The text compaction data.
+ * @param byteCompactionData The byte compaction data if there
+ * was a mode shift.
+ * @param length The size of the text compaction and byte compaction data.
+ * @param result The decoded data is appended to the result.
+ */
+ private static void decodeTextCompaction(int[] textCompactionData,
+ int[] byteCompactionData,
+ int length,
+ StringBuilder result) {
+ // Beginning from an initial state of the Alpha sub-mode
+ // The default compaction mode for PDF417 in effect at the start of each symbol shall always be Text
+ // Compaction mode Alpha sub-mode (uppercase alphabetic). A latch codeword from another mode to the Text
+ // Compaction mode shall always switch to the Text Compaction Alpha sub-mode.
+ Mode subMode = Mode.ALPHA;
+ Mode priorToShiftMode = Mode.ALPHA;
+ int i = 0;
+ while (i < length) {
+ int subModeCh = textCompactionData[i];
+ char ch = 0;
+ switch (subMode) {
+ case ALPHA:
+ // Alpha (uppercase alphabetic)
+ if (subModeCh < 26) {
+ // Upper case Alpha Character
+ ch = (char) ('A' + subModeCh);
+ } else {
+ if (subModeCh == 26) {
+ ch = ' ';
+ } else if (subModeCh == LL) {
+ subMode = Mode.LOWER;
+ } else if (subModeCh == ML) {
+ subMode = Mode.MIXED;
+ } else if (subModeCh == PS) {
+ // Shift to punctuation
+ priorToShiftMode = subMode;
+ subMode = Mode.PUNCT_SHIFT;
+ } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
+ result.append((char) byteCompactionData[i]);
+ } else if (subModeCh == TEXT_COMPACTION_MODE_LATCH) {
+ subMode = Mode.ALPHA;
+ }
+ }
+ break;
+
+ case LOWER:
+ // Lower (lowercase alphabetic)
+ if (subModeCh < 26) {
+ ch = (char) ('a' + subModeCh);
+ } else {
+ if (subModeCh == 26) {
+ ch = ' ';
+ } else if (subModeCh == AS) {
+ // Shift to alpha
+ priorToShiftMode = subMode;
+ subMode = Mode.ALPHA_SHIFT;
+ } else if (subModeCh == ML) {
+ subMode = Mode.MIXED;
+ } else if (subModeCh == PS) {
+ // Shift to punctuation
+ priorToShiftMode = subMode;
+ subMode = Mode.PUNCT_SHIFT;
+ } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
+ result.append((char) byteCompactionData[i]);
+ } else if (subModeCh == TEXT_COMPACTION_MODE_LATCH) {
+ subMode = Mode.ALPHA;
+ }
+ }
+ break;
+
+ case MIXED:
+ // Mixed (numeric and some punctuation)
+ if (subModeCh < PL) {
+ ch = MIXED_CHARS[subModeCh];
+ } else {
+ if (subModeCh == PL) {
+ subMode = Mode.PUNCT;
+ } else if (subModeCh == 26) {
+ ch = ' ';
+ } else if (subModeCh == LL) {
+ subMode = Mode.LOWER;
+ } else if (subModeCh == AL) {
+ subMode = Mode.ALPHA;
+ } else if (subModeCh == PS) {
+ // Shift to punctuation
+ priorToShiftMode = subMode;
+ subMode = Mode.PUNCT_SHIFT;
+ } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
+ result.append((char) byteCompactionData[i]);
+ } else if (subModeCh == TEXT_COMPACTION_MODE_LATCH) {
+ subMode = Mode.ALPHA;
+ }
+ }
+ break;
+
+ case PUNCT:
+ // Punctuation
+ if (subModeCh < PAL) {
+ ch = PUNCT_CHARS[subModeCh];
+ } else {
+ if (subModeCh == PAL) {
+ subMode = Mode.ALPHA;
+ } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
+ result.append((char) byteCompactionData[i]);
+ } else if (subModeCh == TEXT_COMPACTION_MODE_LATCH) {
+ subMode = Mode.ALPHA;
+ }
+ }
+ break;
+
+ case ALPHA_SHIFT:
+ // Restore sub-mode
+ subMode = priorToShiftMode;
+ if (subModeCh < 26) {
+ ch = (char) ('A' + subModeCh);
+ } else {
+ if (subModeCh == 26) {
+ ch = ' ';
+ } else if (subModeCh == TEXT_COMPACTION_MODE_LATCH) {
+ subMode = Mode.ALPHA;
+ }
+ }
+ break;
+
+ case PUNCT_SHIFT:
+ // Restore sub-mode
+ subMode = priorToShiftMode;
+ if (subModeCh < PAL) {
+ ch = PUNCT_CHARS[subModeCh];
+ } else {
+ if (subModeCh == PAL) {
+ subMode = Mode.ALPHA;
+ } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
+ // PS before Shift-to-Byte is used as a padding character,
+ // see 5.4.2.4 of the specification
+ result.append((char) byteCompactionData[i]);
+ } else if (subModeCh == TEXT_COMPACTION_MODE_LATCH) {
+ subMode = Mode.ALPHA;
+ }
+ }
+ break;
+ }
+ if (ch != 0) {
+ // Append decoded character to result
+ result.append(ch);
+ }
+ i++;
+ }
+ }
+
+ /**
+ * Byte Compaction mode (see 5.4.3) permits all 256 possible 8-bit byte values to be encoded.
+ * This includes all ASCII characters value 0 to 127 inclusive and provides for international
+ * character set support.
+ *
+ * @param mode The byte compaction mode i.e. 901 or 924
+ * @param codewords The array of codewords (data + error)
+ * @param codeIndex The current index into the codeword array.
+ * @param result The decoded data is appended to the result.
+ * @return The next index into the codeword array.
+ */
+ private static int byteCompaction(int mode, int[] codewords, int codeIndex, StringBuilder result) {
+ if (mode == BYTE_COMPACTION_MODE_LATCH) {
+ // Total number of Byte Compaction characters to be encoded
+ // is not a multiple of 6
+ int count = 0;
+ long value = 0;
+ char[] decodedData = new char[6];
+ int[] byteCompactedCodewords = new int[6];
+ boolean end = false;
+ int nextCode = codewords[codeIndex++];
+ while ((codeIndex < codewords[0]) && !end) {
+ byteCompactedCodewords[count++] = nextCode;
+ // Base 900
+ value = 900 * value + nextCode;
+ nextCode = codewords[codeIndex++];
+ // perhaps it should be ok to check only nextCode >= TEXT_COMPACTION_MODE_LATCH
+ if (nextCode == TEXT_COMPACTION_MODE_LATCH ||
+ nextCode == BYTE_COMPACTION_MODE_LATCH ||
+ nextCode == NUMERIC_COMPACTION_MODE_LATCH ||
+ nextCode == BYTE_COMPACTION_MODE_LATCH_6 ||
+ nextCode == BEGIN_MACRO_PDF417_CONTROL_BLOCK ||
+ nextCode == BEGIN_MACRO_PDF417_OPTIONAL_FIELD ||
+ nextCode == MACRO_PDF417_TERMINATOR) {
+ codeIndex--;
+ end = true;
+ } else {
+ if ((count % 5 == 0) && (count > 0)) {
+ // Decode every 5 codewords
+ // Convert to Base 256
+ for (int j = 0; j < 6; ++j) {
+ decodedData[5 - j] = (char) (value % 256);
+ value >>= 8;
+ }
+ result.append(decodedData);
+ count = 0;
+ }
+ }
+ }
+
+ // if the end of all codewords is reached the last codeword needs to be added
+ if (codeIndex == codewords[0] && nextCode < TEXT_COMPACTION_MODE_LATCH) {
+ byteCompactedCodewords[count++] = nextCode;
+ }
+
+ // If Byte Compaction mode is invoked with codeword 901,
+ // the last group of codewords is interpreted directly
+ // as one byte per codeword, without compaction.
+ for (int i = 0; i < count; i++) {
+ result.append((char) byteCompactedCodewords[i]);
+ }
+
+ } else if (mode == BYTE_COMPACTION_MODE_LATCH_6) {
+ // Total number of Byte Compaction characters to be encoded
+ // is an integer multiple of 6
+ int count = 0;
+ long value = 0;
+ boolean end = false;
+ while (codeIndex < codewords[0] && !end) {
+ int code = codewords[codeIndex++];
+ if (code < TEXT_COMPACTION_MODE_LATCH) {
+ count++;
+ // Base 900
+ value = 900 * value + code;
+ } else {
+ if (code == TEXT_COMPACTION_MODE_LATCH ||
+ code == BYTE_COMPACTION_MODE_LATCH ||
+ code == NUMERIC_COMPACTION_MODE_LATCH ||
+ code == BYTE_COMPACTION_MODE_LATCH_6 ||
+ code == BEGIN_MACRO_PDF417_CONTROL_BLOCK ||
+ code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD ||
+ code == MACRO_PDF417_TERMINATOR) {
+ codeIndex--;
+ end = true;
+ }
+ }
+ if ((count % 5 == 0) && (count > 0)) {
+ // Decode every 5 codewords
+ // Convert to Base 256
+ char[] decodedData = new char[6];
+ for (int j = 0; j < 6; ++j) {
+ decodedData[5 - j] = (char) (value & 0xFF);
+ value >>= 8;
+ }
+ result.append(decodedData);
+ count = 0;
+ }
+ }
+ }
+ return codeIndex;
+ }
+
+ /**
+ * Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings.
+ *
+ * @param codewords The array of codewords (data + error)
+ * @param codeIndex The current index into the codeword array.
+ * @param result The decoded data is appended to the result.
+ * @return The next index into the codeword array.
+ */
+ private static int numericCompaction(int[] codewords, int codeIndex, StringBuilder result) throws FormatException {
+ int count = 0;
+ boolean end = false;
+
+ int[] numericCodewords = new int[MAX_NUMERIC_CODEWORDS];
+
+ while (codeIndex < codewords[0] && !end) {
+ int code = codewords[codeIndex++];
+ if (codeIndex == codewords[0]) {
+ end = true;
+ }
+ if (code < TEXT_COMPACTION_MODE_LATCH) {
+ numericCodewords[count] = code;
+ count++;
+ } else {
+ if (code == TEXT_COMPACTION_MODE_LATCH ||
+ code == BYTE_COMPACTION_MODE_LATCH ||
+ code == BYTE_COMPACTION_MODE_LATCH_6 ||
+ code == BEGIN_MACRO_PDF417_CONTROL_BLOCK ||
+ code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD ||
+ code == MACRO_PDF417_TERMINATOR) {
+ codeIndex--;
+ end = true;
+ }
+ }
+ if (count % MAX_NUMERIC_CODEWORDS == 0 ||
+ code == NUMERIC_COMPACTION_MODE_LATCH ||
+ end) {
+ // Re-invoking Numeric Compaction mode (by using codeword 902
+ // while in Numeric Compaction mode) serves to terminate the
+ // current Numeric Compaction mode grouping as described in 5.4.4.2,
+ // and then to start a new one grouping.
+ String s = decodeBase900toBase10(numericCodewords, count);
+ result.append(s);
+ count = 0;
+ }
+ }
+ return codeIndex;
+ }
+
+ /**
+ * Convert a list of Numeric Compacted codewords from Base 900 to Base 10.
+ *
+ * @param codewords The array of codewords
+ * @param count The number of codewords
+ * @return The decoded string representing the Numeric data.
+ */
+ /*
+ EXAMPLE
+ Encode the fifteen digit numeric string 000213298174000
+ Prefix the numeric string with a 1 and set the initial value of
+ t = 1 000 213 298 174 000
+ Calculate codeword 0
+ d0 = 1 000 213 298 174 000 mod 900 = 200
+
+ t = 1 000 213 298 174 000 div 900 = 1 111 348 109 082
+ Calculate codeword 1
+ d1 = 1 111 348 109 082 mod 900 = 282
+
+ t = 1 111 348 109 082 div 900 = 1 234 831 232
+ Calculate codeword 2
+ d2 = 1 234 831 232 mod 900 = 632
+
+ t = 1 234 831 232 div 900 = 1 372 034
+ Calculate codeword 3
+ d3 = 1 372 034 mod 900 = 434
+
+ t = 1 372 034 div 900 = 1 524
+ Calculate codeword 4
+ d4 = 1 524 mod 900 = 624
+
+ t = 1 524 div 900 = 1
+ Calculate codeword 5
+ d5 = 1 mod 900 = 1
+ t = 1 div 900 = 0
+ Codeword sequence is: 1, 624, 434, 632, 282, 200
+
+ Decode the above codewords involves
+ 1 x 900 power of 5 + 624 x 900 power of 4 + 434 x 900 power of 3 +
+ 632 x 900 power of 2 + 282 x 900 power of 1 + 200 x 900 power of 0 = 1000213298174000
+
+ Remove leading 1 => Result is 000213298174000
+ */
+ private static String decodeBase900toBase10(int[] codewords, int count) throws FormatException {
+ BigInteger result = BigInteger.ZERO;
+ for (int i = 0; i < count; i++) {
+ result = result.add(EXP900[count - i - 1].multiply(BigInteger.valueOf(codewords[i])));
+ }
+ String resultString = result.toString();
+ if (resultString.charAt(0) != '1') {
+ throw FormatException.getFormatInstance();
+ }
+ return resultString.substring(1);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResult.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResult.java
new file mode 100644
index 000000000..74892ee54
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResult.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright 2013 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.pdf417.PDF417Common;
+
+import java.util.Formatter;
+
+/**
+ * @author Guenther Grau
+ */
+final class DetectionResult {
+
+ private static final int ADJUST_ROW_NUMBER_SKIP = 2;
+
+ private final BarcodeMetadata barcodeMetadata;
+ private final DetectionResultColumn[] detectionResultColumns;
+ private BoundingBox boundingBox;
+ private final int barcodeColumnCount;
+
+ DetectionResult(BarcodeMetadata barcodeMetadata, BoundingBox boundingBox) {
+ this.barcodeMetadata = barcodeMetadata;
+ this.barcodeColumnCount = barcodeMetadata.getColumnCount();
+ this.boundingBox = boundingBox;
+ detectionResultColumns = new DetectionResultColumn[barcodeColumnCount + 2];
+ }
+
+ DetectionResultColumn[] getDetectionResultColumns() {
+ adjustIndicatorColumnRowNumbers(detectionResultColumns[0]);
+ adjustIndicatorColumnRowNumbers(detectionResultColumns[barcodeColumnCount + 1]);
+ int unadjustedCodewordCount = PDF417Common.MAX_CODEWORDS_IN_BARCODE;
+ int previousUnadjustedCount;
+ do {
+ previousUnadjustedCount = unadjustedCodewordCount;
+ unadjustedCodewordCount = adjustRowNumbers();
+ } while (unadjustedCodewordCount > 0 && unadjustedCodewordCount < previousUnadjustedCount);
+ return detectionResultColumns;
+ }
+
+ private void adjustIndicatorColumnRowNumbers(DetectionResultColumn detectionResultColumn) {
+ if (detectionResultColumn != null) {
+ ((DetectionResultRowIndicatorColumn) detectionResultColumn)
+ .adjustCompleteIndicatorColumnRowNumbers(barcodeMetadata);
+ }
+ }
+
+ // TODO ensure that no detected codewords with unknown row number are left
+ // we should be able to estimate the row height and use it as a hint for the row number
+ // we should also fill the rows top to bottom and bottom to top
+ /**
+ * @return number of codewords which don't have a valid row number. Note that the count is not accurate as codewords
+ * will be counted several times. It just serves as an indicator to see when we can stop adjusting row numbers
+ */
+ private int adjustRowNumbers() {
+ int unadjustedCount = adjustRowNumbersByRow();
+ if (unadjustedCount == 0) {
+ return 0;
+ }
+ for (int barcodeColumn = 1; barcodeColumn < barcodeColumnCount + 1; barcodeColumn++) {
+ Codeword[] codewords = detectionResultColumns[barcodeColumn].getCodewords();
+ for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) {
+ if (codewords[codewordsRow] == null) {
+ continue;
+ }
+ if (!codewords[codewordsRow].hasValidRowNumber()) {
+ adjustRowNumbers(barcodeColumn, codewordsRow, codewords);
+ }
+ }
+ }
+ return unadjustedCount;
+ }
+
+ private int adjustRowNumbersByRow() {
+ adjustRowNumbersFromBothRI();
+ // TODO we should only do full row adjustments if row numbers of left and right row indicator column match.
+ // Maybe it's even better to calculated the height (in codeword rows) and divide it by the number of barcode
+ // rows. This, together with the LRI and RRI row numbers should allow us to get a good estimate where a row
+ // number starts and ends.
+ int unadjustedCount = adjustRowNumbersFromLRI();
+ return unadjustedCount + adjustRowNumbersFromRRI();
+ }
+
+ private void adjustRowNumbersFromBothRI() {
+ if (detectionResultColumns[0] == null || detectionResultColumns[barcodeColumnCount + 1] == null) {
+ return;
+ }
+ Codeword[] LRIcodewords = detectionResultColumns[0].getCodewords();
+ Codeword[] RRIcodewords = detectionResultColumns[barcodeColumnCount + 1].getCodewords();
+ for (int codewordsRow = 0; codewordsRow < LRIcodewords.length; codewordsRow++) {
+ if (LRIcodewords[codewordsRow] != null &&
+ RRIcodewords[codewordsRow] != null &&
+ LRIcodewords[codewordsRow].getRowNumber() == RRIcodewords[codewordsRow].getRowNumber()) {
+ for (int barcodeColumn = 1; barcodeColumn <= barcodeColumnCount; barcodeColumn++) {
+ Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
+ if (codeword == null) {
+ continue;
+ }
+ codeword.setRowNumber(LRIcodewords[codewordsRow].getRowNumber());
+ if (!codeword.hasValidRowNumber()) {
+ detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow] = null;
+ }
+ }
+ }
+ }
+ }
+
+ private int adjustRowNumbersFromRRI() {
+ if (detectionResultColumns[barcodeColumnCount + 1] == null) {
+ return 0;
+ }
+ int unadjustedCount = 0;
+ Codeword[] codewords = detectionResultColumns[barcodeColumnCount + 1].getCodewords();
+ for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) {
+ if (codewords[codewordsRow] == null) {
+ continue;
+ }
+ int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber();
+ int invalidRowCounts = 0;
+ for (int barcodeColumn = barcodeColumnCount + 1; barcodeColumn > 0 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn--) {
+ Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
+ if (codeword != null) {
+ invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword);
+ if (!codeword.hasValidRowNumber()) {
+ unadjustedCount++;
+ }
+ }
+ }
+ }
+ return unadjustedCount;
+ }
+
+ private int adjustRowNumbersFromLRI() {
+ if (detectionResultColumns[0] == null) {
+ return 0;
+ }
+ int unadjustedCount = 0;
+ Codeword[] codewords = detectionResultColumns[0].getCodewords();
+ for (int codewordsRow = 0; codewordsRow < codewords.length; codewordsRow++) {
+ if (codewords[codewordsRow] == null) {
+ continue;
+ }
+ int rowIndicatorRowNumber = codewords[codewordsRow].getRowNumber();
+ int invalidRowCounts = 0;
+ for (int barcodeColumn = 1; barcodeColumn < barcodeColumnCount + 1 && invalidRowCounts < ADJUST_ROW_NUMBER_SKIP; barcodeColumn++) {
+ Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
+ if (codeword != null) {
+ invalidRowCounts = adjustRowNumberIfValid(rowIndicatorRowNumber, invalidRowCounts, codeword);
+ if (!codeword.hasValidRowNumber()) {
+ unadjustedCount++;
+ }
+ }
+ }
+ }
+ return unadjustedCount;
+ }
+
+ private static int adjustRowNumberIfValid(int rowIndicatorRowNumber, int invalidRowCounts, Codeword codeword) {
+ if (codeword == null) {
+ return invalidRowCounts;
+ }
+ if (!codeword.hasValidRowNumber()) {
+ if (codeword.isValidRowNumber(rowIndicatorRowNumber)) {
+ codeword.setRowNumber(rowIndicatorRowNumber);
+ invalidRowCounts = 0;
+ } else {
+ ++invalidRowCounts;
+ }
+ }
+ return invalidRowCounts;
+ }
+
+ private void adjustRowNumbers(int barcodeColumn, int codewordsRow, Codeword[] codewords) {
+ Codeword codeword = codewords[codewordsRow];
+ Codeword[] previousColumnCodewords = detectionResultColumns[barcodeColumn - 1].getCodewords();
+ Codeword[] nextColumnCodewords = previousColumnCodewords;
+ if (detectionResultColumns[barcodeColumn + 1] != null) {
+ nextColumnCodewords = detectionResultColumns[barcodeColumn + 1].getCodewords();
+ }
+
+ Codeword[] otherCodewords = new Codeword[14];
+
+ otherCodewords[2] = previousColumnCodewords[codewordsRow];
+ otherCodewords[3] = nextColumnCodewords[codewordsRow];
+
+ if (codewordsRow > 0) {
+ otherCodewords[0] = codewords[codewordsRow - 1];
+ otherCodewords[4] = previousColumnCodewords[codewordsRow - 1];
+ otherCodewords[5] = nextColumnCodewords[codewordsRow - 1];
+ }
+ if (codewordsRow > 1) {
+ otherCodewords[8] = codewords[codewordsRow - 2];
+ otherCodewords[10] = previousColumnCodewords[codewordsRow - 2];
+ otherCodewords[11] = nextColumnCodewords[codewordsRow - 2];
+ }
+ if (codewordsRow < codewords.length - 1) {
+ otherCodewords[1] = codewords[codewordsRow + 1];
+ otherCodewords[6] = previousColumnCodewords[codewordsRow + 1];
+ otherCodewords[7] = nextColumnCodewords[codewordsRow + 1];
+ }
+ if (codewordsRow < codewords.length - 2) {
+ otherCodewords[9] = codewords[codewordsRow + 2];
+ otherCodewords[12] = previousColumnCodewords[codewordsRow + 2];
+ otherCodewords[13] = nextColumnCodewords[codewordsRow + 2];
+ }
+ for (Codeword otherCodeword : otherCodewords) {
+ if (adjustRowNumber(codeword, otherCodeword)) {
+ return;
+ }
+ }
+ }
+
+ /**
+ * @return true, if row number was adjusted, false otherwise
+ */
+ private static boolean adjustRowNumber(Codeword codeword, Codeword otherCodeword) {
+ if (otherCodeword == null) {
+ return false;
+ }
+ if (otherCodeword.hasValidRowNumber() && otherCodeword.getBucket() == codeword.getBucket()) {
+ codeword.setRowNumber(otherCodeword.getRowNumber());
+ return true;
+ }
+ return false;
+ }
+
+ int getBarcodeColumnCount() {
+ return barcodeColumnCount;
+ }
+
+ int getBarcodeRowCount() {
+ return barcodeMetadata.getRowCount();
+ }
+
+ int getBarcodeECLevel() {
+ return barcodeMetadata.getErrorCorrectionLevel();
+ }
+
+ public void setBoundingBox(BoundingBox boundingBox) {
+ this.boundingBox = boundingBox;
+ }
+
+ BoundingBox getBoundingBox() {
+ return boundingBox;
+ }
+
+ void setDetectionResultColumn(int barcodeColumn, DetectionResultColumn detectionResultColumn) {
+ detectionResultColumns[barcodeColumn] = detectionResultColumn;
+ }
+
+ DetectionResultColumn getDetectionResultColumn(int barcodeColumn) {
+ return detectionResultColumns[barcodeColumn];
+ }
+
+ @Override
+ public String toString() {
+ DetectionResultColumn rowIndicatorColumn = detectionResultColumns[0];
+ if (rowIndicatorColumn == null) {
+ rowIndicatorColumn = detectionResultColumns[barcodeColumnCount + 1];
+ }
+ Formatter formatter = new Formatter();
+ for (int codewordsRow = 0; codewordsRow < rowIndicatorColumn.getCodewords().length; codewordsRow++) {
+ formatter.format("CW %3d:", codewordsRow);
+ for (int barcodeColumn = 0; barcodeColumn < barcodeColumnCount + 2; barcodeColumn++) {
+ if (detectionResultColumns[barcodeColumn] == null) {
+ formatter.format(" | ");
+ continue;
+ }
+ Codeword codeword = detectionResultColumns[barcodeColumn].getCodewords()[codewordsRow];
+ if (codeword == null) {
+ formatter.format(" | ");
+ continue;
+ }
+ formatter.format(" %3d|%3d", codeword.getRowNumber(), codeword.getValue());
+ }
+ formatter.format("%n");
+ }
+ String result = formatter.toString();
+ formatter.close();
+ return result;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultColumn.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultColumn.java
new file mode 100644
index 000000000..bda0b398f
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultColumn.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2013 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 java.util.Formatter;
+
+/**
+ * @author Guenther Grau
+ */
+class DetectionResultColumn {
+
+ private static final int MAX_NEARBY_DISTANCE = 5;
+
+ private final BoundingBox boundingBox;
+ private final Codeword[] codewords;
+
+ DetectionResultColumn(BoundingBox boundingBox) {
+ this.boundingBox = new BoundingBox(boundingBox);
+ codewords = new Codeword[boundingBox.getMaxY() - boundingBox.getMinY() + 1];
+ }
+
+ final Codeword getCodewordNearby(int imageRow) {
+ Codeword codeword = getCodeword(imageRow);
+ if (codeword != null) {
+ return codeword;
+ }
+ for (int i = 1; i < MAX_NEARBY_DISTANCE; i++) {
+ int nearImageRow = imageRowToCodewordIndex(imageRow) - i;
+ if (nearImageRow >= 0) {
+ codeword = codewords[nearImageRow];
+ if (codeword != null) {
+ return codeword;
+ }
+ }
+ nearImageRow = imageRowToCodewordIndex(imageRow) + i;
+ if (nearImageRow < codewords.length) {
+ codeword = codewords[nearImageRow];
+ if (codeword != null) {
+ return codeword;
+ }
+ }
+ }
+ return null;
+ }
+
+ final int imageRowToCodewordIndex(int imageRow) {
+ return imageRow - boundingBox.getMinY();
+ }
+
+ final void setCodeword(int imageRow, Codeword codeword) {
+ codewords[imageRowToCodewordIndex(imageRow)] = codeword;
+ }
+
+ final Codeword getCodeword(int imageRow) {
+ return codewords[imageRowToCodewordIndex(imageRow)];
+ }
+
+ final BoundingBox getBoundingBox() {
+ return boundingBox;
+ }
+
+ final Codeword[] getCodewords() {
+ return codewords;
+ }
+
+ @Override
+ public String toString() {
+ Formatter formatter = new Formatter();
+ int row = 0;
+ for (Codeword codeword : codewords) {
+ if (codeword == null) {
+ formatter.format("%3d: | %n", row++);
+ continue;
+ }
+ formatter.format("%3d: %3d|%3d%n", row++, codeword.getRowNumber(), codeword.getValue());
+ }
+ String result = formatter.toString();
+ formatter.close();
+ return result;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultRowIndicatorColumn.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultRowIndicatorColumn.java
new file mode 100644
index 000000000..b9484c0c0
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/DetectionResultRowIndicatorColumn.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2013 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.ResultPoint;
+import com.google.zxing.pdf417.PDF417Common;
+
+/**
+ * @author Guenther Grau
+ */
+final class DetectionResultRowIndicatorColumn extends DetectionResultColumn {
+
+ private final boolean isLeft;
+
+ DetectionResultRowIndicatorColumn(BoundingBox boundingBox, boolean isLeft) {
+ super(boundingBox);
+ this.isLeft = isLeft;
+ }
+
+ void setRowNumbers() {
+ for (Codeword codeword : getCodewords()) {
+ if (codeword != null) {
+ codeword.setRowNumberAsRowIndicatorColumn();
+ }
+ }
+ }
+
+ // TODO implement properly
+ // TODO maybe we should add missing codewords to store the correct row number to make
+ // finding row numbers for other columns easier
+ // use row height count to make detection of invalid row numbers more reliable
+ int adjustCompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) {
+ Codeword[] codewords = getCodewords();
+ setRowNumbers();
+ removeIncorrectCodewords(codewords, barcodeMetadata);
+ BoundingBox boundingBox = getBoundingBox();
+ ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight();
+ ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight();
+ int firstRow = imageRowToCodewordIndex((int) top.getY());
+ int lastRow = imageRowToCodewordIndex((int) bottom.getY());
+ // We need to be careful using the average row height. Barcode could be skewed so that we have smaller and
+ // taller rows
+ float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount();
+ int barcodeRow = -1;
+ int maxRowHeight = 1;
+ int currentRowHeight = 0;
+ for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
+ if (codewords[codewordsRow] == null) {
+ continue;
+ }
+ Codeword codeword = codewords[codewordsRow];
+
+ // float expectedRowNumber = (codewordsRow - firstRow) / averageRowHeight;
+ // if (Math.abs(codeword.getRowNumber() - expectedRowNumber) > 2) {
+ // SimpleLog.log(LEVEL.WARNING,
+ // "Removing codeword, rowNumberSkew too high, codeword[" + codewordsRow + "]: Expected Row: " +
+ // expectedRowNumber + ", RealRow: " + codeword.getRowNumber() + ", value: " + codeword.getValue());
+ // codewords[codewordsRow] = null;
+ // }
+
+ int rowDifference = codeword.getRowNumber() - barcodeRow;
+
+ // TODO improve handling with case where first row indicator doesn't start with 0
+
+ if (rowDifference == 0) {
+ currentRowHeight++;
+ } else if (rowDifference == 1) {
+ maxRowHeight = Math.max(maxRowHeight, currentRowHeight);
+ currentRowHeight = 1;
+ barcodeRow = codeword.getRowNumber();
+ } else if (rowDifference < 0 ||
+ codeword.getRowNumber() >= barcodeMetadata.getRowCount() ||
+ rowDifference > codewordsRow) {
+ codewords[codewordsRow] = null;
+ } else {
+ int checkedRows;
+ if (maxRowHeight > 2) {
+ checkedRows = (maxRowHeight - 2) * rowDifference;
+ } else {
+ checkedRows = rowDifference;
+ }
+ boolean closePreviousCodewordFound = checkedRows >= codewordsRow;
+ for (int i = 1; i <= checkedRows && !closePreviousCodewordFound; i++) {
+ // there must be (height * rowDifference) number of codewords missing. For now we assume height = 1.
+ // This should hopefully get rid of most problems already.
+ closePreviousCodewordFound = codewords[codewordsRow - i] != null;
+ }
+ if (closePreviousCodewordFound) {
+ codewords[codewordsRow] = null;
+ } else {
+ barcodeRow = codeword.getRowNumber();
+ currentRowHeight = 1;
+ }
+ }
+ }
+ return (int) (averageRowHeight + 0.5);
+ }
+
+ int[] getRowHeights() throws FormatException {
+ BarcodeMetadata barcodeMetadata = getBarcodeMetadata();
+ if (barcodeMetadata == null) {
+ return null;
+ }
+ adjustIncompleteIndicatorColumnRowNumbers(barcodeMetadata);
+ int[] result = new int[barcodeMetadata.getRowCount()];
+ for (Codeword codeword : getCodewords()) {
+ if (codeword != null) {
+ int rowNumber = codeword.getRowNumber();
+ if (rowNumber >= result.length) {
+ throw FormatException.getFormatInstance();
+ }
+ result[rowNumber]++;
+ } // else throw exception?
+ }
+ return result;
+ }
+
+ // TODO maybe we should add missing codewords to store the correct row number to make
+ // finding row numbers for other columns easier
+ // use row height count to make detection of invalid row numbers more reliable
+ int adjustIncompleteIndicatorColumnRowNumbers(BarcodeMetadata barcodeMetadata) {
+ BoundingBox boundingBox = getBoundingBox();
+ ResultPoint top = isLeft ? boundingBox.getTopLeft() : boundingBox.getTopRight();
+ ResultPoint bottom = isLeft ? boundingBox.getBottomLeft() : boundingBox.getBottomRight();
+ int firstRow = imageRowToCodewordIndex((int) top.getY());
+ int lastRow = imageRowToCodewordIndex((int) bottom.getY());
+ float averageRowHeight = (lastRow - firstRow) / (float) barcodeMetadata.getRowCount();
+ Codeword[] codewords = getCodewords();
+ int barcodeRow = -1;
+ int maxRowHeight = 1;
+ int currentRowHeight = 0;
+ for (int codewordsRow = firstRow; codewordsRow < lastRow; codewordsRow++) {
+ if (codewords[codewordsRow] == null) {
+ continue;
+ }
+ Codeword codeword = codewords[codewordsRow];
+
+ codeword.setRowNumberAsRowIndicatorColumn();
+
+ int rowDifference = codeword.getRowNumber() - barcodeRow;
+
+ // TODO improve handling with case where first row indicator doesn't start with 0
+
+ if (rowDifference == 0) {
+ currentRowHeight++;
+ } else if (rowDifference == 1) {
+ maxRowHeight = Math.max(maxRowHeight, currentRowHeight);
+ currentRowHeight = 1;
+ barcodeRow = codeword.getRowNumber();
+ } else if (codeword.getRowNumber() >= barcodeMetadata.getRowCount()) {
+ codewords[codewordsRow] = null;
+ } else {
+ barcodeRow = codeword.getRowNumber();
+ currentRowHeight = 1;
+ }
+ }
+ return (int) (averageRowHeight + 0.5);
+ }
+
+ BarcodeMetadata getBarcodeMetadata() {
+ Codeword[] codewords = getCodewords();
+ BarcodeValue barcodeColumnCount = new BarcodeValue();
+ BarcodeValue barcodeRowCountUpperPart = new BarcodeValue();
+ BarcodeValue barcodeRowCountLowerPart = new BarcodeValue();
+ BarcodeValue barcodeECLevel = new BarcodeValue();
+ for (Codeword codeword : codewords) {
+ if (codeword == null) {
+ continue;
+ }
+ codeword.setRowNumberAsRowIndicatorColumn();
+ int rowIndicatorValue = codeword.getValue() % 30;
+ int codewordRowNumber = codeword.getRowNumber();
+ if (!isLeft) {
+ codewordRowNumber += 2;
+ }
+ switch (codewordRowNumber % 3) {
+ case 0:
+ barcodeRowCountUpperPart.setValue(rowIndicatorValue * 3 + 1);
+ break;
+ case 1:
+ barcodeECLevel.setValue(rowIndicatorValue / 3);
+ barcodeRowCountLowerPart.setValue(rowIndicatorValue % 3);
+ break;
+ case 2:
+ barcodeColumnCount.setValue(rowIndicatorValue + 1);
+ break;
+ }
+ }
+ // Maybe we should check if we have ambiguous values?
+ if ((barcodeColumnCount.getValue().length == 0) ||
+ (barcodeRowCountUpperPart.getValue().length == 0) ||
+ (barcodeRowCountLowerPart.getValue().length == 0) ||
+ (barcodeECLevel.getValue().length == 0) ||
+ barcodeColumnCount.getValue()[0] < 1 ||
+ barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] < PDF417Common.MIN_ROWS_IN_BARCODE ||
+ barcodeRowCountUpperPart.getValue()[0] + barcodeRowCountLowerPart.getValue()[0] > PDF417Common.MAX_ROWS_IN_BARCODE) {
+ return null;
+ }
+ BarcodeMetadata barcodeMetadata = new BarcodeMetadata(barcodeColumnCount.getValue()[0],
+ barcodeRowCountUpperPart.getValue()[0], barcodeRowCountLowerPart.getValue()[0], barcodeECLevel.getValue()[0]);
+ removeIncorrectCodewords(codewords, barcodeMetadata);
+ return barcodeMetadata;
+ }
+
+ private void removeIncorrectCodewords(Codeword[] codewords, BarcodeMetadata barcodeMetadata) {
+ // Remove codewords which do not match the metadata
+ // TODO Maybe we should keep the incorrect codewords for the start and end positions?
+ for (int codewordRow = 0; codewordRow < codewords.length; codewordRow++) {
+ Codeword codeword = codewords[codewordRow];
+ if (codewords[codewordRow] == null) {
+ continue;
+ }
+ int rowIndicatorValue = codeword.getValue() % 30;
+ int codewordRowNumber = codeword.getRowNumber();
+ if (codewordRowNumber > barcodeMetadata.getRowCount()) {
+ codewords[codewordRow] = null;
+ continue;
+ }
+ if (!isLeft) {
+ codewordRowNumber += 2;
+ }
+ switch (codewordRowNumber % 3) {
+ case 0:
+ if (rowIndicatorValue * 3 + 1 != barcodeMetadata.getRowCountUpperPart()) {
+ codewords[codewordRow] = null;
+ }
+ break;
+ case 1:
+ if (rowIndicatorValue / 3 != barcodeMetadata.getErrorCorrectionLevel() ||
+ rowIndicatorValue % 3 != barcodeMetadata.getRowCountLowerPart()) {
+ codewords[codewordRow] = null;
+ }
+ break;
+ case 2:
+ if (rowIndicatorValue + 1 != barcodeMetadata.getColumnCount()) {
+ codewords[codewordRow] = null;
+ }
+ break;
+ }
+ }
+ }
+
+ boolean isLeft() {
+ return isLeft;
+ }
+
+ @Override
+ public String toString() {
+ return "IsLeft: " + isLeft + '\n' + super.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/PDF417CodewordDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/PDF417CodewordDecoder.java
new file mode 100644
index 000000000..017f3f399
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/PDF417CodewordDecoder.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2013 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.pdf417.PDF417Common;
+
+/**
+ * @author Guenther Grau
+ * @author creatale GmbH (christoph.schulz@creatale.de)
+ */
+final class PDF417CodewordDecoder {
+
+ private static final float[][] RATIOS_TABLE =
+ new float[PDF417Common.SYMBOL_TABLE.length][PDF417Common.BARS_IN_MODULE];
+
+ static {
+ // Pre-computes the symbol ratio table.
+ for (int i = 0; i < PDF417Common.SYMBOL_TABLE.length; i++) {
+ int currentSymbol = PDF417Common.SYMBOL_TABLE[i];
+ int currentBit = currentSymbol & 0x1;
+ for (int j = 0; j < PDF417Common.BARS_IN_MODULE; j++) {
+ float size = 0.0f;
+ while ((currentSymbol & 0x1) == currentBit) {
+ size += 1.0f;
+ currentSymbol >>= 1;
+ }
+ currentBit = currentSymbol & 0x1;
+ RATIOS_TABLE[i][PDF417Common.BARS_IN_MODULE - j - 1] = size / PDF417Common.MODULES_IN_CODEWORD;
+ }
+ }
+ }
+
+ private PDF417CodewordDecoder() {
+ }
+
+ static int getDecodedValue(int[] moduleBitCount) {
+ int decodedValue = getDecodedCodewordValue(sampleBitCounts(moduleBitCount));
+ if (decodedValue != -1) {
+ return decodedValue;
+ }
+ return getClosestDecodedValue(moduleBitCount);
+ }
+
+ private static int[] sampleBitCounts(int[] moduleBitCount) {
+ float bitCountSum = PDF417Common.getBitCountSum(moduleBitCount);
+ int[] result = new int[PDF417Common.BARS_IN_MODULE];
+ int bitCountIndex = 0;
+ int sumPreviousBits = 0;
+ for (int i = 0; i < PDF417Common.MODULES_IN_CODEWORD; i++) {
+ float sampleIndex =
+ bitCountSum / (2 * PDF417Common.MODULES_IN_CODEWORD) +
+ (i * bitCountSum) / PDF417Common.MODULES_IN_CODEWORD;
+ if (sumPreviousBits + moduleBitCount[bitCountIndex] <= sampleIndex) {
+ sumPreviousBits += moduleBitCount[bitCountIndex];
+ bitCountIndex++;
+ }
+ result[bitCountIndex]++;
+ }
+ return result;
+ }
+
+ private static int getDecodedCodewordValue(int[] moduleBitCount) {
+ int decodedValue = getBitValue(moduleBitCount);
+ return PDF417Common.getCodeword(decodedValue) == -1 ? -1 : decodedValue;
+ }
+
+ private static int getBitValue(int[] moduleBitCount) {
+ long result = 0;
+ for (int i = 0; i < moduleBitCount.length; i++) {
+ for (int bit = 0; bit < moduleBitCount[i]; bit++) {
+ result = (result << 1) | (i % 2 == 0 ? 1 : 0);
+ }
+ }
+ return (int) result;
+ }
+
+ private static int getClosestDecodedValue(int[] moduleBitCount) {
+ int bitCountSum = PDF417Common.getBitCountSum(moduleBitCount);
+ float[] bitCountRatios = new float[PDF417Common.BARS_IN_MODULE];
+ for (int i = 0; i < bitCountRatios.length; i++) {
+ bitCountRatios[i] = moduleBitCount[i] / (float) bitCountSum;
+ }
+ float bestMatchError = Float.MAX_VALUE;
+ int bestMatch = -1;
+ for (int j = 0; j < RATIOS_TABLE.length; j++) {
+ float error = 0.0f;
+ float[] ratioTableRow = RATIOS_TABLE[j];
+ for (int k = 0; k < PDF417Common.BARS_IN_MODULE; k++) {
+ float diff = ratioTableRow[k] - bitCountRatios[k];
+ error += diff * diff;
+ if (error >= bestMatchError) {
+ break;
+ }
+ }
+ if (error < bestMatchError) {
+ bestMatchError = error;
+ bestMatch = PDF417Common.SYMBOL_TABLE[j];
+ }
+ }
+ return bestMatch;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/PDF417ScanningDecoder.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/PDF417ScanningDecoder.java
new file mode 100644
index 000000000..106573694
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/PDF417ScanningDecoder.java
@@ -0,0 +1,624 @@
+/*
+ * Copyright 2013 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.ChecksumException;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.pdf417.PDF417Common;
+import com.google.zxing.pdf417.decoder.ec.ErrorCorrection;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Formatter;
+import java.util.List;
+
+/**
+ * @author Guenther Grau
+ */
+public final class PDF417ScanningDecoder {
+
+ private static final int CODEWORD_SKEW_SIZE = 2;
+
+ private static final int MAX_ERRORS = 3;
+ private static final int MAX_EC_CODEWORDS = 512;
+ private static final ErrorCorrection errorCorrection = new ErrorCorrection();
+
+ private PDF417ScanningDecoder() {
+ }
+
+ // TODO don't pass in minCodewordWidth and maxCodewordWidth, pass in barcode columns for start and stop pattern
+ // columns. That way width can be deducted from the pattern column.
+ // This approach also allows to detect more details about the barcode, e.g. if a bar type (white or black) is wider
+ // than it should be. This can happen if the scanner used a bad blackpoint.
+ public static DecoderResult decode(BitMatrix image,
+ ResultPoint imageTopLeft,
+ ResultPoint imageBottomLeft,
+ ResultPoint imageTopRight,
+ ResultPoint imageBottomRight,
+ int minCodewordWidth,
+ int maxCodewordWidth) throws NotFoundException, FormatException, ChecksumException {
+ BoundingBox boundingBox = new BoundingBox(image, imageTopLeft, imageBottomLeft, imageTopRight, imageBottomRight);
+ DetectionResultRowIndicatorColumn leftRowIndicatorColumn = null;
+ DetectionResultRowIndicatorColumn rightRowIndicatorColumn = null;
+ DetectionResult detectionResult = null;
+ for (int i = 0; i < 2; i++) {
+ if (imageTopLeft != null) {
+ leftRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopLeft, true, minCodewordWidth,
+ maxCodewordWidth);
+ }
+ if (imageTopRight != null) {
+ rightRowIndicatorColumn = getRowIndicatorColumn(image, boundingBox, imageTopRight, false, minCodewordWidth,
+ maxCodewordWidth);
+ }
+ detectionResult = merge(leftRowIndicatorColumn, rightRowIndicatorColumn);
+ if (detectionResult == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ if (i == 0 && detectionResult.getBoundingBox() != null &&
+ (detectionResult.getBoundingBox().getMinY() < boundingBox.getMinY() || detectionResult.getBoundingBox()
+ .getMaxY() > boundingBox.getMaxY())) {
+ boundingBox = detectionResult.getBoundingBox();
+ } else {
+ detectionResult.setBoundingBox(boundingBox);
+ break;
+ }
+ }
+ int maxBarcodeColumn = detectionResult.getBarcodeColumnCount() + 1;
+ detectionResult.setDetectionResultColumn(0, leftRowIndicatorColumn);
+ detectionResult.setDetectionResultColumn(maxBarcodeColumn, rightRowIndicatorColumn);
+
+ boolean leftToRight = leftRowIndicatorColumn != null;
+ for (int barcodeColumnCount = 1; barcodeColumnCount <= maxBarcodeColumn; barcodeColumnCount++) {
+ int barcodeColumn = leftToRight ? barcodeColumnCount : maxBarcodeColumn - barcodeColumnCount;
+ if (detectionResult.getDetectionResultColumn(barcodeColumn) != null) {
+ // This will be the case for the opposite row indicator column, which doesn't need to be decoded again.
+ continue;
+ }
+ DetectionResultColumn detectionResultColumn;
+ if (barcodeColumn == 0 || barcodeColumn == maxBarcodeColumn) {
+ detectionResultColumn = new DetectionResultRowIndicatorColumn(boundingBox, barcodeColumn == 0);
+ } else {
+ detectionResultColumn = new DetectionResultColumn(boundingBox);
+ }
+ detectionResult.setDetectionResultColumn(barcodeColumn, detectionResultColumn);
+ int startColumn = -1;
+ int previousStartColumn = startColumn;
+ // TODO start at a row for which we know the start position, then detect upwards and downwards from there.
+ for (int imageRow = boundingBox.getMinY(); imageRow <= boundingBox.getMaxY(); imageRow++) {
+ startColumn = getStartColumn(detectionResult, barcodeColumn, imageRow, leftToRight);
+ if (startColumn < 0 || startColumn > boundingBox.getMaxX()) {
+ if (previousStartColumn == -1) {
+ continue;
+ }
+ startColumn = previousStartColumn;
+ }
+ Codeword codeword = detectCodeword(image, boundingBox.getMinX(), boundingBox.getMaxX(), leftToRight,
+ startColumn, imageRow, minCodewordWidth, maxCodewordWidth);
+ if (codeword != null) {
+ detectionResultColumn.setCodeword(imageRow, codeword);
+ previousStartColumn = startColumn;
+ minCodewordWidth = Math.min(minCodewordWidth, codeword.getWidth());
+ maxCodewordWidth = Math.max(maxCodewordWidth, codeword.getWidth());
+ }
+ }
+ }
+ return createDecoderResult(detectionResult);
+ }
+
+ private static DetectionResult merge(DetectionResultRowIndicatorColumn leftRowIndicatorColumn,
+ DetectionResultRowIndicatorColumn rightRowIndicatorColumn)
+ throws NotFoundException, FormatException {
+ if (leftRowIndicatorColumn == null && rightRowIndicatorColumn == null) {
+ return null;
+ }
+ BarcodeMetadata barcodeMetadata = getBarcodeMetadata(leftRowIndicatorColumn, rightRowIndicatorColumn);
+ if (barcodeMetadata == null) {
+ return null;
+ }
+ BoundingBox boundingBox = BoundingBox.merge(adjustBoundingBox(leftRowIndicatorColumn),
+ adjustBoundingBox(rightRowIndicatorColumn));
+ return new DetectionResult(barcodeMetadata, boundingBox);
+ }
+
+ private static BoundingBox adjustBoundingBox(DetectionResultRowIndicatorColumn rowIndicatorColumn)
+ throws NotFoundException, FormatException {
+ if (rowIndicatorColumn == null) {
+ return null;
+ }
+ int[] rowHeights = rowIndicatorColumn.getRowHeights();
+ if (rowHeights == null) {
+ return null;
+ }
+ int maxRowHeight = getMax(rowHeights);
+ int missingStartRows = 0;
+ for (int rowHeight : rowHeights) {
+ missingStartRows += maxRowHeight - rowHeight;
+ if (rowHeight > 0) {
+ break;
+ }
+ }
+ Codeword[] codewords = rowIndicatorColumn.getCodewords();
+ for (int row = 0; missingStartRows > 0 && codewords[row] == null; row++) {
+ missingStartRows--;
+ }
+ int missingEndRows = 0;
+ for (int row = rowHeights.length - 1; row >= 0; row--) {
+ missingEndRows += maxRowHeight - rowHeights[row];
+ if (rowHeights[row] > 0) {
+ break;
+ }
+ }
+ for (int row = codewords.length - 1; missingEndRows > 0 && codewords[row] == null; row--) {
+ missingEndRows--;
+ }
+ return rowIndicatorColumn.getBoundingBox().addMissingRows(missingStartRows, missingEndRows,
+ rowIndicatorColumn.isLeft());
+ }
+
+ private static int getMax(int[] values) {
+ int maxValue = -1;
+ for (int value : values) {
+ maxValue = Math.max(maxValue, value);
+ }
+ return maxValue;
+ }
+
+ private static BarcodeMetadata getBarcodeMetadata(DetectionResultRowIndicatorColumn leftRowIndicatorColumn,
+ DetectionResultRowIndicatorColumn rightRowIndicatorColumn) {
+ BarcodeMetadata leftBarcodeMetadata;
+ if (leftRowIndicatorColumn == null ||
+ (leftBarcodeMetadata = leftRowIndicatorColumn.getBarcodeMetadata()) == null) {
+ return rightRowIndicatorColumn == null ? null : rightRowIndicatorColumn.getBarcodeMetadata();
+ }
+ BarcodeMetadata rightBarcodeMetadata;
+ if (rightRowIndicatorColumn == null ||
+ (rightBarcodeMetadata = rightRowIndicatorColumn.getBarcodeMetadata()) == null) {
+ return leftBarcodeMetadata;
+ }
+
+ if (leftBarcodeMetadata.getColumnCount() != rightBarcodeMetadata.getColumnCount() &&
+ leftBarcodeMetadata.getErrorCorrectionLevel() != rightBarcodeMetadata.getErrorCorrectionLevel() &&
+ leftBarcodeMetadata.getRowCount() != rightBarcodeMetadata.getRowCount()) {
+ return null;
+ }
+ return leftBarcodeMetadata;
+ }
+
+ private static DetectionResultRowIndicatorColumn getRowIndicatorColumn(BitMatrix image,
+ BoundingBox boundingBox,
+ ResultPoint startPoint,
+ boolean leftToRight,
+ int minCodewordWidth,
+ int maxCodewordWidth) {
+ DetectionResultRowIndicatorColumn rowIndicatorColumn = new DetectionResultRowIndicatorColumn(boundingBox,
+ leftToRight);
+ for (int i = 0; i < 2; i++) {
+ int increment = i == 0 ? 1 : -1;
+ int startColumn = (int) startPoint.getX();
+ for (int imageRow = (int) startPoint.getY(); imageRow <= boundingBox.getMaxY() &&
+ imageRow >= boundingBox.getMinY(); imageRow += increment) {
+ Codeword codeword = detectCodeword(image, 0, image.getWidth(), leftToRight, startColumn, imageRow,
+ minCodewordWidth, maxCodewordWidth);
+ if (codeword != null) {
+ rowIndicatorColumn.setCodeword(imageRow, codeword);
+ if (leftToRight) {
+ startColumn = codeword.getStartX();
+ } else {
+ startColumn = codeword.getEndX();
+ }
+ }
+ }
+ }
+ return rowIndicatorColumn;
+ }
+
+ private static void adjustCodewordCount(DetectionResult detectionResult, BarcodeValue[][] barcodeMatrix)
+ throws NotFoundException {
+ int[] numberOfCodewords = barcodeMatrix[0][1].getValue();
+ int calculatedNumberOfCodewords = detectionResult.getBarcodeColumnCount() *
+ detectionResult.getBarcodeRowCount() -
+ getNumberOfECCodeWords(detectionResult.getBarcodeECLevel());
+ if (numberOfCodewords.length == 0) {
+ if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > PDF417Common.MAX_CODEWORDS_IN_BARCODE) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords);
+ } else if (numberOfCodewords[0] != calculatedNumberOfCodewords) {
+ // The calculated one is more reliable as it is derived from the row indicator columns
+ barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords);
+ }
+ }
+
+ private static DecoderResult createDecoderResult(DetectionResult detectionResult) throws FormatException,
+ ChecksumException, NotFoundException {
+ BarcodeValue[][] barcodeMatrix = createBarcodeMatrix(detectionResult);
+ adjustCodewordCount(detectionResult, barcodeMatrix);
+ Collection erasures = new ArrayList<>();
+ int[] codewords = new int[detectionResult.getBarcodeRowCount() * detectionResult.getBarcodeColumnCount()];
+ List ambiguousIndexValuesList = new ArrayList<>();
+ List ambiguousIndexesList = new ArrayList<>();
+ for (int row = 0; row < detectionResult.getBarcodeRowCount(); row++) {
+ for (int column = 0; column < detectionResult.getBarcodeColumnCount(); column++) {
+ int[] values = barcodeMatrix[row][column + 1].getValue();
+ int codewordIndex = row * detectionResult.getBarcodeColumnCount() + column;
+ if (values.length == 0) {
+ erasures.add(codewordIndex);
+ } else if (values.length == 1) {
+ codewords[codewordIndex] = values[0];
+ } else {
+ ambiguousIndexesList.add(codewordIndex);
+ ambiguousIndexValuesList.add(values);
+ }
+ }
+ }
+ int[][] ambiguousIndexValues = new int[ambiguousIndexValuesList.size()][];
+ for (int i = 0; i < ambiguousIndexValues.length; i++) {
+ ambiguousIndexValues[i] = ambiguousIndexValuesList.get(i);
+ }
+ return createDecoderResultFromAmbiguousValues(detectionResult.getBarcodeECLevel(), codewords,
+ PDF417Common.toIntArray(erasures), PDF417Common.toIntArray(ambiguousIndexesList), ambiguousIndexValues);
+ }
+
+ /**
+ * This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The
+ * current error correction implementation doesn't deal with erasures very well, so it's better to provide a value
+ * for these ambiguous codewords instead of treating it as an erasure. The problem is that we don't know which of
+ * the ambiguous values to choose. We try decode using the first value, and if that fails, we use another of the
+ * ambiguous values and try to decode again. This usually only happens on very hard to read and decode barcodes,
+ * so decoding the normal barcodes is not affected by this.
+ *
+ * @param erasureArray contains the indexes of erasures
+ * @param ambiguousIndexes array with the indexes that have more than one most likely value
+ * @param ambiguousIndexValues two dimensional array that contains the ambiguous values. The first dimension must
+ * be the same length as the ambiguousIndexes array
+ */
+ private static DecoderResult createDecoderResultFromAmbiguousValues(int ecLevel,
+ int[] codewords,
+ int[] erasureArray,
+ int[] ambiguousIndexes,
+ int[][] ambiguousIndexValues)
+ throws FormatException, ChecksumException {
+ int[] ambiguousIndexCount = new int[ambiguousIndexes.length];
+
+ int tries = 100;
+ while (tries-- > 0) {
+ for (int i = 0; i < ambiguousIndexCount.length; i++) {
+ codewords[ambiguousIndexes[i]] = ambiguousIndexValues[i][ambiguousIndexCount[i]];
+ }
+ try {
+ return decodeCodewords(codewords, ecLevel, erasureArray);
+ } catch (ChecksumException ignored) {
+ //
+ }
+ if (ambiguousIndexCount.length == 0) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ for (int i = 0; i < ambiguousIndexCount.length; i++) {
+ if (ambiguousIndexCount[i] < ambiguousIndexValues[i].length - 1) {
+ ambiguousIndexCount[i]++;
+ break;
+ } else {
+ ambiguousIndexCount[i] = 0;
+ if (i == ambiguousIndexCount.length - 1) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ }
+ }
+ }
+ throw ChecksumException.getChecksumInstance();
+ }
+
+ private static BarcodeValue[][] createBarcodeMatrix(DetectionResult detectionResult) {
+ BarcodeValue[][] barcodeMatrix = new BarcodeValue[detectionResult.getBarcodeRowCount()][detectionResult
+ .getBarcodeColumnCount() + 2];
+ for (int row = 0; row < barcodeMatrix.length; row++) {
+ for (int column = 0; column < barcodeMatrix[row].length; column++) {
+ barcodeMatrix[row][column] = new BarcodeValue();
+ }
+ }
+
+ int column = -1;
+ for (DetectionResultColumn detectionResultColumn : detectionResult.getDetectionResultColumns()) {
+ column++;
+ if (detectionResultColumn == null) {
+ continue;
+ }
+ for (Codeword codeword : detectionResultColumn.getCodewords()) {
+ if (codeword == null || codeword.getRowNumber() == -1) {
+ continue;
+ }
+ barcodeMatrix[codeword.getRowNumber()][column].setValue(codeword.getValue());
+ }
+ }
+ return barcodeMatrix;
+ }
+
+ private static boolean isValidBarcodeColumn(DetectionResult detectionResult, int barcodeColumn) {
+ return barcodeColumn >= 0 && barcodeColumn <= detectionResult.getBarcodeColumnCount() + 1;
+ }
+
+ private static int getStartColumn(DetectionResult detectionResult,
+ int barcodeColumn,
+ int imageRow,
+ boolean leftToRight) {
+ int offset = leftToRight ? 1 : -1;
+ Codeword codeword = null;
+ if (isValidBarcodeColumn(detectionResult, barcodeColumn - offset)) {
+ codeword = detectionResult.getDetectionResultColumn(barcodeColumn - offset).getCodeword(imageRow);
+ }
+ if (codeword != null) {
+ return leftToRight ? codeword.getEndX() : codeword.getStartX();
+ }
+ codeword = detectionResult.getDetectionResultColumn(barcodeColumn).getCodewordNearby(imageRow);
+ if (codeword != null) {
+ return leftToRight ? codeword.getStartX() : codeword.getEndX();
+ }
+ if (isValidBarcodeColumn(detectionResult, barcodeColumn - offset)) {
+ codeword = detectionResult.getDetectionResultColumn(barcodeColumn - offset).getCodewordNearby(imageRow);
+ }
+ if (codeword != null) {
+ return leftToRight ? codeword.getEndX() : codeword.getStartX();
+ }
+ int skippedColumns = 0;
+
+ while (isValidBarcodeColumn(detectionResult, barcodeColumn - offset)) {
+ barcodeColumn -= offset;
+ for (Codeword previousRowCodeword : detectionResult.getDetectionResultColumn(barcodeColumn).getCodewords()) {
+ if (previousRowCodeword != null) {
+ return (leftToRight ? previousRowCodeword.getEndX() : previousRowCodeword.getStartX()) +
+ offset *
+ skippedColumns *
+ (previousRowCodeword.getEndX() - previousRowCodeword.getStartX());
+ }
+ }
+ skippedColumns++;
+ }
+ return leftToRight ? detectionResult.getBoundingBox().getMinX() : detectionResult.getBoundingBox().getMaxX();
+ }
+
+ private static Codeword detectCodeword(BitMatrix image,
+ int minColumn,
+ int maxColumn,
+ boolean leftToRight,
+ int startColumn,
+ int imageRow,
+ int minCodewordWidth,
+ int maxCodewordWidth) {
+ startColumn = adjustCodewordStartColumn(image, minColumn, maxColumn, leftToRight, startColumn, imageRow);
+ // we usually know fairly exact now how long a codeword is. We should provide minimum and maximum expected length
+ // and try to adjust the read pixels, e.g. remove single pixel errors or try to cut off exceeding pixels.
+ // min and maxCodewordWidth should not be used as they are calculated for the whole barcode an can be inaccurate
+ // for the current position
+ int[] moduleBitCount = getModuleBitCount(image, minColumn, maxColumn, leftToRight, startColumn, imageRow);
+ if (moduleBitCount == null) {
+ return null;
+ }
+ int endColumn;
+ int codewordBitCount = PDF417Common.getBitCountSum(moduleBitCount);
+ if (leftToRight) {
+ endColumn = startColumn + codewordBitCount;
+ } else {
+ for (int i = 0; i < moduleBitCount.length >> 1; i++) {
+ int tmpCount = moduleBitCount[i];
+ moduleBitCount[i] = moduleBitCount[moduleBitCount.length - 1 - i];
+ moduleBitCount[moduleBitCount.length - 1 - i] = tmpCount;
+ }
+ endColumn = startColumn;
+ startColumn = endColumn - codewordBitCount;
+ }
+ // TODO implement check for width and correction of black and white bars
+ // use start (and maybe stop pattern) to determine if blackbars are wider than white bars. If so, adjust.
+ // should probably done only for codewords with a lot more than 17 bits.
+ // The following fixes 10-1.png, which has wide black bars and small white bars
+ // for (int i = 0; i < moduleBitCount.length; i++) {
+ // if (i % 2 == 0) {
+ // moduleBitCount[i]--;
+ // } else {
+ // moduleBitCount[i]++;
+ // }
+ // }
+
+ // We could also use the width of surrounding codewords for more accurate results, but this seems
+ // sufficient for now
+ if (!checkCodewordSkew(codewordBitCount, minCodewordWidth, maxCodewordWidth)) {
+ // We could try to use the startX and endX position of the codeword in the same column in the previous row,
+ // create the bit count from it and normalize it to 8. This would help with single pixel errors.
+ return null;
+ }
+
+ int decodedValue = PDF417CodewordDecoder.getDecodedValue(moduleBitCount);
+ int codeword = PDF417Common.getCodeword(decodedValue);
+ if (codeword == -1) {
+ return null;
+ }
+ return new Codeword(startColumn, endColumn, getCodewordBucketNumber(decodedValue), codeword);
+ }
+
+ private static int[] getModuleBitCount(BitMatrix image,
+ int minColumn,
+ int maxColumn,
+ boolean leftToRight,
+ int startColumn,
+ int imageRow) {
+ int imageColumn = startColumn;
+ int[] moduleBitCount = new int[8];
+ int moduleNumber = 0;
+ int increment = leftToRight ? 1 : -1;
+ boolean previousPixelValue = leftToRight;
+ while (((leftToRight && imageColumn < maxColumn) || (!leftToRight && imageColumn >= minColumn)) &&
+ moduleNumber < moduleBitCount.length) {
+ if (image.get(imageColumn, imageRow) == previousPixelValue) {
+ moduleBitCount[moduleNumber]++;
+ imageColumn += increment;
+ } else {
+ moduleNumber++;
+ previousPixelValue = !previousPixelValue;
+ }
+ }
+ if (moduleNumber == moduleBitCount.length ||
+ (((leftToRight && imageColumn == maxColumn) || (!leftToRight && imageColumn == minColumn)) && moduleNumber == moduleBitCount.length - 1)) {
+ return moduleBitCount;
+ }
+ return null;
+ }
+
+ private static int getNumberOfECCodeWords(int barcodeECLevel) {
+ return 2 << barcodeECLevel;
+ }
+
+ private static int adjustCodewordStartColumn(BitMatrix image,
+ int minColumn,
+ int maxColumn,
+ boolean leftToRight,
+ int codewordStartColumn,
+ int imageRow) {
+ int correctedStartColumn = codewordStartColumn;
+ int increment = leftToRight ? -1 : 1;
+ // there should be no black pixels before the start column. If there are, then we need to start earlier.
+ for (int i = 0; i < 2; i++) {
+ while (((leftToRight && correctedStartColumn >= minColumn) || (!leftToRight && correctedStartColumn < maxColumn)) &&
+ leftToRight == image.get(correctedStartColumn, imageRow)) {
+ if (Math.abs(codewordStartColumn - correctedStartColumn) > CODEWORD_SKEW_SIZE) {
+ return codewordStartColumn;
+ }
+ correctedStartColumn += increment;
+ }
+ increment = -increment;
+ leftToRight = !leftToRight;
+ }
+ return correctedStartColumn;
+ }
+
+ private static boolean checkCodewordSkew(int codewordSize, int minCodewordWidth, int maxCodewordWidth) {
+ return minCodewordWidth - CODEWORD_SKEW_SIZE <= codewordSize &&
+ codewordSize <= maxCodewordWidth + CODEWORD_SKEW_SIZE;
+ }
+
+ private static DecoderResult decodeCodewords(int[] codewords, int ecLevel, int[] erasures) throws FormatException,
+ ChecksumException {
+ if (codewords.length == 0) {
+ throw FormatException.getFormatInstance();
+ }
+
+ int numECCodewords = 1 << (ecLevel + 1);
+ int correctedErrorsCount = correctErrors(codewords, erasures, numECCodewords);
+ verifyCodewordCount(codewords, numECCodewords);
+
+ // Decode the codewords
+ DecoderResult decoderResult = DecodedBitStreamParser.decode(codewords, String.valueOf(ecLevel));
+ decoderResult.setErrorsCorrected(correctedErrorsCount);
+ decoderResult.setErasures(erasures.length);
+ return decoderResult;
+ }
+
+ /**
+ * Given data and error-correction codewords received, possibly corrupted by errors, attempts to
+ * correct the errors in-place.
+ *
+ * @param codewords data and error correction codewords
+ * @param erasures positions of any known erasures
+ * @param numECCodewords number of error correction codewords that are available in codewords
+ * @throws ChecksumException if error correction fails
+ */
+ private static int correctErrors(int[] codewords, int[] erasures, int numECCodewords) throws ChecksumException {
+ if (erasures != null &&
+ erasures.length > numECCodewords / 2 + MAX_ERRORS ||
+ numECCodewords < 0 ||
+ numECCodewords > MAX_EC_CODEWORDS) {
+ // Too many errors or EC Codewords is corrupted
+ throw ChecksumException.getChecksumInstance();
+ }
+ return errorCorrection.decode(codewords, numECCodewords, erasures);
+ }
+
+ /**
+ * Verify that all is OK with the codeword array.
+ */
+ private static void verifyCodewordCount(int[] codewords, int numECCodewords) throws FormatException {
+ if (codewords.length < 4) {
+ // Codeword array size should be at least 4 allowing for
+ // Count CW, At least one Data CW, Error Correction CW, Error Correction CW
+ throw FormatException.getFormatInstance();
+ }
+ // The first codeword, the Symbol Length Descriptor, shall always encode the total number of data
+ // codewords in the symbol, including the Symbol Length Descriptor itself, data codewords and pad
+ // codewords, but excluding the number of error correction codewords.
+ int numberOfCodewords = codewords[0];
+ if (numberOfCodewords > codewords.length) {
+ throw FormatException.getFormatInstance();
+ }
+ if (numberOfCodewords == 0) {
+ // Reset to the length of the array - 8 (Allow for at least level 3 Error Correction (8 Error Codewords)
+ if (numECCodewords < codewords.length) {
+ codewords[0] = codewords.length - numECCodewords;
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ }
+ }
+
+ private static int[] getBitCountForCodeword(int codeword) {
+ int[] result = new int[8];
+ int previousValue = 0;
+ int i = result.length - 1;
+ while (true) {
+ if ((codeword & 0x1) != previousValue) {
+ previousValue = codeword & 0x1;
+ i--;
+ if (i < 0) {
+ break;
+ }
+ }
+ result[i]++;
+ codeword >>= 1;
+ }
+ return result;
+ }
+
+ private static int getCodewordBucketNumber(int codeword) {
+ return getCodewordBucketNumber(getBitCountForCodeword(codeword));
+ }
+
+ private static int getCodewordBucketNumber(int[] moduleBitCount) {
+ return (moduleBitCount[0] - moduleBitCount[2] + moduleBitCount[4] - moduleBitCount[6] + 9) % 9;
+ }
+
+ public static String toString(BarcodeValue[][] barcodeMatrix) {
+ Formatter formatter = new Formatter();
+ for (int row = 0; row < barcodeMatrix.length; row++) {
+ formatter.format("Row %2d: ", row);
+ for (int column = 0; column < barcodeMatrix[row].length; column++) {
+ BarcodeValue barcodeValue = barcodeMatrix[row][column];
+ if (barcodeValue.getValue().length == 0) {
+ formatter.format(" ", (Object[]) null);
+ } else {
+ formatter.format("%4d(%2d)", barcodeValue.getValue()[0],
+ barcodeValue.getConfidence(barcodeValue.getValue()[0]));
+ }
+ }
+ formatter.format("%n");
+ }
+ String result = formatter.toString();
+ formatter.close();
+ return result;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ErrorCorrection.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ErrorCorrection.java
new file mode 100644
index 000000000..eb91d0abc
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ErrorCorrection.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 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.pdf417.decoder.ec;
+
+import com.google.zxing.ChecksumException;
+
+/**
+ * PDF417 error correction implementation.
+ *
+ * This example
+ * is quite useful in understanding the algorithm.
+ *
+ * @author Sean Owen
+ * @see com.google.zxing.common.reedsolomon.ReedSolomonDecoder
+ */
+public final class ErrorCorrection {
+
+ private final ModulusGF field;
+
+ public ErrorCorrection() {
+ this.field = ModulusGF.PDF417_GF;
+ }
+
+ /**
+ * @param received received codewords
+ * @param numECCodewords number of those codewords used for EC
+ * @param erasures location of erasures
+ * @return number of errors
+ * @throws ChecksumException if errors cannot be corrected, maybe because of too many errors
+ */
+ public int decode(int[] received,
+ int numECCodewords,
+ int[] erasures) throws ChecksumException {
+
+ ModulusPoly poly = new ModulusPoly(field, received);
+ int[] S = new int[numECCodewords];
+ boolean error = false;
+ for (int i = numECCodewords; i > 0; i--) {
+ int eval = poly.evaluateAt(field.exp(i));
+ S[numECCodewords - i] = eval;
+ if (eval != 0) {
+ error = true;
+ }
+ }
+
+ if (!error) {
+ return 0;
+ }
+
+ ModulusPoly knownErrors = field.getOne();
+ for (int erasure : erasures) {
+ int b = field.exp(received.length - 1 - erasure);
+ // Add (1 - bx) term:
+ ModulusPoly term = new ModulusPoly(field, new int[] { field.subtract(0, b), 1 });
+ knownErrors = knownErrors.multiply(term);
+ }
+
+ ModulusPoly syndrome = new ModulusPoly(field, S);
+ //syndrome = syndrome.multiply(knownErrors);
+
+ ModulusPoly[] sigmaOmega =
+ runEuclideanAlgorithm(field.buildMonomial(numECCodewords, 1), syndrome, numECCodewords);
+ ModulusPoly sigma = sigmaOmega[0];
+ ModulusPoly omega = sigmaOmega[1];
+
+ //sigma = sigma.multiply(knownErrors);
+
+ int[] errorLocations = findErrorLocations(sigma);
+ int[] errorMagnitudes = findErrorMagnitudes(omega, sigma, errorLocations);
+
+ for (int i = 0; i < errorLocations.length; i++) {
+ int position = received.length - 1 - field.log(errorLocations[i]);
+ if (position < 0) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ received[position] = field.subtract(received[position], errorMagnitudes[i]);
+ }
+ return errorLocations.length;
+ }
+
+ private ModulusPoly[] runEuclideanAlgorithm(ModulusPoly a, ModulusPoly b, int R)
+ throws ChecksumException {
+ // Assume a's degree is >= b's
+ if (a.getDegree() < b.getDegree()) {
+ ModulusPoly temp = a;
+ a = b;
+ b = temp;
+ }
+
+ ModulusPoly rLast = a;
+ ModulusPoly r = b;
+ ModulusPoly tLast = field.getZero();
+ ModulusPoly t = field.getOne();
+
+ // Run Euclidean algorithm until r's degree is less than R/2
+ while (r.getDegree() >= R / 2) {
+ ModulusPoly rLastLast = rLast;
+ ModulusPoly tLastLast = tLast;
+ rLast = r;
+ tLast = t;
+
+ // Divide rLastLast by rLast, with quotient in q and remainder in r
+ if (rLast.isZero()) {
+ // Oops, Euclidean algorithm already terminated?
+ throw ChecksumException.getChecksumInstance();
+ }
+ r = rLastLast;
+ ModulusPoly q = field.getZero();
+ int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree());
+ int dltInverse = field.inverse(denominatorLeadingTerm);
+ while (r.getDegree() >= rLast.getDegree() && !r.isZero()) {
+ int degreeDiff = r.getDegree() - rLast.getDegree();
+ int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse);
+ q = q.add(field.buildMonomial(degreeDiff, scale));
+ r = r.subtract(rLast.multiplyByMonomial(degreeDiff, scale));
+ }
+
+ t = q.multiply(tLast).subtract(tLastLast).negative();
+ }
+
+ int sigmaTildeAtZero = t.getCoefficient(0);
+ if (sigmaTildeAtZero == 0) {
+ throw ChecksumException.getChecksumInstance();
+ }
+
+ int inverse = field.inverse(sigmaTildeAtZero);
+ ModulusPoly sigma = t.multiply(inverse);
+ ModulusPoly omega = r.multiply(inverse);
+ return new ModulusPoly[]{sigma, omega};
+ }
+
+ private int[] findErrorLocations(ModulusPoly errorLocator) throws ChecksumException {
+ // This is a direct application of Chien's search
+ int numErrors = errorLocator.getDegree();
+ int[] result = new int[numErrors];
+ int e = 0;
+ for (int i = 1; i < field.getSize() && e < numErrors; i++) {
+ if (errorLocator.evaluateAt(i) == 0) {
+ result[e] = field.inverse(i);
+ e++;
+ }
+ }
+ if (e != numErrors) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ return result;
+ }
+
+ private int[] findErrorMagnitudes(ModulusPoly errorEvaluator,
+ ModulusPoly errorLocator,
+ int[] errorLocations) {
+ int errorLocatorDegree = errorLocator.getDegree();
+ int[] formalDerivativeCoefficients = new int[errorLocatorDegree];
+ for (int i = 1; i <= errorLocatorDegree; i++) {
+ formalDerivativeCoefficients[errorLocatorDegree - i] =
+ field.multiply(i, errorLocator.getCoefficient(i));
+ }
+ ModulusPoly formalDerivative = new ModulusPoly(field, formalDerivativeCoefficients);
+
+ // This is directly applying Forney's Formula
+ int s = errorLocations.length;
+ int[] result = new int[s];
+ for (int i = 0; i < s; i++) {
+ int xiInverse = field.inverse(errorLocations[i]);
+ int numerator = field.subtract(0, errorEvaluator.evaluateAt(xiInverse));
+ int denominator = field.inverse(formalDerivative.evaluateAt(xiInverse));
+ result[i] = field.multiply(numerator, denominator);
+ }
+ return result;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusGF.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusGF.java
new file mode 100644
index 000000000..4b66d57fb
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusGF.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 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.pdf417.decoder.ec;
+
+import com.google.zxing.pdf417.PDF417Common;
+
+/**
+ * A field based on powers of a generator integer, modulo some modulus.
+ *
+ * @author Sean Owen
+ * @see com.google.zxing.common.reedsolomon.GenericGF
+ */
+public final class ModulusGF {
+
+ public static final ModulusGF PDF417_GF = new ModulusGF(PDF417Common.NUMBER_OF_CODEWORDS, 3);
+
+ private final int[] expTable;
+ private final int[] logTable;
+ private final ModulusPoly zero;
+ private final ModulusPoly one;
+ private final int modulus;
+
+ private ModulusGF(int modulus, int generator) {
+ this.modulus = modulus;
+ expTable = new int[modulus];
+ logTable = new int[modulus];
+ int x = 1;
+ for (int i = 0; i < modulus; i++) {
+ expTable[i] = x;
+ x = (x * generator) % modulus;
+ }
+ for (int i = 0; i < modulus-1; i++) {
+ logTable[expTable[i]] = i;
+ }
+ // logTable[0] == 0 but this should never be used
+ zero = new ModulusPoly(this, new int[]{0});
+ one = new ModulusPoly(this, new int[]{1});
+ }
+
+
+ ModulusPoly getZero() {
+ return zero;
+ }
+
+ ModulusPoly getOne() {
+ return one;
+ }
+
+ ModulusPoly buildMonomial(int degree, int coefficient) {
+ if (degree < 0) {
+ throw new IllegalArgumentException();
+ }
+ if (coefficient == 0) {
+ return zero;
+ }
+ int[] coefficients = new int[degree + 1];
+ coefficients[0] = coefficient;
+ return new ModulusPoly(this, coefficients);
+ }
+
+ int add(int a, int b) {
+ return (a + b) % modulus;
+ }
+
+ int subtract(int a, int b) {
+ return (modulus + a - b) % modulus;
+ }
+
+ int exp(int a) {
+ return expTable[a];
+ }
+
+ int log(int a) {
+ if (a == 0) {
+ throw new IllegalArgumentException();
+ }
+ return logTable[a];
+ }
+
+ int inverse(int a) {
+ if (a == 0) {
+ throw new ArithmeticException();
+ }
+ return expTable[modulus - logTable[a] - 1];
+ }
+
+ int multiply(int a, int b) {
+ if (a == 0 || b == 0) {
+ return 0;
+ }
+ return expTable[(logTable[a] + logTable[b]) % (modulus - 1)];
+ }
+
+ int getSize() {
+ return modulus;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusPoly.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusPoly.java
new file mode 100644
index 000000000..d2b96768d
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/decoder/ec/ModulusPoly.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright 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.pdf417.decoder.ec;
+
+/**
+ * @author Sean Owen
+ * @see com.google.zxing.common.reedsolomon.GenericGFPoly
+ */
+final class ModulusPoly {
+
+ private final ModulusGF field;
+ private final int[] coefficients;
+
+ ModulusPoly(ModulusGF field, int[] coefficients) {
+ if (coefficients.length == 0) {
+ throw new IllegalArgumentException();
+ }
+ this.field = field;
+ int coefficientsLength = coefficients.length;
+ if (coefficientsLength > 1 && coefficients[0] == 0) {
+ // Leading term must be non-zero for anything except the constant polynomial "0"
+ int firstNonZero = 1;
+ while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) {
+ firstNonZero++;
+ }
+ if (firstNonZero == coefficientsLength) {
+ this.coefficients = field.getZero().coefficients;
+ } else {
+ this.coefficients = new int[coefficientsLength - firstNonZero];
+ System.arraycopy(coefficients,
+ firstNonZero,
+ this.coefficients,
+ 0,
+ this.coefficients.length);
+ }
+ } else {
+ this.coefficients = coefficients;
+ }
+ }
+
+ int[] getCoefficients() {
+ return coefficients;
+ }
+
+ /**
+ * @return degree of this polynomial
+ */
+ int getDegree() {
+ return coefficients.length - 1;
+ }
+
+ /**
+ * @return true iff this polynomial is the monomial "0"
+ */
+ boolean isZero() {
+ return coefficients[0] == 0;
+ }
+
+ /**
+ * @return coefficient of x^degree term in this polynomial
+ */
+ int getCoefficient(int degree) {
+ return coefficients[coefficients.length - 1 - degree];
+ }
+
+ /**
+ * @return evaluation of this polynomial at a given point
+ */
+ int evaluateAt(int a) {
+ if (a == 0) {
+ // Just return the x^0 coefficient
+ return getCoefficient(0);
+ }
+ int size = coefficients.length;
+ if (a == 1) {
+ // Just the sum of the coefficients
+ int result = 0;
+ for (int coefficient : coefficients) {
+ result = field.add(result, coefficient);
+ }
+ return result;
+ }
+ int result = coefficients[0];
+ for (int i = 1; i < size; i++) {
+ result = field.add(field.multiply(a, result), coefficients[i]);
+ }
+ return result;
+ }
+
+ ModulusPoly add(ModulusPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
+ }
+ if (isZero()) {
+ return other;
+ }
+ if (other.isZero()) {
+ return this;
+ }
+
+ int[] smallerCoefficients = this.coefficients;
+ int[] largerCoefficients = other.coefficients;
+ if (smallerCoefficients.length > largerCoefficients.length) {
+ int[] temp = smallerCoefficients;
+ smallerCoefficients = largerCoefficients;
+ largerCoefficients = temp;
+ }
+ int[] sumDiff = new int[largerCoefficients.length];
+ int lengthDiff = largerCoefficients.length - smallerCoefficients.length;
+ // Copy high-order terms only found in higher-degree polynomial's coefficients
+ System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff);
+
+ for (int i = lengthDiff; i < largerCoefficients.length; i++) {
+ sumDiff[i] = field.add(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
+ }
+
+ return new ModulusPoly(field, sumDiff);
+ }
+
+ ModulusPoly subtract(ModulusPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
+ }
+ if (other.isZero()) {
+ return this;
+ }
+ return add(other.negative());
+ }
+
+ ModulusPoly multiply(ModulusPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
+ }
+ if (isZero() || other.isZero()) {
+ return field.getZero();
+ }
+ int[] aCoefficients = this.coefficients;
+ int aLength = aCoefficients.length;
+ int[] bCoefficients = other.coefficients;
+ int bLength = bCoefficients.length;
+ int[] product = new int[aLength + bLength - 1];
+ for (int i = 0; i < aLength; i++) {
+ int aCoeff = aCoefficients[i];
+ for (int j = 0; j < bLength; j++) {
+ product[i + j] = field.add(product[i + j], field.multiply(aCoeff, bCoefficients[j]));
+ }
+ }
+ return new ModulusPoly(field, product);
+ }
+
+ ModulusPoly negative() {
+ int size = coefficients.length;
+ int[] negativeCoefficients = new int[size];
+ for (int i = 0; i < size; i++) {
+ negativeCoefficients[i] = field.subtract(0, coefficients[i]);
+ }
+ return new ModulusPoly(field, negativeCoefficients);
+ }
+
+ ModulusPoly multiply(int scalar) {
+ if (scalar == 0) {
+ return field.getZero();
+ }
+ if (scalar == 1) {
+ return this;
+ }
+ int size = coefficients.length;
+ int[] product = new int[size];
+ for (int i = 0; i < size; i++) {
+ product[i] = field.multiply(coefficients[i], scalar);
+ }
+ return new ModulusPoly(field, product);
+ }
+
+ ModulusPoly multiplyByMonomial(int degree, int coefficient) {
+ if (degree < 0) {
+ throw new IllegalArgumentException();
+ }
+ if (coefficient == 0) {
+ return field.getZero();
+ }
+ int size = coefficients.length;
+ int[] product = new int[size + degree];
+ for (int i = 0; i < size; i++) {
+ product[i] = field.multiply(coefficients[i], coefficient);
+ }
+ return new ModulusPoly(field, product);
+ }
+
+ ModulusPoly[] divide(ModulusPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("ModulusPolys do not have same ModulusGF field");
+ }
+ if (other.isZero()) {
+ throw new IllegalArgumentException("Divide by 0");
+ }
+
+ ModulusPoly quotient = field.getZero();
+ ModulusPoly remainder = this;
+
+ int denominatorLeadingTerm = other.getCoefficient(other.getDegree());
+ int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm);
+
+ while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) {
+ int degreeDifference = remainder.getDegree() - other.getDegree();
+ int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm);
+ ModulusPoly term = other.multiplyByMonomial(degreeDifference, scale);
+ ModulusPoly iterationQuotient = field.buildMonomial(degreeDifference, scale);
+ quotient = quotient.add(iterationQuotient);
+ remainder = remainder.subtract(term);
+ }
+
+ return new ModulusPoly[] { quotient, remainder };
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder(8 * getDegree());
+ for (int degree = getDegree(); degree >= 0; degree--) {
+ int coefficient = getCoefficient(degree);
+ if (coefficient != 0) {
+ if (coefficient < 0) {
+ result.append(" - ");
+ coefficient = -coefficient;
+ } else {
+ if (result.length() > 0) {
+ result.append(" + ");
+ }
+ }
+ if (degree == 0 || coefficient != 1) {
+ result.append(coefficient);
+ }
+ if (degree != 0) {
+ if (degree == 1) {
+ result.append('x');
+ } else {
+ result.append("x^");
+ result.append(degree);
+ }
+ }
+ }
+ }
+ return result.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/detector/Detector.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/detector/Detector.java
new file mode 100644
index 000000000..72a2eec34
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/detector/Detector.java
@@ -0,0 +1,350 @@
+/*
+ * 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.detector;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Encapsulates logic that can detect a PDF417 Code in an image, even if the
+ * PDF417 Code is rotated or skewed, or partially obscured.
+ *
+ * @author SITA Lab (kevin.osullivan@sita.aero)
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Guenther Grau
+ */
+public final class Detector {
+
+ private static final int[] INDEXES_START_PATTERN = {0, 4, 1, 5};
+ private static final int[] INDEXES_STOP_PATTERN = {6, 2, 7, 3};
+ private static final int INTEGER_MATH_SHIFT = 8;
+ private static final int PATTERN_MATCH_RESULT_SCALE_FACTOR = 1 << INTEGER_MATH_SHIFT;
+ private static final int MAX_AVG_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.42f);
+ private static final int MAX_INDIVIDUAL_VARIANCE = (int) (PATTERN_MATCH_RESULT_SCALE_FACTOR * 0.8f);
+
+ // B S B S B S B S Bar/Space pattern
+ // 11111111 0 1 0 1 0 1 000
+ private static final int[] START_PATTERN = {8, 1, 1, 1, 1, 1, 1, 3};
+ // 1111111 0 1 000 1 0 1 00 1
+ private static final int[] STOP_PATTERN = {7, 1, 1, 3, 1, 1, 1, 2, 1};
+ private static final int MAX_PIXEL_DRIFT = 3;
+ private static final int MAX_PATTERN_DRIFT = 5;
+ // if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged.
+ // if we set the value too high, then we might detect the start pattern from a neighbor barcode.
+ private static final int SKIPPED_ROW_COUNT_MAX = 25;
+ // A PDF471 barcode should have at least 3 rows, with each row being >= 3 times the module width. Therefore it should be at least
+ // 9 pixels tall. To be conservative, we use about half the size to ensure we don't miss it.
+ private static final int ROW_STEP = 5;
+ private static final int BARCODE_MIN_HEIGHT = 10;
+
+ private Detector() {
+ }
+
+ /**
+ * Detects a PDF417 Code in an image. Only checks 0 and 180 degree rotations.
+ *
+ * @param image barcode image to decode
+ * @param hints optional hints to detector
+ * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
+ * be found and returned
+ * @return {@link PDF417DetectorResult} encapsulating results of detecting a PDF417 code
+ * @throws NotFoundException if no PDF417 Code can be found
+ */
+ public static PDF417DetectorResult detect(BinaryBitmap image, Map hints, boolean multiple)
+ throws NotFoundException {
+ // TODO detection improvement, tryHarder could try several different luminance thresholds/blackpoints or even
+ // different binarizers
+ //boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+
+ BitMatrix bitMatrix = image.getBlackMatrix();
+
+ List barcodeCoordinates = detect(multiple, bitMatrix);
+ if (barcodeCoordinates.isEmpty()) {
+ bitMatrix = bitMatrix.clone();
+ bitMatrix.rotate180();
+ barcodeCoordinates = detect(multiple, bitMatrix);
+ }
+ return new PDF417DetectorResult(bitMatrix, barcodeCoordinates);
+ }
+
+ /**
+ * Detects PDF417 codes in an image. Only checks 0 degree rotation
+ * @param multiple if true, then the image is searched for multiple codes. If false, then at most one code will
+ * be found and returned
+ * @param bitMatrix bit matrix to detect barcodes in
+ * @return List of ResultPoint arrays containing the coordinates of found barcodes
+ */
+ private static List detect(boolean multiple, BitMatrix bitMatrix) {
+ List barcodeCoordinates = new ArrayList<>();
+ int row = 0;
+ int column = 0;
+ boolean foundBarcodeInRow = false;
+ while (row < bitMatrix.getHeight()) {
+ ResultPoint[] vertices = findVertices(bitMatrix, row, column);
+
+ if (vertices[0] == null && vertices[3] == null) {
+ if (!foundBarcodeInRow) {
+ // we didn't find any barcode so that's the end of searching
+ break;
+ }
+ // we didn't find a barcode starting at the given column and row. Try again from the first column and slightly
+ // below the lowest barcode we found so far.
+ foundBarcodeInRow = false;
+ column = 0;
+ for (ResultPoint[] barcodeCoordinate : barcodeCoordinates) {
+ if (barcodeCoordinate[1] != null) {
+ row = (int) Math.max(row, barcodeCoordinate[1].getY());
+ }
+ if (barcodeCoordinate[3] != null) {
+ row = Math.max(row, (int) barcodeCoordinate[3].getY());
+ }
+ }
+ row += ROW_STEP;
+ continue;
+ }
+ foundBarcodeInRow = true;
+ barcodeCoordinates.add(vertices);
+ if (!multiple) {
+ break;
+ }
+ // if we didn't find a right row indicator column, then continue the search for the next barcode after the
+ // start pattern of the barcode just found.
+ if (vertices[2] != null) {
+ column = (int) vertices[2].getX();
+ row = (int) vertices[2].getY();
+ } else {
+ column = (int) vertices[4].getX();
+ row = (int) vertices[4].getY();
+ }
+ }
+ return barcodeCoordinates;
+ }
+
+ /**
+ * Locate the vertices and the codewords area of a black blob using the Start
+ * and Stop patterns as locators.
+ *
+ * @param matrix the scanned barcode image.
+ * @return an array containing the vertices:
+ * vertices[0] x, y top left barcode
+ * vertices[1] x, y bottom left barcode
+ * vertices[2] x, y top right barcode
+ * vertices[3] x, y bottom right barcode
+ * vertices[4] x, y top left codeword area
+ * vertices[5] x, y bottom left codeword area
+ * vertices[6] x, y top right codeword area
+ * vertices[7] x, y bottom right codeword area
+ */
+ private static ResultPoint[] findVertices(BitMatrix matrix, int startRow, int startColumn) {
+ int height = matrix.getHeight();
+ int width = matrix.getWidth();
+
+ ResultPoint[] result = new ResultPoint[8];
+ copyToResult(result, findRowsWithPattern(matrix, height, width, startRow, startColumn, START_PATTERN),
+ INDEXES_START_PATTERN);
+
+ if (result[4] != null) {
+ startColumn = (int) result[4].getX();
+ startRow = (int) result[4].getY();
+ }
+ copyToResult(result, findRowsWithPattern(matrix, height, width, startRow, startColumn, STOP_PATTERN),
+ INDEXES_STOP_PATTERN);
+ return result;
+ }
+
+ private static void copyToResult(ResultPoint[] result, ResultPoint[] tmpResult, int[] destinationIndexes) {
+ for (int i = 0; i < destinationIndexes.length; i++) {
+ result[destinationIndexes[i]] = tmpResult[i];
+ }
+ }
+
+ private static ResultPoint[] findRowsWithPattern(BitMatrix matrix,
+ int height,
+ int width,
+ int startRow,
+ int startColumn,
+ int[] pattern) {
+ ResultPoint[] result = new ResultPoint[4];
+ boolean found = false;
+ int[] counters = new int[pattern.length];
+ for (; startRow < height; startRow += ROW_STEP) {
+ int[] loc = findGuardPattern(matrix, startColumn, startRow, width, false, pattern, counters);
+ if (loc != null) {
+ while (startRow > 0) {
+ int[] previousRowLoc = findGuardPattern(matrix, startColumn, --startRow, width, false, pattern, counters);
+ if (previousRowLoc != null) {
+ loc = previousRowLoc;
+ } else {
+ startRow++;
+ break;
+ }
+ }
+ result[0] = new ResultPoint(loc[0], startRow);
+ result[1] = new ResultPoint(loc[1], startRow);
+ found = true;
+ break;
+ }
+ }
+ int stopRow = startRow + 1;
+ // Last row of the current symbol that contains pattern
+ if (found) {
+ int skippedRowCount = 0;
+ int[] previousRowLoc = {(int) result[0].getX(), (int) result[1].getX()};
+ for (; stopRow < height; stopRow++) {
+ int[] loc = findGuardPattern(matrix, previousRowLoc[0], stopRow, width, false, pattern, counters);
+ // a found pattern is only considered to belong to the same barcode if the start and end positions
+ // don't differ too much. Pattern drift should be not bigger than two for consecutive rows. With
+ // a higher number of skipped rows drift could be larger. To keep it simple for now, we allow a slightly
+ // larger drift and don't check for skipped rows.
+ if (loc != null &&
+ Math.abs(previousRowLoc[0] - loc[0]) < MAX_PATTERN_DRIFT &&
+ Math.abs(previousRowLoc[1] - loc[1]) < MAX_PATTERN_DRIFT) {
+ previousRowLoc = loc;
+ skippedRowCount = 0;
+ } else {
+ if (skippedRowCount > SKIPPED_ROW_COUNT_MAX) {
+ break;
+ } else {
+ skippedRowCount++;
+ }
+ }
+ }
+ stopRow -= skippedRowCount + 1;
+ result[2] = new ResultPoint(previousRowLoc[0], stopRow);
+ result[3] = new ResultPoint(previousRowLoc[1], stopRow);
+ }
+ if (stopRow - startRow < BARCODE_MIN_HEIGHT) {
+ for (int i = 0; i < result.length; i++) {
+ result[i] = null;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @param matrix row of black/white values to search
+ * @param column x position to start search
+ * @param row y position to start search
+ * @param width the number of pixels to search on this row
+ * @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.
+ */
+ private static int[] findGuardPattern(BitMatrix matrix,
+ int column,
+ int row,
+ int width,
+ boolean whiteFirst,
+ int[] pattern,
+ int[] counters) {
+ Arrays.fill(counters, 0, counters.length, 0);
+ int patternLength = pattern.length;
+ boolean isWhite = whiteFirst;
+ int patternStart = column;
+ int pixelDrift = 0;
+
+ // if there are black pixels left of the current pixel shift to the left, but only for MAX_PIXEL_DRIFT pixels
+ while (matrix.get(patternStart, row) && patternStart > 0 && pixelDrift++ < MAX_PIXEL_DRIFT) {
+ patternStart--;
+ }
+ int x = patternStart;
+ int counterPosition = 0;
+ for (; x < width; x++) {
+ boolean pixel = matrix.get(x, row);
+ if (pixel ^ 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, patternLength - 2);
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ if (counterPosition == patternLength - 1) {
+ if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
+ return new int[] {patternStart, x - 1};
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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, where the ratio has been multiplied by 256.
+ * So, 0 means no variance (perfect match); 256 means the total
+ * variance between counters and patterns equals the pattern length,
+ * higher values mean even more variance
+ */
+ private static int patternMatchVariance(int[] counters, int[] pattern, int 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 Integer.MAX_VALUE;
+ }
+ // We're going to fake floating-point math in integers. We just need to use more bits.
+ // Scale up patternLength so that intermediate values below like scaledCounter will have
+ // more "significant digits".
+ int unitBarWidth = (total << INTEGER_MATH_SHIFT) / patternLength;
+ maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> INTEGER_MATH_SHIFT;
+
+ int totalVariance = 0;
+ for (int x = 0; x < numCounters; x++) {
+ int counter = counters[x] << INTEGER_MATH_SHIFT;
+ int scaledPattern = pattern[x] * unitBarWidth;
+ int variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
+ if (variance > maxIndividualVariance) {
+ return Integer.MAX_VALUE;
+ }
+ totalVariance += variance;
+ }
+ return totalVariance / total;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/detector/PDF417DetectorResult.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/detector/PDF417DetectorResult.java
new file mode 100644
index 000000000..081b77803
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/detector/PDF417DetectorResult.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 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.detector;
+
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.List;
+
+/**
+ * @author Guenther Grau
+ */
+public final class PDF417DetectorResult {
+
+ private final BitMatrix bits;
+ private final List points;
+
+ public PDF417DetectorResult(BitMatrix bits, List points) {
+ this.bits = bits;
+ this.points = points;
+ }
+
+ public BitMatrix getBits() {
+ return bits;
+ }
+
+ public List getPoints() {
+ return points;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/BarcodeMatrix.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/BarcodeMatrix.java
new file mode 100644
index 000000000..1cff11359
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/BarcodeMatrix.java
@@ -0,0 +1,82 @@
+/*
+ * 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.pdf417.encoder;
+
+/**
+ * Holds all of the information for a barcode in a format where it can be easily accessable
+ *
+ * @author Jacob Haynes
+ */
+public final class BarcodeMatrix {
+
+ private final BarcodeRow[] matrix;
+ private int currentRow;
+ private final int height;
+ private final int width;
+
+ /**
+ * @param height the height of the matrix (Rows)
+ * @param width the width of the matrix (Cols)
+ */
+ BarcodeMatrix(int height, int width) {
+ matrix = new BarcodeRow[height];
+ //Initializes the array to the correct width
+ for (int i = 0, matrixLength = matrix.length; i < matrixLength; i++) {
+ matrix[i] = new BarcodeRow((width + 4) * 17 + 1);
+ }
+ this.width = width * 17;
+ this.height = height;
+ this.currentRow = -1;
+ }
+
+ void set(int x, int y, byte value) {
+ matrix[y].set(x, value);
+ }
+
+ /*
+ void setMatrix(int x, int y, boolean black) {
+ set(x, y, (byte) (black ? 1 : 0));
+ }
+ */
+
+ void startRow() {
+ ++currentRow;
+ }
+
+ BarcodeRow getCurrentRow() {
+ return matrix[currentRow];
+ }
+
+ public byte[][] getMatrix() {
+ return getScaledMatrix(1, 1);
+ }
+
+ /*
+ public byte[][] getScaledMatrix(int scale) {
+ return getScaledMatrix(scale, scale);
+ }
+ */
+
+ public byte[][] getScaledMatrix(int xScale, int yScale) {
+ byte[][] matrixOut = new byte[height * yScale][width * xScale];
+ int yMax = height * yScale;
+ for (int i = 0; i < yMax; i++) {
+ matrixOut[yMax - i - 1] = matrix[i / yScale].getScaledRow(xScale);
+ }
+ return matrixOut;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/BarcodeRow.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/BarcodeRow.java
new file mode 100644
index 000000000..3ec70d580
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/BarcodeRow.java
@@ -0,0 +1,85 @@
+/*
+ * 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.pdf417.encoder;
+
+/**
+ * @author Jacob Haynes
+ */
+final class BarcodeRow {
+
+ private final byte[] row;
+ //A tacker for position in the bar
+ private int currentLocation;
+
+ /**
+ * Creates a Barcode row of the width
+ */
+ BarcodeRow(int width) {
+ this.row = new byte[width];
+ currentLocation = 0;
+ }
+
+ /**
+ * Sets a specific location in the bar
+ *
+ * @param x The location in the bar
+ * @param value Black if true, white if false;
+ */
+ void set(int x, byte value) {
+ row[x] = value;
+ }
+
+ /**
+ * Sets a specific location in the bar
+ *
+ * @param x The location in the bar
+ * @param black Black if true, white if false;
+ */
+ void set(int x, boolean black) {
+ row[x] = (byte) (black ? 1 : 0);
+ }
+
+ /**
+ * @param black A boolean which is true if the bar black false if it is white
+ * @param width How many spots wide the bar is.
+ */
+ void addBar(boolean black, int width) {
+ for (int ii = 0; ii < width; ii++) {
+ set(currentLocation++, black);
+ }
+ }
+
+ /*
+ byte[] getRow() {
+ return row;
+ }
+ */
+
+ /**
+ * This function scales the row
+ *
+ * @param scale How much you want the image to be scaled, must be greater than or equal to 1.
+ * @return the scaled row
+ */
+ byte[] getScaledRow(int scale) {
+ byte[] output = new byte[row.length * scale];
+ for (int i = 0; i < output.length; i++) {
+ output[i] = row[i / scale];
+ }
+ return output;
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/Compaction.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/Compaction.java
new file mode 100644
index 000000000..d0a85d600
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/Compaction.java
@@ -0,0 +1,26 @@
+/*
+ * 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.pdf417.encoder;
+
+public enum Compaction {
+
+ AUTO,
+ TEXT,
+ BYTE,
+ NUMERIC
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/Dimensions.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/Dimensions.java
new file mode 100644
index 000000000..83a736bd4
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/Dimensions.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 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.pdf417.encoder;
+
+/**
+ * Data object to specify the minimum and maximum number of rows and columns for a PDF417 barcode.
+ *
+ * @author qwandor@google.com (Andrew Walbran)
+ */
+public final class Dimensions {
+
+ private final int minCols;
+ private final int maxCols;
+ private final int minRows;
+ private final int maxRows;
+
+ public Dimensions(int minCols, int maxCols, int minRows, int maxRows) {
+ this.minCols = minCols;
+ this.maxCols = maxCols;
+ this.minRows = minRows;
+ this.maxRows = maxRows;
+ }
+
+ public int getMinCols() {
+ return minCols;
+ }
+
+ public int getMaxCols() {
+ return maxCols;
+ }
+
+ public int getMinRows() {
+ return minRows;
+ }
+
+ public int getMaxRows() {
+ return maxRows;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java
new file mode 100644
index 000000000..2c7555c24
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417.java
@@ -0,0 +1,769 @@
+/*
+ * Copyright 2006 Jeremias Maerki in part, and ZXing Authors in part
+ *
+ * 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.
+ */
+
+/*
+ * This file has been modified from its original form in Barcode4J.
+ */
+
+package com.google.zxing.pdf417.encoder;
+
+import com.google.zxing.WriterException;
+
+import java.nio.charset.Charset;
+
+/**
+ * Top-level class for the logic part of the PDF417 implementation.
+ */
+public final class PDF417 {
+
+ /**
+ * The start pattern (17 bits)
+ */
+ private static final int START_PATTERN = 0x1fea8;
+ /**
+ * The stop pattern (18 bits)
+ */
+ private static final int STOP_PATTERN = 0x3fa29;
+
+ /**
+ * The codeword table from the Annex A of ISO/IEC 15438:2001(E).
+ */
+ private static final int[][] CODEWORD_TABLE = {
+ {0x1d5c0, 0x1eaf0, 0x1f57c, 0x1d4e0, 0x1ea78, 0x1f53e,
+ 0x1a8c0, 0x1d470, 0x1a860, 0x15040, 0x1a830, 0x15020,
+ 0x1adc0, 0x1d6f0, 0x1eb7c, 0x1ace0, 0x1d678, 0x1eb3e,
+ 0x158c0, 0x1ac70, 0x15860, 0x15dc0, 0x1aef0, 0x1d77c,
+ 0x15ce0, 0x1ae78, 0x1d73e, 0x15c70, 0x1ae3c, 0x15ef0,
+ 0x1af7c, 0x15e78, 0x1af3e, 0x15f7c, 0x1f5fa, 0x1d2e0,
+ 0x1e978, 0x1f4be, 0x1a4c0, 0x1d270, 0x1e93c, 0x1a460,
+ 0x1d238, 0x14840, 0x1a430, 0x1d21c, 0x14820, 0x1a418,
+ 0x14810, 0x1a6e0, 0x1d378, 0x1e9be, 0x14cc0, 0x1a670,
+ 0x1d33c, 0x14c60, 0x1a638, 0x1d31e, 0x14c30, 0x1a61c,
+ 0x14ee0, 0x1a778, 0x1d3be, 0x14e70, 0x1a73c, 0x14e38,
+ 0x1a71e, 0x14f78, 0x1a7be, 0x14f3c, 0x14f1e, 0x1a2c0,
+ 0x1d170, 0x1e8bc, 0x1a260, 0x1d138, 0x1e89e, 0x14440,
+ 0x1a230, 0x1d11c, 0x14420, 0x1a218, 0x14410, 0x14408,
+ 0x146c0, 0x1a370, 0x1d1bc, 0x14660, 0x1a338, 0x1d19e,
+ 0x14630, 0x1a31c, 0x14618, 0x1460c, 0x14770, 0x1a3bc,
+ 0x14738, 0x1a39e, 0x1471c, 0x147bc, 0x1a160, 0x1d0b8,
+ 0x1e85e, 0x14240, 0x1a130, 0x1d09c, 0x14220, 0x1a118,
+ 0x1d08e, 0x14210, 0x1a10c, 0x14208, 0x1a106, 0x14360,
+ 0x1a1b8, 0x1d0de, 0x14330, 0x1a19c, 0x14318, 0x1a18e,
+ 0x1430c, 0x14306, 0x1a1de, 0x1438e, 0x14140, 0x1a0b0,
+ 0x1d05c, 0x14120, 0x1a098, 0x1d04e, 0x14110, 0x1a08c,
+ 0x14108, 0x1a086, 0x14104, 0x141b0, 0x14198, 0x1418c,
+ 0x140a0, 0x1d02e, 0x1a04c, 0x1a046, 0x14082, 0x1cae0,
+ 0x1e578, 0x1f2be, 0x194c0, 0x1ca70, 0x1e53c, 0x19460,
+ 0x1ca38, 0x1e51e, 0x12840, 0x19430, 0x12820, 0x196e0,
+ 0x1cb78, 0x1e5be, 0x12cc0, 0x19670, 0x1cb3c, 0x12c60,
+ 0x19638, 0x12c30, 0x12c18, 0x12ee0, 0x19778, 0x1cbbe,
+ 0x12e70, 0x1973c, 0x12e38, 0x12e1c, 0x12f78, 0x197be,
+ 0x12f3c, 0x12fbe, 0x1dac0, 0x1ed70, 0x1f6bc, 0x1da60,
+ 0x1ed38, 0x1f69e, 0x1b440, 0x1da30, 0x1ed1c, 0x1b420,
+ 0x1da18, 0x1ed0e, 0x1b410, 0x1da0c, 0x192c0, 0x1c970,
+ 0x1e4bc, 0x1b6c0, 0x19260, 0x1c938, 0x1e49e, 0x1b660,
+ 0x1db38, 0x1ed9e, 0x16c40, 0x12420, 0x19218, 0x1c90e,
+ 0x16c20, 0x1b618, 0x16c10, 0x126c0, 0x19370, 0x1c9bc,
+ 0x16ec0, 0x12660, 0x19338, 0x1c99e, 0x16e60, 0x1b738,
+ 0x1db9e, 0x16e30, 0x12618, 0x16e18, 0x12770, 0x193bc,
+ 0x16f70, 0x12738, 0x1939e, 0x16f38, 0x1b79e, 0x16f1c,
+ 0x127bc, 0x16fbc, 0x1279e, 0x16f9e, 0x1d960, 0x1ecb8,
+ 0x1f65e, 0x1b240, 0x1d930, 0x1ec9c, 0x1b220, 0x1d918,
+ 0x1ec8e, 0x1b210, 0x1d90c, 0x1b208, 0x1b204, 0x19160,
+ 0x1c8b8, 0x1e45e, 0x1b360, 0x19130, 0x1c89c, 0x16640,
+ 0x12220, 0x1d99c, 0x1c88e, 0x16620, 0x12210, 0x1910c,
+ 0x16610, 0x1b30c, 0x19106, 0x12204, 0x12360, 0x191b8,
+ 0x1c8de, 0x16760, 0x12330, 0x1919c, 0x16730, 0x1b39c,
+ 0x1918e, 0x16718, 0x1230c, 0x12306, 0x123b8, 0x191de,
+ 0x167b8, 0x1239c, 0x1679c, 0x1238e, 0x1678e, 0x167de,
+ 0x1b140, 0x1d8b0, 0x1ec5c, 0x1b120, 0x1d898, 0x1ec4e,
+ 0x1b110, 0x1d88c, 0x1b108, 0x1d886, 0x1b104, 0x1b102,
+ 0x12140, 0x190b0, 0x1c85c, 0x16340, 0x12120, 0x19098,
+ 0x1c84e, 0x16320, 0x1b198, 0x1d8ce, 0x16310, 0x12108,
+ 0x19086, 0x16308, 0x1b186, 0x16304, 0x121b0, 0x190dc,
+ 0x163b0, 0x12198, 0x190ce, 0x16398, 0x1b1ce, 0x1638c,
+ 0x12186, 0x16386, 0x163dc, 0x163ce, 0x1b0a0, 0x1d858,
+ 0x1ec2e, 0x1b090, 0x1d84c, 0x1b088, 0x1d846, 0x1b084,
+ 0x1b082, 0x120a0, 0x19058, 0x1c82e, 0x161a0, 0x12090,
+ 0x1904c, 0x16190, 0x1b0cc, 0x19046, 0x16188, 0x12084,
+ 0x16184, 0x12082, 0x120d8, 0x161d8, 0x161cc, 0x161c6,
+ 0x1d82c, 0x1d826, 0x1b042, 0x1902c, 0x12048, 0x160c8,
+ 0x160c4, 0x160c2, 0x18ac0, 0x1c570, 0x1e2bc, 0x18a60,
+ 0x1c538, 0x11440, 0x18a30, 0x1c51c, 0x11420, 0x18a18,
+ 0x11410, 0x11408, 0x116c0, 0x18b70, 0x1c5bc, 0x11660,
+ 0x18b38, 0x1c59e, 0x11630, 0x18b1c, 0x11618, 0x1160c,
+ 0x11770, 0x18bbc, 0x11738, 0x18b9e, 0x1171c, 0x117bc,
+ 0x1179e, 0x1cd60, 0x1e6b8, 0x1f35e, 0x19a40, 0x1cd30,
+ 0x1e69c, 0x19a20, 0x1cd18, 0x1e68e, 0x19a10, 0x1cd0c,
+ 0x19a08, 0x1cd06, 0x18960, 0x1c4b8, 0x1e25e, 0x19b60,
+ 0x18930, 0x1c49c, 0x13640, 0x11220, 0x1cd9c, 0x1c48e,
+ 0x13620, 0x19b18, 0x1890c, 0x13610, 0x11208, 0x13608,
+ 0x11360, 0x189b8, 0x1c4de, 0x13760, 0x11330, 0x1cdde,
+ 0x13730, 0x19b9c, 0x1898e, 0x13718, 0x1130c, 0x1370c,
+ 0x113b8, 0x189de, 0x137b8, 0x1139c, 0x1379c, 0x1138e,
+ 0x113de, 0x137de, 0x1dd40, 0x1eeb0, 0x1f75c, 0x1dd20,
+ 0x1ee98, 0x1f74e, 0x1dd10, 0x1ee8c, 0x1dd08, 0x1ee86,
+ 0x1dd04, 0x19940, 0x1ccb0, 0x1e65c, 0x1bb40, 0x19920,
+ 0x1eedc, 0x1e64e, 0x1bb20, 0x1dd98, 0x1eece, 0x1bb10,
+ 0x19908, 0x1cc86, 0x1bb08, 0x1dd86, 0x19902, 0x11140,
+ 0x188b0, 0x1c45c, 0x13340, 0x11120, 0x18898, 0x1c44e,
+ 0x17740, 0x13320, 0x19998, 0x1ccce, 0x17720, 0x1bb98,
+ 0x1ddce, 0x18886, 0x17710, 0x13308, 0x19986, 0x17708,
+ 0x11102, 0x111b0, 0x188dc, 0x133b0, 0x11198, 0x188ce,
+ 0x177b0, 0x13398, 0x199ce, 0x17798, 0x1bbce, 0x11186,
+ 0x13386, 0x111dc, 0x133dc, 0x111ce, 0x177dc, 0x133ce,
+ 0x1dca0, 0x1ee58, 0x1f72e, 0x1dc90, 0x1ee4c, 0x1dc88,
+ 0x1ee46, 0x1dc84, 0x1dc82, 0x198a0, 0x1cc58, 0x1e62e,
+ 0x1b9a0, 0x19890, 0x1ee6e, 0x1b990, 0x1dccc, 0x1cc46,
+ 0x1b988, 0x19884, 0x1b984, 0x19882, 0x1b982, 0x110a0,
+ 0x18858, 0x1c42e, 0x131a0, 0x11090, 0x1884c, 0x173a0,
+ 0x13190, 0x198cc, 0x18846, 0x17390, 0x1b9cc, 0x11084,
+ 0x17388, 0x13184, 0x11082, 0x13182, 0x110d8, 0x1886e,
+ 0x131d8, 0x110cc, 0x173d8, 0x131cc, 0x110c6, 0x173cc,
+ 0x131c6, 0x110ee, 0x173ee, 0x1dc50, 0x1ee2c, 0x1dc48,
+ 0x1ee26, 0x1dc44, 0x1dc42, 0x19850, 0x1cc2c, 0x1b8d0,
+ 0x19848, 0x1cc26, 0x1b8c8, 0x1dc66, 0x1b8c4, 0x19842,
+ 0x1b8c2, 0x11050, 0x1882c, 0x130d0, 0x11048, 0x18826,
+ 0x171d0, 0x130c8, 0x19866, 0x171c8, 0x1b8e6, 0x11042,
+ 0x171c4, 0x130c2, 0x171c2, 0x130ec, 0x171ec, 0x171e6,
+ 0x1ee16, 0x1dc22, 0x1cc16, 0x19824, 0x19822, 0x11028,
+ 0x13068, 0x170e8, 0x11022, 0x13062, 0x18560, 0x10a40,
+ 0x18530, 0x10a20, 0x18518, 0x1c28e, 0x10a10, 0x1850c,
+ 0x10a08, 0x18506, 0x10b60, 0x185b8, 0x1c2de, 0x10b30,
+ 0x1859c, 0x10b18, 0x1858e, 0x10b0c, 0x10b06, 0x10bb8,
+ 0x185de, 0x10b9c, 0x10b8e, 0x10bde, 0x18d40, 0x1c6b0,
+ 0x1e35c, 0x18d20, 0x1c698, 0x18d10, 0x1c68c, 0x18d08,
+ 0x1c686, 0x18d04, 0x10940, 0x184b0, 0x1c25c, 0x11b40,
+ 0x10920, 0x1c6dc, 0x1c24e, 0x11b20, 0x18d98, 0x1c6ce,
+ 0x11b10, 0x10908, 0x18486, 0x11b08, 0x18d86, 0x10902,
+ 0x109b0, 0x184dc, 0x11bb0, 0x10998, 0x184ce, 0x11b98,
+ 0x18dce, 0x11b8c, 0x10986, 0x109dc, 0x11bdc, 0x109ce,
+ 0x11bce, 0x1cea0, 0x1e758, 0x1f3ae, 0x1ce90, 0x1e74c,
+ 0x1ce88, 0x1e746, 0x1ce84, 0x1ce82, 0x18ca0, 0x1c658,
+ 0x19da0, 0x18c90, 0x1c64c, 0x19d90, 0x1cecc, 0x1c646,
+ 0x19d88, 0x18c84, 0x19d84, 0x18c82, 0x19d82, 0x108a0,
+ 0x18458, 0x119a0, 0x10890, 0x1c66e, 0x13ba0, 0x11990,
+ 0x18ccc, 0x18446, 0x13b90, 0x19dcc, 0x10884, 0x13b88,
+ 0x11984, 0x10882, 0x11982, 0x108d8, 0x1846e, 0x119d8,
+ 0x108cc, 0x13bd8, 0x119cc, 0x108c6, 0x13bcc, 0x119c6,
+ 0x108ee, 0x119ee, 0x13bee, 0x1ef50, 0x1f7ac, 0x1ef48,
+ 0x1f7a6, 0x1ef44, 0x1ef42, 0x1ce50, 0x1e72c, 0x1ded0,
+ 0x1ef6c, 0x1e726, 0x1dec8, 0x1ef66, 0x1dec4, 0x1ce42,
+ 0x1dec2, 0x18c50, 0x1c62c, 0x19cd0, 0x18c48, 0x1c626,
+ 0x1bdd0, 0x19cc8, 0x1ce66, 0x1bdc8, 0x1dee6, 0x18c42,
+ 0x1bdc4, 0x19cc2, 0x1bdc2, 0x10850, 0x1842c, 0x118d0,
+ 0x10848, 0x18426, 0x139d0, 0x118c8, 0x18c66, 0x17bd0,
+ 0x139c8, 0x19ce6, 0x10842, 0x17bc8, 0x1bde6, 0x118c2,
+ 0x17bc4, 0x1086c, 0x118ec, 0x10866, 0x139ec, 0x118e6,
+ 0x17bec, 0x139e6, 0x17be6, 0x1ef28, 0x1f796, 0x1ef24,
+ 0x1ef22, 0x1ce28, 0x1e716, 0x1de68, 0x1ef36, 0x1de64,
+ 0x1ce22, 0x1de62, 0x18c28, 0x1c616, 0x19c68, 0x18c24,
+ 0x1bce8, 0x19c64, 0x18c22, 0x1bce4, 0x19c62, 0x1bce2,
+ 0x10828, 0x18416, 0x11868, 0x18c36, 0x138e8, 0x11864,
+ 0x10822, 0x179e8, 0x138e4, 0x11862, 0x179e4, 0x138e2,
+ 0x179e2, 0x11876, 0x179f6, 0x1ef12, 0x1de34, 0x1de32,
+ 0x19c34, 0x1bc74, 0x1bc72, 0x11834, 0x13874, 0x178f4,
+ 0x178f2, 0x10540, 0x10520, 0x18298, 0x10510, 0x10508,
+ 0x10504, 0x105b0, 0x10598, 0x1058c, 0x10586, 0x105dc,
+ 0x105ce, 0x186a0, 0x18690, 0x1c34c, 0x18688, 0x1c346,
+ 0x18684, 0x18682, 0x104a0, 0x18258, 0x10da0, 0x186d8,
+ 0x1824c, 0x10d90, 0x186cc, 0x10d88, 0x186c6, 0x10d84,
+ 0x10482, 0x10d82, 0x104d8, 0x1826e, 0x10dd8, 0x186ee,
+ 0x10dcc, 0x104c6, 0x10dc6, 0x104ee, 0x10dee, 0x1c750,
+ 0x1c748, 0x1c744, 0x1c742, 0x18650, 0x18ed0, 0x1c76c,
+ 0x1c326, 0x18ec8, 0x1c766, 0x18ec4, 0x18642, 0x18ec2,
+ 0x10450, 0x10cd0, 0x10448, 0x18226, 0x11dd0, 0x10cc8,
+ 0x10444, 0x11dc8, 0x10cc4, 0x10442, 0x11dc4, 0x10cc2,
+ 0x1046c, 0x10cec, 0x10466, 0x11dec, 0x10ce6, 0x11de6,
+ 0x1e7a8, 0x1e7a4, 0x1e7a2, 0x1c728, 0x1cf68, 0x1e7b6,
+ 0x1cf64, 0x1c722, 0x1cf62, 0x18628, 0x1c316, 0x18e68,
+ 0x1c736, 0x19ee8, 0x18e64, 0x18622, 0x19ee4, 0x18e62,
+ 0x19ee2, 0x10428, 0x18216, 0x10c68, 0x18636, 0x11ce8,
+ 0x10c64, 0x10422, 0x13de8, 0x11ce4, 0x10c62, 0x13de4,
+ 0x11ce2, 0x10436, 0x10c76, 0x11cf6, 0x13df6, 0x1f7d4,
+ 0x1f7d2, 0x1e794, 0x1efb4, 0x1e792, 0x1efb2, 0x1c714,
+ 0x1cf34, 0x1c712, 0x1df74, 0x1cf32, 0x1df72, 0x18614,
+ 0x18e34, 0x18612, 0x19e74, 0x18e32, 0x1bef4},
+ {0x1f560, 0x1fab8, 0x1ea40, 0x1f530, 0x1fa9c, 0x1ea20,
+ 0x1f518, 0x1fa8e, 0x1ea10, 0x1f50c, 0x1ea08, 0x1f506,
+ 0x1ea04, 0x1eb60, 0x1f5b8, 0x1fade, 0x1d640, 0x1eb30,
+ 0x1f59c, 0x1d620, 0x1eb18, 0x1f58e, 0x1d610, 0x1eb0c,
+ 0x1d608, 0x1eb06, 0x1d604, 0x1d760, 0x1ebb8, 0x1f5de,
+ 0x1ae40, 0x1d730, 0x1eb9c, 0x1ae20, 0x1d718, 0x1eb8e,
+ 0x1ae10, 0x1d70c, 0x1ae08, 0x1d706, 0x1ae04, 0x1af60,
+ 0x1d7b8, 0x1ebde, 0x15e40, 0x1af30, 0x1d79c, 0x15e20,
+ 0x1af18, 0x1d78e, 0x15e10, 0x1af0c, 0x15e08, 0x1af06,
+ 0x15f60, 0x1afb8, 0x1d7de, 0x15f30, 0x1af9c, 0x15f18,
+ 0x1af8e, 0x15f0c, 0x15fb8, 0x1afde, 0x15f9c, 0x15f8e,
+ 0x1e940, 0x1f4b0, 0x1fa5c, 0x1e920, 0x1f498, 0x1fa4e,
+ 0x1e910, 0x1f48c, 0x1e908, 0x1f486, 0x1e904, 0x1e902,
+ 0x1d340, 0x1e9b0, 0x1f4dc, 0x1d320, 0x1e998, 0x1f4ce,
+ 0x1d310, 0x1e98c, 0x1d308, 0x1e986, 0x1d304, 0x1d302,
+ 0x1a740, 0x1d3b0, 0x1e9dc, 0x1a720, 0x1d398, 0x1e9ce,
+ 0x1a710, 0x1d38c, 0x1a708, 0x1d386, 0x1a704, 0x1a702,
+ 0x14f40, 0x1a7b0, 0x1d3dc, 0x14f20, 0x1a798, 0x1d3ce,
+ 0x14f10, 0x1a78c, 0x14f08, 0x1a786, 0x14f04, 0x14fb0,
+ 0x1a7dc, 0x14f98, 0x1a7ce, 0x14f8c, 0x14f86, 0x14fdc,
+ 0x14fce, 0x1e8a0, 0x1f458, 0x1fa2e, 0x1e890, 0x1f44c,
+ 0x1e888, 0x1f446, 0x1e884, 0x1e882, 0x1d1a0, 0x1e8d8,
+ 0x1f46e, 0x1d190, 0x1e8cc, 0x1d188, 0x1e8c6, 0x1d184,
+ 0x1d182, 0x1a3a0, 0x1d1d8, 0x1e8ee, 0x1a390, 0x1d1cc,
+ 0x1a388, 0x1d1c6, 0x1a384, 0x1a382, 0x147a0, 0x1a3d8,
+ 0x1d1ee, 0x14790, 0x1a3cc, 0x14788, 0x1a3c6, 0x14784,
+ 0x14782, 0x147d8, 0x1a3ee, 0x147cc, 0x147c6, 0x147ee,
+ 0x1e850, 0x1f42c, 0x1e848, 0x1f426, 0x1e844, 0x1e842,
+ 0x1d0d0, 0x1e86c, 0x1d0c8, 0x1e866, 0x1d0c4, 0x1d0c2,
+ 0x1a1d0, 0x1d0ec, 0x1a1c8, 0x1d0e6, 0x1a1c4, 0x1a1c2,
+ 0x143d0, 0x1a1ec, 0x143c8, 0x1a1e6, 0x143c4, 0x143c2,
+ 0x143ec, 0x143e6, 0x1e828, 0x1f416, 0x1e824, 0x1e822,
+ 0x1d068, 0x1e836, 0x1d064, 0x1d062, 0x1a0e8, 0x1d076,
+ 0x1a0e4, 0x1a0e2, 0x141e8, 0x1a0f6, 0x141e4, 0x141e2,
+ 0x1e814, 0x1e812, 0x1d034, 0x1d032, 0x1a074, 0x1a072,
+ 0x1e540, 0x1f2b0, 0x1f95c, 0x1e520, 0x1f298, 0x1f94e,
+ 0x1e510, 0x1f28c, 0x1e508, 0x1f286, 0x1e504, 0x1e502,
+ 0x1cb40, 0x1e5b0, 0x1f2dc, 0x1cb20, 0x1e598, 0x1f2ce,
+ 0x1cb10, 0x1e58c, 0x1cb08, 0x1e586, 0x1cb04, 0x1cb02,
+ 0x19740, 0x1cbb0, 0x1e5dc, 0x19720, 0x1cb98, 0x1e5ce,
+ 0x19710, 0x1cb8c, 0x19708, 0x1cb86, 0x19704, 0x19702,
+ 0x12f40, 0x197b0, 0x1cbdc, 0x12f20, 0x19798, 0x1cbce,
+ 0x12f10, 0x1978c, 0x12f08, 0x19786, 0x12f04, 0x12fb0,
+ 0x197dc, 0x12f98, 0x197ce, 0x12f8c, 0x12f86, 0x12fdc,
+ 0x12fce, 0x1f6a0, 0x1fb58, 0x16bf0, 0x1f690, 0x1fb4c,
+ 0x169f8, 0x1f688, 0x1fb46, 0x168fc, 0x1f684, 0x1f682,
+ 0x1e4a0, 0x1f258, 0x1f92e, 0x1eda0, 0x1e490, 0x1fb6e,
+ 0x1ed90, 0x1f6cc, 0x1f246, 0x1ed88, 0x1e484, 0x1ed84,
+ 0x1e482, 0x1ed82, 0x1c9a0, 0x1e4d8, 0x1f26e, 0x1dba0,
+ 0x1c990, 0x1e4cc, 0x1db90, 0x1edcc, 0x1e4c6, 0x1db88,
+ 0x1c984, 0x1db84, 0x1c982, 0x1db82, 0x193a0, 0x1c9d8,
+ 0x1e4ee, 0x1b7a0, 0x19390, 0x1c9cc, 0x1b790, 0x1dbcc,
+ 0x1c9c6, 0x1b788, 0x19384, 0x1b784, 0x19382, 0x1b782,
+ 0x127a0, 0x193d8, 0x1c9ee, 0x16fa0, 0x12790, 0x193cc,
+ 0x16f90, 0x1b7cc, 0x193c6, 0x16f88, 0x12784, 0x16f84,
+ 0x12782, 0x127d8, 0x193ee, 0x16fd8, 0x127cc, 0x16fcc,
+ 0x127c6, 0x16fc6, 0x127ee, 0x1f650, 0x1fb2c, 0x165f8,
+ 0x1f648, 0x1fb26, 0x164fc, 0x1f644, 0x1647e, 0x1f642,
+ 0x1e450, 0x1f22c, 0x1ecd0, 0x1e448, 0x1f226, 0x1ecc8,
+ 0x1f666, 0x1ecc4, 0x1e442, 0x1ecc2, 0x1c8d0, 0x1e46c,
+ 0x1d9d0, 0x1c8c8, 0x1e466, 0x1d9c8, 0x1ece6, 0x1d9c4,
+ 0x1c8c2, 0x1d9c2, 0x191d0, 0x1c8ec, 0x1b3d0, 0x191c8,
+ 0x1c8e6, 0x1b3c8, 0x1d9e6, 0x1b3c4, 0x191c2, 0x1b3c2,
+ 0x123d0, 0x191ec, 0x167d0, 0x123c8, 0x191e6, 0x167c8,
+ 0x1b3e6, 0x167c4, 0x123c2, 0x167c2, 0x123ec, 0x167ec,
+ 0x123e6, 0x167e6, 0x1f628, 0x1fb16, 0x162fc, 0x1f624,
+ 0x1627e, 0x1f622, 0x1e428, 0x1f216, 0x1ec68, 0x1f636,
+ 0x1ec64, 0x1e422, 0x1ec62, 0x1c868, 0x1e436, 0x1d8e8,
+ 0x1c864, 0x1d8e4, 0x1c862, 0x1d8e2, 0x190e8, 0x1c876,
+ 0x1b1e8, 0x1d8f6, 0x1b1e4, 0x190e2, 0x1b1e2, 0x121e8,
+ 0x190f6, 0x163e8, 0x121e4, 0x163e4, 0x121e2, 0x163e2,
+ 0x121f6, 0x163f6, 0x1f614, 0x1617e, 0x1f612, 0x1e414,
+ 0x1ec34, 0x1e412, 0x1ec32, 0x1c834, 0x1d874, 0x1c832,
+ 0x1d872, 0x19074, 0x1b0f4, 0x19072, 0x1b0f2, 0x120f4,
+ 0x161f4, 0x120f2, 0x161f2, 0x1f60a, 0x1e40a, 0x1ec1a,
+ 0x1c81a, 0x1d83a, 0x1903a, 0x1b07a, 0x1e2a0, 0x1f158,
+ 0x1f8ae, 0x1e290, 0x1f14c, 0x1e288, 0x1f146, 0x1e284,
+ 0x1e282, 0x1c5a0, 0x1e2d8, 0x1f16e, 0x1c590, 0x1e2cc,
+ 0x1c588, 0x1e2c6, 0x1c584, 0x1c582, 0x18ba0, 0x1c5d8,
+ 0x1e2ee, 0x18b90, 0x1c5cc, 0x18b88, 0x1c5c6, 0x18b84,
+ 0x18b82, 0x117a0, 0x18bd8, 0x1c5ee, 0x11790, 0x18bcc,
+ 0x11788, 0x18bc6, 0x11784, 0x11782, 0x117d8, 0x18bee,
+ 0x117cc, 0x117c6, 0x117ee, 0x1f350, 0x1f9ac, 0x135f8,
+ 0x1f348, 0x1f9a6, 0x134fc, 0x1f344, 0x1347e, 0x1f342,
+ 0x1e250, 0x1f12c, 0x1e6d0, 0x1e248, 0x1f126, 0x1e6c8,
+ 0x1f366, 0x1e6c4, 0x1e242, 0x1e6c2, 0x1c4d0, 0x1e26c,
+ 0x1cdd0, 0x1c4c8, 0x1e266, 0x1cdc8, 0x1e6e6, 0x1cdc4,
+ 0x1c4c2, 0x1cdc2, 0x189d0, 0x1c4ec, 0x19bd0, 0x189c8,
+ 0x1c4e6, 0x19bc8, 0x1cde6, 0x19bc4, 0x189c2, 0x19bc2,
+ 0x113d0, 0x189ec, 0x137d0, 0x113c8, 0x189e6, 0x137c8,
+ 0x19be6, 0x137c4, 0x113c2, 0x137c2, 0x113ec, 0x137ec,
+ 0x113e6, 0x137e6, 0x1fba8, 0x175f0, 0x1bafc, 0x1fba4,
+ 0x174f8, 0x1ba7e, 0x1fba2, 0x1747c, 0x1743e, 0x1f328,
+ 0x1f996, 0x132fc, 0x1f768, 0x1fbb6, 0x176fc, 0x1327e,
+ 0x1f764, 0x1f322, 0x1767e, 0x1f762, 0x1e228, 0x1f116,
+ 0x1e668, 0x1e224, 0x1eee8, 0x1f776, 0x1e222, 0x1eee4,
+ 0x1e662, 0x1eee2, 0x1c468, 0x1e236, 0x1cce8, 0x1c464,
+ 0x1dde8, 0x1cce4, 0x1c462, 0x1dde4, 0x1cce2, 0x1dde2,
+ 0x188e8, 0x1c476, 0x199e8, 0x188e4, 0x1bbe8, 0x199e4,
+ 0x188e2, 0x1bbe4, 0x199e2, 0x1bbe2, 0x111e8, 0x188f6,
+ 0x133e8, 0x111e4, 0x177e8, 0x133e4, 0x111e2, 0x177e4,
+ 0x133e2, 0x177e2, 0x111f6, 0x133f6, 0x1fb94, 0x172f8,
+ 0x1b97e, 0x1fb92, 0x1727c, 0x1723e, 0x1f314, 0x1317e,
+ 0x1f734, 0x1f312, 0x1737e, 0x1f732, 0x1e214, 0x1e634,
+ 0x1e212, 0x1ee74, 0x1e632, 0x1ee72, 0x1c434, 0x1cc74,
+ 0x1c432, 0x1dcf4, 0x1cc72, 0x1dcf2, 0x18874, 0x198f4,
+ 0x18872, 0x1b9f4, 0x198f2, 0x1b9f2, 0x110f4, 0x131f4,
+ 0x110f2, 0x173f4, 0x131f2, 0x173f2, 0x1fb8a, 0x1717c,
+ 0x1713e, 0x1f30a, 0x1f71a, 0x1e20a, 0x1e61a, 0x1ee3a,
+ 0x1c41a, 0x1cc3a, 0x1dc7a, 0x1883a, 0x1987a, 0x1b8fa,
+ 0x1107a, 0x130fa, 0x171fa, 0x170be, 0x1e150, 0x1f0ac,
+ 0x1e148, 0x1f0a6, 0x1e144, 0x1e142, 0x1c2d0, 0x1e16c,
+ 0x1c2c8, 0x1e166, 0x1c2c4, 0x1c2c2, 0x185d0, 0x1c2ec,
+ 0x185c8, 0x1c2e6, 0x185c4, 0x185c2, 0x10bd0, 0x185ec,
+ 0x10bc8, 0x185e6, 0x10bc4, 0x10bc2, 0x10bec, 0x10be6,
+ 0x1f1a8, 0x1f8d6, 0x11afc, 0x1f1a4, 0x11a7e, 0x1f1a2,
+ 0x1e128, 0x1f096, 0x1e368, 0x1e124, 0x1e364, 0x1e122,
+ 0x1e362, 0x1c268, 0x1e136, 0x1c6e8, 0x1c264, 0x1c6e4,
+ 0x1c262, 0x1c6e2, 0x184e8, 0x1c276, 0x18de8, 0x184e4,
+ 0x18de4, 0x184e2, 0x18de2, 0x109e8, 0x184f6, 0x11be8,
+ 0x109e4, 0x11be4, 0x109e2, 0x11be2, 0x109f6, 0x11bf6,
+ 0x1f9d4, 0x13af8, 0x19d7e, 0x1f9d2, 0x13a7c, 0x13a3e,
+ 0x1f194, 0x1197e, 0x1f3b4, 0x1f192, 0x13b7e, 0x1f3b2,
+ 0x1e114, 0x1e334, 0x1e112, 0x1e774, 0x1e332, 0x1e772,
+ 0x1c234, 0x1c674, 0x1c232, 0x1cef4, 0x1c672, 0x1cef2,
+ 0x18474, 0x18cf4, 0x18472, 0x19df4, 0x18cf2, 0x19df2,
+ 0x108f4, 0x119f4, 0x108f2, 0x13bf4, 0x119f2, 0x13bf2,
+ 0x17af0, 0x1bd7c, 0x17a78, 0x1bd3e, 0x17a3c, 0x17a1e,
+ 0x1f9ca, 0x1397c, 0x1fbda, 0x17b7c, 0x1393e, 0x17b3e,
+ 0x1f18a, 0x1f39a, 0x1f7ba, 0x1e10a, 0x1e31a, 0x1e73a,
+ 0x1ef7a, 0x1c21a, 0x1c63a, 0x1ce7a, 0x1defa, 0x1843a,
+ 0x18c7a, 0x19cfa, 0x1bdfa, 0x1087a, 0x118fa, 0x139fa,
+ 0x17978, 0x1bcbe, 0x1793c, 0x1791e, 0x138be, 0x179be,
+ 0x178bc, 0x1789e, 0x1785e, 0x1e0a8, 0x1e0a4, 0x1e0a2,
+ 0x1c168, 0x1e0b6, 0x1c164, 0x1c162, 0x182e8, 0x1c176,
+ 0x182e4, 0x182e2, 0x105e8, 0x182f6, 0x105e4, 0x105e2,
+ 0x105f6, 0x1f0d4, 0x10d7e, 0x1f0d2, 0x1e094, 0x1e1b4,
+ 0x1e092, 0x1e1b2, 0x1c134, 0x1c374, 0x1c132, 0x1c372,
+ 0x18274, 0x186f4, 0x18272, 0x186f2, 0x104f4, 0x10df4,
+ 0x104f2, 0x10df2, 0x1f8ea, 0x11d7c, 0x11d3e, 0x1f0ca,
+ 0x1f1da, 0x1e08a, 0x1e19a, 0x1e3ba, 0x1c11a, 0x1c33a,
+ 0x1c77a, 0x1823a, 0x1867a, 0x18efa, 0x1047a, 0x10cfa,
+ 0x11dfa, 0x13d78, 0x19ebe, 0x13d3c, 0x13d1e, 0x11cbe,
+ 0x13dbe, 0x17d70, 0x1bebc, 0x17d38, 0x1be9e, 0x17d1c,
+ 0x17d0e, 0x13cbc, 0x17dbc, 0x13c9e, 0x17d9e, 0x17cb8,
+ 0x1be5e, 0x17c9c, 0x17c8e, 0x13c5e, 0x17cde, 0x17c5c,
+ 0x17c4e, 0x17c2e, 0x1c0b4, 0x1c0b2, 0x18174, 0x18172,
+ 0x102f4, 0x102f2, 0x1e0da, 0x1c09a, 0x1c1ba, 0x1813a,
+ 0x1837a, 0x1027a, 0x106fa, 0x10ebe, 0x11ebc, 0x11e9e,
+ 0x13eb8, 0x19f5e, 0x13e9c, 0x13e8e, 0x11e5e, 0x13ede,
+ 0x17eb0, 0x1bf5c, 0x17e98, 0x1bf4e, 0x17e8c, 0x17e86,
+ 0x13e5c, 0x17edc, 0x13e4e, 0x17ece, 0x17e58, 0x1bf2e,
+ 0x17e4c, 0x17e46, 0x13e2e, 0x17e6e, 0x17e2c, 0x17e26,
+ 0x10f5e, 0x11f5c, 0x11f4e, 0x13f58, 0x19fae, 0x13f4c,
+ 0x13f46, 0x11f2e, 0x13f6e, 0x13f2c, 0x13f26},
+ {0x1abe0, 0x1d5f8, 0x153c0, 0x1a9f0, 0x1d4fc, 0x151e0,
+ 0x1a8f8, 0x1d47e, 0x150f0, 0x1a87c, 0x15078, 0x1fad0,
+ 0x15be0, 0x1adf8, 0x1fac8, 0x159f0, 0x1acfc, 0x1fac4,
+ 0x158f8, 0x1ac7e, 0x1fac2, 0x1587c, 0x1f5d0, 0x1faec,
+ 0x15df8, 0x1f5c8, 0x1fae6, 0x15cfc, 0x1f5c4, 0x15c7e,
+ 0x1f5c2, 0x1ebd0, 0x1f5ec, 0x1ebc8, 0x1f5e6, 0x1ebc4,
+ 0x1ebc2, 0x1d7d0, 0x1ebec, 0x1d7c8, 0x1ebe6, 0x1d7c4,
+ 0x1d7c2, 0x1afd0, 0x1d7ec, 0x1afc8, 0x1d7e6, 0x1afc4,
+ 0x14bc0, 0x1a5f0, 0x1d2fc, 0x149e0, 0x1a4f8, 0x1d27e,
+ 0x148f0, 0x1a47c, 0x14878, 0x1a43e, 0x1483c, 0x1fa68,
+ 0x14df0, 0x1a6fc, 0x1fa64, 0x14cf8, 0x1a67e, 0x1fa62,
+ 0x14c7c, 0x14c3e, 0x1f4e8, 0x1fa76, 0x14efc, 0x1f4e4,
+ 0x14e7e, 0x1f4e2, 0x1e9e8, 0x1f4f6, 0x1e9e4, 0x1e9e2,
+ 0x1d3e8, 0x1e9f6, 0x1d3e4, 0x1d3e2, 0x1a7e8, 0x1d3f6,
+ 0x1a7e4, 0x1a7e2, 0x145e0, 0x1a2f8, 0x1d17e, 0x144f0,
+ 0x1a27c, 0x14478, 0x1a23e, 0x1443c, 0x1441e, 0x1fa34,
+ 0x146f8, 0x1a37e, 0x1fa32, 0x1467c, 0x1463e, 0x1f474,
+ 0x1477e, 0x1f472, 0x1e8f4, 0x1e8f2, 0x1d1f4, 0x1d1f2,
+ 0x1a3f4, 0x1a3f2, 0x142f0, 0x1a17c, 0x14278, 0x1a13e,
+ 0x1423c, 0x1421e, 0x1fa1a, 0x1437c, 0x1433e, 0x1f43a,
+ 0x1e87a, 0x1d0fa, 0x14178, 0x1a0be, 0x1413c, 0x1411e,
+ 0x141be, 0x140bc, 0x1409e, 0x12bc0, 0x195f0, 0x1cafc,
+ 0x129e0, 0x194f8, 0x1ca7e, 0x128f0, 0x1947c, 0x12878,
+ 0x1943e, 0x1283c, 0x1f968, 0x12df0, 0x196fc, 0x1f964,
+ 0x12cf8, 0x1967e, 0x1f962, 0x12c7c, 0x12c3e, 0x1f2e8,
+ 0x1f976, 0x12efc, 0x1f2e4, 0x12e7e, 0x1f2e2, 0x1e5e8,
+ 0x1f2f6, 0x1e5e4, 0x1e5e2, 0x1cbe8, 0x1e5f6, 0x1cbe4,
+ 0x1cbe2, 0x197e8, 0x1cbf6, 0x197e4, 0x197e2, 0x1b5e0,
+ 0x1daf8, 0x1ed7e, 0x169c0, 0x1b4f0, 0x1da7c, 0x168e0,
+ 0x1b478, 0x1da3e, 0x16870, 0x1b43c, 0x16838, 0x1b41e,
+ 0x1681c, 0x125e0, 0x192f8, 0x1c97e, 0x16de0, 0x124f0,
+ 0x1927c, 0x16cf0, 0x1b67c, 0x1923e, 0x16c78, 0x1243c,
+ 0x16c3c, 0x1241e, 0x16c1e, 0x1f934, 0x126f8, 0x1937e,
+ 0x1fb74, 0x1f932, 0x16ef8, 0x1267c, 0x1fb72, 0x16e7c,
+ 0x1263e, 0x16e3e, 0x1f274, 0x1277e, 0x1f6f4, 0x1f272,
+ 0x16f7e, 0x1f6f2, 0x1e4f4, 0x1edf4, 0x1e4f2, 0x1edf2,
+ 0x1c9f4, 0x1dbf4, 0x1c9f2, 0x1dbf2, 0x193f4, 0x193f2,
+ 0x165c0, 0x1b2f0, 0x1d97c, 0x164e0, 0x1b278, 0x1d93e,
+ 0x16470, 0x1b23c, 0x16438, 0x1b21e, 0x1641c, 0x1640e,
+ 0x122f0, 0x1917c, 0x166f0, 0x12278, 0x1913e, 0x16678,
+ 0x1b33e, 0x1663c, 0x1221e, 0x1661e, 0x1f91a, 0x1237c,
+ 0x1fb3a, 0x1677c, 0x1233e, 0x1673e, 0x1f23a, 0x1f67a,
+ 0x1e47a, 0x1ecfa, 0x1c8fa, 0x1d9fa, 0x191fa, 0x162e0,
+ 0x1b178, 0x1d8be, 0x16270, 0x1b13c, 0x16238, 0x1b11e,
+ 0x1621c, 0x1620e, 0x12178, 0x190be, 0x16378, 0x1213c,
+ 0x1633c, 0x1211e, 0x1631e, 0x121be, 0x163be, 0x16170,
+ 0x1b0bc, 0x16138, 0x1b09e, 0x1611c, 0x1610e, 0x120bc,
+ 0x161bc, 0x1209e, 0x1619e, 0x160b8, 0x1b05e, 0x1609c,
+ 0x1608e, 0x1205e, 0x160de, 0x1605c, 0x1604e, 0x115e0,
+ 0x18af8, 0x1c57e, 0x114f0, 0x18a7c, 0x11478, 0x18a3e,
+ 0x1143c, 0x1141e, 0x1f8b4, 0x116f8, 0x18b7e, 0x1f8b2,
+ 0x1167c, 0x1163e, 0x1f174, 0x1177e, 0x1f172, 0x1e2f4,
+ 0x1e2f2, 0x1c5f4, 0x1c5f2, 0x18bf4, 0x18bf2, 0x135c0,
+ 0x19af0, 0x1cd7c, 0x134e0, 0x19a78, 0x1cd3e, 0x13470,
+ 0x19a3c, 0x13438, 0x19a1e, 0x1341c, 0x1340e, 0x112f0,
+ 0x1897c, 0x136f0, 0x11278, 0x1893e, 0x13678, 0x19b3e,
+ 0x1363c, 0x1121e, 0x1361e, 0x1f89a, 0x1137c, 0x1f9ba,
+ 0x1377c, 0x1133e, 0x1373e, 0x1f13a, 0x1f37a, 0x1e27a,
+ 0x1e6fa, 0x1c4fa, 0x1cdfa, 0x189fa, 0x1bae0, 0x1dd78,
+ 0x1eebe, 0x174c0, 0x1ba70, 0x1dd3c, 0x17460, 0x1ba38,
+ 0x1dd1e, 0x17430, 0x1ba1c, 0x17418, 0x1ba0e, 0x1740c,
+ 0x132e0, 0x19978, 0x1ccbe, 0x176e0, 0x13270, 0x1993c,
+ 0x17670, 0x1bb3c, 0x1991e, 0x17638, 0x1321c, 0x1761c,
+ 0x1320e, 0x1760e, 0x11178, 0x188be, 0x13378, 0x1113c,
+ 0x17778, 0x1333c, 0x1111e, 0x1773c, 0x1331e, 0x1771e,
+ 0x111be, 0x133be, 0x177be, 0x172c0, 0x1b970, 0x1dcbc,
+ 0x17260, 0x1b938, 0x1dc9e, 0x17230, 0x1b91c, 0x17218,
+ 0x1b90e, 0x1720c, 0x17206, 0x13170, 0x198bc, 0x17370,
+ 0x13138, 0x1989e, 0x17338, 0x1b99e, 0x1731c, 0x1310e,
+ 0x1730e, 0x110bc, 0x131bc, 0x1109e, 0x173bc, 0x1319e,
+ 0x1739e, 0x17160, 0x1b8b8, 0x1dc5e, 0x17130, 0x1b89c,
+ 0x17118, 0x1b88e, 0x1710c, 0x17106, 0x130b8, 0x1985e,
+ 0x171b8, 0x1309c, 0x1719c, 0x1308e, 0x1718e, 0x1105e,
+ 0x130de, 0x171de, 0x170b0, 0x1b85c, 0x17098, 0x1b84e,
+ 0x1708c, 0x17086, 0x1305c, 0x170dc, 0x1304e, 0x170ce,
+ 0x17058, 0x1b82e, 0x1704c, 0x17046, 0x1302e, 0x1706e,
+ 0x1702c, 0x17026, 0x10af0, 0x1857c, 0x10a78, 0x1853e,
+ 0x10a3c, 0x10a1e, 0x10b7c, 0x10b3e, 0x1f0ba, 0x1e17a,
+ 0x1c2fa, 0x185fa, 0x11ae0, 0x18d78, 0x1c6be, 0x11a70,
+ 0x18d3c, 0x11a38, 0x18d1e, 0x11a1c, 0x11a0e, 0x10978,
+ 0x184be, 0x11b78, 0x1093c, 0x11b3c, 0x1091e, 0x11b1e,
+ 0x109be, 0x11bbe, 0x13ac0, 0x19d70, 0x1cebc, 0x13a60,
+ 0x19d38, 0x1ce9e, 0x13a30, 0x19d1c, 0x13a18, 0x19d0e,
+ 0x13a0c, 0x13a06, 0x11970, 0x18cbc, 0x13b70, 0x11938,
+ 0x18c9e, 0x13b38, 0x1191c, 0x13b1c, 0x1190e, 0x13b0e,
+ 0x108bc, 0x119bc, 0x1089e, 0x13bbc, 0x1199e, 0x13b9e,
+ 0x1bd60, 0x1deb8, 0x1ef5e, 0x17a40, 0x1bd30, 0x1de9c,
+ 0x17a20, 0x1bd18, 0x1de8e, 0x17a10, 0x1bd0c, 0x17a08,
+ 0x1bd06, 0x17a04, 0x13960, 0x19cb8, 0x1ce5e, 0x17b60,
+ 0x13930, 0x19c9c, 0x17b30, 0x1bd9c, 0x19c8e, 0x17b18,
+ 0x1390c, 0x17b0c, 0x13906, 0x17b06, 0x118b8, 0x18c5e,
+ 0x139b8, 0x1189c, 0x17bb8, 0x1399c, 0x1188e, 0x17b9c,
+ 0x1398e, 0x17b8e, 0x1085e, 0x118de, 0x139de, 0x17bde,
+ 0x17940, 0x1bcb0, 0x1de5c, 0x17920, 0x1bc98, 0x1de4e,
+ 0x17910, 0x1bc8c, 0x17908, 0x1bc86, 0x17904, 0x17902,
+ 0x138b0, 0x19c5c, 0x179b0, 0x13898, 0x19c4e, 0x17998,
+ 0x1bcce, 0x1798c, 0x13886, 0x17986, 0x1185c, 0x138dc,
+ 0x1184e, 0x179dc, 0x138ce, 0x179ce, 0x178a0, 0x1bc58,
+ 0x1de2e, 0x17890, 0x1bc4c, 0x17888, 0x1bc46, 0x17884,
+ 0x17882, 0x13858, 0x19c2e, 0x178d8, 0x1384c, 0x178cc,
+ 0x13846, 0x178c6, 0x1182e, 0x1386e, 0x178ee, 0x17850,
+ 0x1bc2c, 0x17848, 0x1bc26, 0x17844, 0x17842, 0x1382c,
+ 0x1786c, 0x13826, 0x17866, 0x17828, 0x1bc16, 0x17824,
+ 0x17822, 0x13816, 0x17836, 0x10578, 0x182be, 0x1053c,
+ 0x1051e, 0x105be, 0x10d70, 0x186bc, 0x10d38, 0x1869e,
+ 0x10d1c, 0x10d0e, 0x104bc, 0x10dbc, 0x1049e, 0x10d9e,
+ 0x11d60, 0x18eb8, 0x1c75e, 0x11d30, 0x18e9c, 0x11d18,
+ 0x18e8e, 0x11d0c, 0x11d06, 0x10cb8, 0x1865e, 0x11db8,
+ 0x10c9c, 0x11d9c, 0x10c8e, 0x11d8e, 0x1045e, 0x10cde,
+ 0x11dde, 0x13d40, 0x19eb0, 0x1cf5c, 0x13d20, 0x19e98,
+ 0x1cf4e, 0x13d10, 0x19e8c, 0x13d08, 0x19e86, 0x13d04,
+ 0x13d02, 0x11cb0, 0x18e5c, 0x13db0, 0x11c98, 0x18e4e,
+ 0x13d98, 0x19ece, 0x13d8c, 0x11c86, 0x13d86, 0x10c5c,
+ 0x11cdc, 0x10c4e, 0x13ddc, 0x11cce, 0x13dce, 0x1bea0,
+ 0x1df58, 0x1efae, 0x1be90, 0x1df4c, 0x1be88, 0x1df46,
+ 0x1be84, 0x1be82, 0x13ca0, 0x19e58, 0x1cf2e, 0x17da0,
+ 0x13c90, 0x19e4c, 0x17d90, 0x1becc, 0x19e46, 0x17d88,
+ 0x13c84, 0x17d84, 0x13c82, 0x17d82, 0x11c58, 0x18e2e,
+ 0x13cd8, 0x11c4c, 0x17dd8, 0x13ccc, 0x11c46, 0x17dcc,
+ 0x13cc6, 0x17dc6, 0x10c2e, 0x11c6e, 0x13cee, 0x17dee,
+ 0x1be50, 0x1df2c, 0x1be48, 0x1df26, 0x1be44, 0x1be42,
+ 0x13c50, 0x19e2c, 0x17cd0, 0x13c48, 0x19e26, 0x17cc8,
+ 0x1be66, 0x17cc4, 0x13c42, 0x17cc2, 0x11c2c, 0x13c6c,
+ 0x11c26, 0x17cec, 0x13c66, 0x17ce6, 0x1be28, 0x1df16,
+ 0x1be24, 0x1be22, 0x13c28, 0x19e16, 0x17c68, 0x13c24,
+ 0x17c64, 0x13c22, 0x17c62, 0x11c16, 0x13c36, 0x17c76,
+ 0x1be14, 0x1be12, 0x13c14, 0x17c34, 0x13c12, 0x17c32,
+ 0x102bc, 0x1029e, 0x106b8, 0x1835e, 0x1069c, 0x1068e,
+ 0x1025e, 0x106de, 0x10eb0, 0x1875c, 0x10e98, 0x1874e,
+ 0x10e8c, 0x10e86, 0x1065c, 0x10edc, 0x1064e, 0x10ece,
+ 0x11ea0, 0x18f58, 0x1c7ae, 0x11e90, 0x18f4c, 0x11e88,
+ 0x18f46, 0x11e84, 0x11e82, 0x10e58, 0x1872e, 0x11ed8,
+ 0x18f6e, 0x11ecc, 0x10e46, 0x11ec6, 0x1062e, 0x10e6e,
+ 0x11eee, 0x19f50, 0x1cfac, 0x19f48, 0x1cfa6, 0x19f44,
+ 0x19f42, 0x11e50, 0x18f2c, 0x13ed0, 0x19f6c, 0x18f26,
+ 0x13ec8, 0x11e44, 0x13ec4, 0x11e42, 0x13ec2, 0x10e2c,
+ 0x11e6c, 0x10e26, 0x13eec, 0x11e66, 0x13ee6, 0x1dfa8,
+ 0x1efd6, 0x1dfa4, 0x1dfa2, 0x19f28, 0x1cf96, 0x1bf68,
+ 0x19f24, 0x1bf64, 0x19f22, 0x1bf62, 0x11e28, 0x18f16,
+ 0x13e68, 0x11e24, 0x17ee8, 0x13e64, 0x11e22, 0x17ee4,
+ 0x13e62, 0x17ee2, 0x10e16, 0x11e36, 0x13e76, 0x17ef6,
+ 0x1df94, 0x1df92, 0x19f14, 0x1bf34, 0x19f12, 0x1bf32,
+ 0x11e14, 0x13e34, 0x11e12, 0x17e74, 0x13e32, 0x17e72,
+ 0x1df8a, 0x19f0a, 0x1bf1a, 0x11e0a, 0x13e1a, 0x17e3a,
+ 0x1035c, 0x1034e, 0x10758, 0x183ae, 0x1074c, 0x10746,
+ 0x1032e, 0x1076e, 0x10f50, 0x187ac, 0x10f48, 0x187a6,
+ 0x10f44, 0x10f42, 0x1072c, 0x10f6c, 0x10726, 0x10f66,
+ 0x18fa8, 0x1c7d6, 0x18fa4, 0x18fa2, 0x10f28, 0x18796,
+ 0x11f68, 0x18fb6, 0x11f64, 0x10f22, 0x11f62, 0x10716,
+ 0x10f36, 0x11f76, 0x1cfd4, 0x1cfd2, 0x18f94, 0x19fb4,
+ 0x18f92, 0x19fb2, 0x10f14, 0x11f34, 0x10f12, 0x13f74,
+ 0x11f32, 0x13f72, 0x1cfca, 0x18f8a, 0x19f9a, 0x10f0a,
+ 0x11f1a, 0x13f3a, 0x103ac, 0x103a6, 0x107a8, 0x183d6,
+ 0x107a4, 0x107a2, 0x10396, 0x107b6, 0x187d4, 0x187d2,
+ 0x10794, 0x10fb4, 0x10792, 0x10fb2, 0x1c7ea}};
+
+ private static final float PREFERRED_RATIO = 3.0f;
+ private static final float DEFAULT_MODULE_WIDTH = 0.357f; //1px in mm
+ private static final float HEIGHT = 2.0f; //mm
+
+ private BarcodeMatrix barcodeMatrix;
+ private boolean compact;
+ private Compaction compaction;
+ private Charset encoding;
+ private int minCols;
+ private int maxCols;
+ private int maxRows;
+ private int minRows;
+
+ public PDF417() {
+ this(false);
+ }
+
+ public PDF417(boolean compact) {
+ this.compact = compact;
+ compaction = Compaction.AUTO;
+ encoding = null; // Use default
+ minCols = 2;
+ maxCols = 30;
+ maxRows = 30;
+ minRows = 2;
+ }
+
+ public BarcodeMatrix getBarcodeMatrix() {
+ return barcodeMatrix;
+ }
+
+ /**
+ * Calculates the necessary number of rows as described in annex Q of ISO/IEC 15438:2001(E).
+ *
+ * @param m the number of source codewords prior to the additional of the Symbol Length
+ * Descriptor and any pad codewords
+ * @param k the number of error correction codewords
+ * @param c the number of columns in the symbol in the data region (excluding start, stop and
+ * row indicator codewords)
+ * @return the number of rows in the symbol (r)
+ */
+ private static int calculateNumberOfRows(int m, int k, int c) {
+ int r = ((m + 1 + k) / c) + 1;
+ if (c * r >= (m + 1 + k + c)) {
+ r--;
+ }
+ return r;
+ }
+
+ /**
+ * Calculates the number of pad codewords as described in 4.9.2 of ISO/IEC 15438:2001(E).
+ *
+ * @param m the number of source codewords prior to the additional of the Symbol Length
+ * Descriptor and any pad codewords
+ * @param k the number of error correction codewords
+ * @param c the number of columns in the symbol in the data region (excluding start, stop and
+ * row indicator codewords)
+ * @param r the number of rows in the symbol
+ * @return the number of pad codewords
+ */
+ private static int getNumberOfPadCodewords(int m, int k, int c, int r) {
+ int n = c * r - k;
+ return n > m + 1 ? n - m - 1 : 0;
+ }
+
+ private static void encodeChar(int pattern, int len, BarcodeRow logic) {
+ int map = 1 << len - 1;
+ boolean last = (pattern & map) != 0; //Initialize to inverse of first bit
+ int width = 0;
+ for (int i = 0; i < len; i++) {
+ boolean black = (pattern & map) != 0;
+ if (last == black) {
+ width++;
+ } else {
+ logic.addBar(last, width);
+
+ last = black;
+ width = 1;
+ }
+ map >>= 1;
+ }
+ logic.addBar(last, width);
+ }
+
+ private void encodeLowLevel(CharSequence fullCodewords,
+ int c,
+ int r,
+ int errorCorrectionLevel,
+ BarcodeMatrix logic) {
+
+ int idx = 0;
+ for (int y = 0; y < r; y++) {
+ int cluster = y % 3;
+ logic.startRow();
+ encodeChar(START_PATTERN, 17, logic.getCurrentRow());
+
+ int left;
+ int right;
+ if (cluster == 0) {
+ left = (30 * (y / 3)) + ((r - 1) / 3);
+ right = (30 * (y / 3)) + (c - 1);
+ } else if (cluster == 1) {
+ left = (30 * (y / 3)) + (errorCorrectionLevel * 3) + ((r - 1) % 3);
+ right = (30 * (y / 3)) + ((r - 1) / 3);
+ } else {
+ left = (30 * (y / 3)) + (c - 1);
+ right = (30 * (y / 3)) + (errorCorrectionLevel * 3) + ((r - 1) % 3);
+ }
+
+ int pattern = CODEWORD_TABLE[cluster][left];
+ encodeChar(pattern, 17, logic.getCurrentRow());
+
+ for (int x = 0; x < c; x++) {
+ pattern = CODEWORD_TABLE[cluster][fullCodewords.charAt(idx)];
+ encodeChar(pattern, 17, logic.getCurrentRow());
+ idx++;
+ }
+
+ if (compact) {
+ encodeChar(STOP_PATTERN, 1, logic.getCurrentRow()); // encodes stop line for compact pdf417
+ } else {
+ pattern = CODEWORD_TABLE[cluster][right];
+ encodeChar(pattern, 17, logic.getCurrentRow());
+
+ encodeChar(STOP_PATTERN, 18, logic.getCurrentRow());
+ }
+ }
+ }
+
+ /**
+ * @param msg message to encode
+ * @param errorCorrectionLevel PDF417 error correction level to use
+ * @throws WriterException if the contents cannot be encoded in this format
+ */
+ public void generateBarcodeLogic(String msg, int errorCorrectionLevel) throws WriterException {
+
+ //1. step: High-level encoding
+ int errorCorrectionCodeWords = PDF417ErrorCorrection.getErrorCorrectionCodewordCount(errorCorrectionLevel);
+ String highLevel = PDF417HighLevelEncoder.encodeHighLevel(msg, compaction, encoding);
+ int sourceCodeWords = highLevel.length();
+
+ int[] dimension = determineDimensions(sourceCodeWords, errorCorrectionCodeWords);
+
+ int cols = dimension[0];
+ int rows = dimension[1];
+
+ int pad = getNumberOfPadCodewords(sourceCodeWords, errorCorrectionCodeWords, cols, rows);
+
+ //2. step: construct data codewords
+ if (sourceCodeWords + errorCorrectionCodeWords + 1 > 929) { // +1 for symbol length CW
+ throw new WriterException(
+ "Encoded message contains to many code words, message to big (" + msg.length() + " bytes)");
+ }
+ int n = sourceCodeWords + pad + 1;
+ StringBuilder sb = new StringBuilder(n);
+ sb.append((char) n);
+ sb.append(highLevel);
+ for (int i = 0; i < pad; i++) {
+ sb.append((char) 900); //PAD characters
+ }
+ String dataCodewords = sb.toString();
+
+ //3. step: Error correction
+ String ec = PDF417ErrorCorrection.generateErrorCorrection(dataCodewords, errorCorrectionLevel);
+ String fullCodewords = dataCodewords + ec;
+
+ //4. step: low-level encoding
+ barcodeMatrix = new BarcodeMatrix(rows, cols);
+ encodeLowLevel(fullCodewords, cols, rows, errorCorrectionLevel, barcodeMatrix);
+ }
+
+ /**
+ * Determine optimal nr of columns and rows for the specified number of
+ * codewords.
+ *
+ * @param sourceCodeWords number of code words
+ * @param errorCorrectionCodeWords number of error correction code words
+ * @return dimension object containing cols as width and rows as height
+ */
+ private int[] determineDimensions(int sourceCodeWords, int errorCorrectionCodeWords) throws WriterException {
+ float ratio = 0.0f;
+ int[] dimension = null;
+
+ for (int cols = minCols; cols <= maxCols; cols++) {
+
+ int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, cols);
+
+ if (rows < minRows) {
+ break;
+ }
+
+ if (rows > maxRows) {
+ continue;
+ }
+
+ float newRatio = ((17 * cols + 69) * DEFAULT_MODULE_WIDTH) / (rows * HEIGHT);
+
+ // ignore if previous ratio is closer to preferred ratio
+ if (dimension != null && Math.abs(newRatio - PREFERRED_RATIO) > Math.abs(ratio - PREFERRED_RATIO)) {
+ continue;
+ }
+
+ ratio = newRatio;
+ dimension = new int[] {cols, rows};
+ }
+
+ // Handle case when min values were larger than necessary
+ if (dimension == null) {
+ int rows = calculateNumberOfRows(sourceCodeWords, errorCorrectionCodeWords, minCols);
+ if (rows < minRows) {
+ dimension = new int[]{minCols, minRows};
+ }
+ }
+
+ if (dimension == null) {
+ throw new WriterException("Unable to fit message in columns");
+ }
+
+ return dimension;
+ }
+
+ /**
+ * Sets max/min row/col values
+ *
+ * @param maxCols maximum allowed columns
+ * @param minCols minimum allowed columns
+ * @param maxRows maximum allowed rows
+ * @param minRows minimum allowed rows
+ */
+ public void setDimensions(int maxCols, int minCols, int maxRows, int minRows) {
+ this.maxCols = maxCols;
+ this.minCols = minCols;
+ this.maxRows = maxRows;
+ this.minRows = minRows;
+ }
+
+ /**
+ * @param compaction compaction mode to use
+ */
+ public void setCompaction(Compaction compaction) {
+ this.compaction = compaction;
+ }
+
+ /**
+ * @param compact if true, enables compaction
+ */
+ public void setCompact(boolean compact) {
+ this.compact = compact;
+ }
+
+ /**
+ * @param encoding sets character encoding to use
+ */
+ public void setEncoding(Charset encoding) {
+ this.encoding = encoding;
+ }
+
+}
+
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417ErrorCorrection.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417ErrorCorrection.java
new file mode 100644
index 000000000..65281727b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417ErrorCorrection.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2006 Jeremias Maerki in part, and ZXing Authors in part
+ *
+ * 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.
+ */
+
+/*
+ * This file has been modified from its original form in Barcode4J.
+ */
+
+package com.google.zxing.pdf417.encoder;
+
+import com.google.zxing.WriterException;
+
+/**
+ * PDF417 error correction code following the algorithm described in ISO/IEC 15438:2001(E) in
+ * chapter 4.10.
+ */
+final class PDF417ErrorCorrection {
+
+ /**
+ * Tables of coefficients for calculating error correction words
+ * (see annex F, ISO/IEC 15438:2001(E))
+ */
+ private static final int[][] EC_COEFFICIENTS = {
+ {27, 917},
+ {522, 568, 723, 809},
+ {237, 308, 436, 284, 646, 653, 428, 379},
+ {274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295,
+ 42, 176, 65},
+ {361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687,
+ 284, 193, 517, 273, 494, 263, 147, 593, 800, 571, 320, 803,
+ 133, 231, 390, 685, 330, 63, 410},
+ {539, 422, 6, 93, 862, 771, 453, 106, 610, 287, 107, 505, 733,
+ 877, 381, 612, 723, 476, 462, 172, 430, 609, 858, 822, 543,
+ 376, 511, 400, 672, 762, 283, 184, 440, 35, 519, 31, 460,
+ 594, 225, 535, 517, 352, 605, 158, 651, 201, 488, 502, 648,
+ 733, 717, 83, 404, 97, 280, 771, 840, 629, 4, 381, 843,
+ 623, 264, 543},
+ {521, 310, 864, 547, 858, 580, 296, 379, 53, 779, 897, 444, 400,
+ 925, 749, 415, 822, 93, 217, 208, 928, 244, 583, 620, 246,
+ 148, 447, 631, 292, 908, 490, 704, 516, 258, 457, 907, 594,
+ 723, 674, 292, 272, 96, 684, 432, 686, 606, 860, 569, 193,
+ 219, 129, 186, 236, 287, 192, 775, 278, 173, 40, 379, 712,
+ 463, 646, 776, 171, 491, 297, 763, 156, 732, 95, 270, 447,
+ 90, 507, 48, 228, 821, 808, 898, 784, 663, 627, 378, 382,
+ 262, 380, 602, 754, 336, 89, 614, 87, 432, 670, 616, 157,
+ 374, 242, 726, 600, 269, 375, 898, 845, 454, 354, 130, 814,
+ 587, 804, 34, 211, 330, 539, 297, 827, 865, 37, 517, 834,
+ 315, 550, 86, 801, 4, 108, 539},
+ {524, 894, 75, 766, 882, 857, 74, 204, 82, 586, 708, 250, 905,
+ 786, 138, 720, 858, 194, 311, 913, 275, 190, 375, 850, 438,
+ 733, 194, 280, 201, 280, 828, 757, 710, 814, 919, 89, 68,
+ 569, 11, 204, 796, 605, 540, 913, 801, 700, 799, 137, 439,
+ 418, 592, 668, 353, 859, 370, 694, 325, 240, 216, 257, 284,
+ 549, 209, 884, 315, 70, 329, 793, 490, 274, 877, 162, 749,
+ 812, 684, 461, 334, 376, 849, 521, 307, 291, 803, 712, 19,
+ 358, 399, 908, 103, 511, 51, 8, 517, 225, 289, 470, 637,
+ 731, 66, 255, 917, 269, 463, 830, 730, 433, 848, 585, 136,
+ 538, 906, 90, 2, 290, 743, 199, 655, 903, 329, 49, 802,
+ 580, 355, 588, 188, 462, 10, 134, 628, 320, 479, 130, 739,
+ 71, 263, 318, 374, 601, 192, 605, 142, 673, 687, 234, 722,
+ 384, 177, 752, 607, 640, 455, 193, 689, 707, 805, 641, 48,
+ 60, 732, 621, 895, 544, 261, 852, 655, 309, 697, 755, 756,
+ 60, 231, 773, 434, 421, 726, 528, 503, 118, 49, 795, 32,
+ 144, 500, 238, 836, 394, 280, 566, 319, 9, 647, 550, 73,
+ 914, 342, 126, 32, 681, 331, 792, 620, 60, 609, 441, 180,
+ 791, 893, 754, 605, 383, 228, 749, 760, 213, 54, 297, 134,
+ 54, 834, 299, 922, 191, 910, 532, 609, 829, 189, 20, 167,
+ 29, 872, 449, 83, 402, 41, 656, 505, 579, 481, 173, 404,
+ 251, 688, 95, 497, 555, 642, 543, 307, 159, 924, 558, 648,
+ 55, 497, 10},
+ {352, 77, 373, 504, 35, 599, 428, 207, 409, 574, 118, 498, 285,
+ 380, 350, 492, 197, 265, 920, 155, 914, 299, 229, 643, 294,
+ 871, 306, 88, 87, 193, 352, 781, 846, 75, 327, 520, 435,
+ 543, 203, 666, 249, 346, 781, 621, 640, 268, 794, 534, 539,
+ 781, 408, 390, 644, 102, 476, 499, 290, 632, 545, 37, 858,
+ 916, 552, 41, 542, 289, 122, 272, 383, 800, 485, 98, 752,
+ 472, 761, 107, 784, 860, 658, 741, 290, 204, 681, 407, 855,
+ 85, 99, 62, 482, 180, 20, 297, 451, 593, 913, 142, 808,
+ 684, 287, 536, 561, 76, 653, 899, 729, 567, 744, 390, 513,
+ 192, 516, 258, 240, 518, 794, 395, 768, 848, 51, 610, 384,
+ 168, 190, 826, 328, 596, 786, 303, 570, 381, 415, 641, 156,
+ 237, 151, 429, 531, 207, 676, 710, 89, 168, 304, 402, 40,
+ 708, 575, 162, 864, 229, 65, 861, 841, 512, 164, 477, 221,
+ 92, 358, 785, 288, 357, 850, 836, 827, 736, 707, 94, 8,
+ 494, 114, 521, 2, 499, 851, 543, 152, 729, 771, 95, 248,
+ 361, 578, 323, 856, 797, 289, 51, 684, 466, 533, 820, 669,
+ 45, 902, 452, 167, 342, 244, 173, 35, 463, 651, 51, 699,
+ 591, 452, 578, 37, 124, 298, 332, 552, 43, 427, 119, 662,
+ 777, 475, 850, 764, 364, 578, 911, 283, 711, 472, 420, 245,
+ 288, 594, 394, 511, 327, 589, 777, 699, 688, 43, 408, 842,
+ 383, 721, 521, 560, 644, 714, 559, 62, 145, 873, 663, 713,
+ 159, 672, 729, 624, 59, 193, 417, 158, 209, 563, 564, 343,
+ 693, 109, 608, 563, 365, 181, 772, 677, 310, 248, 353, 708,
+ 410, 579, 870, 617, 841, 632, 860, 289, 536, 35, 777, 618,
+ 586, 424, 833, 77, 597, 346, 269, 757, 632, 695, 751, 331,
+ 247, 184, 45, 787, 680, 18, 66, 407, 369, 54, 492, 228,
+ 613, 830, 922, 437, 519, 644, 905, 789, 420, 305, 441, 207,
+ 300, 892, 827, 141, 537, 381, 662, 513, 56, 252, 341, 242,
+ 797, 838, 837, 720, 224, 307, 631, 61, 87, 560, 310, 756,
+ 665, 397, 808, 851, 309, 473, 795, 378, 31, 647, 915, 459,
+ 806, 590, 731, 425, 216, 548, 249, 321, 881, 699, 535, 673,
+ 782, 210, 815, 905, 303, 843, 922, 281, 73, 469, 791, 660,
+ 162, 498, 308, 155, 422, 907, 817, 187, 62, 16, 425, 535,
+ 336, 286, 437, 375, 273, 610, 296, 183, 923, 116, 667, 751,
+ 353, 62, 366, 691, 379, 687, 842, 37, 357, 720, 742, 330,
+ 5, 39, 923, 311, 424, 242, 749, 321, 54, 669, 316, 342,
+ 299, 534, 105, 667, 488, 640, 672, 576, 540, 316, 486, 721,
+ 610, 46, 656, 447, 171, 616, 464, 190, 531, 297, 321, 762,
+ 752, 533, 175, 134, 14, 381, 433, 717, 45, 111, 20, 596,
+ 284, 736, 138, 646, 411, 877, 669, 141, 919, 45, 780, 407,
+ 164, 332, 899, 165, 726, 600, 325, 498, 655, 357, 752, 768,
+ 223, 849, 647, 63, 310, 863, 251, 366, 304, 282, 738, 675,
+ 410, 389, 244, 31, 121, 303, 263}};
+
+ private PDF417ErrorCorrection() {
+ }
+
+ /**
+ * Determines the number of error correction codewords for a specified error correction
+ * level.
+ *
+ * @param errorCorrectionLevel the error correction level (0-8)
+ * @return the number of codewords generated for error correction
+ */
+ static int getErrorCorrectionCodewordCount(int errorCorrectionLevel) {
+ if (errorCorrectionLevel < 0 || errorCorrectionLevel > 8) {
+ throw new IllegalArgumentException("Error correction level must be between 0 and 8!");
+ }
+ return 1 << (errorCorrectionLevel + 1);
+ }
+
+ /**
+ * Returns the recommended minimum error correction level as described in annex E of
+ * ISO/IEC 15438:2001(E).
+ *
+ * @param n the number of data codewords
+ * @return the recommended minimum error correction level
+ */
+ static int getRecommendedMinimumErrorCorrectionLevel(int n) throws WriterException {
+ if (n <= 0) {
+ throw new IllegalArgumentException("n must be > 0");
+ }
+ if (n <= 40) {
+ return 2;
+ }
+ if (n <= 160) {
+ return 3;
+ }
+ if (n <= 320) {
+ return 4;
+ }
+ if (n <= 863) {
+ return 5;
+ }
+ throw new WriterException("No recommendation possible");
+ }
+
+ /**
+ * Generates the error correction codewords according to 4.10 in ISO/IEC 15438:2001(E).
+ *
+ * @param dataCodewords the data codewords
+ * @param errorCorrectionLevel the error correction level (0-8)
+ * @return the String representing the error correction codewords
+ */
+ static String generateErrorCorrection(CharSequence dataCodewords, int errorCorrectionLevel) {
+ int k = getErrorCorrectionCodewordCount(errorCorrectionLevel);
+ char[] e = new char[k];
+ int sld = dataCodewords.length();
+ for (int i = 0; i < sld; i++) {
+ int t1 = (dataCodewords.charAt(i) + e[e.length - 1]) % 929;
+ int t2;
+ int t3;
+ for (int j = k - 1; j >= 1; j--) {
+ t2 = (t1 * EC_COEFFICIENTS[errorCorrectionLevel][j]) % 929;
+ t3 = 929 - t2;
+ e[j] = (char) ((e[j - 1] + t3) % 929);
+ }
+ t2 = (t1 * EC_COEFFICIENTS[errorCorrectionLevel][0]) % 929;
+ t3 = 929 - t2;
+ e[0] = (char) (t3 % 929);
+ }
+ StringBuilder sb = new StringBuilder(k);
+ for (int j = k - 1; j >= 0; j--) {
+ if (e[j] != 0) {
+ e[j] = (char) (929 - e[j]);
+ }
+ sb.append(e[j]);
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java
new file mode 100644
index 000000000..8a5db252a
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/pdf417/encoder/PDF417HighLevelEncoder.java
@@ -0,0 +1,616 @@
+/*
+ * Copyright 2006 Jeremias Maerki in part, and ZXing Authors in part
+ *
+ * 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.
+ */
+
+/*
+ * This file has been modified from its original form in Barcode4J.
+ */
+
+package com.google.zxing.pdf417.encoder;
+
+import com.google.zxing.WriterException;
+import com.google.zxing.common.CharacterSetECI;
+
+import java.math.BigInteger;
+import java.nio.charset.Charset;
+import java.nio.charset.UnsupportedCharsetException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * PDF417 high-level encoder following the algorithm described in ISO/IEC 15438:2001(E) in
+ * annex P.
+ */
+final class PDF417HighLevelEncoder {
+
+ /**
+ * code for Text compaction
+ */
+ private static final int TEXT_COMPACTION = 0;
+
+ /**
+ * code for Byte compaction
+ */
+ private static final int BYTE_COMPACTION = 1;
+
+ /**
+ * code for Numeric compaction
+ */
+ private static final int NUMERIC_COMPACTION = 2;
+
+ /**
+ * Text compaction submode Alpha
+ */
+ private static final int SUBMODE_ALPHA = 0;
+
+ /**
+ * Text compaction submode Lower
+ */
+ private static final int SUBMODE_LOWER = 1;
+
+ /**
+ * Text compaction submode Mixed
+ */
+ private static final int SUBMODE_MIXED = 2;
+
+ /**
+ * Text compaction submode Punctuation
+ */
+ private static final int SUBMODE_PUNCTUATION = 3;
+
+ /**
+ * mode latch to Text Compaction mode
+ */
+ private static final int LATCH_TO_TEXT = 900;
+
+ /**
+ * mode latch to Byte Compaction mode (number of characters NOT a multiple of 6)
+ */
+ private static final int LATCH_TO_BYTE_PADDED = 901;
+
+ /**
+ * mode latch to Numeric Compaction mode
+ */
+ private static final int LATCH_TO_NUMERIC = 902;
+
+ /**
+ * mode shift to Byte Compaction mode
+ */
+ private static final int SHIFT_TO_BYTE = 913;
+
+ /**
+ * mode latch to Byte Compaction mode (number of characters a multiple of 6)
+ */
+ private static final int LATCH_TO_BYTE = 924;
+
+ /**
+ * identifier for a user defined Extended Channel Interpretation (ECI)
+ */
+ private static final int ECI_USER_DEFINED = 925;
+
+ /**
+ * identifier for a general purpose ECO format
+ */
+ private static final int ECI_GENERAL_PURPOSE = 926;
+
+ /**
+ * identifier for an ECI of a character set of code page
+ */
+ private static final int ECI_CHARSET = 927;
+
+ /**
+ * Raw code table for text compaction Mixed sub-mode
+ */
+ private static final byte[] TEXT_MIXED_RAW = {
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 38, 13, 9, 44, 58,
+ 35, 45, 46, 36, 47, 43, 37, 42, 61, 94, 0, 32, 0, 0, 0};
+
+ /**
+ * Raw code table for text compaction: Punctuation sub-mode
+ */
+ private static final byte[] TEXT_PUNCTUATION_RAW = {
+ 59, 60, 62, 64, 91, 92, 93, 95, 96, 126, 33, 13, 9, 44, 58,
+ 10, 45, 46, 36, 47, 34, 124, 42, 40, 41, 63, 123, 125, 39, 0};
+
+ private static final byte[] MIXED = new byte[128];
+ private static final byte[] PUNCTUATION = new byte[128];
+
+ private static final List DEFAULT_ENCODING_NAMES = Arrays.asList("Cp437", "IBM437");
+
+ private PDF417HighLevelEncoder() {
+ }
+
+ static {
+ //Construct inverse lookups
+ Arrays.fill(MIXED, (byte) -1);
+ for (byte i = 0; i < TEXT_MIXED_RAW.length; i++) {
+ byte b = TEXT_MIXED_RAW[i];
+ if (b > 0) {
+ MIXED[b] = i;
+ }
+ }
+ Arrays.fill(PUNCTUATION, (byte) -1);
+ for (byte i = 0; i < TEXT_PUNCTUATION_RAW.length; i++) {
+ byte b = TEXT_PUNCTUATION_RAW[i];
+ if (b > 0) {
+ PUNCTUATION[b] = i;
+ }
+ }
+ }
+
+ /**
+ * Performs high-level encoding of a PDF417 message using the algorithm described in annex P
+ * of ISO/IEC 15438:2001(E). If byte compaction has been selected, then only byte compaction
+ * is used.
+ *
+ * @param msg the message
+ * @param compaction compaction mode to use
+ * @param encoding character encoding used to encode in default or byte compaction
+ * or {@code null} for default / not applicable
+ * @return the encoded message (the char values range from 0 to 928)
+ */
+ static String encodeHighLevel(String msg, Compaction compaction, Charset encoding) throws WriterException {
+
+ //the codewords 0..928 are encoded as Unicode characters
+ StringBuilder sb = new StringBuilder(msg.length());
+
+ if (encoding != null || !DEFAULT_ENCODING_NAMES.contains(encoding.name())) {
+ CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding.name());
+ if (eci != null) {
+ encodingECI(eci.getValue(), sb);
+ }
+ }
+
+ int len = msg.length();
+ int p = 0;
+ int textSubMode = SUBMODE_ALPHA;
+
+ // User selected encoding mode
+ byte[] bytes = null; //Fill later and only if needed
+ if (compaction == Compaction.TEXT) {
+ encodeText(msg, p, len, sb, textSubMode);
+
+ } else if (compaction == Compaction.BYTE) {
+ bytes = toBytes(msg, encoding);
+ encodeBinary(bytes, p, bytes.length, BYTE_COMPACTION, sb);
+
+ } else if (compaction == Compaction.NUMERIC) {
+ sb.append((char) LATCH_TO_NUMERIC);
+ encodeNumeric(msg, p, len, sb);
+
+ } else {
+ int encodingMode = TEXT_COMPACTION; //Default mode, see 4.4.2.1
+ while (p < len) {
+ int n = determineConsecutiveDigitCount(msg, p);
+ if (n >= 13) {
+ sb.append((char) LATCH_TO_NUMERIC);
+ encodingMode = NUMERIC_COMPACTION;
+ textSubMode = SUBMODE_ALPHA; //Reset after latch
+ encodeNumeric(msg, p, n, sb);
+ p += n;
+ } else {
+ int t = determineConsecutiveTextCount(msg, p);
+ if (t >= 5 || n == len) {
+ if (encodingMode != TEXT_COMPACTION) {
+ sb.append((char) LATCH_TO_TEXT);
+ encodingMode = TEXT_COMPACTION;
+ textSubMode = SUBMODE_ALPHA; //start with submode alpha after latch
+ }
+ textSubMode = encodeText(msg, p, t, sb, textSubMode);
+ p += t;
+ } else {
+ if (bytes == null) {
+ bytes = toBytes(msg, encoding);
+ }
+ int b = determineConsecutiveBinaryCount(msg, bytes, p);
+ if (b == 0) {
+ b = 1;
+ }
+ if (b == 1 && encodingMode == TEXT_COMPACTION) {
+ //Switch for one byte (instead of latch)
+ encodeBinary(bytes, p, 1, TEXT_COMPACTION, sb);
+ } else {
+ //Mode latch performed by encodeBinary()
+ encodeBinary(bytes, p, b, encodingMode, sb);
+ encodingMode = BYTE_COMPACTION;
+ textSubMode = SUBMODE_ALPHA; //Reset after latch
+ }
+ p += b;
+ }
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ private static byte[] toBytes(String msg, Charset encoding) throws WriterException {
+ // Defer instantiating default Charset until needed, since it may be for an unsupported
+ // encoding. For example the default of Cp437 doesn't seem to exist on Android.
+ if (encoding == null) {
+ for (String encodingName : DEFAULT_ENCODING_NAMES) {
+ try {
+ encoding = Charset.forName(encodingName);
+ } catch (UnsupportedCharsetException uce) {
+ // continue
+ }
+ }
+ if (encoding == null) {
+ throw new WriterException("No support for any encoding: " + DEFAULT_ENCODING_NAMES);
+ }
+ }
+ return msg.getBytes(encoding);
+ }
+
+ /**
+ * Encode parts of the message using Text Compaction as described in ISO/IEC 15438:2001(E),
+ * chapter 4.4.2.
+ *
+ * @param msg the message
+ * @param startpos the start position within the message
+ * @param count the number of characters to encode
+ * @param sb receives the encoded codewords
+ * @param initialSubmode should normally be SUBMODE_ALPHA
+ * @return the text submode in which this method ends
+ */
+ private static int encodeText(CharSequence msg,
+ int startpos,
+ int count,
+ StringBuilder sb,
+ int initialSubmode) {
+ StringBuilder tmp = new StringBuilder(count);
+ int submode = initialSubmode;
+ int idx = 0;
+ while (true) {
+ char ch = msg.charAt(startpos + idx);
+ switch (submode) {
+ case SUBMODE_ALPHA:
+ if (isAlphaUpper(ch)) {
+ if (ch == ' ') {
+ tmp.append((char) 26); //space
+ } else {
+ tmp.append((char) (ch - 65));
+ }
+ } else {
+ if (isAlphaLower(ch)) {
+ submode = SUBMODE_LOWER;
+ tmp.append((char) 27); //ll
+ continue;
+ } else if (isMixed(ch)) {
+ submode = SUBMODE_MIXED;
+ tmp.append((char) 28); //ml
+ continue;
+ } else {
+ tmp.append((char) 29); //ps
+ tmp.append((char) PUNCTUATION[ch]);
+ break;
+ }
+ }
+ break;
+ case SUBMODE_LOWER:
+ if (isAlphaLower(ch)) {
+ if (ch == ' ') {
+ tmp.append((char) 26); //space
+ } else {
+ tmp.append((char) (ch - 97));
+ }
+ } else {
+ if (isAlphaUpper(ch)) {
+ tmp.append((char) 27); //as
+ tmp.append((char) (ch - 65));
+ //space cannot happen here, it is also in "Lower"
+ break;
+ } else if (isMixed(ch)) {
+ submode = SUBMODE_MIXED;
+ tmp.append((char) 28); //ml
+ continue;
+ } else {
+ tmp.append((char) 29); //ps
+ tmp.append((char) PUNCTUATION[ch]);
+ break;
+ }
+ }
+ break;
+ case SUBMODE_MIXED:
+ if (isMixed(ch)) {
+ tmp.append((char) MIXED[ch]);
+ } else {
+ if (isAlphaUpper(ch)) {
+ submode = SUBMODE_ALPHA;
+ tmp.append((char) 28); //al
+ continue;
+ } else if (isAlphaLower(ch)) {
+ submode = SUBMODE_LOWER;
+ tmp.append((char) 27); //ll
+ continue;
+ } else {
+ if (startpos + idx + 1 < count) {
+ char next = msg.charAt(startpos + idx + 1);
+ if (isPunctuation(next)) {
+ submode = SUBMODE_PUNCTUATION;
+ tmp.append((char) 25); //pl
+ continue;
+ }
+ }
+ tmp.append((char) 29); //ps
+ tmp.append((char) PUNCTUATION[ch]);
+ }
+ }
+ break;
+ default: //SUBMODE_PUNCTUATION
+ if (isPunctuation(ch)) {
+ tmp.append((char) PUNCTUATION[ch]);
+ } else {
+ submode = SUBMODE_ALPHA;
+ tmp.append((char) 29); //al
+ continue;
+ }
+ }
+ idx++;
+ if (idx >= count) {
+ break;
+ }
+ }
+ char h = 0;
+ int len = tmp.length();
+ for (int i = 0; i < len; i++) {
+ boolean odd = (i % 2) != 0;
+ if (odd) {
+ h = (char) ((h * 30) + tmp.charAt(i));
+ sb.append(h);
+ } else {
+ h = tmp.charAt(i);
+ }
+ }
+ if ((len % 2) != 0) {
+ sb.append((char) ((h * 30) + 29)); //ps
+ }
+ return submode;
+ }
+
+ /**
+ * Encode parts of the message using Byte Compaction as described in ISO/IEC 15438:2001(E),
+ * chapter 4.4.3. The Unicode characters will be converted to binary using the cp437
+ * codepage.
+ *
+ * @param bytes the message converted to a byte array
+ * @param startpos the start position within the message
+ * @param count the number of bytes to encode
+ * @param startmode the mode from which this method starts
+ * @param sb receives the encoded codewords
+ */
+ private static void encodeBinary(byte[] bytes,
+ int startpos,
+ int count,
+ int startmode,
+ StringBuilder sb) {
+ if (count == 1 && startmode == TEXT_COMPACTION) {
+ sb.append((char) SHIFT_TO_BYTE);
+ } else {
+ boolean sixpack = ((count % 6) == 0);
+ if (sixpack) {
+ sb.append((char)LATCH_TO_BYTE);
+ } else {
+ sb.append((char)LATCH_TO_BYTE_PADDED);
+ }
+ }
+
+ int idx = startpos;
+ // Encode sixpacks
+ if (count >= 6) {
+ char[] chars = new char[5];
+ while ((startpos + count - idx) >= 6) {
+ long t = 0;
+ for (int i = 0; i < 6; i++) {
+ t <<= 8;
+ t += bytes[idx + i] & 0xff;
+ }
+ for (int i = 0; i < 5; i++) {
+ chars[i] = (char) (t % 900);
+ t /= 900;
+ }
+ for (int i = chars.length - 1; i >= 0; i--) {
+ sb.append(chars[i]);
+ }
+ idx += 6;
+ }
+ }
+ //Encode rest (remaining n<5 bytes if any)
+ for (int i = idx; i < startpos + count; i++) {
+ int ch = bytes[i] & 0xff;
+ sb.append((char) ch);
+ }
+ }
+
+ private static void encodeNumeric(String msg, int startpos, int count, StringBuilder sb) {
+ int idx = 0;
+ StringBuilder tmp = new StringBuilder(count / 3 + 1);
+ BigInteger num900 = BigInteger.valueOf(900);
+ BigInteger num0 = BigInteger.valueOf(0);
+ while (idx < count - 1) {
+ tmp.setLength(0);
+ int len = Math.min(44, count - idx);
+ String part = '1' + msg.substring(startpos + idx, startpos + idx + len);
+ BigInteger bigint = new BigInteger(part);
+ do {
+ tmp.append((char) bigint.mod(num900).intValue());
+ bigint = bigint.divide(num900);
+ } while (!bigint.equals(num0));
+
+ //Reverse temporary string
+ for (int i = tmp.length() - 1; i >= 0; i--) {
+ sb.append(tmp.charAt(i));
+ }
+ idx += len;
+ }
+ }
+
+
+ private static boolean isDigit(char ch) {
+ return ch >= '0' && ch <= '9';
+ }
+
+ private static boolean isAlphaUpper(char ch) {
+ return ch == ' ' || (ch >= 'A' && ch <= 'Z');
+ }
+
+ private static boolean isAlphaLower(char ch) {
+ return ch == ' ' || (ch >= 'a' && ch <= 'z');
+ }
+
+ private static boolean isMixed(char ch) {
+ return MIXED[ch] != -1;
+ }
+
+ private static boolean isPunctuation(char ch) {
+ return PUNCTUATION[ch] != -1;
+ }
+
+ private static boolean isText(char ch) {
+ return ch == '\t' || ch == '\n' || ch == '\r' || (ch >= 32 && ch <= 126);
+ }
+
+ /**
+ * Determines the number of consecutive characters that are encodable using numeric compaction.
+ *
+ * @param msg the message
+ * @param startpos the start position within the message
+ * @return the requested character count
+ */
+ private static int determineConsecutiveDigitCount(CharSequence msg, int startpos) {
+ int count = 0;
+ int len = msg.length();
+ int idx = startpos;
+ if (idx < len) {
+ char ch = msg.charAt(idx);
+ while (isDigit(ch) && idx < len) {
+ count++;
+ idx++;
+ if (idx < len) {
+ ch = msg.charAt(idx);
+ }
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Determines the number of consecutive characters that are encodable using text compaction.
+ *
+ * @param msg the message
+ * @param startpos the start position within the message
+ * @return the requested character count
+ */
+ private static int determineConsecutiveTextCount(CharSequence msg, int startpos) {
+ int len = msg.length();
+ int idx = startpos;
+ while (idx < len) {
+ char ch = msg.charAt(idx);
+ int numericCount = 0;
+ while (numericCount < 13 && isDigit(ch) && idx < len) {
+ numericCount++;
+ idx++;
+ if (idx < len) {
+ ch = msg.charAt(idx);
+ }
+ }
+ if (numericCount >= 13) {
+ return idx - startpos - numericCount;
+ }
+ if (numericCount > 0) {
+ //Heuristic: All text-encodable chars or digits are binary encodable
+ continue;
+ }
+ ch = msg.charAt(idx);
+
+ //Check if character is encodable
+ if (!isText(ch)) {
+ break;
+ }
+ idx++;
+ }
+ return idx - startpos;
+ }
+
+ /**
+ * Determines the number of consecutive characters that are encodable using binary compaction.
+ *
+ * @param msg the message
+ * @param bytes the message converted to a byte array
+ * @param startpos the start position within the message
+ * @return the requested character count
+ */
+ private static int determineConsecutiveBinaryCount(CharSequence msg, byte[] bytes, int startpos)
+ throws WriterException {
+ int len = msg.length();
+ int idx = startpos;
+ while (idx < len) {
+ char ch = msg.charAt(idx);
+ int numericCount = 0;
+
+ while (numericCount < 13 && isDigit(ch)) {
+ numericCount++;
+ //textCount++;
+ int i = idx + numericCount;
+ if (i >= len) {
+ break;
+ }
+ ch = msg.charAt(i);
+ }
+ if (numericCount >= 13) {
+ return idx - startpos;
+ }
+ int textCount = 0;
+ while (textCount < 5 && isText(ch)) {
+ textCount++;
+ int i = idx + textCount;
+ if (i >= len) {
+ break;
+ }
+ ch = msg.charAt(i);
+ }
+ if (textCount >= 5) {
+ return idx - startpos;
+ }
+ ch = msg.charAt(idx);
+
+ //Check if character is encodable
+ //Sun returns a ASCII 63 (?) for a character that cannot be mapped. Let's hope all
+ //other VMs do the same
+ if (bytes[idx] == 63 && ch != '?') {
+ throw new WriterException("Non-encodable character detected: " + ch + " (Unicode: " + (int) ch + ')');
+ }
+ idx++;
+ }
+ return idx - startpos;
+ }
+
+ private static void encodingECI(int eci, StringBuilder sb) throws WriterException {
+ if (eci >= 0 && eci < 900) {
+ sb.append((char) ECI_CHARSET);
+ sb.append((char) eci);
+ } else if (eci < 810900) {
+ sb.append((char) ECI_GENERAL_PURPOSE);
+ sb.append((char) (eci / 900 - 1));
+ sb.append((char) (eci % 900));
+ } else if (eci < 811800) {
+ sb.append((char) ECI_USER_DEFINED);
+ sb.append((char) (810900 - eci));
+ } else {
+ throw new WriterException("ECI number not in valid range from 0..811799, but was " + eci);
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/QRCodeReader.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/QRCodeReader.java
new file mode 100644
index 000000000..96284de3a
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/QRCodeReader.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2007 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.qrcode;
+
+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.Reader;
+import com.google.zxing.Result;
+import com.google.zxing.ResultMetadataType;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.qrcode.decoder.Decoder;
+import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData;
+import com.google.zxing.qrcode.detector.Detector;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode QR Codes in an image.
+ *
+ * @author Sean Owen
+ */
+public class QRCodeReader implements Reader {
+
+ private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
+
+ private final Decoder decoder = new Decoder();
+
+ protected final Decoder getDecoder() {
+ return decoder;
+ }
+
+ /**
+ * Locates and decodes a QR code in an image.
+ *
+ * @return a String representing the content encoded by the QR code
+ * @throws NotFoundException if a QR code cannot be found
+ * @throws FormatException if a QR code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ @Override
+ public final Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+ DecoderResult decoderResult;
+ ResultPoint[] points;
+ if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
+ BitMatrix bits = extractPureBits(image.getBlackMatrix());
+ decoderResult = decoder.decode(bits, hints);
+ points = NO_POINTS;
+ } else {
+ DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect(hints);
+ decoderResult = decoder.decode(detectorResult.getBits(), hints);
+ points = detectorResult.getPoints();
+ }
+
+ // If the code was mirrored: swap the bottom-left and the top-right points.
+ if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
+ ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
+ }
+
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
+ List byteSegments = decoderResult.getByteSegments();
+ if (byteSegments != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
+ }
+ String ecLevel = decoderResult.getECLevel();
+ if (ecLevel != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+ if (decoderResult.hasStructuredAppend()) {
+ result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
+ decoderResult.getStructuredAppendSequenceNumber());
+ result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
+ decoderResult.getStructuredAppendParity());
+ }
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ // do nothing
+ }
+
+ /**
+ * This method detects a code in a "pure" image -- that is, pure monochrome image
+ * which contains only an unrotated, unskewed, image of a code, with some white border
+ * around it. This is a specialized method that works exceptionally fast in this special
+ * case.
+ *
+ * @see com.google.zxing.datamatrix.DataMatrixReader#extractPureBits(BitMatrix)
+ */
+ private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException {
+
+ int[] leftTopBlack = image.getTopLeftOnBit();
+ int[] rightBottomBlack = image.getBottomRightOnBit();
+ if (leftTopBlack == null || rightBottomBlack == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ float moduleSize = moduleSize(leftTopBlack, image);
+
+ int top = leftTopBlack[1];
+ int bottom = rightBottomBlack[1];
+ int left = leftTopBlack[0];
+ int right = rightBottomBlack[0];
+
+ // Sanity check!
+ if (left >= right || top >= bottom) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ if (bottom - top != right - left) {
+ // Special case, where bottom-right module wasn't black so we found something else in the last row
+ // Assume it's a square, so use height as the width
+ right = left + (bottom - top);
+ }
+
+ int matrixWidth = Math.round((right - left + 1) / moduleSize);
+ int matrixHeight = Math.round((bottom - top + 1) / moduleSize);
+ if (matrixWidth <= 0 || matrixHeight <= 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ if (matrixHeight != matrixWidth) {
+ // Only possibly decode square regions
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Push in the "border" by half the module width so that we start
+ // sampling in the middle of the module. Just in case the image is a
+ // little off, this will help recover.
+ int nudge = (int) (moduleSize / 2.0f);
+ top += nudge;
+ left += nudge;
+
+ // But careful that this does not sample off the edge
+ int nudgedTooFarRight = left + (int) ((matrixWidth - 1) * moduleSize) - (right - 1);
+ if (nudgedTooFarRight > 0) {
+ if (nudgedTooFarRight > nudge) {
+ // Neither way fits; abort
+ throw NotFoundException.getNotFoundInstance();
+ }
+ left -= nudgedTooFarRight;
+ }
+ int nudgedTooFarDown = top + (int) ((matrixHeight - 1) * moduleSize) - (bottom - 1);
+ if (nudgedTooFarDown > 0) {
+ if (nudgedTooFarDown > nudge) {
+ // Neither way fits; abort
+ throw NotFoundException.getNotFoundInstance();
+ }
+ top -= nudgedTooFarDown;
+ }
+
+ // Now just read off the bits
+ BitMatrix bits = new BitMatrix(matrixWidth, matrixHeight);
+ for (int y = 0; y < matrixHeight; y++) {
+ int iOffset = top + (int) (y * moduleSize);
+ for (int x = 0; x < matrixWidth; x++) {
+ if (image.get(left + (int) (x * moduleSize), iOffset)) {
+ bits.set(x, y);
+ }
+ }
+ }
+ return bits;
+ }
+
+ private static float moduleSize(int[] leftTopBlack, BitMatrix image) throws NotFoundException {
+ int height = image.getHeight();
+ int width = image.getWidth();
+ int x = leftTopBlack[0];
+ int y = leftTopBlack[1];
+ boolean inBlack = true;
+ int transitions = 0;
+ while (x < width && y < height) {
+ if (inBlack != image.get(x, y)) {
+ if (++transitions == 5) {
+ break;
+ }
+ inBlack = !inBlack;
+ }
+ x++;
+ y++;
+ }
+ if (x == width || y == height) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ return (x - leftTopBlack[0]) / 7.0f;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/QRCodeWriter.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/QRCodeWriter.java
new file mode 100644
index 000000000..a4fabf406
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/QRCodeWriter.java
@@ -0,0 +1,120 @@
+/*
+ * 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.qrcode;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.Writer;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.encoder.ByteMatrix;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import com.google.zxing.qrcode.encoder.Encoder;
+import com.google.zxing.qrcode.encoder.QRCode;
+
+import java.util.Map;
+
+/**
+ * This object renders a QR Code as a BitMatrix 2D array of greyscale values.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class QRCodeWriter implements Writer {
+
+ private static final int QUIET_ZONE_SIZE = 4;
+
+ @Override
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
+ throws WriterException {
+
+ return encode(contents, format, width, height, null);
+ }
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+
+ if (contents.isEmpty()) {
+ throw new IllegalArgumentException("Found empty contents");
+ }
+
+ if (format != BarcodeFormat.QR_CODE) {
+ throw new IllegalArgumentException("Can only encode QR_CODE, but got " + format);
+ }
+
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' +
+ height);
+ }
+
+ ErrorCorrectionLevel errorCorrectionLevel = ErrorCorrectionLevel.L;
+ int quietZone = QUIET_ZONE_SIZE;
+ if (hints != null) {
+ ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel) hints.get(EncodeHintType.ERROR_CORRECTION);
+ if (requestedECLevel != null) {
+ errorCorrectionLevel = requestedECLevel;
+ }
+ Integer quietZoneInt = (Integer) hints.get(EncodeHintType.MARGIN);
+ if (quietZoneInt != null) {
+ quietZone = quietZoneInt;
+ }
+ }
+
+ QRCode code = Encoder.encode(contents, errorCorrectionLevel, hints);
+ return renderResult(code, width, height, quietZone);
+ }
+
+ // Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses
+ // 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap).
+ private static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) {
+ ByteMatrix input = code.getMatrix();
+ if (input == null) {
+ throw new IllegalStateException();
+ }
+ int inputWidth = input.getWidth();
+ int inputHeight = input.getHeight();
+ int qrWidth = inputWidth + (quietZone << 1);
+ int qrHeight = inputHeight + (quietZone << 1);
+ int outputWidth = Math.max(width, qrWidth);
+ int outputHeight = Math.max(height, qrHeight);
+
+ int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight);
+ // Padding includes both the quiet zone and the extra white pixels to accommodate the requested
+ // dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone.
+ // If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will
+ // handle all the padding from 100x100 (the actual QR) up to 200x160.
+ int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
+ int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
+
+ BitMatrix output = new BitMatrix(outputWidth, outputHeight);
+
+ for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
+ // Write the contents of this row of the barcode
+ for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
+ if (input.get(inputX, inputY) == 1) {
+ output.setRegion(outputX, outputY, multiple, multiple);
+ }
+ }
+ }
+
+ return output;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/BitMatrixParser.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/BitMatrixParser.java
new file mode 100644
index 000000000..7d3d949bd
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/BitMatrixParser.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * @author Sean Owen
+ */
+final class BitMatrixParser {
+
+ private final BitMatrix bitMatrix;
+ private Version parsedVersion;
+ private FormatInformation parsedFormatInfo;
+ private boolean mirror;
+
+ /**
+ * @param bitMatrix {@link BitMatrix} to parse
+ * @throws FormatException if dimension is not >= 21 and 1 mod 4
+ */
+ BitMatrixParser(BitMatrix bitMatrix) throws FormatException {
+ int dimension = bitMatrix.getHeight();
+ if (dimension < 21 || (dimension & 0x03) != 1) {
+ throw FormatException.getFormatInstance();
+ }
+ this.bitMatrix = bitMatrix;
+ }
+
+ /**
+ * Reads format information from one of its two locations within the QR Code.
+ *
+ * @return {@link FormatInformation} encapsulating the QR Code's format info
+ * @throws FormatException if both format information locations cannot be parsed as
+ * the valid encoding of format information
+ */
+ FormatInformation readFormatInformation() throws FormatException {
+
+ if (parsedFormatInfo != null) {
+ return parsedFormatInfo;
+ }
+
+ // Read top-left format info bits
+ int formatInfoBits1 = 0;
+ for (int i = 0; i < 6; i++) {
+ formatInfoBits1 = copyBit(i, 8, formatInfoBits1);
+ }
+ // .. and skip a bit in the timing pattern ...
+ formatInfoBits1 = copyBit(7, 8, formatInfoBits1);
+ formatInfoBits1 = copyBit(8, 8, formatInfoBits1);
+ formatInfoBits1 = copyBit(8, 7, formatInfoBits1);
+ // .. and skip a bit in the timing pattern ...
+ for (int j = 5; j >= 0; j--) {
+ formatInfoBits1 = copyBit(8, j, formatInfoBits1);
+ }
+
+ // Read the top-right/bottom-left pattern too
+ int dimension = bitMatrix.getHeight();
+ int formatInfoBits2 = 0;
+ int jMin = dimension - 7;
+ for (int j = dimension - 1; j >= jMin; j--) {
+ formatInfoBits2 = copyBit(8, j, formatInfoBits2);
+ }
+ for (int i = dimension - 8; i < dimension; i++) {
+ formatInfoBits2 = copyBit(i, 8, formatInfoBits2);
+ }
+
+ parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits1, formatInfoBits2);
+ if (parsedFormatInfo != null) {
+ return parsedFormatInfo;
+ }
+ throw FormatException.getFormatInstance();
+ }
+
+ /**
+ * Reads version information from one of its two locations within the QR Code.
+ *
+ * @return {@link Version} encapsulating the QR Code's version
+ * @throws FormatException if both version information locations cannot be parsed as
+ * the valid encoding of version information
+ */
+ Version readVersion() throws FormatException {
+
+ if (parsedVersion != null) {
+ return parsedVersion;
+ }
+
+ int dimension = bitMatrix.getHeight();
+
+ int provisionalVersion = (dimension - 17) >> 2;
+ if (provisionalVersion <= 6) {
+ return Version.getVersionForNumber(provisionalVersion);
+ }
+
+ // Read top-right version info: 3 wide by 6 tall
+ int versionBits = 0;
+ int ijMin = dimension - 11;
+ for (int j = 5; j >= 0; j--) {
+ for (int i = dimension - 9; i >= ijMin; i--) {
+ versionBits = copyBit(i, j, versionBits);
+ }
+ }
+
+ Version theParsedVersion = Version.decodeVersionInformation(versionBits);
+ if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) {
+ parsedVersion = theParsedVersion;
+ return theParsedVersion;
+ }
+
+ // Hmm, failed. Try bottom left: 6 wide by 3 tall
+ versionBits = 0;
+ for (int i = 5; i >= 0; i--) {
+ for (int j = dimension - 9; j >= ijMin; j--) {
+ versionBits = copyBit(i, j, versionBits);
+ }
+ }
+
+ theParsedVersion = Version.decodeVersionInformation(versionBits);
+ if (theParsedVersion != null && theParsedVersion.getDimensionForVersion() == dimension) {
+ parsedVersion = theParsedVersion;
+ return theParsedVersion;
+ }
+ throw FormatException.getFormatInstance();
+ }
+
+ private int copyBit(int i, int j, int versionBits) {
+ boolean bit = mirror ? bitMatrix.get(j, i) : bitMatrix.get(i, j);
+ return bit ? (versionBits << 1) | 0x1 : versionBits << 1;
+ }
+
+ /**
+ * Reads the bits in the {@link BitMatrix} representing the finder pattern in the
+ * correct order in order to reconstruct the codewords bytes contained within the
+ * QR Code.
+ *
+ * @return bytes encoded within the QR Code
+ * @throws FormatException if the exact number of bytes expected is not read
+ */
+ byte[] readCodewords() throws FormatException {
+
+ FormatInformation formatInfo = readFormatInformation();
+ Version version = readVersion();
+
+ // Get the data mask for the format used in this QR Code. This will exclude
+ // some bits from reading as we wind through the bit matrix.
+ DataMask dataMask = DataMask.forReference(formatInfo.getDataMask());
+ int dimension = bitMatrix.getHeight();
+ dataMask.unmaskBitMatrix(bitMatrix, dimension);
+
+ BitMatrix functionPattern = version.buildFunctionPattern();
+
+ boolean readingUp = true;
+ byte[] result = new byte[version.getTotalCodewords()];
+ int resultOffset = 0;
+ int currentByte = 0;
+ int bitsRead = 0;
+ // Read columns in pairs, from right to left
+ for (int j = dimension - 1; j > 0; j -= 2) {
+ if (j == 6) {
+ // Skip whole column with vertical alignment pattern;
+ // saves time and makes the other code proceed more cleanly
+ j--;
+ }
+ // Read alternatingly from bottom to top then top to bottom
+ for (int count = 0; count < dimension; count++) {
+ int i = readingUp ? dimension - 1 - count : count;
+ for (int col = 0; col < 2; col++) {
+ // Ignore bits covered by the function pattern
+ if (!functionPattern.get(j - col, i)) {
+ // Read a bit
+ bitsRead++;
+ currentByte <<= 1;
+ if (bitMatrix.get(j - col, i)) {
+ currentByte |= 1;
+ }
+ // If we've made a whole byte, save it off
+ if (bitsRead == 8) {
+ result[resultOffset++] = (byte) currentByte;
+ bitsRead = 0;
+ currentByte = 0;
+ }
+ }
+ }
+ }
+ readingUp ^= true; // readingUp = !readingUp; // switch directions
+ }
+ if (resultOffset != version.getTotalCodewords()) {
+ throw FormatException.getFormatInstance();
+ }
+ return result;
+ }
+
+ /**
+ * Revert the mask removal done while reading the code words. The bit matrix should revert to its original state.
+ */
+ void remask() {
+ if (parsedFormatInfo == null) {
+ return; // We have no format information, and have no data mask
+ }
+ DataMask dataMask = DataMask.forReference(parsedFormatInfo.getDataMask());
+ int dimension = bitMatrix.getHeight();
+ dataMask.unmaskBitMatrix(bitMatrix, dimension);
+ }
+
+ /**
+ * Prepare the parser for a mirrored operation.
+ * This flag has effect only on the {@link #readFormatInformation()} and the
+ * {@link #readVersion()}. Before proceeding with {@link #readCodewords()} the
+ * {@link #mirror()} method should be called.
+ *
+ * @param mirror Whether to read version and format information mirrored.
+ */
+ void setMirror(boolean mirror) {
+ parsedVersion = null;
+ parsedFormatInfo = null;
+ this.mirror = mirror;
+ }
+
+ /** Mirror the bit matrix in order to attempt a second reading. */
+ void mirror() {
+ for (int x = 0; x < bitMatrix.getWidth(); x++) {
+ for (int y = x + 1; y < bitMatrix.getHeight(); y++) {
+ if (bitMatrix.get(x, y) != bitMatrix.get(y, x)) {
+ bitMatrix.flip(y, x);
+ bitMatrix.flip(x, y);
+ }
+ }
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DataBlock.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DataBlock.java
new file mode 100755
index 000000000..8f5cdcba7
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DataBlock.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+/**
+ * Encapsulates a block of data within a QR Code. QR Codes may split their data into
+ * multiple blocks, each of which is a unit of data and error-correction codewords. Each
+ * is represented by an instance of this class.
+ *
+ * @author Sean Owen
+ */
+final class DataBlock {
+
+ private final int numDataCodewords;
+ private final byte[] codewords;
+
+ private DataBlock(int numDataCodewords, byte[] codewords) {
+ this.numDataCodewords = numDataCodewords;
+ this.codewords = codewords;
+ }
+
+ /**
+ * When QR Codes use multiple data blocks, they are actually interleaved.
+ * That is, the first byte of data block 1 to n is written, then the second bytes, and so on. This
+ * method will separate the data into original blocks.
+ *
+ * @param rawCodewords bytes as read directly from the QR Code
+ * @param version version of the QR Code
+ * @param ecLevel error-correction level of the QR Code
+ * @return DataBlocks containing original bytes, "de-interleaved" from representation in the
+ * QR Code
+ */
+ static DataBlock[] getDataBlocks(byte[] rawCodewords,
+ Version version,
+ ErrorCorrectionLevel ecLevel) {
+
+ if (rawCodewords.length != version.getTotalCodewords()) {
+ throw new IllegalArgumentException();
+ }
+
+ // Figure out the number and size of data blocks used by this version and
+ // error correction level
+ Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
+
+ // First count the total number of data blocks
+ int totalBlocks = 0;
+ Version.ECB[] ecBlockArray = ecBlocks.getECBlocks();
+ for (Version.ECB ecBlock : ecBlockArray) {
+ totalBlocks += ecBlock.getCount();
+ }
+
+ // Now establish DataBlocks of the appropriate size and number of data codewords
+ DataBlock[] result = new DataBlock[totalBlocks];
+ int numResultBlocks = 0;
+ for (Version.ECB ecBlock : ecBlockArray) {
+ for (int i = 0; i < ecBlock.getCount(); i++) {
+ int numDataCodewords = ecBlock.getDataCodewords();
+ int numBlockCodewords = ecBlocks.getECCodewordsPerBlock() + numDataCodewords;
+ result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]);
+ }
+ }
+
+ // All blocks have the same amount of data, except that the last n
+ // (where n may be 0) have 1 more byte. Figure out where these start.
+ int shorterBlocksTotalCodewords = result[0].codewords.length;
+ int longerBlocksStartAt = result.length - 1;
+ while (longerBlocksStartAt >= 0) {
+ int numCodewords = result[longerBlocksStartAt].codewords.length;
+ if (numCodewords == shorterBlocksTotalCodewords) {
+ break;
+ }
+ longerBlocksStartAt--;
+ }
+ longerBlocksStartAt++;
+
+ int shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.getECCodewordsPerBlock();
+ // The last elements of result may be 1 element longer;
+ // first fill out as many elements as all of them have
+ int rawCodewordsOffset = 0;
+ for (int i = 0; i < shorterBlocksNumDataCodewords; i++) {
+ for (int j = 0; j < numResultBlocks; j++) {
+ result[j].codewords[i] = rawCodewords[rawCodewordsOffset++];
+ }
+ }
+ // Fill out the last data block in the longer ones
+ for (int j = longerBlocksStartAt; j < numResultBlocks; j++) {
+ result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++];
+ }
+ // Now add in error correction blocks
+ int max = result[0].codewords.length;
+ for (int i = shorterBlocksNumDataCodewords; i < max; i++) {
+ for (int j = 0; j < numResultBlocks; j++) {
+ int iOffset = j < longerBlocksStartAt ? i : i + 1;
+ result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++];
+ }
+ }
+ return result;
+ }
+
+ int getNumDataCodewords() {
+ return numDataCodewords;
+ }
+
+ byte[] getCodewords() {
+ return codewords;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java
new file mode 100755
index 000000000..353c1a826
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * Encapsulates data masks for the data bits in a QR code, per ISO 18004:2006 6.8. Implementations
+ * of this class can un-mask a raw BitMatrix. For simplicity, they will unmask the entire BitMatrix,
+ * including areas used for finder patterns, timing patterns, etc. These areas should be unused
+ * after the point they are unmasked anyway.
+ *
+ * Note that the diagram in section 6.8.1 is misleading since it indicates that i is column position
+ * and j is row position. In fact, as the text says, i is row position and j is column position.
+ *
+ * @author Sean Owen
+ */
+abstract class DataMask {
+
+ /**
+ * See ISO 18004:2006 6.8.1
+ */
+ private static final DataMask[] DATA_MASKS = {
+ new DataMask000(),
+ new DataMask001(),
+ new DataMask010(),
+ new DataMask011(),
+ new DataMask100(),
+ new DataMask101(),
+ new DataMask110(),
+ new DataMask111(),
+ };
+
+ private DataMask() {
+ }
+
+ /**
+ * Implementations of this method reverse the data masking process applied to a QR Code and
+ * make its bits ready to read.
+ *
+ * @param bits representation of QR Code bits
+ * @param dimension dimension of QR Code, represented by bits, being unmasked
+ */
+ final void unmaskBitMatrix(BitMatrix bits, int dimension) {
+ for (int i = 0; i < dimension; i++) {
+ for (int j = 0; j < dimension; j++) {
+ if (isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ }
+
+ abstract boolean isMasked(int i, int j);
+
+ /**
+ * @param reference a value between 0 and 7 indicating one of the eight possible
+ * data mask patterns a QR Code may use
+ * @return DataMask encapsulating the data mask pattern
+ */
+ static DataMask forReference(int reference) {
+ if (reference < 0 || reference > 7) {
+ throw new IllegalArgumentException();
+ }
+ return DATA_MASKS[reference];
+ }
+
+ /**
+ * 000: mask bits for which (x + y) mod 2 == 0
+ */
+ private static final class DataMask000 extends DataMask {
+ @Override
+ boolean isMasked(int i, int j) {
+ return ((i + j) & 0x01) == 0;
+ }
+ }
+
+ /**
+ * 001: mask bits for which x mod 2 == 0
+ */
+ private static final class DataMask001 extends DataMask {
+ @Override
+ boolean isMasked(int i, int j) {
+ return (i & 0x01) == 0;
+ }
+ }
+
+ /**
+ * 010: mask bits for which y mod 3 == 0
+ */
+ private static final class DataMask010 extends DataMask {
+ @Override
+ boolean isMasked(int i, int j) {
+ return j % 3 == 0;
+ }
+ }
+
+ /**
+ * 011: mask bits for which (x + y) mod 3 == 0
+ */
+ private static final class DataMask011 extends DataMask {
+ @Override
+ boolean isMasked(int i, int j) {
+ return (i + j) % 3 == 0;
+ }
+ }
+
+ /**
+ * 100: mask bits for which (x/2 + y/3) mod 2 == 0
+ */
+ private static final class DataMask100 extends DataMask {
+ @Override
+ boolean isMasked(int i, int j) {
+ return (((i >>> 1) + (j /3)) & 0x01) == 0;
+ }
+ }
+
+ /**
+ * 101: mask bits for which xy mod 2 + xy mod 3 == 0
+ */
+ private static final class DataMask101 extends DataMask {
+ @Override
+ boolean isMasked(int i, int j) {
+ int temp = i * j;
+ return (temp & 0x01) + (temp % 3) == 0;
+ }
+ }
+
+ /**
+ * 110: mask bits for which (xy mod 2 + xy mod 3) mod 2 == 0
+ */
+ private static final class DataMask110 extends DataMask {
+ @Override
+ boolean isMasked(int i, int j) {
+ int temp = i * j;
+ return (((temp & 0x01) + (temp % 3)) & 0x01) == 0;
+ }
+ }
+
+ /**
+ * 111: mask bits for which ((x+y)mod 2 + xy mod 3) mod 2 == 0
+ */
+ private static final class DataMask111 extends DataMask {
+ @Override
+ boolean isMasked(int i, int j) {
+ return ((((i + j) & 0x01) + ((i * j) % 3)) & 0x01) == 0;
+ }
+ }
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java
new file mode 100644
index 000000000..09e8a5745
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitSource;
+import com.google.zxing.common.CharacterSetECI;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.StringUtils;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * QR Codes can encode text as bits in one of several modes, and can use multiple modes
+ * in one QR Code. This class decodes the bits back into text.
+ *
+ * See ISO 18004:2006, 6.4.3 - 6.4.7
+ *
+ * @author Sean Owen
+ */
+final class DecodedBitStreamParser {
+
+ /**
+ * See ISO 18004:2006, 6.4.4 Table 5
+ */
+ private static final char[] ALPHANUMERIC_CHARS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
+ 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ ' ', '$', '%', '*', '+', '-', '.', '/', ':'
+ };
+ private static final int GB2312_SUBSET = 1;
+
+ private DecodedBitStreamParser() {
+ }
+
+ static DecoderResult decode(byte[] bytes,
+ Version version,
+ ErrorCorrectionLevel ecLevel,
+ Map hints) throws FormatException {
+ BitSource bits = new BitSource(bytes);
+ StringBuilder result = new StringBuilder(50);
+ List byteSegments = new ArrayList<>(1);
+ int symbolSequence = -1;
+ int parityData = -1;
+
+ try {
+ CharacterSetECI currentCharacterSetECI = null;
+ boolean fc1InEffect = false;
+ Mode mode;
+ do {
+ // While still another segment to read...
+ if (bits.available() < 4) {
+ // OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
+ mode = Mode.TERMINATOR;
+ } else {
+ mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
+ }
+ if (mode != Mode.TERMINATOR) {
+ if (mode == Mode.FNC1_FIRST_POSITION || mode == Mode.FNC1_SECOND_POSITION) {
+ // We do little with FNC1 except alter the parsed result a bit according to the spec
+ fc1InEffect = true;
+ } else if (mode == Mode.STRUCTURED_APPEND) {
+ if (bits.available() < 16) {
+ throw FormatException.getFormatInstance();
+ }
+ // sequence number and parity is added later to the result metadata
+ // Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
+ symbolSequence = bits.readBits(8);
+ parityData = bits.readBits(8);
+ } else if (mode == Mode.ECI) {
+ // Count doesn't apply to ECI
+ int value = parseECIValue(bits);
+ currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value);
+ if (currentCharacterSetECI == null) {
+ throw FormatException.getFormatInstance();
+ }
+ } else {
+ // First handle Hanzi mode which does not start with character count
+ if (mode == Mode.HANZI) {
+ //chinese mode contains a sub set indicator right after mode indicator
+ int subset = bits.readBits(4);
+ int countHanzi = bits.readBits(mode.getCharacterCountBits(version));
+ if (subset == GB2312_SUBSET) {
+ decodeHanziSegment(bits, result, countHanzi);
+ }
+ } else {
+ // "Normal" QR code modes:
+ // How many characters will follow, encoded in this mode?
+ int count = bits.readBits(mode.getCharacterCountBits(version));
+ if (mode == Mode.NUMERIC) {
+ decodeNumericSegment(bits, result, count);
+ } else if (mode == Mode.ALPHANUMERIC) {
+ decodeAlphanumericSegment(bits, result, count, fc1InEffect);
+ } else if (mode == Mode.BYTE) {
+ decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints);
+ } else if (mode == Mode.KANJI) {
+ decodeKanjiSegment(bits, result, count);
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ }
+ }
+ }
+ } while (mode != Mode.TERMINATOR);
+ } catch (IllegalArgumentException iae) {
+ // from readBits() calls
+ throw FormatException.getFormatInstance();
+ }
+
+ return new DecoderResult(bytes,
+ result.toString(),
+ byteSegments.isEmpty() ? null : byteSegments,
+ ecLevel == null ? null : ecLevel.toString(),
+ symbolSequence,
+ parityData);
+ }
+
+ /**
+ * See specification GBT 18284-2000
+ */
+ private static void decodeHanziSegment(BitSource bits,
+ StringBuilder result,
+ int count) throws FormatException {
+ // Don't crash trying to read more bits than we have available.
+ if (count * 13 > bits.available()) {
+ throw FormatException.getFormatInstance();
+ }
+
+ // Each character will require 2 bytes. Read the characters as 2-byte pairs
+ // and decode as GB2312 afterwards
+ byte[] buffer = new byte[2 * count];
+ int offset = 0;
+ while (count > 0) {
+ // Each 13 bits encodes a 2-byte character
+ int twoBytes = bits.readBits(13);
+ int assembledTwoBytes = ((twoBytes / 0x060) << 8) | (twoBytes % 0x060);
+ if (assembledTwoBytes < 0x003BF) {
+ // In the 0xA1A1 to 0xAAFE range
+ assembledTwoBytes += 0x0A1A1;
+ } else {
+ // In the 0xB0A1 to 0xFAFE range
+ assembledTwoBytes += 0x0A6A1;
+ }
+ buffer[offset] = (byte) ((assembledTwoBytes >> 8) & 0xFF);
+ buffer[offset + 1] = (byte) (assembledTwoBytes & 0xFF);
+ offset += 2;
+ count--;
+ }
+
+ try {
+ result.append(new String(buffer, StringUtils.GB2312));
+ } catch (UnsupportedEncodingException ignored) {
+ throw FormatException.getFormatInstance();
+ }
+ }
+
+ private static void decodeKanjiSegment(BitSource bits,
+ StringBuilder result,
+ int count) throws FormatException {
+ // Don't crash trying to read more bits than we have available.
+ if (count * 13 > bits.available()) {
+ throw FormatException.getFormatInstance();
+ }
+
+ // Each character will require 2 bytes. Read the characters as 2-byte pairs
+ // and decode as Shift_JIS afterwards
+ byte[] buffer = new byte[2 * count];
+ int offset = 0;
+ while (count > 0) {
+ // Each 13 bits encodes a 2-byte character
+ int twoBytes = bits.readBits(13);
+ int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0);
+ if (assembledTwoBytes < 0x01F00) {
+ // In the 0x8140 to 0x9FFC range
+ assembledTwoBytes += 0x08140;
+ } else {
+ // In the 0xE040 to 0xEBBF range
+ assembledTwoBytes += 0x0C140;
+ }
+ buffer[offset] = (byte) (assembledTwoBytes >> 8);
+ buffer[offset + 1] = (byte) assembledTwoBytes;
+ offset += 2;
+ count--;
+ }
+ // Shift_JIS may not be supported in some environments:
+ try {
+ result.append(new String(buffer, StringUtils.SHIFT_JIS));
+ } catch (UnsupportedEncodingException ignored) {
+ throw FormatException.getFormatInstance();
+ }
+ }
+
+ private static void decodeByteSegment(BitSource bits,
+ StringBuilder result,
+ int count,
+ CharacterSetECI currentCharacterSetECI,
+ Collection byteSegments,
+ Map hints) throws FormatException {
+ // Don't crash trying to read more bits than we have available.
+ if (count << 3 > bits.available()) {
+ throw FormatException.getFormatInstance();
+ }
+
+ byte[] readBytes = new byte[count];
+ for (int i = 0; i < count; i++) {
+ readBytes[i] = (byte) bits.readBits(8);
+ }
+ String encoding;
+ if (currentCharacterSetECI == null) {
+ // The spec isn't clear on this mode; see
+ // section 6.4.5: t does not say which encoding to assuming
+ // upon decoding. I have seen ISO-8859-1 used as well as
+ // Shift_JIS -- without anything like an ECI designator to
+ // give a hint.
+ encoding = StringUtils.guessEncoding(readBytes, hints);
+ } else {
+ encoding = currentCharacterSetECI.name();
+ }
+ try {
+ result.append(new String(readBytes, encoding));
+ } catch (UnsupportedEncodingException ignored) {
+ throw FormatException.getFormatInstance();
+ }
+ byteSegments.add(readBytes);
+ }
+
+ private static char toAlphaNumericChar(int value) throws FormatException {
+ if (value >= ALPHANUMERIC_CHARS.length) {
+ throw FormatException.getFormatInstance();
+ }
+ return ALPHANUMERIC_CHARS[value];
+ }
+
+ private static void decodeAlphanumericSegment(BitSource bits,
+ StringBuilder result,
+ int count,
+ boolean fc1InEffect) throws FormatException {
+ // Read two characters at a time
+ int start = result.length();
+ while (count > 1) {
+ if (bits.available() < 11) {
+ throw FormatException.getFormatInstance();
+ }
+ int nextTwoCharsBits = bits.readBits(11);
+ result.append(toAlphaNumericChar(nextTwoCharsBits / 45));
+ result.append(toAlphaNumericChar(nextTwoCharsBits % 45));
+ count -= 2;
+ }
+ if (count == 1) {
+ // special case: one character left
+ if (bits.available() < 6) {
+ throw FormatException.getFormatInstance();
+ }
+ result.append(toAlphaNumericChar(bits.readBits(6)));
+ }
+ // See section 6.4.8.1, 6.4.8.2
+ if (fc1InEffect) {
+ // We need to massage the result a bit if in an FNC1 mode:
+ for (int i = start; i < result.length(); i++) {
+ if (result.charAt(i) == '%') {
+ if (i < result.length() - 1 && result.charAt(i + 1) == '%') {
+ // %% is rendered as %
+ result.deleteCharAt(i + 1);
+ } else {
+ // In alpha mode, % should be converted to FNC1 separator 0x1D
+ result.setCharAt(i, (char) 0x1D);
+ }
+ }
+ }
+ }
+ }
+
+ private static void decodeNumericSegment(BitSource bits,
+ StringBuilder result,
+ int count) throws FormatException {
+ // Read three digits at a time
+ while (count >= 3) {
+ // Each 10 bits encodes three digits
+ if (bits.available() < 10) {
+ throw FormatException.getFormatInstance();
+ }
+ int threeDigitsBits = bits.readBits(10);
+ if (threeDigitsBits >= 1000) {
+ throw FormatException.getFormatInstance();
+ }
+ result.append(toAlphaNumericChar(threeDigitsBits / 100));
+ result.append(toAlphaNumericChar((threeDigitsBits / 10) % 10));
+ result.append(toAlphaNumericChar(threeDigitsBits % 10));
+ count -= 3;
+ }
+ if (count == 2) {
+ // Two digits left over to read, encoded in 7 bits
+ if (bits.available() < 7) {
+ throw FormatException.getFormatInstance();
+ }
+ int twoDigitsBits = bits.readBits(7);
+ if (twoDigitsBits >= 100) {
+ throw FormatException.getFormatInstance();
+ }
+ result.append(toAlphaNumericChar(twoDigitsBits / 10));
+ result.append(toAlphaNumericChar(twoDigitsBits % 10));
+ } else if (count == 1) {
+ // One digit left over to read
+ if (bits.available() < 4) {
+ throw FormatException.getFormatInstance();
+ }
+ int digitBits = bits.readBits(4);
+ if (digitBits >= 10) {
+ throw FormatException.getFormatInstance();
+ }
+ result.append(toAlphaNumericChar(digitBits));
+ }
+ }
+
+ private static int parseECIValue(BitSource bits) throws FormatException {
+ int firstByte = bits.readBits(8);
+ if ((firstByte & 0x80) == 0) {
+ // just one byte
+ return firstByte & 0x7F;
+ }
+ if ((firstByte & 0xC0) == 0x80) {
+ // two bytes
+ int secondByte = bits.readBits(8);
+ return ((firstByte & 0x3F) << 8) | secondByte;
+ }
+ if ((firstByte & 0xE0) == 0xC0) {
+ // three bytes
+ int secondThirdBytes = bits.readBits(16);
+ return ((firstByte & 0x1F) << 16) | secondThirdBytes;
+ }
+ throw FormatException.getFormatInstance();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Decoder.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Decoder.java
new file mode 100644
index 000000000..b28267e20
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Decoder.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import com.google.zxing.ChecksumException;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+import java.util.Map;
+
+/**
+ * The main class which implements QR Code decoding -- as opposed to locating and extracting
+ * the QR Code from an image.
+ *
+ * @author Sean Owen
+ */
+public final class Decoder {
+
+ private final ReedSolomonDecoder rsDecoder;
+
+ public Decoder() {
+ rsDecoder = new ReedSolomonDecoder(GenericGF.QR_CODE_FIELD_256);
+ }
+
+ public DecoderResult decode(boolean[][] image) throws ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ /**
+ * Convenience method that can decode a QR Code represented as a 2D array of booleans.
+ * "true" is taken to mean a black module.
+ *
+ * @param image booleans representing white/black QR Code modules
+ * @param hints decoding hints that should be used to influence decoding
+ * @return text and bytes encoded within the QR Code
+ * @throws FormatException if the QR Code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ public DecoderResult decode(boolean[][] image, Map hints)
+ throws ChecksumException, FormatException {
+ int dimension = image.length;
+ BitMatrix bits = new BitMatrix(dimension);
+ for (int i = 0; i < dimension; i++) {
+ for (int j = 0; j < dimension; j++) {
+ if (image[i][j]) {
+ bits.set(j, i);
+ }
+ }
+ }
+ return decode(bits, hints);
+ }
+
+ public DecoderResult decode(BitMatrix bits) throws ChecksumException, FormatException {
+ return decode(bits, null);
+ }
+
+ /**
+ * Decodes a QR Code represented as a {@link BitMatrix}. A 1 or "true" is taken to mean a black module.
+ *
+ * @param bits booleans representing white/black QR Code modules
+ * @param hints decoding hints that should be used to influence decoding
+ * @return text and bytes encoded within the QR Code
+ * @throws FormatException if the QR Code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ public DecoderResult decode(BitMatrix bits, Map hints)
+ throws FormatException, ChecksumException {
+
+ // Construct a parser and read version, error-correction level
+ BitMatrixParser parser = new BitMatrixParser(bits);
+ FormatException fe = null;
+ ChecksumException ce = null;
+ try {
+ return decode(parser, hints);
+ } catch (FormatException e) {
+ fe = e;
+ } catch (ChecksumException e) {
+ ce = e;
+ }
+
+ try {
+
+ // Revert the bit matrix
+ parser.remask();
+
+ // Will be attempting a mirrored reading of the version and format info.
+ parser.setMirror(true);
+
+ // Preemptively read the version.
+ parser.readVersion();
+
+ // Preemptively read the format information.
+ parser.readFormatInformation();
+
+ /*
+ * Since we're here, this means we have successfully detected some kind
+ * of version and format information when mirrored. This is a good sign,
+ * that the QR code may be mirrored, and we should try once more with a
+ * mirrored content.
+ */
+ // Prepare for a mirrored reading.
+ parser.mirror();
+
+ DecoderResult result = decode(parser, hints);
+
+ // Success! Notify the caller that the code was mirrored.
+ result.setOther(new QRCodeDecoderMetaData(true));
+
+ return result;
+
+ } catch (FormatException | ChecksumException e) {
+ // Throw the exception from the original reading
+ if (fe != null) {
+ throw fe;
+ }
+ if (ce != null) {
+ throw ce;
+ }
+ throw e;
+
+ }
+ }
+
+ private DecoderResult decode(BitMatrixParser parser, Map hints)
+ throws FormatException, ChecksumException {
+ Version version = parser.readVersion();
+ ErrorCorrectionLevel ecLevel = parser.readFormatInformation().getErrorCorrectionLevel();
+
+ // Read codewords
+ byte[] codewords = parser.readCodewords();
+ // Separate into data blocks
+ DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);
+
+ // Count total number of data bytes
+ int totalBytes = 0;
+ for (DataBlock dataBlock : dataBlocks) {
+ totalBytes += dataBlock.getNumDataCodewords();
+ }
+ byte[] resultBytes = new byte[totalBytes];
+ int resultOffset = 0;
+
+ // Error-correct and copy data blocks together into a stream of bytes
+ for (DataBlock dataBlock : dataBlocks) {
+ byte[] codewordBytes = dataBlock.getCodewords();
+ int numDataCodewords = dataBlock.getNumDataCodewords();
+ correctErrors(codewordBytes, numDataCodewords);
+ for (int i = 0; i < numDataCodewords; i++) {
+ resultBytes[resultOffset++] = codewordBytes[i];
+ }
+ }
+
+ // Decode the contents of that stream of bytes
+ return DecodedBitStreamParser.decode(resultBytes, version, ecLevel, hints);
+ }
+
+ /**
+ * Given data and error-correction codewords received, possibly corrupted by errors, attempts to
+ * correct the errors in-place using Reed-Solomon error correction.
+ *
+ * @param codewordBytes data and error correction codewords
+ * @param numDataCodewords number of codewords that are data bytes
+ * @throws ChecksumException if error correction fails
+ */
+ private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {
+ int numCodewords = codewordBytes.length;
+ // First read into an array of ints
+ int[] codewordsInts = new int[numCodewords];
+ for (int i = 0; i < numCodewords; i++) {
+ codewordsInts[i] = codewordBytes[i] & 0xFF;
+ }
+ int numECCodewords = codewordBytes.length - numDataCodewords;
+ try {
+ rsDecoder.decode(codewordsInts, numECCodewords);
+ } catch (ReedSolomonException ignored) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ // Copy back into array of bytes -- only need to worry about the bytes that were data
+ // We don't care about errors in the error-correction codewords
+ for (int i = 0; i < numDataCodewords; i++) {
+ codewordBytes[i] = (byte) codewordsInts[i];
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java
new file mode 100644
index 000000000..0f1e11387
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+/**
+ * See ISO 18004:2006, 6.5.1. This enum encapsulates the four error correction levels
+ * defined by the QR code standard.
+ *
+ * @author Sean Owen
+ */
+public enum ErrorCorrectionLevel {
+
+ /** L = ~7% correction */
+ L(0x01),
+ /** M = ~15% correction */
+ M(0x00),
+ /** Q = ~25% correction */
+ Q(0x03),
+ /** H = ~30% correction */
+ H(0x02);
+
+ private static final ErrorCorrectionLevel[] FOR_BITS = {M, L, H, Q};
+
+ private final int bits;
+
+ ErrorCorrectionLevel(int bits) {
+ this.bits = bits;
+ }
+
+ public int getBits() {
+ return bits;
+ }
+
+ /**
+ * @param bits int containing the two bits encoding a QR Code's error correction level
+ * @return ErrorCorrectionLevel representing the encoded error correction level
+ */
+ public static ErrorCorrectionLevel forBits(int bits) {
+ if (bits < 0 || bits >= FOR_BITS.length) {
+ throw new IllegalArgumentException();
+ }
+ return FOR_BITS[bits];
+ }
+
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/FormatInformation.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/FormatInformation.java
new file mode 100644
index 000000000..894e32175
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/FormatInformation.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+/**
+ * Encapsulates a QR Code's format information, including the data mask used and
+ * error correction level.
+ *
+ * @author Sean Owen
+ * @see DataMask
+ * @see ErrorCorrectionLevel
+ */
+final class FormatInformation {
+
+ private static final int FORMAT_INFO_MASK_QR = 0x5412;
+
+ /**
+ * See ISO 18004:2006, Annex C, Table C.1
+ */
+ private static final int[][] FORMAT_INFO_DECODE_LOOKUP = {
+ {0x5412, 0x00},
+ {0x5125, 0x01},
+ {0x5E7C, 0x02},
+ {0x5B4B, 0x03},
+ {0x45F9, 0x04},
+ {0x40CE, 0x05},
+ {0x4F97, 0x06},
+ {0x4AA0, 0x07},
+ {0x77C4, 0x08},
+ {0x72F3, 0x09},
+ {0x7DAA, 0x0A},
+ {0x789D, 0x0B},
+ {0x662F, 0x0C},
+ {0x6318, 0x0D},
+ {0x6C41, 0x0E},
+ {0x6976, 0x0F},
+ {0x1689, 0x10},
+ {0x13BE, 0x11},
+ {0x1CE7, 0x12},
+ {0x19D0, 0x13},
+ {0x0762, 0x14},
+ {0x0255, 0x15},
+ {0x0D0C, 0x16},
+ {0x083B, 0x17},
+ {0x355F, 0x18},
+ {0x3068, 0x19},
+ {0x3F31, 0x1A},
+ {0x3A06, 0x1B},
+ {0x24B4, 0x1C},
+ {0x2183, 0x1D},
+ {0x2EDA, 0x1E},
+ {0x2BED, 0x1F},
+ };
+
+ /**
+ * Offset i holds the number of 1 bits in the binary representation of i
+ */
+ private static final int[] BITS_SET_IN_HALF_BYTE =
+ {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
+
+ private final ErrorCorrectionLevel errorCorrectionLevel;
+ private final byte dataMask;
+
+ private FormatInformation(int formatInfo) {
+ // Bits 3,4
+ errorCorrectionLevel = ErrorCorrectionLevel.forBits((formatInfo >> 3) & 0x03);
+ // Bottom 3 bits
+ dataMask = (byte) (formatInfo & 0x07);
+ }
+
+ static int numBitsDiffering(int a, int b) {
+ a ^= b; // a now has a 1 bit exactly where its bit differs with b's
+ // Count bits set quickly with a series of lookups:
+ return BITS_SET_IN_HALF_BYTE[a & 0x0F] +
+ BITS_SET_IN_HALF_BYTE[(a >>> 4 & 0x0F)] +
+ BITS_SET_IN_HALF_BYTE[(a >>> 8 & 0x0F)] +
+ BITS_SET_IN_HALF_BYTE[(a >>> 12 & 0x0F)] +
+ BITS_SET_IN_HALF_BYTE[(a >>> 16 & 0x0F)] +
+ BITS_SET_IN_HALF_BYTE[(a >>> 20 & 0x0F)] +
+ BITS_SET_IN_HALF_BYTE[(a >>> 24 & 0x0F)] +
+ BITS_SET_IN_HALF_BYTE[(a >>> 28 & 0x0F)];
+ }
+
+ /**
+ * @param maskedFormatInfo1 format info indicator, with mask still applied
+ * @param maskedFormatInfo2 second copy of same info; both are checked at the same time
+ * to establish best match
+ * @return information about the format it specifies, or {@code null}
+ * if doesn't seem to match any known pattern
+ */
+ static FormatInformation decodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) {
+ FormatInformation formatInfo = doDecodeFormatInformation(maskedFormatInfo1, maskedFormatInfo2);
+ if (formatInfo != null) {
+ return formatInfo;
+ }
+ // Should return null, but, some QR codes apparently
+ // do not mask this info. Try again by actually masking the pattern
+ // first
+ return doDecodeFormatInformation(maskedFormatInfo1 ^ FORMAT_INFO_MASK_QR,
+ maskedFormatInfo2 ^ FORMAT_INFO_MASK_QR);
+ }
+
+ private static FormatInformation doDecodeFormatInformation(int maskedFormatInfo1, int maskedFormatInfo2) {
+ // Find the int in FORMAT_INFO_DECODE_LOOKUP with fewest bits differing
+ int bestDifference = Integer.MAX_VALUE;
+ int bestFormatInfo = 0;
+ for (int[] decodeInfo : FORMAT_INFO_DECODE_LOOKUP) {
+ int targetInfo = decodeInfo[0];
+ if (targetInfo == maskedFormatInfo1 || targetInfo == maskedFormatInfo2) {
+ // Found an exact match
+ return new FormatInformation(decodeInfo[1]);
+ }
+ int bitsDifference = numBitsDiffering(maskedFormatInfo1, targetInfo);
+ if (bitsDifference < bestDifference) {
+ bestFormatInfo = decodeInfo[1];
+ bestDifference = bitsDifference;
+ }
+ if (maskedFormatInfo1 != maskedFormatInfo2) {
+ // also try the other option
+ bitsDifference = numBitsDiffering(maskedFormatInfo2, targetInfo);
+ if (bitsDifference < bestDifference) {
+ bestFormatInfo = decodeInfo[1];
+ bestDifference = bitsDifference;
+ }
+ }
+ }
+ // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits
+ // differing means we found a match
+ if (bestDifference <= 3) {
+ return new FormatInformation(bestFormatInfo);
+ }
+ return null;
+ }
+
+ ErrorCorrectionLevel getErrorCorrectionLevel() {
+ return errorCorrectionLevel;
+ }
+
+ byte getDataMask() {
+ return dataMask;
+ }
+
+ @Override
+ public int hashCode() {
+ return (errorCorrectionLevel.ordinal() << 3) | (int) dataMask;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof FormatInformation)) {
+ return false;
+ }
+ FormatInformation other = (FormatInformation) o;
+ return this.errorCorrectionLevel == other.errorCorrectionLevel &&
+ this.dataMask == other.dataMask;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Mode.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Mode.java
new file mode 100644
index 000000000..b7e9ab3a9
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Mode.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+/**
+ * See ISO 18004:2006, 6.4.1, Tables 2 and 3. This enum encapsulates the various modes in which
+ * data can be encoded to bits in the QR code standard.
+ *
+ * @author Sean Owen
+ */
+public enum Mode {
+
+ TERMINATOR(new int[]{0, 0, 0}, 0x00), // Not really a mode...
+ NUMERIC(new int[]{10, 12, 14}, 0x01),
+ ALPHANUMERIC(new int[]{9, 11, 13}, 0x02),
+ STRUCTURED_APPEND(new int[]{0, 0, 0}, 0x03), // Not supported
+ BYTE(new int[]{8, 16, 16}, 0x04),
+ ECI(new int[]{0, 0, 0}, 0x07), // character counts don't apply
+ KANJI(new int[]{8, 10, 12}, 0x08),
+ FNC1_FIRST_POSITION(new int[]{0, 0, 0}, 0x05),
+ FNC1_SECOND_POSITION(new int[]{0, 0, 0}, 0x09),
+ /** See GBT 18284-2000; "Hanzi" is a transliteration of this mode name. */
+ HANZI(new int[]{8, 10, 12}, 0x0D);
+
+ private final int[] characterCountBitsForVersions;
+ private final int bits;
+
+ Mode(int[] characterCountBitsForVersions, int bits) {
+ this.characterCountBitsForVersions = characterCountBitsForVersions;
+ this.bits = bits;
+ }
+
+ /**
+ * @param bits four bits encoding a QR Code data mode
+ * @return Mode encoded by these bits
+ * @throws IllegalArgumentException if bits do not correspond to a known mode
+ */
+ public static Mode forBits(int bits) {
+ switch (bits) {
+ case 0x0:
+ return TERMINATOR;
+ case 0x1:
+ return NUMERIC;
+ case 0x2:
+ return ALPHANUMERIC;
+ case 0x3:
+ return STRUCTURED_APPEND;
+ case 0x4:
+ return BYTE;
+ case 0x5:
+ return FNC1_FIRST_POSITION;
+ case 0x7:
+ return ECI;
+ case 0x8:
+ return KANJI;
+ case 0x9:
+ return FNC1_SECOND_POSITION;
+ case 0xD:
+ // 0xD is defined in GBT 18284-2000, may not be supported in foreign country
+ return HANZI;
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ /**
+ * @param version version in question
+ * @return number of bits used, in this QR Code symbol {@link Version}, to encode the
+ * count of characters that will follow encoded in this Mode
+ */
+ public int getCharacterCountBits(Version version) {
+ int number = version.getVersionNumber();
+ int offset;
+ if (number <= 9) {
+ offset = 0;
+ } else if (number <= 26) {
+ offset = 1;
+ } else {
+ offset = 2;
+ }
+ return characterCountBitsForVersions[offset];
+ }
+
+ public int getBits() {
+ return bits;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java
new file mode 100644
index 000000000..75821e300
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/QRCodeDecoderMetaData.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2013 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.qrcode.decoder;
+
+import com.google.zxing.ResultPoint;
+
+/**
+ * Meta-data container for QR Code decoding. Instances of this class may be used to convey information back to the
+ * decoding caller. Callers are expected to process this.
+ *
+ * @see com.google.zxing.common.DecoderResult#getOther()
+ */
+public final class QRCodeDecoderMetaData {
+
+ private final boolean mirrored;
+
+ QRCodeDecoderMetaData(boolean mirrored) {
+ this.mirrored = mirrored;
+ }
+
+ /**
+ * @return true if the QR Code was mirrored.
+ */
+ public boolean isMirrored() {
+ return mirrored;
+ }
+
+ /**
+ * Apply the result points' order correction due to mirroring.
+ *
+ * @param points Array of points to apply mirror correction to.
+ */
+ public void applyMirroredCorrection(ResultPoint[] points) {
+ if (!mirrored || points == null || points.length < 3) {
+ return;
+ }
+ ResultPoint bottomLeft = points[0];
+ points[0] = points[2];
+ points[2] = bottomLeft;
+ // No need to 'fix' top-left and alignment pattern.
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Version.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Version.java
new file mode 100755
index 000000000..b649e207e
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/decoder/Version.java
@@ -0,0 +1,578 @@
+/*
+ * Copyright 2007 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.qrcode.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * See ISO 18004:2006 Annex D
+ *
+ * @author Sean Owen
+ */
+public final class Version {
+
+ /**
+ * See ISO 18004:2006 Annex D.
+ * Element i represents the raw version bits that specify version i + 7
+ */
+ private static final int[] VERSION_DECODE_INFO = {
+ 0x07C94, 0x085BC, 0x09A99, 0x0A4D3, 0x0BBF6,
+ 0x0C762, 0x0D847, 0x0E60D, 0x0F928, 0x10B78,
+ 0x1145D, 0x12A17, 0x13532, 0x149A6, 0x15683,
+ 0x168C9, 0x177EC, 0x18EC4, 0x191E1, 0x1AFAB,
+ 0x1B08E, 0x1CC1A, 0x1D33F, 0x1ED75, 0x1F250,
+ 0x209D5, 0x216F0, 0x228BA, 0x2379F, 0x24B0B,
+ 0x2542E, 0x26A64, 0x27541, 0x28C69
+ };
+
+ private static final Version[] VERSIONS = buildVersions();
+
+ private final int versionNumber;
+ private final int[] alignmentPatternCenters;
+ private final ECBlocks[] ecBlocks;
+ private final int totalCodewords;
+
+ private Version(int versionNumber,
+ int[] alignmentPatternCenters,
+ ECBlocks... ecBlocks) {
+ this.versionNumber = versionNumber;
+ this.alignmentPatternCenters = alignmentPatternCenters;
+ this.ecBlocks = ecBlocks;
+ int total = 0;
+ int ecCodewords = ecBlocks[0].getECCodewordsPerBlock();
+ ECB[] ecbArray = ecBlocks[0].getECBlocks();
+ for (ECB ecBlock : ecbArray) {
+ total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords);
+ }
+ this.totalCodewords = total;
+ }
+
+ public int getVersionNumber() {
+ return versionNumber;
+ }
+
+ public int[] getAlignmentPatternCenters() {
+ return alignmentPatternCenters;
+ }
+
+ public int getTotalCodewords() {
+ return totalCodewords;
+ }
+
+ public int getDimensionForVersion() {
+ return 17 + 4 * versionNumber;
+ }
+
+ public ECBlocks getECBlocksForLevel(ErrorCorrectionLevel ecLevel) {
+ return ecBlocks[ecLevel.ordinal()];
+ }
+
+ /**
+ * Deduces version information purely from QR Code dimensions.
+ *
+ * @param dimension dimension in modules
+ * @return Version for a QR Code of that dimension
+ * @throws FormatException if dimension is not 1 mod 4
+ */
+ public static Version getProvisionalVersionForDimension(int dimension) throws FormatException {
+ if (dimension % 4 != 1) {
+ throw FormatException.getFormatInstance();
+ }
+ try {
+ return getVersionForNumber((dimension - 17) >> 2);
+ } catch (IllegalArgumentException ignored) {
+ throw FormatException.getFormatInstance();
+ }
+ }
+
+ public static Version getVersionForNumber(int versionNumber) {
+ if (versionNumber < 1 || versionNumber > 40) {
+ throw new IllegalArgumentException();
+ }
+ return VERSIONS[versionNumber - 1];
+ }
+
+ static Version decodeVersionInformation(int versionBits) {
+ int bestDifference = Integer.MAX_VALUE;
+ int bestVersion = 0;
+ for (int i = 0; i < VERSION_DECODE_INFO.length; i++) {
+ int targetVersion = VERSION_DECODE_INFO[i];
+ // Do the version info bits match exactly? done.
+ if (targetVersion == versionBits) {
+ return getVersionForNumber(i + 7);
+ }
+ // Otherwise see if this is the closest to a real version info bit string
+ // we have seen so far
+ int bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion);
+ if (bitsDifference < bestDifference) {
+ bestVersion = i + 7;
+ bestDifference = bitsDifference;
+ }
+ }
+ // We can tolerate up to 3 bits of error since no two version info codewords will
+ // differ in less than 8 bits.
+ if (bestDifference <= 3) {
+ return getVersionForNumber(bestVersion);
+ }
+ // If we didn't find a close enough match, fail
+ return null;
+ }
+
+ /**
+ * See ISO 18004:2006 Annex E
+ */
+ BitMatrix buildFunctionPattern() {
+ int dimension = getDimensionForVersion();
+ BitMatrix bitMatrix = new BitMatrix(dimension);
+
+ // Top left finder pattern + separator + format
+ bitMatrix.setRegion(0, 0, 9, 9);
+ // Top right finder pattern + separator + format
+ bitMatrix.setRegion(dimension - 8, 0, 8, 9);
+ // Bottom left finder pattern + separator + format
+ bitMatrix.setRegion(0, dimension - 8, 9, 8);
+
+ // Alignment patterns
+ int max = alignmentPatternCenters.length;
+ for (int x = 0; x < max; x++) {
+ int i = alignmentPatternCenters[x] - 2;
+ for (int y = 0; y < max; y++) {
+ if ((x == 0 && (y == 0 || y == max - 1)) || (x == max - 1 && y == 0)) {
+ // No alignment patterns near the three finder paterns
+ continue;
+ }
+ bitMatrix.setRegion(alignmentPatternCenters[y] - 2, i, 5, 5);
+ }
+ }
+
+ // Vertical timing pattern
+ bitMatrix.setRegion(6, 9, 1, dimension - 17);
+ // Horizontal timing pattern
+ bitMatrix.setRegion(9, 6, dimension - 17, 1);
+
+ if (versionNumber > 6) {
+ // Version info, top right
+ bitMatrix.setRegion(dimension - 11, 0, 3, 6);
+ // Version info, bottom left
+ bitMatrix.setRegion(0, dimension - 11, 6, 3);
+ }
+
+ return bitMatrix;
+ }
+
+ /**
+ * Encapsulates a set of error-correction blocks in one symbol version. Most versions will
+ * use blocks of differing sizes within one version, so, this encapsulates the parameters for
+ * each set of blocks. It also holds the number of error-correction codewords per block since it
+ * will be the same across all blocks within one version.
+ */
+ public static final class ECBlocks {
+ private final int ecCodewordsPerBlock;
+ private final ECB[] ecBlocks;
+
+ ECBlocks(int ecCodewordsPerBlock, ECB... ecBlocks) {
+ this.ecCodewordsPerBlock = ecCodewordsPerBlock;
+ this.ecBlocks = ecBlocks;
+ }
+
+ public int getECCodewordsPerBlock() {
+ return ecCodewordsPerBlock;
+ }
+
+ public int getNumBlocks() {
+ int total = 0;
+ for (ECB ecBlock : ecBlocks) {
+ total += ecBlock.getCount();
+ }
+ return total;
+ }
+
+ public int getTotalECCodewords() {
+ return ecCodewordsPerBlock * getNumBlocks();
+ }
+
+ public ECB[] getECBlocks() {
+ return ecBlocks;
+ }
+ }
+
+ /**
+ * Encapsualtes the parameters for one error-correction block in one symbol version.
+ * This includes the number of data codewords, and the number of times a block with these
+ * parameters is used consecutively in the QR code version's format.
+ */
+ public static final class ECB {
+ private final int count;
+ private final int dataCodewords;
+
+ ECB(int count, int dataCodewords) {
+ this.count = count;
+ this.dataCodewords = dataCodewords;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public int getDataCodewords() {
+ return dataCodewords;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(versionNumber);
+ }
+
+ /**
+ * See ISO 18004:2006 6.5.1 Table 9
+ */
+ private static Version[] buildVersions() {
+ return new Version[]{
+ new Version(1, new int[]{},
+ new ECBlocks(7, new ECB(1, 19)),
+ new ECBlocks(10, new ECB(1, 16)),
+ new ECBlocks(13, new ECB(1, 13)),
+ new ECBlocks(17, new ECB(1, 9))),
+ new Version(2, new int[]{6, 18},
+ new ECBlocks(10, new ECB(1, 34)),
+ new ECBlocks(16, new ECB(1, 28)),
+ new ECBlocks(22, new ECB(1, 22)),
+ new ECBlocks(28, new ECB(1, 16))),
+ new Version(3, new int[]{6, 22},
+ new ECBlocks(15, new ECB(1, 55)),
+ new ECBlocks(26, new ECB(1, 44)),
+ new ECBlocks(18, new ECB(2, 17)),
+ new ECBlocks(22, new ECB(2, 13))),
+ new Version(4, new int[]{6, 26},
+ new ECBlocks(20, new ECB(1, 80)),
+ new ECBlocks(18, new ECB(2, 32)),
+ new ECBlocks(26, new ECB(2, 24)),
+ new ECBlocks(16, new ECB(4, 9))),
+ new Version(5, new int[]{6, 30},
+ new ECBlocks(26, new ECB(1, 108)),
+ new ECBlocks(24, new ECB(2, 43)),
+ new ECBlocks(18, new ECB(2, 15),
+ new ECB(2, 16)),
+ new ECBlocks(22, new ECB(2, 11),
+ new ECB(2, 12))),
+ new Version(6, new int[]{6, 34},
+ new ECBlocks(18, new ECB(2, 68)),
+ new ECBlocks(16, new ECB(4, 27)),
+ new ECBlocks(24, new ECB(4, 19)),
+ new ECBlocks(28, new ECB(4, 15))),
+ new Version(7, new int[]{6, 22, 38},
+ new ECBlocks(20, new ECB(2, 78)),
+ new ECBlocks(18, new ECB(4, 31)),
+ new ECBlocks(18, new ECB(2, 14),
+ new ECB(4, 15)),
+ new ECBlocks(26, new ECB(4, 13),
+ new ECB(1, 14))),
+ new Version(8, new int[]{6, 24, 42},
+ new ECBlocks(24, new ECB(2, 97)),
+ new ECBlocks(22, new ECB(2, 38),
+ new ECB(2, 39)),
+ new ECBlocks(22, new ECB(4, 18),
+ new ECB(2, 19)),
+ new ECBlocks(26, new ECB(4, 14),
+ new ECB(2, 15))),
+ new Version(9, new int[]{6, 26, 46},
+ new ECBlocks(30, new ECB(2, 116)),
+ new ECBlocks(22, new ECB(3, 36),
+ new ECB(2, 37)),
+ new ECBlocks(20, new ECB(4, 16),
+ new ECB(4, 17)),
+ new ECBlocks(24, new ECB(4, 12),
+ new ECB(4, 13))),
+ new Version(10, new int[]{6, 28, 50},
+ new ECBlocks(18, new ECB(2, 68),
+ new ECB(2, 69)),
+ new ECBlocks(26, new ECB(4, 43),
+ new ECB(1, 44)),
+ new ECBlocks(24, new ECB(6, 19),
+ new ECB(2, 20)),
+ new ECBlocks(28, new ECB(6, 15),
+ new ECB(2, 16))),
+ new Version(11, new int[]{6, 30, 54},
+ new ECBlocks(20, new ECB(4, 81)),
+ new ECBlocks(30, new ECB(1, 50),
+ new ECB(4, 51)),
+ new ECBlocks(28, new ECB(4, 22),
+ new ECB(4, 23)),
+ new ECBlocks(24, new ECB(3, 12),
+ new ECB(8, 13))),
+ new Version(12, new int[]{6, 32, 58},
+ new ECBlocks(24, new ECB(2, 92),
+ new ECB(2, 93)),
+ new ECBlocks(22, new ECB(6, 36),
+ new ECB(2, 37)),
+ new ECBlocks(26, new ECB(4, 20),
+ new ECB(6, 21)),
+ new ECBlocks(28, new ECB(7, 14),
+ new ECB(4, 15))),
+ new Version(13, new int[]{6, 34, 62},
+ new ECBlocks(26, new ECB(4, 107)),
+ new ECBlocks(22, new ECB(8, 37),
+ new ECB(1, 38)),
+ new ECBlocks(24, new ECB(8, 20),
+ new ECB(4, 21)),
+ new ECBlocks(22, new ECB(12, 11),
+ new ECB(4, 12))),
+ new Version(14, new int[]{6, 26, 46, 66},
+ new ECBlocks(30, new ECB(3, 115),
+ new ECB(1, 116)),
+ new ECBlocks(24, new ECB(4, 40),
+ new ECB(5, 41)),
+ new ECBlocks(20, new ECB(11, 16),
+ new ECB(5, 17)),
+ new ECBlocks(24, new ECB(11, 12),
+ new ECB(5, 13))),
+ new Version(15, new int[]{6, 26, 48, 70},
+ new ECBlocks(22, new ECB(5, 87),
+ new ECB(1, 88)),
+ new ECBlocks(24, new ECB(5, 41),
+ new ECB(5, 42)),
+ new ECBlocks(30, new ECB(5, 24),
+ new ECB(7, 25)),
+ new ECBlocks(24, new ECB(11, 12),
+ new ECB(7, 13))),
+ new Version(16, new int[]{6, 26, 50, 74},
+ new ECBlocks(24, new ECB(5, 98),
+ new ECB(1, 99)),
+ new ECBlocks(28, new ECB(7, 45),
+ new ECB(3, 46)),
+ new ECBlocks(24, new ECB(15, 19),
+ new ECB(2, 20)),
+ new ECBlocks(30, new ECB(3, 15),
+ new ECB(13, 16))),
+ new Version(17, new int[]{6, 30, 54, 78},
+ new ECBlocks(28, new ECB(1, 107),
+ new ECB(5, 108)),
+ new ECBlocks(28, new ECB(10, 46),
+ new ECB(1, 47)),
+ new ECBlocks(28, new ECB(1, 22),
+ new ECB(15, 23)),
+ new ECBlocks(28, new ECB(2, 14),
+ new ECB(17, 15))),
+ new Version(18, new int[]{6, 30, 56, 82},
+ new ECBlocks(30, new ECB(5, 120),
+ new ECB(1, 121)),
+ new ECBlocks(26, new ECB(9, 43),
+ new ECB(4, 44)),
+ new ECBlocks(28, new ECB(17, 22),
+ new ECB(1, 23)),
+ new ECBlocks(28, new ECB(2, 14),
+ new ECB(19, 15))),
+ new Version(19, new int[]{6, 30, 58, 86},
+ new ECBlocks(28, new ECB(3, 113),
+ new ECB(4, 114)),
+ new ECBlocks(26, new ECB(3, 44),
+ new ECB(11, 45)),
+ new ECBlocks(26, new ECB(17, 21),
+ new ECB(4, 22)),
+ new ECBlocks(26, new ECB(9, 13),
+ new ECB(16, 14))),
+ new Version(20, new int[]{6, 34, 62, 90},
+ new ECBlocks(28, new ECB(3, 107),
+ new ECB(5, 108)),
+ new ECBlocks(26, new ECB(3, 41),
+ new ECB(13, 42)),
+ new ECBlocks(30, new ECB(15, 24),
+ new ECB(5, 25)),
+ new ECBlocks(28, new ECB(15, 15),
+ new ECB(10, 16))),
+ new Version(21, new int[]{6, 28, 50, 72, 94},
+ new ECBlocks(28, new ECB(4, 116),
+ new ECB(4, 117)),
+ new ECBlocks(26, new ECB(17, 42)),
+ new ECBlocks(28, new ECB(17, 22),
+ new ECB(6, 23)),
+ new ECBlocks(30, new ECB(19, 16),
+ new ECB(6, 17))),
+ new Version(22, new int[]{6, 26, 50, 74, 98},
+ new ECBlocks(28, new ECB(2, 111),
+ new ECB(7, 112)),
+ new ECBlocks(28, new ECB(17, 46)),
+ new ECBlocks(30, new ECB(7, 24),
+ new ECB(16, 25)),
+ new ECBlocks(24, new ECB(34, 13))),
+ new Version(23, new int[]{6, 30, 54, 78, 102},
+ new ECBlocks(30, new ECB(4, 121),
+ new ECB(5, 122)),
+ new ECBlocks(28, new ECB(4, 47),
+ new ECB(14, 48)),
+ new ECBlocks(30, new ECB(11, 24),
+ new ECB(14, 25)),
+ new ECBlocks(30, new ECB(16, 15),
+ new ECB(14, 16))),
+ new Version(24, new int[]{6, 28, 54, 80, 106},
+ new ECBlocks(30, new ECB(6, 117),
+ new ECB(4, 118)),
+ new ECBlocks(28, new ECB(6, 45),
+ new ECB(14, 46)),
+ new ECBlocks(30, new ECB(11, 24),
+ new ECB(16, 25)),
+ new ECBlocks(30, new ECB(30, 16),
+ new ECB(2, 17))),
+ new Version(25, new int[]{6, 32, 58, 84, 110},
+ new ECBlocks(26, new ECB(8, 106),
+ new ECB(4, 107)),
+ new ECBlocks(28, new ECB(8, 47),
+ new ECB(13, 48)),
+ new ECBlocks(30, new ECB(7, 24),
+ new ECB(22, 25)),
+ new ECBlocks(30, new ECB(22, 15),
+ new ECB(13, 16))),
+ new Version(26, new int[]{6, 30, 58, 86, 114},
+ new ECBlocks(28, new ECB(10, 114),
+ new ECB(2, 115)),
+ new ECBlocks(28, new ECB(19, 46),
+ new ECB(4, 47)),
+ new ECBlocks(28, new ECB(28, 22),
+ new ECB(6, 23)),
+ new ECBlocks(30, new ECB(33, 16),
+ new ECB(4, 17))),
+ new Version(27, new int[]{6, 34, 62, 90, 118},
+ new ECBlocks(30, new ECB(8, 122),
+ new ECB(4, 123)),
+ new ECBlocks(28, new ECB(22, 45),
+ new ECB(3, 46)),
+ new ECBlocks(30, new ECB(8, 23),
+ new ECB(26, 24)),
+ new ECBlocks(30, new ECB(12, 15),
+ new ECB(28, 16))),
+ new Version(28, new int[]{6, 26, 50, 74, 98, 122},
+ new ECBlocks(30, new ECB(3, 117),
+ new ECB(10, 118)),
+ new ECBlocks(28, new ECB(3, 45),
+ new ECB(23, 46)),
+ new ECBlocks(30, new ECB(4, 24),
+ new ECB(31, 25)),
+ new ECBlocks(30, new ECB(11, 15),
+ new ECB(31, 16))),
+ new Version(29, new int[]{6, 30, 54, 78, 102, 126},
+ new ECBlocks(30, new ECB(7, 116),
+ new ECB(7, 117)),
+ new ECBlocks(28, new ECB(21, 45),
+ new ECB(7, 46)),
+ new ECBlocks(30, new ECB(1, 23),
+ new ECB(37, 24)),
+ new ECBlocks(30, new ECB(19, 15),
+ new ECB(26, 16))),
+ new Version(30, new int[]{6, 26, 52, 78, 104, 130},
+ new ECBlocks(30, new ECB(5, 115),
+ new ECB(10, 116)),
+ new ECBlocks(28, new ECB(19, 47),
+ new ECB(10, 48)),
+ new ECBlocks(30, new ECB(15, 24),
+ new ECB(25, 25)),
+ new ECBlocks(30, new ECB(23, 15),
+ new ECB(25, 16))),
+ new Version(31, new int[]{6, 30, 56, 82, 108, 134},
+ new ECBlocks(30, new ECB(13, 115),
+ new ECB(3, 116)),
+ new ECBlocks(28, new ECB(2, 46),
+ new ECB(29, 47)),
+ new ECBlocks(30, new ECB(42, 24),
+ new ECB(1, 25)),
+ new ECBlocks(30, new ECB(23, 15),
+ new ECB(28, 16))),
+ new Version(32, new int[]{6, 34, 60, 86, 112, 138},
+ new ECBlocks(30, new ECB(17, 115)),
+ new ECBlocks(28, new ECB(10, 46),
+ new ECB(23, 47)),
+ new ECBlocks(30, new ECB(10, 24),
+ new ECB(35, 25)),
+ new ECBlocks(30, new ECB(19, 15),
+ new ECB(35, 16))),
+ new Version(33, new int[]{6, 30, 58, 86, 114, 142},
+ new ECBlocks(30, new ECB(17, 115),
+ new ECB(1, 116)),
+ new ECBlocks(28, new ECB(14, 46),
+ new ECB(21, 47)),
+ new ECBlocks(30, new ECB(29, 24),
+ new ECB(19, 25)),
+ new ECBlocks(30, new ECB(11, 15),
+ new ECB(46, 16))),
+ new Version(34, new int[]{6, 34, 62, 90, 118, 146},
+ new ECBlocks(30, new ECB(13, 115),
+ new ECB(6, 116)),
+ new ECBlocks(28, new ECB(14, 46),
+ new ECB(23, 47)),
+ new ECBlocks(30, new ECB(44, 24),
+ new ECB(7, 25)),
+ new ECBlocks(30, new ECB(59, 16),
+ new ECB(1, 17))),
+ new Version(35, new int[]{6, 30, 54, 78, 102, 126, 150},
+ new ECBlocks(30, new ECB(12, 121),
+ new ECB(7, 122)),
+ new ECBlocks(28, new ECB(12, 47),
+ new ECB(26, 48)),
+ new ECBlocks(30, new ECB(39, 24),
+ new ECB(14, 25)),
+ new ECBlocks(30, new ECB(22, 15),
+ new ECB(41, 16))),
+ new Version(36, new int[]{6, 24, 50, 76, 102, 128, 154},
+ new ECBlocks(30, new ECB(6, 121),
+ new ECB(14, 122)),
+ new ECBlocks(28, new ECB(6, 47),
+ new ECB(34, 48)),
+ new ECBlocks(30, new ECB(46, 24),
+ new ECB(10, 25)),
+ new ECBlocks(30, new ECB(2, 15),
+ new ECB(64, 16))),
+ new Version(37, new int[]{6, 28, 54, 80, 106, 132, 158},
+ new ECBlocks(30, new ECB(17, 122),
+ new ECB(4, 123)),
+ new ECBlocks(28, new ECB(29, 46),
+ new ECB(14, 47)),
+ new ECBlocks(30, new ECB(49, 24),
+ new ECB(10, 25)),
+ new ECBlocks(30, new ECB(24, 15),
+ new ECB(46, 16))),
+ new Version(38, new int[]{6, 32, 58, 84, 110, 136, 162},
+ new ECBlocks(30, new ECB(4, 122),
+ new ECB(18, 123)),
+ new ECBlocks(28, new ECB(13, 46),
+ new ECB(32, 47)),
+ new ECBlocks(30, new ECB(48, 24),
+ new ECB(14, 25)),
+ new ECBlocks(30, new ECB(42, 15),
+ new ECB(32, 16))),
+ new Version(39, new int[]{6, 26, 54, 82, 110, 138, 166},
+ new ECBlocks(30, new ECB(20, 117),
+ new ECB(4, 118)),
+ new ECBlocks(28, new ECB(40, 47),
+ new ECB(7, 48)),
+ new ECBlocks(30, new ECB(43, 24),
+ new ECB(22, 25)),
+ new ECBlocks(30, new ECB(10, 15),
+ new ECB(67, 16))),
+ new Version(40, new int[]{6, 30, 58, 86, 114, 142, 170},
+ new ECBlocks(30, new ECB(19, 118),
+ new ECB(6, 119)),
+ new ECBlocks(28, new ECB(18, 47),
+ new ECB(31, 48)),
+ new ECBlocks(30, new ECB(34, 24),
+ new ECB(34, 25)),
+ new ECBlocks(30, new ECB(20, 15),
+ new ECB(61, 16)))
+ };
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/AlignmentPattern.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/AlignmentPattern.java
new file mode 100644
index 000000000..96d919422
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/AlignmentPattern.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import com.google.zxing.ResultPoint;
+
+/**
+ * Encapsulates an alignment pattern, which are the smaller square patterns found in
+ * all but the simplest QR Codes.
+ *
+ * @author Sean Owen
+ */
+public final class AlignmentPattern extends ResultPoint {
+
+ private final float estimatedModuleSize;
+
+ AlignmentPattern(float posX, float posY, float estimatedModuleSize) {
+ super(posX, posY);
+ this.estimatedModuleSize = estimatedModuleSize;
+ }
+
+ /**
+ * Determines if this alignment pattern "about equals" an alignment pattern at the stated
+ * position and size -- meaning, it is at nearly the same center with nearly the same size.
+ */
+ boolean aboutEquals(float moduleSize, float i, float j) {
+ if (Math.abs(i - getY()) <= moduleSize && Math.abs(j - getX()) <= moduleSize) {
+ float moduleSizeDiff = Math.abs(moduleSize - estimatedModuleSize);
+ return moduleSizeDiff <= 1.0f || moduleSizeDiff <= estimatedModuleSize;
+ }
+ return false;
+ }
+
+ /**
+ * Combines this object's current estimate of a finder pattern position and module size
+ * with a new estimate. It returns a new {@code FinderPattern} containing an average of the two.
+ */
+ AlignmentPattern combineEstimate(float i, float j, float newModuleSize) {
+ float combinedX = (getX() + j) / 2.0f;
+ float combinedY = (getY() + i) / 2.0f;
+ float combinedModuleSize = (estimatedModuleSize + newModuleSize) / 2.0f;
+ return new AlignmentPattern(combinedX, combinedY, combinedModuleSize);
+ }
+
+}
\ No newline at end of file
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java
new file mode 100644
index 000000000..95fbc2ecb
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder
+ * patterns but are smaller and appear at regular intervals throughout the image.
+ *
+ * At the moment this only looks for the bottom-right alignment pattern.
+ *
+ * This is mostly a simplified copy of {@link FinderPatternFinder}. It is copied,
+ * pasted and stripped down here for maximum performance but does unfortunately duplicate
+ * some code.
+ *
+ * This class is thread-safe but not reentrant. Each thread must allocate its own object.
+ *
+ * @author Sean Owen
+ */
+final class AlignmentPatternFinder {
+
+ private final BitMatrix image;
+ private final List possibleCenters;
+ private final int startX;
+ private final int startY;
+ private final int width;
+ private final int height;
+ private final float moduleSize;
+ private final int[] crossCheckStateCount;
+ private final ResultPointCallback resultPointCallback;
+
+ /**
+ * Creates a finder that will look in a portion of the whole image.
+ *
+ * @param image image to search
+ * @param startX left column from which to start searching
+ * @param startY top row from which to start searching
+ * @param width width of region to search
+ * @param height height of region to search
+ * @param moduleSize estimated module size so far
+ */
+ AlignmentPatternFinder(BitMatrix image,
+ int startX,
+ int startY,
+ int width,
+ int height,
+ float moduleSize,
+ ResultPointCallback resultPointCallback) {
+ this.image = image;
+ this.possibleCenters = new ArrayList<>(5);
+ this.startX = startX;
+ this.startY = startY;
+ this.width = width;
+ this.height = height;
+ this.moduleSize = moduleSize;
+ this.crossCheckStateCount = new int[3];
+ this.resultPointCallback = resultPointCallback;
+ }
+
+ /**
+ * This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since
+ * it's pretty performance-critical and so is written to be fast foremost.
+ *
+ * @return {@link AlignmentPattern} if found
+ * @throws NotFoundException if not found
+ */
+ AlignmentPattern find() throws NotFoundException {
+ int startX = this.startX;
+ int height = this.height;
+ int maxJ = startX + width;
+ int middleI = startY + (height >> 1);
+ // We are looking for black/white/black modules in 1:1:1 ratio;
+ // this tracks the number of black/white/black modules seen so far
+ int[] stateCount = new int[3];
+ for (int iGen = 0; iGen < height; iGen++) {
+ // Search from middle outwards
+ int i = middleI + ((iGen & 0x01) == 0 ? (iGen + 1) >> 1 : -((iGen + 1) >> 1));
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ int j = startX;
+ // Burn off leading white pixels before anything else; if we start in the middle of
+ // a white run, it doesn't make sense to count its length, since we don't know if the
+ // white run continued to the left of the start point
+ while (j < maxJ && !image.get(j, i)) {
+ j++;
+ }
+ int currentState = 0;
+ while (j < maxJ) {
+ if (image.get(j, i)) {
+ // Black pixel
+ if (currentState == 1) { // Counting black pixels
+ stateCount[currentState]++;
+ } else { // Counting white pixels
+ if (currentState == 2) { // A winner?
+ if (foundPatternCross(stateCount)) { // Yes
+ AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, j);
+ if (confirmed != null) {
+ return confirmed;
+ }
+ }
+ stateCount[0] = stateCount[2];
+ stateCount[1] = 1;
+ stateCount[2] = 0;
+ currentState = 1;
+ } else {
+ stateCount[++currentState]++;
+ }
+ }
+ } else { // White pixel
+ if (currentState == 1) { // Counting black pixels
+ currentState++;
+ }
+ stateCount[currentState]++;
+ }
+ j++;
+ }
+ if (foundPatternCross(stateCount)) {
+ AlignmentPattern confirmed = handlePossibleCenter(stateCount, i, maxJ);
+ if (confirmed != null) {
+ return confirmed;
+ }
+ }
+
+ }
+
+ // Hmm, nothing we saw was observed and confirmed twice. If we had
+ // any guess at all, return it.
+ if (!possibleCenters.isEmpty()) {
+ return possibleCenters.get(0);
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /**
+ * Given a count of black/white/black pixels just seen and an end position,
+ * figures the location of the center of this black/white/black run.
+ */
+ private static float centerFromEnd(int[] stateCount, int end) {
+ return (float) (end - stateCount[2]) - stateCount[1] / 2.0f;
+ }
+
+ /**
+ * @param stateCount count of black/white/black pixels just read
+ * @return true iff the proportions of the counts is close enough to the 1/1/1 ratios
+ * used by alignment patterns to be considered a match
+ */
+ private boolean foundPatternCross(int[] stateCount) {
+ float moduleSize = this.moduleSize;
+ float maxVariance = moduleSize / 2.0f;
+ for (int i = 0; i < 3; i++) {
+ if (Math.abs(moduleSize - stateCount[i]) >= maxVariance) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * After a horizontal scan finds a potential alignment pattern, this method
+ * "cross-checks" by scanning down vertically through the center of the possible
+ * alignment pattern to see if the same proportion is detected.
+ *
+ * @param startI row where an alignment pattern was detected
+ * @param centerJ center of the section that appears to cross an alignment pattern
+ * @param maxCount maximum reasonable number of modules that should be
+ * observed in any reading state, based on the results of the horizontal scan
+ * @return vertical center of alignment pattern, or {@link Float#NaN} if not found
+ */
+ private float crossCheckVertical(int startI, int centerJ, int maxCount,
+ int originalStateCountTotal) {
+ BitMatrix image = this.image;
+
+ int maxI = image.getHeight();
+ int[] stateCount = crossCheckStateCount;
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+
+ // Start counting up from center
+ int i = startI;
+ while (i >= 0 && image.get(centerJ, i) && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i--;
+ }
+ // If already too many modules in this state or ran off the edge:
+ if (i < 0 || stateCount[1] > maxCount) {
+ return Float.NaN;
+ }
+ while (i >= 0 && !image.get(centerJ, i) && stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ i--;
+ }
+ if (stateCount[0] > maxCount) {
+ return Float.NaN;
+ }
+
+ // Now also count down from center
+ i = startI + 1;
+ while (i < maxI && image.get(centerJ, i) && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i++;
+ }
+ if (i == maxI || stateCount[1] > maxCount) {
+ return Float.NaN;
+ }
+ while (i < maxI && !image.get(centerJ, i) && stateCount[2] <= maxCount) {
+ stateCount[2]++;
+ i++;
+ }
+ if (stateCount[2] > maxCount) {
+ return Float.NaN;
+ }
+
+ int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
+ if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
+ return Float.NaN;
+ }
+
+ return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;
+ }
+
+ /**
+ * This is called when a horizontal scan finds a possible alignment pattern. It will
+ * cross check with a vertical scan, and if successful, will see if this pattern had been
+ * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have
+ * found the alignment pattern.
+ *
+ * @param stateCount reading state module counts from horizontal scan
+ * @param i row where alignment pattern may be found
+ * @param j end of possible alignment pattern in row
+ * @return {@link AlignmentPattern} if we have found the same pattern twice, or null if not
+ */
+ private AlignmentPattern handlePossibleCenter(int[] stateCount, int i, int j) {
+ int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
+ float centerJ = centerFromEnd(stateCount, j);
+ float centerI = crossCheckVertical(i, (int) centerJ, 2 * stateCount[1], stateCountTotal);
+ if (!Float.isNaN(centerI)) {
+ float estimatedModuleSize = (float) (stateCount[0] + stateCount[1] + stateCount[2]) / 3.0f;
+ for (AlignmentPattern center : possibleCenters) {
+ // Look for about the same center and module size:
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ return center.combineEstimate(centerI, centerJ, estimatedModuleSize);
+ }
+ }
+ // Hadn't found this before; save it
+ AlignmentPattern point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+ possibleCenters.add(point);
+ if (resultPointCallback != null) {
+ resultPointCallback.foundPossibleResultPoint(point);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/Detector.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/Detector.java
new file mode 100644
index 000000000..beae1a6a0
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/Detector.java
@@ -0,0 +1,405 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.common.GridSampler;
+import com.google.zxing.common.PerspectiveTransform;
+import com.google.zxing.common.detector.MathUtils;
+import com.google.zxing.qrcode.decoder.Version;
+
+import java.util.Map;
+
+/**
+ * Encapsulates logic that can detect a QR Code in an image, even if the QR Code
+ * is rotated or skewed, or partially obscured.
+ *
+ * @author Sean Owen
+ */
+public class Detector {
+
+ private final BitMatrix image;
+ private ResultPointCallback resultPointCallback;
+
+ public Detector(BitMatrix image) {
+ this.image = image;
+ }
+
+ protected final BitMatrix getImage() {
+ return image;
+ }
+
+ protected final ResultPointCallback getResultPointCallback() {
+ return resultPointCallback;
+ }
+
+ /**
+ * Detects a QR Code in an image.
+ *
+ * @return {@link DetectorResult} encapsulating results of detecting a QR Code
+ * @throws NotFoundException if QR Code cannot be found
+ * @throws FormatException if a QR Code cannot be decoded
+ */
+ public DetectorResult detect() throws NotFoundException, FormatException {
+ return detect(null);
+ }
+
+ /**
+ * Detects a QR Code in an image.
+ *
+ * @param hints optional hints to detector
+ * @return {@link DetectorResult} encapsulating results of detecting a QR Code
+ * @throws NotFoundException if QR Code cannot be found
+ * @throws FormatException if a QR Code cannot be decoded
+ */
+ public final DetectorResult detect(Map hints) throws NotFoundException, FormatException {
+
+ resultPointCallback = hints == null ? null :
+ (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+
+ FinderPatternFinder finder = new FinderPatternFinder(image, resultPointCallback);
+ FinderPatternInfo info = finder.find(hints);
+
+ return processFinderPatternInfo(info);
+ }
+
+ protected final DetectorResult processFinderPatternInfo(FinderPatternInfo info)
+ throws NotFoundException, FormatException {
+
+ FinderPattern topLeft = info.getTopLeft();
+ FinderPattern topRight = info.getTopRight();
+ FinderPattern bottomLeft = info.getBottomLeft();
+
+ float moduleSize = calculateModuleSize(topLeft, topRight, bottomLeft);
+ if (moduleSize < 1.0f) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int dimension = computeDimension(topLeft, topRight, bottomLeft, moduleSize);
+ Version provisionalVersion = Version.getProvisionalVersionForDimension(dimension);
+ int modulesBetweenFPCenters = provisionalVersion.getDimensionForVersion() - 7;
+
+ AlignmentPattern alignmentPattern = null;
+ // Anything above version 1 has an alignment pattern
+ if (provisionalVersion.getAlignmentPatternCenters().length > 0) {
+
+ // Guess where a "bottom right" finder pattern would have been
+ float bottomRightX = topRight.getX() - topLeft.getX() + bottomLeft.getX();
+ float bottomRightY = topRight.getY() - topLeft.getY() + bottomLeft.getY();
+
+ // Estimate that alignment pattern is closer by 3 modules
+ // from "bottom right" to known top left location
+ float correctionToTopLeft = 1.0f - 3.0f / (float) modulesBetweenFPCenters;
+ int estAlignmentX = (int) (topLeft.getX() + correctionToTopLeft * (bottomRightX - topLeft.getX()));
+ int estAlignmentY = (int) (topLeft.getY() + correctionToTopLeft * (bottomRightY - topLeft.getY()));
+
+ // Kind of arbitrary -- expand search radius before giving up
+ for (int i = 4; i <= 16; i <<= 1) {
+ try {
+ alignmentPattern = findAlignmentInRegion(moduleSize,
+ estAlignmentX,
+ estAlignmentY,
+ (float) i);
+ break;
+ } catch (NotFoundException re) {
+ // try next round
+ }
+ }
+ // If we didn't find alignment pattern... well try anyway without it
+ }
+
+ PerspectiveTransform transform =
+ createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension);
+
+ BitMatrix bits = sampleGrid(image, transform, dimension);
+
+ ResultPoint[] points;
+ if (alignmentPattern == null) {
+ points = new ResultPoint[]{bottomLeft, topLeft, topRight};
+ } else {
+ points = new ResultPoint[]{bottomLeft, topLeft, topRight, alignmentPattern};
+ }
+ return new DetectorResult(bits, points);
+ }
+
+ private static PerspectiveTransform createTransform(ResultPoint topLeft,
+ ResultPoint topRight,
+ ResultPoint bottomLeft,
+ ResultPoint alignmentPattern,
+ int dimension) {
+ float dimMinusThree = (float) dimension - 3.5f;
+ float bottomRightX;
+ float bottomRightY;
+ float sourceBottomRightX;
+ float sourceBottomRightY;
+ if (alignmentPattern != null) {
+ bottomRightX = alignmentPattern.getX();
+ bottomRightY = alignmentPattern.getY();
+ sourceBottomRightX = dimMinusThree - 3.0f;
+ sourceBottomRightY = sourceBottomRightX;
+ } else {
+ // Don't have an alignment pattern, just make up the bottom-right point
+ bottomRightX = (topRight.getX() - topLeft.getX()) + bottomLeft.getX();
+ bottomRightY = (topRight.getY() - topLeft.getY()) + bottomLeft.getY();
+ sourceBottomRightX = dimMinusThree;
+ sourceBottomRightY = dimMinusThree;
+ }
+
+ return PerspectiveTransform.quadrilateralToQuadrilateral(
+ 3.5f,
+ 3.5f,
+ dimMinusThree,
+ 3.5f,
+ sourceBottomRightX,
+ sourceBottomRightY,
+ 3.5f,
+ dimMinusThree,
+ topLeft.getX(),
+ topLeft.getY(),
+ topRight.getX(),
+ topRight.getY(),
+ bottomRightX,
+ bottomRightY,
+ bottomLeft.getX(),
+ bottomLeft.getY());
+ }
+
+ private static BitMatrix sampleGrid(BitMatrix image,
+ PerspectiveTransform transform,
+ int dimension) throws NotFoundException {
+
+ GridSampler sampler = GridSampler.getInstance();
+ return sampler.sampleGrid(image, dimension, dimension, transform);
+ }
+
+ /**
+ * Computes the dimension (number of modules on a size) of the QR Code based on the position
+ * of the finder patterns and estimated module size.
+ */
+ private static int computeDimension(ResultPoint topLeft,
+ ResultPoint topRight,
+ ResultPoint bottomLeft,
+ float moduleSize) throws NotFoundException {
+ int tltrCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, topRight) / moduleSize);
+ int tlblCentersDimension = MathUtils.round(ResultPoint.distance(topLeft, bottomLeft) / moduleSize);
+ int dimension = ((tltrCentersDimension + tlblCentersDimension) >> 1) + 7;
+ switch (dimension & 0x03) { // mod 4
+ case 0:
+ dimension++;
+ break;
+ // 1? do nothing
+ case 2:
+ dimension--;
+ break;
+ case 3:
+ throw NotFoundException.getNotFoundInstance();
+ }
+ return dimension;
+ }
+
+ /**
+ * Computes an average estimated module size based on estimated derived from the positions
+ * of the three finder patterns.
+ *
+ * @param topLeft detected top-left finder pattern center
+ * @param topRight detected top-right finder pattern center
+ * @param bottomLeft detected bottom-left finder pattern center
+ * @return estimated module size
+ */
+ protected final float calculateModuleSize(ResultPoint topLeft,
+ ResultPoint topRight,
+ ResultPoint bottomLeft) {
+ // Take the average
+ return (calculateModuleSizeOneWay(topLeft, topRight) +
+ calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2.0f;
+ }
+
+ /**
+ * Estimates module size based on two finder patterns -- it uses
+ * {@link #sizeOfBlackWhiteBlackRunBothWays(int, int, int, int)} to figure the
+ * width of each, measuring along the axis between their centers.
+ */
+ private float calculateModuleSizeOneWay(ResultPoint pattern, ResultPoint otherPattern) {
+ float moduleSizeEst1 = sizeOfBlackWhiteBlackRunBothWays((int) pattern.getX(),
+ (int) pattern.getY(),
+ (int) otherPattern.getX(),
+ (int) otherPattern.getY());
+ float moduleSizeEst2 = sizeOfBlackWhiteBlackRunBothWays((int) otherPattern.getX(),
+ (int) otherPattern.getY(),
+ (int) pattern.getX(),
+ (int) pattern.getY());
+ if (Float.isNaN(moduleSizeEst1)) {
+ return moduleSizeEst2 / 7.0f;
+ }
+ if (Float.isNaN(moduleSizeEst2)) {
+ return moduleSizeEst1 / 7.0f;
+ }
+ // Average them, and divide by 7 since we've counted the width of 3 black modules,
+ // and 1 white and 1 black module on either side. Ergo, divide sum by 14.
+ return (moduleSizeEst1 + moduleSizeEst2) / 14.0f;
+ }
+
+ /**
+ * See {@link #sizeOfBlackWhiteBlackRun(int, int, int, int)}; computes the total width of
+ * a finder pattern by looking for a black-white-black run from the center in the direction
+ * of another point (another finder pattern center), and in the opposite direction too.
+ */
+ private float sizeOfBlackWhiteBlackRunBothWays(int fromX, int fromY, int toX, int toY) {
+
+ float result = sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
+
+ // Now count other way -- don't run off image though of course
+ float scale = 1.0f;
+ int otherToX = fromX - (toX - fromX);
+ if (otherToX < 0) {
+ scale = (float) fromX / (float) (fromX - otherToX);
+ otherToX = 0;
+ } else if (otherToX >= image.getWidth()) {
+ scale = (float) (image.getWidth() - 1 - fromX) / (float) (otherToX - fromX);
+ otherToX = image.getWidth() - 1;
+ }
+ int otherToY = (int) (fromY - (toY - fromY) * scale);
+
+ scale = 1.0f;
+ if (otherToY < 0) {
+ scale = (float) fromY / (float) (fromY - otherToY);
+ otherToY = 0;
+ } else if (otherToY >= image.getHeight()) {
+ scale = (float) (image.getHeight() - 1 - fromY) / (float) (otherToY - fromY);
+ otherToY = image.getHeight() - 1;
+ }
+ otherToX = (int) (fromX + (otherToX - fromX) * scale);
+
+ result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);
+
+ // Middle pixel is double-counted this way; subtract 1
+ return result - 1.0f;
+ }
+
+ /**
+ * This method traces a line from a point in the image, in the direction towards another point.
+ * It begins in a black region, and keeps going until it finds white, then black, then white again.
+ * It reports the distance from the start to this point.
+ *
+ * This is used when figuring out how wide a finder pattern is, when the finder pattern
+ * may be skewed or rotated.
+ */
+ private float sizeOfBlackWhiteBlackRun(int fromX, int fromY, int toX, int toY) {
+ // Mild variant of Bresenham's algorithm;
+ // see http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
+ boolean steep = Math.abs(toY - fromY) > Math.abs(toX - fromX);
+ if (steep) {
+ int temp = fromX;
+ fromX = fromY;
+ fromY = temp;
+ temp = toX;
+ toX = toY;
+ toY = temp;
+ }
+
+ int dx = Math.abs(toX - fromX);
+ int dy = Math.abs(toY - fromY);
+ int error = -dx >> 1;
+ int xstep = fromX < toX ? 1 : -1;
+ int ystep = fromY < toY ? 1 : -1;
+
+ // In black pixels, looking for white, first or second time.
+ int state = 0;
+ // Loop up until x == toX, but not beyond
+ int xLimit = toX + xstep;
+ for (int x = fromX, y = fromY; x != xLimit; x += xstep) {
+ int realX = steep ? y : x;
+ int realY = steep ? x : y;
+
+ // Does current pixel mean we have moved white to black or vice versa?
+ // Scanning black in state 0,2 and white in state 1, so if we find the wrong
+ // color, advance to next state or end if we are in state 2 already
+ if ((state == 1) == image.get(realX, realY)) {
+ if (state == 2) {
+ return MathUtils.distance(x, y, fromX, fromY);
+ }
+ state++;
+ }
+
+ error += dy;
+ if (error > 0) {
+ if (y == toY) {
+ break;
+ }
+ y += ystep;
+ error -= dx;
+ }
+ }
+ // Found black-white-black; give the benefit of the doubt that the next pixel outside the image
+ // is "white" so this last point at (toX+xStep,toY) is the right ending. This is really a
+ // small approximation; (toX+xStep,toY+yStep) might be really correct. Ignore this.
+ if (state == 2) {
+ return MathUtils.distance(toX + xstep, toY, fromX, fromY);
+ }
+ // else we didn't find even black-white-black; no estimate is really possible
+ return Float.NaN;
+ }
+
+ /**
+ * Attempts to locate an alignment pattern in a limited region of the image, which is
+ * guessed to contain it. This method uses {@link AlignmentPattern}.
+ *
+ * @param overallEstModuleSize estimated module size so far
+ * @param estAlignmentX x coordinate of center of area probably containing alignment pattern
+ * @param estAlignmentY y coordinate of above
+ * @param allowanceFactor number of pixels in all directions to search from the center
+ * @return {@link AlignmentPattern} if found, or null otherwise
+ * @throws NotFoundException if an unexpected error occurs during detection
+ */
+ protected final AlignmentPattern findAlignmentInRegion(float overallEstModuleSize,
+ int estAlignmentX,
+ int estAlignmentY,
+ float allowanceFactor)
+ throws NotFoundException {
+ // Look for an alignment pattern (3 modules in size) around where it
+ // should be
+ int allowance = (int) (allowanceFactor * overallEstModuleSize);
+ int alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance);
+ int alignmentAreaRightX = Math.min(image.getWidth() - 1, estAlignmentX + allowance);
+ if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int alignmentAreaTopY = Math.max(0, estAlignmentY - allowance);
+ int alignmentAreaBottomY = Math.min(image.getHeight() - 1, estAlignmentY + allowance);
+ if (alignmentAreaBottomY - alignmentAreaTopY < overallEstModuleSize * 3) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ AlignmentPatternFinder alignmentFinder =
+ new AlignmentPatternFinder(
+ image,
+ alignmentAreaLeftX,
+ alignmentAreaTopY,
+ alignmentAreaRightX - alignmentAreaLeftX,
+ alignmentAreaBottomY - alignmentAreaTopY,
+ overallEstModuleSize,
+ resultPointCallback);
+ return alignmentFinder.find();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPattern.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPattern.java
new file mode 100644
index 000000000..a64e7c2b3
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPattern.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import com.google.zxing.ResultPoint;
+
+/**
+ * Encapsulates a finder pattern, which are the three square patterns found in
+ * the corners of QR Codes. It also encapsulates a count of similar finder patterns,
+ * as a convenience to the finder's bookkeeping.
+ *
+ * @author Sean Owen
+ */
+public final class FinderPattern extends ResultPoint {
+
+ private final float estimatedModuleSize;
+ private final int count;
+
+ FinderPattern(float posX, float posY, float estimatedModuleSize) {
+ this(posX, posY, estimatedModuleSize, 1);
+ }
+
+ private FinderPattern(float posX, float posY, float estimatedModuleSize, int count) {
+ super(posX, posY);
+ this.estimatedModuleSize = estimatedModuleSize;
+ this.count = count;
+ }
+
+ public float getEstimatedModuleSize() {
+ return estimatedModuleSize;
+ }
+
+ int getCount() {
+ return count;
+ }
+
+ /*
+ void incrementCount() {
+ this.count++;
+ }
+ */
+
+ /**
+ * Determines if this finder pattern "about equals" a finder pattern at the stated
+ * position and size -- meaning, it is at nearly the same center with nearly the same size.
+ */
+ boolean aboutEquals(float moduleSize, float i, float j) {
+ if (Math.abs(i - getY()) <= moduleSize && Math.abs(j - getX()) <= moduleSize) {
+ float moduleSizeDiff = Math.abs(moduleSize - estimatedModuleSize);
+ return moduleSizeDiff <= 1.0f || moduleSizeDiff <= estimatedModuleSize;
+ }
+ return false;
+ }
+
+ /**
+ * Combines this object's current estimate of a finder pattern position and module size
+ * with a new estimate. It returns a new {@code FinderPattern} containing a weighted average
+ * based on count.
+ */
+ FinderPattern combineEstimate(float i, float j, float newModuleSize) {
+ int combinedCount = count + 1;
+ float combinedX = (count * getX() + j) / combinedCount;
+ float combinedY = (count * getY() + i) / combinedCount;
+ float combinedModuleSize = (count * estimatedModuleSize + newModuleSize) / combinedCount;
+ return new FinderPattern(combinedX, combinedY, combinedModuleSize, combinedCount);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java
new file mode 100755
index 000000000..f35b0af4b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternFinder.java
@@ -0,0 +1,680 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class attempts to find finder patterns in a QR Code. Finder patterns are the square
+ * markers at three corners of a QR Code.
+ *
+ * This class is thread-safe but not reentrant. Each thread must allocate its own object.
+ *
+ * @author Sean Owen
+ */
+public class FinderPatternFinder {
+
+ private static final int CENTER_QUORUM = 2;
+ protected static final int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center
+ protected static final int MAX_MODULES = 57; // support up to version 10 for mobile clients
+ private static final int INTEGER_MATH_SHIFT = 8;
+
+ private final BitMatrix image;
+ private final List possibleCenters;
+ private boolean hasSkipped;
+ private final int[] crossCheckStateCount;
+ private final ResultPointCallback resultPointCallback;
+
+ /**
+ * Creates a finder that will search the image for three finder patterns.
+ *
+ * @param image image to search
+ */
+ public FinderPatternFinder(BitMatrix image) {
+ this(image, null);
+ }
+
+ public FinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) {
+ this.image = image;
+ this.possibleCenters = new ArrayList<>();
+ this.crossCheckStateCount = new int[5];
+ this.resultPointCallback = resultPointCallback;
+ }
+
+ protected final BitMatrix getImage() {
+ return image;
+ }
+
+ protected final List getPossibleCenters() {
+ return possibleCenters;
+ }
+
+ final FinderPatternInfo find(Map hints) throws NotFoundException {
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ boolean pureBarcode = hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE);
+ int maxI = image.getHeight();
+ int maxJ = image.getWidth();
+ // We are looking for black/white/black/white/black modules in
+ // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
+
+ // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the
+ // image, and then account for the center being 3 modules in size. This gives the smallest
+ // number of pixels the center could be, so skip this often. When trying harder, look for all
+ // QR versions regardless of how dense they are.
+ int iSkip = (3 * maxI) / (4 * MAX_MODULES);
+ if (iSkip < MIN_SKIP || tryHarder) {
+ iSkip = MIN_SKIP;
+ }
+
+ boolean done = false;
+ int[] stateCount = new int[5];
+ for (int i = iSkip - 1; i < maxI && !done; i += iSkip) {
+ // Get a row of black/white values
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ int currentState = 0;
+ for (int j = 0; j < maxJ; j++) {
+ if (image.get(j, i)) {
+ // Black pixel
+ if ((currentState & 1) == 1) { // Counting white pixels
+ currentState++;
+ }
+ stateCount[currentState]++;
+ } else { // White pixel
+ if ((currentState & 1) == 0) { // Counting black pixels
+ if (currentState == 4) { // A winner?
+ if (foundPatternCross(stateCount)) { // Yes
+ boolean confirmed = handlePossibleCenter(stateCount, i, j, pureBarcode);
+ if (confirmed) {
+ // Start examining every other line. Checking each line turned out to be too
+ // expensive and didn't improve performance.
+ iSkip = 2;
+ if (hasSkipped) {
+ done = haveMultiplyConfirmedCenters();
+ } else {
+ int rowSkip = findRowSkip();
+ if (rowSkip > stateCount[2]) {
+ // Skip rows between row of lower confirmed center
+ // and top of presumed third confirmed center
+ // but back up a bit to get a full chance of detecting
+ // it, entire width of center of finder pattern
+
+ // Skip by rowSkip, but back off by stateCount[2] (size of last center
+ // of pattern we saw) to be conservative, and also back off by iSkip which
+ // is about to be re-added
+ i += rowSkip - stateCount[2] - iSkip;
+ j = maxJ - 1;
+ }
+ }
+ } else {
+ stateCount[0] = stateCount[2];
+ stateCount[1] = stateCount[3];
+ stateCount[2] = stateCount[4];
+ stateCount[3] = 1;
+ stateCount[4] = 0;
+ currentState = 3;
+ continue;
+ }
+ // Clear state to start looking again
+ currentState = 0;
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ } else { // No, shift counts back by two
+ stateCount[0] = stateCount[2];
+ stateCount[1] = stateCount[3];
+ stateCount[2] = stateCount[4];
+ stateCount[3] = 1;
+ stateCount[4] = 0;
+ currentState = 3;
+ }
+ } else {
+ stateCount[++currentState]++;
+ }
+ } else { // Counting white pixels
+ stateCount[currentState]++;
+ }
+ }
+ }
+ if (foundPatternCross(stateCount)) {
+ boolean confirmed = handlePossibleCenter(stateCount, i, maxJ, pureBarcode);
+ if (confirmed) {
+ iSkip = stateCount[0];
+ if (hasSkipped) {
+ // Found a third one
+ done = haveMultiplyConfirmedCenters();
+ }
+ }
+ }
+ }
+
+ FinderPattern[] patternInfo = selectBestPatterns();
+ ResultPoint.orderBestPatterns(patternInfo);
+
+ return new FinderPatternInfo(patternInfo);
+ }
+
+ /**
+ * Given a count of black/white/black/white/black pixels just seen and an end position,
+ * figures the location of the center of this run.
+ */
+ private static float centerFromEnd(int[] stateCount, int end) {
+ return (float) (end - stateCount[4] - stateCount[3]) - stateCount[2] / 2.0f;
+ }
+
+ /**
+ * @param stateCount count of black/white/black/white/black pixels just read
+ * @return true iff the proportions of the counts is close enough to the 1/1/3/1/1 ratios
+ * used by finder patterns to be considered a match
+ */
+ protected static boolean foundPatternCross(int[] stateCount) {
+ int totalModuleSize = 0;
+ for (int i = 0; i < 5; i++) {
+ int count = stateCount[i];
+ if (count == 0) {
+ return false;
+ }
+ totalModuleSize += count;
+ }
+ if (totalModuleSize < 7) {
+ return false;
+ }
+ int moduleSize = (totalModuleSize << INTEGER_MATH_SHIFT) / 7;
+ int maxVariance = moduleSize / 2;
+ // Allow less than 50% variance from 1-1-3-1-1 proportions
+ return Math.abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance &&
+ Math.abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance &&
+ Math.abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance &&
+ Math.abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance &&
+ Math.abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance;
+ }
+
+ private int[] getCrossCheckStateCount() {
+ crossCheckStateCount[0] = 0;
+ crossCheckStateCount[1] = 0;
+ crossCheckStateCount[2] = 0;
+ crossCheckStateCount[3] = 0;
+ crossCheckStateCount[4] = 0;
+ return crossCheckStateCount;
+ }
+
+ /**
+ * After a vertical and horizontal scan finds a potential finder pattern, this method
+ * "cross-cross-cross-checks" by scanning down diagonally through the center of the possible
+ * finder pattern to see if the same proportion is detected.
+ *
+ * @param startI row where a finder pattern was detected
+ * @param centerJ center of the section that appears to cross a finder pattern
+ * @param maxCount maximum reasonable number of modules that should be
+ * observed in any reading state, based on the results of the horizontal scan
+ * @param originalStateCountTotal The original state count total.
+ * @return true if proportions are withing expected limits
+ */
+ private boolean crossCheckDiagonal(int startI, int centerJ, int maxCount, int originalStateCountTotal) {
+ int[] stateCount = getCrossCheckStateCount();
+
+ // Start counting up, left from center finding black center mass
+ int i = 0;
+ while (startI >= i && centerJ >= i && image.get(centerJ - i, startI - i)) {
+ stateCount[2]++;
+ i++;
+ }
+
+ if (startI < i || centerJ < i) {
+ return false;
+ }
+
+ // Continue up, left finding white space
+ while (startI >= i && centerJ >= i && !image.get(centerJ - i, startI - i) &&
+ stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i++;
+ }
+
+ // If already too many modules in this state or ran off the edge:
+ if (startI < i || centerJ < i || stateCount[1] > maxCount) {
+ return false;
+ }
+
+ // Continue up, left finding black border
+ while (startI >= i && centerJ >= i && image.get(centerJ - i, startI - i) &&
+ stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ i++;
+ }
+ if (stateCount[0] > maxCount) {
+ return false;
+ }
+
+ int maxI = image.getHeight();
+ int maxJ = image.getWidth();
+
+ // Now also count down, right from center
+ i = 1;
+ while (startI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, startI + i)) {
+ stateCount[2]++;
+ i++;
+ }
+
+ // Ran off the edge?
+ if (startI + i >= maxI || centerJ + i >= maxJ) {
+ return false;
+ }
+
+ while (startI + i < maxI && centerJ + i < maxJ && !image.get(centerJ + i, startI + i) &&
+ stateCount[3] < maxCount) {
+ stateCount[3]++;
+ i++;
+ }
+
+ if (startI + i >= maxI || centerJ + i >= maxJ || stateCount[3] >= maxCount) {
+ return false;
+ }
+
+ while (startI + i < maxI && centerJ + i < maxJ && image.get(centerJ + i, startI + i) &&
+ stateCount[4] < maxCount) {
+ stateCount[4]++;
+ i++;
+ }
+
+ if (stateCount[4] >= maxCount) {
+ return false;
+ }
+
+ // If we found a finder-pattern-like section, but its size is more than 100% different than
+ // the original, assume it's a false positive
+ int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
+ return
+ Math.abs(stateCountTotal - originalStateCountTotal) < 2 * originalStateCountTotal &&
+ foundPatternCross(stateCount);
+ }
+
+ /**
+ * After a horizontal scan finds a potential finder pattern, this method
+ * "cross-checks" by scanning down vertically through the center of the possible
+ * finder pattern to see if the same proportion is detected.
+ *
+ * @param startI row where a finder pattern was detected
+ * @param centerJ center of the section that appears to cross a finder pattern
+ * @param maxCount maximum reasonable number of modules that should be
+ * observed in any reading state, based on the results of the horizontal scan
+ * @return vertical center of finder pattern, or {@link Float#NaN} if not found
+ */
+ private float crossCheckVertical(int startI, int centerJ, int maxCount,
+ int originalStateCountTotal) {
+ BitMatrix image = this.image;
+
+ int maxI = image.getHeight();
+ int[] stateCount = getCrossCheckStateCount();
+
+ // Start counting up from center
+ int i = startI;
+ while (i >= 0 && image.get(centerJ, i)) {
+ stateCount[2]++;
+ i--;
+ }
+ if (i < 0) {
+ return Float.NaN;
+ }
+ while (i >= 0 && !image.get(centerJ, i) && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i--;
+ }
+ // If already too many modules in this state or ran off the edge:
+ if (i < 0 || stateCount[1] > maxCount) {
+ return Float.NaN;
+ }
+ while (i >= 0 && image.get(centerJ, i) && stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ i--;
+ }
+ if (stateCount[0] > maxCount) {
+ return Float.NaN;
+ }
+
+ // Now also count down from center
+ i = startI + 1;
+ while (i < maxI && image.get(centerJ, i)) {
+ stateCount[2]++;
+ i++;
+ }
+ if (i == maxI) {
+ return Float.NaN;
+ }
+ while (i < maxI && !image.get(centerJ, i) && stateCount[3] < maxCount) {
+ stateCount[3]++;
+ i++;
+ }
+ if (i == maxI || stateCount[3] >= maxCount) {
+ return Float.NaN;
+ }
+ while (i < maxI && image.get(centerJ, i) && stateCount[4] < maxCount) {
+ stateCount[4]++;
+ i++;
+ }
+ if (stateCount[4] >= maxCount) {
+ return Float.NaN;
+ }
+
+ // If we found a finder-pattern-like section, but its size is more than 40% different than
+ // the original, assume it's a false positive
+ int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
+ stateCount[4];
+ if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
+ return Float.NaN;
+ }
+
+ return foundPatternCross(stateCount) ? centerFromEnd(stateCount, i) : Float.NaN;
+ }
+
+ /**
+ * Like {@link #crossCheckVertical(int, int, int, int)}, and in fact is basically identical,
+ * except it reads horizontally instead of vertically. This is used to cross-cross
+ * check a vertical cross check and locate the real center of the alignment pattern.
+ */
+ private float crossCheckHorizontal(int startJ, int centerI, int maxCount,
+ int originalStateCountTotal) {
+ BitMatrix image = this.image;
+
+ int maxJ = image.getWidth();
+ int[] stateCount = getCrossCheckStateCount();
+
+ int j = startJ;
+ while (j >= 0 && image.get(j, centerI)) {
+ stateCount[2]++;
+ j--;
+ }
+ if (j < 0) {
+ return Float.NaN;
+ }
+ while (j >= 0 && !image.get(j, centerI) && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ j--;
+ }
+ if (j < 0 || stateCount[1] > maxCount) {
+ return Float.NaN;
+ }
+ while (j >= 0 && image.get(j, centerI) && stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ j--;
+ }
+ if (stateCount[0] > maxCount) {
+ return Float.NaN;
+ }
+
+ j = startJ + 1;
+ while (j < maxJ && image.get(j, centerI)) {
+ stateCount[2]++;
+ j++;
+ }
+ if (j == maxJ) {
+ return Float.NaN;
+ }
+ while (j < maxJ && !image.get(j, centerI) && stateCount[3] < maxCount) {
+ stateCount[3]++;
+ j++;
+ }
+ if (j == maxJ || stateCount[3] >= maxCount) {
+ return Float.NaN;
+ }
+ while (j < maxJ && image.get(j, centerI) && stateCount[4] < maxCount) {
+ stateCount[4]++;
+ j++;
+ }
+ if (stateCount[4] >= maxCount) {
+ return Float.NaN;
+ }
+
+ // If we found a finder-pattern-like section, but its size is significantly different than
+ // the original, assume it's a false positive
+ int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
+ stateCount[4];
+ if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) {
+ return Float.NaN;
+ }
+
+ return foundPatternCross(stateCount) ? centerFromEnd(stateCount, j) : Float.NaN;
+ }
+
+ /**
+ * This is called when a horizontal scan finds a possible alignment pattern. It will
+ * cross check with a vertical scan, and if successful, will, ah, cross-cross-check
+ * with another horizontal scan. This is needed primarily to locate the real horizontal
+ * center of the pattern in cases of extreme skew.
+ * And then we cross-cross-cross check with another diagonal scan.
+ *
+ * If that succeeds the finder pattern location is added to a list that tracks
+ * the number of times each location has been nearly-matched as a finder pattern.
+ * Each additional find is more evidence that the location is in fact a finder
+ * pattern center
+ *
+ * @param stateCount reading state module counts from horizontal scan
+ * @param i row where finder pattern may be found
+ * @param j end of possible finder pattern in row
+ * @param pureBarcode true if in "pure barcode" mode
+ * @return true if a finder pattern candidate was found this time
+ */
+ protected final boolean handlePossibleCenter(int[] stateCount, int i, int j, boolean pureBarcode) {
+ int stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] +
+ stateCount[4];
+ float centerJ = centerFromEnd(stateCount, j);
+ float centerI = crossCheckVertical(i, (int) centerJ, stateCount[2], stateCountTotal);
+ if (!Float.isNaN(centerI)) {
+ // Re-cross check
+ centerJ = crossCheckHorizontal((int) centerJ, (int) centerI, stateCount[2], stateCountTotal);
+ if (!Float.isNaN(centerJ) &&
+ (!pureBarcode || crossCheckDiagonal((int) centerI, (int) centerJ, stateCount[2], stateCountTotal))) {
+ float estimatedModuleSize = (float) stateCountTotal / 7.0f;
+ boolean found = false;
+ for (int index = 0; index < possibleCenters.size(); index++) {
+ FinderPattern center = possibleCenters.get(index);
+ // Look for about the same center and module size:
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ possibleCenters.set(index, center.combineEstimate(centerI, centerJ, estimatedModuleSize));
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ FinderPattern point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
+ possibleCenters.add(point);
+ if (resultPointCallback != null) {
+ resultPointCallback.foundPossibleResultPoint(point);
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @return number of rows we could safely skip during scanning, based on the first
+ * two finder patterns that have been located. In some cases their position will
+ * allow us to infer that the third pattern must lie below a certain point farther
+ * down in the image.
+ */
+ private int findRowSkip() {
+ int max = possibleCenters.size();
+ if (max <= 1) {
+ return 0;
+ }
+ ResultPoint firstConfirmedCenter = null;
+ for (FinderPattern center : possibleCenters) {
+ if (center.getCount() >= CENTER_QUORUM) {
+ if (firstConfirmedCenter == null) {
+ firstConfirmedCenter = center;
+ } else {
+ // We have two confirmed centers
+ // How far down can we skip before resuming looking for the next
+ // pattern? In the worst case, only the difference between the
+ // difference in the x / y coordinates of the two centers.
+ // This is the case where you find top left last.
+ hasSkipped = true;
+ return (int) (Math.abs(firstConfirmedCenter.getX() - center.getX()) -
+ Math.abs(firstConfirmedCenter.getY() - center.getY())) / 2;
+ }
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * @return true iff we have found at least 3 finder patterns that have been detected
+ * at least {@link #CENTER_QUORUM} times each, and, the estimated module size of the
+ * candidates is "pretty similar"
+ */
+ private boolean haveMultiplyConfirmedCenters() {
+ int confirmedCount = 0;
+ float totalModuleSize = 0.0f;
+ int max = possibleCenters.size();
+ for (FinderPattern pattern : possibleCenters) {
+ if (pattern.getCount() >= CENTER_QUORUM) {
+ confirmedCount++;
+ totalModuleSize += pattern.getEstimatedModuleSize();
+ }
+ }
+ if (confirmedCount < 3) {
+ return false;
+ }
+ // OK, we have at least 3 confirmed centers, but, it's possible that one is a "false positive"
+ // and that we need to keep looking. We detect this by asking if the estimated module sizes
+ // vary too much. We arbitrarily say that when the total deviation from average exceeds
+ // 5% of the total module size estimates, it's too much.
+ float average = totalModuleSize / (float) max;
+ float totalDeviation = 0.0f;
+ for (FinderPattern pattern : possibleCenters) {
+ totalDeviation += Math.abs(pattern.getEstimatedModuleSize() - average);
+ }
+ return totalDeviation <= 0.05f * totalModuleSize;
+ }
+
+ /**
+ * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are
+ * those that have been detected at least {@link #CENTER_QUORUM} times, and whose module
+ * size differs from the average among those patterns the least
+ * @throws NotFoundException if 3 such finder patterns do not exist
+ */
+ private FinderPattern[] selectBestPatterns() throws NotFoundException {
+
+ int startSize = possibleCenters.size();
+ if (startSize < 3) {
+ // Couldn't find enough finder patterns
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Filter outlier possibilities whose module size is too different
+ if (startSize > 3) {
+ // But we can only afford to do so if we have at least 4 possibilities to choose from
+ float totalModuleSize = 0.0f;
+ float square = 0.0f;
+ for (FinderPattern center : possibleCenters) {
+ float size = center.getEstimatedModuleSize();
+ totalModuleSize += size;
+ square += size * size;
+ }
+ float average = totalModuleSize / (float) startSize;
+ float stdDev = (float) Math.sqrt(square / startSize - average * average);
+
+ Collections.sort(possibleCenters, new FurthestFromAverageComparator(average));
+
+ float limit = Math.max(0.2f * average, stdDev);
+
+ for (int i = 0; i < possibleCenters.size() && possibleCenters.size() > 3; i++) {
+ FinderPattern pattern = possibleCenters.get(i);
+ if (Math.abs(pattern.getEstimatedModuleSize() - average) > limit) {
+ possibleCenters.remove(i);
+ i--;
+ }
+ }
+ }
+
+ if (possibleCenters.size() > 3) {
+ // Throw away all but those first size candidate points we found.
+
+ float totalModuleSize = 0.0f;
+ for (FinderPattern possibleCenter : possibleCenters) {
+ totalModuleSize += possibleCenter.getEstimatedModuleSize();
+ }
+
+ float average = totalModuleSize / (float) possibleCenters.size();
+
+ Collections.sort(possibleCenters, new CenterComparator(average));
+
+ possibleCenters.subList(3, possibleCenters.size()).clear();
+ }
+
+ return new FinderPattern[]{
+ possibleCenters.get(0),
+ possibleCenters.get(1),
+ possibleCenters.get(2)
+ };
+ }
+
+ /**
+ *
Orders by furthest from average
+ */
+ private static final class FurthestFromAverageComparator implements Comparator, Serializable {
+ private final float average;
+ private FurthestFromAverageComparator(float f) {
+ average = f;
+ }
+ @Override
+ public int compare(FinderPattern center1, FinderPattern center2) {
+ float dA = Math.abs(center2.getEstimatedModuleSize() - average);
+ float dB = Math.abs(center1.getEstimatedModuleSize() - average);
+ return dA < dB ? -1 : dA == dB ? 0 : 1;
+ }
+ }
+
+ /**
+ * Orders by {@link FinderPattern#getCount()}, descending.
+ */
+ private static final class CenterComparator implements Comparator, Serializable {
+ private final float average;
+ private CenterComparator(float f) {
+ average = f;
+ }
+ @Override
+ public int compare(FinderPattern center1, FinderPattern center2) {
+ if (center2.getCount() == center1.getCount()) {
+ float dA = Math.abs(center2.getEstimatedModuleSize() - average);
+ float dB = Math.abs(center1.getEstimatedModuleSize() - average);
+ return dA < dB ? 1 : dA == dB ? 0 : -1;
+ } else {
+ return center2.getCount() - center1.getCount();
+ }
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternInfo.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternInfo.java
new file mode 100644
index 000000000..3c3401085
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/detector/FinderPatternInfo.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 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.qrcode.detector;
+
+/**
+ * Encapsulates information about finder patterns in an image, including the location of
+ * the three finder patterns, and their estimated module size.
+ *
+ * @author Sean Owen
+ */
+public final class FinderPatternInfo {
+
+ private final FinderPattern bottomLeft;
+ private final FinderPattern topLeft;
+ private final FinderPattern topRight;
+
+ public FinderPatternInfo(FinderPattern[] patternCenters) {
+ this.bottomLeft = patternCenters[0];
+ this.topLeft = patternCenters[1];
+ this.topRight = patternCenters[2];
+ }
+
+ public FinderPattern getBottomLeft() {
+ return bottomLeft;
+ }
+
+ public FinderPattern getTopLeft() {
+ return topLeft;
+ }
+
+ public FinderPattern getTopRight() {
+ return topRight;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/BlockPair.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/BlockPair.java
new file mode 100644
index 000000000..5714d9c3a
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/BlockPair.java
@@ -0,0 +1,37 @@
+/*
+ * 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.qrcode.encoder;
+
+final class BlockPair {
+
+ private final byte[] dataBytes;
+ private final byte[] errorCorrectionBytes;
+
+ BlockPair(byte[] data, byte[] errorCorrection) {
+ dataBytes = data;
+ errorCorrectionBytes = errorCorrection;
+ }
+
+ public byte[] getDataBytes() {
+ return dataBytes;
+ }
+
+ public byte[] getErrorCorrectionBytes() {
+ return errorCorrectionBytes;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/ByteMatrix.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/ByteMatrix.java
new file mode 100644
index 000000000..d7fef0601
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/ByteMatrix.java
@@ -0,0 +1,98 @@
+/*
+ * 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.qrcode.encoder;
+
+/**
+ * JAVAPORT: The original code was a 2D array of ints, but since it only ever gets assigned
+ * -1, 0, and 1, I'm going to use less memory and go with bytes.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class ByteMatrix {
+
+ private final byte[][] bytes;
+ private final int width;
+ private final int height;
+
+ public ByteMatrix(int width, int height) {
+ bytes = new byte[height][width];
+ this.width = width;
+ this.height = height;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public byte get(int x, int y) {
+ return bytes[y][x];
+ }
+
+ /**
+ * @return an internal representation as bytes, in row-major order. array[y][x] represents point (x,y)
+ */
+ public byte[][] getArray() {
+ return bytes;
+ }
+
+ public void set(int x, int y, byte value) {
+ bytes[y][x] = value;
+ }
+
+ public void set(int x, int y, int value) {
+ bytes[y][x] = (byte) value;
+ }
+
+ public void set(int x, int y, boolean value) {
+ bytes[y][x] = (byte) (value ? 1 : 0);
+ }
+
+ public void clear(byte value) {
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ bytes[y][x] = value;
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder(2 * width * height + 2);
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ switch (bytes[y][x]) {
+ case 0:
+ result.append(" 0");
+ break;
+ case 1:
+ result.append(" 1");
+ break;
+ default:
+ result.append(" ");
+ break;
+ }
+ }
+ result.append('\n');
+ }
+ return result.toString();
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java
new file mode 100644
index 000000000..425ca8ed0
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/Encoder.java
@@ -0,0 +1,578 @@
+/*
+ * 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.qrcode.encoder;
+
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitArray;
+import com.google.zxing.common.CharacterSetECI;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import com.google.zxing.qrcode.decoder.Mode;
+import com.google.zxing.qrcode.decoder.Version;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * @author satorux@google.com (Satoru Takabayashi) - creator
+ * @author dswitkin@google.com (Daniel Switkin) - ported from C++
+ */
+public final class Encoder {
+
+ // The original table is defined in the table 5 of JISX0510:2004 (p.19).
+ private static final int[] ALPHANUMERIC_TABLE = {
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x00-0x0f
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0x10-0x1f
+ 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, // 0x20-0x2f
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, // 0x30-0x3f
+ -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, // 0x40-0x4f
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, // 0x50-0x5f
+ };
+
+ static final String DEFAULT_BYTE_MODE_ENCODING = "ISO-8859-1";
+
+ private Encoder() {
+ }
+
+ // The mask penalty calculation is complicated. See Table 21 of JISX0510:2004 (p.45) for details.
+ // Basically it applies four rules and summate all penalties.
+ private static int calculateMaskPenalty(ByteMatrix matrix) {
+ return MaskUtil.applyMaskPenaltyRule1(matrix)
+ + MaskUtil.applyMaskPenaltyRule2(matrix)
+ + MaskUtil.applyMaskPenaltyRule3(matrix)
+ + MaskUtil.applyMaskPenaltyRule4(matrix);
+ }
+
+ /**
+ * @param content text to encode
+ * @param ecLevel error correction level to use
+ * @return {@link QRCode} representing the encoded QR code
+ * @throws WriterException if encoding can't succeed, because of for example invalid content
+ * or configuration
+ */
+ public static QRCode encode(String content, ErrorCorrectionLevel ecLevel) throws WriterException {
+ return encode(content, ecLevel, null);
+ }
+
+ public static QRCode encode(String content,
+ ErrorCorrectionLevel ecLevel,
+ Map hints) throws WriterException {
+
+ // Determine what character encoding has been specified by the caller, if any
+ String encoding = hints == null ? null : (String) hints.get(EncodeHintType.CHARACTER_SET);
+ if (encoding == null) {
+ encoding = DEFAULT_BYTE_MODE_ENCODING;
+ }
+
+ // Pick an encoding mode appropriate for the content. Note that this will not attempt to use
+ // multiple modes / segments even if that were more efficient. Twould be nice.
+ Mode mode = chooseMode(content, encoding);
+
+ // This will store the header information, like mode and
+ // length, as well as "header" segments like an ECI segment.
+ BitArray headerBits = new BitArray();
+
+ // Append ECI segment if applicable
+ if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.equals(encoding)) {
+ CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
+ if (eci != null) {
+ appendECI(eci, headerBits);
+ }
+ }
+
+ // (With ECI in place,) Write the mode marker
+ appendModeInfo(mode, headerBits);
+
+ // Collect data within the main segment, separately, to count its size if needed. Don't add it to
+ // main payload yet.
+ BitArray dataBits = new BitArray();
+ appendBytes(content, mode, dataBits, encoding);
+
+ // Hard part: need to know version to know how many bits length takes. But need to know how many
+ // bits it takes to know version. First we take a guess at version by assuming version will be
+ // the minimum, 1:
+
+ int provisionalBitsNeeded = headerBits.getSize()
+ + mode.getCharacterCountBits(Version.getVersionForNumber(1))
+ + dataBits.getSize();
+ Version provisionalVersion = chooseVersion(provisionalBitsNeeded, ecLevel);
+
+ // Use that guess to calculate the right version. I am still not sure this works in 100% of cases.
+
+ int bitsNeeded = headerBits.getSize()
+ + mode.getCharacterCountBits(provisionalVersion)
+ + dataBits.getSize();
+ Version version = chooseVersion(bitsNeeded, ecLevel);
+
+ BitArray headerAndDataBits = new BitArray();
+ headerAndDataBits.appendBitArray(headerBits);
+ // Find "length" of main segment and write it
+ int numLetters = mode == Mode.BYTE ? dataBits.getSizeInBytes() : content.length();
+ appendLengthInfo(numLetters, version, mode, headerAndDataBits);
+ // Put data together into the overall payload
+ headerAndDataBits.appendBitArray(dataBits);
+
+ Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
+ int numDataBytes = version.getTotalCodewords() - ecBlocks.getTotalECCodewords();
+
+ // Terminate the bits properly.
+ terminateBits(numDataBytes, headerAndDataBits);
+
+ // Interleave data bits with error correction code.
+ BitArray finalBits = interleaveWithECBytes(headerAndDataBits,
+ version.getTotalCodewords(),
+ numDataBytes,
+ ecBlocks.getNumBlocks());
+
+ QRCode qrCode = new QRCode();
+
+ qrCode.setECLevel(ecLevel);
+ qrCode.setMode(mode);
+ qrCode.setVersion(version);
+
+ // Choose the mask pattern and set to "qrCode".
+ int dimension = version.getDimensionForVersion();
+ ByteMatrix matrix = new ByteMatrix(dimension, dimension);
+ int maskPattern = chooseMaskPattern(finalBits, ecLevel, version, matrix);
+ qrCode.setMaskPattern(maskPattern);
+
+ // Build the matrix and set it to "qrCode".
+ MatrixUtil.buildMatrix(finalBits, ecLevel, version, maskPattern, matrix);
+ qrCode.setMatrix(matrix);
+
+ return qrCode;
+ }
+
+ /**
+ * @return the code point of the table used in alphanumeric mode or
+ * -1 if there is no corresponding code in the table.
+ */
+ static int getAlphanumericCode(int code) {
+ if (code < ALPHANUMERIC_TABLE.length) {
+ return ALPHANUMERIC_TABLE[code];
+ }
+ return -1;
+ }
+
+ public static Mode chooseMode(String content) {
+ return chooseMode(content, null);
+ }
+
+ /**
+ * Choose the best mode by examining the content. Note that 'encoding' is used as a hint;
+ * if it is Shift_JIS, and the input is only double-byte Kanji, then we return {@link Mode#KANJI}.
+ */
+ private static Mode chooseMode(String content, String encoding) {
+ if ("Shift_JIS".equals(encoding)) {
+ // Choose Kanji mode if all input are double-byte characters
+ return isOnlyDoubleByteKanji(content) ? Mode.KANJI : Mode.BYTE;
+ }
+ boolean hasNumeric = false;
+ boolean hasAlphanumeric = false;
+ for (int i = 0; i < content.length(); ++i) {
+ char c = content.charAt(i);
+ if (c >= '0' && c <= '9') {
+ hasNumeric = true;
+ } else if (getAlphanumericCode(c) != -1) {
+ hasAlphanumeric = true;
+ } else {
+ return Mode.BYTE;
+ }
+ }
+ if (hasAlphanumeric) {
+ return Mode.ALPHANUMERIC;
+ }
+ if (hasNumeric) {
+ return Mode.NUMERIC;
+ }
+ return Mode.BYTE;
+ }
+
+ private static boolean isOnlyDoubleByteKanji(String content) {
+ byte[] bytes;
+ try {
+ bytes = content.getBytes("Shift_JIS");
+ } catch (UnsupportedEncodingException ignored) {
+ return false;
+ }
+ int length = bytes.length;
+ if (length % 2 != 0) {
+ return false;
+ }
+ for (int i = 0; i < length; i += 2) {
+ int byte1 = bytes[i] & 0xFF;
+ if ((byte1 < 0x81 || byte1 > 0x9F) && (byte1 < 0xE0 || byte1 > 0xEB)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static int chooseMaskPattern(BitArray bits,
+ ErrorCorrectionLevel ecLevel,
+ Version version,
+ ByteMatrix matrix) throws WriterException {
+
+ int minPenalty = Integer.MAX_VALUE; // Lower penalty is better.
+ int bestMaskPattern = -1;
+ // We try all mask patterns to choose the best one.
+ for (int maskPattern = 0; maskPattern < QRCode.NUM_MASK_PATTERNS; maskPattern++) {
+ MatrixUtil.buildMatrix(bits, ecLevel, version, maskPattern, matrix);
+ int penalty = calculateMaskPenalty(matrix);
+ if (penalty < minPenalty) {
+ minPenalty = penalty;
+ bestMaskPattern = maskPattern;
+ }
+ }
+ return bestMaskPattern;
+ }
+
+ private static Version chooseVersion(int numInputBits, ErrorCorrectionLevel ecLevel) throws WriterException {
+ // In the following comments, we use numbers of Version 7-H.
+ for (int versionNum = 1; versionNum <= 40; versionNum++) {
+ Version version = Version.getVersionForNumber(versionNum);
+ // numBytes = 196
+ int numBytes = version.getTotalCodewords();
+ // getNumECBytes = 130
+ Version.ECBlocks ecBlocks = version.getECBlocksForLevel(ecLevel);
+ int numEcBytes = ecBlocks.getTotalECCodewords();
+ // getNumDataBytes = 196 - 130 = 66
+ int numDataBytes = numBytes - numEcBytes;
+ int totalInputBytes = (numInputBits + 7) / 8;
+ if (numDataBytes >= totalInputBytes) {
+ return version;
+ }
+ }
+ throw new WriterException("Data too big");
+ }
+
+ /**
+ * Terminate bits as described in 8.4.8 and 8.4.9 of JISX0510:2004 (p.24).
+ */
+ static void terminateBits(int numDataBytes, BitArray bits) throws WriterException {
+ int capacity = numDataBytes << 3;
+ if (bits.getSize() > capacity) {
+ throw new WriterException("data bits cannot fit in the QR Code" + bits.getSize() + " > " +
+ capacity);
+ }
+ for (int i = 0; i < 4 && bits.getSize() < capacity; ++i) {
+ bits.appendBit(false);
+ }
+ // Append termination bits. See 8.4.8 of JISX0510:2004 (p.24) for details.
+ // If the last byte isn't 8-bit aligned, we'll add padding bits.
+ int numBitsInLastByte = bits.getSize() & 0x07;
+ if (numBitsInLastByte > 0) {
+ for (int i = numBitsInLastByte; i < 8; i++) {
+ bits.appendBit(false);
+ }
+ }
+ // If we have more space, we'll fill the space with padding patterns defined in 8.4.9 (p.24).
+ int numPaddingBytes = numDataBytes - bits.getSizeInBytes();
+ for (int i = 0; i < numPaddingBytes; ++i) {
+ bits.appendBits((i & 0x01) == 0 ? 0xEC : 0x11, 8);
+ }
+ if (bits.getSize() != capacity) {
+ throw new WriterException("Bits size does not equal capacity");
+ }
+ }
+
+ /**
+ * Get number of data bytes and number of error correction bytes for block id "blockID". Store
+ * the result in "numDataBytesInBlock", and "numECBytesInBlock". See table 12 in 8.5.1 of
+ * JISX0510:2004 (p.30)
+ */
+ static void getNumDataBytesAndNumECBytesForBlockID(int numTotalBytes,
+ int numDataBytes,
+ int numRSBlocks,
+ int blockID,
+ int[] numDataBytesInBlock,
+ int[] numECBytesInBlock) throws WriterException {
+ if (blockID >= numRSBlocks) {
+ throw new WriterException("Block ID too large");
+ }
+ // numRsBlocksInGroup2 = 196 % 5 = 1
+ int numRsBlocksInGroup2 = numTotalBytes % numRSBlocks;
+ // numRsBlocksInGroup1 = 5 - 1 = 4
+ int numRsBlocksInGroup1 = numRSBlocks - numRsBlocksInGroup2;
+ // numTotalBytesInGroup1 = 196 / 5 = 39
+ int numTotalBytesInGroup1 = numTotalBytes / numRSBlocks;
+ // numTotalBytesInGroup2 = 39 + 1 = 40
+ int numTotalBytesInGroup2 = numTotalBytesInGroup1 + 1;
+ // numDataBytesInGroup1 = 66 / 5 = 13
+ int numDataBytesInGroup1 = numDataBytes / numRSBlocks;
+ // numDataBytesInGroup2 = 13 + 1 = 14
+ int numDataBytesInGroup2 = numDataBytesInGroup1 + 1;
+ // numEcBytesInGroup1 = 39 - 13 = 26
+ int numEcBytesInGroup1 = numTotalBytesInGroup1 - numDataBytesInGroup1;
+ // numEcBytesInGroup2 = 40 - 14 = 26
+ int numEcBytesInGroup2 = numTotalBytesInGroup2 - numDataBytesInGroup2;
+ // Sanity checks.
+ // 26 = 26
+ if (numEcBytesInGroup1 != numEcBytesInGroup2) {
+ throw new WriterException("EC bytes mismatch");
+ }
+ // 5 = 4 + 1.
+ if (numRSBlocks != numRsBlocksInGroup1 + numRsBlocksInGroup2) {
+ throw new WriterException("RS blocks mismatch");
+ }
+ // 196 = (13 + 26) * 4 + (14 + 26) * 1
+ if (numTotalBytes !=
+ ((numDataBytesInGroup1 + numEcBytesInGroup1) *
+ numRsBlocksInGroup1) +
+ ((numDataBytesInGroup2 + numEcBytesInGroup2) *
+ numRsBlocksInGroup2)) {
+ throw new WriterException("Total bytes mismatch");
+ }
+
+ if (blockID < numRsBlocksInGroup1) {
+ numDataBytesInBlock[0] = numDataBytesInGroup1;
+ numECBytesInBlock[0] = numEcBytesInGroup1;
+ } else {
+ numDataBytesInBlock[0] = numDataBytesInGroup2;
+ numECBytesInBlock[0] = numEcBytesInGroup2;
+ }
+ }
+
+ /**
+ * Interleave "bits" with corresponding error correction bytes. On success, store the result in
+ * "result". The interleave rule is complicated. See 8.6 of JISX0510:2004 (p.37) for details.
+ */
+ static BitArray interleaveWithECBytes(BitArray bits,
+ int numTotalBytes,
+ int numDataBytes,
+ int numRSBlocks) throws WriterException {
+
+ // "bits" must have "getNumDataBytes" bytes of data.
+ if (bits.getSizeInBytes() != numDataBytes) {
+ throw new WriterException("Number of bits and data bytes does not match");
+ }
+
+ // Step 1. Divide data bytes into blocks and generate error correction bytes for them. We'll
+ // store the divided data bytes blocks and error correction bytes blocks into "blocks".
+ int dataBytesOffset = 0;
+ int maxNumDataBytes = 0;
+ int maxNumEcBytes = 0;
+
+ // Since, we know the number of reedsolmon blocks, we can initialize the vector with the number.
+ Collection blocks = new ArrayList<>(numRSBlocks);
+
+ for (int i = 0; i < numRSBlocks; ++i) {
+ int[] numDataBytesInBlock = new int[1];
+ int[] numEcBytesInBlock = new int[1];
+ getNumDataBytesAndNumECBytesForBlockID(
+ numTotalBytes, numDataBytes, numRSBlocks, i,
+ numDataBytesInBlock, numEcBytesInBlock);
+
+ int size = numDataBytesInBlock[0];
+ byte[] dataBytes = new byte[size];
+ bits.toBytes(8*dataBytesOffset, dataBytes, 0, size);
+ byte[] ecBytes = generateECBytes(dataBytes, numEcBytesInBlock[0]);
+ blocks.add(new BlockPair(dataBytes, ecBytes));
+
+ maxNumDataBytes = Math.max(maxNumDataBytes, size);
+ maxNumEcBytes = Math.max(maxNumEcBytes, ecBytes.length);
+ dataBytesOffset += numDataBytesInBlock[0];
+ }
+ if (numDataBytes != dataBytesOffset) {
+ throw new WriterException("Data bytes does not match offset");
+ }
+
+ BitArray result = new BitArray();
+
+ // First, place data blocks.
+ for (int i = 0; i < maxNumDataBytes; ++i) {
+ for (BlockPair block : blocks) {
+ byte[] dataBytes = block.getDataBytes();
+ if (i < dataBytes.length) {
+ result.appendBits(dataBytes[i], 8);
+ }
+ }
+ }
+ // Then, place error correction blocks.
+ for (int i = 0; i < maxNumEcBytes; ++i) {
+ for (BlockPair block : blocks) {
+ byte[] ecBytes = block.getErrorCorrectionBytes();
+ if (i < ecBytes.length) {
+ result.appendBits(ecBytes[i], 8);
+ }
+ }
+ }
+ if (numTotalBytes != result.getSizeInBytes()) { // Should be same.
+ throw new WriterException("Interleaving error: " + numTotalBytes + " and " +
+ result.getSizeInBytes() + " differ.");
+ }
+
+ return result;
+ }
+
+ static byte[] generateECBytes(byte[] dataBytes, int numEcBytesInBlock) {
+ int numDataBytes = dataBytes.length;
+ int[] toEncode = new int[numDataBytes + numEcBytesInBlock];
+ for (int i = 0; i < numDataBytes; i++) {
+ toEncode[i] = dataBytes[i] & 0xFF;
+ }
+ new ReedSolomonEncoder(GenericGF.QR_CODE_FIELD_256).encode(toEncode, numEcBytesInBlock);
+
+ byte[] ecBytes = new byte[numEcBytesInBlock];
+ for (int i = 0; i < numEcBytesInBlock; i++) {
+ ecBytes[i] = (byte) toEncode[numDataBytes + i];
+ }
+ return ecBytes;
+ }
+
+ /**
+ * Append mode info. On success, store the result in "bits".
+ */
+ static void appendModeInfo(Mode mode, BitArray bits) {
+ bits.appendBits(mode.getBits(), 4);
+ }
+
+
+ /**
+ * Append length info. On success, store the result in "bits".
+ */
+ static void appendLengthInfo(int numLetters, Version version, Mode mode, BitArray bits) throws WriterException {
+ int numBits = mode.getCharacterCountBits(version);
+ if (numLetters >= (1 << numBits)) {
+ throw new WriterException(numLetters + " is bigger than " + ((1 << numBits) - 1));
+ }
+ bits.appendBits(numLetters, numBits);
+ }
+
+ /**
+ * Append "bytes" in "mode" mode (encoding) into "bits". On success, store the result in "bits".
+ */
+ static void appendBytes(String content,
+ Mode mode,
+ BitArray bits,
+ String encoding) throws WriterException {
+ switch (mode) {
+ case NUMERIC:
+ appendNumericBytes(content, bits);
+ break;
+ case ALPHANUMERIC:
+ appendAlphanumericBytes(content, bits);
+ break;
+ case BYTE:
+ append8BitBytes(content, bits, encoding);
+ break;
+ case KANJI:
+ appendKanjiBytes(content, bits);
+ break;
+ default:
+ throw new WriterException("Invalid mode: " + mode);
+ }
+ }
+
+ static void appendNumericBytes(CharSequence content, BitArray bits) {
+ int length = content.length();
+ int i = 0;
+ while (i < length) {
+ int num1 = content.charAt(i) - '0';
+ if (i + 2 < length) {
+ // Encode three numeric letters in ten bits.
+ int num2 = content.charAt(i + 1) - '0';
+ int num3 = content.charAt(i + 2) - '0';
+ bits.appendBits(num1 * 100 + num2 * 10 + num3, 10);
+ i += 3;
+ } else if (i + 1 < length) {
+ // Encode two numeric letters in seven bits.
+ int num2 = content.charAt(i + 1) - '0';
+ bits.appendBits(num1 * 10 + num2, 7);
+ i += 2;
+ } else {
+ // Encode one numeric letter in four bits.
+ bits.appendBits(num1, 4);
+ i++;
+ }
+ }
+ }
+
+ static void appendAlphanumericBytes(CharSequence content, BitArray bits) throws WriterException {
+ int length = content.length();
+ int i = 0;
+ while (i < length) {
+ int code1 = getAlphanumericCode(content.charAt(i));
+ if (code1 == -1) {
+ throw new WriterException();
+ }
+ if (i + 1 < length) {
+ int code2 = getAlphanumericCode(content.charAt(i + 1));
+ if (code2 == -1) {
+ throw new WriterException();
+ }
+ // Encode two alphanumeric letters in 11 bits.
+ bits.appendBits(code1 * 45 + code2, 11);
+ i += 2;
+ } else {
+ // Encode one alphanumeric letter in six bits.
+ bits.appendBits(code1, 6);
+ i++;
+ }
+ }
+ }
+
+ static void append8BitBytes(String content, BitArray bits, String encoding)
+ throws WriterException {
+ byte[] bytes;
+ try {
+ bytes = content.getBytes(encoding);
+ } catch (UnsupportedEncodingException uee) {
+ throw new WriterException(uee);
+ }
+ for (byte b : bytes) {
+ bits.appendBits(b, 8);
+ }
+ }
+
+ static void appendKanjiBytes(String content, BitArray bits) throws WriterException {
+ byte[] bytes;
+ try {
+ bytes = content.getBytes("Shift_JIS");
+ } catch (UnsupportedEncodingException uee) {
+ throw new WriterException(uee);
+ }
+ int length = bytes.length;
+ for (int i = 0; i < length; i += 2) {
+ int byte1 = bytes[i] & 0xFF;
+ int byte2 = bytes[i + 1] & 0xFF;
+ int code = (byte1 << 8) | byte2;
+ int subtracted = -1;
+ if (code >= 0x8140 && code <= 0x9ffc) {
+ subtracted = code - 0x8140;
+ } else if (code >= 0xe040 && code <= 0xebbf) {
+ subtracted = code - 0xc140;
+ }
+ if (subtracted == -1) {
+ throw new WriterException("Invalid byte sequence");
+ }
+ int encoded = ((subtracted >> 8) * 0xc0) + (subtracted & 0xff);
+ bits.appendBits(encoded, 13);
+ }
+ }
+
+ private static void appendECI(CharacterSetECI eci, BitArray bits) {
+ bits.appendBits(Mode.ECI.getBits(), 4);
+ // This is correct for values up to 127, which is all we need now.
+ bits.appendBits(eci.getValue(), 8);
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java
new file mode 100644
index 000000000..6b0062bca
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java
@@ -0,0 +1,217 @@
+/*
+ * 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.qrcode.encoder;
+
+/**
+ * @author Satoru Takabayashi
+ * @author Daniel Switkin
+ * @author Sean Owen
+ */
+final class MaskUtil {
+
+ // Penalty weights from section 6.8.2.1
+ private static final int N1 = 3;
+ private static final int N2 = 3;
+ private static final int N3 = 40;
+ private static final int N4 = 10;
+
+ private MaskUtil() {
+ // do nothing
+ }
+
+ /**
+ * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
+ * give penalty to them. Example: 00000 or 11111.
+ */
+ static int applyMaskPenaltyRule1(ByteMatrix matrix) {
+ return applyMaskPenaltyRule1Internal(matrix, true) + applyMaskPenaltyRule1Internal(matrix, false);
+ }
+
+ /**
+ * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
+ * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a
+ * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block.
+ */
+ static int applyMaskPenaltyRule2(ByteMatrix matrix) {
+ int penalty = 0;
+ byte[][] array = matrix.getArray();
+ int width = matrix.getWidth();
+ int height = matrix.getHeight();
+ for (int y = 0; y < height - 1; y++) {
+ for (int x = 0; x < width - 1; x++) {
+ int value = array[y][x];
+ if (value == array[y][x + 1] && value == array[y + 1][x] && value == array[y + 1][x + 1]) {
+ penalty++;
+ }
+ }
+ }
+ return N2 * penalty;
+ }
+
+ /**
+ * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4
+ * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we
+ * find patterns like 000010111010000, we give penalty once.
+ */
+ static int applyMaskPenaltyRule3(ByteMatrix matrix) {
+ int numPenalties = 0;
+ byte[][] array = matrix.getArray();
+ int width = matrix.getWidth();
+ int height = matrix.getHeight();
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ byte[] arrayY = array[y]; // We can at least optimize this access
+ if (x + 6 < width &&
+ arrayY[x] == 1 &&
+ arrayY[x + 1] == 0 &&
+ arrayY[x + 2] == 1 &&
+ arrayY[x + 3] == 1 &&
+ arrayY[x + 4] == 1 &&
+ arrayY[x + 5] == 0 &&
+ arrayY[x + 6] == 1 &&
+ (isWhiteHorizontal(arrayY, x - 4, x) || isWhiteHorizontal(arrayY, x + 7, x + 11))) {
+ numPenalties++;
+ }
+ if (y + 6 < height &&
+ array[y][x] == 1 &&
+ array[y + 1][x] == 0 &&
+ array[y + 2][x] == 1 &&
+ array[y + 3][x] == 1 &&
+ array[y + 4][x] == 1 &&
+ array[y + 5][x] == 0 &&
+ array[y + 6][x] == 1 &&
+ (isWhiteVertical(array, x, y - 4, y) || isWhiteVertical(array, x, y + 7, y + 11))) {
+ numPenalties++;
+ }
+ }
+ }
+ return numPenalties * N3;
+ }
+
+ private static boolean isWhiteHorizontal(byte[] rowArray, int from, int to) {
+ for (int i = from; i < to; i++) {
+ if (i >= 0 && i < rowArray.length && rowArray[i] == 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isWhiteVertical(byte[][] array, int col, int from, int to) {
+ for (int i = from; i < to; i++) {
+ if (i >= 0 && i < array.length && array[i][col] == 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
+ * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance.
+ */
+ static int applyMaskPenaltyRule4(ByteMatrix matrix) {
+ int numDarkCells = 0;
+ byte[][] array = matrix.getArray();
+ int width = matrix.getWidth();
+ int height = matrix.getHeight();
+ for (int y = 0; y < height; y++) {
+ byte[] arrayY = array[y];
+ for (int x = 0; x < width; x++) {
+ if (arrayY[x] == 1) {
+ numDarkCells++;
+ }
+ }
+ }
+ int numTotalCells = matrix.getHeight() * matrix.getWidth();
+ int fivePercentVariances = Math.abs(numDarkCells * 2 - numTotalCells) * 10 / numTotalCells;
+ return fivePercentVariances * N4;
+ }
+
+ /**
+ * Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask
+ * pattern conditions.
+ */
+ static boolean getDataMaskBit(int maskPattern, int x, int y) {
+ int intermediate;
+ int temp;
+ switch (maskPattern) {
+ case 0:
+ intermediate = (y + x) & 0x1;
+ break;
+ case 1:
+ intermediate = y & 0x1;
+ break;
+ case 2:
+ intermediate = x % 3;
+ break;
+ case 3:
+ intermediate = (y + x) % 3;
+ break;
+ case 4:
+ intermediate = ((y >>> 1) + (x / 3)) & 0x1;
+ break;
+ case 5:
+ temp = y * x;
+ intermediate = (temp & 0x1) + (temp % 3);
+ break;
+ case 6:
+ temp = y * x;
+ intermediate = ((temp & 0x1) + (temp % 3)) & 0x1;
+ break;
+ case 7:
+ temp = y * x;
+ intermediate = ((temp % 3) + ((y + x) & 0x1)) & 0x1;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid mask pattern: " + maskPattern);
+ }
+ return intermediate == 0;
+ }
+
+ /**
+ * Helper function for applyMaskPenaltyRule1. We need this for doing this calculation in both
+ * vertical and horizontal orders respectively.
+ */
+ private static int applyMaskPenaltyRule1Internal(ByteMatrix matrix, boolean isHorizontal) {
+ int penalty = 0;
+ int iLimit = isHorizontal ? matrix.getHeight() : matrix.getWidth();
+ int jLimit = isHorizontal ? matrix.getWidth() : matrix.getHeight();
+ byte[][] array = matrix.getArray();
+ for (int i = 0; i < iLimit; i++) {
+ int numSameBitCells = 0;
+ int prevBit = -1;
+ for (int j = 0; j < jLimit; j++) {
+ int bit = isHorizontal ? array[i][j] : array[j][i];
+ if (bit == prevBit) {
+ numSameBitCells++;
+ } else {
+ if (numSameBitCells >= 5) {
+ penalty += N1 + (numSameBitCells - 5);
+ }
+ numSameBitCells = 1; // Include the cell itself.
+ prevBit = bit;
+ }
+ }
+ if (numSameBitCells >= 5) {
+ penalty += N1 + (numSameBitCells - 5);
+ }
+ }
+ return penalty;
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/MatrixUtil.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/MatrixUtil.java
new file mode 100644
index 000000000..37e21fa2b
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/MatrixUtil.java
@@ -0,0 +1,482 @@
+/*
+ * 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.qrcode.encoder;
+
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitArray;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import com.google.zxing.qrcode.decoder.Version;
+
+/**
+ * @author satorux@google.com (Satoru Takabayashi) - creator
+ * @author dswitkin@google.com (Daniel Switkin) - ported from C++
+ */
+final class MatrixUtil {
+
+ private MatrixUtil() {
+ // do nothing
+ }
+
+ private static final int[][] POSITION_DETECTION_PATTERN = {
+ {1, 1, 1, 1, 1, 1, 1},
+ {1, 0, 0, 0, 0, 0, 1},
+ {1, 0, 1, 1, 1, 0, 1},
+ {1, 0, 1, 1, 1, 0, 1},
+ {1, 0, 1, 1, 1, 0, 1},
+ {1, 0, 0, 0, 0, 0, 1},
+ {1, 1, 1, 1, 1, 1, 1},
+ };
+
+ private static final int[][] POSITION_ADJUSTMENT_PATTERN = {
+ {1, 1, 1, 1, 1},
+ {1, 0, 0, 0, 1},
+ {1, 0, 1, 0, 1},
+ {1, 0, 0, 0, 1},
+ {1, 1, 1, 1, 1},
+ };
+
+ // From Appendix E. Table 1, JIS0510X:2004 (p 71). The table was double-checked by komatsu.
+ private static final int[][] POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE = {
+ {-1, -1, -1, -1, -1, -1, -1}, // Version 1
+ { 6, 18, -1, -1, -1, -1, -1}, // Version 2
+ { 6, 22, -1, -1, -1, -1, -1}, // Version 3
+ { 6, 26, -1, -1, -1, -1, -1}, // Version 4
+ { 6, 30, -1, -1, -1, -1, -1}, // Version 5
+ { 6, 34, -1, -1, -1, -1, -1}, // Version 6
+ { 6, 22, 38, -1, -1, -1, -1}, // Version 7
+ { 6, 24, 42, -1, -1, -1, -1}, // Version 8
+ { 6, 26, 46, -1, -1, -1, -1}, // Version 9
+ { 6, 28, 50, -1, -1, -1, -1}, // Version 10
+ { 6, 30, 54, -1, -1, -1, -1}, // Version 11
+ { 6, 32, 58, -1, -1, -1, -1}, // Version 12
+ { 6, 34, 62, -1, -1, -1, -1}, // Version 13
+ { 6, 26, 46, 66, -1, -1, -1}, // Version 14
+ { 6, 26, 48, 70, -1, -1, -1}, // Version 15
+ { 6, 26, 50, 74, -1, -1, -1}, // Version 16
+ { 6, 30, 54, 78, -1, -1, -1}, // Version 17
+ { 6, 30, 56, 82, -1, -1, -1}, // Version 18
+ { 6, 30, 58, 86, -1, -1, -1}, // Version 19
+ { 6, 34, 62, 90, -1, -1, -1}, // Version 20
+ { 6, 28, 50, 72, 94, -1, -1}, // Version 21
+ { 6, 26, 50, 74, 98, -1, -1}, // Version 22
+ { 6, 30, 54, 78, 102, -1, -1}, // Version 23
+ { 6, 28, 54, 80, 106, -1, -1}, // Version 24
+ { 6, 32, 58, 84, 110, -1, -1}, // Version 25
+ { 6, 30, 58, 86, 114, -1, -1}, // Version 26
+ { 6, 34, 62, 90, 118, -1, -1}, // Version 27
+ { 6, 26, 50, 74, 98, 122, -1}, // Version 28
+ { 6, 30, 54, 78, 102, 126, -1}, // Version 29
+ { 6, 26, 52, 78, 104, 130, -1}, // Version 30
+ { 6, 30, 56, 82, 108, 134, -1}, // Version 31
+ { 6, 34, 60, 86, 112, 138, -1}, // Version 32
+ { 6, 30, 58, 86, 114, 142, -1}, // Version 33
+ { 6, 34, 62, 90, 118, 146, -1}, // Version 34
+ { 6, 30, 54, 78, 102, 126, 150}, // Version 35
+ { 6, 24, 50, 76, 102, 128, 154}, // Version 36
+ { 6, 28, 54, 80, 106, 132, 158}, // Version 37
+ { 6, 32, 58, 84, 110, 136, 162}, // Version 38
+ { 6, 26, 54, 82, 110, 138, 166}, // Version 39
+ { 6, 30, 58, 86, 114, 142, 170}, // Version 40
+ };
+
+ // Type info cells at the left top corner.
+ private static final int[][] TYPE_INFO_COORDINATES = {
+ {8, 0},
+ {8, 1},
+ {8, 2},
+ {8, 3},
+ {8, 4},
+ {8, 5},
+ {8, 7},
+ {8, 8},
+ {7, 8},
+ {5, 8},
+ {4, 8},
+ {3, 8},
+ {2, 8},
+ {1, 8},
+ {0, 8},
+ };
+
+ // From Appendix D in JISX0510:2004 (p. 67)
+ private static final int VERSION_INFO_POLY = 0x1f25; // 1 1111 0010 0101
+
+ // From Appendix C in JISX0510:2004 (p.65).
+ private static final int TYPE_INFO_POLY = 0x537;
+ private static final int TYPE_INFO_MASK_PATTERN = 0x5412;
+
+ // Set all cells to -1. -1 means that the cell is empty (not set yet).
+ //
+ // JAVAPORT: We shouldn't need to do this at all. The code should be rewritten to begin encoding
+ // with the ByteMatrix initialized all to zero.
+ static void clearMatrix(ByteMatrix matrix) {
+ matrix.clear((byte) -1);
+ }
+
+ // Build 2D matrix of QR Code from "dataBits" with "ecLevel", "version" and "getMaskPattern". On
+ // success, store the result in "matrix" and return true.
+ static void buildMatrix(BitArray dataBits,
+ ErrorCorrectionLevel ecLevel,
+ Version version,
+ int maskPattern,
+ ByteMatrix matrix) throws WriterException {
+ clearMatrix(matrix);
+ embedBasicPatterns(version, matrix);
+ // Type information appear with any version.
+ embedTypeInfo(ecLevel, maskPattern, matrix);
+ // Version info appear if version >= 7.
+ maybeEmbedVersionInfo(version, matrix);
+ // Data should be embedded at end.
+ embedDataBits(dataBits, maskPattern, matrix);
+ }
+
+ // Embed basic patterns. On success, modify the matrix and return true.
+ // The basic patterns are:
+ // - Position detection patterns
+ // - Timing patterns
+ // - Dark dot at the left bottom corner
+ // - Position adjustment patterns, if need be
+ static void embedBasicPatterns(Version version, ByteMatrix matrix) throws WriterException {
+ // Let's get started with embedding big squares at corners.
+ embedPositionDetectionPatternsAndSeparators(matrix);
+ // Then, embed the dark dot at the left bottom corner.
+ embedDarkDotAtLeftBottomCorner(matrix);
+
+ // Position adjustment patterns appear if version >= 2.
+ maybeEmbedPositionAdjustmentPatterns(version, matrix);
+ // Timing patterns should be embedded after position adj. patterns.
+ embedTimingPatterns(matrix);
+ }
+
+ // Embed type information. On success, modify the matrix.
+ static void embedTypeInfo(ErrorCorrectionLevel ecLevel, int maskPattern, ByteMatrix matrix)
+ throws WriterException {
+ BitArray typeInfoBits = new BitArray();
+ makeTypeInfoBits(ecLevel, maskPattern, typeInfoBits);
+
+ for (int i = 0; i < typeInfoBits.getSize(); ++i) {
+ // Place bits in LSB to MSB order. LSB (least significant bit) is the last value in
+ // "typeInfoBits".
+ boolean bit = typeInfoBits.get(typeInfoBits.getSize() - 1 - i);
+
+ // Type info bits at the left top corner. See 8.9 of JISX0510:2004 (p.46).
+ int x1 = TYPE_INFO_COORDINATES[i][0];
+ int y1 = TYPE_INFO_COORDINATES[i][1];
+ matrix.set(x1, y1, bit);
+
+ if (i < 8) {
+ // Right top corner.
+ int x2 = matrix.getWidth() - i - 1;
+ int y2 = 8;
+ matrix.set(x2, y2, bit);
+ } else {
+ // Left bottom corner.
+ int x2 = 8;
+ int y2 = matrix.getHeight() - 7 + (i - 8);
+ matrix.set(x2, y2, bit);
+ }
+ }
+ }
+
+ // Embed version information if need be. On success, modify the matrix and return true.
+ // See 8.10 of JISX0510:2004 (p.47) for how to embed version information.
+ static void maybeEmbedVersionInfo(Version version, ByteMatrix matrix) throws WriterException {
+ if (version.getVersionNumber() < 7) { // Version info is necessary if version >= 7.
+ return; // Don't need version info.
+ }
+ BitArray versionInfoBits = new BitArray();
+ makeVersionInfoBits(version, versionInfoBits);
+
+ int bitIndex = 6 * 3 - 1; // It will decrease from 17 to 0.
+ for (int i = 0; i < 6; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ // Place bits in LSB (least significant bit) to MSB order.
+ boolean bit = versionInfoBits.get(bitIndex);
+ bitIndex--;
+ // Left bottom corner.
+ matrix.set(i, matrix.getHeight() - 11 + j, bit);
+ // Right bottom corner.
+ matrix.set(matrix.getHeight() - 11 + j, i, bit);
+ }
+ }
+ }
+
+ // Embed "dataBits" using "getMaskPattern". On success, modify the matrix and return true.
+ // For debugging purposes, it skips masking process if "getMaskPattern" is -1.
+ // See 8.7 of JISX0510:2004 (p.38) for how to embed data bits.
+ static void embedDataBits(BitArray dataBits, int maskPattern, ByteMatrix matrix)
+ throws WriterException {
+ int bitIndex = 0;
+ int direction = -1;
+ // Start from the right bottom cell.
+ int x = matrix.getWidth() - 1;
+ int y = matrix.getHeight() - 1;
+ while (x > 0) {
+ // Skip the vertical timing pattern.
+ if (x == 6) {
+ x -= 1;
+ }
+ while (y >= 0 && y < matrix.getHeight()) {
+ for (int i = 0; i < 2; ++i) {
+ int xx = x - i;
+ // Skip the cell if it's not empty.
+ if (!isEmpty(matrix.get(xx, y))) {
+ continue;
+ }
+ boolean bit;
+ if (bitIndex < dataBits.getSize()) {
+ bit = dataBits.get(bitIndex);
+ ++bitIndex;
+ } else {
+ // Padding bit. If there is no bit left, we'll fill the left cells with 0, as described
+ // in 8.4.9 of JISX0510:2004 (p. 24).
+ bit = false;
+ }
+
+ // Skip masking if mask_pattern is -1.
+ if (maskPattern != -1 && MaskUtil.getDataMaskBit(maskPattern, xx, y)) {
+ bit = !bit;
+ }
+ matrix.set(xx, y, bit);
+ }
+ y += direction;
+ }
+ direction = -direction; // Reverse the direction.
+ y += direction;
+ x -= 2; // Move to the left.
+ }
+ // All bits should be consumed.
+ if (bitIndex != dataBits.getSize()) {
+ throw new WriterException("Not all bits consumed: " + bitIndex + '/' + dataBits.getSize());
+ }
+ }
+
+ // Return the position of the most significant bit set (to one) in the "value". The most
+ // significant bit is position 32. If there is no bit set, return 0. Examples:
+ // - findMSBSet(0) => 0
+ // - findMSBSet(1) => 1
+ // - findMSBSet(255) => 8
+ static int findMSBSet(int value) {
+ int numDigits = 0;
+ while (value != 0) {
+ value >>>= 1;
+ ++numDigits;
+ }
+ return numDigits;
+ }
+
+ // Calculate BCH (Bose-Chaudhuri-Hocquenghem) code for "value" using polynomial "poly". The BCH
+ // code is used for encoding type information and version information.
+ // Example: Calculation of version information of 7.
+ // f(x) is created from 7.
+ // - 7 = 000111 in 6 bits
+ // - f(x) = x^2 + x^1 + x^0
+ // g(x) is given by the standard (p. 67)
+ // - g(x) = x^12 + x^11 + x^10 + x^9 + x^8 + x^5 + x^2 + 1
+ // Multiply f(x) by x^(18 - 6)
+ // - f'(x) = f(x) * x^(18 - 6)
+ // - f'(x) = x^14 + x^13 + x^12
+ // Calculate the remainder of f'(x) / g(x)
+ // x^2
+ // __________________________________________________
+ // g(x) )x^14 + x^13 + x^12
+ // x^14 + x^13 + x^12 + x^11 + x^10 + x^7 + x^4 + x^2
+ // --------------------------------------------------
+ // x^11 + x^10 + x^7 + x^4 + x^2
+ //
+ // The remainder is x^11 + x^10 + x^7 + x^4 + x^2
+ // Encode it in binary: 110010010100
+ // The return value is 0xc94 (1100 1001 0100)
+ //
+ // Since all coefficients in the polynomials are 1 or 0, we can do the calculation by bit
+ // operations. We don't care if cofficients are positive or negative.
+ static int calculateBCHCode(int value, int poly) {
+ // If poly is "1 1111 0010 0101" (version info poly), msbSetInPoly is 13. We'll subtract 1
+ // from 13 to make it 12.
+ int msbSetInPoly = findMSBSet(poly);
+ value <<= msbSetInPoly - 1;
+ // Do the division business using exclusive-or operations.
+ while (findMSBSet(value) >= msbSetInPoly) {
+ value ^= poly << (findMSBSet(value) - msbSetInPoly);
+ }
+ // Now the "value" is the remainder (i.e. the BCH code)
+ return value;
+ }
+
+ // Make bit vector of type information. On success, store the result in "bits" and return true.
+ // Encode error correction level and mask pattern. See 8.9 of
+ // JISX0510:2004 (p.45) for details.
+ static void makeTypeInfoBits(ErrorCorrectionLevel ecLevel, int maskPattern, BitArray bits)
+ throws WriterException {
+ if (!QRCode.isValidMaskPattern(maskPattern)) {
+ throw new WriterException("Invalid mask pattern");
+ }
+ int typeInfo = (ecLevel.getBits() << 3) | maskPattern;
+ bits.appendBits(typeInfo, 5);
+
+ int bchCode = calculateBCHCode(typeInfo, TYPE_INFO_POLY);
+ bits.appendBits(bchCode, 10);
+
+ BitArray maskBits = new BitArray();
+ maskBits.appendBits(TYPE_INFO_MASK_PATTERN, 15);
+ bits.xor(maskBits);
+
+ if (bits.getSize() != 15) { // Just in case.
+ throw new WriterException("should not happen but we got: " + bits.getSize());
+ }
+ }
+
+ // Make bit vector of version information. On success, store the result in "bits" and return true.
+ // See 8.10 of JISX0510:2004 (p.45) for details.
+ static void makeVersionInfoBits(Version version, BitArray bits) throws WriterException {
+ bits.appendBits(version.getVersionNumber(), 6);
+ int bchCode = calculateBCHCode(version.getVersionNumber(), VERSION_INFO_POLY);
+ bits.appendBits(bchCode, 12);
+
+ if (bits.getSize() != 18) { // Just in case.
+ throw new WriterException("should not happen but we got: " + bits.getSize());
+ }
+ }
+
+ // Check if "value" is empty.
+ private static boolean isEmpty(int value) {
+ return value == -1;
+ }
+
+ private static void embedTimingPatterns(ByteMatrix matrix) {
+ // -8 is for skipping position detection patterns (size 7), and two horizontal/vertical
+ // separation patterns (size 1). Thus, 8 = 7 + 1.
+ for (int i = 8; i < matrix.getWidth() - 8; ++i) {
+ int bit = (i + 1) % 2;
+ // Horizontal line.
+ if (isEmpty(matrix.get(i, 6))) {
+ matrix.set(i, 6, bit);
+ }
+ // Vertical line.
+ if (isEmpty(matrix.get(6, i))) {
+ matrix.set(6, i, bit);
+ }
+ }
+ }
+
+ // Embed the lonely dark dot at left bottom corner. JISX0510:2004 (p.46)
+ private static void embedDarkDotAtLeftBottomCorner(ByteMatrix matrix) throws WriterException {
+ if (matrix.get(8, matrix.getHeight() - 8) == 0) {
+ throw new WriterException();
+ }
+ matrix.set(8, matrix.getHeight() - 8, 1);
+ }
+
+ private static void embedHorizontalSeparationPattern(int xStart,
+ int yStart,
+ ByteMatrix matrix) throws WriterException {
+ for (int x = 0; x < 8; ++x) {
+ if (!isEmpty(matrix.get(xStart + x, yStart))) {
+ throw new WriterException();
+ }
+ matrix.set(xStart + x, yStart, 0);
+ }
+ }
+
+ private static void embedVerticalSeparationPattern(int xStart,
+ int yStart,
+ ByteMatrix matrix) throws WriterException {
+ for (int y = 0; y < 7; ++y) {
+ if (!isEmpty(matrix.get(xStart, yStart + y))) {
+ throw new WriterException();
+ }
+ matrix.set(xStart, yStart + y, 0);
+ }
+ }
+
+ // Note that we cannot unify the function with embedPositionDetectionPattern() despite they are
+ // almost identical, since we cannot write a function that takes 2D arrays in different sizes in
+ // C/C++. We should live with the fact.
+ private static void embedPositionAdjustmentPattern(int xStart, int yStart, ByteMatrix matrix) {
+ for (int y = 0; y < 5; ++y) {
+ for (int x = 0; x < 5; ++x) {
+ matrix.set(xStart + x, yStart + y, POSITION_ADJUSTMENT_PATTERN[y][x]);
+ }
+ }
+ }
+
+ private static void embedPositionDetectionPattern(int xStart, int yStart, ByteMatrix matrix) {
+ for (int y = 0; y < 7; ++y) {
+ for (int x = 0; x < 7; ++x) {
+ matrix.set(xStart + x, yStart + y, POSITION_DETECTION_PATTERN[y][x]);
+ }
+ }
+ }
+
+ // Embed position detection patterns and surrounding vertical/horizontal separators.
+ private static void embedPositionDetectionPatternsAndSeparators(ByteMatrix matrix) throws WriterException {
+ // Embed three big squares at corners.
+ int pdpWidth = POSITION_DETECTION_PATTERN[0].length;
+ // Left top corner.
+ embedPositionDetectionPattern(0, 0, matrix);
+ // Right top corner.
+ embedPositionDetectionPattern(matrix.getWidth() - pdpWidth, 0, matrix);
+ // Left bottom corner.
+ embedPositionDetectionPattern(0, matrix.getWidth() - pdpWidth, matrix);
+
+ // Embed horizontal separation patterns around the squares.
+ int hspWidth = 8;
+ // Left top corner.
+ embedHorizontalSeparationPattern(0, hspWidth - 1, matrix);
+ // Right top corner.
+ embedHorizontalSeparationPattern(matrix.getWidth() - hspWidth,
+ hspWidth - 1, matrix);
+ // Left bottom corner.
+ embedHorizontalSeparationPattern(0, matrix.getWidth() - hspWidth, matrix);
+
+ // Embed vertical separation patterns around the squares.
+ int vspSize = 7;
+ // Left top corner.
+ embedVerticalSeparationPattern(vspSize, 0, matrix);
+ // Right top corner.
+ embedVerticalSeparationPattern(matrix.getHeight() - vspSize - 1, 0, matrix);
+ // Left bottom corner.
+ embedVerticalSeparationPattern(vspSize, matrix.getHeight() - vspSize,
+ matrix);
+ }
+
+ // Embed position adjustment patterns if need be.
+ private static void maybeEmbedPositionAdjustmentPatterns(Version version, ByteMatrix matrix) {
+ if (version.getVersionNumber() < 2) { // The patterns appear if version >= 2
+ return;
+ }
+ int index = version.getVersionNumber() - 1;
+ int[] coordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index];
+ int numCoordinates = POSITION_ADJUSTMENT_PATTERN_COORDINATE_TABLE[index].length;
+ for (int i = 0; i < numCoordinates; ++i) {
+ for (int j = 0; j < numCoordinates; ++j) {
+ int y = coordinates[i];
+ int x = coordinates[j];
+ if (x == -1 || y == -1) {
+ continue;
+ }
+ // If the cell is unset, we embed the position adjustment pattern here.
+ if (isEmpty(matrix.get(x, y))) {
+ // -2 is necessary since the x/y coordinates point to the center of the pattern, not the
+ // left top corner.
+ embedPositionAdjustmentPattern(x - 2, y - 2, matrix);
+ }
+ }
+ }
+ }
+
+}
diff --git a/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/QRCode.java b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/QRCode.java
new file mode 100644
index 000000000..b560de92f
--- /dev/null
+++ b/extern/zxing-core/src/main/java/com/google/zxing/qrcode/encoder/QRCode.java
@@ -0,0 +1,108 @@
+/*
+ * 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.qrcode.encoder;
+
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+import com.google.zxing.qrcode.decoder.Mode;
+import com.google.zxing.qrcode.decoder.Version;
+
+/**
+ * @author satorux@google.com (Satoru Takabayashi) - creator
+ * @author dswitkin@google.com (Daniel Switkin) - ported from C++
+ */
+public final class QRCode {
+
+ public static final int NUM_MASK_PATTERNS = 8;
+
+ private Mode mode;
+ private ErrorCorrectionLevel ecLevel;
+ private Version version;
+ private int maskPattern;
+ private ByteMatrix matrix;
+
+ public QRCode() {
+ maskPattern = -1;
+ }
+
+ public Mode getMode() {
+ return mode;
+ }
+
+ public ErrorCorrectionLevel getECLevel() {
+ return ecLevel;
+ }
+
+ public Version getVersion() {
+ return version;
+ }
+
+ public int getMaskPattern() {
+ return maskPattern;
+ }
+
+ public ByteMatrix getMatrix() {
+ return matrix;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder(200);
+ result.append("<<\n");
+ result.append(" mode: ");
+ result.append(mode);
+ result.append("\n ecLevel: ");
+ result.append(ecLevel);
+ result.append("\n version: ");
+ result.append(version);
+ result.append("\n maskPattern: ");
+ result.append(maskPattern);
+ if (matrix == null) {
+ result.append("\n matrix: null\n");
+ } else {
+ result.append("\n matrix:\n");
+ result.append(matrix);
+ }
+ result.append(">>\n");
+ return result.toString();
+ }
+
+ public void setMode(Mode value) {
+ mode = value;
+ }
+
+ public void setECLevel(ErrorCorrectionLevel value) {
+ ecLevel = value;
+ }
+
+ public void setVersion(Version version) {
+ this.version = version;
+ }
+
+ public void setMaskPattern(int value) {
+ maskPattern = value;
+ }
+
+ public void setMatrix(ByteMatrix value) {
+ matrix = value;
+ }
+
+ // Check if "mask_pattern" is valid.
+ public static boolean isValidMaskPattern(int maskPattern) {
+ return maskPattern >= 0 && maskPattern < NUM_MASK_PATTERNS;
+ }
+
+}