From bb61a6680f667e8a5587da67e7b7bee03ffa8b90 Mon Sep 17 00:00:00 2001
From: xuenhua
Date: Mon, 16 Dec 2024 16:42:38 +0800
Subject: [PATCH] =?UTF-8?q?j2me=E4=BA=8C=E7=BB=B4=E7=A0=81=E7=BC=96?=
=?UTF-8?q?=E8=A7=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.xml | 83 +
jsr75_1.0.jar | Bin 0 -> 11020 bytes
nbproject/build-impl.xml | 1376 +++++++++++++++++
nbproject/genfiles.properties | 8 +
nbproject/private/private.properties | 7 +
nbproject/private/private.xml | 4 +
nbproject/project.properties | 146 ++
nbproject/project.xml | 10 +
res/zxing-icon.png | Bin 0 -> 5894 bytes
src/com/google/zxing/BarcodeFormat.java | 103 ++
src/com/google/zxing/Binarizer.java | 80 +
src/com/google/zxing/BinaryBitmap.java | 128 ++
src/com/google/zxing/ChecksumException.java | 37 +
src/com/google/zxing/DecodeHintType.java | 79 +
src/com/google/zxing/EncodeHintType.java | 39 +
src/com/google/zxing/FormatException.java | 38 +
src/com/google/zxing/LuminanceSource.java | 113 ++
src/com/google/zxing/MultiFormatReader.java | 166 ++
src/com/google/zxing/MultiFormatWriter.java | 65 +
src/com/google/zxing/NotFoundException.java | 37 +
src/com/google/zxing/Reader.java | 64 +
src/com/google/zxing/ReaderException.java | 98 ++
src/com/google/zxing/Result.java | 143 ++
src/com/google/zxing/ResultMetadataType.java | 109 ++
src/com/google/zxing/ResultPoint.java | 127 ++
src/com/google/zxing/ResultPointCallback.java | 29 +
src/com/google/zxing/Writer.java | 54 +
src/com/google/zxing/WriterException.java | 35 +
.../j2me/LCDUIImageLuminanceSource.java | 95 ++
.../google/zxing/client/j2me/PNGEncoder.java | 262 ++++
.../google/zxing/client/j2me/ZXMIDlet.java | 670 ++++++++
src/com/google/zxing/client/j2me/ZXMIDlet.vmd | 310 ++++
.../result/AbstractDoCoMoResultParser.java | 39 +
.../result/AddressBookAUResultParser.java | 73 +
.../result/AddressBookDoCoMoResultParser.java | 85 +
.../result/AddressBookParsedResult.java | 122 ++
.../client/result/BizcardResultParser.java | 94 ++
.../result/BookmarkDoCoMoResultParser.java | 46 +
.../client/result/CalendarParsedResult.java | 134 ++
.../result/EmailAddressParsedResult.java | 61 +
.../result/EmailAddressResultParser.java | 64 +
.../result/EmailDoCoMoResultParser.java | 87 ++
.../result/ExpandedProductParsedResult.java | 195 +++
.../result/ExpandedProductResultParser.java | 199 +++
.../zxing/client/result/GeoParsedResult.java | 132 ++
.../zxing/client/result/GeoResultParser.java | 77 +
.../zxing/client/result/ISBNParsedResult.java | 39 +
.../zxing/client/result/ISBNResultParser.java | 54 +
.../zxing/client/result/ParsedResult.java | 73 +
.../zxing/client/result/ParsedResultType.java | 53 +
.../client/result/ProductParsedResult.java | 49 +
.../client/result/ProductResultParser.java | 66 +
.../zxing/client/result/ResultParser.java | 319 ++++
.../client/result/SMSMMSResultParser.java | 107 ++
.../zxing/client/result/SMSParsedResult.java | 104 ++
.../client/result/SMSTOMMSTOResultParser.java | 57 +
.../zxing/client/result/TelParsedResult.java | 54 +
.../zxing/client/result/TelResultParser.java | 44 +
.../zxing/client/result/TextParsedResult.java | 48 +
.../zxing/client/result/URIParsedResult.java | 113 ++
.../zxing/client/result/URIResultParser.java | 87 ++
.../client/result/URLTOResultParser.java | 47 +
.../client/result/VCardResultParser.java | 346 +++++
.../client/result/VEventResultParser.java | 54 +
.../zxing/client/result/WifiParsedResult.java | 53 +
.../zxing/client/result/WifiResultParser.java | 50 +
.../optional/AbstractNDEFResultParser.java | 45 +
.../client/result/optional/NDEFRecord.java | 87 ++
.../optional/NDEFSmartPosterParsedResult.java | 63 +
.../optional/NDEFSmartPosterResultParser.java | 81 +
.../result/optional/NDEFTextResultParser.java | 57 +
.../result/optional/NDEFURIResultParser.java | 95 ++
src/com/google/zxing/common/BitArray.java | 247 +++
src/com/google/zxing/common/BitMatrix.java | 226 +++
src/com/google/zxing/common/BitSource.java | 97 ++
.../google/zxing/common/CharacterSetECI.java | 110 ++
src/com/google/zxing/common/Collections.java | 53 +
src/com/google/zxing/common/Comparator.java | 27 +
.../google/zxing/common/DecoderResult.java | 63 +
.../zxing/common/DefaultGridSampler.java | 81 +
.../google/zxing/common/DetectorResult.java | 46 +
src/com/google/zxing/common/ECI.java | 52 +
.../common/GlobalHistogramBinarizer.java | 196 +++
src/com/google/zxing/common/GridSampler.java | 171 ++
.../google/zxing/common/HybridBinarizer.java | 185 +++
.../zxing/common/PerspectiveTransform.java | 148 ++
src/com/google/zxing/common/StringUtils.java | 191 +++
.../detector/MonochromeRectangleDetector.java | 209 +++
.../detector/WhiteRectangleDetector.java | 316 ++++
.../zxing/common/reedsolomon/GF256.java | 139 ++
.../zxing/common/reedsolomon/GF256Poly.java | 263 ++++
.../reedsolomon/ReedSolomonDecoder.java | 194 +++
.../reedsolomon/ReedSolomonEncoder.java | 75 +
.../reedsolomon/ReedSolomonException.java | 31 +
.../zxing/datamatrix/DataMatrixReader.java | 161 ++
.../datamatrix/decoder/BitMatrixParser.java | 446 ++++++
.../zxing/datamatrix/decoder/DataBlock.java | 118 ++
.../decoder/DecodedBitStreamParser.java | 462 ++++++
.../zxing/datamatrix/decoder/Decoder.java | 134 ++
.../zxing/datamatrix/decoder/Version.java | 242 +++
.../zxing/datamatrix/detector/Detector.java | 348 +++++
.../google/zxing/multi/ByQuadrantReader.java | 96 ++
.../multi/GenericMultipleBarcodeReader.java | 156 ++
.../zxing/multi/MultipleBarcodeReader.java | 37 +
.../zxing/multi/qrcode/QRCodeMultiReader.java | 80 +
.../multi/qrcode/detector/MultiDetector.java | 72 +
.../detector/MultiFinderPatternFinder.java | 324 ++++
src/com/google/zxing/oned/CodaBarReader.java | 263 ++++
src/com/google/zxing/oned/Code128Reader.java | 473 ++++++
src/com/google/zxing/oned/Code128Writer.java | 73 +
src/com/google/zxing/oned/Code39Reader.java | 333 ++++
src/com/google/zxing/oned/Code39Writer.java | 82 +
src/com/google/zxing/oned/Code93Reader.java | 273 ++++
src/com/google/zxing/oned/EAN13Reader.java | 135 ++
src/com/google/zxing/oned/EAN13Writer.java | 81 +
src/com/google/zxing/oned/EAN8Reader.java | 72 +
src/com/google/zxing/oned/EAN8Writer.java | 76 +
.../zxing/oned/EANManufacturerOrgSupport.java | 170 ++
src/com/google/zxing/oned/ITFReader.java | 348 +++++
src/com/google/zxing/oned/ITFWriter.java | 68 +
.../zxing/oned/MultiFormatOneDReader.java | 109 ++
.../zxing/oned/MultiFormatUPCEANReader.java | 113 ++
src/com/google/zxing/oned/OneDReader.java | 297 ++++
src/com/google/zxing/oned/UPCAReader.java | 75 +
.../zxing/oned/UPCEANExtensionSupport.java | 186 +++
src/com/google/zxing/oned/UPCEANReader.java | 355 +++++
src/com/google/zxing/oned/UPCEANWriter.java | 104 ++
src/com/google/zxing/oned/UPCEReader.java | 153 ++
.../zxing/oned/rss/AbstractRSSReader.java | 110 ++
.../google/zxing/oned/rss/DataCharacter.java | 37 +
.../google/zxing/oned/rss/FinderPattern.java | 48 +
src/com/google/zxing/oned/rss/Pair.java | 41 +
.../google/zxing/oned/rss/RSS14Reader.java | 494 ++++++
src/com/google/zxing/oned/rss/RSSUtils.java | 155 ++
.../oned/rss/expanded/BitArrayBuilder.java | 85 +
.../zxing/oned/rss/expanded/ExpandedPair.java | 68 +
.../oned/rss/expanded/RSSExpandedReader.java | 578 +++++++
.../expanded/decoders/AI013103decoder.java | 47 +
.../expanded/decoders/AI01320xDecoder.java | 55 +
.../expanded/decoders/AI01392xDecoder.java | 66 +
.../expanded/decoders/AI01393xDecoder.java | 76 +
.../expanded/decoders/AI013x0x1xDecoder.java | 106 ++
.../expanded/decoders/AI013x0xDecoder.java | 56 +
.../expanded/decoders/AI01AndOtherAIs.java | 56 +
.../rss/expanded/decoders/AI01decoder.java | 81 +
.../expanded/decoders/AI01weightDecoder.java | 58 +
.../decoders/AbstractExpandedDecoder.java | 85 +
.../rss/expanded/decoders/AnyAIDecoder.java | 48 +
.../expanded/decoders/BlockParsedResult.java | 60 +
.../decoders/CurrentParsingState.java | 69 +
.../rss/expanded/decoders/DecodedChar.java | 52 +
.../expanded/decoders/DecodedInformation.java | 63 +
.../rss/expanded/decoders/DecodedNumeric.java | 79 +
.../rss/expanded/decoders/DecodedObject.java | 44 +
.../rss/expanded/decoders/FieldParser.java | 285 ++++
.../decoders/GeneralAppIdDecoder.java | 414 +++++
src/com/google/zxing/pdf417/PDF417Reader.java | 79 +
.../zxing/pdf417/decoder/BitMatrixParser.java | 1163 ++++++++++++++
.../decoder/DecodedBitStreamParser.java | 629 ++++++++
.../google/zxing/pdf417/decoder/Decoder.java | 150 ++
.../zxing/pdf417/detector/Detector.java | 498 ++++++
src/com/google/zxing/qrcode/QRCodeReader.java | 166 ++
src/com/google/zxing/qrcode/QRCodeWriter.java | 108 ++
.../zxing/qrcode/decoder/BitMatrixParser.java | 203 +++
.../zxing/qrcode/decoder/DataBlock.java | 123 ++
.../google/zxing/qrcode/decoder/DataMask.java | 155 ++
.../decoder/DecodedBitStreamParser.java | 261 ++++
.../google/zxing/qrcode/decoder/Decoder.java | 148 ++
.../qrcode/decoder/ErrorCorrectionLevel.java | 86 ++
.../qrcode/decoder/FormatInformation.java | 171 ++
src/com/google/zxing/qrcode/decoder/Mode.java | 112 ++
.../google/zxing/qrcode/decoder/Version.java | 586 +++++++
.../qrcode/detector/AlignmentPattern.java | 48 +
.../detector/AlignmentPatternFinder.java | 279 ++++
.../zxing/qrcode/detector/Detector.java | 400 +++++
.../zxing/qrcode/detector/FinderPattern.java | 63 +
.../qrcode/detector/FinderPatternFinder.java | 585 +++++++
.../qrcode/detector/FinderPatternInfo.java | 49 +
.../zxing/qrcode/encoder/BlockPair.java | 37 +
.../zxing/qrcode/encoder/ByteMatrix.java | 97 ++
.../google/zxing/qrcode/encoder/Encoder.java | 557 +++++++
.../google/zxing/qrcode/encoder/MaskUtil.java | 217 +++
.../zxing/qrcode/encoder/MatrixUtil.java | 524 +++++++
.../google/zxing/qrcode/encoder/QRCode.java | 239 +++
src/zxing-icon.png | Bin 0 -> 5894 bytes
185 files changed, 28952 insertions(+)
create mode 100644 build.xml
create mode 100644 jsr75_1.0.jar
create mode 100644 nbproject/build-impl.xml
create mode 100644 nbproject/genfiles.properties
create mode 100644 nbproject/private/private.properties
create mode 100644 nbproject/private/private.xml
create mode 100644 nbproject/project.properties
create mode 100644 nbproject/project.xml
create mode 100644 res/zxing-icon.png
create mode 100644 src/com/google/zxing/BarcodeFormat.java
create mode 100644 src/com/google/zxing/Binarizer.java
create mode 100644 src/com/google/zxing/BinaryBitmap.java
create mode 100644 src/com/google/zxing/ChecksumException.java
create mode 100644 src/com/google/zxing/DecodeHintType.java
create mode 100644 src/com/google/zxing/EncodeHintType.java
create mode 100644 src/com/google/zxing/FormatException.java
create mode 100644 src/com/google/zxing/LuminanceSource.java
create mode 100644 src/com/google/zxing/MultiFormatReader.java
create mode 100644 src/com/google/zxing/MultiFormatWriter.java
create mode 100644 src/com/google/zxing/NotFoundException.java
create mode 100644 src/com/google/zxing/Reader.java
create mode 100644 src/com/google/zxing/ReaderException.java
create mode 100644 src/com/google/zxing/Result.java
create mode 100644 src/com/google/zxing/ResultMetadataType.java
create mode 100644 src/com/google/zxing/ResultPoint.java
create mode 100644 src/com/google/zxing/ResultPointCallback.java
create mode 100644 src/com/google/zxing/Writer.java
create mode 100644 src/com/google/zxing/WriterException.java
create mode 100644 src/com/google/zxing/client/j2me/LCDUIImageLuminanceSource.java
create mode 100644 src/com/google/zxing/client/j2me/PNGEncoder.java
create mode 100644 src/com/google/zxing/client/j2me/ZXMIDlet.java
create mode 100644 src/com/google/zxing/client/j2me/ZXMIDlet.vmd
create mode 100644 src/com/google/zxing/client/result/AbstractDoCoMoResultParser.java
create mode 100644 src/com/google/zxing/client/result/AddressBookAUResultParser.java
create mode 100644 src/com/google/zxing/client/result/AddressBookDoCoMoResultParser.java
create mode 100644 src/com/google/zxing/client/result/AddressBookParsedResult.java
create mode 100644 src/com/google/zxing/client/result/BizcardResultParser.java
create mode 100644 src/com/google/zxing/client/result/BookmarkDoCoMoResultParser.java
create mode 100644 src/com/google/zxing/client/result/CalendarParsedResult.java
create mode 100644 src/com/google/zxing/client/result/EmailAddressParsedResult.java
create mode 100644 src/com/google/zxing/client/result/EmailAddressResultParser.java
create mode 100644 src/com/google/zxing/client/result/EmailDoCoMoResultParser.java
create mode 100644 src/com/google/zxing/client/result/ExpandedProductParsedResult.java
create mode 100644 src/com/google/zxing/client/result/ExpandedProductResultParser.java
create mode 100644 src/com/google/zxing/client/result/GeoParsedResult.java
create mode 100644 src/com/google/zxing/client/result/GeoResultParser.java
create mode 100644 src/com/google/zxing/client/result/ISBNParsedResult.java
create mode 100644 src/com/google/zxing/client/result/ISBNResultParser.java
create mode 100644 src/com/google/zxing/client/result/ParsedResult.java
create mode 100644 src/com/google/zxing/client/result/ParsedResultType.java
create mode 100644 src/com/google/zxing/client/result/ProductParsedResult.java
create mode 100644 src/com/google/zxing/client/result/ProductResultParser.java
create mode 100644 src/com/google/zxing/client/result/ResultParser.java
create mode 100644 src/com/google/zxing/client/result/SMSMMSResultParser.java
create mode 100644 src/com/google/zxing/client/result/SMSParsedResult.java
create mode 100644 src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java
create mode 100644 src/com/google/zxing/client/result/TelParsedResult.java
create mode 100644 src/com/google/zxing/client/result/TelResultParser.java
create mode 100644 src/com/google/zxing/client/result/TextParsedResult.java
create mode 100644 src/com/google/zxing/client/result/URIParsedResult.java
create mode 100644 src/com/google/zxing/client/result/URIResultParser.java
create mode 100644 src/com/google/zxing/client/result/URLTOResultParser.java
create mode 100644 src/com/google/zxing/client/result/VCardResultParser.java
create mode 100644 src/com/google/zxing/client/result/VEventResultParser.java
create mode 100644 src/com/google/zxing/client/result/WifiParsedResult.java
create mode 100644 src/com/google/zxing/client/result/WifiResultParser.java
create mode 100644 src/com/google/zxing/client/result/optional/AbstractNDEFResultParser.java
create mode 100644 src/com/google/zxing/client/result/optional/NDEFRecord.java
create mode 100644 src/com/google/zxing/client/result/optional/NDEFSmartPosterParsedResult.java
create mode 100644 src/com/google/zxing/client/result/optional/NDEFSmartPosterResultParser.java
create mode 100644 src/com/google/zxing/client/result/optional/NDEFTextResultParser.java
create mode 100644 src/com/google/zxing/client/result/optional/NDEFURIResultParser.java
create mode 100644 src/com/google/zxing/common/BitArray.java
create mode 100644 src/com/google/zxing/common/BitMatrix.java
create mode 100644 src/com/google/zxing/common/BitSource.java
create mode 100644 src/com/google/zxing/common/CharacterSetECI.java
create mode 100644 src/com/google/zxing/common/Collections.java
create mode 100644 src/com/google/zxing/common/Comparator.java
create mode 100644 src/com/google/zxing/common/DecoderResult.java
create mode 100644 src/com/google/zxing/common/DefaultGridSampler.java
create mode 100644 src/com/google/zxing/common/DetectorResult.java
create mode 100644 src/com/google/zxing/common/ECI.java
create mode 100644 src/com/google/zxing/common/GlobalHistogramBinarizer.java
create mode 100644 src/com/google/zxing/common/GridSampler.java
create mode 100644 src/com/google/zxing/common/HybridBinarizer.java
create mode 100644 src/com/google/zxing/common/PerspectiveTransform.java
create mode 100644 src/com/google/zxing/common/StringUtils.java
create mode 100644 src/com/google/zxing/common/detector/MonochromeRectangleDetector.java
create mode 100644 src/com/google/zxing/common/detector/WhiteRectangleDetector.java
create mode 100644 src/com/google/zxing/common/reedsolomon/GF256.java
create mode 100644 src/com/google/zxing/common/reedsolomon/GF256Poly.java
create mode 100644 src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
create mode 100644 src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java
create mode 100644 src/com/google/zxing/common/reedsolomon/ReedSolomonException.java
create mode 100644 src/com/google/zxing/datamatrix/DataMatrixReader.java
create mode 100644 src/com/google/zxing/datamatrix/decoder/BitMatrixParser.java
create mode 100644 src/com/google/zxing/datamatrix/decoder/DataBlock.java
create mode 100644 src/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java
create mode 100644 src/com/google/zxing/datamatrix/decoder/Decoder.java
create mode 100644 src/com/google/zxing/datamatrix/decoder/Version.java
create mode 100644 src/com/google/zxing/datamatrix/detector/Detector.java
create mode 100644 src/com/google/zxing/multi/ByQuadrantReader.java
create mode 100644 src/com/google/zxing/multi/GenericMultipleBarcodeReader.java
create mode 100644 src/com/google/zxing/multi/MultipleBarcodeReader.java
create mode 100644 src/com/google/zxing/multi/qrcode/QRCodeMultiReader.java
create mode 100644 src/com/google/zxing/multi/qrcode/detector/MultiDetector.java
create mode 100644 src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java
create mode 100644 src/com/google/zxing/oned/CodaBarReader.java
create mode 100644 src/com/google/zxing/oned/Code128Reader.java
create mode 100644 src/com/google/zxing/oned/Code128Writer.java
create mode 100644 src/com/google/zxing/oned/Code39Reader.java
create mode 100644 src/com/google/zxing/oned/Code39Writer.java
create mode 100644 src/com/google/zxing/oned/Code93Reader.java
create mode 100644 src/com/google/zxing/oned/EAN13Reader.java
create mode 100644 src/com/google/zxing/oned/EAN13Writer.java
create mode 100644 src/com/google/zxing/oned/EAN8Reader.java
create mode 100644 src/com/google/zxing/oned/EAN8Writer.java
create mode 100644 src/com/google/zxing/oned/EANManufacturerOrgSupport.java
create mode 100644 src/com/google/zxing/oned/ITFReader.java
create mode 100644 src/com/google/zxing/oned/ITFWriter.java
create mode 100644 src/com/google/zxing/oned/MultiFormatOneDReader.java
create mode 100644 src/com/google/zxing/oned/MultiFormatUPCEANReader.java
create mode 100644 src/com/google/zxing/oned/OneDReader.java
create mode 100644 src/com/google/zxing/oned/UPCAReader.java
create mode 100644 src/com/google/zxing/oned/UPCEANExtensionSupport.java
create mode 100644 src/com/google/zxing/oned/UPCEANReader.java
create mode 100644 src/com/google/zxing/oned/UPCEANWriter.java
create mode 100644 src/com/google/zxing/oned/UPCEReader.java
create mode 100644 src/com/google/zxing/oned/rss/AbstractRSSReader.java
create mode 100644 src/com/google/zxing/oned/rss/DataCharacter.java
create mode 100644 src/com/google/zxing/oned/rss/FinderPattern.java
create mode 100644 src/com/google/zxing/oned/rss/Pair.java
create mode 100644 src/com/google/zxing/oned/rss/RSS14Reader.java
create mode 100644 src/com/google/zxing/oned/rss/RSSUtils.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/BitArrayBuilder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/ExpandedPair.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/RSSExpandedReader.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AI013103decoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AI01320xDecoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AI01392xDecoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AI01393xDecoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AI013x0x1xDecoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AI013x0xDecoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AI01AndOtherAIs.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AI01decoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AI01weightDecoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AbstractExpandedDecoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/AnyAIDecoder.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/BlockParsedResult.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/CurrentParsingState.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/DecodedChar.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/DecodedInformation.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/DecodedNumeric.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/DecodedObject.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/FieldParser.java
create mode 100644 src/com/google/zxing/oned/rss/expanded/decoders/GeneralAppIdDecoder.java
create mode 100644 src/com/google/zxing/pdf417/PDF417Reader.java
create mode 100644 src/com/google/zxing/pdf417/decoder/BitMatrixParser.java
create mode 100644 src/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java
create mode 100644 src/com/google/zxing/pdf417/decoder/Decoder.java
create mode 100644 src/com/google/zxing/pdf417/detector/Detector.java
create mode 100644 src/com/google/zxing/qrcode/QRCodeReader.java
create mode 100644 src/com/google/zxing/qrcode/QRCodeWriter.java
create mode 100644 src/com/google/zxing/qrcode/decoder/BitMatrixParser.java
create mode 100644 src/com/google/zxing/qrcode/decoder/DataBlock.java
create mode 100644 src/com/google/zxing/qrcode/decoder/DataMask.java
create mode 100644 src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java
create mode 100644 src/com/google/zxing/qrcode/decoder/Decoder.java
create mode 100644 src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java
create mode 100644 src/com/google/zxing/qrcode/decoder/FormatInformation.java
create mode 100644 src/com/google/zxing/qrcode/decoder/Mode.java
create mode 100644 src/com/google/zxing/qrcode/decoder/Version.java
create mode 100644 src/com/google/zxing/qrcode/detector/AlignmentPattern.java
create mode 100644 src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java
create mode 100644 src/com/google/zxing/qrcode/detector/Detector.java
create mode 100644 src/com/google/zxing/qrcode/detector/FinderPattern.java
create mode 100644 src/com/google/zxing/qrcode/detector/FinderPatternFinder.java
create mode 100644 src/com/google/zxing/qrcode/detector/FinderPatternInfo.java
create mode 100644 src/com/google/zxing/qrcode/encoder/BlockPair.java
create mode 100644 src/com/google/zxing/qrcode/encoder/ByteMatrix.java
create mode 100644 src/com/google/zxing/qrcode/encoder/Encoder.java
create mode 100644 src/com/google/zxing/qrcode/encoder/MaskUtil.java
create mode 100644 src/com/google/zxing/qrcode/encoder/MatrixUtil.java
create mode 100644 src/com/google/zxing/qrcode/encoder/QRCode.java
create mode 100644 src/zxing-icon.png
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..3e69a1a
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+ Builds, tests, and runs the project .
+
+
+
diff --git a/jsr75_1.0.jar b/jsr75_1.0.jar
new file mode 100644
index 0000000000000000000000000000000000000000..6733cdba6a663359c5446918cced01051945b629
GIT binary patch
literal 11020
zcmcI~1z40@*ETS8OAZ1e-Q5k+-O`eSbT>*fNOz|KDhN_4jSQWFAR^r$B_T-THyqD-
z(Rs!5$Mx-tXJ(jr_PzGL_u6Z(XRW2K1VBWGyKF*85kh}``1=J7`mQ3QEy=2&D$B0^
z*KG)JsBnVN+c3eTO4ZPxzdPD~
zUgH7v6W>47>oOR2aoM`Pzf-1wJ8jkMbdk}M;GyD)`tpO}bY-AG^aH0haZ3yJX*4o7
zE;NhTB++>ZoeJ`2#~wh)lLuT&1_wFhTM}hq(K@y6S&_;u%_8(K)X2#yY2k&Zal2zP
zKFm47xgyD=w)H5p@~P+h&c$q!)kk-RHM?3BQ5M;iIR}5=7S2ngeEBGaj#=WCCK`Ku
z@xJJ+AK=F%q4W7`l9%uQG-T)pTQg5H@4tz-{2rDs{QunJU}NFx1hTYow{dd(slz36
zmmU9UM9>dEK!53ASRtg>_OY_D2mPTV@$Wl2+c^B)>7TV7jWmxch2H!bdR-3p
z_appEnv|2HBgo=%mQwakZXin;Zwrv~<-cqe_GWHwI?+w&trEDQt9virceZxEW2>y|
zRINnwyo+YzS*3b2wwaTSLd;lXLP`r27zG|(Iw08V((`_axTQuwJ{O}Y9&U+`bC3{l5
ze~kTDh{m)o+}5H_Dn%o>f$v<>M_x_Jvo(MdAy%z}vj9E_S=I!f?Z$+EtMx5J7E2~;
z6cMxL=%hTEi~%~46vn$byK4oq%Kn83v0@s>SPy{@#l)zF7S`?^qn0kL`Q1WP&JTGO
zQi*=|T_tZ^*9^ooV4=EliE~(0Rf=Jts!(UU3zEgo>P#1?5o6
z*f(~*lGrA%TJ5GNN|ohsfw}wH^BkqPadM_&C$T79Gv@X#af-gTJ2O~jr1K4jCFz*G
z73N7S^9v(+#xJS5k9BXjBt&Bu}}3(Yvs!OC3ot2H0HHHjG3
zTS@?9|ivqjIs9ZHOTaLpzf9!
zK3i|tO8KU|H#AAC-;P)EG>o@__yZ<;4ZE^<`3Jfk)Dww0$_v>^qFC$|IQ1-pNrQ45
z0f|jKZ43gcUqktTa-csp7%DO
zewAHeT4!0a*?w4t^NOv7ux-2~Vc=#R(jb;=gY1_;hxi6w*K%3UJ!?g{i@#0GM
zsEQJ_h9n-*)IFH(8vC|vB7xy}!jA6mk;)&Rj&7WLBjE0!@lf-mX}p?=Zga-Nn=sK+
zCDJf5U8O14EF7kxn#ld|?gqGHosO%iuWZ8ZS=o(=$Pm?(bl1`^_n%eOt?)3>zjC!R
zc3vX3HL+rkyp2PMqjRT5JnK6dbDCXJG%_ayr&Lhb#)xGks>DTw=cpp|77nr8snRXC
zm~sVY5@luJ@uf
zFeH^4ge8gWsre*d04RgQrZY*~zzxQq?1PWhhz!t(F5m)^YyC6`5O)cMxlacg55HaP
zp-Y%%;jVV@KK3hl<$6CyOq%y=IhpH*mVO)f1mvgRG<57w44^NP*K|ik@zHpbfbq=MYG^z1ScRy{lqIQiM;h-W&jFKcH(&bTqoMgndxqUFl&Pa>y9&aRtK_=ySh{yX=
zNHCReyMaan`KK?d#;c!64AtyT;D4nM40$9}45e@pdhz~QpA_uvLHEtIRH6V&xog#z^KpKyMAVI=mWjTkihX`RoM!s9Ng_%tX
zL6;cblpj`pK8y*YK73DKSk*vnwixuRruqHcFAqDYt#&uFaQ~q%4@dQNZ*mf#>_|&E
zo77K?ewmmUerQ6L4#&uipNaNp3lQFGU5QR47Z-PK^zgIWM)>+#Q^*IumB;Mgmx#F(
z9FeiZ1N_e{qe0{J?-DUwHSSo9zge*-%aXmPBU+#kB{kv%>4g}Ojgj8~{q$w8
zg4|p)14!`>j(TO}qaLwW45(#cqI!c~=MZ0J#WXVk`qzx(L<9-F!p@lQtO|&j4$+5+
zauZSkgoh~11|L}f!f_OJnQoT1atUA3noU?r+7PD_dlm#lS+-PHz{T=UZO57cXzPa|
z!qM8vyz^Mj+Ij)EK2mJ}jz{klM$>;?zHn7goA1F>B%YT@No=(vi|Vd#w=>0bGe!|dmZr1-iSNd|lviq|X8E
z^`EGBiqezX;~)i`Cb2$`j#!Uqc7=Bs7eG5?l}JKz*?>IycOmR}lXG_M2oG{<;hH6l
z5NB!Ff}CzB?I7&>?NGUpJjnV4KTGKeQEI<+#8!zgOZ_tN{v+`~`UsOCxkq_{UFNxQ
z$2^tDd6eHE_am^pQ)|&xZ-^tAMEFD=@d`+tVT#=PI+{MxJ_w6FCEuBlJa0qw;z703VIN^&;NYB0M&jK86?2ef
z@tBJ(*Xh=I5=)^#-x34gX;Mk43n+!^qlVQ7Nka#`gmt~f!BThC$31aFo^|(<6BL<4
z%=U#33>HC5d>trp1wHf5uLLIAZ-l=1&Y-n`y3REpYj!uU?}_u~BI
zvN{yE9ZYw$vEv#kRme4oy)&MyKQbcisH=sF;iL7V1x)y_i5{+4$VhI3)OYbWn#&@nATjR_#^!RdpUKl&(C}x
z0!*LusxM(5ZfLWHqrkTf*UlK^`q8pE@Dog#rDyS!KYYLRctHKaWwG@Jk<^SQJ4w1J
zgJ5ol)O;N#(wLT#Qu8zcq*e>_&X;gZ*`eoSbpcYOq``cuUY09;LD5`bMgE&IR(M6^
zBMlq9M6W%yH##;u%APgbcj-^54MwOr-W4=
zr=+i$UfoCo4NFf}M3>MpzX~8cXPZy2KSKUhJuhAqZplHH#uU0VbpNt6KP%@Xba`;W
z-(?g9A1xe*$}XzZtpn22fu&CoAp}cz%5BHvX+?{s@p1<+C|bg~-Vs^UN;+D7%lke#
zopF3}afE34q$i)mb7R1YLBGkb;jp_!Ui)P0Th!uGy$EndureS-_tMsybt-a)6WkX54-ENi|R;F*&~kh
zbavgGB@+_fs
z%vJF8TH~8~pXv;PW}7hm7JS_%A1PK+3@4qjP$QD>_+>ZRwuG#v=?^>!o(^LzcOP)S
zQ$L@SH8B6w#s$<}Rz6uqURYDA1e3
zHLc}?(bb2z1LJO%ZFcMTLzh4bx*QzWFNLfP$lg-M!P(vCTF*f2(lby|;~?qmZm~+r
zNYgj(>TXHMHCRZ^$V_VOzMYbym-;*}DLw5ag?6THVn)`wK|;2HdSXUiS9d;AV(HM}
zfHGUJno18M+B&i2%6iB_{R&aCb}L~ucSRL<1t-@d+oRjcm~4!_{Yo{+1jKsORR5wdJNOZ*bj9%2v=b-dN+#JiU28>MhG0}(U>
zp^O7zGy~CW0|{@_GAA1oV^dq$Q>v%*F5lDIuCy_BkN&r|Gz^F
z+HZ+TSy^fsg_80gUNHaRMgM=DmkLTx=?N$&%K$jI+t+K1x`N8z4!EYV(})Caw$fCs
z%80}0AyE>2rzCT*RR4YKz-m4j96&&4rv{^dS+0hkx!B;cjv3Xh$I<0cxz-%
z%Tx*UHm+JB@ws-?WBhPihQ$OoqDW~aLam%TeAo*KTRGX0@$03lA2P^$ICRVz+;Vcd
zyZyUnx$KP}>J?Ai>{G^H*6i-iP?R>h1Agr$uJfJ7&Q1rk?w-M7VtcH~|DLPT{E5TD
zO~Oj=)uD5X=5no*N^&sPi^Y3e35b(Kt6ypA6cU?mkIx=_r{0ZORxQu%>XFuZiuxo+
z$(H;bW9REf&p#y`un+ifJ3U$TXej)iA9hm@->b66)O|F*)2ir2Q)Xh3Sv*au9jgU3+W@3llFt9|@DNoDo=6FkP?+l3zD
ziI#jw$2rvug9Uz^{Qj#7-8S-4{F=yc+WYEf1NQwK{JTfyxAcuWL#jL)td*aBTBTYb
z{?(`pH(BaBLiNHLYSbD3s24Ch#UUPg?5g8}SJl~Eb)+R4X*;oM*8tYawt|UtMGmSM
zW@9`<@i!R|OXeR>)pEJa1CG=?Gh{0@_`mn?e>Zy9f7-P{3fJx2DPimNN-rFYJio&h;u=S4#8BTC#(@L?7s(h`lk1MEWO3PYYGgbqnR@fu
zFo)avTS~$UChYBuJBx4n<4&&$;fw!tKW7lJ@cn|;M9a#IXC0C
zzm!3&3Th@f?PJLQj3mRqCju-dx}eF=Z^T@wDmtjq$9suM6HOySl~Aa_MKey8?4}Mm
zpF9}^3L7BJ??+;PIHG^)0j9OjVv+#F0t-hw+PjpBqnyco%y{;#aKy5u@ErolUfssh
zH+5DF2sB<&4nB-`LwiZJP2)`=m^XLRl@fx|-GMmfakkOT^nfR^k-0x^T1S1GxomSCQOE~O(-gOSi_iDbd%;#?Ub@X5
z{IzP=R?kOR_>($|aJ`IqxSqs?%{BSyR!ue^FtGsI)9UN9m5Bx~ba*;ikfS0aa(%Y4
zib-V3J{39}PxHOD(&5j``_PPfW4zu3Y
zn|1HIJNTsZUrvoD_bM>7Bkx*TFmX*sEqp3taBhqZ+zn-xG&S1;-c$}C=N1wp?0fxS
z%!9+OaGAnaqiy}x1SQyGQ$B*jWt)SlDbUwdY&YpEV}(MWt*7wKW^R=*f*VY5*uVR5nsR#Wp?p{k{q%N|P;GtJYjOCT=tzLz8D4;r?l
zW4C)yVwo*GwrW8X@sceI#~Qwt4WHiHkv}`l!?Yh0uTl!y{ua)IN9rT+$um7u2Kpx^
zQs>9EIh|&n#0-A+G9A)^f*0J5ghzs>k0(VE#D$~wb%zCK5J`F7@cI5T+%bJZ4i!W3
zZqBwrv-ta|MBf8YSWn2_MnJ@v1U^Y24Ju?NB2T6j)~-R`fqhdjKf=e2UwWe})BsC2vRarhqwP;t{euUgf
zGTRDV6b6Ef!nhXwj_#12`h5_ApfF@&WWIuvV{
zrIjGPczM`vt;{r?sZznKc^aq&@VoPL9Algu^QbJ^N$d#zOudtPMCKzXDD->(_Sv8y
zcXjpI#n-RIaKN2Jh-QJ!+AKG6hV0j^cN_Jn-F-+#?Trr(IkHYz5vH^g
z4X3$`xyF;0c*(F8IRx_4cqM-!l7%pt0tQ;SuNGf43sno=Sqtm~B0L)@2}{n2vpkDh
zd`0p%@K_!>zd2*Q@$_y{Ob7Ky=Bv-ZkA!RXq0(vSX|J2J`b%`*9qT#@OpyZVo7^_F
z#*M=|!OSN?ac|;V52GJD$HaX^ahvLTjDYD$)G$F+$R(2&w|GC}*)fTitgR9HPyu%q
zYXRBtUQ^JFiQI!WGNV4o0ompd+UxsDo7Q7?6TSr*VE#4PA~)ty6Rk8BQM23<=B(a^
zk|B}plF$?P$JC5uZTqrp$VTKuyyHp=icSrAA3e>cb&0?1hg-bI(o1hZ_Nh$^Z`etm
z=}L7PabP<0Tbbu7=w@r`dBqAU)DUf(<6;5CJ4)B1DH-$)-Co6T1<7yY7DGZ1$Ah!K
z-HUjItzSmy%FrY!R>WU$yd@vyY+{F&Y49T9Wn1n?2k+$1udw1)yOR*>gjN|_{DVtd
zixBlg!qHr#E;h^&?|yz!m5X0oLjIn<&%dR5+ZQze#@t5iDXwcn{iXsGI5_(2&qz&>
zGsw(c)59M0x5Z7ibDPx01s@8$v45uEY>1ACPGhv?%z0?27-3Gq@9MyqLW@!(p>A;_
z*<~}nJIB8tCz#}@9g_m8e+*zRT2Q@p=KKDJ(`>K7+&=k})!DZfbWiBW84aHEgCipk
zIss83quqCOHHsbe?WsIRp3nemp4bq~cGlKy7>Ht~UyujPndL7Z@pZkiIgpJ^omjD+
ztvWo7-P&TPPn^&F=#@7lpY`&IqG64DfFZT=^lVvB;Vwyf?lv8FOpv4CgQ^am9#5g6hI@Pi
zz@s%PTBj~o4Sf}J1HE0(2VU`CAYVN)bq?Jp-?@gA^Ns`Q7bdV~Ow17%EBznf=C*I$
z#;)uK&SN6Yvwt-WE_?vA`iNddUsO>7{470h*?-VoW>fmylRyMLGs)EVll!e(VJJX@
z)yG`}i=XG*iDC-`GF7tZ-@A-pskPqrT*c=p&jEK;%RQb_Jl-@1Ewb1&2OcKr5hw)}
zb!(axR~mupI$d)>F`%ervM1h-e}7wAXmX
z!cf5Fkh+%og!JIv1sxyhAz|4^(qhiI0tGF#)vkG)7Tv1i&sI&7TlcKk&6sTy5Khpx
zE$sL@n^-YxUYgeFZrr-2QNkz(@u#N6cHH0@-A6S5D9o-4hZO{1y#
zdE-3LaVDPX-W3iO-7yRffD@;$#qy`_qCjm%@z>u0OvNtLkPHr99JJ)h;?<`%-eM|n(gmg!?co3}I4q{c{a?>Ip8ZT@c2#=}?>NSLm?-`#+|
zMztaOgH-klRbsiL6W0wsl2#_g4N@l2heYQnzn%v(r>~7!q0TGb<(Y8(#?*F_cKUlu
z4vnjZNDu_ha~H9eX&P=AY@8s901)VsD#BQIOCUtd-R1-_gDYWU6X9IBT7I1T;GMw-
zNoEM8{$gVA5@F29%eckW|#^We2mqiP`L1(9f8mw`2xI$!Kd)|cF$H(qY(
zTUp=41ZN3)$6C&
z?fvbkH(D54F8#(BxZC{F>#se+>vJTi
zhVVaQptcHR4Xh@~7&}n(tExsdjo#~smQ#I~X{;oGe>ncO1)BU83d*jHzi584j?de
zxw?Zae*|cLO}1ZUZT^2^Gp2C=9fAm7AxtA8SRzg-d>R-NWM_8Y6m;Lz!V-6MM>3t%
zNHaMj%@NBr2uS3jHo$QU*$SDC4cYTDbwsp!maL5|gN%Kf7*P)hl&q3SB<{vX9FCUA
z)Ugu=49Zp@#PiT6^58$^Ax`AsuIIt{Bp=U1@xhsHh1XON85*I1M?is~?tO)Rg!^ao
z-$cP-!C`~PSKzpK*Mk2ilnjdo8*;otV}O<*{zh;AvOx+eFabzdSlFQ073?(OpJ1;-
zWUz>^;f5>3QQ)5tuh#+CC&?>BWvE%dX6mrdm9WEv^@6WJmv3DQdgT+t!ofOvS8y7)
x{|I;ev|$~iD@b?BYa#!|O;T4v`s)b433n4t2WpHmso>yH;M66hl(;xJ{s&K-UA_PS
literal 0
HcmV?d00001
diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml
new file mode 100644
index 0000000..62d7d43
--- /dev/null
+++ b/nbproject/build-impl.xml
@@ -0,0 +1,1376 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classpath to J2ME Ant extension library (libs.j2me_ant_ext.classpath property) is not set. For example: location of mobility/modules/org-netbeans-mobility-antext.jar file in the IDE installation directory.
+ Platform home (platform.home property) is not set. Value of this property should be ${platform.active.description} emulator home directory location.
+ Platform boot classpath (platform.bootclasspath property) is not set. Value of this property should be ${platform.active.description} emulator boot classpath containing all J2ME classes provided by emulator.
+ Must set src.dir
+ Must set build.dir
+ Must set dist.dir
+ Must set dist.jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set preprocessed.dir
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set build.classes.dir
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must select some files in the IDE or set javac.includes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set obfuscated.classes.dir
+
+
+
+ Must set obfuscated.classes.dir
+ Must set obfuscator.srcjar
+ Must set obfuscator.destjar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set preverify.classes.dir
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MicroEdition-Configuration: ${platform.configuration}
+
+ MicroEdition-Configuration: ${platform.configuration}
+
+
+
+ MicroEdition-Profile: ${platform.profile}
+
+ MicroEdition-Profile: ${platform.profile}
+
+
+
+ Must set dist.jad
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${manifest.midlets}${evaluated.manifest.apipermissions}${evaluated.manifest.pushregistry}${manifest.others}${manifest.jad}
+ ${manifest.midlets}${evaluated.manifest.apipermissions}${evaluated.manifest.pushregistry}${manifest.others}${manifest.manifest}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Starting emulator with port number ${active.debug.port}
+
+
+
+
+
+
+
+
+
+
+
+ Starting emulator with port number ${active.debug.port}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Must set dist.javadoc.dir
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${all.configurations}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Property deployment.${deployment.method}.scriptfile not set. The property should point to an Ant script providing ${deployment.method} deployment.
+
+
+
+
+
+
+
+ Classpath to Ant Contrib library (libs.ant-contrib.classpath property) is not set.
+
+
+
+
+
+
+
+
+ Active project configuration: @{cfg}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Property build.root.dir is not set. By default its value should be \"build\".
+ Property dist.root.dir is not set. By default its value should be \"dist\".
+
+
+
+
+
diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties
new file mode 100644
index 0000000..f448732
--- /dev/null
+++ b/nbproject/genfiles.properties
@@ -0,0 +1,8 @@
+# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
+# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
+build.xml.data.CRC32=9f5f57dd
+build.xml.script.CRC32=51aac932
+build.xml.stylesheet.CRC32=9c6a911d
+nbproject/build-impl.xml.data.CRC32=9f5f57dd
+nbproject/build-impl.xml.script.CRC32=3413fbf5
+nbproject/build-impl.xml.stylesheet.CRC32=296da595
diff --git a/nbproject/private/private.properties b/nbproject/private/private.properties
new file mode 100644
index 0000000..bc5f7ef
--- /dev/null
+++ b/nbproject/private/private.properties
@@ -0,0 +1,7 @@
+#Mon, 16 Dec 2024 16:04:01 +0800
+app-version.autoincrement=true
+config.active=
+deployment.counter=11
+deployment.number=0.0.10
+javadoc.preview=true
+netbeans.user=C\:\\Users\\enhua\\.netbeans\\6.9
diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml
new file mode 100644
index 0000000..c1f155a
--- /dev/null
+++ b/nbproject/private/private.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/nbproject/project.properties b/nbproject/project.properties
new file mode 100644
index 0000000..d1a7245
--- /dev/null
+++ b/nbproject/project.properties
@@ -0,0 +1,146 @@
+abilities=MMAPI=1.1,JSR82=1.1,JSR280=1.0,JSR226=1.0,MIDP=2.1,JSR229=1.1,SATSA=1.0,CLDC=1.1,JSR177=1.0,JSR179=1.0,J2MEWS=1.0,WMA=2.0,JSR172=1.0,JSR256=1.0,OBEX=1.0,ColorScreen,JSR238=1.0,JSR239=1.0,JSR211=1.0,JSR234=1.0,ScreenWidth=240,JSR75=1.0,JSR184=1.1,ScreenHeight=320,ScreenColorDepth=16,JSR180=1.1.0,
+all.configurations=\
+application.args=
+application.description=
+application.description.detail=
+application.name=
+application.vendor=Vendor
+build.classes.dir=${build.dir}/compiled
+build.classes.excludes=**/*.java,**/*.form,**/*.class,**/.nbintdb,**/*.mvd,**/*.wsclient,**/*.vmd
+build.dir=build/${config.active}
+build.root.dir=build
+debug.level=debug
+debugger.timeout=
+deployment.copy.target=${file.reference.deploy-1}
+deployment.instance=default
+deployment.jarurl=${dist.jar}
+deployment.method=NONE
+deployment.override.jarurl=false
+dist.dir=dist/${config.active}
+dist.jad=QRreader.jad
+dist.jar=QRreader.jar
+dist.javadoc.dir=${dist.dir}/doc
+dist.root.dir=dist
+extra.classpath=
+file.reference.builtin.ks=${netbeans.user}/config/j2me/builtin.ks
+file.reference.deploy-1=deploy
+file.reference.jsr75_1.0.jar=jsr75_1.0.jar
+file.reference.netbeans_midp_components_basic.jar=../../../../../Program Files/NetBeans 6.9.1/mobility/modules/ext/netbeans_midp_components_basic.jar
+filter.exclude.tests=false
+filter.excludes=
+filter.more.excludes=**/overview.html,**/package.html
+filter.use.standard=true
+jar.compress=true
+javac.debug=true
+javac.deprecation=false
+javac.encoding=UTF-8
+javac.optimize=false
+javac.source=1.3
+javac.target=1.3
+javadoc.author=false
+javadoc.encoding=
+javadoc.noindex=false
+javadoc.nonavbar=false
+javadoc.notree=false
+javadoc.private=false
+javadoc.splitindex=true
+javadoc.use=true
+javadoc.version=false
+javadoc.windowtitle=
+libs.classpath=${libs.NetBeans MIDP Components.classpath};${libs.NetBeans MIDP Components PDA.classpath};${file.reference.jsr75_1.0.jar};${file.reference.netbeans_midp_components_basic.jar}
+main.class=
+main.class.class=applet
+manifest.apipermissions=
+manifest.file=manifest.mf
+manifest.is.liblet=false
+manifest.jad=
+manifest.manifest=
+manifest.midlets=MIDlet-1: \u4e8c\u7ef4\u7801\u52a9\u624b,/zxing-icon.png,com.google.zxing.client.j2me.ZXMIDlet\n
+manifest.others=MIDlet-Vendor: Vendor\nMIDlet-Name: \u4e8c\u7ef4\u7801\u52a9\u624b\nMIDlet-Version: 1.0\n
+manifest.pushregistry=
+name=QRreader
+no.dependencies=false
+nokiaS80.application.icon=
+nsicom.application.monitorhost=
+nsicom.application.runremote=
+nsicom.application.runverbose=
+nsicom.remoteapp.location=\\My Documents\\NetBeans Applications
+nsicom.remotevm.location=\\Windows\\creme\\bin\\CrEme.exe
+obfuscated.classes.dir=${build.dir}/obfuscated
+obfuscation.custom=
+obfuscation.level=0
+obfuscator.destjar=${build.dir}/obfuscated.jar
+obfuscator.srcjar=${build.dir}/before-obfuscation.jar
+platform.active=Java_TM__Platform_Micro_Edition_SDK_3_0
+platform.active.description=Java(TM) Platform Micro Edition SDK 3.0
+platform.apis=MMAPI-1.1,SATSA-1.0,WMA-1.1
+platform.bootclasspath=${platform.home}/lib/jsr135_1.1.jar:${platform.home}/lib/jsr177_1.0.jar:${platform.home}/lib/jsr120_1.1.jar:${platform.home}/lib/midp_2.1.jar:${platform.home}/lib/cldc_1.1.jar
+platform.configuration=CLDC-1.1
+platform.device=ClamshellCldcPhone1
+platform.fat.jar=true
+platform.profile=MIDP-2.1
+platform.trigger=CLDC
+platform.type=UEI-1.0.1
+preprocessed.dir=${build.dir}/preprocessed
+preverify.classes.dir=${build.dir}/preverified
+preverify.sources.dir=${build.dir}/preverifysrc
+resources.dir=resources
+ricoh.application.email=
+ricoh.application.fax=
+ricoh.application.icon=
+ricoh.application.target-jar=
+ricoh.application.telephone=
+ricoh.application.uid=12248964
+ricoh.application.version=
+ricoh.dalp.application-desc.auto-run=false
+ricoh.dalp.application-desc.energy-save=
+ricoh.dalp.application-desc.exec-auth=
+ricoh.dalp.application-desc.visible=true
+ricoh.dalp.argument=
+ricoh.dalp.codebase=
+ricoh.dalp.display-mode.color=true
+ricoh.dalp.display-mode.is-4line-support=false
+ricoh.dalp.display-mode.is-hvga-support=true
+ricoh.dalp.display-mode.is-vga-support=false
+ricoh.dalp.display-mode.is-wvga-support=false
+ricoh.dalp.information.abbreviation=
+ricoh.dalp.information.icon.basepath=
+ricoh.dalp.information.icon.location=
+ricoh.dalp.information.is-icon-used=true
+ricoh.dalp.install.destination=hdd
+ricoh.dalp.install.mode.auto=true
+ricoh.dalp.install.work-dir=hdd
+ricoh.dalp.is-managed=true
+ricoh.dalp.resources.dsdk.version=2.0
+ricoh.dalp.resources.jar.basepath=
+ricoh.dalp.resources.jar.version=
+ricoh.dalp.version=
+ricoh.icon.invert=false
+ricoh.platform.target.version=
+run.cmd.options=
+run.jvmargs=
+run.method=STANDARD
+run.security.domain=minimum
+run.use.security.domain=false
+savaje.application.icon=
+savaje.application.icon.focused=
+savaje.application.icon.small=
+savaje.application.uid=TBD
+savaje.bundle.base=
+savaje.bundle.debug=false
+savaje.bundle.debug.port=
+semc.application.caps=
+semc.application.icon=
+semc.application.icon.count=
+semc.application.icon.splash=
+semc.application.icon.splash.installonly=false
+semc.application.uid=E5047815
+semc.certificate.path=
+semc.private.key.password=
+semc.private.key.path=
+sign.alias=trusted
+sign.enabled=false
+sign.keystore=${file.reference.builtin.ks}
+src.dir=src
+use.emptyapis=true
+use.preprocessor=true
diff --git a/nbproject/project.xml b/nbproject/project.xml
new file mode 100644
index 0000000..42e3b7d
--- /dev/null
+++ b/nbproject/project.xml
@@ -0,0 +1,10 @@
+
+
+ org.netbeans.modules.kjava.j2meproject
+
+
+ QRreader
+ 1.6
+
+
+
diff --git a/res/zxing-icon.png b/res/zxing-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee33194c04e70fca8e7337a3b93852fbf1aa66f1
GIT binary patch
literal 5894
zcmWkyWmFVh7@no1yFt3UrE}>{rI!|vj*pP;Tv(+$Md^@kkS+l!r4|VhrArXL@ywaI
zXU@4l-h1y;?@iFtQN_ci#s&ZYPhAaa0FLtiUQ7(|`@z|30~{be2C9lc-3;vy@CUKjyiOgeU=*-6Z654NI-2u%`PcEmyq%qIG@Y8}_${9#@~5CZ38+wTE|A)$fYBxzq^~SUeGnw+c;fOpG
zK&2E+qn!p+!i7vMMJW6G<;(d@Ez<*Ze`~&i23m*3D9;dKQ8XK)1WasuE6B(5C?=bD0+v!l5lk(IXU^8TQL@rIov6#yvZ1y(fEugt}0pV^Sq?xX|j60sQ-sv
zCq*2T1=1tfnbg!8HI^p_z)BJ|?6Ql*R04RtJ1j(Tfx+#QQNWyy-O2yp$AK166$L6@
z*|weUVsg1pQv=7Dz@iR1&5;jxzvz}G_EN)yltmpAIUOAzGv(JiOlF6ckk>10w`B^M!#lNAsv`$sHsaomu+*XtX##JcROi798v2+
z2DD1W)c2fh=}}9{%1GqyR&^E^^Hs%ZG4^S_yu4t^+hggE!{5gZMV)Q7=@j{x3feRg
zZ3&kv2CT%NSTg51stQR(#MJe=mHa!DJ{U3h`u~~RSc=6WVFO_8p4*DU7aqO0^eY{9
z?}oVq!lU{zw~{|e0DLdAX!KYS*6d2$XgSxVaQR55gGZ$+oKS_OyTh)%|Csm=uBTqB
z$_hU`9yQN41YIr*I-J?Jum9#~asRl{E9(~OZXP!b7(n@nmfNexX|eVqwIrYPt0%JY
zvBC0rBnTiD*%$8f&8dz&k+qy!`ZaFF_4Lbi)}#AHVX1G}byGDWqhl2n-y2ePobhHE
z3B5Yd*diz57FUnN|A?<+0B5YLTEbpHX?NSH6d|%E@f|A9WfjpzRq(o;VGZeC50QdmILb=@LZB$Q)o@6|+-9O-#tSxVcZO
zt!1jyxSk0l(Qx~jHf_$svLi$NhoV*mHBN?m;dO|-DQ1P%D{laJ(8kNfRNlCQ-21Ru3w8f>uik@Rw7IiMo#iMg?`??4IT3S
z@IWJ8hXCyH>!P&Hzn9%m)<%M;bX?7;VX%0Ek4|DM;H_yggqIcAfb%Lp&Bv
z|6jR9rFF5fN}iGhirtW+*JoIf0}SN6W~AqD`K`L+;j(|!f?J(t>3A$VjW(9QEV*c<
zL9j_$3erdJSk{F_MA!Sbhr&kkJbDvcm+Kg5@uLJ|+uOzMot$?1BT%!&y_gVh-lXN^
zpd-VFAe{f;dE
zx$a;6<4s>^WXk5F?Aq6_eNAN7pD3)i=1P=SnjDgbGlz$v_KuE&yS^weOnCI?g$2~U
zO@bMT57pJDBQzf2KB>LEy^nsQYr)4oadAtsEdFY+(6DEwVsK9mOq{xo3aZo7Q#Ca;
z%F8E0LPCTT6mjzX@Qn>w@d6!fLmZ5#mmVIVDySG6lNG)`Q^&=}FRiS^INzU*+dNja
zwx*Z8KT)c&?z74Z22m1lwdy1&AP}DS%)=#w4*rCblhbUo-D`)Co}PanExfRhPMj7W
zES6d;?G3V!xCs%l_480sLj&8}$LEn#4NfIYZa0#g5>SXhM817X3iN-eel933jtLk{
zr971)w;N4W4hjmI>L4K|W{INJE|Pe+I#VDOmz<0Xglh_`kdEwM-!;MpP`WSXHE#~e
z;AwezNvKcop??oza41eL7Ia-_P3o;uQfQtb?{Cjr0S<2NA&`x#=>7NiuaCaA
z5dbGs!n3fKFF6j|=jRQghis(pPlh;nc%nyl6#V^LNuxsh=v|j;24eAO=H}+YExY_}
zJUt0%=)-80uX)>0`?t`XW(&zUY~_C*d@dWCorQP2D4LBcQ-!`e_PLSPNg@p7RHM@FS)l)dlS3oZEFCk19zcl*m%bI;7SJh@
zO-UeOXA%}Bv9z@G=5_pecqoLwv45WK=V##b4xX5l#5}AO8Y;V9=QUF?CRG3Jo5gBT
zc6Qa}?(XiV{GTh0c5P%Gdl`j=Iuv{sEY?o!eulCT%Rtj#-PRg^5
zxx$=-53N$a+jm++u0|wA$Hw9puduPPYff|=SVJ$$=pn%GzQ@q+hc#DNWhbX}`@E9^
z?j^L_xY=PH;tuKKT{vor6V;SQHCAA*tD!ojJ~)8o*6DF(;DDvNJIGxE@rAoE7Q
zpCQ&Uj0AnMMp
zE-&OT77GhYaaEOC@tHj?F0RIG^XSe12rK~5%;Zr3c@HX%fZqh^1p%(w-Ok02g@c2G
zx$N`k1QN(h?LpR$=k{ni8n2?TvI>0nUd(vmqnx+4BBRT7*OmKAibKISyf>YvZ)KGVqEkdvRQYKzSWt~s4>h1?Zk`0L
ze!O>Q6kY~$hRvFTpMP{MZb=b~hWpgWoEG8xr0cNT+tVf~3U^oOOPPfJ6ye^ZgQl+T
z=-=ylfnmC5IvGP}y2R|B+jq>!d35JL7HT$Fd;o*{@UMqI>Z}?Xz4$=D=_iV9|1OYu
z8JIiB-Lova!wa^K`=w|V&za{wl=m#_1VWOtX}
zZhX0yJYz_h6`^ewV1^U-e!#Xpdk#%XQnJzahrxSTy9Hl>fpq(E?_C;5ab6ZtQ8M6U
z&9%OyvomP1%5~yCF%kP)cOVHB71c_!(`?md7&3vq!R3nvw{H!)CEjkrs^KefVOwHc
z6^lsD7lSzw5Kw=>i7GBGTvma*WaCpvB=Q85YvgBD`i!Kiy1Go3(}5@S|17S_W3|cQ
zMWZb`KAm(GmCTiirDY0;)9<^xA*_J(%~q7{vBkM`b%cpAJqjeb9k-Uo%8h#hpOAKY
zTi~1fGg*%t+5s4$pP!$2@D-1mIJ@=*0UC<9-y!M!LpanhgQ~K!64}=Gmv^bwJnoun
zV`xEF?x}HLQ`^Hs=;HFS-AulOXVk7n^o&
zG=JYcv5bt&$?a=3ZS7C3zrLQnlY5#7li%bEp&Z?iNuBoy;U45L>qT1E!UdVxK(mHF
zF{v9wC8#kF66P`0GMOjl(0Dc54Tr-=nSVY^Gq?($xUk78*uUpccq$XcB2+(al#Lla
z=}YLiZ$I?Qf9IxaG+joPCW(T7U?PKH>vGw86y&y`$AlYu7#luG92
zc{Z@u*Ihx288vF&Eocy8eFs10HjUnZhui&h2|ki7dp$X3nU2W3bY7xx<9Qzjh{FlK
zBSs6lJuMUsJ{na%%rccLiX*Y(Gq-m531n5H<1{r3D{Dz(Bax}8=}Xu1Qjkr&yu2@8
zy?Tq}VPRo=e7eeXj(C9Djy}pDF)1hIVRDu#=J(l
z__Wm-C~aKQ>}7GtdE^-g`v+sLVJuVW`T5DBu1jjFDq&%A(sXddm-2E!4ZLKd?}mi)
zh#3&wvohDTzk9B&c=P>0-vtwoMn#;heFZf3W_-d8AW3)50T`E&Ok}wX0`7BNt70A-
z$r;?wZS7fbi|TRk<4`?%E;IL*?GkNm{*WbPBhuu}ydyLCIZ?Myf_hdueAxYuR!Shn
zU0pQJ&dx$2>6T%1b=cqzhFZhXk96m5Fy3hp0ChEV0~;6v70X+2UDKB
zwl*G^lXq`g!CX-wtvp|mLxkZ7%~ID67JkM{%o%Hv}c1Ht-7z6^*0Q#Ac
zko!Lih2L}hw4vS0(t2o|*v?zJkj{VD5P!sZf7{~mtr??n+=lOZ>W+3AB4}^jk
zI%rP>Q8AMv;Mu`~X8zkP+f-GX9rBiwSoIgV(E=stBJ&84L%plg6_oFOTf
zYj-DIR0np8lZCpU6T&0=#LnUgzLSt9E|G>78t^Jn%f;n`eJm(#;(=%75i(Pm4vuUp
zgN3-x7c?U8S;VfcY`?a;fiYS*pHriWl|6FKv!>-KVku&nm~$!S$7qI^MZu&|G>>Wt
zz_1+hh$hH=UT~8_K|1qxg
zrr&1r5dMKyL9i-0M6z9o3SR0`AnB)3^_GQUb{F~0z0KF#qW3jb{lo7~hhB#F->hwo
z?In4}Fr#u(v$Cv$ut50-*+gz;ZeFoQYlESBzt$)ci(QwBKEU{fOuZdGo>mqi4*;G&
z=2=CaJ#$r0AI57Gl9i&_pDoJj58BPh@~S*cz6n_HZY?b$IN`Zs(pgtMTCZ8Sg}-_($oWI-&Oa~7KZ{O0t2Pft%(b+uARh)is3tmnC7n{;?(CEKrH
zatTC=>dLg&x_zef6hg(grmnRPEqS9u*)op9SwG2{)OD6?N-@lX|Fxy1r-bF$#6;x6
zF6jnErBeY{OpL~ETIL5~Y3bz5Oq!F{k2Y&>-;Yhqv})|YGLS1WGNj6=EoV8*M#G|7
z(G{|6FsdXIrZNhY5iYXCKVdayG#`(MG{%cF-`nes#kzb(AeQX@thRK@PyZSrrSe$C
ztjd=Q3r*LYJ2wQXnPLN=jpIIvzcb_Ky1p!zVXVCB82``~C^6@WOS)9e1kX
zT@2L>bFZ$MZJQZc9HVW3hPk1(wl-Gy5Repi=qr*zz3^J}=f@S^2}8FSN3(jocg*?_vT4zx}Y7WqF1%<>oj
literal 0
HcmV?d00001
diff --git a/src/com/google/zxing/BarcodeFormat.java b/src/com/google/zxing/BarcodeFormat.java
new file mode 100644
index 0000000..1cca09a
--- /dev/null
+++ b/src/com/google/zxing/BarcodeFormat.java
@@ -0,0 +1,103 @@
+/*
+ * 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.Hashtable;
+
+/**
+ * Enumerates barcode formats known to this package.
+ *
+ * @author Sean Owen
+ */
+public final class BarcodeFormat {
+
+ // No, we can't use an enum here. J2ME doesn't support it.
+
+ private static final Hashtable VALUES = new Hashtable();
+
+ /** QR Code 2D barcode format. */
+ public static final BarcodeFormat QR_CODE = new BarcodeFormat("QR_CODE");
+
+ /** Data Matrix 2D barcode format. */
+ public static final BarcodeFormat DATA_MATRIX = new BarcodeFormat("DATA_MATRIX");
+
+ /** UPC-E 1D format. */
+ public static final BarcodeFormat UPC_E = new BarcodeFormat("UPC_E");
+
+ /** UPC-A 1D format. */
+ public static final BarcodeFormat UPC_A = new BarcodeFormat("UPC_A");
+
+ /** EAN-8 1D format. */
+ public static final BarcodeFormat EAN_8 = new BarcodeFormat("EAN_8");
+
+ /** EAN-13 1D format. */
+ public static final BarcodeFormat EAN_13 = new BarcodeFormat("EAN_13");
+
+ /** UPC/EAN extension format. Not a stand-alone format. */
+ public static final BarcodeFormat UPC_EAN_EXTENSION = new BarcodeFormat("UPC_EAN_EXTENSION");
+
+ /** Code 128 1D format. */
+ public static final BarcodeFormat CODE_128 = new BarcodeFormat("CODE_128");
+
+ /** Code 39 1D format. */
+ public static final BarcodeFormat CODE_39 = new BarcodeFormat("CODE_39");
+
+ /** Code 93 1D format. */
+ public static final BarcodeFormat CODE_93 = new BarcodeFormat("CODE_93");
+
+ /** CODABAR 1D format. */
+ public static final BarcodeFormat CODABAR = new BarcodeFormat("CODABAR");
+
+ /** ITF (Interleaved Two of Five) 1D format. */
+ public static final BarcodeFormat ITF = new BarcodeFormat("ITF");
+
+ /** RSS 14 */
+ public static final BarcodeFormat RSS14 = new BarcodeFormat("RSS14");
+
+ /** PDF417 format. */
+ public static final BarcodeFormat PDF417 = new BarcodeFormat("PDF417");
+
+ /** RSS EXPANDED */
+ public static final BarcodeFormat RSS_EXPANDED = new BarcodeFormat("RSS_EXPANDED");
+
+ private final String name;
+
+ private BarcodeFormat(String name) {
+ this.name = name;
+ VALUES.put(name, this);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ public static BarcodeFormat valueOf(String name) {
+ if (name == null || name.length() == 0) {
+ throw new IllegalArgumentException();
+ }
+ BarcodeFormat format = (BarcodeFormat) VALUES.get(name);
+ if (format == null) {
+ throw new IllegalArgumentException();
+ }
+ return format;
+ }
+
+}
diff --git a/src/com/google/zxing/Binarizer.java b/src/com/google/zxing/Binarizer.java
new file mode 100644
index 0000000..912a3b5
--- /dev/null
+++ b/src/com/google/zxing/Binarizer.java
@@ -0,0 +1,80 @@
+/*
+ * 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) {
+ if (source == null) {
+ throw new IllegalArgumentException("Source must be non-null.");
+ }
+ this.source = source;
+ }
+
+ public 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, 0 <= y < 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).
+ */
+ 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).
+ */
+ 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);
+
+}
diff --git a/src/com/google/zxing/BinaryBitmap.java b/src/com/google/zxing/BinaryBitmap.java
new file mode 100644
index 0000000..b97e467
--- /dev/null
+++ b/src/com/google/zxing/BinaryBitmap.java
@@ -0,0 +1,128 @@
+/*
+ * 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;
+ matrix = null;
+ }
+
+ /**
+ * @return The width of the bitmap.
+ */
+ public int getWidth() {
+ return binarizer.getLuminanceSource().getWidth();
+ }
+
+ /**
+ * @return The height of the bitmap.
+ */
+ public int getHeight() {
+ return binarizer.getLuminanceSource().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, 0 <= y < 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).
+ */
+ 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).
+ */
+ 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, 0 <= left < getWidth().
+ * @param top The top coordinate, 0 <= top <= 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. Only callable if isRotateSupported() is true.
+ *
+ * @return A rotated version of this object.
+ */
+ public BinaryBitmap rotateCounterClockwise() {
+ LuminanceSource newSource = binarizer.getLuminanceSource().rotateCounterClockwise();
+ return new BinaryBitmap(binarizer.createBinarizer(newSource));
+ }
+
+}
diff --git a/src/com/google/zxing/ChecksumException.java b/src/com/google/zxing/ChecksumException.java
new file mode 100644
index 0000000..dedb4be
--- /dev/null
+++ b/src/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/src/com/google/zxing/DecodeHintType.java b/src/com/google/zxing/DecodeHintType.java
new file mode 100644
index 0000000..20b922c
--- /dev/null
+++ b/src/com/google/zxing/DecodeHintType.java
@@ -0,0 +1,79 @@
+/*
+ * 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;
+
+/**
+ * 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.Hashtable)
+ */
+public final class DecodeHintType {
+
+ // No, we can't use an enum here. J2ME doesn't support it.
+
+ /**
+ * Unspecified, application-specific hint. Maps to an unspecified {@link Object}.
+ */
+ public static final DecodeHintType OTHER = new DecodeHintType();
+
+ /**
+ * Image is a pure monochrome image of a barcode. Doesn't matter what it maps to;
+ * use {@link Boolean#TRUE}.
+ */
+ public static final DecodeHintType PURE_BARCODE = new DecodeHintType();
+
+ /**
+ * Image is known to be of one of a few possible formats.
+ * Maps to a {@link java.util.Vector} of {@link BarcodeFormat}s.
+ */
+ public static final DecodeHintType POSSIBLE_FORMATS = new DecodeHintType();
+
+ /**
+ * 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}.
+ */
+ public static final DecodeHintType TRY_HARDER = new DecodeHintType();
+
+ /**
+ * Specifies what character encoding to use when decoding, where applicable (type String)
+ */
+ public static final DecodeHintType CHARACTER_SET = new DecodeHintType();
+
+ /**
+ * Allowed lengths of encoded data -- reject anything else. Maps to an int[].
+ */
+ public static final DecodeHintType ALLOWED_LENGTHS = new DecodeHintType();
+
+ /**
+ * Assume Code 39 codes employ a check digit. Maps to {@link Boolean}.
+ */
+ public static final DecodeHintType ASSUME_CODE_39_CHECK_DIGIT = new DecodeHintType();
+
+ /**
+ * The caller needs to be notified via callback when a possible {@link ResultPoint}
+ * is found. Maps to a {@link ResultPointCallback}.
+ */
+ public static final DecodeHintType NEED_RESULT_POINT_CALLBACK = new DecodeHintType();
+
+ private DecodeHintType() {
+ }
+
+}
diff --git a/src/com/google/zxing/EncodeHintType.java b/src/com/google/zxing/EncodeHintType.java
new file mode 100644
index 0000000..35afc15
--- /dev/null
+++ b/src/com/google/zxing/EncodeHintType.java
@@ -0,0 +1,39 @@
+/*
+ * 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 final class EncodeHintType {
+
+ /**
+ * Specifies what degree of error correction to use, for example in QR Codes (type Integer).
+ */
+ public static final EncodeHintType ERROR_CORRECTION = new EncodeHintType();
+
+ /**
+ * Specifies what character encoding to use where applicable (type String)
+ */
+ public static final EncodeHintType CHARACTER_SET = new EncodeHintType();
+
+ private EncodeHintType() {
+ }
+
+}
diff --git a/src/com/google/zxing/FormatException.java b/src/com/google/zxing/FormatException.java
new file mode 100644
index 0000000..6967e93
--- /dev/null
+++ b/src/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/src/com/google/zxing/LuminanceSource.java b/src/com/google/zxing/LuminanceSource.java
new file mode 100644
index 0000000..4b6d453
--- /dev/null
+++ b/src/com/google/zxing/LuminanceSource.java
@@ -0,0 +1,113 @@
+/*
+ * 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, 0 <= y < 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:
+ * 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, 0 <= left < getWidth().
+ * @param top The top coordinate, 0 <= top <= 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 RuntimeException("This luminance source does not support cropping.");
+ }
+
+ /**
+ * @return Whether this subclass supports counter-clockwise rotation.
+ */
+ public boolean isRotateSupported() {
+ return false;
+ }
+
+ /**
+ * Returns a new object with rotated image data. Only callable if isRotateSupported() is true.
+ *
+ * @return A rotated version of this object.
+ */
+ public LuminanceSource rotateCounterClockwise() {
+ throw new RuntimeException("This luminance source does not support rotation.");
+ }
+
+}
diff --git a/src/com/google/zxing/MultiFormatReader.java b/src/com/google/zxing/MultiFormatReader.java
new file mode 100644
index 0000000..42a97fe
--- /dev/null
+++ b/src/com/google/zxing/MultiFormatReader.java
@@ -0,0 +1,166 @@
+/*
+ * 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.datamatrix.DataMatrixReader;
+import com.google.zxing.oned.MultiFormatOneDReader;
+import com.google.zxing.pdf417.PDF417Reader;
+import com.google.zxing.qrcode.QRCodeReader;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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 Hashtable hints;
+ private Vector 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
+ */
+ 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
+ */
+ public Result decode(BinaryBitmap image, Hashtable 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(Hashtable hints) {
+ this.hints = hints;
+
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ Vector formats = hints == null ? null : (Vector) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ readers = new Vector();
+ 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.RSS14) ||
+ formats.contains(BarcodeFormat.RSS_EXPANDED);
+ // Put 1D readers upfront in "normal" mode
+ if (addOneDReader && !tryHarder) {
+ readers.addElement(new MultiFormatOneDReader(hints));
+ }
+ if (formats.contains(BarcodeFormat.QR_CODE)) {
+ readers.addElement(new QRCodeReader());
+ }
+ if (formats.contains(BarcodeFormat.DATA_MATRIX)) {
+ readers.addElement(new DataMatrixReader());
+ }
+ if (formats.contains(BarcodeFormat.PDF417)) {
+ readers.addElement(new PDF417Reader());
+ }
+ // At end in "try harder" mode
+ if (addOneDReader && tryHarder) {
+ readers.addElement(new MultiFormatOneDReader(hints));
+ }
+ }
+ if (readers.isEmpty()) {
+ if (!tryHarder) {
+ readers.addElement(new MultiFormatOneDReader(hints));
+ }
+ readers.addElement(new QRCodeReader());
+
+ readers.addElement(new DataMatrixReader());
+
+ // TODO: Enable once PDF417 has passed QA
+ //readers.addElement(new PDF417Reader());
+
+ if (tryHarder) {
+ readers.addElement(new MultiFormatOneDReader(hints));
+ }
+ }
+ }
+
+ public void reset() {
+ int size = readers.size();
+ for (int i = 0; i < size; i++) {
+ Reader reader = (Reader) readers.elementAt(i);
+ reader.reset();
+ }
+ }
+
+ private Result decodeInternal(BinaryBitmap image) throws NotFoundException {
+ int size = readers.size();
+ for (int i = 0; i < size; i++) {
+ Reader reader = (Reader) readers.elementAt(i);
+ try {
+ return reader.decode(image, hints);
+ } catch (ReaderException re) {
+ // continue
+ }
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+}
diff --git a/src/com/google/zxing/MultiFormatWriter.java b/src/com/google/zxing/MultiFormatWriter.java
new file mode 100644
index 0000000..297b79e
--- /dev/null
+++ b/src/com/google/zxing/MultiFormatWriter.java
@@ -0,0 +1,65 @@
+/*
+ * 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 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.qrcode.QRCodeWriter;
+
+import java.util.Hashtable;
+
+/**
+ * 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 {
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width,
+ int height) throws WriterException {
+
+ return encode(contents, format, width, height, null);
+ }
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height,
+ Hashtable hints) throws WriterException {
+
+ Writer writer;
+ if (format == BarcodeFormat.EAN_8) {
+ writer = new EAN8Writer();
+ } else if (format == BarcodeFormat.EAN_13) {
+ writer = new EAN13Writer();
+ } else if (format == BarcodeFormat.QR_CODE) {
+ writer = new QRCodeWriter();
+ } else if (format == BarcodeFormat.CODE_39) {
+ writer = new Code39Writer();
+ } else if (format == BarcodeFormat.CODE_128) {
+ writer = new Code128Writer();
+ } else if (format == BarcodeFormat.ITF) {
+ writer = new ITFWriter();
+ } else {
+ throw new IllegalArgumentException("No encoder available for format " + format);
+ }
+ return writer.encode(contents, format, width, height, hints);
+ }
+
+}
diff --git a/src/com/google/zxing/NotFoundException.java b/src/com/google/zxing/NotFoundException.java
new file mode 100644
index 0000000..dedab8d
--- /dev/null
+++ b/src/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/src/com/google/zxing/Reader.java b/src/com/google/zxing/Reader.java
new file mode 100644
index 0000000..47e843b
--- /dev/null
+++ b/src/com/google/zxing/Reader.java
@@ -0,0 +1,64 @@
+/*
+ * 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.Hashtable;
+
+/**
+ * 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 the barcode cannot be located or decoded for any reason
+ */
+ 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.Hashtable} 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 the barcode cannot be located or decoded for any reason
+ */
+ Result decode(BinaryBitmap image, Hashtable 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/src/com/google/zxing/ReaderException.java b/src/com/google/zxing/ReaderException.java
new file mode 100644
index 0000000..224a497
--- /dev/null
+++ b/src/com/google/zxing/ReaderException.java
@@ -0,0 +1,98 @@
+/*
+ * 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 {
+
+ // TODO: Currently we throw up to 400 ReaderExceptions while scanning a single 240x240 image before
+ // rejecting it. This involves a lot of overhead and memory allocation, and affects both performance
+ // and latency on continuous scan clients. In the future, we should change all the decoders not to
+ // throw exceptions for routine events, like not finding a barcode on a given row. Instead, we
+ // should return error codes back to the callers, and simply delete this class. In the mean time, I
+ // have altered this class to be as lightweight as possible, by ignoring the exception string, and
+ // by disabling the generation of stack traces, which is especially time consuming. These are just
+ // temporary measures, pending the big cleanup.
+
+ //private static final ReaderException instance = new ReaderException();
+
+ // EXCEPTION TRACKING SUPPORT
+ // Identifies who is throwing exceptions and how often. To use:
+ //
+ // 1. Uncomment these lines and the code below which uses them.
+ // 2. Uncomment the two corresponding lines in j2se/CommandLineRunner.decode()
+ // 3. Change core to build as Java 1.5 temporarily
+// private static int exceptionCount = 0;
+// private static Map throwers = new HashMap(32);
+
+ ReaderException() {
+ // do nothing
+ }
+
+ //public static ReaderException getInstance() {
+// Exception e = new Exception();
+// // Take the stack frame before this one.
+// StackTraceElement stack = e.getStackTrace()[1];
+// String key = stack.getClassName() + "." + stack.getMethodName() + "(), line " +
+// stack.getLineNumber();
+// if (throwers.containsKey(key)) {
+// Integer value = throwers.get(key);
+// value++;
+// throwers.put(key, value);
+// } else {
+// throwers.put(key, 1);
+// }
+// exceptionCount++;
+
+ //return instance;
+ //}
+
+// public static int getExceptionCountAndReset() {
+// int temp = exceptionCount;
+// exceptionCount = 0;
+// return temp;
+// }
+//
+// public static String getThrowersAndReset() {
+// StringBuilder builder = new StringBuilder(1024);
+// Object[] keys = throwers.keySet().toArray();
+// for (int x = 0; x < keys.length; x++) {
+// String key = (String) keys[x];
+// Integer value = throwers.get(key);
+// builder.append(key);
+// builder.append(": ");
+// builder.append(value);
+// builder.append("\n");
+// }
+// throwers.clear();
+// return builder.toString();
+// }
+
+ // 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.
+ public final Throwable fillInStackTrace() {
+ return null;
+ }
+
+}
diff --git a/src/com/google/zxing/Result.java b/src/com/google/zxing/Result.java
new file mode 100644
index 0000000..ee1af52
--- /dev/null
+++ b/src/com/google/zxing/Result.java
@@ -0,0 +1,143 @@
+/*
+ * 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.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * 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 Hashtable 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) {
+ if (text == null && rawBytes == null) {
+ throw new IllegalArgumentException("Text and bytes are null");
+ }
+ 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, if applicable, otherwise null
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * @return raw bytes encoded by the barcode, if applicable, otherwise 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 Hashtable} mapping {@link ResultMetadataType} keys to values. May be
+ * null
. This contains optional metadata about what was detected about the barcode,
+ * like orientation.
+ */
+ public Hashtable getResultMetadata() {
+ return resultMetadata;
+ }
+
+ public void putMetadata(ResultMetadataType type, Object value) {
+ if (resultMetadata == null) {
+ resultMetadata = new Hashtable(3);
+ }
+ resultMetadata.put(type, value);
+ }
+
+ public void putAllMetadata(Hashtable metadata) {
+ if (metadata != null) {
+ if (resultMetadata == null) {
+ resultMetadata = metadata;
+ } else {
+ Enumeration e = metadata.keys();
+ while (e.hasMoreElements()) {
+ ResultMetadataType key = (ResultMetadataType) e.nextElement();
+ Object value = metadata.get(key);
+ resultMetadata.put(key, value);
+ }
+ }
+ }
+ }
+
+ public void addResultPoints(ResultPoint[] newPoints) {
+ if (resultPoints == null) {
+ resultPoints = newPoints;
+ } else if (newPoints != null && newPoints.length > 0) {
+ ResultPoint[] allPoints = new ResultPoint[resultPoints.length + newPoints.length];
+ System.arraycopy(resultPoints, 0, allPoints, 0, resultPoints.length);
+ System.arraycopy(newPoints, 0, allPoints, resultPoints.length, newPoints.length);
+ resultPoints = allPoints;
+ }
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public String toString() {
+ if (text == null) {
+ return "[" + rawBytes.length + " bytes]";
+ } else {
+ return text;
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/ResultMetadataType.java b/src/com/google/zxing/ResultMetadataType.java
new file mode 100644
index 0000000..33d69d9
--- /dev/null
+++ b/src/com/google/zxing/ResultMetadataType.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;
+
+import java.util.Hashtable;
+
+/**
+ * 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 final class ResultMetadataType {
+
+ // No, we can't use an enum here. J2ME doesn't support it.
+
+ private static final Hashtable VALUES = new Hashtable();
+
+ // No, we can't use an enum here. J2ME doesn't support it.
+
+ /**
+ * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
+ */
+ public static final ResultMetadataType OTHER = new ResultMetadataType("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).
+ */
+ public static final ResultMetadataType ORIENTATION = new ResultMetadataType("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.Vector} of byte arrays corresponding to the
+ * raw bytes in the byte segments in the barcode, in order.
+ */
+ public static final ResultMetadataType BYTE_SEGMENTS = new ResultMetadataType("BYTE_SEGMENTS");
+
+ /**
+ * Error correction level used, if applicable. The value type depends on the
+ * format, but is typically a String.
+ */
+ public static final ResultMetadataType ERROR_CORRECTION_LEVEL = new ResultMetadataType("ERROR_CORRECTION_LEVEL");
+
+ /**
+ * For some periodicals, indicates the issue number as an {@link Integer}.
+ */
+ public static final ResultMetadataType ISSUE_NUMBER = new ResultMetadataType("ISSUE_NUMBER");
+
+ /**
+ * For some products, indicates the suggested retail price in the barcode as a
+ * formatted {@link String}.
+ */
+ public static final ResultMetadataType SUGGESTED_PRICE = new ResultMetadataType("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".
+ */
+ public static final ResultMetadataType POSSIBLE_COUNTRY = new ResultMetadataType("POSSIBLE_COUNTRY");
+
+ private final String name;
+
+ private ResultMetadataType(String name) {
+ this.name = name;
+ VALUES.put(name, this);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ public static ResultMetadataType valueOf(String name) {
+ if (name == null || name.length() == 0) {
+ throw new IllegalArgumentException();
+ }
+ ResultMetadataType format = (ResultMetadataType) VALUES.get(name);
+ if (format == null) {
+ throw new IllegalArgumentException();
+ }
+ return format;
+ }
+
+}
diff --git a/src/com/google/zxing/ResultPoint.java b/src/com/google/zxing/ResultPoint.java
new file mode 100644
index 0000000..0c8e883
--- /dev/null
+++ b/src/com/google/zxing/ResultPoint.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+/**
+ * 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;
+ }
+
+ public boolean equals(Object other) {
+ if (other instanceof ResultPoint) {
+ ResultPoint otherPoint = (ResultPoint) other;
+ return x == otherPoint.x && y == otherPoint.y;
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return 31 * Float.floatToIntBits(x) + Float.floatToIntBits(y);
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(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 < AC and
+ * BC < AC and the angle between BC and BA is less than 180 degrees.
+ */
+ 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, pointB, 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;
+ }
+
+
+ /**
+ * @return distance between two points
+ */
+ public static float distance(ResultPoint pattern1, ResultPoint pattern2) {
+ float xDiff = pattern1.getX() - pattern2.getX();
+ float yDiff = pattern1.getY() - pattern2.getY();
+ return (float) Math.sqrt((double) (xDiff * xDiff + yDiff * yDiff));
+ }
+
+ /**
+ * 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/src/com/google/zxing/ResultPointCallback.java b/src/com/google/zxing/ResultPointCallback.java
new file mode 100644
index 0000000..0c85410
--- /dev/null
+++ b/src/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/src/com/google/zxing/Writer.java b/src/com/google/zxing/Writer.java
new file mode 100644
index 0000000..6474ca7
--- /dev/null
+++ b/src/com/google/zxing/Writer.java
@@ -0,0 +1,54 @@
+/*
+ * 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.Hashtable;
+
+/**
+ * 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 The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white)
+ */
+ 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 The generated barcode as a Matrix of unsigned bytes (0 == black, 255 == white)
+ */
+ BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Hashtable hints)
+ throws WriterException;
+
+}
diff --git a/src/com/google/zxing/WriterException.java b/src/com/google/zxing/WriterException.java
new file mode 100644
index 0000000..0c19af0
--- /dev/null
+++ b/src/com/google/zxing/WriterException.java
@@ -0,0 +1,35 @@
+/*
+ * 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() {
+ super();
+ }
+
+ public WriterException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2me/LCDUIImageLuminanceSource.java b/src/com/google/zxing/client/j2me/LCDUIImageLuminanceSource.java
new file mode 100644
index 0000000..4f929f2
--- /dev/null
+++ b/src/com/google/zxing/client/j2me/LCDUIImageLuminanceSource.java
@@ -0,0 +1,95 @@
+/*
+ * 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.client.j2me;
+
+import com.google.zxing.LuminanceSource;
+
+import javax.microedition.lcdui.Image;
+
+/**
+ * A LuminanceSource based on Java ME's Image class. It does not support cropping or rotation.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ */
+public final class LCDUIImageLuminanceSource extends LuminanceSource {
+
+ private final Image image;
+ private int[] rgbData;
+
+ public LCDUIImageLuminanceSource(Image image) {
+ super(image.getWidth(), image.getHeight());
+ this.image = image;
+ }
+
+ // Instead of multiplying by 306, 601, 117, we multiply by 256, 512, 256, so that
+ // the multiplies can be implemented as shifts.
+ //
+ // Really, it's:
+ //
+ // return ((((pixel >> 16) & 0xFF) << 8) +
+ // (((pixel >> 8) & 0xFF) << 9) +
+ // (( pixel & 0xFF) << 8)) >> 10;
+ //
+ // That is, we're replacing the coefficients in the original with powers of two,
+ // which can be implemented as shifts, even though changing the coefficients slightly
+ // alters the conversion. The difference is not significant for our purposes.
+ 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];
+ }
+
+ if (rgbData == null || rgbData.length < width) {
+ rgbData = new int[width];
+ }
+ image.getRGB(rgbData, 0, width, 0, y, width, 1);
+ for (int x = 0; x < width; x++) {
+ int pixel = rgbData[x];
+ int luminance = (((pixel & 0x00FF0000) >> 16) +
+ ((pixel & 0x0000FF00) >> 7) +
+ (pixel & 0x000000FF )) >> 2;
+ row[x] = (byte) luminance;
+ }
+ return row;
+ }
+
+ public byte[] getMatrix() {
+ int width = getWidth();
+ int height = getHeight();
+ int area = width * height;
+ byte[] matrix = new byte[area];
+
+ int[] rgb = new int[area];
+ image.getRGB(rgb, 0, width, 0, 0, width, height);
+ for (int y = 0; y < height; y++) {
+ int offset = y * width;
+ for (int x = 0; x < width; x++) {
+ int pixel = rgb[offset + x];
+ int luminance = (((pixel & 0x00FF0000) >> 16) +
+ ((pixel & 0x0000FF00) >> 7) +
+ (pixel & 0x000000FF )) >> 2;
+ matrix[offset + x] = (byte) luminance;
+ }
+ }
+ return matrix;
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2me/PNGEncoder.java b/src/com/google/zxing/client/j2me/PNGEncoder.java
new file mode 100644
index 0000000..2bfc5fb
--- /dev/null
+++ b/src/com/google/zxing/client/j2me/PNGEncoder.java
@@ -0,0 +1,262 @@
+package com.google.zxing.client.j2me;
+/*
+ * Minimal PNG encoder to create PNG streams (and MIDP images) from RGBA arrays.
+ *
+ * Copyright 2006-2009 Christian Fröschlin
+ *
+ * www.chrfr.de
+ *
+ *
+ * Changelog:
+ *
+ * 09/22/08: Fixed Adler checksum calculation and byte order for storing length
+ * of zlib deflate block. Thanks to Miloslav Ruzicka for noting this.
+ *
+ * 05/12/09: Split PNG and ZLIB functionality into separate classes. Added
+ * support for images > 64K by splitting the data into multiple uncompressed
+ * deflate blocks.
+ *
+ * 03/19/10: Re-packaged, and modified interface to be more MIDP-2 friendly,
+ * using int[] rather than byte[] data. Alpha channel is now optional, to
+ * allow a smaller output size. New toPNG() method works directly from an
+ * Image object. (Graham Hughes, for Forum Nokia)
+ *
+ * Terms of Use:
+ *
+ * You may use the PNG encoder free of charge for any purpose you desire, as
+ * long as you do not claim credit for the original sources and agree not to
+ * hold me responsible for any damage arising out of its use.
+ *
+ * If you have a suitable location in GUI or documentation for giving credit,
+ * I'd appreciate a mention of
+ *
+ * PNG encoder (C) 2006-2009 by Christian Fröschlin, www.chrfr.de
+ *
+ * but that's not mandatory.
+ *
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import javax.microedition.lcdui.Image;
+
+public class PNGEncoder {
+ private static final byte[] SIGNATURE = new byte[] { (byte) 137, (byte) 80,
+ (byte) 78, (byte) 71, (byte) 13, (byte) 10, (byte) 26, (byte) 10 };
+
+ /**
+ * Generate a PNG data stream from a pixel array.
+ *
+ * Setting processAlpha to false will result in a PNG file that contains no
+ * transparency information, but may be up to 25% smaller.
+ *
+ * The pixel array must contain (width * height) pixels.
+ *
+ * @param width
+ * width of image, in pixels
+ * @param height
+ * height of image, in pixels
+ * @param argb
+ * pixel array, as populated from Image.getRGB()
+ * @param processAlpha
+ * true if you want to keep alpha channel data
+ * @return PNG data in a byte[]
+ * @throws IllegalArgumentException
+ * if the size of the pixel array does not match the specified
+ * width and height
+ */
+ public static byte[] toPNG(int width, int height, int[] argb,
+ boolean processAlpha) throws IllegalArgumentException {
+ ByteArrayOutputStream png;
+ try {
+ byte[] header = createHeaderChunk(width, height, processAlpha);
+ byte[] data = createDataChunk(width, height, argb, processAlpha);
+ byte[] trailer = createTrailerChunk();
+
+ png = new ByteArrayOutputStream(SIGNATURE.length + header.length
+ + data.length + trailer.length);
+ png.write(SIGNATURE);
+ png.write(header);
+ png.write(data);
+ png.write(trailer);
+ } catch (IOException ioe) {
+ // none of the code should ever throw an IOException
+ throw new IllegalStateException("Unexpected " + ioe);
+ }
+ return png.toByteArray();
+ }
+
+ /**
+ * Generate a PNG data stream from an Image object.
+ *
+ * @param img
+ * source Image
+ * @param processAlpha
+ * true if you want to keep the alpha channel data
+ * @return PNG data in a byte[]
+ */
+ public static byte[] toPNG(Image img, boolean processAlpha) {
+ int width = img.getWidth();
+ int height = img.getHeight();
+ int[] argb = new int[width * height];
+ img.getRGB(argb, 0, width, 0, 0, width, height);
+ // allow garbage collection, if this is the only reference
+ img = null;
+ return toPNG(width, height, argb, processAlpha);
+ }
+
+ private static byte[] createHeaderChunk(int width, int height,
+ boolean processAlpha) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(13);
+ DataOutputStream chunk = new DataOutputStream(baos);
+ chunk.writeInt(width);
+ chunk.writeInt(height);
+ chunk.writeByte(8); // Bitdepth
+ chunk.writeByte(processAlpha ? 6 : 2); // Colortype ARGB or RGB
+ chunk.writeByte(0); // Compression
+ chunk.writeByte(0); // Filter
+ chunk.writeByte(0); // Interlace
+ return toChunk("IHDR", baos.toByteArray());
+ }
+
+ private static byte[] createDataChunk(int width, int height, int[] argb,
+ boolean processAlpha) throws IOException, IllegalArgumentException {
+ if (argb.length != (width * height)) {
+ throw new IllegalArgumentException(
+ "array size does not match image dimensions");
+ }
+ int source = 0;
+ int dest = 0;
+ byte[] raw = new byte[(processAlpha ? 4 : 3) * (width * height)
+ + height];
+ for (int y = 0; y < height; y++) {
+ raw[dest++] = 0; // No filter
+ for (int x = 0; x < width; x++) {
+ int pixel = argb[source++];
+ raw[dest++] = (byte) (pixel >> 16); // red
+ raw[dest++] = (byte) (pixel >> 8); // green
+ raw[dest++] = (byte) (pixel); // blue
+ if (processAlpha) {
+ raw[dest++] = (byte) (pixel >> 24); // alpha
+ }
+ }
+ }
+ return toChunk("IDAT", toZLIB(raw));
+ }
+
+ private static byte[] createTrailerChunk() throws IOException {
+ return toChunk("IEND", new byte[] {});
+ }
+
+ private static byte[] toChunk(String id, byte[] raw) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(raw.length + 12);
+ DataOutputStream chunk = new DataOutputStream(baos);
+
+ chunk.writeInt(raw.length);
+
+ byte[] bid = new byte[4];
+ for (int i = 0; i < 4; i++) {
+ bid[i] = (byte) id.charAt(i);
+ }
+
+ chunk.write(bid);
+
+ chunk.write(raw);
+
+ int crc = 0xFFFFFFFF;
+ crc = updateCRC(crc, bid);
+ crc = updateCRC(crc, raw);
+ chunk.writeInt(~crc);
+
+ return baos.toByteArray();
+ }
+
+ private static int[] crcTable = null;
+
+ private static void createCRCTable() {
+ crcTable = new int[256];
+
+ for (int i = 0; i < 256; i++) {
+ int c = i;
+ for (int k = 0; k < 8; k++) {
+ c = ((c & 1) > 0) ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
+ }
+ crcTable[i] = c;
+ }
+ }
+
+ private static int updateCRC(int crc, byte[] raw) {
+ if (crcTable == null) {
+ createCRCTable();
+ }
+
+ for (int i = 0; i < raw.length; i++) {
+ crc = crcTable[(crc ^ raw[i]) & 0xFF] ^ (crc >>> 8);
+ }
+
+ return crc;
+ }
+
+ /*
+ * This method is called to encode the image data as a zlib block as
+ * required by the PNG specification. This file comes with a minimal ZLIB
+ * encoder which uses uncompressed deflate blocks (fast, short, easy, but no
+ * compression). If you want compression, call another encoder (such as
+ * JZLib?) here.
+ */
+ private static byte[] toZLIB(byte[] raw) throws IOException {
+ return ZLIB.toZLIB(raw);
+ }
+}
+
+class ZLIB {
+ private static final int BLOCK_SIZE = 32000;
+
+ public static byte[] toZLIB(byte[] raw) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(raw.length + 6
+ + (raw.length / BLOCK_SIZE) * 5);
+ DataOutputStream zlib = new DataOutputStream(baos);
+
+ byte tmp = (byte) 8;
+ zlib.writeByte(tmp); // CM = 8, CMINFO = 0
+ zlib.writeByte((31 - ((tmp << 8) % 31)) % 31); // FCHECK(FDICT/FLEVEL=0)
+
+ int pos = 0;
+ while (raw.length - pos > BLOCK_SIZE) {
+ writeUncompressedDeflateBlock(zlib, false, raw, pos,
+ (char) BLOCK_SIZE);
+ pos += BLOCK_SIZE;
+ }
+
+ writeUncompressedDeflateBlock(zlib, true, raw, pos,
+ (char) (raw.length - pos));
+
+ // zlib check sum of uncompressed data
+ zlib.writeInt(calcADLER32(raw));
+
+ return baos.toByteArray();
+ }
+
+ private static void writeUncompressedDeflateBlock(DataOutputStream zlib,
+ boolean last, byte[] raw, int off, char len) throws IOException {
+ zlib.writeByte((byte) (last ? 1 : 0)); // Final flag, Compression type 0
+ zlib.writeByte((byte) (len & 0xFF)); // Length LSB
+ zlib.writeByte((byte) ((len & 0xFF00) >> 8)); // Length MSB
+ zlib.writeByte((byte) (~len & 0xFF)); // Length 1st complement LSB
+ zlib.writeByte((byte) ((~len & 0xFF00) >> 8)); // Length 1st complement
+ // MSB
+ zlib.write(raw, off, len); // Data
+ }
+
+ private static int calcADLER32(byte[] raw) {
+ int s1 = 1;
+ int s2 = 0;
+ for (int i = 0; i < raw.length; i++) {
+ int abs = raw[i] >= 0 ? raw[i] : (raw[i] + 256);
+ s1 = (s1 + abs) % 65521;
+ s2 = (s2 + s1) % 65521;
+ }
+ return (s2 << 16) + s1;
+ }
+}
diff --git a/src/com/google/zxing/client/j2me/ZXMIDlet.java b/src/com/google/zxing/client/j2me/ZXMIDlet.java
new file mode 100644
index 0000000..b7bd2e9
--- /dev/null
+++ b/src/com/google/zxing/client/j2me/ZXMIDlet.java
@@ -0,0 +1,670 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.google.zxing.client.j2me;
+
+import org.netbeans.microedition.lcdui.SplashScreen;
+import org.netbeans.microedition.lcdui.pda.FileBrowser;
+import java.io.*;
+import javax.microedition.io.file.*;
+import javax.microedition.midlet.*;
+import javax.microedition.lcdui.*;
+import com.google.zxing.*;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.qrcode.QRCodeWriter;
+import java.util.Date;
+import org.netbeans.microedition.lcdui.SplashScreen;
+import org.netbeans.microedition.lcdui.pda.FileBrowser;
+
+/**
+ * @author URSER
+ */
+public class ZXMIDlet extends MIDlet implements CommandListener {
+
+private boolean midletPaused = false;
+private Image img;
+private Image qrCodeImage;
+private String filename;
+private static final int BACK = 0xFF000000;
+private static final int WHTIE = 0xFFFFFFFF;
+////GEN-BEGIN:|fields|0|
+private FileBrowser fileBrowser;
+private TextBox textBox;
+private SplashScreen splashScreen;
+private TextBox textBox1;
+private TextBox textBox2;
+private Command backCommand;
+private Command exitCommand;
+private Command backCommand1;
+private Command itemCommand1;
+private Command itemCommand;
+private Command okCommand;
+private Command cancelCommand;
+private Command exitCommand1;
+private Command cancelCommand1;
+private Command exitCommand2;
+private Command backCommand2;
+private Command backCommand3;
+private Command helpCommand;
+private Command okCommand1;
+private Command itemCommand2;
+private Image image;
+////GEN-END:|fields|0|
+ /**
+ * The ZXMIDlet constructor.
+ */
+ public ZXMIDlet() {
+ }
+
+ ////GEN-BEGIN:|methods|0|
+ ////GEN-END:|methods|0|
+ ////GEN-BEGIN:|0-initialize|0|0-preInitialize
+ /**
+ * Initilizes the application.
+ * It is called only once when the MIDlet is started. The method is called before the startMIDlet
method.
+ */
+ private void initialize() {//GEN-END:|0-initialize|0|0-preInitialize
+ // write pre-initialize user code here
+//GEN-LINE:|0-initialize|1|0-postInitialize
+ // write post-initialize user code here
+ }//GEN-BEGIN:|0-initialize|2|
+ ////GEN-END:|0-initialize|2|
+
+ ////GEN-BEGIN:|3-startMIDlet|0|3-preAction
+ /**
+ * Performs an action assigned to the Mobile Device - MIDlet Started point.
+ */
+ public void startMIDlet() {//GEN-END:|3-startMIDlet|0|3-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getSplashScreen());//GEN-LINE:|3-startMIDlet|1|3-postAction
+ // write post-action user code here
+ }//GEN-BEGIN:|3-startMIDlet|2|
+ ////GEN-END:|3-startMIDlet|2|
+
+ ////GEN-BEGIN:|4-resumeMIDlet|0|4-preAction
+ /**
+ * Performs an action assigned to the Mobile Device - MIDlet Resumed point.
+ */
+ public void resumeMIDlet() {//GEN-END:|4-resumeMIDlet|0|4-preAction
+ // write pre-action user code here
+//GEN-LINE:|4-resumeMIDlet|1|4-postAction
+ // write post-action user code here
+ }//GEN-BEGIN:|4-resumeMIDlet|2|
+ ////GEN-END:|4-resumeMIDlet|2|
+
+ ////GEN-BEGIN:|5-switchDisplayable|0|5-preSwitch
+ /**
+ * Switches a current displayable in a display. The display
instance is taken from getDisplay
method. This method is used by all actions in the design for switching displayable.
+ * @param alert the Alert which is temporarily set to the display; if null
, then nextDisplayable
is set immediately
+ * @param nextDisplayable the Displayable to be set
+ */
+ public void switchDisplayable(Alert alert, Displayable nextDisplayable) {//GEN-END:|5-switchDisplayable|0|5-preSwitch
+ // write pre-switch user code here
+ Display display = getDisplay();//GEN-BEGIN:|5-switchDisplayable|1|5-postSwitch
+ if (alert == null) {
+ display.setCurrent(nextDisplayable);
+ } else {
+ display.setCurrent(alert, nextDisplayable);
+ }//GEN-END:|5-switchDisplayable|1|5-postSwitch
+ // write post-switch user code here
+ }//GEN-BEGIN:|5-switchDisplayable|2|
+ ////GEN-END:|5-switchDisplayable|2|
+
+ ////GEN-BEGIN:|7-commandAction|0|7-preCommandAction
+ /**
+ * Called by a system to indicated that a command has been invoked on a particular displayable.
+ * @param command the Command that was invoked
+ * @param displayable the Displayable where the command was invoked
+ */
+ public void commandAction(Command command, Displayable displayable) {//GEN-END:|7-commandAction|0|7-preCommandAction
+ // write pre-action user code here
+ if (displayable == fileBrowser) {//GEN-BEGIN:|7-commandAction|1|16-preAction
+ if (command == FileBrowser.SELECT_FILE_COMMAND) {//GEN-END:|7-commandAction|1|16-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getTextBox());//GEN-LINE:|7-commandAction|2|16-postAction
+ readFile();// write post-action user code here
+ } else if (command == cancelCommand1) {//GEN-LINE:|7-commandAction|3|54-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getSplashScreen());//GEN-LINE:|7-commandAction|4|54-postAction
+ // write post-action user code here
+ } else if (command == exitCommand) {//GEN-LINE:|7-commandAction|5|30-preAction
+ // write pre-action user code here
+ exitMIDlet();//GEN-LINE:|7-commandAction|6|30-postAction
+ // write post-action user code here
+ }//GEN-BEGIN:|7-commandAction|7|23-preAction
+ } else if (displayable == splashScreen) {
+ if (command == SplashScreen.DISMISS_COMMAND) {//GEN-END:|7-commandAction|7|23-preAction
+ // write pre-action user code here
+//GEN-LINE:|7-commandAction|8|23-postAction
+ // write post-action user code here
+ } else if (command == exitCommand2) {//GEN-LINE:|7-commandAction|9|60-preAction
+ // write pre-action user code here
+ exitMIDlet();//GEN-LINE:|7-commandAction|10|60-postAction
+ // write post-action user code here
+ } else if (command == helpCommand) {//GEN-LINE:|7-commandAction|11|70-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getTextBox2());//GEN-LINE:|7-commandAction|12|70-postAction
+ // write post-action user code here
+ } else if (command == itemCommand) {//GEN-LINE:|7-commandAction|13|38-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getFileBrowser());//GEN-LINE:|7-commandAction|14|38-postAction
+ // write post-action user code here
+ } else if (command == itemCommand1) {//GEN-LINE:|7-commandAction|15|40-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getTextBox1());//GEN-LINE:|7-commandAction|16|40-postAction
+ // write post-action user code here
+ }//GEN-BEGIN:|7-commandAction|17|28-preAction
+ } else if (displayable == textBox) {
+ if (command == backCommand) {//GEN-END:|7-commandAction|17|28-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getFileBrowser());//GEN-LINE:|7-commandAction|18|28-postAction
+ // write post-action user code here
+ } else if (command == backCommand2) {//GEN-LINE:|7-commandAction|19|57-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getSplashScreen());//GEN-LINE:|7-commandAction|20|57-postAction
+ // write post-action user code here
+ }//GEN-BEGIN:|7-commandAction|21|45-preAction
+ } else if (displayable == textBox1) {
+ if (command == cancelCommand) {//GEN-END:|7-commandAction|21|45-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getSplashScreen());//GEN-LINE:|7-commandAction|22|45-postAction
+ // write post-action user code here
+ } else if (command == exitCommand1) {//GEN-LINE:|7-commandAction|23|51-preAction
+ // write pre-action user code here
+ exitMIDlet();//GEN-LINE:|7-commandAction|24|51-postAction
+ // write post-action user code here
+ } else if (command == okCommand) {//GEN-LINE:|7-commandAction|25|47-preAction
+ // write pre-action user code here
+//GEN-LINE:|7-commandAction|26|47-postAction
+ //
+ DoEncode();
+ switchDisplayable(null, getSplashScreen());// write post-action user code here
+ }//GEN-BEGIN:|7-commandAction|27|73-preAction
+ } else if (displayable == textBox2) {
+ if (command == okCommand1) {//GEN-END:|7-commandAction|27|73-preAction
+ // write pre-action user code here
+ switchDisplayable(null, getSplashScreen());//GEN-LINE:|7-commandAction|28|73-postAction
+ // write post-action user code here
+ }//GEN-BEGIN:|7-commandAction|29|7-postCommandAction
+ }//GEN-END:|7-commandAction|29|7-postCommandAction
+ // write post-action user code here
+ }//GEN-BEGIN:|7-commandAction|30|
+ ////GEN-END:|7-commandAction|30|
+
+ ////GEN-BEGIN:|14-getter|0|14-preInit
+ /**
+ * Returns an initiliazed instance of fileBrowser component.
+ * @return the initialized component instance
+ */
+ public FileBrowser getFileBrowser() {
+ if (fileBrowser == null) {//GEN-END:|14-getter|0|14-preInit
+ // write pre-init user code here
+ fileBrowser = new FileBrowser(getDisplay());//GEN-BEGIN:|14-getter|1|14-postInit
+ fileBrowser.setTitle("\u9009\u62E9\u56FE\u7247");
+ fileBrowser.setCommandListener(this);
+ fileBrowser.addCommand(FileBrowser.SELECT_FILE_COMMAND);
+ fileBrowser.addCommand(getExitCommand());
+ fileBrowser.addCommand(getCancelCommand1());//GEN-END:|14-getter|1|14-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|14-getter|2|
+ return fileBrowser;
+ }
+ ////GEN-END:|14-getter|2|
+
+ ////GEN-BEGIN:|17-getter|0|17-preInit
+ /**
+ * Returns an initiliazed instance of textBox component.
+ * @return the initialized component instance
+ */
+ public TextBox getTextBox() {
+ if (textBox == null) {//GEN-END:|17-getter|0|17-preInit
+ // write pre-init user code here
+ textBox = new TextBox("\u89E3\u7801\u6587\u5B57", "", 3000, TextField.ANY);//GEN-BEGIN:|17-getter|1|17-postInit
+ textBox.addCommand(getBackCommand());
+ textBox.addCommand(getBackCommand2());
+ textBox.setCommandListener(this);//GEN-END:|17-getter|1|17-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|17-getter|2|
+ return textBox;
+ }
+ ////GEN-END:|17-getter|2|
+
+ ////GEN-BEGIN:|22-getter|0|22-preInit
+ /**
+ * Returns an initiliazed instance of splashScreen component.
+ * @return the initialized component instance
+ */
+ public SplashScreen getSplashScreen() {
+ if (splashScreen == null) {//GEN-END:|22-getter|0|22-preInit
+ // write pre-init user code here
+ splashScreen = new SplashScreen(getDisplay());//GEN-BEGIN:|22-getter|1|22-postInit
+ splashScreen.setTitle("\u4E8C\u7EF4\u7801\u89E3\u7801\u7A0B\u5E8F");
+ splashScreen.addCommand(getItemCommand());
+ splashScreen.addCommand(getItemCommand1());
+ splashScreen.addCommand(getExitCommand2());
+ splashScreen.addCommand(getHelpCommand());
+ splashScreen.setCommandListener(this);
+ splashScreen.setImage(getImage());
+ splashScreen.setText("\u89E3\u7801\u56FE\u7247\u6216\u7F16\u7801\u6587\u5B57");//GEN-END:|22-getter|1|22-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|22-getter|2|
+ return splashScreen;
+ }
+ ////GEN-END:|22-getter|2|
+
+ ////GEN-BEGIN:|27-getter|0|27-preInit
+ /**
+ * Returns an initiliazed instance of backCommand component.
+ * @return the initialized component instance
+ */
+ public Command getBackCommand() {
+ if (backCommand == null) {//GEN-END:|27-getter|0|27-preInit
+ // write pre-init user code here
+ backCommand = new Command("\u540E\u9000", Command.BACK, 0);//GEN-LINE:|27-getter|1|27-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|27-getter|2|
+ return backCommand;
+ }
+ ////GEN-END:|27-getter|2|
+
+ ////GEN-BEGIN:|29-getter|0|29-preInit
+ /**
+ * Returns an initiliazed instance of exitCommand component.
+ * @return the initialized component instance
+ */
+ public Command getExitCommand() {
+ if (exitCommand == null) {//GEN-END:|29-getter|0|29-preInit
+ // write pre-init user code here
+ exitCommand = new Command("\u9000\u51FA", Command.EXIT, 0);//GEN-LINE:|29-getter|1|29-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|29-getter|2|
+ return exitCommand;
+ }
+ ////GEN-END:|29-getter|2|
+
+ ////GEN-BEGIN:|33-getter|0|33-preInit
+ /**
+ * Returns an initiliazed instance of image component.
+ * @return the initialized component instance
+ */
+ public Image getImage() {
+ if (image == null) {//GEN-END:|33-getter|0|33-preInit
+ // write pre-init user code here
+ try {//GEN-BEGIN:|33-getter|1|33-@java.io.IOException
+ image = Image.createImage("/zxing-icon.png");
+ } catch (java.io.IOException e) {//GEN-END:|33-getter|1|33-@java.io.IOException
+ e.printStackTrace();
+ }//GEN-LINE:|33-getter|2|33-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|33-getter|3|
+ return image;
+ }
+ ////GEN-END:|33-getter|3|
+
+ ////GEN-BEGIN:|34-getter|0|34-preInit
+ /**
+ * Returns an initiliazed instance of backCommand1 component.
+ * @return the initialized component instance
+ */
+ public Command getBackCommand1() {
+ if (backCommand1 == null) {//GEN-END:|34-getter|0|34-preInit
+ // write pre-init user code here
+ backCommand1 = new Command("\u540E\u9000", Command.BACK, 0);//GEN-LINE:|34-getter|1|34-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|34-getter|2|
+ return backCommand1;
+ }
+ ////GEN-END:|34-getter|2|
+
+ ////GEN-BEGIN:|37-getter|0|37-preInit
+ /**
+ * Returns an initiliazed instance of itemCommand component.
+ * @return the initialized component instance
+ */
+ public Command getItemCommand() {
+ if (itemCommand == null) {//GEN-END:|37-getter|0|37-preInit
+ // write pre-init user code here
+ itemCommand = new Command("\u89E3\u7801", Command.ITEM, 0);//GEN-LINE:|37-getter|1|37-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|37-getter|2|
+ return itemCommand;
+ }
+ ////GEN-END:|37-getter|2|
+
+ ////GEN-BEGIN:|39-getter|0|39-preInit
+ /**
+ * Returns an initiliazed instance of itemCommand1 component.
+ * @return the initialized component instance
+ */
+ public Command getItemCommand1() {
+ if (itemCommand1 == null) {//GEN-END:|39-getter|0|39-preInit
+ // write pre-init user code here
+ itemCommand1 = new Command("\u7F16\u7801", Command.ITEM, 0);//GEN-LINE:|39-getter|1|39-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|39-getter|2|
+ return itemCommand1;
+ }
+ ////GEN-END:|39-getter|2|
+
+ ////GEN-BEGIN:|42-getter|0|42-preInit
+ /**
+ * Returns an initiliazed instance of textBox1 component.
+ * @return the initialized component instance
+ */
+ public TextBox getTextBox1() {
+ if (textBox1 == null) {//GEN-END:|42-getter|0|42-preInit
+ // write pre-init user code here
+ textBox1 = new TextBox("\u7F16\u7801\u6587\u5B57", "", 3000, TextField.ANY);//GEN-BEGIN:|42-getter|1|42-postInit
+ textBox1.addCommand(getOkCommand());
+ textBox1.addCommand(getCancelCommand());
+ textBox1.addCommand(getExitCommand1());
+ textBox1.setCommandListener(this);//GEN-END:|42-getter|1|42-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|42-getter|2|
+ return textBox1;
+ }
+ ////GEN-END:|42-getter|2|
+
+ ////GEN-BEGIN:|44-getter|0|44-preInit
+ /**
+ * Returns an initiliazed instance of cancelCommand component.
+ * @return the initialized component instance
+ */
+ public Command getCancelCommand() {
+ if (cancelCommand == null) {//GEN-END:|44-getter|0|44-preInit
+ // write pre-init user code here
+ cancelCommand = new Command("\u53D6\u6D88", Command.CANCEL, 0);//GEN-LINE:|44-getter|1|44-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|44-getter|2|
+ return cancelCommand;
+ }
+ ////GEN-END:|44-getter|2|
+
+ ////GEN-BEGIN:|46-getter|0|46-preInit
+ /**
+ * Returns an initiliazed instance of okCommand component.
+ * @return the initialized component instance
+ */
+ public Command getOkCommand() {
+ if (okCommand == null) {//GEN-END:|46-getter|0|46-preInit
+ // write pre-init user code here
+ okCommand = new Command("\u786E\u5B9A", Command.OK, 0);//GEN-LINE:|46-getter|1|46-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|46-getter|2|
+ return okCommand;
+ }
+ ////GEN-END:|46-getter|2|
+
+ ////GEN-BEGIN:|50-getter|0|50-preInit
+ /**
+ * Returns an initiliazed instance of exitCommand1 component.
+ * @return the initialized component instance
+ */
+ public Command getExitCommand1() {
+ if (exitCommand1 == null) {//GEN-END:|50-getter|0|50-preInit
+ // write pre-init user code here
+ exitCommand1 = new Command("\u9000\u51FA", Command.EXIT, 0);//GEN-LINE:|50-getter|1|50-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|50-getter|2|
+ return exitCommand1;
+ }
+ ////GEN-END:|50-getter|2|
+
+ ////GEN-BEGIN:|53-getter|0|53-preInit
+ /**
+ * Returns an initiliazed instance of cancelCommand1 component.
+ * @return the initialized component instance
+ */
+ public Command getCancelCommand1() {
+ if (cancelCommand1 == null) {//GEN-END:|53-getter|0|53-preInit
+ // write pre-init user code here
+ cancelCommand1 = new Command("\u53D6\u6D88", Command.CANCEL, 0);//GEN-LINE:|53-getter|1|53-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|53-getter|2|
+ return cancelCommand1;
+ }
+ ////GEN-END:|53-getter|2|
+
+ ////GEN-BEGIN:|56-getter|0|56-preInit
+ /**
+ * Returns an initiliazed instance of backCommand2 component.
+ * @return the initialized component instance
+ */
+ public Command getBackCommand2() {
+ if (backCommand2 == null) {//GEN-END:|56-getter|0|56-preInit
+ // write pre-init user code here
+ backCommand2 = new Command("\u4E3B\u754C\u9762", Command.BACK, 0);//GEN-LINE:|56-getter|1|56-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|56-getter|2|
+ return backCommand2;
+ }
+ ////GEN-END:|56-getter|2|
+
+ ////GEN-BEGIN:|59-getter|0|59-preInit
+ /**
+ * Returns an initiliazed instance of exitCommand2 component.
+ * @return the initialized component instance
+ */
+ public Command getExitCommand2() {
+ if (exitCommand2 == null) {//GEN-END:|59-getter|0|59-preInit
+ // write pre-init user code here
+ exitCommand2 = new Command("\u9000\u51FA", Command.EXIT, 0);//GEN-LINE:|59-getter|1|59-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|59-getter|2|
+ return exitCommand2;
+ }
+ ////GEN-END:|59-getter|2|
+
+ ////GEN-BEGIN:|62-getter|0|62-preInit
+ /**
+ * Returns an initiliazed instance of backCommand3 component.
+ * @return the initialized component instance
+ */
+ public Command getBackCommand3() {
+ if (backCommand3 == null) {//GEN-END:|62-getter|0|62-preInit
+ // write pre-init user code here
+ backCommand3 = new Command("\u540E\u9000", Command.BACK, 0);//GEN-LINE:|62-getter|1|62-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|62-getter|2|
+ return backCommand3;
+ }
+ ////GEN-END:|62-getter|2|
+
+
+
+ ////GEN-BEGIN:|68-getter|0|68-preInit
+ /**
+ * Returns an initiliazed instance of textBox2 component.
+ * @return the initialized component instance
+ */
+ public TextBox getTextBox2() {
+ if (textBox2 == null) {//GEN-END:|68-getter|0|68-preInit
+ // write pre-init user code here
+ textBox2 = new TextBox("\u5173\u4E8E", "\u4E8C\u7EF4\u7801\u52A9\u624B\nPowered by Zxing\nVersion 3.1\n\u7248\u6743(C) 2011-2012 xuenhua\nhttp://weibo.com/xuenhua\nhttp://code.google.com/p/zxing", 172, TextField.ANY | TextField.UNEDITABLE);//GEN-BEGIN:|68-getter|1|68-postInit
+ textBox2.addCommand(getOkCommand1());
+ textBox2.setCommandListener(this);//GEN-END:|68-getter|1|68-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|68-getter|2|
+ return textBox2;
+ }
+ ////GEN-END:|68-getter|2|
+
+ ////GEN-BEGIN:|69-getter|0|69-preInit
+ /**
+ * Returns an initiliazed instance of helpCommand component.
+ * @return the initialized component instance
+ */
+ public Command getHelpCommand() {
+ if (helpCommand == null) {//GEN-END:|69-getter|0|69-preInit
+ // write pre-init user code here
+ helpCommand = new Command("\u5173\u4E8E", Command.HELP, 0);//GEN-LINE:|69-getter|1|69-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|69-getter|2|
+ return helpCommand;
+ }
+ ////GEN-END:|69-getter|2|
+
+ ////GEN-BEGIN:|72-getter|0|72-preInit
+ /**
+ * Returns an initiliazed instance of okCommand1 component.
+ * @return the initialized component instance
+ */
+ public Command getOkCommand1() {
+ if (okCommand1 == null) {//GEN-END:|72-getter|0|72-preInit
+ // write pre-init user code here
+ okCommand1 = new Command("\u786E\u5B9A", Command.OK, 0);//GEN-LINE:|72-getter|1|72-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|72-getter|2|
+ return okCommand1;
+ }
+ ////GEN-END:|72-getter|2|
+
+ ////GEN-BEGIN:|75-getter|0|75-preInit
+ /**
+ * Returns an initiliazed instance of itemCommand2 component.
+ * @return the initialized component instance
+ */
+ public Command getItemCommand2() {
+ if (itemCommand2 == null) {//GEN-END:|75-getter|0|75-preInit
+ // write pre-init user code here
+ itemCommand2 = new Command("\u8BBE\u7F6E", Command.ITEM, 0);//GEN-LINE:|75-getter|1|75-postInit
+ // write post-init user code here
+ }//GEN-BEGIN:|75-getter|2|
+ return itemCommand2;
+ }
+ ////GEN-END:|75-getter|2|
+
+
+
+ /**
+ * Returns a display instance.
+ * @return the display instance.
+ */
+ public Display getDisplay() {
+ return Display.getDisplay(this);
+ }
+
+ /**
+ * Exits MIDlet.
+ */
+ public void exitMIDlet() {
+ switchDisplayable(null, null);
+ destroyApp(true);
+ notifyDestroyed();
+ }
+
+ /**
+ * Called when MIDlet is started.
+ * Checks whether the MIDlet have been already started and initialize/starts or resumes the MIDlet.
+ */
+ public void startApp() {
+ if (midletPaused) {
+ resumeMIDlet();
+ } else {
+ initialize();
+ startMIDlet();
+ }
+ midletPaused = false;
+ }
+
+ /**
+ * Called when MIDlet is paused.
+ */
+ public void pauseApp() {
+ midletPaused = true;
+ }
+
+ /**
+ * Called to signal the MIDlet to terminate.
+ * @param unconditional if true, then the MIDlet has to be unconditionally terminated and all resources has to be released.
+ */
+ public void destroyApp(boolean unconditional) {
+ }
+ private void readFile() {
+ try {
+ FileConnection textFile = fileBrowser.getSelectedFile();
+ getTextBox().setString("正在解码……");
+ InputStream fis = textFile.openInputStream();
+
+ //read image SO Slowly!
+// byte[] imagearray = null;
+// ByteArrayOutputStream bytestream = new ByteArrayOutputStream();
+// int ch;
+// while( ( ch = fis.read() ) != -1 )
+// {
+// bytestream.write( ch );
+// }
+// imagearray = bytestream.toByteArray();
+// img = Image.createImage( imagearray, 0, imagearray.length );
+ img = Image.createImage(fis);
+
+
+
+ LuminanceSource source = new LCDUIImageLuminanceSource(img);
+ BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+ Result result;
+ try {
+ result = new MultiFormatReader().decode(bitmap);
+ textBox.setString(result.getText());
+ } catch (ReaderException re) {
+ textBox.setString(re.toString());
+ }
+ fis.close();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ }
+ private Image encode(String content) {
+ try {
+ QRCodeWriter qrCodeWriter = new QRCodeWriter();
+ int qrWidth = 200;
+ int qrHeigth = 200;
+ BitMatrix qrBitMatrix = qrCodeWriter.encode(content,
+ BarcodeFormat.QR_CODE, qrWidth, qrHeigth);
+ int[] rgb = new int[qrWidth * qrHeigth];
+ for (int y = 0; y < qrBitMatrix.getHeight(); y++) {
+ for (int x = 0; x < qrWidth; x++) {
+ int offset = y * qrHeigth;
+ rgb[offset + x] = qrBitMatrix.get(x, y) ? BACK : WHTIE;
+ }
+ }
+ return Image.createRGBImage(rgb, qrWidth, qrHeigth, false);
+ } catch (WriterException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ private void saveQRCodeImage() {
+ byte[] pngData = PNGEncoder.toPNG(qrCodeImage, false);
+ javax.microedition.io.Connection c = null;
+// java.io.OutputStream os = null;
+ filename = Long.toString((new Date()).getTime()) + ".png";
+ try {
+ String path = "E:/" + filename;
+ c = javax.microedition.io.Connector.open("file:///" + path, javax.microedition.io.Connector.READ_WRITE);// (FileConnection) Connector.open(url, Connector.READ_WRITE);
+ javax.microedition.io.file.FileConnection fc =
+ (javax.microedition.io.file.FileConnection) c;
+ if (!fc.exists()) {
+ fc.create();
+ }
+ OutputStream outputStream = fc.openOutputStream();
+
+ outputStream.write(pngData);
+ outputStream.close();
+ fc.close();
+ } catch (IOException ioe) {
+ System.out.println("IOException: " + ioe.getMessage());
+ } catch (SecurityException se) {
+ System.out.println("Security exception:" + se.getMessage());
+
+ }
+ }
+
+ private void DoEncode() {
+ qrCodeImage = encode(textBox1.getString());
+ saveQRCodeImage();
+ }
+}
diff --git a/src/com/google/zxing/client/j2me/ZXMIDlet.vmd b/src/com/google/zxing/client/j2me/ZXMIDlet.vmd
new file mode 100644
index 0000000..d7988ba
--- /dev/null
+++ b/src/com/google/zxing/client/j2me/ZXMIDlet.vmd
@@ -0,0 +1,310 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/com/google/zxing/client/result/AbstractDoCoMoResultParser.java b/src/com/google/zxing/client/result/AbstractDoCoMoResultParser.java
new file mode 100644
index 0000000..3d2c1d2
--- /dev/null
+++ b/src/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/src/com/google/zxing/client/result/AddressBookAUResultParser.java b/src/com/google/zxing/client/result/AddressBookAUResultParser.java
new file mode 100644
index 0000000..49ab174
--- /dev/null
+++ b/src/com/google/zxing/client/result/AddressBookAUResultParser.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.Vector;
+
+/**
+ * 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
+ */
+final class AddressBookAUResultParser extends ResultParser {
+
+ public static AddressBookParsedResult parse(Result result) {
+ String rawText = result.getText();
+ // MEMORY is mandatory; seems like a decent indicator, as does end-of-record separator CR/LF
+ if (rawText == null || rawText.indexOf("MEMORY") < 0 || rawText.indexOf("\r\n") < 0) {
+ 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), pronunciation, phoneNumbers, emails, note,
+ addresses, null, null, null, null);
+ }
+
+ private static String[] matchMultipleValuePrefix(String prefix, int max, String rawText,
+ boolean trim) {
+ Vector 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 Vector(max); // lazy init
+ }
+ values.addElement(value);
+ }
+ if (values == null) {
+ return null;
+ }
+ return toStringArray(values);
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/AddressBookDoCoMoResultParser.java b/src/com/google/zxing/client/result/AddressBookDoCoMoResultParser.java
new file mode 100644
index 0000000..86fe8b9
--- /dev/null
+++ b/src/com/google/zxing/client/result/AddressBookDoCoMoResultParser.java
@@ -0,0 +1,85 @@
+/*
+ * 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
+ */
+final class AddressBookDoCoMoResultParser extends AbstractDoCoMoResultParser {
+
+ public static AddressBookParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null || !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 (birthday != null && !isStringOfDigits(birthday, 8)) {
+ // No reason to throw out the whole card because the birthday is formatted wrong.
+ birthday = null;
+ }
+ String url = matchSingleDoCoMoPrefixedField("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),
+ pronunciation,
+ phoneNumbers,
+ emails,
+ note,
+ addresses,
+ org,
+ birthday,
+ null,
+ url);
+ }
+
+ 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/src/com/google/zxing/client/result/AddressBookParsedResult.java b/src/com/google/zxing/client/result/AddressBookParsedResult.java
new file mode 100644
index 0000000..c84587a
--- /dev/null
+++ b/src/com/google/zxing/client/result/AddressBookParsedResult.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.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class AddressBookParsedResult extends ParsedResult {
+
+ private final String[] names;
+ private final String pronunciation;
+ private final String[] phoneNumbers;
+ private final String[] emails;
+ private final String note;
+ private final String[] addresses;
+ private final String org;
+ private final String birthday;
+ private final String title;
+ private final String url;
+
+ public AddressBookParsedResult(String[] names,
+ String pronunciation,
+ String[] phoneNumbers,
+ String[] emails,
+ String note,
+ String[] addresses,
+ String org,
+ String birthday,
+ String title,
+ String url) {
+ super(ParsedResultType.ADDRESSBOOK);
+ this.names = names;
+ this.pronunciation = pronunciation;
+ this.phoneNumbers = phoneNumbers;
+ this.emails = emails;
+ this.note = note;
+ this.addresses = addresses;
+ this.org = org;
+ this.birthday = birthday;
+ this.title = title;
+ this.url = url;
+ }
+
+ public String[] getNames() {
+ return names;
+ }
+
+ /**
+ * 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;
+ }
+
+ public String[] getEmails() {
+ return emails;
+ }
+
+ public String getNote() {
+ return note;
+ }
+
+ public String[] getAddresses() {
+ return addresses;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getOrg() {
+ return org;
+ }
+
+ public String getURL() {
+ return url;
+ }
+
+ /**
+ * @return birthday formatted as yyyyMMdd (e.g. 19780917)
+ */
+ public String getBirthday() {
+ return birthday;
+ }
+
+ public String getDisplayResult() {
+ StringBuffer result = new StringBuffer(100);
+ maybeAppend(names, result);
+ maybeAppend(pronunciation, result);
+ maybeAppend(title, result);
+ maybeAppend(org, result);
+ maybeAppend(addresses, result);
+ maybeAppend(phoneNumbers, result);
+ maybeAppend(emails, result);
+ maybeAppend(url, result);
+ maybeAppend(birthday, result);
+ maybeAppend(note, result);
+ return result.toString();
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/BizcardResultParser.java b/src/com/google/zxing/client/result/BizcardResultParser.java
new file mode 100644
index 0000000..0de970a
--- /dev/null
+++ b/src/com/google/zxing/client/result/BizcardResultParser.java
@@ -0,0 +1,94 @@
+/*
+ * 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.Vector;
+
+/**
+ * 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
+ */
+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
+
+ public static AddressBookParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null || !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,
+ buildPhoneNumbers(phoneNumber1, phoneNumber2, phoneNumber3),
+ maybeWrap(email),
+ null,
+ addresses,
+ org,
+ null,
+ title,
+ null);
+ }
+
+ private static String[] buildPhoneNumbers(String number1, String number2, String number3) {
+ Vector numbers = new Vector(3);
+ if (number1 != null) {
+ numbers.addElement(number1);
+ }
+ if (number2 != null) {
+ numbers.addElement(number2);
+ }
+ if (number3 != null) {
+ numbers.addElement(number3);
+ }
+ int size = numbers.size();
+ if (size == 0) {
+ return null;
+ }
+ String[] result = new String[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = (String) numbers.elementAt(i);
+ }
+ return result;
+ }
+
+ private static String buildName(String firstName, String lastName) {
+ if (firstName == null) {
+ return lastName;
+ } else {
+ return lastName == null ? firstName : firstName + ' ' + lastName;
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/BookmarkDoCoMoResultParser.java b/src/com/google/zxing/client/result/BookmarkDoCoMoResultParser.java
new file mode 100644
index 0000000..aa07e99
--- /dev/null
+++ b/src/com/google/zxing/client/result/BookmarkDoCoMoResultParser.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.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * @author Sean Owen
+ */
+final class BookmarkDoCoMoResultParser extends AbstractDoCoMoResultParser {
+
+ private BookmarkDoCoMoResultParser() {
+ }
+
+ public static URIParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null || !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];
+ if (!URIResultParser.isBasicallyValidURI(uri)) {
+ return null;
+ }
+ return new URIParsedResult(uri, title);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/CalendarParsedResult.java b/src/com/google/zxing/client/result/CalendarParsedResult.java
new file mode 100644
index 0000000..e2e4531
--- /dev/null
+++ b/src/com/google/zxing/client/result/CalendarParsedResult.java
@@ -0,0 +1,134 @@
+/*
+ * 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 CalendarParsedResult extends ParsedResult {
+
+ private final String summary;
+ private final String start;
+ private final String end;
+ private final String location;
+ private final String attendee;
+ private final String description;
+
+ public CalendarParsedResult(String summary,
+ String start,
+ String end,
+ String location,
+ String attendee,
+ String description) {
+ super(ParsedResultType.CALENDAR);
+ // Start is required, end is not
+ if (start == null) {
+ throw new IllegalArgumentException();
+ }
+ validateDate(start);
+ if (end == null) {
+ end = start;
+ } else {
+ validateDate(end);
+ }
+ this.summary = summary;
+ this.start = start;
+ this.end = end;
+ this.location = location;
+ this.attendee = attendee;
+ this.description = description;
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ /**
+ * We would return the start and end date as a {@link java.util.Date} except that this code
+ * needs to work under JavaME / MIDP and there is no date parsing library available there, such
+ * as java.text.SimpleDateFormat
.
See validateDate() for the return format.
+ *
+ * @return start time formatted as a RFC 2445 DATE or DATE-TIME.
+ */
+ public String getStart() {
+ return start;
+ }
+
+ /**
+ * @see #getStart(). May return null if the event has no duration.
+ */
+ public String getEnd() {
+ return end;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public String getAttendee() {
+ return attendee;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getDisplayResult() {
+ StringBuffer result = new StringBuffer(100);
+ maybeAppend(summary, result);
+ maybeAppend(start, result);
+ maybeAppend(end, result);
+ maybeAppend(location, result);
+ maybeAppend(attendee, result);
+ maybeAppend(description, result);
+ return result.toString();
+ }
+
+ /**
+ * 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 date The string to validate
+ */
+ private static void validateDate(String date) {
+ if (date != null) {
+ int length = date.length();
+ if (length != 8 && length != 15 && length != 16) {
+ throw new IllegalArgumentException();
+ }
+ for (int i = 0; i < 8; i++) {
+ if (!Character.isDigit(date.charAt(i))) {
+ throw new IllegalArgumentException();
+ }
+ }
+ if (length > 8) {
+ if (date.charAt(8) != 'T') {
+ throw new IllegalArgumentException();
+ }
+ for (int i = 9; i < 15; i++) {
+ if (!Character.isDigit(date.charAt(i))) {
+ throw new IllegalArgumentException();
+ }
+ }
+ if (length == 16 && date.charAt(15) != 'Z') {
+ throw new IllegalArgumentException();
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/EmailAddressParsedResult.java b/src/com/google/zxing/client/result/EmailAddressParsedResult.java
new file mode 100644
index 0000000..425e766
--- /dev/null
+++ b/src/com/google/zxing/client/result/EmailAddressParsedResult.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+ }
+
+ public String getDisplayResult() {
+ StringBuffer result = new StringBuffer(30);
+ maybeAppend(emailAddress, result);
+ maybeAppend(subject, result);
+ maybeAppend(body, result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/EmailAddressResultParser.java b/src/com/google/zxing/client/result/EmailAddressResultParser.java
new file mode 100644
index 0000000..1c32e78
--- /dev/null
+++ b/src/com/google/zxing/client/result/EmailAddressResultParser.java
@@ -0,0 +1,64 @@
+/*
+ * 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.Hashtable;
+
+/**
+ * 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
+ */
+final class EmailAddressResultParser extends ResultParser {
+
+ public static EmailAddressParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null) {
+ return null;
+ }
+ 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);
+ }
+ Hashtable nameValues = parseNameValuePairs(rawText);
+ String subject = null;
+ String body = null;
+ if (nameValues != null) {
+ if (emailAddress.length() == 0) {
+ emailAddress = (String) nameValues.get("to");
+ }
+ subject = (String) nameValues.get("subject");
+ body = (String) 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/src/com/google/zxing/client/result/EmailDoCoMoResultParser.java b/src/com/google/zxing/client/result/EmailDoCoMoResultParser.java
new file mode 100644
index 0000000..b4eb70e
--- /dev/null
+++ b/src/com/google/zxing/client/result/EmailDoCoMoResultParser.java
@@ -0,0 +1,87 @@
+/*
+ * 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 "MATMSG" email message entry format.
+ *
+ * Supported keys: TO, SUB, BODY
+ *
+ * @author Sean Owen
+ */
+final class EmailDoCoMoResultParser extends AbstractDoCoMoResultParser {
+
+ private static final char[] ATEXT_SYMBOLS =
+ {'@','.','!','#','$','%','&','\'','*','+','-','/','=','?','^','_','`','{','|','}','~'};
+
+ public static EmailAddressParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null || !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 '@' 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) {
+ if (email == null) {
+ return false;
+ }
+ boolean atFound = false;
+ for (int i = 0; i < email.length(); i++) {
+ char c = email.charAt(i);
+ if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < '0' || c > '9') &&
+ !isAtextSymbol(c)) {
+ return false;
+ }
+ if (c == '@') {
+ if (atFound) {
+ return false;
+ }
+ atFound = true;
+ }
+ }
+ return atFound;
+ }
+
+ private static boolean isAtextSymbol(char c) {
+ for (int i = 0; i < ATEXT_SYMBOLS.length; i++) {
+ if (c == ATEXT_SYMBOLS[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/ExpandedProductParsedResult.java b/src/com/google/zxing/client/result/ExpandedProductParsedResult.java
new file mode 100644
index 0000000..a5cd3dc
--- /dev/null
+++ b/src/com/google/zxing/client/result/ExpandedProductParsedResult.java
@@ -0,0 +1,195 @@
+/*
+ * 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.Hashtable;
+
+/**
+ * @author Antonio Manuel Benjumea Conde, Servinform, S.A.
+ * @author Agustín Delgado, Servinform, S.A.
+ */
+public class ExpandedProductParsedResult extends ParsedResult {
+
+ public static final String KILOGRAM = "KG";
+ public static final String POUND = "LB";
+
+ 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 Hashtable uncommonAIs;
+
+ ExpandedProductParsedResult() {
+ super(ParsedResultType.PRODUCT);
+ this.productID = "";
+ this.sscc = "";
+ this.lotNumber = "";
+ this.productionDate = "";
+ this.packagingDate = "";
+ this.bestBeforeDate = "";
+ this.expirationDate = "";
+ this.weight = "";
+ this.weightType = "";
+ this.weightIncrement = "";
+ this.price = "";
+ this.priceIncrement = "";
+ this.priceCurrency = "";
+ this.uncommonAIs = new Hashtable();
+ }
+
+ public ExpandedProductParsedResult(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, Hashtable uncommonAIs) {
+ super(ParsedResultType.PRODUCT);
+ 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;
+ }
+
+ public boolean equals(Object o){
+ if (!(o instanceof ExpandedProductParsedResult)) {
+ return false;
+ }
+
+ ExpandedProductParsedResult other = (ExpandedProductParsedResult)o;
+
+ return this.productID.equals( other.productID)
+ && this.sscc.equals( other.sscc)
+ && this.lotNumber.equals( other.lotNumber)
+ && this.productionDate.equals( other.productionDate)
+ && this.bestBeforeDate.equals( other.bestBeforeDate)
+ && this.expirationDate.equals( other.expirationDate)
+ && this.weight.equals( other.weight)
+ && this.weightType.equals( other.weightType)
+ && this.weightIncrement.equals( other.weightIncrement)
+ && this.price.equals( other.price)
+ && this.priceIncrement.equals( other.priceIncrement)
+ && this.priceCurrency.equals( other.priceCurrency)
+ && this.uncommonAIs.equals( other.uncommonAIs);
+ }
+
+ public int hashCode(){
+ int hash1 = this.productID.hashCode();
+ hash1 = 31 * hash1 + this.sscc.hashCode();
+ hash1 = 31 * hash1 + this.lotNumber.hashCode();
+ hash1 = 31 * hash1 + this.productionDate.hashCode();
+ hash1 = 31 * hash1 + this.bestBeforeDate.hashCode();
+ hash1 = 31 * hash1 + this.expirationDate.hashCode();
+ hash1 = 31 * hash1 + this.weight.hashCode();
+
+ int hash2 = this.weightType.hashCode();
+ hash2 = 31 * hash2 + this.weightIncrement.hashCode();
+ hash2 = 31 * hash2 + this.price.hashCode();
+ hash2 = 31 * hash2 + this.priceIncrement.hashCode();
+ hash2 = 31 * hash2 + this.priceCurrency.hashCode();
+ hash2 = 31 * hash2 + this.uncommonAIs.hashCode();
+ return hash1 ^ hash2;
+ }
+
+ 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 Hashtable getUncommonAIs() {
+ return uncommonAIs;
+ }
+
+ public String getDisplayResult() {
+ return productID;
+ }
+}
diff --git a/src/com/google/zxing/client/result/ExpandedProductResultParser.java b/src/com/google/zxing/client/result/ExpandedProductResultParser.java
new file mode 100644
index 0000000..cddbda8
--- /dev/null
+++ b/src/com/google/zxing/client/result/ExpandedProductResultParser.java
@@ -0,0 +1,199 @@
+/*
+ * 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.Hashtable;
+
+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.
+ */
+final class ExpandedProductResultParser extends ResultParser {
+
+ private ExpandedProductResultParser() {
+ }
+
+ // Treat all RSS EXPANDED, in the sense that they are all
+ // product barcodes with complementary data.
+ public static ExpandedProductParsedResult parse(Result result) {
+ BarcodeFormat format = result.getBarcodeFormat();
+ if (!(BarcodeFormat.RSS_EXPANDED.equals(format))) {
+ // ExtendedProductParsedResult NOT created. Not a RSS Expanded barcode
+ return null;
+ }
+ // Really neither of these should happen:
+ String rawText = result.getText();
+ if (rawText == null) {
+ // ExtendedProductParsedResult NOT created. Input text is NULL
+ return null;
+ }
+
+ 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 = "-";
+ Hashtable uncommonAIs = new Hashtable();
+
+ int i = 0;
+
+ while (i < rawText.length()) {
+ String ai = findAIvalue(i, rawText);
+ if ("ERROR".equals(ai)) {
+ // 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();
+
+ if ("00".equals(ai)) {
+ sscc = value;
+ } else if ("01".equals(ai)) {
+ productID = value;
+ } else if ("10".equals(ai)) {
+ lotNumber = value;
+ } else if ("11".equals(ai)) {
+ productionDate = value;
+ } else if ("13".equals(ai)) {
+ packagingDate = value;
+ } else if ("15".equals(ai)) {
+ bestBeforeDate = value;
+ } else if ("17".equals(ai)) {
+ expirationDate = value;
+ } else if ("3100".equals(ai) || "3101".equals(ai)
+ || "3102".equals(ai) || "3103".equals(ai)
+ || "3104".equals(ai) || "3105".equals(ai)
+ || "3106".equals(ai) || "3107".equals(ai)
+ || "3108".equals(ai) || "3109".equals(ai)) {
+ weight = value;
+ weightType = ExpandedProductParsedResult.KILOGRAM;
+ weightIncrement = ai.substring(3);
+ } else if ("3200".equals(ai) || "3201".equals(ai)
+ || "3202".equals(ai) || "3203".equals(ai)
+ || "3204".equals(ai) || "3205".equals(ai)
+ || "3206".equals(ai) || "3207".equals(ai)
+ || "3208".equals(ai) || "3209".equals(ai)) {
+ weight = value;
+ weightType = ExpandedProductParsedResult.POUND;
+ weightIncrement = ai.substring(3);
+ } else if ("3920".equals(ai) || "3921".equals(ai)
+ || "3922".equals(ai) || "3923".equals(ai)) {
+ price = value;
+ priceIncrement = ai.substring(3);
+ } else if ("3930".equals(ai) || "3931".equals(ai)
+ || "3932".equals(ai) || "3933".equals(ai)) {
+ 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);
+ } else {
+ // No match with common AIs
+ uncommonAIs.put(ai, value);
+ }
+ }
+
+ return new ExpandedProductParsedResult(productID, sscc, lotNumber,
+ productionDate, packagingDate, bestBeforeDate, expirationDate,
+ weight, weightType, weightIncrement, price, priceIncrement,
+ priceCurrency, uncommonAIs);
+ }
+
+ private static String findAIvalue(int i, String rawText) {
+ StringBuffer buf = new StringBuffer();
+ char c = rawText.charAt(i);
+ // First character must be a open parenthesis.If not, ERROR
+ if (c != '(') {
+ return "ERROR";
+ }
+
+ String rawTextAux = rawText.substring(i + 1);
+
+ for (int index = 0; index < rawTextAux.length(); index++) {
+ char currentChar = rawTextAux.charAt(index);
+ switch (currentChar){
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ buf.append(currentChar);
+ break;
+ case ')':
+ return buf.toString();
+ default:
+ return "ERROR";
+ }
+ }
+ return buf.toString();
+ }
+
+ private static String findValue(int i, String rawText) {
+ StringBuffer buf = new StringBuffer();
+ 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 ("ERROR".equals(findAIvalue(index, rawTextAux))) {
+ buf.append('(');
+ } else {
+ break;
+ }
+ } else {
+ buf.append(c);
+ }
+ }
+ return buf.toString();
+ }
+}
diff --git a/src/com/google/zxing/client/result/GeoParsedResult.java b/src/com/google/zxing/client/result/GeoParsedResult.java
new file mode 100644
index 0000000..dc50e8a
--- /dev/null
+++ b/src/com/google/zxing/client/result/GeoParsedResult.java
@@ -0,0 +1,132 @@
+/*
+ * 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() {
+ StringBuffer result = new StringBuffer();
+ 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;
+ }
+
+ public String getDisplayResult() {
+ StringBuffer result = new StringBuffer(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();
+ }
+
+ /**
+ * @return a URI link to Google Maps which display the point on the Earth described
+ * by this instance, and sets the zoom level in a way that roughly reflects the
+ * altitude, if specified
+ */
+ /*
+ public String getGoogleMapsURI() {
+ StringBuffer result = new StringBuffer(50);
+ result.append("http://maps.google.com/?ll=");
+ result.append(latitude);
+ result.append(',');
+ result.append(longitude);
+ if (altitude > 0.0f) {
+ // Map altitude to zoom level, cleverly. Roughly, zoom level 19 is like a
+ // view from 1000ft, 18 is like 2000ft, 17 like 4000ft, and so on.
+ double altitudeInFeet = altitude * 3.28;
+ int altitudeInKFeet = (int) (altitudeInFeet / 1000.0);
+ // No Math.log() available here, so compute log base 2 the old fashioned way
+ // Here logBaseTwo will take on a value between 0 and 18 actually
+ int logBaseTwo = 0;
+ while (altitudeInKFeet > 1 && logBaseTwo < 18) {
+ altitudeInKFeet >>= 1;
+ logBaseTwo++;
+ }
+ int zoom = 19 - logBaseTwo;
+ result.append("&z=");
+ result.append(zoom);
+ }
+ return result.toString();
+ }
+ */
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/GeoResultParser.java b/src/com/google/zxing/client/result/GeoResultParser.java
new file mode 100644
index 0000000..8459ddc
--- /dev/null
+++ b/src/com/google/zxing/client/result/GeoResultParser.java
@@ -0,0 +1,77 @@
+/*
+ * 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 "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
+ */
+final class GeoResultParser extends ResultParser {
+
+ private GeoResultParser() {
+ }
+
+ public static GeoParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null || (!rawText.startsWith("geo:") && !rawText.startsWith("GEO:"))) {
+ return null;
+ }
+ // Drop geo, query portion
+ int queryStart = rawText.indexOf('?', 4);
+ String query;
+ String geoURIWithoutQuery;
+ if (queryStart < 0) {
+ query = null;
+ geoURIWithoutQuery = rawText.substring(4);
+ } else {
+ query = rawText.substring(queryStart + 1);
+ geoURIWithoutQuery = rawText.substring(4, queryStart);
+ }
+ int latitudeEnd = geoURIWithoutQuery.indexOf(',');
+ if (latitudeEnd < 0) {
+ return null;
+ }
+ int longitudeEnd = geoURIWithoutQuery.indexOf(',', latitudeEnd + 1);
+ double latitude, longitude, altitude;
+ try {
+ latitude = Double.parseDouble(geoURIWithoutQuery.substring(0, latitudeEnd));
+ if (latitude > 90.0 || latitude < -90.0) {
+ return null;
+ }
+ if (longitudeEnd < 0) {
+ longitude = Double.parseDouble(geoURIWithoutQuery.substring(latitudeEnd + 1));
+ altitude = 0.0;
+ } else {
+ longitude = Double.parseDouble(geoURIWithoutQuery.substring(latitudeEnd + 1, longitudeEnd));
+ altitude = Double.parseDouble(geoURIWithoutQuery.substring(longitudeEnd + 1));
+ }
+ if (longitude > 180.0 || longitude < -180.0 || altitude < 0) {
+ return null;
+ }
+ } catch (NumberFormatException nfe) {
+ return null;
+ }
+ return new GeoParsedResult(latitude, longitude, altitude, query);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/ISBNParsedResult.java b/src/com/google/zxing/client/result/ISBNParsedResult.java
new file mode 100644
index 0000000..8f0fe89
--- /dev/null
+++ b/src/com/google/zxing/client/result/ISBNParsedResult.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+ }
+
+ public String getDisplayResult() {
+ return isbn;
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/ISBNResultParser.java b/src/com/google/zxing/client/result/ISBNResultParser.java
new file mode 100644
index 0000000..943dd14
--- /dev/null
+++ b/src/com/google/zxing/client/result/ISBNResultParser.java
@@ -0,0 +1,54 @@
+/*
+ * 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 class ISBNResultParser extends ResultParser {
+
+ private ISBNResultParser() {
+ }
+
+ // ISBN-13 For Dummies
+ // http://www.bisg.org/isbn-13/for.dummies.html
+ public static ISBNParsedResult parse(Result result) {
+ BarcodeFormat format = result.getBarcodeFormat();
+ if (!BarcodeFormat.EAN_13.equals(format)) {
+ return null;
+ }
+ String rawText = result.getText();
+ if (rawText == null) {
+ return null;
+ }
+ 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/src/com/google/zxing/client/result/ParsedResult.java b/src/com/google/zxing/client/result/ParsedResult.java
new file mode 100644
index 0000000..65550bf
--- /dev/null
+++ b/src/com/google/zxing/client/result/ParsedResult.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+/**
+ * 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(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 ParsedResultType getType() {
+ return type;
+ }
+
+ public abstract String getDisplayResult();
+
+ public String toString() {
+ return getDisplayResult();
+ }
+
+ public static void maybeAppend(String value, StringBuffer result) {
+ if (value != null && value.length() > 0) {
+ // Don't add a newline before the first value
+ if (result.length() > 0) {
+ result.append('\n');
+ }
+ result.append(value);
+ }
+ }
+
+ public static void maybeAppend(String[] value, StringBuffer result) {
+ if (value != null) {
+ for (int i = 0; i < value.length; i++) {
+ if (value[i] != null && value[i].length() > 0) {
+ if (result.length() > 0) {
+ result.append('\n');
+ }
+ result.append(value[i]);
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/ParsedResultType.java b/src/com/google/zxing/client/result/ParsedResultType.java
new file mode 100644
index 0000000..92c81cb
--- /dev/null
+++ b/src/com/google/zxing/client/result/ParsedResultType.java
@@ -0,0 +1,53 @@
+/*
+ * 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 final class ParsedResultType {
+
+ public static final ParsedResultType ADDRESSBOOK = new ParsedResultType("ADDRESSBOOK");
+ public static final ParsedResultType EMAIL_ADDRESS = new ParsedResultType("EMAIL_ADDRESS");
+ public static final ParsedResultType PRODUCT = new ParsedResultType("PRODUCT");
+ public static final ParsedResultType URI = new ParsedResultType("URI");
+ public static final ParsedResultType TEXT = new ParsedResultType("TEXT");
+ public static final ParsedResultType ANDROID_INTENT = new ParsedResultType("ANDROID_INTENT");
+ public static final ParsedResultType GEO = new ParsedResultType("GEO");
+ public static final ParsedResultType TEL = new ParsedResultType("TEL");
+ public static final ParsedResultType SMS = new ParsedResultType("SMS");
+ public static final ParsedResultType CALENDAR = new ParsedResultType("CALENDAR");
+ public static final ParsedResultType WIFI = new ParsedResultType("WIFI");
+ // "optional" types
+ public static final ParsedResultType NDEF_SMART_POSTER = new ParsedResultType("NDEF_SMART_POSTER");
+ public static final ParsedResultType MOBILETAG_RICH_WEB = new ParsedResultType("MOBILETAG_RICH_WEB");
+ public static final ParsedResultType ISBN = new ParsedResultType("ISBN");
+
+ private final String name;
+
+ private ParsedResultType(String name) {
+ this.name = name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/ProductParsedResult.java b/src/com/google/zxing/client/result/ProductParsedResult.java
new file mode 100644
index 0000000..9014ca8
--- /dev/null
+++ b/src/com/google/zxing/client/result/ProductParsedResult.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;
+
+/**
+ * @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;
+ }
+
+ public String getDisplayResult() {
+ return productID;
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/ProductResultParser.java b/src/com/google/zxing/client/result/ProductResultParser.java
new file mode 100644
index 0000000..0ca23b2
--- /dev/null
+++ b/src/com/google/zxing/client/result/ProductResultParser.java
@@ -0,0 +1,66 @@
+/*
+ * 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)
+ */
+final class ProductResultParser extends ResultParser {
+
+ private ProductResultParser() {
+ }
+
+ // Treat all UPC and EAN variants as UPCs, in the sense that they are all product barcodes.
+ public static ProductParsedResult parse(Result result) {
+ BarcodeFormat format = result.getBarcodeFormat();
+ if (!(BarcodeFormat.UPC_A.equals(format) || BarcodeFormat.UPC_E.equals(format) ||
+ BarcodeFormat.EAN_8.equals(format) || BarcodeFormat.EAN_13.equals(format))) {
+ return null;
+ }
+ // Really neither of these should happen:
+ String rawText = result.getText();
+ if (rawText == null) {
+ return null;
+ }
+
+ int length = rawText.length();
+ for (int x = 0; x < length; x++) {
+ char c = rawText.charAt(x);
+ if (c < '0' || c > '9') {
+ return null;
+ }
+ }
+ // Not actually checking the checksum again here
+
+ String normalizedProductID;
+ // Expand UPC-E for purposes of searching
+ if (BarcodeFormat.UPC_E.equals(format)) {
+ normalizedProductID = UPCEReader.convertUPCEtoUPCA(rawText);
+ } else {
+ normalizedProductID = rawText;
+ }
+
+ return new ProductParsedResult(rawText, normalizedProductID);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/ResultParser.java b/src/com/google/zxing/client/result/ResultParser.java
new file mode 100644
index 0000000..df30865
--- /dev/null
+++ b/src/com/google/zxing/client/result/ResultParser.java
@@ -0,0 +1,319 @@
+/*
+ * 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.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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(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 ResultParser {
+
+ public static ParsedResult parseResult(Result theResult) {
+ // This is a bit messy, but given limited options in MIDP / CLDC, this may well be the simplest
+ // way to go about this. For example, we have no reflection available, really.
+ // Order is important here.
+ ParsedResult result;
+ if ((result = BookmarkDoCoMoResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = AddressBookDoCoMoResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = EmailDoCoMoResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = AddressBookAUResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = VCardResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = BizcardResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = VEventResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = EmailAddressResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = TelResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = SMSMMSResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = SMSTOMMSTOResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = GeoResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = WifiResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = URLTOResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = URIResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = ISBNResultParser.parse(theResult)) != null) {
+ // We depend on ISBN parsing coming before UPC, as it is a subset.
+ return result;
+ } else if ((result = ProductResultParser.parse(theResult)) != null) {
+ return result;
+ } else if ((result = ExpandedProductResultParser.parse(theResult)) != null) {
+ return result;
+ }
+ return new TextParsedResult(theResult.getText(), null);
+ }
+
+ protected static void maybeAppend(String value, StringBuffer result) {
+ if (value != null) {
+ result.append('\n');
+ result.append(value);
+ }
+ }
+
+ protected static void maybeAppend(String[] value, StringBuffer result) {
+ if (value != null) {
+ for (int i = 0; i < value.length; i++) {
+ result.append('\n');
+ result.append(value[i]);
+ }
+ }
+ }
+
+ protected static String[] maybeWrap(String value) {
+ return value == null ? null : new String[] { value };
+ }
+
+ protected static String unescapeBackslash(String escaped) {
+ if (escaped != null) {
+ int backslash = escaped.indexOf((int) '\\');
+ if (backslash >= 0) {
+ int max = escaped.length();
+ StringBuffer unescaped = new StringBuffer(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();
+ }
+ }
+ return escaped;
+ }
+
+ private static String urlDecode(String escaped) {
+
+ // No we can't use java.net.URLDecoder here. JavaME doesn't have it.
+ if (escaped == null) {
+ return null;
+ }
+ char[] escapedArray = escaped.toCharArray();
+
+ int first = findFirstEscape(escapedArray);
+ if (first < 0) {
+ return escaped;
+ }
+
+ int max = escapedArray.length;
+ // final length is at most 2 less than original due to at least 1 unescaping
+ StringBuffer unescaped = new StringBuffer(max - 2);
+ // Can append everything up to first escape character
+ unescaped.append(escapedArray, 0, first);
+
+ for (int i = first; i < max; i++) {
+ char c = escapedArray[i];
+ if (c == '+') {
+ // + is translated directly into a space
+ unescaped.append(' ');
+ } else if (c == '%') {
+ // Are there even two more chars? if not we will just copy the escaped sequence and be done
+ if (i >= max - 2) {
+ unescaped.append('%'); // append that % and move on
+ } else {
+ int firstDigitValue = parseHexDigit(escapedArray[++i]);
+ int secondDigitValue = parseHexDigit(escapedArray[++i]);
+ if (firstDigitValue < 0 || secondDigitValue < 0) {
+ // bad digit, just move on
+ unescaped.append('%');
+ unescaped.append(escapedArray[i-1]);
+ unescaped.append(escapedArray[i]);
+ }
+ unescaped.append((char) ((firstDigitValue << 4) + secondDigitValue));
+ }
+ } else {
+ unescaped.append(c);
+ }
+ }
+ return unescaped.toString();
+ }
+
+ private static int findFirstEscape(char[] escapedArray) {
+ int max = escapedArray.length;
+ for (int i = 0; i < max; i++) {
+ char c = escapedArray[i];
+ if (c == '+' || c == '%') {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private static int parseHexDigit(char c) {
+ if (c >= 'a') {
+ if (c <= 'f') {
+ return 10 + (c - 'a');
+ }
+ } else if (c >= 'A') {
+ if (c <= 'F') {
+ return 10 + (c - 'A');
+ }
+ } else if (c >= '0') {
+ if (c <= '9') {
+ return c - '0';
+ }
+ }
+ return -1;
+ }
+
+ protected static boolean isStringOfDigits(String value, int length) {
+ if (value == null) {
+ return false;
+ }
+ int stringLength = value.length();
+ if (length != stringLength) {
+ return false;
+ }
+ for (int i = 0; i < length; i++) {
+ char c = value.charAt(i);
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected static boolean isSubstringOfDigits(String value, int offset, int length) {
+ if (value == null) {
+ return false;
+ }
+ int stringLength = value.length();
+ int max = offset + length;
+ if (stringLength < max) {
+ return false;
+ }
+ for (int i = offset; i < max; i++) {
+ char c = value.charAt(i);
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static Hashtable parseNameValuePairs(String uri) {
+ int paramStart = uri.indexOf('?');
+ if (paramStart < 0) {
+ return null;
+ }
+ Hashtable result = new Hashtable(3);
+ paramStart++;
+ int paramEnd;
+ while ((paramEnd = uri.indexOf('&', paramStart)) >= 0) {
+ appendKeyValue(uri, paramStart, paramEnd, result);
+ paramStart = paramEnd + 1;
+ }
+ appendKeyValue(uri, paramStart, uri.length(), result);
+ return result;
+ }
+
+ private static void appendKeyValue(String uri, int paramStart, int paramEnd, Hashtable result) {
+ int separator = uri.indexOf('=', paramStart);
+ if (separator >= 0) {
+ // key = value
+ String key = uri.substring(paramStart, separator);
+ String value = uri.substring(separator + 1, paramEnd);
+ value = urlDecode(value);
+ result.put(key, value);
+ }
+ // Can't put key, null into a hashtable
+ }
+
+ static String[] matchPrefixedField(String prefix, String rawText, char endChar, boolean trim) {
+ Vector 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 done = false;
+ while (!done) {
+ 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();
+ done = true;
+ } else if (rawText.charAt(i - 1) == '\\') {
+ // semicolon was escaped so continue
+ i++;
+ } else {
+ // found a match
+ if (matches == null) {
+ matches = new Vector(3); // lazy init
+ }
+ String element = unescapeBackslash(rawText.substring(start, i));
+ if (trim) {
+ element = element.trim();
+ }
+ matches.addElement(element);
+ i++;
+ done = true;
+ }
+ }
+ }
+ if (matches == null || matches.isEmpty()) {
+ return null;
+ }
+ return toStringArray(matches);
+ }
+
+ static String matchSinglePrefixedField(String prefix, String rawText, char endChar, boolean trim) {
+ String[] matches = matchPrefixedField(prefix, rawText, endChar, trim);
+ return matches == null ? null : matches[0];
+ }
+
+ static String[] toStringArray(Vector strings) {
+ int size = strings.size();
+ String[] result = new String[size];
+ for (int j = 0; j < size; j++) {
+ result[j] = (String) strings.elementAt(j);
+ }
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/SMSMMSResultParser.java b/src/com/google/zxing/client/result/SMSMMSResultParser.java
new file mode 100644
index 0000000..e309132
--- /dev/null
+++ b/src/com/google/zxing/client/result/SMSMMSResultParser.java
@@ -0,0 +1,107 @@
+/*
+ * 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.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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
+ */
+final class SMSMMSResultParser extends ResultParser {
+
+ private SMSMMSResultParser() {
+ }
+
+ public static SMSParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null) {
+ return null;
+ }
+ 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
+ Hashtable nameValuePairs = parseNameValuePairs(rawText);
+ String subject = null;
+ String body = null;
+ boolean querySyntax = false;
+ if (nameValuePairs != null && !nameValuePairs.isEmpty()) {
+ subject = (String) nameValuePairs.get("subject");
+ body = (String) 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;
+ Vector numbers = new Vector(1);
+ Vector vias = new Vector(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(toStringArray(numbers), toStringArray(vias), subject, body);
+ }
+
+ private static void addNumberVia(Vector numbers, Vector vias, String numberPart) {
+ int numberEnd = numberPart.indexOf(';');
+ if (numberEnd < 0) {
+ numbers.addElement(numberPart);
+ vias.addElement(null);
+ } else {
+ numbers.addElement(numberPart.substring(0, numberEnd));
+ String maybeVia = numberPart.substring(numberEnd + 1);
+ String via;
+ if (maybeVia.startsWith("via=")) {
+ via = maybeVia.substring(4);
+ } else {
+ via = null;
+ }
+ vias.addElement(via);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/SMSParsedResult.java b/src/com/google/zxing/client/result/SMSParsedResult.java
new file mode 100644
index 0000000..93792ae
--- /dev/null
+++ b/src/com/google/zxing/client/result/SMSParsedResult.java
@@ -0,0 +1,104 @@
+/*
+ * 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() {
+ StringBuffer result = new StringBuffer();
+ 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[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;
+ }
+
+ public String getDisplayResult() {
+ StringBuffer result = new StringBuffer(100);
+ maybeAppend(numbers, result);
+ maybeAppend(subject, result);
+ maybeAppend(body, result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java b/src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java
new file mode 100644
index 0000000..76418d0
--- /dev/null
+++ b/src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java
@@ -0,0 +1,57 @@
+/*
+ * 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:
+ * 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
+ */
+final class SMSTOMMSTOResultParser extends ResultParser {
+
+ private SMSTOMMSTOResultParser() {
+ }
+
+ public static SMSParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null) {
+ return null;
+ }
+ 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/src/com/google/zxing/client/result/TelParsedResult.java b/src/com/google/zxing/client/result/TelParsedResult.java
new file mode 100644
index 0000000..a618fe7
--- /dev/null
+++ b/src/com/google/zxing/client/result/TelParsedResult.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+ }
+
+ public String getDisplayResult() {
+ StringBuffer result = new StringBuffer(20);
+ maybeAppend(number, result);
+ maybeAppend(title, result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/TelResultParser.java b/src/com/google/zxing/client/result/TelResultParser.java
new file mode 100644
index 0000000..d3f7775
--- /dev/null
+++ b/src/com/google/zxing/client/result/TelResultParser.java
@@ -0,0 +1,44 @@
+/*
+ * 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
+ */
+final class TelResultParser extends ResultParser {
+
+ private TelResultParser() {
+ }
+
+ public static TelParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null || (!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/src/com/google/zxing/client/result/TextParsedResult.java b/src/com/google/zxing/client/result/TextParsedResult.java
new file mode 100644
index 0000000..052d4d0
--- /dev/null
+++ b/src/com/google/zxing/client/result/TextParsedResult.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+ }
+
+ public String getDisplayResult() {
+ return text;
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/URIParsedResult.java b/src/com/google/zxing/client/result/URIParsedResult.java
new file mode 100644
index 0000000..6931c45
--- /dev/null
+++ b/src/com/google/zxing/client/result/URIParsedResult.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.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class URIParsedResult extends ParsedResult {
+
+ 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 containsUser();
+ }
+
+ private boolean containsUser() {
+ // This method is likely not 100% RFC compliant yet
+ int hostStart = uri.indexOf(':'); // we should always have scheme at this point
+ hostStart++;
+ // Skip slashes preceding host
+ int uriLength = uri.length();
+ while (hostStart < uriLength && uri.charAt(hostStart) == '/') {
+ hostStart++;
+ }
+ int hostEnd = uri.indexOf('/', hostStart);
+ if (hostEnd < 0) {
+ hostEnd = uriLength;
+ }
+ int at = uri.indexOf('@', hostStart);
+ return at >= hostStart && at < hostEnd;
+ }
+
+ public String getDisplayResult() {
+ StringBuffer result = new StringBuffer(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) {
+ 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;
+ } else {
+ // Lowercase protocol to avoid problems
+ uri = uri.substring(0, protocolEnd).toLowerCase() + uri.substring(protocolEnd);
+ }
+ return uri;
+ }
+
+ private static boolean isColonFollowedByPortNumber(String uri, int protocolEnd) {
+ int nextSlash = uri.indexOf('/', protocolEnd + 1);
+ if (nextSlash < 0) {
+ nextSlash = uri.length();
+ }
+ if (nextSlash <= protocolEnd + 1) {
+ return false;
+ }
+ for (int x = protocolEnd + 1; x < nextSlash; x++) {
+ if (uri.charAt(x) < '0' || uri.charAt(x) > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/URIResultParser.java b/src/com/google/zxing/client/result/URIResultParser.java
new file mode 100644
index 0000000..5b2c24b
--- /dev/null
+++ b/src/com/google/zxing/client/result/URIResultParser.java
@@ -0,0 +1,87 @@
+/*
+ * 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;
+
+/**
+ * Tries to parse results that are a URI of some kind.
+ *
+ * @author Sean Owen
+ */
+final class URIResultParser extends ResultParser {
+
+ private URIResultParser() {
+ }
+
+ public static URIParsedResult parse(Result result) {
+ String rawText = result.getText();
+ // We specifically handle the odd "URL" scheme here for simplicity
+ if (rawText != null && rawText.startsWith("URL:")) {
+ rawText = rawText.substring(4);
+ }
+ if (!isBasicallyValidURI(rawText)) {
+ return null;
+ }
+ return new URIParsedResult(rawText, null);
+ }
+
+ /**
+ * Determines whether a string is not obviously not a URI. This implements crude checks; this class does not
+ * intend to strictly check URIs as its only function is to represent what is in a barcode, but, it does
+ * need to know when a string is obviously not a URI.
+ */
+ static boolean isBasicallyValidURI(String uri) {
+ if (uri == null || uri.indexOf(' ') >= 0 || uri.indexOf('\n') >= 0) {
+ return false;
+ }
+ // Look for period in a domain but followed by at least a two-char TLD
+ // Forget strings that don't have a valid-looking protocol
+ int period = uri.indexOf('.');
+ if (period >= uri.length() - 2) {
+ return false;
+ }
+ int colon = uri.indexOf(':');
+ if (period < 0 && colon < 0) {
+ return false;
+ }
+ if (colon >= 0) {
+ if (period < 0 || period > colon) {
+ // colon ends the protocol
+ for (int i = 0; i < colon; i++) {
+ char c = uri.charAt(i);
+ if ((c < 'a' || c > 'z') && (c < 'A' || c > 'Z')) {
+ return false;
+ }
+ }
+ } else {
+ // colon starts the port; crudely look for at least two numbers
+ if (colon >= uri.length() - 2) {
+ return false;
+ }
+ for (int i = colon + 1; i < colon + 3; i++) {
+ char c = uri.charAt(i);
+ if (c < '0' || c > '9') {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/URLTOResultParser.java b/src/com/google/zxing/client/result/URLTOResultParser.java
new file mode 100644
index 0000000..0ccd638
--- /dev/null
+++ b/src/com/google/zxing/client/result/URLTOResultParser.java
@@ -0,0 +1,47 @@
+/*
+ * 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
+ */
+final class URLTOResultParser {
+
+ private URLTOResultParser() {
+ }
+
+ public static URIParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null || (!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/src/com/google/zxing/client/result/VCardResultParser.java b/src/com/google/zxing/client/result/VCardResultParser.java
new file mode 100644
index 0000000..4d53971
--- /dev/null
+++ b/src/com/google/zxing/client/result/VCardResultParser.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.client.result;
+
+import com.google.zxing.Result;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Vector;
+
+/**
+ * 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
+ */
+final class VCardResultParser extends ResultParser {
+
+ private VCardResultParser() {
+ }
+
+ public static AddressBookParsedResult parse(Result result) {
+ // Although we should insist on the raw text ending with "END:VCARD", there's no reason
+ // to throw out everything else we parsed just because this was omitted. In fact, Eclair
+ // is doing just that, and we can't parse its contacts without this leniency.
+ String rawText = result.getText();
+ if (rawText == null || !rawText.startsWith("BEGIN:VCARD")) {
+ return null;
+ }
+ String[] names = matchVCardPrefixedField("FN", rawText, true);
+ if (names == null) {
+ // If no display names found, look for regular name fields and format them
+ names = matchVCardPrefixedField("N", rawText, true);
+ formatNames(names);
+ }
+ String[] phoneNumbers = matchVCardPrefixedField("TEL", rawText, true);
+ String[] emails = matchVCardPrefixedField("EMAIL", rawText, true);
+ String note = matchSingleVCardPrefixedField("NOTE", rawText, false);
+ String[] addresses = matchVCardPrefixedField("ADR", rawText, true);
+ if (addresses != null) {
+ for (int i = 0; i < addresses.length; i++) {
+ addresses[i] = formatAddress(addresses[i]);
+ }
+ }
+ String org = matchSingleVCardPrefixedField("ORG", rawText, true);
+ String birthday = matchSingleVCardPrefixedField("BDAY", rawText, true);
+ if (!isLikeVCardDate(birthday)) {
+ birthday = null;
+ }
+ String title = matchSingleVCardPrefixedField("TITLE", rawText, true);
+ String url = matchSingleVCardPrefixedField("URL", rawText, true);
+ return new AddressBookParsedResult(names, null, phoneNumbers, emails, note, addresses, org,
+ birthday, title, url);
+ }
+
+ private static String[] matchVCardPrefixedField(String prefix, String rawText, boolean trim) {
+ Vector matches = null;
+ int i = 0;
+ int max = rawText.length();
+
+ while (i < max) {
+
+ i = rawText.indexOf(prefix, i);
+ if (i < 0) {
+ break;
+ }
+
+ if (i > 0 && rawText.charAt(i - 1) != '\n') {
+ // then this didn't start a new token, we matched in the middle of something
+ i++;
+ continue;
+ }
+ i += prefix.length(); // Skip past this prefix we found to start
+ if (rawText.charAt(i) != ':' && rawText.charAt(i) != ';') {
+ continue;
+ }
+
+ int metadataStart = i;
+ while (rawText.charAt(i) != ':') { // Skip until a colon
+ i++;
+ }
+
+ boolean quotedPrintable = false;
+ String quotedPrintableCharset = null;
+ if (i > metadataStart) {
+ // There was something after the tag, before colon
+ int j = metadataStart+1;
+ while (j <= i) {
+ if (rawText.charAt(j) == ';' || rawText.charAt(j) == ':') {
+ String metadata = rawText.substring(metadataStart+1, j);
+ int equals = metadata.indexOf('=');
+ if (equals >= 0) {
+ String key = metadata.substring(0, equals);
+ String value = metadata.substring(equals+1);
+ if (key.equalsIgnoreCase("ENCODING")) {
+ if (value.equalsIgnoreCase("QUOTED-PRINTABLE")) {
+ quotedPrintable = true;
+ }
+ } else if (key.equalsIgnoreCase("CHARSET")) {
+ quotedPrintableCharset = value;
+ }
+ }
+ metadataStart = j;
+ }
+ j++;
+ }
+ }
+
+ i++; // skip colon
+
+ 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
+ (rawText.charAt(i-1) == '=' || // this is a continuation
+ 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 Vector(1); // lazy init
+ }
+ if (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);
+ } else {
+ element = stripContinuationCRLF(element);
+ }
+ matches.addElement(element);
+ i++;
+ } else {
+ i++;
+ }
+
+ }
+
+ if (matches == null || matches.isEmpty()) {
+ return null;
+ }
+ return toStringArray(matches);
+ }
+
+ private static String stripContinuationCRLF(String value) {
+ int length = value.length();
+ StringBuffer result = new StringBuffer(length);
+ boolean lastWasLF = false;
+ for (int i = 0; i < length; i++) {
+ if (lastWasLF) {
+ lastWasLF = false;
+ continue;
+ }
+ char c = value.charAt(i);
+ lastWasLF = false;
+ switch (c) {
+ case '\n':
+ lastWasLF = true;
+ break;
+ case '\r':
+ break;
+ default:
+ result.append(c);
+ }
+ }
+ return result.toString();
+ }
+
+ private static String decodeQuotedPrintable(String value, String charset) {
+ int length = value.length();
+ StringBuffer result = new StringBuffer(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') {
+ // Ignore, it's just a continuation symbol
+ } else {
+ char nextNextChar = value.charAt(i+2);
+ try {
+ int encodedByte = 16 * toHexValue(nextChar) + toHexValue(nextNextChar);
+ fragmentBuffer.write(encodedByte);
+ } catch (IllegalArgumentException iae) {
+ // continue, 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 int toHexValue(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ } else if (c >= 'A' && c <= 'F') {
+ return c - 'A' + 10;
+ } else if (c >= 'a' && c <= 'f') {
+ return c - 'a' + 10;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static void maybeAppendFragment(ByteArrayOutputStream fragmentBuffer,
+ String charset,
+ StringBuffer result) {
+ if (fragmentBuffer.size() > 0) {
+ byte[] fragmentBytes = fragmentBuffer.toByteArray();
+ String fragment;
+ if (charset == null) {
+ fragment = new String(fragmentBytes);
+ } else {
+ try {
+ fragment = new String(fragmentBytes, charset);
+ } catch (UnsupportedEncodingException e) {
+ // Yikes, well try anyway:
+ fragment = new String(fragmentBytes);
+ }
+ }
+ fragmentBuffer.reset();
+ result.append(fragment);
+ }
+ }
+
+ static String matchSingleVCardPrefixedField(String prefix, String rawText, boolean trim) {
+ String[] values = matchVCardPrefixedField(prefix, rawText, trim);
+ return values == null ? null : values[0];
+ }
+
+ private static boolean isLikeVCardDate(String value) {
+ if (value == null) {
+ return true;
+ }
+ // Not really sure this is true but matches practice
+ // Mach YYYYMMDD
+ if (isStringOfDigits(value, 8)) {
+ return true;
+ }
+ // or YYYY-MM-DD
+ return
+ value.length() == 10 &&
+ value.charAt(4) == '-' &&
+ value.charAt(7) == '-' &&
+ isSubstringOfDigits(value, 0, 4) &&
+ isSubstringOfDigits(value, 5, 2) &&
+ isSubstringOfDigits(value, 8, 2);
+ }
+
+ private static String formatAddress(String address) {
+ if (address == null) {
+ return null;
+ }
+ int length = address.length();
+ StringBuffer newAddress = new StringBuffer(length);
+ for (int j = 0; j < length; j++) {
+ char c = address.charAt(j);
+ if (c == ';') {
+ newAddress.append(' ');
+ } else {
+ newAddress.append(c);
+ }
+ }
+ return newAddress.toString().trim();
+ }
+
+ /**
+ * 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(String[] names) {
+ if (names != null) {
+ for (int i = 0; i < names.length; i++) {
+ String name = names[i];
+ String[] components = new String[5];
+ int start = 0;
+ int end;
+ int componentIndex = 0;
+ while ((end = name.indexOf(';', start)) > 0) {
+ components[componentIndex] = name.substring(start, end);
+ componentIndex++;
+ start = end + 1;
+ }
+ components[componentIndex] = name.substring(start);
+ StringBuffer newName = new StringBuffer(100);
+ maybeAppendComponent(components, 3, newName);
+ maybeAppendComponent(components, 1, newName);
+ maybeAppendComponent(components, 2, newName);
+ maybeAppendComponent(components, 0, newName);
+ maybeAppendComponent(components, 4, newName);
+ names[i] = newName.toString().trim();
+ }
+ }
+ }
+
+ private static void maybeAppendComponent(String[] components, int i, StringBuffer newName) {
+ if (components[i] != null) {
+ newName.append(' ');
+ newName.append(components[i]);
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/VEventResultParser.java b/src/com/google/zxing/client/result/VEventResultParser.java
new file mode 100644
index 0000000..2dd3ac6
--- /dev/null
+++ b/src/com/google/zxing/client/result/VEventResultParser.java
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+/**
+ * Partially implements the iCalendar format's "VEVENT" format for specifying a
+ * calendar event. See RFC 2445. This supports SUMMARY, LOCATION, DTSTART and DTEND fields.
+ *
+ * @author Sean Owen
+ */
+final class VEventResultParser extends ResultParser {
+
+ private VEventResultParser() {
+ }
+
+ public static CalendarParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (rawText == null) {
+ return null;
+ }
+ int vEventStart = rawText.indexOf("BEGIN:VEVENT");
+ if (vEventStart < 0) {
+ return null;
+ }
+
+ String summary = VCardResultParser.matchSingleVCardPrefixedField("SUMMARY", rawText, true);
+ String start = VCardResultParser.matchSingleVCardPrefixedField("DTSTART", rawText, true);
+ String end = VCardResultParser.matchSingleVCardPrefixedField("DTEND", rawText, true);
+ String location = VCardResultParser.matchSingleVCardPrefixedField("LOCATION", rawText, true);
+ String description = VCardResultParser.matchSingleVCardPrefixedField("DESCRIPTION", rawText, true);
+ try {
+ return new CalendarParsedResult(summary, start, end, location, null, description);
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/WifiParsedResult.java b/src/com/google/zxing/client/result/WifiParsedResult.java
new file mode 100644
index 0000000..21fa03e
--- /dev/null
+++ b/src/com/google/zxing/client/result/WifiParsedResult.java
@@ -0,0 +1,53 @@
+/*
+ * 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;
+
+ public WifiParsedResult(String networkEncryption, String ssid, String password) {
+ super(ParsedResultType.WIFI);
+ this.ssid = ssid;
+ this.networkEncryption = networkEncryption;
+ this.password = password;
+ }
+
+ public String getSsid() {
+ return ssid;
+ }
+
+ public String getNetworkEncryption() {
+ return networkEncryption;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public String getDisplayResult() {
+ StringBuffer result = new StringBuffer(80);
+ maybeAppend(ssid, result);
+ maybeAppend(networkEncryption, result);
+ maybeAppend(password, result);
+ return result.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/WifiResultParser.java b/src/com/google/zxing/client/result/WifiResultParser.java
new file mode 100644
index 0000000..32bf856
--- /dev/null
+++ b/src/com/google/zxing/client/result/WifiResultParser.java
@@ -0,0 +1,50 @@
+/*
+ * 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:
+ * WIFI:T:WPA;S:mynetwork;P:mypass;;
+ *
+ * The fields can come in any order, and there should be tests to see
+ * if we can parse them all correctly.
+ *
+ * @author Vikram Aggarwal
+ */
+final class WifiResultParser extends ResultParser {
+
+ private WifiResultParser() {
+ }
+
+ public static WifiParsedResult parse(Result result) {
+ String rawText = result.getText();
+
+ if (rawText == null || !rawText.startsWith("WIFI:")) {
+ return null;
+ }
+
+ // Don't remove leading or trailing whitespace
+ boolean trim = false;
+ String ssid = matchSinglePrefixedField("S:", rawText, ';', trim);
+ String pass = matchSinglePrefixedField("P:", rawText, ';', trim);
+ String type = matchSinglePrefixedField("T:", rawText, ';', trim);
+
+ return new WifiParsedResult(type, ssid, pass);
+ }
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/optional/AbstractNDEFResultParser.java b/src/com/google/zxing/client/result/optional/AbstractNDEFResultParser.java
new file mode 100644
index 0000000..59cb0b2
--- /dev/null
+++ b/src/com/google/zxing/client/result/optional/AbstractNDEFResultParser.java
@@ -0,0 +1,45 @@
+/*
+ * 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.optional;
+
+import com.google.zxing.client.result.ResultParser;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Superclass for classes encapsulating results in the NDEF format.
+ * See http://www.nfc-forum.org/specs/.
+ *
+ * This code supports a limited subset of NDEF messages, ones that are plausibly
+ * useful in 2D barcode formats. This generally includes 1-record messages, no chunking,
+ * "short record" syntax, no ID field.
+ *
+ * @author Sean Owen
+ */
+abstract class AbstractNDEFResultParser extends ResultParser {
+
+ static String bytesToString(byte[] bytes, int offset, int length, String encoding) {
+ try {
+ return new String(bytes, offset, length, encoding);
+ } catch (UnsupportedEncodingException uee) {
+ // This should only be used when 'encoding' is an encoding that must necessarily
+ // be supported by the JVM, like UTF-8
+ throw new RuntimeException("Platform does not support required encoding: " + uee);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/optional/NDEFRecord.java b/src/com/google/zxing/client/result/optional/NDEFRecord.java
new file mode 100644
index 0000000..5a51407
--- /dev/null
+++ b/src/com/google/zxing/client/result/optional/NDEFRecord.java
@@ -0,0 +1,87 @@
+/*
+ * 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.optional;
+
+/**
+ * Represents a record in an NDEF message. This class only supports certain types
+ * of records -- namely, non-chunked records, where ID length is omitted, and only
+ * "short records".
+ *
+ * @author Sean Owen
+ */
+final class NDEFRecord {
+
+ private static final int SUPPORTED_HEADER_MASK = 0x3F; // 0 0 1 1 1 111 (the bottom 6 bits matter)
+ private static final int SUPPORTED_HEADER = 0x11; // 0 0 0 1 0 001
+
+ public static final String TEXT_WELL_KNOWN_TYPE = "T";
+ public static final String URI_WELL_KNOWN_TYPE = "U";
+ public static final String SMART_POSTER_WELL_KNOWN_TYPE = "Sp";
+ public static final String ACTION_WELL_KNOWN_TYPE = "act";
+
+ private final int header;
+ private final String type;
+ private final byte[] payload;
+ private final int totalRecordLength;
+
+ private NDEFRecord(int header, String type, byte[] payload, int totalRecordLength) {
+ this.header = header;
+ this.type = type;
+ this.payload = payload;
+ this.totalRecordLength = totalRecordLength;
+ }
+
+ static NDEFRecord readRecord(byte[] bytes, int offset) {
+ int header = bytes[offset] & 0xFF;
+ // Does header match what we support in the bits we care about?
+ // XOR figures out where we differ, and if any of those are in the mask, fail
+ if (((header ^ SUPPORTED_HEADER) & SUPPORTED_HEADER_MASK) != 0) {
+ return null;
+ }
+ int typeLength = bytes[offset + 1] & 0xFF;
+
+ int payloadLength = bytes[offset + 2] & 0xFF;
+
+ String type = AbstractNDEFResultParser.bytesToString(bytes, offset + 3, typeLength, "US-ASCII");
+
+ byte[] payload = new byte[payloadLength];
+ System.arraycopy(bytes, offset + 3 + typeLength, payload, 0, payloadLength);
+
+ return new NDEFRecord(header, type, payload, 3 + typeLength + payloadLength);
+ }
+
+ boolean isMessageBegin() {
+ return (header & 0x80) != 0;
+ }
+
+ boolean isMessageEnd() {
+ return (header & 0x40) != 0;
+ }
+
+ String getType() {
+ return type;
+ }
+
+ byte[] getPayload() {
+ return payload;
+ }
+
+ int getTotalRecordLength() {
+ return totalRecordLength;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/optional/NDEFSmartPosterParsedResult.java b/src/com/google/zxing/client/result/optional/NDEFSmartPosterParsedResult.java
new file mode 100644
index 0000000..2c0607c
--- /dev/null
+++ b/src/com/google/zxing/client/result/optional/NDEFSmartPosterParsedResult.java
@@ -0,0 +1,63 @@
+/*
+ * 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.optional;
+
+import com.google.zxing.client.result.ParsedResult;
+import com.google.zxing.client.result.ParsedResultType;
+
+/**
+ * @author Sean Owen
+ */
+public final class NDEFSmartPosterParsedResult extends ParsedResult {
+
+ public static final int ACTION_UNSPECIFIED = -1;
+ public static final int ACTION_DO = 0;
+ public static final int ACTION_SAVE = 1;
+ public static final int ACTION_OPEN = 2;
+
+ private final String title;
+ private final String uri;
+ private final int action;
+
+ NDEFSmartPosterParsedResult(int action, String uri, String title) {
+ super(ParsedResultType.NDEF_SMART_POSTER);
+ this.action = action;
+ this.uri = uri;
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getURI() {
+ return uri;
+ }
+
+ public int getAction() {
+ return action;
+ }
+
+ public String getDisplayResult() {
+ if (title == null) {
+ return uri;
+ } else {
+ return title + '\n' + uri;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/optional/NDEFSmartPosterResultParser.java b/src/com/google/zxing/client/result/optional/NDEFSmartPosterResultParser.java
new file mode 100644
index 0000000..ac3ea1c
--- /dev/null
+++ b/src/com/google/zxing/client/result/optional/NDEFSmartPosterResultParser.java
@@ -0,0 +1,81 @@
+/*
+ * 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.optional;
+
+import com.google.zxing.Result;
+
+/**
+ * Recognizes an NDEF message that encodes information according to the
+ * "Smart Poster Record Type Definition" specification.
+ *
+ * This actually only supports some parts of the Smart Poster format: title,
+ * URI, and action records. Icon records are not supported because the size
+ * of these records are infeasibly large for barcodes. Size and type records
+ * are not supported. Multiple titles are not supported.
+ *
+ * @author Sean Owen
+ */
+final class NDEFSmartPosterResultParser extends AbstractNDEFResultParser {
+
+ public static NDEFSmartPosterParsedResult parse(Result result) {
+ byte[] bytes = result.getRawBytes();
+ if (bytes == null) {
+ return null;
+ }
+ NDEFRecord headerRecord = NDEFRecord.readRecord(bytes, 0);
+ // Yes, header record starts and ends a message
+ if (headerRecord == null || !headerRecord.isMessageBegin() || !headerRecord.isMessageEnd()) {
+ return null;
+ }
+ if (!headerRecord.getType().equals(NDEFRecord.SMART_POSTER_WELL_KNOWN_TYPE)) {
+ return null;
+ }
+
+ int offset = 0;
+ int recordNumber = 0;
+ NDEFRecord ndefRecord = null;
+ byte[] payload = headerRecord.getPayload();
+ int action = NDEFSmartPosterParsedResult.ACTION_UNSPECIFIED;
+ String title = null;
+ String uri = null;
+
+ while (offset < payload.length && (ndefRecord = NDEFRecord.readRecord(payload, offset)) != null) {
+ if (recordNumber == 0 && !ndefRecord.isMessageBegin()) {
+ return null;
+ }
+
+ String type = ndefRecord.getType();
+ if (NDEFRecord.TEXT_WELL_KNOWN_TYPE.equals(type)) {
+ String[] languageText = NDEFTextResultParser.decodeTextPayload(ndefRecord.getPayload());
+ title = languageText[1];
+ } else if (NDEFRecord.URI_WELL_KNOWN_TYPE.equals(type)) {
+ uri = NDEFURIResultParser.decodeURIPayload(ndefRecord.getPayload());
+ } else if (NDEFRecord.ACTION_WELL_KNOWN_TYPE.equals(type)) {
+ action = ndefRecord.getPayload()[0];
+ }
+ recordNumber++;
+ offset += ndefRecord.getTotalRecordLength();
+ }
+
+ if (recordNumber == 0 || (ndefRecord != null && !ndefRecord.isMessageEnd())) {
+ return null;
+ }
+
+ return new NDEFSmartPosterParsedResult(action, uri, title);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/optional/NDEFTextResultParser.java b/src/com/google/zxing/client/result/optional/NDEFTextResultParser.java
new file mode 100644
index 0000000..40651c5
--- /dev/null
+++ b/src/com/google/zxing/client/result/optional/NDEFTextResultParser.java
@@ -0,0 +1,57 @@
+/*
+ * 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.optional;
+
+import com.google.zxing.Result;
+import com.google.zxing.client.result.TextParsedResult;
+
+/**
+ * Recognizes an NDEF message that encodes text according to the
+ * "Text Record Type Definition" specification.
+ *
+ * @author Sean Owen
+ */
+final class NDEFTextResultParser extends AbstractNDEFResultParser {
+
+ public static TextParsedResult parse(Result result) {
+ byte[] bytes = result.getRawBytes();
+ if (bytes == null) {
+ return null;
+ }
+ NDEFRecord ndefRecord = NDEFRecord.readRecord(bytes, 0);
+ if (ndefRecord == null || !ndefRecord.isMessageBegin() || !ndefRecord.isMessageEnd()) {
+ return null;
+ }
+ if (!ndefRecord.getType().equals(NDEFRecord.TEXT_WELL_KNOWN_TYPE)) {
+ return null;
+ }
+ String[] languageText = decodeTextPayload(ndefRecord.getPayload());
+ return new TextParsedResult(languageText[0], languageText[1]);
+ }
+
+ static String[] decodeTextPayload(byte[] payload) {
+ byte statusByte = payload[0];
+ boolean isUTF16 = (statusByte & 0x80) != 0;
+ int languageLength = statusByte & 0x1F;
+ // language is always ASCII-encoded:
+ String language = bytesToString(payload, 1, languageLength, "US-ASCII");
+ String encoding = isUTF16 ? "UTF-16" : "UTF8";
+ String text = bytesToString(payload, 1 + languageLength, payload.length - languageLength - 1, encoding);
+ return new String[] { language, text };
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/optional/NDEFURIResultParser.java b/src/com/google/zxing/client/result/optional/NDEFURIResultParser.java
new file mode 100644
index 0000000..3532c5b
--- /dev/null
+++ b/src/com/google/zxing/client/result/optional/NDEFURIResultParser.java
@@ -0,0 +1,95 @@
+/*
+ * 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.optional;
+
+import com.google.zxing.Result;
+import com.google.zxing.client.result.URIParsedResult;
+
+/**
+ * Recognizes an NDEF message that encodes a URI according to the
+ * "URI Record Type Definition" specification.
+ *
+ * @author Sean Owen
+ */
+final class NDEFURIResultParser extends AbstractNDEFResultParser {
+
+ private static final String[] URI_PREFIXES = {
+ null,
+ "http://www.",
+ "https://www.",
+ "http://",
+ "https://",
+ "tel:",
+ "mailto:",
+ "ftp://anonymous:anonymous@",
+ "ftp://ftp.",
+ "ftps://",
+ "sftp://",
+ "smb://",
+ "nfs://",
+ "ftp://",
+ "dav://",
+ "news:",
+ "telnet://",
+ "imap:",
+ "rtsp://",
+ "urn:",
+ "pop:",
+ "sip:",
+ "sips:",
+ "tftp:",
+ "btspp://",
+ "btl2cap://",
+ "btgoep://",
+ "tcpobex://",
+ "irdaobex://",
+ "file://",
+ "urn:epc:id:",
+ "urn:epc:tag:",
+ "urn:epc:pat:",
+ "urn:epc:raw:",
+ "urn:epc:",
+ "urn:nfc:",
+ };
+
+ public static URIParsedResult parse(Result result) {
+ byte[] bytes = result.getRawBytes();
+ if (bytes == null) {
+ return null;
+ }
+ NDEFRecord ndefRecord = NDEFRecord.readRecord(bytes, 0);
+ if (ndefRecord == null || !ndefRecord.isMessageBegin() || !ndefRecord.isMessageEnd()) {
+ return null;
+ }
+ if (!ndefRecord.getType().equals(NDEFRecord.URI_WELL_KNOWN_TYPE)) {
+ return null;
+ }
+ String fullURI = decodeURIPayload(ndefRecord.getPayload());
+ return new URIParsedResult(fullURI, null);
+ }
+
+ static String decodeURIPayload(byte[] payload) {
+ int identifierCode = payload[0] & 0xFF;
+ String prefix = null;
+ if (identifierCode < URI_PREFIXES.length) {
+ prefix = URI_PREFIXES[identifierCode];
+ }
+ String restOfURI = bytesToString(payload, 1, payload.length - 1, "UTF8");
+ return prefix == null ? restOfURI : prefix + restOfURI;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/BitArray.java b/src/com/google/zxing/common/BitArray.java
new file mode 100644
index 0000000..bce6cee
--- /dev/null
+++ b/src/com/google/zxing/common/BitArray.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.common;
+
+/**
+ * A simple, fast array of bits, represented compactly by an array of ints internally.
+ *
+ * @author Sean Owen
+ */
+public final class BitArray {
+
+ // TODO: I have changed these members to be public so ProGuard can inline get() and set(). Ideally
+ // they'd be private and we'd use the -allowaccessmodification flag, but Dalvik rejects the
+ // resulting binary at runtime on Android. If we find a solution to this, these should be changed
+ // back to private.
+ public int[] bits;
+ public int size;
+
+ public BitArray() {
+ this.size = 0;
+ this.bits = new int[1];
+ }
+
+ public BitArray(int size) {
+ this.size = size;
+ this.bits = makeArray(size);
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public int getSizeInBytes() {
+ return (size + 7) >> 3;
+ }
+
+ private void ensureCapacity(int size) {
+ if (size > bits.length << 5) {
+ 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 >> 5] & (1 << (i & 0x1F))) != 0;
+ }
+
+ /**
+ * Sets bit i.
+ *
+ * @param i bit to set
+ */
+ public void set(int i) {
+ bits[i >> 5] |= 1 << (i & 0x1F);
+ }
+
+ /**
+ * Flips bit i.
+ *
+ * @param i bit to set
+ */
+ public void flip(int i) {
+ bits[i >> 5] ^= 1 << (i & 0x1F);
+ }
+
+ /**
+ * 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 >> 5] = newBits;
+ }
+
+ /**
+ * 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 >> 5;
+ int lastInt = end >> 5;
+ 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 >> 5] |= (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.
+ */
+ 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.getSize();
+ 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];
+ int size = this.size;
+ for (int i = 0; i < size; i++) {
+ if (get(size - i - 1)) {
+ newBits[i >> 5] |= 1 << (i & 0x1F);
+ }
+ }
+ bits = newBits;
+ }
+
+ private static int[] makeArray(int size) {
+ return new int[(size + 31) >> 5];
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(size);
+ for (int i = 0; i < size; i++) {
+ if ((i & 0x07) == 0) {
+ result.append(' ');
+ }
+ result.append(get(i) ? 'X' : '.');
+ }
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/BitMatrix.java b/src/com/google/zxing/common/BitMatrix.java
new file mode 100644
index 0000000..26cd599
--- /dev/null
+++ b/src/com/google/zxing/common/BitMatrix.java
@@ -0,0 +1,226 @@
+/*
+ * 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;
+
+/**
+ * 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 {
+
+ // TODO: Just like BitArray, these need to be public so ProGuard can inline them.
+ public final int width;
+ public final int height;
+ public final int rowSize;
+ public 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];
+ }
+
+ /**
+ * 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);
+ }
+ int offset = y * rowSize;
+ for (int x = 0; x < rowSize; x++) {
+ row.setBulk(x << 5, bits[offset + x]);
+ }
+ return row;
+ }
+
+ /**
+ * This is useful in detecting a corner of a 'pure' barcode.
+ *
+ * @return {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};
+ }
+
+ /**
+ * @return The width of the matrix
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * @return The height of the matrix
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ public boolean equals(Object o) {
+ if (!(o instanceof BitMatrix)) {
+ return false;
+ }
+ BitMatrix other = (BitMatrix) o;
+ if (width != other.width || height != other.height ||
+ rowSize != other.rowSize || bits.length != other.bits.length) {
+ return false;
+ }
+ for (int i = 0; i < bits.length; i++) {
+ if (bits[i] != other.bits[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public int hashCode() {
+ int hash = width;
+ hash = 31 * hash + width;
+ hash = 31 * hash + height;
+ hash = 31 * hash + rowSize;
+ for (int i = 0; i < bits.length; i++) {
+ hash = 31 * hash + bits[i];
+ }
+ return hash;
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(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();
+ }
+
+}
diff --git a/src/com/google/zxing/common/BitSource.java b/src/com/google/zxing/common/BitSource.java
new file mode 100644
index 0000000..e5d7b1c
--- /dev/null
+++ b/src/com/google/zxing/common/BitSource.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+ }
+
+ /**
+ * @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]
+ */
+ public int readBits(int numBits) {
+ if (numBits < 1 || numBits > 32) {
+ throw new IllegalArgumentException();
+ }
+
+ 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/src/com/google/zxing/common/CharacterSetECI.java b/src/com/google/zxing/common/CharacterSetECI.java
new file mode 100644
index 0000000..26a1518
--- /dev/null
+++ b/src/com/google/zxing/common/CharacterSetECI.java
@@ -0,0 +1,110 @@
+/*
+ * 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 java.util.Hashtable;
+
+/**
+ * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1
+ * of ISO 18004.
+ *
+ * @author Sean Owen
+ */
+public final class CharacterSetECI extends ECI {
+
+ private static Hashtable VALUE_TO_ECI;
+ private static Hashtable NAME_TO_ECI;
+
+ private static void initialize() {
+ VALUE_TO_ECI = new Hashtable(29);
+ NAME_TO_ECI = new Hashtable(29);
+ // TODO figure out if these values are even right!
+ addCharacterSet(0, "Cp437");
+ addCharacterSet(1, new String[] {"ISO8859_1", "ISO-8859-1"});
+ addCharacterSet(2, "Cp437");
+ addCharacterSet(3, new String[] {"ISO8859_1", "ISO-8859-1"});
+ addCharacterSet(4, "ISO8859_2");
+ addCharacterSet(5, "ISO8859_3");
+ addCharacterSet(6, "ISO8859_4");
+ addCharacterSet(7, "ISO8859_5");
+ addCharacterSet(8, "ISO8859_6");
+ addCharacterSet(9, "ISO8859_7");
+ addCharacterSet(10, "ISO8859_8");
+ addCharacterSet(11, "ISO8859_9");
+ addCharacterSet(12, "ISO8859_10");
+ addCharacterSet(13, "ISO8859_11");
+ addCharacterSet(15, "ISO8859_13");
+ addCharacterSet(16, "ISO8859_14");
+ addCharacterSet(17, "ISO8859_15");
+ addCharacterSet(18, "ISO8859_16");
+ addCharacterSet(20, new String[] {"SJIS", "Shift_JIS"});
+ }
+
+ private final String encodingName;
+
+ private CharacterSetECI(int value, String encodingName) {
+ super(value);
+ this.encodingName = encodingName;
+ }
+
+ public String getEncodingName() {
+ return encodingName;
+ }
+
+ private static void addCharacterSet(int value, String encodingName) {
+ CharacterSetECI eci = new CharacterSetECI(value, encodingName);
+ VALUE_TO_ECI.put(new Integer(value), eci); // can't use valueOf
+ NAME_TO_ECI.put(encodingName, eci);
+ }
+
+ private static void addCharacterSet(int value, String[] encodingNames) {
+ CharacterSetECI eci = new CharacterSetECI(value, encodingNames[0]);
+ VALUE_TO_ECI.put(new Integer(value), eci); // can't use valueOf
+ for (int i = 0; i < encodingNames.length; i++) {
+ NAME_TO_ECI.put(encodingNames[i], eci);
+ }
+ }
+
+ /**
+ * @param value character set ECI value
+ * @return {@link CharacterSetECI} representing ECI of given value, or null if it is legal but
+ * unsupported
+ * @throws IllegalArgumentException if ECI value is invalid
+ */
+ public static CharacterSetECI getCharacterSetECIByValue(int value) {
+ if (VALUE_TO_ECI == null) {
+ initialize();
+ }
+ if (value < 0 || value >= 900) {
+ throw new IllegalArgumentException("Bad ECI value: " + value);
+ }
+ return (CharacterSetECI) VALUE_TO_ECI.get(new Integer(value));
+ }
+
+ /**
+ * @param name character set ECI encoding name
+ * @return {@link CharacterSetECI} representing ECI for character encoding, or null if it is legal
+ * but unsupported
+ */
+ public static CharacterSetECI getCharacterSetECIByName(String name) {
+ if (NAME_TO_ECI == null) {
+ initialize();
+ }
+ return (CharacterSetECI) NAME_TO_ECI.get(name);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/Collections.java b/src/com/google/zxing/common/Collections.java
new file mode 100644
index 0000000..319ebfe
--- /dev/null
+++ b/src/com/google/zxing/common/Collections.java
@@ -0,0 +1,53 @@
+/*
+ * 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.Vector;
+
+/**
+ * This is basically a substitute for java.util.Collections
, which is not
+ * present in MIDP 2.0 / CLDC 1.1.
+ *
+ * @author Sean Owen
+ */
+public final class Collections {
+
+ private Collections() {
+ }
+
+ /**
+ * Sorts its argument (destructively) using insert sort; in the context of this package
+ * insertion sort is simple and efficient given its relatively small inputs.
+ *
+ * @param vector vector to sort
+ * @param comparator comparator to define sort ordering
+ */
+ public static void insertionSort(Vector vector, Comparator comparator) {
+ int max = vector.size();
+ for (int i = 1; i < max; i++) {
+ Object value = vector.elementAt(i);
+ int j = i - 1;
+ Object valueB;
+ while (j >= 0 && comparator.compare((valueB = vector.elementAt(j)), value) > 0) {
+ vector.setElementAt(valueB, j + 1);
+ j--;
+ }
+ vector.setElementAt(value, j + 1);
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/common/Comparator.java b/src/com/google/zxing/common/Comparator.java
new file mode 100644
index 0000000..e1be15e
--- /dev/null
+++ b/src/com/google/zxing/common/Comparator.java
@@ -0,0 +1,27 @@
+/*
+ * 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 is merely a clone of Comparator
since it is not available in
+ * CLDC 1.1 / MIDP 2.0.
+ */
+public interface Comparator {
+
+ int compare(Object o1, Object o2);
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/DecoderResult.java b/src/com/google/zxing/common/DecoderResult.java
new file mode 100644
index 0000000..ce0e3c5
--- /dev/null
+++ b/src/com/google/zxing/common/DecoderResult.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.common;
+
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import java.util.Vector;
+
+/**
+ * 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 Vector byteSegments;
+ private final ErrorCorrectionLevel ecLevel;
+
+ public DecoderResult(byte[] rawBytes, String text, Vector byteSegments, ErrorCorrectionLevel ecLevel) {
+ if (rawBytes == null && text == null) {
+ throw new IllegalArgumentException();
+ }
+ this.rawBytes = rawBytes;
+ this.text = text;
+ this.byteSegments = byteSegments;
+ this.ecLevel = ecLevel;
+ }
+
+ public byte[] getRawBytes() {
+ return rawBytes;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public Vector getByteSegments() {
+ return byteSegments;
+ }
+
+ public ErrorCorrectionLevel getECLevel() {
+ return ecLevel;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/DefaultGridSampler.java b/src/com/google/zxing/common/DefaultGridSampler.java
new file mode 100644
index 0000000..9a2a683
--- /dev/null
+++ b/src/com/google/zxing/common/DefaultGridSampler.java
@@ -0,0 +1,81 @@
+/*
+ * 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 {
+
+ public BitMatrix sampleGrid(BitMatrix image,
+ int dimension,
+ 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, dimension, transform);
+ }
+
+ public BitMatrix sampleGrid(BitMatrix image,
+ int dimension,
+ PerspectiveTransform transform) throws NotFoundException {
+ BitMatrix bits = new BitMatrix(dimension);
+ float[] points = new float[dimension << 1];
+ for (int y = 0; y < dimension; 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/src/com/google/zxing/common/DetectorResult.java b/src/com/google/zxing/common/DetectorResult.java
new file mode 100644
index 0000000..5e3786a
--- /dev/null
+++ b/src/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 final class DetectorResult {
+
+ private final BitMatrix bits;
+ private final ResultPoint[] points;
+
+ public DetectorResult(BitMatrix bits, ResultPoint[] points) {
+ this.bits = bits;
+ this.points = points;
+ }
+
+ public BitMatrix getBits() {
+ return bits;
+ }
+
+ public ResultPoint[] getPoints() {
+ return points;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/ECI.java b/src/com/google/zxing/common/ECI.java
new file mode 100644
index 0000000..9d725db
--- /dev/null
+++ b/src/com/google/zxing/common/ECI.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.common;
+
+/**
+ * Superclass of classes encapsulating types ECIs, according to "Extended Channel Interpretations"
+ * 5.3 of ISO 18004.
+ *
+ * @author Sean Owen
+ */
+public abstract class ECI {
+
+ private final int value;
+
+ ECI(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ /**
+ * @param value ECI value
+ * @return {@link ECI} representing ECI of given value, or null if it is legal but unsupported
+ * @throws IllegalArgumentException if ECI value is invalid
+ */
+ public static ECI getECIByValue(int value) {
+ if (value < 0 || value > 999999) {
+ throw new IllegalArgumentException("Bad ECI value: " + value);
+ }
+ if (value < 900) { // Character set ECIs use 000000 - 000899
+ return CharacterSetECI.getCharacterSetECIByValue(value);
+ }
+ return null;
+ }
+
+}
diff --git a/src/com/google/zxing/common/GlobalHistogramBinarizer.java b/src/com/google/zxing/common/GlobalHistogramBinarizer.java
new file mode 100644
index 0000000..144c099
--- /dev/null
+++ b/src/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 byte[] luminances = null;
+ private int[] buckets = null;
+
+ public GlobalHistogramBinarizer(LuminanceSource source) {
+ super(source);
+ }
+
+ // Applies simple sharpening to the row data to improve performance of the 1D Readers.
+ 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.
+ 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;
+ }
+
+ public Binarizer createBinarizer(LuminanceSource source) {
+ return new GlobalHistogramBinarizer(source);
+ }
+
+ private void initArrays(int luminanceSize) {
+ if (luminances == null || luminances.length < luminanceSize) {
+ luminances = new byte[luminanceSize];
+ }
+ if (buckets == null) {
+ buckets = new int[LUMINANCE_BUCKETS];
+ } else {
+ 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.
+ // TODO: It might be worth comparing the brightest and darkest pixels seen, rather than the
+ // two peaks, to determine the contrast.
+ 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/src/com/google/zxing/common/GridSampler.java b/src/com/google/zxing/common/GridSampler.java
new file mode 100644
index 0000000..3b55eb6
--- /dev/null
+++ b/src/com/google/zxing/common/GridSampler.java
@@ -0,0 +1,171 @@
+/*
+ * 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 {@link 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) {
+ if (newGridSampler == null) {
+ throw new IllegalArgumentException();
+ }
+ gridSampler = newGridSampler;
+ }
+
+ /**
+ * @return the current implementation of {@link GridSampler}
+ */
+ public static GridSampler getInstance() {
+ return gridSampler;
+ }
+
+ /**
+ * Samples an image for a square matrix of bits of the given dimension. This is used to extract
+ * the black/white modules of a 2D barcode like a QR Code found in an image. Because this barcode
+ * may be rotated or perspective-distorted, the caller supplies four points in the source image
+ * that define known points in the barcode, so that the image may be sampled appropriately.
+ *
+ * The last eight "from" parameters are four X/Y coordinate pairs of locations of points in
+ * the image that define some significant points in the image to be sample. For example,
+ * these may be the location of finder pattern in a QR Code.
+ *
+ * The first eight "to" parameters are four X/Y coordinate pairs measured in the destination
+ * {@link BitMatrix}, from the top left, where the known points in the image given by the "from"
+ * parameters map to.
+ *
+ * These 16 parameters define the transformation needed to sample the image.
+ *
+ * @param image image to sample
+ * @param dimension width/height of {@link BitMatrix} to sample from image
+ * @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 dimension,
+ 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 BitMatrix sampleGrid(BitMatrix image,
+ int dimension,
+ PerspectiveTransform transform) throws NotFoundException {
+ throw new IllegalStateException(); // Can't use UnsupportedOperationException
+ }
+
+
+ /**
+ * 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/src/com/google/zxing/common/HybridBinarizer.java b/src/com/google/zxing/common/HybridBinarizer.java
new file mode 100644
index 0000000..e89b06f
--- /dev/null
+++ b/src/com/google/zxing/common/HybridBinarizer.java
@@ -0,0 +1,185 @@
+/*
+ * 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 MINIMUM_DIMENSION = 40;
+
+ private BitMatrix matrix = null;
+
+ public HybridBinarizer(LuminanceSource source) {
+ super(source);
+ }
+
+ public BitMatrix getBlackMatrix() throws NotFoundException {
+ binarizeEntireImage();
+ return matrix;
+ }
+
+ public Binarizer createBinarizer(LuminanceSource source) {
+ return new HybridBinarizer(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.
+ private void binarizeEntireImage() throws NotFoundException {
+ if (matrix == null) {
+ LuminanceSource source = getLuminanceSource();
+ if (source.getWidth() >= MINIMUM_DIMENSION && source.getHeight() >= MINIMUM_DIMENSION) {
+ byte[] luminances = source.getMatrix();
+ int width = source.getWidth();
+ int height = source.getHeight();
+ int subWidth = width >> 3;
+ if ((width & 0x07) != 0) {
+ subWidth++;
+ }
+ int subHeight = height >> 3;
+ if ((height & 0x07) != 0) {
+ subHeight++;
+ }
+ int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height);
+
+ matrix = new BitMatrix(width, height);
+ calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, matrix);
+ } else {
+ // If the image is too small, fall back to the global histogram approach.
+ matrix = super.getBlackMatrix();
+ }
+ }
+ }
+
+ // For each 8x8 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 8 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 << 3;
+ if ((yoffset + 8) >= height) {
+ yoffset = height - 8;
+ }
+ for (int x = 0; x < subWidth; x++) {
+ int xoffset = x << 3;
+ if ((xoffset + 8) >= width) {
+ xoffset = width - 8;
+ }
+ int left = (x > 1) ? x : 2;
+ left = (left < subWidth - 2) ? left : subWidth - 3;
+ int top = (y > 1) ? y : 2;
+ top = (top < subHeight - 2) ? top : subHeight - 3;
+ int sum = 0;
+ for (int z = -2; z <= 2; z++) {
+ int[] blackRow = blackPoints[top + z];
+ sum += blackRow[left - 2];
+ sum += blackRow[left - 1];
+ sum += blackRow[left];
+ sum += blackRow[left + 1];
+ sum += blackRow[left + 2];
+ }
+ int average = sum / 25;
+ threshold8x8Block(luminances, xoffset, yoffset, average, width, matrix);
+ }
+ }
+ }
+
+ // Applies a single threshold to an 8x8 block of pixels.
+ private static void threshold8x8Block(byte[] luminances, int xoffset, int yoffset, int threshold,
+ int stride, BitMatrix matrix) {
+ for (int y = 0; y < 8; y++) {
+ int offset = (yoffset + y) * stride + xoffset;
+ for (int x = 0; x < 8; x++) {
+ int pixel = luminances[offset + x] & 0xff;
+ if (pixel < threshold) {
+ matrix.set(xoffset + x, yoffset + y);
+ }
+ }
+ }
+ }
+
+ // Calculates a single black point for each 8x8 block of pixels and saves it away.
+ 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 << 3;
+ if ((yoffset + 8) >= height) {
+ yoffset = height - 8;
+ }
+ for (int x = 0; x < subWidth; x++) {
+ int xoffset = x << 3;
+ if ((xoffset + 8) >= width) {
+ xoffset = width - 8;
+ }
+ int sum = 0;
+ int min = 255;
+ int max = 0;
+ for (int yy = 0; yy < 8; yy++) {
+ int offset = (yoffset + yy) * width + xoffset;
+ for (int xx = 0; xx < 8; xx++) {
+ int pixel = luminances[offset + xx] & 0xff;
+ sum += pixel;
+ if (pixel < min) {
+ min = pixel;
+ }
+ if (pixel > max) {
+ max = pixel;
+ }
+ }
+ }
+
+ // If the contrast is inadequate, use half the minimum, so that this block will be
+ // treated as part of the white background, but won't drag down neighboring blocks
+ // too much.
+ int average;
+ if (max - min > 24) {
+ average = sum >> 6;
+ } else {
+ // When min == max == 0, let average be 1 so all is black
+ average = max == 0 ? 1 : min >> 1;
+ }
+ blackPoints[y][x] = average;
+ }
+ }
+ return blackPoints;
+ }
+
+}
diff --git a/src/com/google/zxing/common/PerspectiveTransform.java b/src/com/google/zxing/common/PerspectiveTransform.java
new file mode 100644
index 0000000..19743fc
--- /dev/null
+++ b/src/com/google/zxing/common/PerspectiveTransform.java
@@ -0,0 +1,148 @@
+/*
+ * 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, a12, a13, a21, a22, a23, a31, a32, 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;
+ }
+ }
+
+ /** Convenience method, not optimized for performance. */
+ 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 dy2 = y3 - y2;
+ float dy3 = y0 - y1 + y2 - y3;
+ if (dy2 == 0.0f && dy3 == 0.0f) {
+ 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 dx3 = x0 - x1 + x2 - x3;
+ float dy1 = y1 - 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/src/com/google/zxing/common/StringUtils.java b/src/com/google/zxing/common/StringUtils.java
new file mode 100644
index 0000000..d3a7c2b
--- /dev/null
+++ b/src/com/google/zxing/common/StringUtils.java
@@ -0,0 +1,191 @@
+/*
+ * 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.util.Hashtable;
+
+import com.google.zxing.DecodeHintType;
+
+/**
+ * Common string-related functions.
+ *
+ * @author Sean Owen
+ */
+public final class StringUtils {
+
+ private static final String PLATFORM_DEFAULT_ENCODING =
+ System.getProperty("file.encoding");
+ public static final String SHIFT_JIS = "SJIS";
+ 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, Hashtable hints) {
+ if (hints != null) {
+ String characterSet = (String) hints.get(DecodeHintType.CHARACTER_SET);
+ if (characterSet != null) {
+ return characterSet;
+ }
+ }
+ // Does it start with the UTF-8 byte order mark? then guess it's UTF-8
+ if (bytes.length > 3 &&
+ bytes[0] == (byte) 0xEF &&
+ bytes[1] == (byte) 0xBB &&
+ bytes[2] == (byte) 0xBF) {
+ return UTF8;
+ }
+ // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
+ // which should be by far the most common encodings. ISO-8859-1
+ // should not have bytes in the 0x80 - 0x9F range, while Shift_JIS
+ // uses this as a first byte of a two-byte character. If we see this
+ // followed by a valid second byte in Shift_JIS, assume it is Shift_JIS.
+ // If we see something else in that second byte, we'll make the risky guess
+ // that it's UTF-8.
+ int length = bytes.length;
+ boolean canBeISO88591 = true;
+ boolean canBeShiftJIS = true;
+ boolean canBeUTF8 = true;
+ int utf8BytesLeft = 0;
+ int maybeDoubleByteCount = 0;
+ int maybeSingleByteKatakanaCount = 0;
+ boolean sawLatin1Supplement = false;
+ boolean sawUTF8Start = false;
+ boolean lastWasPossibleDoubleByteStart = false;
+
+ for (int i = 0;
+ i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8);
+ i++) {
+
+ int value = bytes[i] & 0xFF;
+
+ // UTF-8 stuff
+ if (value >= 0x80 && value <= 0xBF) {
+ if (utf8BytesLeft > 0) {
+ utf8BytesLeft--;
+ }
+ } else {
+ if (utf8BytesLeft > 0) {
+ canBeUTF8 = false;
+ }
+ if (value >= 0xC0 && value <= 0xFD) {
+ sawUTF8Start = true;
+ int valueCopy = value;
+ while ((valueCopy & 0x40) != 0) {
+ utf8BytesLeft++;
+ valueCopy <<= 1;
+ }
+ }
+ }
+
+ // ISO-8859-1 stuff
+
+ if ((value == 0xC2 || value == 0xC3) && i < length - 1) {
+ // This is really a poor hack. The slightly more exotic characters people might want to put in
+ // a QR Code, by which I mean the Latin-1 supplement characters (e.g. u-umlaut) have encodings
+ // that start with 0xC2 followed by [0xA0,0xBF], or start with 0xC3 followed by [0x80,0xBF].
+ int nextValue = bytes[i + 1] & 0xFF;
+ if (nextValue <= 0xBF &&
+ ((value == 0xC2 && nextValue >= 0xA0) || (value == 0xC3 && nextValue >= 0x80))) {
+ sawLatin1Supplement = true;
+ }
+ }
+ if (value >= 0x7F && value <= 0x9F) {
+ canBeISO88591 = false;
+ }
+
+ // Shift_JIS stuff
+
+ if (value >= 0xA1 && value <= 0xDF) {
+ // count the number of characters that might be a Shift_JIS single-byte Katakana character
+ if (!lastWasPossibleDoubleByteStart) {
+ maybeSingleByteKatakanaCount++;
+ }
+ }
+ if (!lastWasPossibleDoubleByteStart &&
+ ((value >= 0xF0 && value <= 0xFF) || value == 0x80 || value == 0xA0)) {
+ canBeShiftJIS = false;
+ }
+ if (((value >= 0x81 && value <= 0x9F) || (value >= 0xE0 && value <= 0xEF))) {
+ // These start double-byte characters in Shift_JIS. Let's see if it's followed by a valid
+ // second byte.
+ if (lastWasPossibleDoubleByteStart) {
+ // If we just checked this and the last byte for being a valid double-byte
+ // char, don't check starting on this byte. If this and the last byte
+ // formed a valid pair, then this shouldn't be checked to see if it starts
+ // a double byte pair of course.
+ lastWasPossibleDoubleByteStart = false;
+ } else {
+ // ... otherwise do check to see if this plus the next byte form a valid
+ // double byte pair encoding a character.
+ lastWasPossibleDoubleByteStart = true;
+ if (i >= bytes.length - 1) {
+ canBeShiftJIS = false;
+ } else {
+ int nextValue = bytes[i + 1] & 0xFF;
+ if (nextValue < 0x40 || nextValue > 0xFC) {
+ canBeShiftJIS = false;
+ } else {
+ maybeDoubleByteCount++;
+ }
+ // There is some conflicting information out there about which bytes can follow which in
+ // double-byte Shift_JIS characters. The rule above seems to be the one that matches practice.
+ }
+ }
+ } else {
+ lastWasPossibleDoubleByteStart = false;
+ }
+ }
+ if (utf8BytesLeft > 0) {
+ canBeUTF8 = false;
+ }
+
+ // Easy -- if assuming Shift_JIS and no evidence it can't be, done
+ if (canBeShiftJIS && ASSUME_SHIFT_JIS) {
+ return SHIFT_JIS;
+ }
+ if (canBeUTF8 && sawUTF8Start) {
+ return UTF8;
+ }
+ // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough. The crude heuristic is:
+ // - If we saw
+ // - at least 3 bytes that starts a double-byte value (bytes that are rare in ISO-8859-1), or
+ // - over 5% of bytes could be single-byte Katakana (also rare in ISO-8859-1),
+ // - and, saw no sequences that are invalid in Shift_JIS, then we conclude Shift_JIS
+ if (canBeShiftJIS && (maybeDoubleByteCount >= 3 || 20 * maybeSingleByteKatakanaCount > length)) {
+ return SHIFT_JIS;
+ }
+ // Otherwise, we default to ISO-8859-1 unless we know it can't be
+ if (!sawLatin1Supplement && canBeISO88591) {
+ return ISO88591;
+ }
+ // Otherwise, we take a wild guess with platform encoding
+ return PLATFORM_DEFAULT_ENCODING;
+ }
+
+}
diff --git a/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java b/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java
new file mode 100644
index 0000000..950a223
--- /dev/null
+++ b/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java
@@ -0,0 +1,209 @@
+/*
+ * 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/src/com/google/zxing/common/detector/WhiteRectangleDetector.java b/src/com/google/zxing/common/detector/WhiteRectangleDetector.java
new file mode 100644
index 0000000..93af0bc
--- /dev/null
+++ b/src/com/google/zxing/common/detector/WhiteRectangleDetector.java
@@ -0,0 +1,316 @@
+/*
+ * 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 = 40;
+ private static final int CORR = 1;
+
+ private final BitMatrix image;
+ private final int height;
+ private final int width;
+
+ public WhiteRectangleDetector(BitMatrix image) {
+ this.image = image;
+ height = image.getHeight();
+ width = image.getWidth();
+ }
+
+ /**
+ *
+ * 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 = (width - INIT_SIZE) >> 1;
+ int right = (width + INIT_SIZE) >> 1;
+ int up = (height - INIT_SIZE) >> 1;
+ int down = (height + INIT_SIZE) >> 1;
+ boolean sizeExceeded = false;
+ boolean aBlackPointFoundOnBorder = true;
+ boolean atLeastOneBlackPointFoundOnBorder = false;
+
+ while (aBlackPointFoundOnBorder) {
+
+ aBlackPointFoundOnBorder = false;
+
+ // .....
+ // . |
+ // .....
+ boolean rightBorderNotWhite = true;
+ while (rightBorderNotWhite && right < width) {
+ rightBorderNotWhite = containsBlackPoint(up, down, right, false);
+ if (rightBorderNotWhite) {
+ right++;
+ aBlackPointFoundOnBorder = true;
+ }
+ }
+
+ if (right >= width) {
+ sizeExceeded = true;
+ break;
+ }
+
+ // .....
+ // . .
+ // .___.
+ boolean bottomBorderNotWhite = true;
+ while (bottomBorderNotWhite && down < height) {
+ bottomBorderNotWhite = containsBlackPoint(left, right, down, true);
+ if (bottomBorderNotWhite) {
+ down++;
+ aBlackPointFoundOnBorder = true;
+ }
+ }
+
+ if (down >= height) {
+ sizeExceeded = true;
+ break;
+ }
+
+ // .....
+ // | .
+ // .....
+ boolean leftBorderNotWhite = true;
+ while (leftBorderNotWhite && left >= 0) {
+ leftBorderNotWhite = containsBlackPoint(up, down, left, false);
+ if (leftBorderNotWhite) {
+ left--;
+ aBlackPointFoundOnBorder = true;
+ }
+ }
+
+ if (left < 0) {
+ sizeExceeded = true;
+ break;
+ }
+
+ // .___.
+ // . .
+ // .....
+ boolean topBorderNotWhite = true;
+ while (topBorderNotWhite && up >= 0) {
+ topBorderNotWhite = containsBlackPoint(left, right, up, true);
+ if (topBorderNotWhite) {
+ up--;
+ aBlackPointFoundOnBorder = true;
+ }
+ }
+
+ 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();
+ }
+ }
+
+ /**
+ * Ends up being a bit faster than Math.round(). This merely rounds its
+ * argument to the nearest int, where x.5 rounds up.
+ */
+ private static int round(float d) {
+ return (int) (d + 0.5f);
+ }
+
+ private ResultPoint getBlackPointOnSegment(float aX, float aY, float bX, float bY) {
+ int dist = distanceL2(aX, aY, bX, bY);
+ float xStep = (bX - aX) / dist;
+ float yStep = (bY - aY) / dist;
+
+ for (int i = 0; i < dist; i++) {
+ int x = round(aX + i * xStep);
+ int y = round(aY + i * yStep);
+ if (image.get(x, y)) {
+ return new ResultPoint(x, y);
+ }
+ }
+ return null;
+ }
+
+ private static int distanceL2(float aX, float aY, float bX, float bY) {
+ float xDiff = aX - bX;
+ float yDiff = aY - bY;
+ return round((float) Math.sqrt(xDiff * xDiff + yDiff * yDiff));
+ }
+
+ /**
+ * 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) {
+ 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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/reedsolomon/GF256.java b/src/com/google/zxing/common/reedsolomon/GF256.java
new file mode 100644
index 0000000..9c58275
--- /dev/null
+++ b/src/com/google/zxing/common/reedsolomon/GF256.java
@@ -0,0 +1,139 @@
+/*
+ * 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 Field GF(256). Operations use a given primitive polynomial in calculations.
+ *
+ * Throughout this package, elements of GF(256) are represented as an int
+ * for convenience and speed (but at the cost of memory).
+ * Only the bottom 8 bits are really used.
+ *
+ * @author Sean Owen
+ */
+public final class GF256 {
+
+ public static final GF256 QR_CODE_FIELD = new GF256(0x011D); // x^8 + x^4 + x^3 + x^2 + 1
+ public static final GF256 DATA_MATRIX_FIELD = new GF256(0x012D); // x^8 + x^5 + x^3 + x^2 + 1
+
+ private final int[] expTable;
+ private final int[] logTable;
+ private final GF256Poly zero;
+ private final GF256Poly one;
+
+ /**
+ * Create a representation of GF(256) 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
+ */
+ private GF256(int primitive) {
+ expTable = new int[256];
+ logTable = new int[256];
+ int x = 1;
+ for (int i = 0; i < 256; i++) {
+ expTable[i] = x;
+ x <<= 1; // x = x * 2; we're assuming the generator alpha is 2
+ if (x >= 0x100) {
+ x ^= primitive;
+ }
+ }
+ for (int i = 0; i < 255; i++) {
+ logTable[expTable[i]] = i;
+ }
+ // logTable[0] == 0 but this should never be used
+ zero = new GF256Poly(this, new int[]{0});
+ one = new GF256Poly(this, new int[]{1});
+ }
+
+ GF256Poly getZero() {
+ return zero;
+ }
+
+ GF256Poly getOne() {
+ return one;
+ }
+
+ /**
+ * @return the monomial representing coefficient * x^degree
+ */
+ GF256Poly 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 GF256Poly(this, coefficients);
+ }
+
+ /**
+ * Implements both addition and subtraction -- they are the same in GF(256).
+ *
+ * @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(256)
+ */
+ int exp(int a) {
+ return expTable[a];
+ }
+
+ /**
+ * @return base 2 log of a in GF(256)
+ */
+ int log(int a) {
+ if (a == 0) {
+ throw new IllegalArgumentException();
+ }
+ return logTable[a];
+ }
+
+ /**
+ * @return multiplicative inverse of a
+ */
+ int inverse(int a) {
+ if (a == 0) {
+ throw new ArithmeticException();
+ }
+ return expTable[255 - logTable[a]];
+ }
+
+ /**
+ * @param a
+ * @param b
+ * @return product of a and b in GF(256)
+ */
+ int multiply(int a, int b) {
+ if (a == 0 || b == 0) {
+ return 0;
+ }
+ int logSum = logTable[a] + logTable[b];
+ // index is a sped-up alternative to logSum % 255 since sum
+ // is in [0,510]. Thanks to jmsachs for the idea
+ return expTable[(logSum & 0xFF) + (logSum >>> 8)];
+ }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/GF256Poly.java b/src/com/google/zxing/common/reedsolomon/GF256Poly.java
new file mode 100644
index 0000000..8da7f93
--- /dev/null
+++ b/src/com/google/zxing/common/reedsolomon/GF256Poly.java
@@ -0,0 +1,263 @@
+/*
+ * 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 GF(256).
+ * 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 GF256Poly {
+
+ private final GF256 field;
+ private final int[] coefficients;
+
+ /**
+ * @param field the {@link GF256} instance representing the field to use
+ * to perform computations
+ * @param coefficients coefficients as ints representing elements of GF(256), 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")
+ */
+ GF256Poly(GF256 field, int[] coefficients) {
+ if (coefficients == null || 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 i = 0; i < size; i++) {
+ result = GF256.addOrSubtract(result, coefficients[i]);
+ }
+ return result;
+ }
+ int result = coefficients[0];
+ for (int i = 1; i < size; i++) {
+ result = GF256.addOrSubtract(field.multiply(a, result), coefficients[i]);
+ }
+ return result;
+ }
+
+ GF256Poly addOrSubtract(GF256Poly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("GF256Polys do not have same GF256 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] = GF256.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
+ }
+
+ return new GF256Poly(field, sumDiff);
+ }
+
+ GF256Poly multiply(GF256Poly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("GF256Polys do not have same GF256 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] = GF256.addOrSubtract(product[i + j],
+ field.multiply(aCoeff, bCoefficients[j]));
+ }
+ }
+ return new GF256Poly(field, product);
+ }
+
+ GF256Poly 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 GF256Poly(field, product);
+ }
+
+ GF256Poly 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 GF256Poly(field, product);
+ }
+
+ GF256Poly[] divide(GF256Poly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("GF256Polys do not have same GF256 field");
+ }
+ if (other.isZero()) {
+ throw new IllegalArgumentException("Divide by 0");
+ }
+
+ GF256Poly quotient = field.getZero();
+ GF256Poly 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);
+ GF256Poly term = other.multiplyByMonomial(degreeDifference, scale);
+ GF256Poly iterationQuotient = field.buildMonomial(degreeDifference, scale);
+ quotient = quotient.addOrSubtract(iterationQuotient);
+ remainder = remainder.addOrSubtract(term);
+ }
+
+ return new GF256Poly[] { quotient, remainder };
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(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/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
new file mode 100644
index 0000000..e7d05de
--- /dev/null
+++ b/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
@@ -0,0 +1,194 @@
+/*
+ * 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 GF256 field;
+
+ public ReedSolomonDecoder(GF256 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 {
+ GF256Poly poly = new GF256Poly(field, received);
+ int[] syndromeCoefficients = new int[twoS];
+ boolean dataMatrix = field.equals(GF256.DATA_MATRIX_FIELD);
+ boolean noError = true;
+ for (int i = 0; i < twoS; i++) {
+ // Thanks to sanfordsquires for this fix:
+ int eval = poly.evaluateAt(field.exp(dataMatrix ? i + 1 : i));
+ syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval;
+ if (eval != 0) {
+ noError = false;
+ }
+ }
+ if (noError) {
+ return;
+ }
+ GF256Poly syndrome = new GF256Poly(field, syndromeCoefficients);
+ GF256Poly[] sigmaOmega =
+ runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS);
+ GF256Poly sigma = sigmaOmega[0];
+ GF256Poly omega = sigmaOmega[1];
+ int[] errorLocations = findErrorLocations(sigma);
+ int[] errorMagnitudes = findErrorMagnitudes(omega, errorLocations, dataMatrix);
+ 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] = GF256.addOrSubtract(received[position], errorMagnitudes[i]);
+ }
+ }
+
+ private GF256Poly[] runEuclideanAlgorithm(GF256Poly a, GF256Poly b, int R)
+ throws ReedSolomonException {
+ // Assume a's degree is >= b's
+ if (a.getDegree() < b.getDegree()) {
+ GF256Poly temp = a;
+ a = b;
+ b = temp;
+ }
+
+ GF256Poly rLast = a;
+ GF256Poly r = b;
+ GF256Poly sLast = field.getOne();
+ GF256Poly s = field.getZero();
+ GF256Poly tLast = field.getZero();
+ GF256Poly t = field.getOne();
+
+ // Run Euclidean algorithm until r's degree is less than R/2
+ while (r.getDegree() >= R / 2) {
+ GF256Poly rLastLast = rLast;
+ GF256Poly sLastLast = sLast;
+ GF256Poly tLastLast = tLast;
+ rLast = r;
+ sLast = s;
+ 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;
+ GF256Poly 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));
+ }
+
+ s = q.multiply(sLast).addOrSubtract(sLastLast);
+ t = q.multiply(tLast).addOrSubtract(tLastLast);
+ }
+
+ int sigmaTildeAtZero = t.getCoefficient(0);
+ if (sigmaTildeAtZero == 0) {
+ throw new ReedSolomonException("sigmaTilde(0) was zero");
+ }
+
+ int inverse = field.inverse(sigmaTildeAtZero);
+ GF256Poly sigma = t.multiply(inverse);
+ GF256Poly omega = r.multiply(inverse);
+ return new GF256Poly[]{sigma, omega};
+ }
+
+ private int[] findErrorLocations(GF256Poly 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 < 256 && 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(GF256Poly errorEvaluator, int[] errorLocations, boolean dataMatrix) {
+ // 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,
+ // GF256.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));
+ // Thanks to sanfordsquires for this fix:
+ if (dataMatrix) {
+ result[i] = field.multiply(result[i], xiInverse);
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java
new file mode 100644
index 0000000..5ce80c8
--- /dev/null
+++ b/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.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.common.reedsolomon;
+
+import java.util.Vector;
+
+/**
+ * Implements Reed-Solomon enbcoding, as the name implies.
+ *
+ * @author Sean Owen
+ * @author William Rucklidge
+ */
+public final class ReedSolomonEncoder {
+
+ private final GF256 field;
+ private final Vector cachedGenerators;
+
+ public ReedSolomonEncoder(GF256 field) {
+ if (!GF256.QR_CODE_FIELD.equals(field)) {
+ throw new IllegalArgumentException("Only QR Code is supported at this time");
+ }
+ this.field = field;
+ this.cachedGenerators = new Vector();
+ cachedGenerators.addElement(new GF256Poly(field, new int[] { 1 }));
+ }
+
+ private GF256Poly buildGenerator(int degree) {
+ if (degree >= cachedGenerators.size()) {
+ GF256Poly lastGenerator = (GF256Poly) cachedGenerators.elementAt(cachedGenerators.size() - 1);
+ for (int d = cachedGenerators.size(); d <= degree; d++) {
+ GF256Poly nextGenerator = lastGenerator.multiply(new GF256Poly(field, new int[] { 1, field.exp(d - 1) }));
+ cachedGenerators.addElement(nextGenerator);
+ lastGenerator = nextGenerator;
+ }
+ }
+ return (GF256Poly) cachedGenerators.elementAt(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");
+ }
+ GF256Poly generator = buildGenerator(ecBytes);
+ int[] infoCoefficients = new int[dataBytes];
+ System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes);
+ GF256Poly info = new GF256Poly(field, infoCoefficients);
+ info = info.multiplyByMonomial(ecBytes, 1);
+ GF256Poly 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/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java
new file mode 100644
index 0000000..d5b45a6
--- /dev/null
+++ b/src/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/src/com/google/zxing/datamatrix/DataMatrixReader.java b/src/com/google/zxing/datamatrix/DataMatrixReader.java
new file mode 100644
index 0000000..25f8fa4
--- /dev/null
+++ b/src/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.Hashtable;
+
+/**
+ * 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
+ */
+ public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ public Result decode(BinaryBitmap image, Hashtable 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);
+ if (decoderResult.getByteSegments() != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, decoderResult.getByteSegments());
+ }
+ if (decoderResult.getECLevel() != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel().toString());
+ }
+ return result;
+ }
+
+ public void reset() {
+ // do nothing
+ }
+
+ /**
+ * This method detects a Data Matrix code in a "pure" image -- that is, pure monochrome image
+ * which contains only an unrotated, unskewed, image of a Data Matrix 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 height = image.getHeight();
+ int width = image.getWidth();
+ int minDimension = Math.min(height, width);
+
+ // And then keep tracking across the top-left black module to determine module size
+ //int moduleEnd = borderWidth;
+ int[] leftTopBlack = image.getTopLeftOnBit();
+ if (leftTopBlack == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int x = leftTopBlack[0];
+ int y = leftTopBlack[1];
+ while (x < minDimension && y < minDimension && image.get(x, y)) {
+ x++;
+ }
+ if (x == minDimension) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int moduleSize = x - leftTopBlack[0];
+
+ // And now find where the rightmost black module on the first row ends
+ int rowEndOfSymbol = width - 1;
+ while (rowEndOfSymbol >= 0 && !image.get(rowEndOfSymbol, y)) {
+ rowEndOfSymbol--;
+ }
+ if (rowEndOfSymbol < 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ rowEndOfSymbol++;
+
+ // Make sure width of barcode is a multiple of module size
+ if ((rowEndOfSymbol - x) % moduleSize != 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int dimension = 2 + ((rowEndOfSymbol - x) / moduleSize);
+
+ y += moduleSize;
+
+ // 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.
+ x -= moduleSize >> 1;
+ y -= moduleSize >> 1;
+
+ if ((x + (dimension - 1) * moduleSize) >= width ||
+ (y + (dimension - 1) * moduleSize) >= height) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Now just read off the bits
+ BitMatrix bits = new BitMatrix(dimension);
+ for (int i = 0; i < dimension; i++) {
+ int iOffset = y + i * moduleSize;
+ for (int j = 0; j < dimension; j++) {
+ if (image.get(x + j * moduleSize, iOffset)) {
+ bits.set(j, i);
+ }
+ }
+ }
+ return bits;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/datamatrix/decoder/BitMatrixParser.java b/src/com/google/zxing/datamatrix/decoder/BitMatrixParser.java
new file mode 100644
index 0000000..c33839b
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/decoder/BitMatrixParser.java
@@ -0,0 +1,446 @@
+/*
+ * 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 < 10 or > 144 or not 0 mod 2
+ */
+ BitMatrixParser(BitMatrix bitMatrix) throws FormatException {
+ int dimension = bitMatrix.getHeight();
+ if (dimension < 10 || dimension > 144 || (dimension & 0x01) != 0) {
+ throw FormatException.getFormatInstance();
+ }
+
+ version = readVersion(bitMatrix);
+ this.mappingBitMatrix = extractDataRegion(bitMatrix);
+ // TODO(bbrown): Make this work for rectangular symbols
+ this.readMappingMatrix = new BitMatrix(this.mappingBitMatrix.getHeight());
+ }
+
+ /**
+ * 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.
+ */
+ Version readVersion(BitMatrix bitMatrix) throws FormatException {
+
+ if (version != null) {
+ return version;
+ }
+
+ // TODO(bbrown): make this work for rectangular dimensions as well.
+ int numRows = bitMatrix.getHeight();
+ int numColumns = numRows;
+
+ 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;
+ // TODO(bbrown): Data Matrix can be rectangular, assuming square for now
+ int numRows = mappingBitMatrix.getHeight();
+ int numColumns = numRows;
+
+ 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();
+
+ // TODO(bbrown): Make this work with rectangular codes
+ 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;
+
+ // TODO(bbrown): Make this work with rectangular codes
+ BitMatrix bitMatrixWithoutAlignment = new BitMatrix(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/src/com/google/zxing/datamatrix/decoder/DataBlock.java b/src/com/google/zxing/datamatrix/decoder/DataBlock.java
new file mode 100644
index 0000000..1e605ac
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/decoder/DataBlock.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.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 {@link DataBlock}s 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 (int i = 0; i < ecBlockArray.length; i++) {
+ totalBlocks += ecBlockArray[i].getCount();
+ }
+
+ // Now establish DataBlocks of the appropriate size and number of data codewords
+ DataBlock[] result = new DataBlock[totalBlocks];
+ int numResultBlocks = 0;
+ for (int j = 0; j < ecBlockArray.length; j++) {
+ Version.ECB ecBlock = ecBlockArray[j];
+ 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/src/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java b/src/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java
new file mode 100644
index 0000000..16ce63d
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java
@@ -0,0 +1,462 @@
+/*
+ * 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.Vector;
+
+/**
+ * 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 {
+
+ /**
+ * 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 static final int PAD_ENCODE = 0; // Not really an encoding
+ private static final int ASCII_ENCODE = 1;
+ private static final int C40_ENCODE = 2;
+ private static final int TEXT_ENCODE = 3;
+ private static final int ANSIX12_ENCODE = 4;
+ private static final int EDIFACT_ENCODE = 5;
+ private static final int BASE256_ENCODE = 6;
+
+ private DecodedBitStreamParser() {
+ }
+
+ static DecoderResult decode(byte[] bytes) throws FormatException {
+ BitSource bits = new BitSource(bytes);
+ StringBuffer result = new StringBuffer(100);
+ StringBuffer resultTrailer = new StringBuffer(0);
+ Vector byteSegments = new Vector(1);
+ int mode = ASCII_ENCODE;
+ do {
+ if (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 = ASCII_ENCODE;
+ }
+ } while (mode != PAD_ENCODE && bits.available() > 0);
+ if (resultTrailer.length() > 0) {
+ result.append(resultTrailer.toString());
+ }
+ 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 int decodeAsciiSegment(BitSource bits, StringBuffer result, StringBuffer 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)
+ oneByte = upperShift ? (oneByte + 128) : oneByte;
+ upperShift = false;
+ result.append((char) (oneByte - 1));
+ return ASCII_ENCODE;
+ } else if (oneByte == 129) { // Pad
+ return 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 C40_ENCODE;
+ } else if (oneByte == 231) { // Latch to Base 256 encodation
+ return BASE256_ENCODE;
+ } else if (oneByte == 232) { // FNC1
+ //throw ReaderException.getInstance();
+ // Ignore this symbol for now
+ } else if (oneByte == 233) { // Structured Append
+ //throw ReaderException.getInstance();
+ // Ignore this symbol for now
+ } else if (oneByte == 234) { // Reader Programming
+ //throw ReaderException.getInstance();
+ // Ignore this symbol for now
+ } 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 ANSIX12_ENCODE;
+ } else if (oneByte == 239) { // Latch to Text encodation
+ return TEXT_ENCODE;
+ } else if (oneByte == 240) { // Latch to EDIFACT encodation
+ return 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
+ throw FormatException.getFormatInstance();
+ }
+ } while (bits.available() > 0);
+ return ASCII_ENCODE;
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.5 and Annex C, Table C.1
+ */
+ private static void decodeC40Segment(BitSource bits, StringBuffer 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];
+ 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);
+
+ int shift = 0;
+ for (int i = 0; i < 3; i++) {
+ int cValue = cValues[i];
+ switch (shift) {
+ case 0:
+ if (cValue < 3) {
+ shift = cValue + 1;
+ } else {
+ if (upperShift) {
+ result.append((char) (C40_BASIC_SET_CHARS[cValue] + 128));
+ upperShift = false;
+ } else {
+ result.append(C40_BASIC_SET_CHARS[cValue]);
+ }
+ }
+ break;
+ case 1:
+ if (upperShift) {
+ result.append((char) (cValue + 128));
+ upperShift = false;
+ } else {
+ result.append(cValue);
+ }
+ shift = 0;
+ break;
+ case 2:
+ if (cValue < 27) {
+ if (upperShift) {
+ result.append((char) (C40_SHIFT2_SET_CHARS[cValue] + 128));
+ upperShift = false;
+ } else {
+ result.append(C40_SHIFT2_SET_CHARS[cValue]);
+ }
+ } else if (cValue == 27) { // FNC1
+ throw FormatException.getFormatInstance();
+ } 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, StringBuffer 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];
+ 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);
+
+ int shift = 0;
+ for (int i = 0; i < 3; i++) {
+ int cValue = cValues[i];
+ switch (shift) {
+ case 0:
+ if (cValue < 3) {
+ shift = cValue + 1;
+ } else {
+ if (upperShift) {
+ result.append((char) (TEXT_BASIC_SET_CHARS[cValue] + 128));
+ upperShift = false;
+ } else {
+ result.append(TEXT_BASIC_SET_CHARS[cValue]);
+ }
+ }
+ break;
+ case 1:
+ if (upperShift) {
+ result.append((char) (cValue + 128));
+ upperShift = false;
+ } else {
+ result.append(cValue);
+ }
+ shift = 0;
+ break;
+ case 2:
+ // Shift 2 for Text is the same encoding as C40
+ if (cValue < 27) {
+ if (upperShift) {
+ result.append((char) (C40_SHIFT2_SET_CHARS[cValue] + 128));
+ upperShift = false;
+ } else {
+ result.append(C40_SHIFT2_SET_CHARS[cValue]);
+ }
+ } else if (cValue == 27) { // FNC1
+ throw FormatException.getFormatInstance();
+ } else if (cValue == 30) { // Upper Shift
+ upperShift = true;
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ shift = 0;
+ break;
+ case 3:
+ if (upperShift) {
+ result.append((char) (TEXT_SHIFT3_SET_CHARS[cValue] + 128));
+ upperShift = false;
+ } else {
+ result.append(TEXT_SHIFT3_SET_CHARS[cValue]);
+ }
+ shift = 0;
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+ }
+ } while (bits.available() > 0);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.7
+ */
+ private static void decodeAnsiX12Segment(BitSource bits, StringBuffer 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, StringBuffer result) {
+ boolean unlatch = false;
+ 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 == 0x2B67) { // 011111
+ unlatch = true;
+ // If we encounter the unlatch code then continue reading because the Codeword triple
+ // is padded with 0's
+ }
+
+ if (!unlatch) {
+ if ((edifactValue & 32) == 0) { // no 1 in the leading (6th) bit
+ edifactValue |= 64; // Add a leading 01 to the 6 bit binary value
+ }
+ result.append(edifactValue);
+ }
+ }
+ } while (!unlatch && bits.available() > 0);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.9 and Annex B, B.2
+ */
+ private static void decodeBase256Segment(BitSource bits, StringBuffer result, Vector byteSegments)
+ throws FormatException {
+ // Figure out how long the Base 256 Segment is.
+ int d1 = bits.readBits(8);
+ 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) + bits.readBits(8);
+ }
+ 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] = unrandomize255State(bits.readBits(8), i);
+ }
+ byteSegments.addElement(bytes);
+ try {
+ result.append(new String(bytes, "ISO8859_1"));
+ } catch (UnsupportedEncodingException uee) {
+ throw new RuntimeException("Platform does not support required encoding: " + uee);
+ }
+ }
+
+ /**
+ * See ISO 16022:2006, Annex B, B.2
+ */
+ private static byte unrandomize255State(int randomizedBase256Codeword,
+ int base256CodewordPosition) {
+ int pseudoRandomNumber = ((149 * base256CodewordPosition) % 255) + 1;
+ int tempVariable = randomizedBase256Codeword - pseudoRandomNumber;
+ return (byte) (tempVariable >= 0 ? tempVariable : (tempVariable + 256));
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/decoder/Decoder.java b/src/com/google/zxing/datamatrix/decoder/Decoder.java
new file mode 100644
index 0000000..c7a771b
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/decoder/Decoder.java
@@ -0,0 +1,134 @@
+/*
+ * 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.GF256;
+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(GF256.DATA_MATRIX_FIELD);
+ }
+
+ /**
+ * 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.readVersion(bits);
+
+ // Read codewords
+ byte[] codewords = parser.readCodewords();
+ // Separate into data blocks
+ DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version);
+
+ // Count total number of data bytes
+ int totalBytes = 0;
+ for (int i = 0; i < dataBlocks.length; i++) {
+ totalBytes += dataBlocks[i].getNumDataCodewords();
+ }
+ byte[] resultBytes = new byte[totalBytes];
+ int resultOffset = 0;
+
+ // Error-correct and copy data blocks together into a stream of bytes
+ for (int j = 0; j < dataBlocks.length; j++) {
+ DataBlock dataBlock = dataBlocks[j];
+ 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);
+ }
+
+ /**
+ * 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 rse) {
+ 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/src/com/google/zxing/datamatrix/decoder/Version.java b/src/com/google/zxing/datamatrix/decoder/Version.java
new file mode 100644
index 0000000..45f9e35
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/decoder/Version.java
@@ -0,0 +1,242 @@
+/*
+ * 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 (int i = 0; i < ecbArray.length; i++) {
+ ECB ecBlock = ecbArray[i];
+ 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 {@link 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();
+ }
+
+ // TODO(bbrown): This is doing a linear search through the array of versions.
+ // If we interleave the rectangular versions with the square versions we could
+ // do a binary search.
+ int numVersions = VERSIONS.length;
+ for (int i = 0; i < numVersions; ++i){
+ Version version = VERSIONS[i];
+ 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;
+ }
+ }
+
+ 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, 10, 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/src/com/google/zxing/datamatrix/detector/Detector.java b/src/com/google/zxing/datamatrix/detector/Detector.java
new file mode 100644
index 0000000..59ffbad
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/detector/Detector.java
@@ -0,0 +1,348 @@
+/*
+ * 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.Collections;
+import com.google.zxing.common.Comparator;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.common.GridSampler;
+import com.google.zxing.common.detector.WhiteRectangleDetector;
+
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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 {
+
+ // Trick to avoid creating new Integer objects below -- a sort of crude copy of
+ // the Integer.valueOf(int) optimization added in Java 5, not in J2ME
+ private static final Integer[] INTEGERS =
+ { new Integer(0), new Integer(1), new Integer(2), new Integer(3), new Integer(4) };
+ // No, can't use valueOf()
+
+ private final BitMatrix image;
+ private final WhiteRectangleDetector rectangleDetector;
+
+ public Detector(BitMatrix image) {
+ 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
+ Vector transitions = new Vector(4);
+ transitions.addElement(transitionsBetween(pointA, pointB));
+ transitions.addElement(transitionsBetween(pointA, pointC));
+ transitions.addElement(transitionsBetween(pointB, pointD));
+ transitions.addElement(transitionsBetween(pointC, pointD));
+ Collections.insertionSort(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 = (ResultPointsAndTransitions) transitions.elementAt(0);
+ ResultPointsAndTransitions lSideTwo = (ResultPointsAndTransitions) transitions.elementAt(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.
+ Hashtable pointCount = new Hashtable();
+ 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;
+ Enumeration points = pointCount.keys();
+ while (points.hasMoreElements()) {
+ ResultPoint point = (ResultPoint) points.nextElement();
+ Integer value = (Integer) pointCount.get(point);
+ if (value.intValue() == 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 dimension = Math.min(transitionsBetween(topLeft, topRight).getTransitions(),
+ transitionsBetween(bottomRight, topRight).getTransitions());
+ if ((dimension & 0x01) == 1) {
+ // it can't be odd, so, round... up?
+ dimension++;
+ }
+ dimension += 2;
+
+ //correct top right point to match the white module
+ ResultPoint correctedTopRight = correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension);
+ if (correctedTopRight == null){
+ correctedTopRight = topRight;
+ }
+
+ //We redetermine the dimension using the corrected top right point
+ int dimension2 = Math.max(transitionsBetween(topLeft, correctedTopRight).getTransitions(),
+ transitionsBetween(bottomRight, correctedTopRight).getTransitions());
+ dimension2++;
+ if ((dimension2 & 0x01) == 1) {
+ dimension2++;
+ }
+
+ BitMatrix bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, correctedTopRight, dimension2);
+
+ 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
+ */
+ 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, bottomRight) / (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;
+ } else 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());
+
+ if (l1 <= l2){
+ return c1;
+ }
+
+ return c2;
+ }
+
+ private boolean isValid(ResultPoint p) {
+ return (p.getX() >= 0 && p.getX() < image.width && p.getY() > 0 && p.getY() < image.height);
+ }
+
+ /**
+ * Ends up being a bit faster than Math.round(). This merely rounds its
+ * argument to the nearest int, where x.5 rounds up.
+ */
+ private static int round(float d) {
+ return (int) (d + 0.5f);
+ }
+
+// L2 distance
+ private static int distance(ResultPoint a, ResultPoint b) {
+ return round((float) Math.sqrt((a.getX() - b.getX())
+ * (a.getX() - b.getX()) + (a.getY() - b.getY())
+ * (a.getY() - b.getY())));
+ }
+
+ /**
+ * Increments the Integer associated with a key by one.
+ */
+ private static void increment(Hashtable table, ResultPoint key) {
+ Integer value = (Integer) table.get(key);
+ table.put(key, value == null ? INTEGERS[1] : INTEGERS[value.intValue() + 1]);
+ }
+
+ private static BitMatrix sampleGrid(BitMatrix image,
+ ResultPoint topLeft,
+ ResultPoint bottomLeft,
+ ResultPoint bottomRight,
+ ResultPoint topRight,
+ int dimension) throws NotFoundException {
+
+ GridSampler sampler = GridSampler.getInstance();
+
+ return sampler.sampleGrid(image,
+ dimension,
+ 0.5f,
+ 0.5f,
+ dimension - 0.5f,
+ 0.5f,
+ dimension - 0.5f,
+ dimension - 0.5f,
+ 0.5f,
+ dimension - 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 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;
+ }
+ public ResultPoint getFrom() {
+ return from;
+ }
+ public ResultPoint getTo() {
+ return to;
+ }
+ public int getTransitions() {
+ return transitions;
+ }
+ public String toString() {
+ return from + "/" + to + '/' + transitions;
+ }
+ }
+
+ /**
+ * Orders ResultPointsAndTransitions by number of transitions, ascending.
+ */
+ private static class ResultPointsAndTransitionsComparator implements Comparator {
+ public int compare(Object o1, Object o2) {
+ return ((ResultPointsAndTransitions) o1).getTransitions() - ((ResultPointsAndTransitions) o2).getTransitions();
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/multi/ByQuadrantReader.java b/src/com/google/zxing/multi/ByQuadrantReader.java
new file mode 100644
index 0000000..35904d3
--- /dev/null
+++ b/src/com/google/zxing/multi/ByQuadrantReader.java
@@ -0,0 +1,96 @@
+/*
+ * 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.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Reader;
+import com.google.zxing.Result;
+
+import java.util.Hashtable;
+
+/**
+ * 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;
+ }
+
+ public Result decode(BinaryBitmap image)
+ throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ public Result decode(BinaryBitmap image, Hashtable 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);
+ }
+
+ public void reset() {
+ delegate.reset();
+ }
+
+}
diff --git a/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java b/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java
new file mode 100644
index 0000000..70d4542
--- /dev/null
+++ b/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java
@@ -0,0 +1,156 @@
+/*
+ * 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.NotFoundException;
+import com.google.zxing.Reader;
+import com.google.zxing.ReaderException;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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 com.google.zxing.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
+ * new ByQuadrantReader(reader)
.
+ *
+ * @author Sean Owen
+ */
+public final class GenericMultipleBarcodeReader implements MultipleBarcodeReader {
+
+ private static final int MIN_DIMENSION_TO_RECUR = 100;
+
+ private final Reader delegate;
+
+ public GenericMultipleBarcodeReader(Reader delegate) {
+ this.delegate = delegate;
+ }
+
+ public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
+ return decodeMultiple(image, null);
+ }
+
+ public Result[] decodeMultiple(BinaryBitmap image, Hashtable hints)
+ throws NotFoundException {
+ Vector results = new Vector();
+ doDecodeMultiple(image, hints, results, 0, 0);
+ if (results.isEmpty()) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int numResults = results.size();
+ Result[] resultArray = new Result[numResults];
+ for (int i = 0; i < numResults; i++) {
+ resultArray[i] = (Result) results.elementAt(i);
+ }
+ return resultArray;
+ }
+
+ private void doDecodeMultiple(BinaryBitmap image,
+ Hashtable hints,
+ Vector results,
+ int xOffset,
+ int yOffset) {
+ Result result;
+ try {
+ result = delegate.decode(image, hints);
+ } catch (ReaderException re) {
+ return;
+ }
+ boolean alreadyFound = false;
+ for (int i = 0; i < results.size(); i++) {
+ Result existingResult = (Result) results.elementAt(i);
+ if (existingResult.getText().equals(result.getText())) {
+ alreadyFound = true;
+ break;
+ }
+ }
+ if (alreadyFound) {
+ return;
+ }
+ results.addElement(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 (int i = 0; i < resultPoints.length; i++) {
+ ResultPoint point = resultPoints[i];
+ 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);
+ }
+ // Decode above barcode
+ if (minY > MIN_DIMENSION_TO_RECUR) {
+ doDecodeMultiple(image.crop(0, 0, width, (int) minY),
+ hints, results, xOffset, yOffset);
+ }
+ // 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);
+ }
+ // 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);
+ }
+ }
+
+ private static Result translateResultPoints(Result result, int xOffset, int yOffset) {
+ ResultPoint[] oldResultPoints = result.getResultPoints();
+ 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);
+ }
+ return new Result(result.getText(), result.getRawBytes(), newResultPoints,
+ result.getBarcodeFormat());
+ }
+
+}
diff --git a/src/com/google/zxing/multi/MultipleBarcodeReader.java b/src/com/google/zxing/multi/MultipleBarcodeReader.java
new file mode 100644
index 0000000..5f0c7eb
--- /dev/null
+++ b/src/com/google/zxing/multi/MultipleBarcodeReader.java
@@ -0,0 +1,37 @@
+/*
+ * 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.NotFoundException;
+import com.google.zxing.Result;
+
+import java.util.Hashtable;
+
+/**
+ * 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, Hashtable hints) throws NotFoundException;
+
+}
diff --git a/src/com/google/zxing/multi/qrcode/QRCodeMultiReader.java b/src/com/google/zxing/multi/qrcode/QRCodeMultiReader.java
new file mode 100644
index 0000000..72a7d68
--- /dev/null
+++ b/src/com/google/zxing/multi/qrcode/QRCodeMultiReader.java
@@ -0,0 +1,80 @@
+/*
+ * 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.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 java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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];
+
+ public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
+ return decodeMultiple(image, null);
+ }
+
+ public Result[] decodeMultiple(BinaryBitmap image, Hashtable hints) throws NotFoundException {
+ Vector results = new Vector();
+ DetectorResult[] detectorResult = new MultiDetector(image.getBlackMatrix()).detectMulti(hints);
+ for (int i = 0; i < detectorResult.length; i++) {
+ try {
+ DecoderResult decoderResult = getDecoder().decode(detectorResult[i].getBits());
+ ResultPoint[] points = detectorResult[i].getPoints();
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points,
+ BarcodeFormat.QR_CODE);
+ if (decoderResult.getByteSegments() != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, decoderResult.getByteSegments());
+ }
+ if (decoderResult.getECLevel() != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel().toString());
+ }
+ results.addElement(result);
+ } catch (ReaderException re) {
+ // ignore and continue
+ }
+ }
+ if (results.isEmpty()) {
+ return EMPTY_RESULT_ARRAY;
+ } else {
+ Result[] resultArray = new Result[results.size()];
+ for (int i = 0; i < results.size(); i++) {
+ resultArray[i] = (Result) results.elementAt(i);
+ }
+ return resultArray;
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java b/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java
new file mode 100644
index 0000000..584c414
--- /dev/null
+++ b/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java
@@ -0,0 +1,72 @@
+/*
+ * 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.NotFoundException;
+import com.google.zxing.ReaderException;
+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.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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(Hashtable hints) throws NotFoundException {
+ BitMatrix image = getImage();
+ MultiFinderPatternFinder finder = new MultiFinderPatternFinder(image);
+ FinderPatternInfo[] info = finder.findMulti(hints);
+
+ if (info == null || info.length == 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ Vector result = new Vector();
+ for (int i = 0; i < info.length; i++) {
+ try {
+ result.addElement(processFinderPatternInfo(info[i]));
+ } catch (ReaderException e) {
+ // ignore
+ }
+ }
+ if (result.isEmpty()) {
+ return EMPTY_DETECTOR_RESULTS;
+ } else {
+ DetectorResult[] resultArray = new DetectorResult[result.size()];
+ for (int i = 0; i < result.size(); i++) {
+ resultArray[i] = (DetectorResult) result.elementAt(i);
+ }
+ return resultArray;
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java b/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java
new file mode 100644
index 0000000..6f19918
--- /dev/null
+++ b/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java
@@ -0,0 +1,324 @@
+/*
+ * 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.common.Collections;
+import com.google.zxing.common.Comparator;
+import com.google.zxing.qrcode.detector.FinderPattern;
+import com.google.zxing.qrcode.detector.FinderPatternFinder;
+import com.google.zxing.qrcode.detector.FinderPatternInfo;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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 class ModuleSizeComparator implements Comparator {
+ public int compare(Object center1, Object center2) {
+ float value = ((FinderPattern) center2).getEstimatedModuleSize() -
+ ((FinderPattern) 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[][] selectBestPatterns() throws NotFoundException {
+ Vector 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[]{
+ (FinderPattern) possibleCenters.elementAt(0),
+ (FinderPattern) possibleCenters.elementAt(1),
+ (FinderPattern) possibleCenters.elementAt(2)
+ }
+ };
+ }
+
+ // Sort by estimated module size to speed up the upcoming checks
+ Collections.insertionSort(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.
+ */
+
+ Vector results = new Vector(); // holder for the results
+
+ for (int i1 = 0; i1 < (size - 2); i1++) {
+ FinderPattern p1 = (FinderPattern) possibleCenters.elementAt(i1);
+ if (p1 == null) {
+ continue;
+ }
+
+ for (int i2 = i1 + 1; i2 < (size - 1); i2++) {
+ FinderPattern p2 = (FinderPattern) possibleCenters.elementAt(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 = (FinderPattern) possibleCenters.elementAt(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;
+ 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.addElement(test);
+ } // end iterate p3
+ } // end iterate p2
+ } // end iterate p1
+
+ if (!results.isEmpty()) {
+ FinderPattern[][] resultArray = new FinderPattern[results.size()][];
+ for (int i = 0; i < results.size(); i++) {
+ resultArray[i] = (FinderPattern[]) results.elementAt(i);
+ }
+ return resultArray;
+ }
+
+ // Nothing found!
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ public FinderPatternInfo[] findMulti(Hashtable hints) throws NotFoundException {
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ 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)) { // Yes
+ boolean confirmed = handlePossibleCenter(stateCount, i, j);
+ if (!confirmed) {
+ do { // Advance to next black pixel
+ j++;
+ } while (j < maxJ && !image.get(j, i));
+ j--; // back up to that last white pixel
+ }
+ // 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);
+ } // end if foundPatternCross
+ } // for i=iSkip-1 ...
+ FinderPattern[][] patternInfo = selectBestPatterns();
+ Vector result = new Vector();
+ for (int i = 0; i < patternInfo.length; i++) {
+ FinderPattern[] pattern = patternInfo[i];
+ ResultPoint.orderBestPatterns(pattern);
+ result.addElement(new FinderPatternInfo(pattern));
+ }
+
+ if (result.isEmpty()) {
+ return EMPTY_RESULT_ARRAY;
+ } else {
+ FinderPatternInfo[] resultArray = new FinderPatternInfo[result.size()];
+ for (int i = 0; i < result.size(); i++) {
+ resultArray[i] = (FinderPatternInfo) result.elementAt(i);
+ }
+ return resultArray;
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/oned/CodaBarReader.java b/src/com/google/zxing/oned/CodaBarReader.java
new file mode 100644
index 0000000..16ccad5
--- /dev/null
+++ b/src/com/google/zxing/oned/CodaBarReader.java
@@ -0,0 +1,263 @@
+/*
+ * 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 java.util.Hashtable;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Result;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitArray;
+
+/**
+ * Decodes Codabar barcodes.
+ *
+ * @author Bas Vijfwinkel
+ */
+public final class CodaBarReader extends OneDReader {
+
+ private static final String ALPHABET_STRING = "0123456789-$:/.+ABCDTN";
+ private 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. NOTE
+ * : c is equal to the * pattern NOTE : d is equal to the e pattern
+ */
+ private static final int[] CHARACTER_ENCODINGS = {
+ 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9
+ 0x00c, 0x018, 0x025, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD
+ 0x01A, 0x029 //TN
+ };
+
+ // minimal number of characters that should be present (inclusing start and stop characters)
+ // this check has been added to reduce the number of false positive on other formats
+ // until the cause for this behaviour has been determined
+ // under normal circumstances this should be set to 3
+ private static final int minCharacterLength = 6;
+
+ // multiple start/end patterns
+ // official start and end patterns
+ private static final char[] STARTEND_ENCODING = {'E', '*', 'A', 'B', 'C', 'D', 'T', 'N'};
+ // some codabar generator allow the codabar string to be closed by every character
+ //private static final char[] STARTEND_ENCODING = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '$', ':', '/', '.', '+', 'A', 'B', 'C', 'D', 'T', 'N'};
+
+ // 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
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws NotFoundException {
+ int[] start = findAsteriskPattern(row);
+ start[1] = 0; // BAS: settings this to 0 improves the recognition rate somehow?
+ int nextStart = start[1];
+ int end = row.getSize();
+
+ // Read off white space
+ while (nextStart < end && !row.get(nextStart)) {
+ nextStart++;
+ }
+
+ StringBuffer result = new StringBuffer();
+ //int[] counters = new int[7];
+ int[] counters;
+ int lastStart;
+
+ do {
+ counters = new int[]{0, 0, 0, 0, 0, 0, 0}; // reset counters
+ recordPattern(row, nextStart, counters);
+
+ char decodedChar = toNarrowWidePattern(counters);
+ if (decodedChar == '!') {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ result.append(decodedChar);
+ lastStart = nextStart;
+ for (int i = 0; i < counters.length; i++) {
+ nextStart += counters[i];
+ }
+
+ // Read off white space
+ while (nextStart < end && !row.get(nextStart)) {
+ nextStart++;
+ }
+ } while (nextStart < end); // no fixed end pattern so keep on reading while data is available
+
+ // Look for whitespace after pattern:
+ int lastPatternSize = 0;
+ for (int i = 0; i < counters.length; i++) {
+ lastPatternSize += counters[i];
+ }
+
+ int whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize;
+ // If 50% of last pattern size, following last pattern, is not whitespace, fail
+ // (but if it's whitespace to the very end of the image, that's OK)
+ if ((nextStart) != end && (whiteSpaceAfterEnd / 2 < lastPatternSize)) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // valid result?
+ if (result.length() < 2)
+ {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ char startchar = result.charAt(0);
+ if (!arrayContains(STARTEND_ENCODING, startchar))
+ {
+ //invalid start character
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // find stop character
+ for (int k = 1;k < result.length() ;k++)
+ {
+ if (result.charAt(k) == startchar)
+ {
+ // found stop character -> discard rest of the string
+ if ((k+1) != result.length())
+ {
+ result.delete(k+1,result.length()-1);
+ k = result.length();// break out of loop
+ }
+ }
+ }
+
+ // remove stop/start characters character and check if a string longer than 5 characters is contained
+ if (result.length() > minCharacterLength)
+ {
+ result.deleteCharAt(result.length()-1);
+ result.deleteCharAt(0);
+ }
+ else
+ {
+ // Almost surely a false positive ( start + stop + at least 1 character)
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ float left = (float) (start[1] + start[0]) / 2.0f;
+ float right = (float) (nextStart + lastStart) / 2.0f;
+ return new Result(
+ result.toString(),
+ null,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODABAR);
+ }
+
+ private static int[] findAsteriskPattern(BitArray row) throws NotFoundException {
+ int width = row.getSize();
+ int rowOffset = 0;
+ while (rowOffset < width) {
+ if (row.get(rowOffset)) {
+ break;
+ }
+ rowOffset++;
+ }
+
+ int counterPosition = 0;
+ int[] counters = new int[7];
+ int patternStart = rowOffset;
+ boolean isWhite = false;
+ int patternLength = counters.length;
+
+ for (int i = rowOffset; i < width; i++) {
+ boolean pixel = row.get(i);
+ if (pixel ^ isWhite) {
+ counters[counterPosition]++;
+ } else {
+ if (counterPosition == patternLength - 1) {
+ try {
+ if (arrayContains(STARTEND_ENCODING, toNarrowWidePattern(counters))) {
+ // Look for whitespace before start pattern, >= 50% of width of start pattern
+ if (row.isRange(Math.max(0, patternStart - (i - patternStart) / 2), patternStart, false)) {
+ return new int[]{patternStart, i};
+ }
+ }
+ } catch (IllegalArgumentException re) {
+ // no match, continue
+ }
+ patternStart += counters[0] + counters[1];
+ for (int y = 2; y < patternLength; y++) {
+ counters[y - 2] = counters[y];
+ }
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite ^= true; // isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private static boolean arrayContains(char[] array, char key) {
+ if (array != null) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == key) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static char toNarrowWidePattern(int[] counters) {
+ // BAS : I have changed the following part because some codabar images would fail with the original routine
+ // I took from the Code39Reader.java file
+ // ----------- change start
+ int numCounters = counters.length;
+ int maxNarrowCounter = 0;
+
+ int minCounter = Integer.MAX_VALUE;
+ for (int i = 0; i < numCounters; i++) {
+ if (counters[i] < minCounter) {
+ minCounter = counters[i];
+ }
+ if (counters[i] > maxNarrowCounter) {
+ maxNarrowCounter = counters[i];
+ }
+ }
+ // ---------- change end
+
+
+ do {
+ int wideCounters = 0;
+ int pattern = 0;
+ for (int i = 0; i < numCounters; i++) {
+ if (counters[i] > maxNarrowCounter) {
+ pattern |= 1 << (numCounters - 1 - i);
+ wideCounters++;
+ }
+ }
+
+ if ((wideCounters == 2) || (wideCounters == 3)) {
+ for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
+ if (CHARACTER_ENCODINGS[i] == pattern) {
+ return ALPHABET[i];
+ }
+ }
+ }
+ maxNarrowCounter--;
+ } while (maxNarrowCounter > minCounter);
+ return '!';
+ }
+
+}
diff --git a/src/com/google/zxing/oned/Code128Reader.java b/src/com/google/zxing/oned/Code128Reader.java
new file mode 100644
index 0000000..b41d954
--- /dev/null
+++ b/src/com/google/zxing/oned/Code128Reader.java
@@ -0,0 +1,473 @@
+/*
+ * 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.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.Hashtable;
+
+/**
+ * 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 = 0;
+ while (rowOffset < width) {
+ if (row.get(rowOffset)) {
+ break;
+ }
+ rowOffset++;
+ }
+
+ 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++) {
+ boolean pixel = row.get(i);
+ if (pixel ^ 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;
+ }
+ }
+ if (bestMatch >= 0) {
+ // Look for whitespace before start pattern, >= 50% of width of start pattern
+ if (row.isRange(Math.max(0, patternStart - (i - patternStart) / 2), patternStart,
+ false)) {
+ return new int[]{patternStart, i, bestMatch};
+ }
+ }
+ patternStart += counters[0] + counters[1];
+ for (int y = 2; y < patternLength; y++) {
+ counters[y - 2] = counters[y];
+ }
+ 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();
+ }
+ }
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
+ throws NotFoundException, FormatException, ChecksumException {
+
+ int[] startPatternInfo = findStartPattern(row);
+ int startCode = startPatternInfo[2];
+ 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;
+
+ StringBuffer result = new StringBuffer(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;
+
+ while (!done) {
+
+ boolean unshift = isNextShifted;
+ isNextShifted = false;
+
+ // Save off last code
+ lastCode = code;
+
+ // Decode another code from image
+ code = decodeCode(row, counters, nextStart);
+
+ // 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 i = 0; i < counters.length; i++) {
+ nextStart += counters[i];
+ }
+
+ // 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) {
+ result.append((char) (' ' + code));
+ } else if (code < 96) {
+ result.append((char) (code - 64));
+ } 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:
+ case CODE_FNC_2:
+ case CODE_FNC_3:
+ case CODE_FNC_4_A:
+ // do nothing?
+ 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) {
+ result.append((char) (' ' + code));
+ } else {
+ if (code != CODE_STOP) {
+ lastCharacterWasPrintable = false;
+ }
+ switch (code) {
+ case CODE_FNC_1:
+ case CODE_FNC_2:
+ case CODE_FNC_3:
+ case CODE_FNC_4_B:
+ // do nothing?
+ break;
+ case CODE_SHIFT:
+ isNextShifted = true;
+ codeSet = CODE_CODE_C;
+ 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:
+ // do nothing?
+ 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) {
+ switch (codeSet) {
+ case CODE_CODE_A:
+ codeSet = CODE_CODE_C;
+ break;
+ case CODE_CODE_B:
+ codeSet = CODE_CODE_A;
+ break;
+ case CODE_CODE_C:
+ codeSet = CODE_CODE_B;
+ break;
+ }
+ }
+
+ }
+
+ // 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:
+ int width = row.getSize();
+ while (nextStart < width && row.get(nextStart)) {
+ nextStart++;
+ }
+ if (!row.isRange(nextStart, Math.min(width, 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();
+ // 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);
+ }
+ }
+
+ String resultString = result.toString();
+
+ if (resultString.length() == 0) {
+ // Almost surely a false positive
+ throw FormatException.getFormatInstance();
+ }
+
+ float left = (float) (startPatternInfo[1] + startPatternInfo[0]) / 2.0f;
+ float right = (float) (nextStart + lastStart) / 2.0f;
+ return new Result(
+ resultString,
+ null,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODE_128);
+
+ }
+
+}
diff --git a/src/com/google/zxing/oned/Code128Writer.java b/src/com/google/zxing/oned/Code128Writer.java
new file mode 100644
index 0000000..22a5d66
--- /dev/null
+++ b/src/com/google/zxing/oned/Code128Writer.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 java.util.Hashtable;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * This object renders a CODE128 code as a {@link BitMatrix}.
+ *
+ * @author erik.barbara@gmail.com (Erik Barbara)
+ */
+public final class Code128Writer extends UPCEANWriter {
+
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Hashtable 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);
+ }
+
+ public byte[] 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 codeWidth = 11 + 11 + 13; //start plus check plus stop character
+ //get total code width for this barcode
+ for (int i = 0; i < length; i++) {
+ int[] patterns = Code128Reader.CODE_PATTERNS[contents.charAt(i) - ' '];
+ for (int j = 0; j < patterns.length; j++) {
+ codeWidth += patterns[j];
+ }
+ }
+ byte[] result = new byte[codeWidth];
+ int pos = appendPattern(result, 0, Code128Reader.CODE_PATTERNS[104], 1);
+ int check = 104;
+ //append next character to bytematrix
+ for(int i = 0; i < length; i++) {
+ check += (contents.charAt(i) - ' ') * (i + 1);
+ pos += appendPattern(result, pos, Code128Reader.CODE_PATTERNS[contents.charAt(i) - ' '],1);
+ }
+ //compute checksum and append it along with end character and quiet space
+ check %= 103;
+ pos += appendPattern(result,pos,Code128Reader.CODE_PATTERNS[check],1);
+ pos += appendPattern(result,pos,Code128Reader.CODE_PATTERNS[106],1);
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/oned/Code39Reader.java b/src/com/google/zxing/oned/Code39Reader.java
new file mode 100644
index 0000000..0bc66bf
--- /dev/null
+++ b/src/com/google/zxing/oned/Code39Reader.java
@@ -0,0 +1,333 @@
+/*
+ * 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.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.Hashtable;
+
+/**
+ * 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;
+
+ /**
+ * 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() {
+ usingCheckDigit = false;
+ extendedMode = 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 = usingCheckDigit;
+ this.extendedMode = 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;
+ }
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
+ throws NotFoundException, ChecksumException, FormatException {
+
+ int[] start = findAsteriskPattern(row);
+ int nextStart = start[1];
+ int end = row.getSize();
+
+ // Read off white space
+ while (nextStart < end && !row.get(nextStart)) {
+ nextStart++;
+ }
+
+ StringBuffer result = new StringBuffer(20);
+ int[] counters = new int[9];
+ char decodedChar;
+ int lastStart;
+ do {
+ recordPattern(row, nextStart, counters);
+ int pattern = toNarrowWidePattern(counters);
+ if (pattern < 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decodedChar = patternToChar(pattern);
+ result.append(decodedChar);
+ lastStart = nextStart;
+ for (int i = 0; i < counters.length; i++) {
+ nextStart += counters[i];
+ }
+ // Read off white space
+ while (nextStart < end && !row.get(nextStart)) {
+ nextStart++;
+ }
+ } while (decodedChar != '*');
+ result.deleteCharAt(result.length() - 1); // remove asterisk
+
+ // Look for whitespace after pattern:
+ int lastPatternSize = 0;
+ for (int i = 0; i < counters.length; i++) {
+ lastPatternSize += counters[i];
+ }
+ int whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize;
+ // If 50% of last pattern size, following last pattern, is not whitespace, fail
+ // (but if it's whitespace to the very end of the image, that's OK)
+ if (nextStart != end && whiteSpaceAfterEnd / 2 < lastPatternSize) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ if (usingCheckDigit) {
+ int max = result.length() - 1;
+ int total = 0;
+ for (int i = 0; i < max; i++) {
+ total += ALPHABET_STRING.indexOf(result.charAt(i));
+ }
+ if (result.charAt(max) != ALPHABET[total % 43]) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ result.deleteCharAt(max);
+ }
+
+ if (result.length() == 0) {
+ // Almost surely a 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 = (float) (nextStart + lastStart) / 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) throws NotFoundException {
+ int width = row.getSize();
+ int rowOffset = 0;
+ while (rowOffset < width) {
+ if (row.get(rowOffset)) {
+ break;
+ }
+ rowOffset++;
+ }
+
+ int counterPosition = 0;
+ int[] counters = new int[9];
+ int patternStart = rowOffset;
+ boolean isWhite = false;
+ int patternLength = counters.length;
+
+ for (int i = rowOffset; i < width; i++) {
+ boolean pixel = row.get(i);
+ if (pixel ^ isWhite) {
+ counters[counterPosition]++;
+ } else {
+ if (counterPosition == patternLength - 1) {
+ if (toNarrowWidePattern(counters) == ASTERISK_ENCODING) {
+ // Look for whitespace before start pattern, >= 50% of width of start pattern
+ if (row.isRange(Math.max(0, patternStart - (i - patternStart) / 2), patternStart, false)) {
+ return new int[]{patternStart, i};
+ }
+ }
+ patternStart += counters[0] + counters[1];
+ for (int y = 2; y < patternLength; y++) {
+ counters[y - 2] = counters[y];
+ }
+ 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 i = 0; i < numCounters; i++) {
+ int counter = counters[i];
+ 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 (counters[i] > 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 (counters[i] > 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(StringBuffer encoded) throws FormatException {
+ int length = encoded.length();
+ StringBuffer decoded = new StringBuffer(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/src/com/google/zxing/oned/Code39Writer.java b/src/com/google/zxing/oned/Code39Writer.java
new file mode 100644
index 0000000..dc9cc24
--- /dev/null
+++ b/src/com/google/zxing/oned/Code39Writer.java
@@ -0,0 +1,82 @@
+/*
+ * 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 java.util.Hashtable;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * This object renders a CODE39 code as a {@link BitMatrix}.
+ *
+ * @author erik.barbara@gmail.com (Erik Barbara)
+ */
+public final class Code39Writer extends UPCEANWriter {
+
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Hashtable 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);
+ }
+
+ public byte[] 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));
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[indexInString], widths);
+ for(int j = 0; j < widths.length; j++) {
+ codeWidth += widths[j];
+ }
+ }
+ byte[] result = new byte[codeWidth];
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[39], widths);
+ int pos = appendPattern(result, 0, widths, 1);
+ int[] narrowWhite = {1};
+ pos += appendPattern(result, pos, narrowWhite, 0);
+ //append next character to bytematrix
+ for(int i = length-1; i >= 0; i--) {
+ int indexInString = Code39Reader.ALPHABET_STRING.indexOf(contents.charAt(i));
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[indexInString], widths);
+ pos += appendPattern(result, pos, widths, 1);
+ pos += appendPattern(result, pos, narrowWhite, 0);
+ }
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[39], widths);
+ pos += appendPattern(result, pos, widths, 1);
+ return result;
+ }
+
+ private static void toIntArray(int a, int[] toReturn) {
+ for (int i = 0; i < 9; i++) {
+ int temp = a & (1 << i);
+ toReturn[i] = (temp == 0) ? 1 : 2;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/oned/Code93Reader.java b/src/com/google/zxing/oned/Code93Reader.java
new file mode 100644
index 0000000..3435169
--- /dev/null
+++ b/src/com/google/zxing/oned/Code93Reader.java
@@ -0,0 +1,273 @@
+/*
+ * 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 java.util.Hashtable;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.ChecksumException;
+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;
+
+/**
+ * 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];
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
+ throws NotFoundException, ChecksumException, FormatException {
+
+ int[] start = findAsteriskPattern(row);
+ int nextStart = start[1];
+ int end = row.getSize();
+
+ // Read off white space
+ while (nextStart < end && !row.get(nextStart)) {
+ nextStart++;
+ }
+
+ StringBuffer result = new StringBuffer(20);
+ int[] counters = new int[6];
+ char decodedChar;
+ int lastStart;
+ do {
+ recordPattern(row, nextStart, counters);
+ int pattern = toPattern(counters);
+ if (pattern < 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decodedChar = patternToChar(pattern);
+ result.append(decodedChar);
+ lastStart = nextStart;
+ for (int i = 0; i < counters.length; i++) {
+ nextStart += counters[i];
+ }
+ // Read off white space
+ while (nextStart < end && !row.get(nextStart)) {
+ nextStart++;
+ }
+ } while (decodedChar != '*');
+ result.deleteCharAt(result.length() - 1); // remove asterisk
+
+ // Should be at least one more black module
+ if (nextStart == end || !row.get(nextStart)) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ if (result.length() < 2) {
+ // Almost surely a false positive
+ 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 = (float) (nextStart + lastStart) / 2.0f;
+ return new Result(
+ resultString,
+ null,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODE_93);
+
+ }
+
+ private static int[] findAsteriskPattern(BitArray row) throws NotFoundException {
+ int width = row.getSize();
+ int rowOffset = 0;
+ while (rowOffset < width) {
+ if (row.get(rowOffset)) {
+ break;
+ }
+ rowOffset++;
+ }
+
+ 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++) {
+ boolean pixel = row.get(i);
+ if (pixel ^ isWhite) {
+ counters[counterPosition]++;
+ } else {
+ if (counterPosition == patternLength - 1) {
+ if (toPattern(counters) == ASTERISK_ENCODING) {
+ return new int[]{patternStart, i};
+ }
+ patternStart += counters[0] + counters[1];
+ for (int y = 2; y < patternLength; y++) {
+ counters[y - 2] = counters[y];
+ }
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private static int toPattern(int[] counters) {
+ int max = counters.length;
+ int sum = 0;
+ for (int i = 0; i < max; i++) {
+ sum += counters[i];
+ }
+ 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(StringBuffer encoded) throws FormatException {
+ int length = encoded.length();
+ StringBuffer decoded = new StringBuffer(length);
+ for (int i = 0; i < length; i++) {
+ char c = encoded.charAt(i);
+ if (c >= 'a' && c <= 'd') {
+ 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(StringBuffer result) throws ChecksumException {
+ int length = result.length();
+ checkOneChecksum(result, length - 2, 20);
+ checkOneChecksum(result, length - 1, 15);
+ }
+
+ private static void checkOneChecksum(StringBuffer 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/src/com/google/zxing/oned/EAN13Reader.java b/src/com/google/zxing/oned/EAN13Reader.java
new file mode 100644
index 0000000..9c90e83
--- /dev/null
+++ b/src/com/google/zxing/oned/EAN13Reader.java
@@ -0,0 +1,135 @@
+/*
+ * 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];
+ }
+
+ protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer 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 i = 0; i < counters.length; i++) {
+ rowOffset += counters[i];
+ }
+ 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 i = 0; i < counters.length; i++) {
+ rowOffset += counters[i];
+ }
+ }
+
+ return rowOffset;
+ }
+
+ 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(StringBuffer 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/src/com/google/zxing/oned/EAN13Writer.java b/src/com/google/zxing/oned/EAN13Writer.java
new file mode 100644
index 0000000..ca6d67a
--- /dev/null
+++ b/src/com/google/zxing/oned/EAN13Writer.java
@@ -0,0 +1,81 @@
+/*
+ * 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.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Hashtable;
+
+
+/**
+ * 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 codeWidth = 3 + // start guard
+ (7 * 6) + // left bars
+ 5 + // middle guard
+ (7 * 6) + // right bars
+ 3; // end guard
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height,
+ Hashtable 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);
+ }
+
+ public byte[] encode(String contents) {
+ if (contents.length() != 13) {
+ throw new IllegalArgumentException(
+ "Requested contents should be 13 digits long, but got " + contents.length());
+ }
+
+ int firstDigit = Integer.parseInt(contents.substring(0, 1));
+ int parities = EAN13Reader.FIRST_DIGIT_ENCODINGS[firstDigit];
+ byte[] result = new byte[codeWidth];
+ int pos = 0;
+
+ pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, 1);
+
+ // 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], 0);
+ }
+
+ pos += appendPattern(result, pos, UPCEANReader.MIDDLE_PATTERN, 0);
+
+ for (int i = 7; i <= 12; i++) {
+ int digit = Integer.parseInt(contents.substring(i, i + 1));
+ pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], 1);
+ }
+ pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, 1);
+
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/EAN8Reader.java b/src/com/google/zxing/oned/EAN8Reader.java
new file mode 100644
index 0000000..30fbacb
--- /dev/null
+++ b/src/com/google/zxing/oned/EAN8Reader.java
@@ -0,0 +1,72 @@
+/*
+ * 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];
+ }
+
+ protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer 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 i = 0; i < counters.length; i++) {
+ rowOffset += counters[i];
+ }
+ }
+
+ 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 i = 0; i < counters.length; i++) {
+ rowOffset += counters[i];
+ }
+ }
+
+ return rowOffset;
+ }
+
+ BarcodeFormat getBarcodeFormat() {
+ return BarcodeFormat.EAN_8;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/EAN8Writer.java b/src/com/google/zxing/oned/EAN8Writer.java
new file mode 100644
index 0000000..4273707
--- /dev/null
+++ b/src/com/google/zxing/oned/EAN8Writer.java
@@ -0,0 +1,76 @@
+/*
+ * 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.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Hashtable;
+
+/**
+ * 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 codeWidth = 3 + // start guard
+ (7 * 4) + // left bars
+ 5 + // middle guard
+ (7 * 4) + // right bars
+ 3; // end guard
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height,
+ Hashtable 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 (0 = white, 1 = black) */
+ public byte[] encode(String contents) {
+ if (contents.length() != 8) {
+ throw new IllegalArgumentException(
+ "Requested contents should be 8 digits long, but got " + contents.length());
+ }
+
+ byte[] result = new byte[codeWidth];
+ int pos = 0;
+
+ pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, 1);
+
+ for (int i = 0; i <= 3; i++) {
+ int digit = Integer.parseInt(contents.substring(i, i + 1));
+ pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], 0);
+ }
+
+ pos += appendPattern(result, pos, UPCEANReader.MIDDLE_PATTERN, 0);
+
+ for (int i = 4; i <= 7; i++) {
+ int digit = Integer.parseInt(contents.substring(i, i + 1));
+ pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], 1);
+ }
+ pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, 1);
+
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/EANManufacturerOrgSupport.java b/src/com/google/zxing/oned/EANManufacturerOrgSupport.java
new file mode 100644
index 0000000..c477ca8
--- /dev/null
+++ b/src/com/google/zxing/oned/EANManufacturerOrgSupport.java
@@ -0,0 +1,170 @@
+/*
+ * 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.Vector;
+
+/**
+ * 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 Vector ranges = new Vector();
+ private final Vector countryIdentifiers = new Vector();
+
+ 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 = (int[]) ranges.elementAt(i);
+ int start = range[0];
+ if (prefix < start) {
+ return null;
+ }
+ int end = range.length == 1 ? start : range[1];
+ if (prefix <= end) {
+ return (String) countryIdentifiers.elementAt(i);
+ }
+ }
+ return null;
+ }
+
+ private void add(int[] range, String id) {
+ ranges.addElement(range);
+ countryIdentifiers.addElement(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/src/com/google/zxing/oned/ITFReader.java b/src/com/google/zxing/oned/ITFReader.java
new file mode 100644
index 0000000..7aef5ae
--- /dev/null
+++ b/src/com/google/zxing/oned/ITFReader.java
@@ -0,0 +1,348 @@
+/*
+ * 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.Hashtable;
+
+/**
+ * Implements decoding of the ITF format.
+ *
+ * "ITF" stands for Interleaved Two of Five. This Reader will scan ITF barcode with 6, 10 or 14
+ * digits. 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.8f);
+
+ private static final int W = 3; // Pixel width of a wide line
+ private static final int N = 1; // Pixed width of a narrow line
+
+ private static final int[] DEFAULT_ALLOWED_LENGTHS = { 6, 10, 12, 14, 44 };
+
+ // 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
+ };
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws FormatException, NotFoundException {
+
+ // Find out where the Middle section (payload) starts & ends
+ int[] startRange = decodeStart(row);
+ int[] endRange = decodeEnd(row);
+
+ StringBuffer result = new StringBuffer(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 6, 10 or 14 digits.
+ int length = resultString.length();
+ boolean lengthOK = false;
+ for (int i = 0; i < allowedLengths.length; i++) {
+ if (length == allowedLengths[i]) {
+ lengthOK = true;
+ break;
+ }
+
+ }
+ 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 StringBuffer} to append decoded chars to
+ * @throws NotFoundException if decoding could not complete successfully
+ */
+ private static void decodeMiddle(BitArray row, int payloadStart, int payloadEnd,
+ StringBuffer 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 i = 0; i < counterDigitPair.length; i++) {
+ payloadStart += counterDigitPair[i];
+ }
+ }
+ }
+
+ /**
+ * 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
+
+ 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 = 0;
+ while (endStart < width) {
+ if (row.get(endStart)) {
+ break;
+ }
+ endStart++;
+ }
+ 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++) {
+ boolean pixel = row.get(x);
+ 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];
+ for (int y = 2; y < patternLength; y++) {
+ counters[y - 2] = counters[y];
+ }
+ 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/src/com/google/zxing/oned/ITFWriter.java b/src/com/google/zxing/oned/ITFWriter.java
new file mode 100644
index 0000000..16894a6
--- /dev/null
+++ b/src/com/google/zxing/oned/ITFWriter.java
@@ -0,0 +1,68 @@
+/*
+ * 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 java.util.Hashtable;
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * This object renders a ITF code as a {@link BitMatrix}.
+ *
+ * @author erik.barbara@gmail.com (Erik Barbara)
+ */
+public final class ITFWriter extends UPCEANWriter {
+
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Hashtable 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);
+ }
+
+ public byte[] 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);
+ }
+ byte[] result = new byte[9 + 9 * length];
+ int[] start = {1, 1, 1, 1};
+ int pos = appendPattern(result, 0, start, 1);
+ 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, 1);
+ }
+ int[] end = {3, 1, 1};
+ pos += appendPattern(result, pos, end, 1);
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/oned/MultiFormatOneDReader.java b/src/com/google/zxing/oned/MultiFormatOneDReader.java
new file mode 100644
index 0000000..fa3de5d
--- /dev/null
+++ b/src/com/google/zxing/oned/MultiFormatOneDReader.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.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.Hashtable;
+import java.util.Vector;
+
+/**
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ */
+public final class MultiFormatOneDReader extends OneDReader {
+
+ private final Vector readers;
+
+ public MultiFormatOneDReader(Hashtable hints) {
+ Vector possibleFormats = hints == null ? null :
+ (Vector) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ boolean useCode39CheckDigit = hints != null &&
+ hints.get(DecodeHintType.ASSUME_CODE_39_CHECK_DIGIT) != null;
+ readers = new Vector();
+ 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.addElement(new MultiFormatUPCEANReader(hints));
+ }
+ if (possibleFormats.contains(BarcodeFormat.CODE_39)) {
+ readers.addElement(new Code39Reader(useCode39CheckDigit));
+ }
+ if (possibleFormats.contains(BarcodeFormat.CODE_93)) {
+ readers.addElement(new Code93Reader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.CODE_128)) {
+ readers.addElement(new Code128Reader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.ITF)) {
+ readers.addElement(new ITFReader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.CODABAR)) {
+ readers.addElement(new CodaBarReader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.RSS14)) {
+ readers.addElement(new RSS14Reader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.RSS_EXPANDED)){
+ readers.addElement(new RSSExpandedReader());
+ }
+ }
+ if (readers.isEmpty()) {
+ readers.addElement(new MultiFormatUPCEANReader(hints));
+ readers.addElement(new Code39Reader());
+ //readers.addElement(new CodaBarReader());
+ readers.addElement(new Code93Reader());
+ readers.addElement(new Code128Reader());
+ readers.addElement(new ITFReader());
+ readers.addElement(new RSS14Reader());
+ readers.addElement(new RSSExpandedReader());
+ }
+ }
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws NotFoundException {
+ int size = readers.size();
+ for (int i = 0; i < size; i++) {
+ OneDReader reader = (OneDReader) readers.elementAt(i);
+ try {
+ return reader.decodeRow(rowNumber, row, hints);
+ } catch (ReaderException re) {
+ // continue
+ }
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ public void reset() {
+ int size = readers.size();
+ for (int i = 0; i < size; i++) {
+ Reader reader = (Reader) readers.elementAt(i);
+ reader.reset();
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/oned/MultiFormatUPCEANReader.java b/src/com/google/zxing/oned/MultiFormatUPCEANReader.java
new file mode 100644
index 0000000..e561f59
--- /dev/null
+++ b/src/com/google/zxing/oned/MultiFormatUPCEANReader.java
@@ -0,0 +1,113 @@
+/*
+ * 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.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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 Vector readers;
+
+ public MultiFormatUPCEANReader(Hashtable hints) {
+ Vector possibleFormats = hints == null ? null :
+ (Vector) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ readers = new Vector();
+ if (possibleFormats != null) {
+ if (possibleFormats.contains(BarcodeFormat.EAN_13)) {
+ readers.addElement(new EAN13Reader());
+ } else if (possibleFormats.contains(BarcodeFormat.UPC_A)) {
+ readers.addElement(new UPCAReader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.EAN_8)) {
+ readers.addElement(new EAN8Reader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.UPC_E)) {
+ readers.addElement(new UPCEReader());
+ }
+ }
+ if (readers.isEmpty()) {
+ readers.addElement(new EAN13Reader());
+ // UPC-A is covered by EAN-13
+ readers.addElement(new EAN8Reader());
+ readers.addElement(new UPCEReader());
+ }
+ }
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws NotFoundException {
+ // Compute this location once and reuse it on multiple implementations
+ int[] startGuardPattern = UPCEANReader.findStartGuardPattern(row);
+ int size = readers.size();
+ for (int i = 0; i < size; i++) {
+ UPCEANReader reader = (UPCEANReader) readers.elementAt(i);
+ Result result;
+ try {
+ result = reader.decodeRow(rowNumber, row, startGuardPattern, hints);
+ } catch (ReaderException re) {
+ 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 =
+ BarcodeFormat.EAN_13.equals(result.getBarcodeFormat()) &&
+ result.getText().charAt(0) == '0';
+ Vector possibleFormats = hints == null ? null : (Vector) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ boolean canReturnUPCA = possibleFormats == null || possibleFormats.contains(BarcodeFormat.UPC_A);
+
+ if (ean13MayBeUPCA && canReturnUPCA) {
+ return new Result(result.getText().substring(1), null, result.getResultPoints(), BarcodeFormat.UPC_A);
+ }
+ return result;
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ public void reset() {
+ int size = readers.size();
+ for (int i = 0; i < size; i++) {
+ Reader reader = (Reader) readers.elementAt(i);
+ reader.reset();
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/oned/OneDReader.java b/src/com/google/zxing/oned/OneDReader.java
new file mode 100644
index 0000000..102483c
--- /dev/null
+++ b/src/com/google/zxing/oned/OneDReader.java
@@ -0,0 +1,297 @@
+/*
+ * 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.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * 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;
+
+ 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.
+ public Result decode(BinaryBitmap image, Hashtable 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
+ Hashtable 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)).intValue()) % 360;
+ }
+ result.putMetadata(ResultMetadataType.ORIENTATION, new Integer(orientation));
+ // Update result points
+ ResultPoint[] points = result.getResultPoints();
+ 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;
+ }
+ }
+ }
+
+ 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, Hashtable 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 nfe) {
+ 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)) {
+ Hashtable newHints = new Hashtable(); // Can't use clone() in J2ME
+ Enumeration hintEnum = hints.keys();
+ while (hintEnum.hasMoreElements()) {
+ Object key = hintEnum.nextElement();
+ if (!key.equals(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
+ newHints.put(key, hints.get(key));
+ }
+ }
+ 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, new Integer(180));
+ // And remember to flip the result points horizontally.
+ ResultPoint[] points = result.getResultPoints();
+ 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;
+ for (int i = 0; i < numCounters; i++) {
+ counters[i] = 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) {
+ boolean pixel = row.get(i);
+ if (pixel ^ 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 an error occurs or barcode cannot be found
+ */
+ public abstract Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
+ throws NotFoundException, ChecksumException, FormatException;
+
+}
diff --git a/src/com/google/zxing/oned/UPCAReader.java b/src/com/google/zxing/oned/UPCAReader.java
new file mode 100644
index 0000000..b90c1b8
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCAReader.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.BinaryBitmap;
+import com.google.zxing.ChecksumException;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Result;
+import com.google.zxing.common.BitArray;
+
+import java.util.Hashtable;
+
+/**
+ * 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();
+
+ public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, Hashtable hints)
+ throws NotFoundException, FormatException, ChecksumException {
+ return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, startGuardRange, hints));
+ }
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
+ throws NotFoundException, FormatException, ChecksumException {
+ return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, hints));
+ }
+
+ public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
+ return maybeReturnResult(ean13Reader.decode(image));
+ }
+
+ public Result decode(BinaryBitmap image, Hashtable hints) throws NotFoundException, FormatException {
+ return maybeReturnResult(ean13Reader.decode(image, hints));
+ }
+
+ BarcodeFormat getBarcodeFormat() {
+ return BarcodeFormat.UPC_A;
+ }
+
+ protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer 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/src/com/google/zxing/oned/UPCEANExtensionSupport.java b/src/com/google/zxing/oned/UPCEANExtensionSupport.java
new file mode 100644
index 0000000..dea116d
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEANExtensionSupport.java
@@ -0,0 +1,186 @@
+/*
+ * 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.Hashtable;
+
+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;
+
+final class UPCEANExtensionSupport {
+
+ private static final int[] EXTENSION_START_PATTERN = {1,1,2};
+ 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 StringBuffer decodeRowStringBuffer = new StringBuffer();
+
+ Result decodeRow(int rowNumber, BitArray row, int rowOffset) throws NotFoundException {
+
+ int[] extensionStartRange = UPCEANReader.findGuardPattern(row, rowOffset, false, EXTENSION_START_PATTERN);
+
+ StringBuffer result = decodeRowStringBuffer;
+ result.setLength(0);
+ int end = decodeMiddle(row, extensionStartRange, result);
+
+ String resultString = result.toString();
+ Hashtable 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, StringBuffer 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 i = 0; i < counters.length; i++) {
+ rowOffset += counters[i];
+ }
+ if (bestMatch >= 10) {
+ lgPatternFound |= 1 << (4 - x);
+ }
+ if (x != 4) {
+ // Read off separator if not last
+ while (rowOffset < end && !row.get(rowOffset)) {
+ rowOffset++;
+ }
+ while (rowOffset < end && row.get(rowOffset)) {
+ 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(String 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 Hashtable} mapping
+ * one {@link ResultMetadataType} to appropriate value, or null
if not known
+ */
+ private static Hashtable parseExtensionString(String raw) {
+ ResultMetadataType type;
+ Object value;
+ switch (raw.length()) {
+ case 2:
+ type = ResultMetadataType.ISSUE_NUMBER;
+ value = parseExtension2String(raw);
+ break;
+ case 5:
+ type = ResultMetadataType.SUGGESTED_PRICE;
+ value = parseExtension5String(raw);
+ break;
+ default:
+ return null;
+ }
+ if (value == null) {
+ return null;
+ }
+ Hashtable result = new Hashtable(1);
+ result.put(type, value);
+ return result;
+ }
+
+ private static Integer parseExtension2String(String raw) {
+ return Integer.valueOf(raw);
+ }
+
+ private static String parseExtension5String(String raw) {
+ String currency = null;
+ switch (raw.charAt(0)) {
+ case '0':
+ currency = "£";
+ break;
+ case '5':
+ currency = "$";
+ break;
+ case '9':
+ if ("99991".equals(raw)) {
+ return "0.00";
+ } else if ("99990".equals(raw)) {
+ return "Used";
+ }
+ break;
+ default:
+ currency = "";
+ break;
+ }
+ int rawAmount = Integer.parseInt(raw.substring(1));
+ return currency + (rawAmount / 100) + '.' + (rawAmount % 100);
+ }
+
+}
diff --git a/src/com/google/zxing/oned/UPCEANReader.java b/src/com/google/zxing/oned/UPCEANReader.java
new file mode 100644
index 0000000..6de318e
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEANReader.java
@@ -0,0 +1,355 @@
+/*
+ * 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.Hashtable;
+
+/**
+ * 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.42f);
+ 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][];
+ for (int i = 0; i < 10; i++) {
+ L_AND_G_PATTERNS[i] = L_PATTERNS[i];
+ }
+ 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 StringBuffer decodeRowStringBuffer;
+ private final UPCEANExtensionSupport extensionReader;
+ private final EANManufacturerOrgSupport eanManSupport;
+
+ protected UPCEANReader() {
+ decodeRowStringBuffer = new StringBuffer(20);
+ extensionReader = new UPCEANExtensionSupport();
+ eanManSupport = new EANManufacturerOrgSupport();
+ }
+
+ static int[] findStartGuardPattern(BitArray row) throws NotFoundException {
+ boolean foundStart = false;
+ int[] startRange = null;
+ int nextStart = 0;
+ while (!foundStart) {
+ startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN);
+ 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;
+ }
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints)
+ throws NotFoundException, ChecksumException, FormatException {
+ return decodeRow(rowNumber, row, findStartGuardPattern(row), hints);
+ }
+
+ /**
+ * Like {@link #decodeRow(int, BitArray, java.util.Hashtable)}, 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.
+ */
+ public Result decodeRow(int rowNumber, BitArray row, int[] startGuardRange, Hashtable 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
+ ));
+ }
+
+ StringBuffer 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();
+ 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);
+
+ try {
+ Result extensionResult = extensionReader.decodeRow(rowNumber, row, endRange[1]);
+ decodeResult.putAllMetadata(extensionResult.getResultMetadata());
+ decodeResult.addResultPoints(extensionResult.getResultPoints());
+ } catch (ReaderException re) {
+ // continue
+ }
+
+ if (BarcodeFormat.EAN_13.equals(format) || BarcodeFormat.UPC_A.equals(format)) {
+ String countryID = eanManSupport.lookupCountryIdentifier(resultString);
+ if (countryID != null) {
+ decodeResult.putMetadata(ResultMetadataType.POSSIBLE_COUNTRY, countryID);
+ }
+ }
+
+ return decodeResult;
+ }
+
+ /**
+ * @return {@link #checkStandardUPCEANChecksum(String)}
+ */
+ boolean checkChecksum(String s) throws ChecksumException, 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
+ */
+ private static boolean checkStandardUPCEANChecksum(String 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);
+ }
+
+ /**
+ * @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
+ * @return start/end horizontal offset of guard pattern, as an array of two ints
+ * @throws NotFoundException if pattern is not found
+ */
+ static int[] findGuardPattern(BitArray row, int rowOffset, boolean whiteFirst, int[] pattern)
+ throws NotFoundException {
+ int patternLength = pattern.length;
+ int[] counters = new int[patternLength];
+ int width = row.getSize();
+ boolean isWhite = false;
+ while (rowOffset < width) {
+ isWhite = !row.get(rowOffset);
+ if (whiteFirst == isWhite) {
+ break;
+ }
+ rowOffset++;
+ }
+
+ int counterPosition = 0;
+ int patternStart = rowOffset;
+ for (int x = rowOffset; x < width; x++) {
+ boolean pixel = row.get(x);
+ 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];
+ for (int y = 2; y < patternLength; y++) {
+ counters[y - 2] = counters[y];
+ }
+ 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 StringBuffer} 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, StringBuffer resultString)
+ throws NotFoundException;
+
+}
diff --git a/src/com/google/zxing/oned/UPCEANWriter.java b/src/com/google/zxing/oned/UPCEANWriter.java
new file mode 100644
index 0000000..68fa4a0
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEANWriter.java
@@ -0,0 +1,104 @@
+/*
+ * 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.Writer;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Hashtable;
+
+/**
+ * Encapsulates functionality and implementation that is common to UPC and EAN families
+ * of one-dimensional barcodes.
+ *
+ * @author aripollak@gmail.com (Ari Pollak)
+ */
+public abstract class UPCEANWriter implements Writer {
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
+ throws WriterException {
+ return encode(contents, format, width, height, null);
+ }
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height,
+ Hashtable hints) throws WriterException {
+ if (contents == null || contents.length() == 0) {
+ throw new IllegalArgumentException("Found empty contents");
+ }
+
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Requested dimensions are too small: "
+ + width + 'x' + height);
+ }
+
+ byte[] code = encode(contents);
+ return renderResult(code, width, height);
+ }
+
+ /** @return a byte array of horizontal pixels (0 = white, 1 = black) */
+ private static BitMatrix renderResult(byte[] code, int width, int height) {
+ int inputWidth = code.length;
+ // Add quiet zone on both sides
+ int fullWidth = inputWidth + (UPCEANReader.START_END_PATTERN.length << 1);
+ 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] == 1) {
+ output.setRegion(outputX, 0, multiple, outputHeight);
+ }
+ }
+ return output;
+ }
+
+
+ /**
+ * Appends the given pattern to the target array starting at pos.
+ *
+ * @param startColor
+ * starting color - 0 for white, 1 for black
+ * @return the number of elements added to target.
+ */
+ protected static int appendPattern(byte[] target, int pos, int[] pattern, int startColor) {
+ if (startColor != 0 && startColor != 1) {
+ throw new IllegalArgumentException(
+ "startColor must be either 0 or 1, but got: " + startColor);
+ }
+
+ byte color = (byte) startColor;
+ int numAdded = 0;
+ for (int i = 0; i < pattern.length; i++) {
+ for (int j = 0; j < pattern[i]; j++) {
+ target[pos] = color;
+ pos += 1;
+ numAdded += 1;
+ }
+ color ^= 1; // flip color after each segment
+ }
+ return numAdded;
+ }
+
+ /** @return a byte array of horizontal pixels (0 = white, 1 = black) */
+ public abstract byte[] encode(String contents);
+
+}
diff --git a/src/com/google/zxing/oned/UPCEReader.java b/src/com/google/zxing/oned/UPCEReader.java
new file mode 100644
index 0000000..ef74b97
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEReader.java
@@ -0,0 +1,153 @@
+/*
+ * 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.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];
+ }
+
+ protected int decodeMiddle(BitArray row, int[] startRange, StringBuffer 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 i = 0; i < counters.length; i++) {
+ rowOffset += counters[i];
+ }
+ if (bestMatch >= 10) {
+ lgPatternFound |= 1 << (5 - x);
+ }
+ }
+
+ determineNumSysAndCheckDigit(result, lgPatternFound);
+
+ return rowOffset;
+ }
+
+ protected int[] decodeEnd(BitArray row, int endStart) throws NotFoundException {
+ return findGuardPattern(row, endStart, true, MIDDLE_END_PATTERN);
+ }
+
+ protected boolean checkChecksum(String s) throws FormatException, ChecksumException {
+ return super.checkChecksum(convertUPCEtoUPCA(s));
+ }
+
+ private static void determineNumSysAndCheckDigit(StringBuffer 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();
+ }
+
+ 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);
+ StringBuffer result = new StringBuffer(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/src/com/google/zxing/oned/rss/AbstractRSSReader.java b/src/com/google/zxing/oned/rss/AbstractRSSReader.java
new file mode 100644
index 0000000..fce9e83
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/AbstractRSSReader.java
@@ -0,0 +1,110 @@
+/*
+ * 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.4f);
+
+ private static final float MIN_FINDER_PATTERN_RATIO = 9.5f / 12.0f;
+ private static final float MAX_FINDER_PATTERN_RATIO = 12.5f / 14.0f;
+
+ protected final int[] decodeFinderCounters;
+ protected final int[] dataCharacterCounters;
+ protected final float[] oddRoundingErrors;
+ protected final float[] evenRoundingErrors;
+ protected final int[] oddCounts;
+ protected 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 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 i = 0; i < array.length; i++) {
+ count += array[i];
+ }
+ 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 i = 0; i < counters.length; i++) {
+ int counter = counters[i];
+ if (counter > maxCounter) {
+ maxCounter = counter;
+ }
+ if (counter < minCounter) {
+ minCounter = counter;
+ }
+ }
+ return maxCounter < 10 * minCounter;
+ }
+ return false;
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/DataCharacter.java b/src/com/google/zxing/oned/rss/DataCharacter.java
new file mode 100644
index 0000000..9b5e311
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/DataCharacter.java
@@ -0,0 +1,37 @@
+/*
+ * 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 int getValue() {
+ return value;
+ }
+
+ public int getChecksumPortion() {
+ return checksumPortion;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/FinderPattern.java b/src/com/google/zxing/oned/rss/FinderPattern.java
new file mode 100644
index 0000000..afc1a13
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/FinderPattern.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/Pair.java b/src/com/google/zxing/oned/rss/Pair.java
new file mode 100644
index 0000000..e2371d2
--- /dev/null
+++ b/src/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/src/com/google/zxing/oned/rss/RSS14Reader.java b/src/com/google/zxing/oned/rss/RSS14Reader.java
new file mode 100644
index 0000000..1c99ac1
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/RSS14Reader.java
@@ -0,0 +1,494 @@
+/*
+ * 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.Enumeration;
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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 Vector possibleLeftPairs;
+ private final Vector possibleRightPairs;
+
+ public RSS14Reader() {
+ possibleLeftPairs = new Vector();
+ possibleRightPairs = new Vector();
+ }
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable 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 numLeftPairs = possibleLeftPairs.size();
+ int numRightPairs = possibleRightPairs.size();
+ for (int l = 0; l < numLeftPairs; l++) {
+ Pair left = (Pair) possibleLeftPairs.elementAt(l);
+ if (left.getCount() > 1) {
+ for (int r = 0; r < numRightPairs; r++) {
+ Pair right = (Pair) possibleRightPairs.elementAt(r);
+ if (right.getCount() > 1) {
+ if (checkChecksum(left, right)) {
+ return constructResult(left, right);
+ }
+ }
+ }
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private static void addOrTally(Vector possiblePairs, Pair pair) {
+ if (pair == null) {
+ return;
+ }
+ Enumeration e = possiblePairs.elements();
+ boolean found = false;
+ while (e.hasMoreElements()) {
+ Pair other = (Pair) e.nextElement();
+ if (other.getValue() == pair.getValue()) {
+ other.incrementCount();
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ possiblePairs.addElement(pair);
+ }
+ }
+
+ public void reset() {
+ possibleLeftPairs.setSize(0);
+ possibleRightPairs.setSize(0);
+ }
+
+ private static Result constructResult(Pair leftPair, Pair rightPair) {
+ long symbolValue = 4537077L * leftPair.getValue() + rightPair.getValue();
+ String text = String.valueOf(symbolValue);
+
+ StringBuffer buffer = new StringBuffer(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.RSS14);
+ }
+
+ 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, Hashtable 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 re) {
+ return null;
+ }
+ }
+
+ private DataCharacter decodeDataCharacter(BitArray row, FinderPattern pattern, boolean outsideChar)
+ throws NotFoundException {
+
+ int[] counters = dataCharacterCounters;
+ 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.oddCounts;
+ int[] evenCounts = this.evenCounts;
+ float[] oddRoundingErrors = this.oddRoundingErrors;
+ float[] evenRoundingErrors = this.evenRoundingErrors;
+
+ 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 = decodeFinderCounters;
+ 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++) {
+ boolean pixel = row.get(x);
+ if (pixel ^ 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 = decodeFinderCounters;
+ for (int i = counters.length - 1; i > 0; i--) {
+ counters[i] = counters[i-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 static int[] normalizeE2SEValues(int[] counters) {
+ int p = 0;
+ for (int i = 0; i < counters.length; i++) {
+ p += counters[i];
+ }
+ int[] normalized = new int[counters.length - 2];
+ for (int i = 0; i < normalized.length; i++) {
+ int e = counters[i] + counters[i+1];
+ float eRatio = (float) e / (float) p;
+ float E = ((eRatio * 32.0f) + 1.0f) / 2.0f;
+ normalized[i] = (int) E;
+ }
+ return normalized;
+ }
+ */
+
+ private void adjustOddEvenCounts(boolean outsideChar, int numModules) throws NotFoundException {
+
+ int oddSum = count(oddCounts);
+ int evenSum = count(evenCounts);
+ 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(oddCounts, oddRoundingErrors);
+ }
+ if (decrementOdd) {
+ decrement(oddCounts, oddRoundingErrors);
+ }
+ if (incrementEven) {
+ if (decrementEven) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ increment(evenCounts, oddRoundingErrors);
+ }
+ if (decrementEven) {
+ decrement(evenCounts, evenRoundingErrors);
+ }
+
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/RSSUtils.java b/src/com/google/zxing/oned/rss/RSSUtils.java
new file mode 100644
index 0000000..6977fc2
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/RSSUtils.java
@@ -0,0 +1,155 @@
+/*
+ * 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 i = 0; i < elements; i++) {
+ n += widths[i];
+ }
+ 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);
+ }
+
+ static int combins(int n, int r) {
+ int maxDenom, 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/src/com/google/zxing/oned/rss/expanded/BitArrayBuilder.java b/src/com/google/zxing/oned/rss/expanded/BitArrayBuilder.java
new file mode 100644
index 0000000..336046c
--- /dev/null
+++ b/src/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 java.util.Vector;
+
+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 BitArrayBuilder {
+
+ private BitArrayBuilder() {
+ }
+
+ static BitArray buildBitArray(Vector pairs) {
+ int charNumber = (pairs.size() << 1) - 1;
+ if ((((ExpandedPair)pairs.lastElement()).getRightChar()) == null) {
+ charNumber -= 1;
+ }
+
+ int size = 12 * charNumber;
+
+ BitArray binary = new BitArray(size);
+ int accPos = 0;
+
+ ExpandedPair firstPair = (ExpandedPair) pairs.elementAt(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 = (ExpandedPair) pairs.elementAt(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/src/com/google/zxing/oned/rss/expanded/ExpandedPair.java b/src/com/google/zxing/oned/rss/expanded/ExpandedPair.java
new file mode 100644
index 0000000..cde1d02
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/ExpandedPair.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;
+
+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;
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/RSSExpandedReader.java b/src/com/google/zxing/oned/rss/expanded/RSSExpandedReader.java
new file mode 100644
index 0000000..6e6ddc6
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/RSSExpandedReader.java
@@ -0,0 +1,578 @@
+/*
+ * 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 java.util.Hashtable;
+import java.util.Vector;
+
+import com.google.zxing.BarcodeFormat;
+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;
+
+/**
+ * @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 Vector pairs = new Vector(MAX_PAIRS);
+ private final int [] startEnd = new int[2];
+ private final int [] currentSequence = new int[LONGEST_SEQUENCE_SIZE];
+
+ public Result decodeRow(int rowNumber, BitArray row, Hashtable hints) throws NotFoundException {
+ this.reset();
+ decodeRow2pairs(rowNumber, row);
+ return constructResult(this.pairs);
+ }
+
+ public void reset() {
+ this.pairs.setSize(0);
+ }
+
+ // Not private for testing
+ Vector decodeRow2pairs(int rowNumber, BitArray row) throws NotFoundException {
+ while(true){
+ ExpandedPair nextPair = retrieveNextPair(row, this.pairs, rowNumber);
+ this.pairs.addElement(nextPair);
+
+ if(nextPair.mayBeLast()){
+ if(checkChecksum()) {
+ return this.pairs;
+ }
+ if(nextPair.mustBeLast()) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+ }
+ }
+
+ private static Result constructResult(Vector pairs) throws NotFoundException{
+ BitArray binary = BitArrayBuilder.buildBitArray(pairs);
+
+ AbstractExpandedDecoder decoder = AbstractExpandedDecoder.createDecoder(binary);
+ String resultingString = decoder.parseInformation();
+
+ ResultPoint [] firstPoints = ((ExpandedPair)pairs.elementAt(0)).getFinderPattern().getResultPoints();
+ ResultPoint [] lastPoints = ((ExpandedPair)pairs.lastElement()).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 = (ExpandedPair)this.pairs.elementAt(0);
+ DataCharacter checkCharacter = firstPair.getLeftChar();
+ DataCharacter firstCharacter = firstPair.getRightChar();
+
+ int checksum = firstCharacter.getChecksumPortion();
+ int S = 2;
+
+ for(int i = 1; i < this.pairs.size(); ++i){
+ ExpandedPair currentPair = (ExpandedPair)this.pairs.elementAt(i);
+ checksum += currentPair.getLeftChar().getChecksumPortion();
+ S++;
+ if(currentPair.getRightChar() != null){
+ checksum += currentPair.getRightChar().getChecksumPortion();
+ S++;
+ }
+ }
+
+ checksum %= 211;
+
+ int checkCharacterValue = 211 * (S - 4) + checksum;
+
+ return checkCharacterValue == checkCharacter.getValue();
+ }
+
+ private static int getNextSecondBar(BitArray row, int initialPos){
+ int currentPos = initialPos;
+ boolean current = row.get(currentPos);
+
+ while(currentPos < row.size && row.get(currentPos) == current) {
+ currentPos++;
+ }
+
+ current = !current;
+ while(currentPos < row.size && row.get(currentPos) == current) {
+ currentPos++;
+ }
+
+ return currentPos;
+ }
+
+ // not private for testing
+ ExpandedPair retrieveNextPair(BitArray row, Vector previousPairs, int rowNumber) throws NotFoundException{
+ boolean isOddPattern = previousPairs.size() % 2 == 0;
+
+ 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);
+
+ boolean mayBeLast = checkPairSequence(previousPairs, pattern);
+
+ DataCharacter leftChar = this.decodeDataCharacter(row, pattern, isOddPattern, true);
+ DataCharacter rightChar;
+ try{
+ rightChar = this.decodeDataCharacter(row, pattern, isOddPattern, false);
+ }catch(NotFoundException nfe){
+ if(mayBeLast) {
+ rightChar = null;
+ } else {
+ throw nfe;
+ }
+ }
+
+ return new ExpandedPair(leftChar, rightChar, pattern, mayBeLast);
+ }
+
+ private boolean checkPairSequence(Vector previousPairs, FinderPattern pattern) throws NotFoundException{
+ int currentSequenceLength = previousPairs.size() + 1;
+ if(currentSequenceLength > this.currentSequence.length) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ for(int pos = 0; pos < previousPairs.size(); ++pos) {
+ this.currentSequence[pos] = ((ExpandedPair) previousPairs.elementAt(pos)).getFinderPattern().getValue();
+ }
+
+ this.currentSequence[currentSequenceLength - 1] = pattern.getValue();
+
+ for(int i = 0; i < FINDER_PATTERN_SEQUENCES.length; ++i){
+ int [] validSequence = FINDER_PATTERN_SEQUENCES[i];
+ if(validSequence.length >= currentSequenceLength){
+ boolean valid = true;
+ for(int pos = 0; pos < currentSequenceLength; ++pos) {
+ if (this.currentSequence[pos] != validSequence[pos]) {
+ valid = false;
+ break;
+ }
+ }
+
+ if(valid) {
+ return currentSequenceLength == validSequence.length;
+ }
+ }
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private void findNextPair(BitArray row, Vector previousPairs, int forcedOffset) throws NotFoundException{
+ int[] counters = this.decodeFinderCounters;
+ 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 = ((ExpandedPair)previousPairs.lastElement());
+ rowOffset = lastPair.getFinderPattern().getStartEnd()[1];
+ }
+ boolean searchingEvenPair = previousPairs.size() % 2 != 0;
+
+ 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++) {
+ boolean pixel = row.get(x);
+ if (pixel ^ 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];
+
+ int firstElementStart = this.startEnd[1] + 1;
+ while(row.get(firstElementStart) && firstElementStart < row.size) {
+ firstElementStart++;
+ }
+
+ end = firstElementStart;
+ firstCounter = end - this.startEnd[1];
+ }
+
+ // Make 'counters' hold 1-4
+ int [] counters = this.decodeFinderCounters;
+ for (int i = counters.length - 1; i > 0; i--) {
+ counters[i] = counters[i - 1];
+ }
+
+ counters[0] = firstCounter;
+ int value;
+ try {
+ value = parseFinderValue(counters, FINDER_PATTERNS);
+ } catch (NotFoundException nfe) {
+ 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.dataCharacterCounters;
+ 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] + 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;
+
+ int[] oddCounts = this.oddCounts;
+ int[] evenCounts = this.evenCounts;
+ float[] oddRoundingErrors = this.oddRoundingErrors;
+ float[] evenRoundingErrors = this.evenRoundingErrors;
+
+ 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) {
+ 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(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.oddCounts);
+ int evenSum = count(this.evenCounts);
+ 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.oddCounts, this.oddRoundingErrors);
+ }
+ if (decrementOdd) {
+ decrement(this.oddCounts, this.oddRoundingErrors);
+ }
+ if (incrementEven) {
+ if (decrementEven) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ increment(this.evenCounts, this.oddRoundingErrors);
+ }
+ if (decrementEven) {
+ decrement(this.evenCounts, this.evenRoundingErrors);
+ }
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/AI013103decoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AI013103decoder.java
new file mode 100644
index 0000000..5ef3fed
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI013103decoder.java
@@ -0,0 +1,47 @@
+/*
+ * 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);
+ }
+
+ protected void addWeightCode(StringBuffer buf, int weight) {
+ buf.append("(3103)");
+ }
+
+ protected int checkWeight(int weight) {
+ return weight;
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/AI01320xDecoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AI01320xDecoder.java
new file mode 100644
index 0000000..1fd7c0e
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01320xDecoder.java
@@ -0,0 +1,55 @@
+/*
+ * 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);
+ }
+
+ protected void addWeightCode(StringBuffer buf, int weight) {
+ if (weight < 10000) {
+ buf.append("(3202)");
+ } else {
+ buf.append("(3203)");
+ }
+ }
+
+ protected int checkWeight(int weight) {
+ if(weight < 10000) {
+ return weight;
+ }
+ return weight - 10000;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/AI01392xDecoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AI01392xDecoder.java
new file mode 100644
index 0000000..e618d81
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01392xDecoder.java
@@ -0,0 +1,66 @@
+/*
+ * 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)
+ */
+final class AI01392xDecoder extends AI01decoder {
+
+ private static final int headerSize = 5 + 1 + 2;
+ private static final int lastDigitSize = 2;
+
+ AI01392xDecoder(BitArray information) {
+ super(information);
+ }
+
+ public String parseInformation() throws NotFoundException {
+ if (this.information.size < headerSize + gtinSize) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ StringBuffer buf = new StringBuffer();
+
+ encodeCompressedGtin(buf, headerSize);
+
+ int lastAIdigit =
+ this.generalDecoder.extractNumericValueFromBitArray(headerSize + gtinSize, lastDigitSize);
+ buf.append("(392");
+ buf.append(lastAIdigit);
+ buf.append(')');
+
+ DecodedInformation decodedInformation =
+ this.generalDecoder.decodeGeneralPurposeField(headerSize + gtinSize + lastDigitSize, null);
+ buf.append(decodedInformation.getNewString());
+
+ return buf.toString();
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/AI01393xDecoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AI01393xDecoder.java
new file mode 100644
index 0000000..67db980
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01393xDecoder.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.
+ */
+
+/*
+ * 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)
+ */
+final class AI01393xDecoder extends AI01decoder {
+
+ private static final int headerSize = 5 + 1 + 2;
+ private static final int lastDigitSize = 2;
+ private static final int firstThreeDigitsSize = 10;
+
+ AI01393xDecoder(BitArray information) {
+ super(information);
+ }
+
+ public String parseInformation() throws NotFoundException {
+ if(this.information.size < headerSize + gtinSize) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ StringBuffer buf = new StringBuffer();
+
+ encodeCompressedGtin(buf, headerSize);
+
+ int lastAIdigit =
+ this.generalDecoder.extractNumericValueFromBitArray(headerSize + gtinSize, lastDigitSize);
+
+ buf.append("(393");
+ buf.append(lastAIdigit);
+ buf.append(')');
+
+ int firstThreeDigits =
+ this.generalDecoder.extractNumericValueFromBitArray(headerSize + gtinSize + lastDigitSize, firstThreeDigitsSize);
+ if(firstThreeDigits / 100 == 0) {
+ buf.append('0');
+ }
+ if(firstThreeDigits / 10 == 0) {
+ buf.append('0');
+ }
+ buf.append(firstThreeDigits);
+
+ DecodedInformation generalInformation =
+ this.generalDecoder.decodeGeneralPurposeField(headerSize + gtinSize + lastDigitSize + firstThreeDigitsSize, null);
+ buf.append(generalInformation.getNewString());
+
+ return buf.toString();
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/AI013x0x1xDecoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AI013x0x1xDecoder.java
new file mode 100644
index 0000000..0c373c3
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI013x0x1xDecoder.java
@@ -0,0 +1,106 @@
+/*
+ * 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 headerSize = 7 + 1;
+ private static final int weightSize = 20;
+ private static final int dateSize = 16;
+
+ private final String dateCode;
+ private final String firstAIdigits;
+
+ AI013x0x1xDecoder(BitArray information, String firstAIdigits, String dateCode) {
+ super(information);
+ this.dateCode = dateCode;
+ this.firstAIdigits = firstAIdigits;
+ }
+
+ public String parseInformation() throws NotFoundException {
+ if (this.information.size != headerSize + gtinSize + weightSize + dateSize) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ StringBuffer buf = new StringBuffer();
+
+ encodeCompressedGtin(buf, headerSize);
+ encodeCompressedWeight(buf, headerSize + gtinSize, weightSize);
+ encodeCompressedDate(buf, headerSize + gtinSize + weightSize);
+
+ return buf.toString();
+ }
+
+ private void encodeCompressedDate(StringBuffer buf, int currentPos) {
+ int numericDate = this.generalDecoder.extractNumericValueFromBitArray(currentPos, dateSize);
+ 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);
+ }
+
+ protected void addWeightCode(StringBuffer buf, int weight) {
+ int lastAI = weight / 100000;
+ buf.append('(');
+ buf.append(this.firstAIdigits);
+ buf.append(lastAI);
+ buf.append(')');
+ }
+
+ protected int checkWeight(int weight) {
+ return weight % 100000;
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/AI013x0xDecoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AI013x0xDecoder.java
new file mode 100644
index 0000000..96bdaff
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI013x0xDecoder.java
@@ -0,0 +1,56 @@
+/*
+ * 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 headerSize = 4 + 1;
+ private static final int weightSize = 15;
+
+ AI013x0xDecoder(BitArray information) {
+ super(information);
+ }
+
+ public String parseInformation() throws NotFoundException {
+ if (this.information.size != headerSize + gtinSize + weightSize) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ StringBuffer buf = new StringBuffer();
+
+ encodeCompressedGtin(buf, headerSize);
+ encodeCompressedWeight(buf, headerSize + gtinSize, weightSize);
+
+ return buf.toString();
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/AI01AndOtherAIs.java b/src/com/google/zxing/oned/rss/expanded/decoders/AI01AndOtherAIs.java
new file mode 100644
index 0000000..49f109b
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01AndOtherAIs.java
@@ -0,0 +1,56 @@
+/*
+ * 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 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);
+ }
+
+ public String parseInformation() throws NotFoundException {
+ StringBuffer buff = new StringBuffer();
+
+ buff.append("(01)");
+ int initialGtinPosition = buff.length();
+ int firstGtinDigit = this.generalDecoder.extractNumericValueFromBitArray(HEADER_SIZE, 4);
+ buff.append(firstGtinDigit);
+
+ this.encodeCompressedGtinWithoutAI(buff, HEADER_SIZE + 4, initialGtinPosition);
+
+ return this.generalDecoder.decodeAllCodes(buff, HEADER_SIZE + 44);
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/AI01decoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AI01decoder.java
new file mode 100644
index 0000000..62f878b
--- /dev/null
+++ b/src/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 gtinSize = 40;
+
+ AI01decoder(BitArray information) {
+ super(information);
+ }
+
+ protected void encodeCompressedGtin(StringBuffer buf, int currentPos) {
+ buf.append("(01)");
+ int initialPosition = buf.length();
+ buf.append('9');
+
+ encodeCompressedGtinWithoutAI(buf, currentPos, initialPosition);
+ }
+
+ protected void encodeCompressedGtinWithoutAI(StringBuffer buf, int currentPos, int initialBufferPosition) {
+ for(int i = 0; i < 4; ++i){
+ int currentBlock = this.generalDecoder.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(StringBuffer 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/src/com/google/zxing/oned/rss/expanded/decoders/AI01weightDecoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AI01weightDecoder.java
new file mode 100644
index 0000000..f120cc0
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01weightDecoder.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.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+abstract class AI01weightDecoder extends AI01decoder {
+
+ AI01weightDecoder(BitArray information) {
+ super(information);
+ }
+
+ protected void encodeCompressedWeight(StringBuffer buf, int currentPos, int weightSize) {
+ int originalWeightNumeric = this.generalDecoder.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(StringBuffer buf, int weight);
+ protected abstract int checkWeight(int weight);
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/AbstractExpandedDecoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AbstractExpandedDecoder.java
new file mode 100644
index 0000000..84fae37
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AbstractExpandedDecoder.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.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)
+ */
+public abstract class AbstractExpandedDecoder {
+
+ protected final BitArray information;
+ protected final GeneralAppIdDecoder generalDecoder;
+
+ AbstractExpandedDecoder(BitArray information){
+ this.information = information;
+ this.generalDecoder = new GeneralAppIdDecoder(information);
+ }
+
+ public abstract String parseInformation() throws NotFoundException;
+
+ public static AbstractExpandedDecoder createDecoder(BitArray information){
+ if (information.get(1)) {
+ return new AI01AndOtherAIs(information);
+ } else 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/src/com/google/zxing/oned/rss/expanded/decoders/AnyAIDecoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/AnyAIDecoder.java
new file mode 100644
index 0000000..4fcac27
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AnyAIDecoder.java
@@ -0,0 +1,48 @@
+/*
+ * 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 AnyAIDecoder extends AbstractExpandedDecoder {
+
+ private static final int HEADER_SIZE = 2 + 1 + 2;
+
+ AnyAIDecoder(BitArray information) {
+ super(information);
+ }
+
+ public String parseInformation() throws NotFoundException {
+ StringBuffer buf = new StringBuffer();
+ return this.generalDecoder.decodeAllCodes(buf, HEADER_SIZE);
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/BlockParsedResult.java b/src/com/google/zxing/oned/rss/expanded/decoders/BlockParsedResult.java
new file mode 100644
index 0000000..132ba55
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/BlockParsedResult.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;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class BlockParsedResult {
+
+ private final DecodedInformation decodedInformation;
+ private final boolean finished;
+
+ BlockParsedResult() {
+ this.finished = true;
+ this.decodedInformation = null;
+ }
+
+ BlockParsedResult(boolean finished) {
+ this.finished = finished;
+ this.decodedInformation = null;
+ }
+
+ BlockParsedResult(DecodedInformation information, boolean finished) {
+ this.finished = finished;
+ this.decodedInformation = information;
+ }
+
+ DecodedInformation getDecodedInformation() {
+ return this.decodedInformation;
+ }
+
+ boolean isFinished() {
+ return this.finished;
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/CurrentParsingState.java b/src/com/google/zxing/oned/rss/expanded/decoders/CurrentParsingState.java
new file mode 100644
index 0000000..cf7d687
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/CurrentParsingState.java
@@ -0,0 +1,69 @@
+/*
+ * 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 {
+
+ int position;
+ private int encoding;
+
+ private static final int NUMERIC = 1;
+ private static final int ALPHA = 2;
+ private static final int ISO_IEC_646 = 4;
+
+ CurrentParsingState(){
+ this.position = 0;
+ this.encoding = NUMERIC;
+ }
+
+ boolean isAlpha(){
+ return this.encoding == ALPHA;
+ }
+
+ boolean isNumeric(){
+ return this.encoding == NUMERIC;
+ }
+
+ boolean isIsoIec646(){
+ return this.encoding == ISO_IEC_646;
+ }
+
+ void setNumeric(){
+ this.encoding = NUMERIC;
+ }
+
+ void setAlpha(){
+ this.encoding = ALPHA;
+ }
+
+ void setIsoIec646(){
+ this.encoding = ISO_IEC_646;
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/DecodedChar.java b/src/com/google/zxing/oned/rss/expanded/decoders/DecodedChar.java
new file mode 100644
index 0000000..790b684
--- /dev/null
+++ b/src/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/src/com/google/zxing/oned/rss/expanded/decoders/DecodedInformation.java b/src/com/google/zxing/oned/rss/expanded/decoders/DecodedInformation.java
new file mode 100644
index 0000000..13eec5c
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/DecodedInformation.java
@@ -0,0 +1,63 @@
+/*
+ * 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/src/com/google/zxing/oned/rss/expanded/decoders/DecodedNumeric.java b/src/com/google/zxing/oned/rss/expanded/decoders/DecodedNumeric.java
new file mode 100644
index 0000000..1b1be42
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/DecodedNumeric.java
@@ -0,0 +1,79 @@
+/*
+ * 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 DecodedNumeric extends DecodedObject {
+
+ private final int firstDigit;
+ private final int secondDigit;
+
+ static final int FNC1 = 10;
+
+ DecodedNumeric(int newPosition, int firstDigit, int secondDigit){
+ super(newPosition);
+
+ this.firstDigit = firstDigit;
+ this.secondDigit = secondDigit;
+
+ if (this.firstDigit < 0 || this.firstDigit > 10) {
+ throw new IllegalArgumentException("Invalid firstDigit: " + firstDigit);
+ }
+
+ if (this.secondDigit < 0 || this.secondDigit > 10) {
+ throw new IllegalArgumentException("Invalid 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/src/com/google/zxing/oned/rss/expanded/decoders/DecodedObject.java b/src/com/google/zxing/oned/rss/expanded/decoders/DecodedObject.java
new file mode 100644
index 0000000..7547384
--- /dev/null
+++ b/src/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 {
+
+ protected final int newPosition;
+
+ DecodedObject(int newPosition){
+ this.newPosition = newPosition;
+ }
+
+ int getNewPosition() {
+ return this.newPosition;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/FieldParser.java b/src/com/google/zxing/oned/rss/expanded/decoders/FieldParser.java
new file mode 100644
index 0000000..91c1d49
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/FieldParser.java
@@ -0,0 +1,285 @@
+/*
+ * 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", new Integer(18) },
+ { "01", new Integer(14) },
+ { "02", new Integer(14) },
+
+ { "10", VARIABLE_LENGTH, new Integer(20) },
+ { "11", new Integer(6) },
+ { "12", new Integer(6) },
+ { "13", new Integer(6) },
+ { "15", new Integer(6) },
+ { "17", new Integer(6) },
+
+ { "20", new Integer(2) },
+ { "21", VARIABLE_LENGTH, new Integer(20) },
+ { "22", VARIABLE_LENGTH, new Integer(29) },
+
+ { "30", VARIABLE_LENGTH, new Integer( 8) },
+ { "37", VARIABLE_LENGTH, new Integer( 8) },
+
+ //internal company codes
+ { "90", VARIABLE_LENGTH, new Integer(30) },
+ { "91", VARIABLE_LENGTH, new Integer(30) },
+ { "92", VARIABLE_LENGTH, new Integer(30) },
+ { "93", VARIABLE_LENGTH, new Integer(30) },
+ { "94", VARIABLE_LENGTH, new Integer(30) },
+ { "95", VARIABLE_LENGTH, new Integer(30) },
+ { "96", VARIABLE_LENGTH, new Integer(30) },
+ { "97", VARIABLE_LENGTH, new Integer(30) },
+ { "98", VARIABLE_LENGTH, new Integer(30) },
+ { "99", VARIABLE_LENGTH, new Integer(30) },
+ };
+
+ private static final Object [][] THREE_DIGIT_DATA_LENGTH = {
+ // Same format as above
+
+ { "240", VARIABLE_LENGTH, new Integer(30) },
+ { "241", VARIABLE_LENGTH, new Integer(30) },
+ { "242", VARIABLE_LENGTH, new Integer( 6) },
+ { "250", VARIABLE_LENGTH, new Integer(30) },
+ { "251", VARIABLE_LENGTH, new Integer(30) },
+ { "253", VARIABLE_LENGTH, new Integer(17) },
+ { "254", VARIABLE_LENGTH, new Integer(20) },
+
+ { "400", VARIABLE_LENGTH, new Integer(30) },
+ { "401", VARIABLE_LENGTH, new Integer(30) },
+ { "402", new Integer(17) },
+ { "403", VARIABLE_LENGTH, new Integer(30) },
+ { "410", new Integer(13) },
+ { "411", new Integer(13) },
+ { "412", new Integer(13) },
+ { "413", new Integer(13) },
+ { "414", new Integer(13) },
+ { "420", VARIABLE_LENGTH, new Integer(20) },
+ { "421", VARIABLE_LENGTH, new Integer(15) },
+ { "422", new Integer( 3) },
+ { "423", VARIABLE_LENGTH, new Integer(15) },
+ { "424", new Integer(3) },
+ { "425", new Integer(3) },
+ { "426", new Integer(3) },
+ };
+
+ private static final Object [][] THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH = {
+ // Same format as above
+
+ { "310", new Integer(6) },
+ { "311", new Integer(6) },
+ { "312", new Integer(6) },
+ { "313", new Integer(6) },
+ { "314", new Integer(6) },
+ { "315", new Integer(6) },
+ { "316", new Integer(6) },
+ { "320", new Integer(6) },
+ { "321", new Integer(6) },
+ { "322", new Integer(6) },
+ { "323", new Integer(6) },
+ { "324", new Integer(6) },
+ { "325", new Integer(6) },
+ { "326", new Integer(6) },
+ { "327", new Integer(6) },
+ { "328", new Integer(6) },
+ { "329", new Integer(6) },
+ { "330", new Integer(6) },
+ { "331", new Integer(6) },
+ { "332", new Integer(6) },
+ { "333", new Integer(6) },
+ { "334", new Integer(6) },
+ { "335", new Integer(6) },
+ { "336", new Integer(6) },
+ { "340", new Integer(6) },
+ { "341", new Integer(6) },
+ { "342", new Integer(6) },
+ { "343", new Integer(6) },
+ { "344", new Integer(6) },
+ { "345", new Integer(6) },
+ { "346", new Integer(6) },
+ { "347", new Integer(6) },
+ { "348", new Integer(6) },
+ { "349", new Integer(6) },
+ { "350", new Integer(6) },
+ { "351", new Integer(6) },
+ { "352", new Integer(6) },
+ { "353", new Integer(6) },
+ { "354", new Integer(6) },
+ { "355", new Integer(6) },
+ { "356", new Integer(6) },
+ { "357", new Integer(6) },
+ { "360", new Integer(6) },
+ { "361", new Integer(6) },
+ { "362", new Integer(6) },
+ { "363", new Integer(6) },
+ { "364", new Integer(6) },
+ { "365", new Integer(6) },
+ { "366", new Integer(6) },
+ { "367", new Integer(6) },
+ { "368", new Integer(6) },
+ { "369", new Integer(6) },
+ { "390", VARIABLE_LENGTH, new Integer(15) },
+ { "391", VARIABLE_LENGTH, new Integer(18) },
+ { "392", VARIABLE_LENGTH, new Integer(15) },
+ { "393", VARIABLE_LENGTH, new Integer(18) },
+ { "703", VARIABLE_LENGTH, new Integer(30) }
+ };
+
+ private static final Object [][] FOUR_DIGIT_DATA_LENGTH = {
+ // Same format as above
+
+ { "7001", new Integer(13) },
+ { "7002", VARIABLE_LENGTH, new Integer(30) },
+ { "7003", new Integer(10) },
+
+ { "8001", new Integer(14) },
+ { "8002", VARIABLE_LENGTH, new Integer(20) },
+ { "8003", VARIABLE_LENGTH, new Integer(30) },
+ { "8004", VARIABLE_LENGTH, new Integer(30) },
+ { "8005", new Integer(6) },
+ { "8006", new Integer(18) },
+ { "8007", VARIABLE_LENGTH, new Integer(30) },
+ { "8008", VARIABLE_LENGTH, new Integer(12) },
+ { "8018", new Integer(18) },
+ { "8020", VARIABLE_LENGTH, new Integer(25) },
+ { "8100", new Integer(6) },
+ { "8101", new Integer(10) },
+ { "8102", new Integer(2) },
+ { "8110", VARIABLE_LENGTH, new Integer(30) },
+ };
+
+ private FieldParser() {
+ }
+
+ static String parseFieldsInGeneralPurpose(String rawInformation) throws NotFoundException{
+ if(rawInformation.length() == 0) {
+ return "";
+ }
+
+ // Processing 2-digit AIs
+
+ if(rawInformation.length() < 2) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String firstTwoDigits = rawInformation.substring(0, 2);
+
+ for (int i=0; i this.information.size){
+ return pos + 4 <= this.information.size;
+ }
+
+ 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) {
+ if(pos + 7 > this.information.size){
+ int numeric = extractNumericValueFromBitArray(pos, 4);
+ if(numeric == 0) {
+ return new DecodedNumeric(this.information.size, DecodedNumeric.FNC1, DecodedNumeric.FNC1);
+ }
+ return new DecodedNumeric(this.information.size, 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) {
+ if(bits > 32) {
+ throw new IllegalArgumentException("extractNumberValueFromBitArray can't handle more than 32 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) {
+ this.buffer.setLength(0);
+
+ if(remaining != null) {
+ this.buffer.append(remaining);
+ }
+
+ this.current.position = pos;
+
+ DecodedInformation lastDecoded = parseBlocks();
+ if(lastDecoded != null && lastDecoded.isRemaining()) {
+ return new DecodedInformation(this.current.position, this.buffer.toString(), lastDecoded.getRemainingValue());
+ }
+ return new DecodedInformation(this.current.position, this.buffer.toString());
+ }
+
+ private DecodedInformation parseBlocks() {
+ boolean isFinished;
+ BlockParsedResult result;
+ do{
+ int initialPosition = current.position;
+
+ 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.position;
+ if(!positionChanged && !isFinished) {
+ break;
+ }
+ } while (!isFinished);
+
+ return result.getDecodedInformation();
+ }
+
+ private BlockParsedResult parseNumericBlock() {
+ while(isStillNumeric(current.position)){
+ DecodedNumeric numeric = decodeNumeric(current.position);
+ current.position = numeric.getNewPosition();
+
+ if(numeric.isFirstDigitFNC1()){
+ DecodedInformation information;
+ if (numeric.isSecondDigitFNC1()) {
+ information = new DecodedInformation(current.position, buffer.toString());
+ } else {
+ information = new DecodedInformation(current.position, buffer.toString(), numeric.getSecondDigit());
+ }
+ return new BlockParsedResult(information, true);
+ }
+ buffer.append(numeric.getFirstDigit());
+
+ if(numeric.isSecondDigitFNC1()){
+ DecodedInformation information = new DecodedInformation(current.position, buffer.toString());
+ return new BlockParsedResult(information, true);
+ }
+ buffer.append(numeric.getSecondDigit());
+ }
+
+ if(isNumericToAlphaNumericLatch(current.position)){
+ current.setAlpha();
+ current.position += 4;
+ }
+ return new BlockParsedResult(false);
+ }
+
+ private BlockParsedResult parseIsoIec646Block() {
+ while (isStillIsoIec646(current.position)) {
+ DecodedChar iso = decodeIsoIec646(current.position);
+ current.position = iso.getNewPosition();
+
+ if (iso.isFNC1()) {
+ DecodedInformation information = new DecodedInformation(current.position, buffer.toString());
+ return new BlockParsedResult(information, true);
+ }
+ buffer.append(iso.getValue());
+ }
+
+ if (isAlphaOr646ToNumericLatch(current.position)) {
+ current.position += 3;
+ current.setNumeric();
+ } else if (isAlphaTo646ToAlphaLatch(current.position)) {
+ if (current.position + 5 < this.information.size) {
+ current.position += 5;
+ } else {
+ current.position = this.information.size;
+ }
+
+ current.setAlpha();
+ }
+ return new BlockParsedResult(false);
+ }
+
+ private BlockParsedResult parseAlphaBlock() {
+ while (isStillAlpha(current.position)) {
+ DecodedChar alpha = decodeAlphanumeric(current.position);
+ current.position = alpha.getNewPosition();
+
+ if(alpha.isFNC1()) {
+ DecodedInformation information = new DecodedInformation(current.position, buffer.toString());
+ return new BlockParsedResult(information, true); //end of the char block
+ }
+
+ buffer.append(alpha.getValue());
+ }
+
+ if (isAlphaOr646ToNumericLatch(current.position)) {
+ current.position += 3;
+ current.setNumeric();
+ } else if (isAlphaTo646ToAlphaLatch(current.position)) {
+ if (current.position + 5 < this.information.size) {
+ current.position += 5;
+ } else {
+ current.position = this.information.size;
+ }
+
+ current.setIsoIec646();
+ }
+ return new BlockParsedResult(false);
+ }
+
+ private boolean isStillIsoIec646(int pos) {
+ if(pos + 5 > this.information.size) {
+ return false;
+ }
+
+ int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
+ if(fiveBitValue >= 5 && fiveBitValue < 16) {
+ return true;
+ }
+
+ if(pos + 7 > this.information.size) {
+ return false;
+ }
+
+ int sevenBitValue = extractNumericValueFromBitArray(pos, 7);
+ if(sevenBitValue >= 64 && sevenBitValue < 116) {
+ return true;
+ }
+
+ if(pos + 8 > this.information.size) {
+ return false;
+ }
+
+ int eightBitValue = extractNumericValueFromBitArray(pos, 8);
+ return eightBitValue >= 232 && eightBitValue < 253;
+
+ }
+
+ private DecodedChar decodeIsoIec646(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 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);
+ switch (eightBitValue){
+ case 232: return new DecodedChar(pos + 8, '!');
+ case 233: return new DecodedChar(pos + 8, '"');
+ case 234: return new DecodedChar(pos + 8, '%');
+ case 235: return new DecodedChar(pos + 8, '&');
+ case 236: return new DecodedChar(pos + 8, '\'');
+ case 237: return new DecodedChar(pos + 8, '(');
+ case 238: return new DecodedChar(pos + 8, ')');
+ case 239: return new DecodedChar(pos + 8, '*');
+ case 240: return new DecodedChar(pos + 8, '+');
+ case 241: return new DecodedChar(pos + 8, ',');
+ case 242: return new DecodedChar(pos + 8, '-');
+ case 243: return new DecodedChar(pos + 8, '.');
+ case 244: return new DecodedChar(pos + 8, '/');
+ case 245: return new DecodedChar(pos + 8, ':');
+ case 246: return new DecodedChar(pos + 8, ';');
+ case 247: return new DecodedChar(pos + 8, '<');
+ case 248: return new DecodedChar(pos + 8, '=');
+ case 249: return new DecodedChar(pos + 8, '>');
+ case 250: return new DecodedChar(pos + 8, '?');
+ case 251: return new DecodedChar(pos + 8, '_');
+ case 252: return new DecodedChar(pos + 8, ' ');
+ }
+
+ throw new RuntimeException("Decoding invalid ISO/IEC 646 value: " + eightBitValue);
+ }
+
+ private boolean isStillAlpha(int pos) {
+ if(pos + 5 > this.information.size) {
+ 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.size) {
+ 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));
+ }
+
+ switch(sixBitValue){
+ case 58: return new DecodedChar(pos + 6, '*');
+ case 59: return new DecodedChar(pos + 6, ',');
+ case 60: return new DecodedChar(pos + 6, '-');
+ case 61: return new DecodedChar(pos + 6, '.');
+ case 62: return new DecodedChar(pos + 6, '/');
+ }
+
+ throw new RuntimeException("Decoding invalid alphanumeric value: " + sixBitValue);
+ }
+
+ private boolean isAlphaTo646ToAlphaLatch(int pos) {
+ if(pos + 1 > this.information.size) {
+ return false;
+ }
+
+ for(int i = 0; i < 5 && i + pos < this.information.size; ++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.size) {
+ 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.size) {
+ return false;
+ }
+
+ for (int i = 0; i < 4 && i + pos < this.information.size; ++i) {
+ if (this.information.get(pos + i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/google/zxing/pdf417/PDF417Reader.java b/src/com/google/zxing/pdf417/PDF417Reader.java
new file mode 100644
index 0000000..4956c38
--- /dev/null
+++ b/src/com/google/zxing/pdf417/PDF417Reader.java
@@ -0,0 +1,79 @@
+/*
+ * 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.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.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.pdf417.decoder.Decoder;
+import com.google.zxing.pdf417.detector.Detector;
+import com.google.zxing.qrcode.QRCodeReader;
+
+import java.util.Hashtable;
+
+/**
+ * This implementation can detect and decode PDF417 codes in an image.
+ *
+ * @author SITA Lab (kevin.osullivan@sita.aero)
+ */
+public final class PDF417Reader implements Reader {
+
+ private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
+
+ private final Decoder decoder = new Decoder();
+
+ /**
+ * 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
+ */
+ public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
+ return decode(image, null);
+ }
+
+ public Result decode(BinaryBitmap image, Hashtable hints)
+ throws NotFoundException, FormatException {
+ DecoderResult decoderResult;
+ ResultPoint[] points;
+ if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
+ BitMatrix bits = QRCodeReader.extractPureBits(image.getBlackMatrix());
+ decoderResult = decoder.decode(bits);
+ points = NO_POINTS;
+ } else {
+ DetectorResult detectorResult = new Detector(image).detect();
+ decoderResult = decoder.decode(detectorResult.getBits());
+ points = detectorResult.getPoints();
+ }
+ return new Result(decoderResult.getText(), decoderResult.getRawBytes(), points,
+ BarcodeFormat.PDF417);
+ }
+
+ public void reset() {
+ // do nothing
+ }
+
+}
diff --git a/src/com/google/zxing/pdf417/decoder/BitMatrixParser.java b/src/com/google/zxing/pdf417/decoder/BitMatrixParser.java
new file mode 100644
index 0000000..d3aa30b
--- /dev/null
+++ b/src/com/google/zxing/pdf417/decoder/BitMatrixParser.java
@@ -0,0 +1,1163 @@
+/*
+ * 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.NotFoundException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ *
+ * This class parses the BitMatrix image into codewords.
+ *
+ *
+ * @author SITA Lab (kevin.osullivan@sita.aero)
+ */
+final class BitMatrixParser {
+
+ private static final int MAX_ROW_DIFFERENCE = 6;
+ private static final int MAX_ROWS = 90;
+ //private static final int MAX_COLUMNS = 30;
+ // Maximum Codewords (Data + Error)
+ private static final int MAX_CW_CAPACITY = 929;
+ private static final int MODULES_IN_SYMBOL = 17;
+
+ private final BitMatrix bitMatrix;
+ private int rows = 0;
+ //private int columns = 0;
+
+ private int leftColumnECData = 0;
+ private int rightColumnECData = 0;
+ private int eraseCount = 0;
+ private int[] erasures = null;
+ private int ecLevel = -1;
+
+ BitMatrixParser(BitMatrix bitMatrix) {
+ this.bitMatrix = bitMatrix;
+ }
+
+ /**
+ * To ensure separability of rows, codewords of consecutive rows belong to
+ * different subsets of all possible codewords. This routine scans the
+ * symbols in the barcode. When it finds a number of consecutive rows which
+ * are the same, it assumes that this is a row of codewords and processes
+ * them into a codeword array.
+ *
+ * @return an array of codewords.
+ */
+ int[] readCodewords() throws FormatException {
+ int width = bitMatrix.getWidth();
+ // TODO should be a rectangular matrix
+ int height = width;
+
+ erasures = new int[MAX_CW_CAPACITY];
+
+ // Get the number of pixels in a module across the X dimension
+ //float moduleWidth = bitMatrix.getModuleWidth();
+ float moduleWidth = 1.0f; // Image has been sampled and reduced
+
+ int[] rowCounters = new int[width];
+ int[] codewords = new int[MAX_CW_CAPACITY];
+ int next = 0;
+ int matchingConsecutiveScans = 0;
+ boolean rowInProgress = false;
+ int rowNumber = 0;
+ int rowHeight = 0;
+ for (int i = 1; i < height; i++) {
+ if (rowNumber >= MAX_ROWS) {
+ // Something is wrong, since we have exceeded
+ // the maximum rows in the specification.
+ // TODO Maybe return error code
+ return null;
+ }
+ int rowDifference = 0;
+ // Scan a line of modules and check the
+ // difference between this and the previous line
+ for (int j = 0; j < width; j++) {
+ // Accumulate differences between this line and the
+ // previous line.
+ if (bitMatrix.get(j, i) != bitMatrix.get(j, i - 1)) {
+ rowDifference++;
+ }
+ }
+ if (rowDifference <= moduleWidth * MAX_ROW_DIFFERENCE) {
+ for (int j = 0; j < width; j++) {
+ // Accumulate the black pixels on this line
+ if (bitMatrix.get(j, i)) {
+ rowCounters[j]++;
+ }
+ }
+ // Increment the number of consecutive rows of pixels
+ // that are more or less the same
+ matchingConsecutiveScans++;
+ // Height of a row is a multiple of the module size in pixels
+ // Usually at least 3 times the module size
+ if (matchingConsecutiveScans >= moduleWidth * 2) { // MGMG
+ // We have some previous matches as well as a match here
+ // Set processing a unique row.
+ rowInProgress = true;
+ }
+ } else {
+ if (rowInProgress) {
+ // Process Row
+ next = processRow(rowCounters, rowNumber, rowHeight, codewords, next);
+ if (next == -1) {
+ // Something is wrong, since we have exceeded
+ // the maximum columns in the specification.
+ // TODO Maybe return error code
+ return null;
+ }
+ // Reinitialize the row counters.
+ for (int j = 0; j < rowCounters.length; j++) {
+ rowCounters[j] = 0;
+ }
+ rowNumber++;
+ rowHeight = 0;
+ }
+ matchingConsecutiveScans = 0;
+ rowInProgress = false;
+ }
+ rowHeight++;
+ }
+ // Check for a row that was in progress before we exited above.
+ if (rowInProgress) {
+ // Process Row
+ if (rowNumber >= MAX_ROWS) {
+ // Something is wrong, since we have exceeded
+ // the maximum rows in the specification.
+ // TODO Maybe return error code
+ return null;
+ }
+ next = processRow(rowCounters, rowNumber, rowHeight, codewords, next);
+ rowNumber++;
+ rows = rowNumber;
+ }
+ erasures = trimArray(erasures, eraseCount);
+ return trimArray(codewords, next);
+ }
+
+ /**
+ * Trim the array to the required size.
+ *
+ * @param array the array
+ * @param size the size to trim it to
+ * @return the new trimmed array
+ */
+ private static int[] trimArray(int[] array, int size) {
+ if (size > 0) {
+ int[] a = new int[size];
+ for (int i = 0; i < size; i++) {
+ a[i] = array[i];
+ }
+ return a;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Convert the symbols in the row to codewords.
+ * Each PDF417 symbol character consists of four bar elements and four space
+ * elements, each of which can be one to six modules wide. The four bar and
+ * four space elements shall measure 17 modules in total.
+ *
+ * @param rowCounters an array containing the counts of black pixels for each column
+ * in the row.
+ * @param rowNumber the current row number of codewords.
+ * @param rowHeight the height of this row in pixels.
+ * @param codewords the codeword array to save codewords into.
+ * @param next the next available index into the codewords array.
+ * @return the next available index into the codeword array after processing
+ * this row.
+ */
+ int processRow(int[] rowCounters, int rowNumber, int rowHeight, int[] codewords, int next)
+ throws FormatException {
+ int width = bitMatrix.getWidth();
+ int columnNumber = 0;
+ long symbol = 0;
+ for (int i = 0; i < width; i += MODULES_IN_SYMBOL) {
+ // This happens in real life and is almost surely a rare misdecode
+ if (i + MODULES_IN_SYMBOL > rowCounters.length) {
+ throw FormatException.getFormatInstance();
+ }
+ for (int mask = MODULES_IN_SYMBOL - 1; mask >= 0; mask--) {
+ if (rowCounters[i + (MODULES_IN_SYMBOL - 1 - mask)] >= rowHeight >>> 1) {
+ symbol |= 1L << mask;
+ }
+ }
+ if (columnNumber > 0) {
+ int cw = getCodeword(symbol);
+ // if (debug) System.out.println(" " + Long.toBinaryString(symbol) +
+ // " cw=" +cw + " ColumnNumber=" +columnNumber + "i=" +i);
+ if (cw < 0 && i < width - MODULES_IN_SYMBOL) {
+ // Skip errors on the Right row indicator column
+ erasures[eraseCount] = next;
+ next++;
+ eraseCount++;
+ } else {
+ codewords[next++] = cw;
+ }
+ } else {
+ // Left row indicator column
+ int cw = getCodeword(symbol);
+ // if (debug) System.out.println(" " + Long.toBinaryString(symbol) +
+ // " cw=" +cw + " ColumnNumber=" +columnNumber + "i=" +i);
+ if (ecLevel < 0) {
+ switch (rowNumber % 3) {
+ case 0:
+ break;
+ case 1:
+ leftColumnECData = cw;
+ break;
+ case 2:
+ break;
+ }
+ }
+ }
+ symbol = 0;
+ //columns = columnNumber;
+ columnNumber++;
+ }
+ if (columnNumber > 1) {
+ // Right row indicator column is in codeword[next]
+ //columns--;
+ // Overwrite the last codeword i.e. Right Row Indicator
+ --next;
+ if (ecLevel < 0) {
+ switch (rowNumber % 3) {
+ case 0:
+ break;
+ case 1:
+ break;
+ case 2:
+ rightColumnECData = codewords[next];
+ if (rightColumnECData == leftColumnECData
+ && leftColumnECData != 0) {
+ ecLevel = ((rightColumnECData % 30) - rows % 3) / 3;
+ }
+ break;
+ }
+ }
+ codewords[next] = 0;
+ }
+ return next;
+ }
+
+ /**
+ * Build a symbol from the pixels.
+ * Each symbol character is defined by an 8-digit bar-space sequence which
+ * represents the module widths of the eight elements of that symbol
+ * character.
+ *
+ * @param counters array of pixel counter corresponding to each Bar/Space pattern.
+ * @return the symbol
+ */
+ /*
+ private static long getSymbol(int[] counters, float moduleWidth) {
+ int pixelsInSymbol = 0;
+ for (int j = 0; j < counters.length; j++) {
+ pixelsInSymbol += counters[j];
+ }
+ float avgModuleWidth = (pixelsInSymbol / 17.0f);
+ boolean toggle = true;
+ int shift = 0;
+ int symbol = 0;
+ for (int j = 0; j < counters.length; j++) {
+ if (counters[j] < moduleWidth && counters[j] > 0) {
+ // Give a very narrow bar/space a chance
+ counters[j] = (int) moduleWidth;
+ }
+ // Calculate number of modules in the symbol.
+ // int modules = (int)(counters[j]/moduleWidth);
+ // int modules = round(counters[j]/moduleWidth);
+ int modules = round(counters[j] / avgModuleWidth);
+ if (modules > 6) {
+ // Maximum size is 6 modules
+ modules = 6;
+ } else if (modules < 1) {
+ modules = 1;
+ }
+ if (toggle) {
+ for (int k = 0; k < modules; k++) {
+ symbol |= 1 << (16 - k - shift);
+ }
+ toggle = false;
+ } else {
+ toggle = true;
+ }
+ shift += modules;
+ }
+ return symbol;
+ }
+ */
+
+ /**
+ * Translate the symbol into a codeword.
+ *
+ * @param symbol
+ * @return the codeword corresponding to the symbol.
+ */
+ private static int getCodeword(long symbol) {
+ long sym = symbol;
+ sym &= 0x3ffff;
+ int i = findCodewordIndex(sym);
+ if (i == -1) {
+ return -1;
+ } else {
+ long cw = CODEWORD_TABLE[i] - 1;
+ cw %= 929;
+ return (int) cw;
+ }
+ }
+
+ /**
+ * 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; // repeat search in bottom half.
+ } else if (symbol > SYMBOL_TABLE[mid]) {
+ first = mid + 1; // Repeat search in top half.
+ } else {
+ return mid; // Found it. return position
+ }
+ }
+ return -1;
+ // if (debug) System.out.println("Failed to find codeword for Symbol=" +
+ // symbol);
+ }
+
+ /**
+ * Ends up being a bit faster than Math.round(). This merely rounds its
+ * argument to the nearest int, where x.5 rounds up.
+ */
+ /*
+ private static int round(float d) {
+ return (int) (d + 0.5f);
+ }
+ */
+
+ /**
+ * Returns an array of locations representing the erasures.
+ */
+ public int[] getErasures() {
+ return erasures;
+ }
+
+ public int getECLevel() {
+ return ecLevel;
+ }
+
+ /**
+ * Convert the symbols in the row to codewords.
+ * Each PDF417 symbol character consists of four bar elements and four space
+ * elements, each of which can be one to six modules wide. The four bar and
+ * four space elements shall measure 17 modules in total.
+ *
+ * @param rowCounters an array containing the counts of black pixels for each column
+ * in the row.
+ * @param rowNumber the current row number of codewords.
+ * @param rowHeight the height of this row in pixels.
+ * @param moduleWidth the size of a module in pixels.
+ * @param codewords the codeword array to save codewords into.
+ * @param next the next available index into the codewords array.
+ * @return the next available index into the codeword array after processing
+ * this row.
+ * @throws NotFoundException
+ */
+ /*
+ int processRow1(int[] rowCounters, int rowNumber, int rowHeight,
+ float moduleWidth, int[] codewords, int next) {
+ int width = bitMatrix.getDimension();
+ int firstBlack = 0;
+
+ for (firstBlack = 0; firstBlack < width; firstBlack++) {
+ // Step forward until we find the first black pixels
+ if (rowCounters[firstBlack] >= rowHeight >>> 1) {
+ break;
+ }
+ }
+
+ int[] counters = new int[8];
+ int state = 0; // In black pixels, looking for white, first or second time
+ long symbol = 0;
+ int columnNumber = 0;
+ for (int i = firstBlack; i < width; i++) {
+ if (state == 1 || state == 3 || state == 5 || state == 7) { // In white
+ // pixels,
+ // looking
+ // for
+ // black
+ // If more than half the column is black
+ if (rowCounters[i] >= rowHeight >>> 1 || i == width - 1) {
+ if (i == width - 1) {
+ counters[state]++;
+ }
+ // In black pixels or the end of a row
+ state++;
+ if (state < 8) {
+ // Don't count the last one
+ counters[state]++;
+ }
+ } else {
+ counters[state]++;
+ }
+ } else {
+ if (rowCounters[i] < rowHeight >>> 1) {
+ // Found white pixels
+ state++;
+ if (state == 7 && i == width - 1) {
+ // Its found white pixels at the end of the row,
+ // give it a chance to exit gracefully
+ i--;
+ } else {
+ // Found white pixels
+ counters[state]++;
+ }
+ } else {
+ if (state < 8) {
+ // Still in black pixels
+ counters[state]++;
+ }
+ }
+ }
+ if (state == 8) { // Found black, white, black, white, black, white,
+ // black, white and stumbled back onto black; done
+ if (columnNumber >= MAX_COLUMNS) {
+ // Something is wrong, since we have exceeded
+ // the maximum columns in the specification.
+ // TODO Maybe return error code
+ return -1;
+ }
+ if (columnNumber > 0) {
+ symbol = getSymbol(counters, moduleWidth);
+ int cw = getCodeword(symbol);
+ // if (debug) System.out.println(" " +
+ // Long.toBinaryString(symbol) + " cw=" +cw + " ColumnNumber="
+ // +columnNumber + "i=" +i);
+ if (cw < 0) {
+ erasures[eraseCount] = next;
+ next++;
+ eraseCount++;
+ } else {
+ codewords[next++] = cw;
+ }
+ } else {
+ // Left row indicator column
+ symbol = getSymbol(counters, moduleWidth);
+ int cw = getCodeword(symbol);
+ if (ecLevel < 0) {
+ switch (rowNumber % 3) {
+ case 0:
+ break;
+ case 1:
+ leftColumnECData = cw;
+ break;
+ case 2:
+ break;
+ }
+ }
+ }
+ // Step back so that this pixel can be examined during the next
+ // pass.
+ i--;
+ counters = new int[8];
+ columns = columnNumber;
+ columnNumber++;
+ // Introduce some errors if (rowNumber == 0 && columnNumber == 4)
+ // { codewords[next-1] = 0; erasures[eraseCount] = next-1;
+ // eraseCount++; } if (rowNumber == 0 && columnNumber == 6) {
+ // codewords[next-1] = 10; erasures[eraseCount] = next-1;
+ // eraseCount++; } if (rowNumber == 0 && columnNumber == 8) {
+ // codewords[next-1] = 10; erasures[eraseCount] = next-1;
+ // eraseCount++; }
+ state = 0;
+ symbol = 0;
+ }
+ }
+ if (columnNumber > 1) {
+ // Right row indicator column is in codeword[next]
+ columns--;
+ // Overwrite the last codeword i.e. Right Row Indicator
+ --next;
+ if (ecLevel < 0) {
+ switch (rowNumber % 3) {
+ case 0:
+ break;
+ case 1:
+ break;
+ case 2:
+ rightColumnECData = codewords[next];
+ if (rightColumnECData == leftColumnECData
+ && leftColumnECData != 0) {
+ ecLevel = ((rightColumnECData % 30) - rows % 3) / 3;
+ }
+ break;
+ }
+ }
+ codewords[next] = 0;
+ }
+ return next;
+ }
+ */
+
+ /**
+ * 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.
+ */
+ private 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/src/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java b/src/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java
new file mode 100644
index 0000000..f224e59
--- /dev/null
+++ b/src/com/google/zxing/pdf417/decoder/DecodedBitStreamParser.java
@@ -0,0 +1,629 @@
+/*
+ * 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;
+
+/**
+ * This class contains the methods for decoding the PDF417 codewords.
+ *
+ * @author SITA Lab (kevin.osullivan@sita.aero)
+ */
+final class DecodedBitStreamParser {
+
+ 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 ALPHA = 0;
+ private static final int LOWER = 1;
+ private static final int MIXED = 2;
+ private static final int PUNCT = 3;
+ private static final int PUNCT_SHIFT = 4;
+
+ 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 = {';', '<', '>', '@', '[', 92, '}', '_', 96, '~', '!',
+ 13, 9, ',', ':', 10, '-', '.', '$', '/', 34, '|', '*',
+ '(', ')', '?', '{', '}', 39};
+
+ private static final char[] MIXED_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '&',
+ 13, 9, ',', ':', '#', '-', '.', '$', '/', '+', '%', '*',
+ '=', '^'};
+
+ // Table containing values for the exponent of 900.
+ // This is used in the numeric compaction decode algorithm.
+ private static final String[] EXP900 =
+ { "000000000000000000000000000000000000000000001",
+ "000000000000000000000000000000000000000000900",
+ "000000000000000000000000000000000000000810000",
+ "000000000000000000000000000000000000729000000",
+ "000000000000000000000000000000000656100000000",
+ "000000000000000000000000000000590490000000000",
+ "000000000000000000000000000531441000000000000",
+ "000000000000000000000000478296900000000000000",
+ "000000000000000000000430467210000000000000000",
+ "000000000000000000387420489000000000000000000",
+ "000000000000000348678440100000000000000000000",
+ "000000000000313810596090000000000000000000000",
+ "000000000282429536481000000000000000000000000",
+ "000000254186582832900000000000000000000000000",
+ "000228767924549610000000000000000000000000000",
+ "205891132094649000000000000000000000000000000"};
+
+ private DecodedBitStreamParser() {
+ }
+
+ static DecoderResult decode(int[] codewords) throws FormatException {
+ StringBuffer result = new StringBuffer(100);
+ // Get compaction mode
+ int codeIndex = 1;
+ int code = codewords[codeIndex++];
+ while (codeIndex < codewords[0]) {
+ switch (code) {
+ case TEXT_COMPACTION_MODE_LATCH: {
+ codeIndex = textCompaction(codewords, codeIndex, result);
+ break;
+ }
+ case BYTE_COMPACTION_MODE_LATCH: {
+ codeIndex = byteCompaction(code, codewords, codeIndex, result);
+ break;
+ }
+ case NUMERIC_COMPACTION_MODE_LATCH: {
+ codeIndex = numericCompaction(codewords, codeIndex, result);
+ break;
+ }
+ case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: {
+ codeIndex = byteCompaction(code, codewords, codeIndex, result);
+ break;
+ }
+ case BYTE_COMPACTION_MODE_LATCH_6: {
+ codeIndex = byteCompaction(code, codewords, codeIndex, result);
+ break;
+ }
+ 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();
+ }
+ }
+ return new DecoderResult(null, result.toString(), null, null);
+ }
+
+ /**
+ * 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, StringBuffer result) {
+ // 2 character per codeword
+ int[] textCompactionData = new int[codewords[0] << 1];
+ // Used to hold the byte compaction value if there is a mode shift
+ int[] byteCompactionData = new int[codewords[0] << 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: {
+ codeIndex--;
+ end = true;
+ break;
+ }
+ case BYTE_COMPACTION_MODE_LATCH: {
+ codeIndex--;
+ end = true;
+ break;
+ }
+ case NUMERIC_COMPACTION_MODE_LATCH: {
+ 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;
+ byteCompactionData[index] = code; //Integer.toHexString(code);
+ index++;
+ break;
+ }
+ case BYTE_COMPACTION_MODE_LATCH_6: {
+ codeIndex--;
+ end = true;
+ 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,
+ StringBuffer 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.
+ int subMode = ALPHA;
+ int priorToShiftMode = 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 = LOWER;
+ } else if (subModeCh == ML) {
+ subMode = MIXED;
+ } else if (subModeCh == PS) {
+ // Shift to punctuation
+ priorToShiftMode = subMode;
+ subMode = PUNCT_SHIFT;
+ } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
+ result.append((char) byteCompactionData[i]);
+ }
+ }
+ break;
+
+ case LOWER:
+ // Lower (lowercase alphabetic)
+ if (subModeCh < 26) {
+ ch = (char) ('a' + subModeCh);
+ } else {
+ if (subModeCh == 26) {
+ ch = ' ';
+ } else if (subModeCh == AL) {
+ subMode = ALPHA;
+ } else if (subModeCh == ML) {
+ subMode = MIXED;
+ } else if (subModeCh == PS) {
+ // Shift to punctuation
+ priorToShiftMode = subMode;
+ subMode = PUNCT_SHIFT;
+ } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
+ result.append((char) byteCompactionData[i]);
+ }
+ }
+ break;
+
+ case MIXED:
+ // Mixed (numeric and some punctuation)
+ if (subModeCh < PL) {
+ ch = MIXED_CHARS[subModeCh];
+ } else {
+ if (subModeCh == PL) {
+ subMode = PUNCT;
+ } else if (subModeCh == 26) {
+ ch = ' ';
+ } else if (subModeCh == AS) {
+ //mode_change = true;
+ } else if (subModeCh == AL) {
+ subMode = ALPHA;
+ } else if (subModeCh == PS) {
+ // Shift to punctuation
+ priorToShiftMode = subMode;
+ subMode = PUNCT_SHIFT;
+ } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
+ result.append((char) byteCompactionData[i]);
+ }
+ }
+ break;
+
+ case PUNCT:
+ // Punctuation
+ if (subModeCh < PS) {
+ ch = PUNCT_CHARS[subModeCh];
+ } else {
+ if (subModeCh == PAL) {
+ subMode = ALPHA;
+ } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) {
+ result.append((char) byteCompactionData[i]);
+ }
+ }
+ break;
+
+ case PUNCT_SHIFT:
+ // Restore sub-mode
+ subMode = priorToShiftMode;
+ if (subModeCh < PS) {
+ ch = PUNCT_CHARS[subModeCh];
+ } else {
+ if (subModeCh == PAL) {
+ subMode = 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, StringBuffer 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;
+ while ((codeIndex < codewords[0]) && !end) {
+ int code = codewords[codeIndex++];
+ if (code < TEXT_COMPACTION_MODE_LATCH) {
+ byteCompactedCodewords[count] = code;
+ 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
+ for (int j = 0; j < 6; ++j) {
+ decodedData[5 - j] = (char) (value % 256);
+ value >>= 8;
+ }
+ result.append(decodedData);
+ count = 0;
+ }
+ }
+ // If Byte Compaction mode is invoked with codeword 901,
+ // the final group of codewords is interpreted directly
+ // as one byte per codeword, without compaction.
+ for (int i = ((count / 5) * 5); 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);
+ }
+ }
+ }
+ 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, StringBuffer result) {
+ 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
+
+ As there are huge numbers involved here we must use fake out the maths using string
+ tokens for the numbers.
+ BigDecimal is not supported by J2ME.
+ */
+ private static String decodeBase900toBase10(int[] codewords, int count) {
+ StringBuffer accum = null;
+ for (int i = 0; i < count; i++) {
+ StringBuffer value = multiply(EXP900[count - i - 1], codewords[i]);
+ if (accum == null) {
+ // First time in accum=0
+ accum = value;
+ } else {
+ accum = add(accum.toString(), value.toString());
+ }
+ }
+ String result = null;
+ // Remove leading '1' which was inserted to preserve
+ // leading zeros
+ for (int i = 0; i < accum.length(); i++) {
+ if (accum.charAt(i) == '1') {
+ //result = accum.substring(i + 1);
+ result = accum.toString().substring(i + 1);
+ break;
+ }
+ }
+ if (result == null) {
+ // No leading 1 => just write the converted number.
+ result = accum.toString();
+ }
+ return result;
+ }
+
+ /**
+ * Multiplies two String numbers
+ *
+ * @param value1 Any number represented as a string.
+ * @param value2 A number <= 999.
+ * @return the result of value1 * value2.
+ */
+ private static StringBuffer multiply(String value1, int value2) {
+ StringBuffer result = new StringBuffer(value1.length());
+ for (int i = 0; i < value1.length(); i++) {
+ // Put zeros into the result.
+ result.append('0');
+ }
+ int hundreds = value2 / 100;
+ int tens = (value2 / 10) % 10;
+ int ones = value2 % 10;
+ // Multiply by ones
+ for (int j = 0; j < ones; j++) {
+ result = add(result.toString(), value1);
+ }
+ // Multiply by tens
+ for (int j = 0; j < tens; j++) {
+ result = add(result.toString(), (value1 + '0').substring(1));
+ }
+ // Multiply by hundreds
+ for (int j = 0; j < hundreds; j++) {
+ result = add(result.toString(), (value1 + "00").substring(2));
+ }
+ return result;
+ }
+
+ /**
+ * Add two numbers which are represented as strings.
+ *
+ * @param value1
+ * @param value2
+ * @return the result of value1 + value2
+ */
+ private static StringBuffer add(String value1, String value2) {
+ StringBuffer temp1 = new StringBuffer(5);
+ StringBuffer temp2 = new StringBuffer(5);
+ StringBuffer result = new StringBuffer(value1.length());
+ for (int i = 0; i < value1.length(); i++) {
+ // Put zeros into the result.
+ result.append('0');
+ }
+ int carry = 0;
+ for (int i = value1.length() - 3; i > -1; i -= 3) {
+
+ temp1.setLength(0);
+ temp1.append(value1.charAt(i));
+ temp1.append(value1.charAt(i + 1));
+ temp1.append(value1.charAt(i + 2));
+
+ temp2.setLength(0);
+ temp2.append(value2.charAt(i));
+ temp2.append(value2.charAt(i + 1));
+ temp2.append(value2.charAt(i + 2));
+
+ int intValue1 = Integer.parseInt(temp1.toString());
+ int intValue2 = Integer.parseInt(temp2.toString());
+
+ int sumval = (intValue1 + intValue2 + carry) % 1000;
+ carry = (intValue1 + intValue2 + carry) / 1000;
+
+ result.setCharAt(i + 2, (char) ((sumval % 10) + '0'));
+ result.setCharAt(i + 1, (char) (((sumval / 10) % 10) + '0'));
+ result.setCharAt(i, (char) ((sumval / 100) + '0'));
+ }
+ return result;
+ }
+
+ /*
+ private static String decodeBase900toBase10(int codewords[], int count) {
+ BigInteger accum = BigInteger.valueOf(0);
+ BigInteger value = null;
+ for (int i = 0; i < count; i++) {
+ value = BigInteger.valueOf(900).pow(count - i - 1);
+ value = value.multiply(BigInteger.valueOf(codewords[i]));
+ accum = accum.add(value);
+ }
+ if (debug) System.out.println("Big Integer " + accum);
+ String result = accum.toString().substring(1);
+ return result;
+ }
+ */
+}
diff --git a/src/com/google/zxing/pdf417/decoder/Decoder.java b/src/com/google/zxing/pdf417/decoder/Decoder.java
new file mode 100644
index 0000000..a3d13fd
--- /dev/null
+++ b/src/com/google/zxing/pdf417/decoder/Decoder.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.pdf417.decoder;
+
+import com.google.zxing.ChecksumException;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+//import com.google.zxing.pdf417.reedsolomon.ReedSolomonDecoder;
+
+/**
+ * The main class which implements PDF417 Code decoding -- as
+ * opposed to locating and extracting the PDF417 Code from an image.
+ *
+ * @author SITA Lab (kevin.osullivan@sita.aero)
+ */
+public final class Decoder {
+
+ private static final int MAX_ERRORS = 3;
+ private static final int MAX_EC_CODEWORDS = 512;
+ //private final ReedSolomonDecoder rsDecoder;
+
+ public Decoder() {
+ // TODO MGMG
+ //rsDecoder = new ReedSolomonDecoder();
+ }
+
+ /**
+ * Convenience method that can decode a PDF417 Code represented as a 2D array of booleans.
+ * "true" is taken to mean a black module.
+ *
+ * @param image booleans representing white/black PDF417 modules
+ * @return text and bytes encoded within the PDF417 Code
+ * @throws NotFoundException if the PDF417 Code cannot be decoded
+ */
+ public DecoderResult decode(boolean[][] image) throws 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[j][i]) {
+ bits.set(j, i);
+ }
+ }
+ }
+ return decode(bits);
+ }
+
+ /**
+ * Decodes a PDF417 Code represented as a {@link BitMatrix}.
+ * A 1 or "true" is taken to mean a black module.
+ *
+ * @param bits booleans representing white/black PDF417 Code modules
+ * @return text and bytes encoded within the PDF417 Code
+ * @throws FormatException if the PDF417 Code cannot be decoded
+ */
+ public DecoderResult decode(BitMatrix bits) throws FormatException {
+ // Construct a parser to read the data codewords and error-correction level
+ BitMatrixParser parser = new BitMatrixParser(bits);
+ int[] codewords = parser.readCodewords();
+ if (codewords == null || codewords.length == 0) {
+ throw FormatException.getFormatInstance();
+ }
+
+ int ecLevel = parser.getECLevel();
+ int numECCodewords = 1 << (ecLevel + 1);
+ int[] erasures = parser.getErasures();
+
+ correctErrors(codewords, erasures, numECCodewords);
+ verifyCodewordCount(codewords, numECCodewords);
+
+ // Decode the codewords
+ return DecodedBitStreamParser.decode(codewords);
+ }
+
+ /**
+ * Verify that all is OK with the codeword array.
+ *
+ * @param codewords
+ * @return an index to the first data codeword.
+ * @throws FormatException
+ */
+ 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();
+ }
+ }
+ }
+
+ /**
+ * Given data and error-correction codewords received, possibly corrupted by errors, attempts to
+ * correct the errors in-place using Reed-Solomon error correction.
+ *
+ * @param codewords data and error correction codewords
+ * @throws ChecksumException if error correction fails
+ */
+ private static int correctErrors(int[] codewords, int[] erasures, int numECCodewords) throws FormatException {
+ if ((erasures != null && erasures.length > numECCodewords / 2 + MAX_ERRORS) ||
+ (numECCodewords < 0 || numECCodewords > MAX_EC_CODEWORDS)) {
+ // Too many errors or EC Codewords is corrupted
+ throw FormatException.getFormatInstance();
+ }
+ // Try to correct the errors
+ // TODO enable error correction
+ int result = 0; // rsDecoder.correctErrors(codewords, numECCodewords);
+ if (erasures != null) {
+ int numErasures = erasures.length;
+ if (result > 0) {
+ numErasures -= result;
+ }
+ if (numErasures > MAX_ERRORS) {
+ // Still too many errors
+ throw FormatException.getFormatInstance();
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/pdf417/detector/Detector.java b/src/com/google/zxing/pdf417/detector/Detector.java
new file mode 100644
index 0000000..a8ceef0
--- /dev/null
+++ b/src/com/google/zxing/pdf417/detector/Detector.java
@@ -0,0 +1,498 @@
+/*
+ * 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.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 java.util.Hashtable;
+
+/**
+ * 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)
+ */
+public final class Detector {
+
+ private static final int MAX_AVG_VARIANCE = (int) ((1 << 8) * 0.42f);
+ private static final int MAX_INDIVIDUAL_VARIANCE = (int) ((1 << 8) * 0.8f);
+ private static final int SKEW_THRESHOLD = 2;
+
+ // 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};
+
+ // 11111111 0 1 0 1 0 1 000
+ private static final int[] START_PATTERN_REVERSE = {3, 1, 1, 1, 1, 1, 1, 8};
+
+ // 1111111 0 1 000 1 0 1 00 1
+ private static final int[] STOP_PATTERN = {7, 1, 1, 3, 1, 1, 1, 2, 1};
+
+ // B S B S B S B S B Bar/Space pattern
+ // 1111111 0 1 000 1 0 1 00 1
+ private static final int[] STOP_PATTERN_REVERSE = {1, 2, 1, 1, 1, 3, 1, 1, 7};
+
+ private final BinaryBitmap image;
+
+ public Detector(BinaryBitmap image) {
+ this.image = image;
+ }
+
+ /**
+ * Detects a PDF417 Code in an image, simply.
+ *
+ * @return {@link DetectorResult} encapsulating results of detecting a PDF417 Code
+ * @throws NotFoundException if no QR Code can be found
+ */
+ public DetectorResult detect() throws NotFoundException {
+ return detect(null);
+ }
+
+ /**
+ * Detects a PDF417 Code in an image. Only checks 0 and 180 degree rotations.
+ *
+ * @param hints optional hints to detector
+ * @return {@link DetectorResult} encapsulating results of detecting a PDF417 Code
+ * @throws NotFoundException if no PDF417 Code can be found
+ */
+ public DetectorResult detect(Hashtable hints) throws NotFoundException {
+ // Fetch the 1 bit matrix once up front.
+ BitMatrix matrix = image.getBlackMatrix();
+
+ // Try to find the vertices assuming the image is upright.
+ ResultPoint[] vertices = findVertices(matrix);
+ if (vertices == null) {
+ // Maybe the image is rotated 180 degrees?
+ vertices = findVertices180(matrix);
+ if (vertices != null) {
+ correctCodeWordVertices(vertices, true);
+ }
+ } else {
+ correctCodeWordVertices(vertices, false);
+ }
+
+ if (vertices == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ float moduleWidth = computeModuleWidth(vertices);
+ if (moduleWidth < 1.0f) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int dimension = computeDimension(vertices[4], vertices[6],
+ vertices[5], vertices[7], moduleWidth);
+ if (dimension < 1) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Deskew and sample image.
+ BitMatrix bits = sampleGrid(matrix, vertices[4], vertices[5],
+ vertices[6], vertices[7], dimension);
+ return new DetectorResult(bits, new ResultPoint[]{vertices[4],
+ vertices[5], vertices[6], vertices[7]});
+ }
+
+ /**
+ * Locate the vertices and the codewords area of a black blob using the Start
+ * and Stop patterns as locators.
+ * TODO: Scanning every row is very expensive. We should only do this for TRY_HARDER.
+ *
+ * @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 height = matrix.getHeight();
+ int width = matrix.getWidth();
+
+ ResultPoint[] result = new ResultPoint[8];
+ boolean found = false;
+
+ // Top Left
+ for (int i = 0; i < height; i++) {
+ int[] loc = findGuardPattern(matrix, 0, i, width, false, START_PATTERN);
+ if (loc != null) {
+ result[0] = new ResultPoint(loc[0], i);
+ result[4] = new ResultPoint(loc[1], i);
+ found = true;
+ break;
+ }
+ }
+ // Bottom left
+ if (found) { // Found the Top Left vertex
+ found = false;
+ for (int i = height - 1; i > 0; i--) {
+ int[] loc = findGuardPattern(matrix, 0, i, width, false, START_PATTERN);
+ if (loc != null) {
+ result[1] = new ResultPoint(loc[0], i);
+ result[5] = new ResultPoint(loc[1], i);
+ found = true;
+ break;
+ }
+ }
+ }
+ // Top right
+ if (found) { // Found the Bottom Left vertex
+ found = false;
+ for (int i = 0; i < height; i++) {
+ int[] loc = findGuardPattern(matrix, 0, i, width, false, STOP_PATTERN);
+ if (loc != null) {
+ result[2] = new ResultPoint(loc[1], i);
+ result[6] = new ResultPoint(loc[0], i);
+ found = true;
+ break;
+ }
+ }
+ }
+ // Bottom right
+ if (found) { // Found the Top right vertex
+ found = false;
+ for (int i = height - 1; i > 0; i--) {
+ int[] loc = findGuardPattern(matrix, 0, i, width, false, STOP_PATTERN);
+ if (loc != null) {
+ result[3] = new ResultPoint(loc[1], i);
+ result[7] = new ResultPoint(loc[0], i);
+ found = true;
+ break;
+ }
+ }
+ }
+ return found ? result : null;
+ }
+
+ /**
+ * Locate the vertices and the codewords area of a black blob using the Start
+ * and Stop patterns as locators. This assumes that the image is rotated 180
+ * degrees and if it locates the start and stop patterns at it will re-map
+ * the vertices for a 0 degree rotation.
+ * TODO: Change assumption about barcode location.
+ * TODO: Scanning every row is very expensive. We should only do this for TRY_HARDER.
+ *
+ * @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[] findVertices180(BitMatrix matrix) {
+ int height = matrix.getHeight();
+ int width = matrix.getWidth();
+ int halfWidth = width >> 1;
+
+ ResultPoint[] result = new ResultPoint[8];
+ boolean found = false;
+
+ // Top Left
+ for (int i = height - 1; i > 0; i--) {
+ int[] loc = findGuardPattern(matrix, halfWidth, i, halfWidth, true, START_PATTERN_REVERSE);
+ if (loc != null) {
+ result[0] = new ResultPoint(loc[1], i);
+ result[4] = new ResultPoint(loc[0], i);
+ found = true;
+ break;
+ }
+ }
+ // Bottom Left
+ if (found) { // Found the Top Left vertex
+ found = false;
+ for (int i = 0; i < height; i++) {
+ int[] loc = findGuardPattern(matrix, halfWidth, i, halfWidth, true, START_PATTERN_REVERSE);
+ if (loc != null) {
+ result[1] = new ResultPoint(loc[1], i);
+ result[5] = new ResultPoint(loc[0], i);
+ found = true;
+ break;
+ }
+ }
+ }
+ // Top Right
+ if (found) { // Found the Bottom Left vertex
+ found = false;
+ for (int i = height - 1; i > 0; i--) {
+ int[] loc = findGuardPattern(matrix, 0, i, halfWidth, false, STOP_PATTERN_REVERSE);
+ if (loc != null) {
+ result[2] = new ResultPoint(loc[0], i);
+ result[6] = new ResultPoint(loc[1], i);
+ found = true;
+ break;
+ }
+ }
+ }
+ // Bottom Right
+ if (found) { // Found the Top Right vertex
+ found = false;
+ for (int i = 0; i < height; i++) {
+ int[] loc = findGuardPattern(matrix, 0, i, halfWidth, false, STOP_PATTERN_REVERSE);
+ if (loc != null) {
+ result[3] = new ResultPoint(loc[0], i);
+ result[7] = new ResultPoint(loc[1], i);
+ found = true;
+ break;
+ }
+ }
+ }
+ return found ? result : null;
+ }
+
+ /**
+ * Because we scan horizontally to detect the start and stop patterns, the vertical component of
+ * the codeword coordinates will be slightly wrong if there is any skew or rotation in the image.
+ * This method moves those points back onto the edges of the theoretically perfect bounding
+ * quadrilateral if needed.
+ *
+ * @param vertices The eight vertices located by findVertices().
+ */
+ private static void correctCodeWordVertices(ResultPoint[] vertices, boolean upsideDown) {
+ float skew = vertices[4].getY() - vertices[6].getY();
+ if (upsideDown) {
+ skew = -skew;
+ }
+ if (skew > SKEW_THRESHOLD) {
+ // Fix v4
+ float length = vertices[4].getX() - vertices[0].getX();
+ float deltax = vertices[6].getX() - vertices[0].getX();
+ float deltay = vertices[6].getY() - vertices[0].getY();
+ float correction = length * deltay / deltax;
+ vertices[4] = new ResultPoint(vertices[4].getX(), vertices[4].getY() + correction);
+ } else if (-skew > SKEW_THRESHOLD) {
+ // Fix v6
+ float length = vertices[2].getX() - vertices[6].getX();
+ float deltax = vertices[2].getX() - vertices[4].getX();
+ float deltay = vertices[2].getY() - vertices[4].getY();
+ float correction = length * deltay / deltax;
+ vertices[6] = new ResultPoint(vertices[6].getX(), vertices[6].getY() - correction);
+ }
+
+ skew = vertices[7].getY() - vertices[5].getY();
+ if (upsideDown) {
+ skew = -skew;
+ }
+ if (skew > SKEW_THRESHOLD) {
+ // Fix v5
+ float length = vertices[5].getX() - vertices[1].getX();
+ float deltax = vertices[7].getX() - vertices[1].getX();
+ float deltay = vertices[7].getY() - vertices[1].getY();
+ float correction = length * deltay / deltax;
+ vertices[5] = new ResultPoint(vertices[5].getX(), vertices[5].getY() + correction);
+ } else if (-skew > SKEW_THRESHOLD) {
+ // Fix v7
+ float length = vertices[3].getX() - vertices[7].getX();
+ float deltax = vertices[3].getX() - vertices[5].getX();
+ float deltay = vertices[3].getY() - vertices[5].getY();
+ float correction = length * deltay / deltax;
+ vertices[7] = new ResultPoint(vertices[7].getX(), vertices[7].getY() - correction);
+ }
+ }
+
+ /**
+ * Estimates module size (pixels in a module) based on the Start and End
+ * finder patterns.
+ *
+ * @param vertices an array of 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
+ * @return the module size.
+ */
+ private static float computeModuleWidth(ResultPoint[] vertices) {
+ float pixels1 = ResultPoint.distance(vertices[0], vertices[4]);
+ float pixels2 = ResultPoint.distance(vertices[1], vertices[5]);
+ float moduleWidth1 = (pixels1 + pixels2) / (17 * 2.0f);
+ float pixels3 = ResultPoint.distance(vertices[6], vertices[2]);
+ float pixels4 = ResultPoint.distance(vertices[7], vertices[3]);
+ float moduleWidth2 = (pixels3 + pixels4) / (18 * 2.0f);
+ return (moduleWidth1 + moduleWidth2) / 2.0f;
+ }
+
+ /**
+ * Computes the dimension (number of modules in a row) of the PDF417 Code
+ * based on vertices of the codeword area and estimated module size.
+ *
+ * @param topLeft of codeword area
+ * @param topRight of codeword area
+ * @param bottomLeft of codeword area
+ * @param bottomRight of codeword are
+ * @param moduleWidth estimated module size
+ * @return the number of modules in a row.
+ */
+ private static int computeDimension(ResultPoint topLeft, ResultPoint topRight,
+ ResultPoint bottomLeft, ResultPoint bottomRight, float moduleWidth) {
+ int topRowDimension = round(ResultPoint.distance(topLeft, topRight) / moduleWidth);
+ int bottomRowDimension = round(ResultPoint.distance(bottomLeft, bottomRight) / moduleWidth);
+ return ((((topRowDimension + bottomRowDimension) >> 1) + 8) / 17) * 17;
+ /*
+ * int topRowDimension = round(ResultPoint.distance(topLeft,
+ * topRight)); //moduleWidth); int bottomRowDimension =
+ * round(ResultPoint.distance(bottomLeft, bottomRight)); //
+ * moduleWidth); int dimension = ((topRowDimension + bottomRowDimension)
+ * >> 1); // Round up to nearest 17 modules i.e. there are 17 modules per
+ * codeword //int dimension = ((((topRowDimension + bottomRowDimension) >>
+ * 1) + 8) / 17) * 17; return dimension;
+ */
+ }
+
+ private static BitMatrix sampleGrid(BitMatrix matrix, ResultPoint topLeft,
+ ResultPoint bottomLeft, ResultPoint topRight, ResultPoint bottomRight, int dimension)
+ throws NotFoundException {
+
+ // Note that unlike the QR Code sampler, we didn't find the center of modules, but the
+ // very corners. So there is no 0.5f here; 0.0f is right.
+ GridSampler sampler = GridSampler.getInstance();
+
+ return sampler.sampleGrid(matrix, dimension, 0.0f, // p1ToX
+ 0.0f, // p1ToY
+ dimension, // p2ToX
+ 0.0f, // p2ToY
+ dimension, // p3ToX
+ dimension, // p3ToY
+ 0.0f, // p4ToX
+ dimension, // p4ToY
+ topLeft.getX(), // p1FromX
+ topLeft.getY(), // p1FromY
+ topRight.getX(), // p2FromX
+ topRight.getY(), // p2FromY
+ bottomRight.getX(), // p3FromX
+ bottomRight.getY(), // p3FromY
+ bottomLeft.getX(), // p4FromX
+ bottomLeft.getY()); // p4FromY
+ }
+
+ /**
+ * Ends up being a bit faster than Math.round(). This merely rounds its
+ * argument to the nearest int, where x.5 rounds up.
+ */
+ private static int round(float d) {
+ return (int) (d + 0.5f);
+ }
+
+ /**
+ * @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
+ * @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 patternLength = pattern.length;
+ // TODO: Find a way to cache this array, as this method is called hundreds of times
+ // per image, and we want to allocate as seldom as possible.
+ int[] counters = new int[patternLength];
+ boolean isWhite = whiteFirst;
+
+ int counterPosition = 0;
+ int patternStart = column;
+ for (int x = column; x < column + 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];
+ for (int y = 2; y < patternLength; y++) {
+ counters[y - 2] = counters[y];
+ }
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ 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 << 8) / patternLength;
+ maxIndividualVariance = (maxIndividualVariance * unitBarWidth) >> 8;
+
+ int totalVariance = 0;
+ for (int x = 0; x < numCounters; x++) {
+ int counter = counters[x] << 8;
+ 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/src/com/google/zxing/qrcode/QRCodeReader.java b/src/com/google/zxing/qrcode/QRCodeReader.java
new file mode 100644
index 0000000..2a12c6d
--- /dev/null
+++ b/src/com/google/zxing/qrcode/QRCodeReader.java
@@ -0,0 +1,166 @@
+/*
+ * 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.detector.Detector;
+
+import java.util.Hashtable;
+
+/**
+ * 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 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
+ */
+ public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ public Result decode(BinaryBitmap image, Hashtable 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();
+ }
+
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.QR_CODE);
+ if (decoderResult.getByteSegments() != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, decoderResult.getByteSegments());
+ }
+ if (decoderResult.getECLevel() != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel().toString());
+ }
+ return result;
+ }
+
+ public void reset() {
+ // do nothing
+ }
+
+ /**
+ * This method detects a barcode in a "pure" image -- that is, pure monochrome image
+ * which contains only an unrotated, unskewed, image of a barcode, with some white border
+ * around it. This is a specialized method that works exceptionally fast in this special
+ * case.
+ */
+ public static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException {
+
+ int height = image.getHeight();
+ int width = image.getWidth();
+ int minDimension = Math.min(height, width);
+
+ // And then keep tracking across the top-left black module to determine module size
+ //int moduleEnd = borderWidth;
+ int[] leftTopBlack = image.getTopLeftOnBit();
+ if (leftTopBlack == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int x = leftTopBlack[0];
+ int y = leftTopBlack[1];
+ while (x < minDimension && y < minDimension && image.get(x, y)) {
+ x++;
+ y++;
+ }
+ if (x == minDimension || y == minDimension) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int moduleSize = x - leftTopBlack[0];
+ if (moduleSize == 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // And now find where the rightmost black module on the first row ends
+ int rowEndOfSymbol = width - 1;
+ while (rowEndOfSymbol > x && !image.get(rowEndOfSymbol, y)) {
+ rowEndOfSymbol--;
+ }
+ if (rowEndOfSymbol <= x) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ rowEndOfSymbol++;
+
+ // Make sure width of barcode is a multiple of module size
+ if ((rowEndOfSymbol - x) % moduleSize != 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int dimension = 1 + ((rowEndOfSymbol - x) / moduleSize);
+
+ // 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. Need to back up at least 1.
+ int backOffAmount = moduleSize == 1 ? 1 : moduleSize >> 1;
+ x -= backOffAmount;
+ y -= backOffAmount;
+
+ if ((x + (dimension - 1) * moduleSize) >= width ||
+ (y + (dimension - 1) * moduleSize) >= height) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Now just read off the bits
+ BitMatrix bits = new BitMatrix(dimension);
+ for (int i = 0; i < dimension; i++) {
+ int iOffset = y + i * moduleSize;
+ for (int j = 0; j < dimension; j++) {
+ if (image.get(x + j * moduleSize, iOffset)) {
+ bits.set(j, i);
+ }
+ }
+ }
+ return bits;
+ }
+
+}
diff --git a/src/com/google/zxing/qrcode/QRCodeWriter.java b/src/com/google/zxing/qrcode/QRCodeWriter.java
new file mode 100644
index 0000000..c0a5e0a
--- /dev/null
+++ b/src/com/google/zxing/qrcode/QRCodeWriter.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;
+
+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.Hashtable;
+
+/**
+ * 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;
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
+ throws WriterException {
+
+ return encode(contents, format, width, height, null);
+ }
+
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height,
+ Hashtable hints) throws WriterException {
+
+ if (contents == null || contents.length() == 0) {
+ 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;
+ if (hints != null) {
+ ErrorCorrectionLevel requestedECLevel = (ErrorCorrectionLevel) hints.get(EncodeHintType.ERROR_CORRECTION);
+ if (requestedECLevel != null) {
+ errorCorrectionLevel = requestedECLevel;
+ }
+ }
+
+ QRCode code = new QRCode();
+ Encoder.encode(contents, errorCorrectionLevel, hints, code);
+ return renderResult(code, width, height);
+ }
+
+ // 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) {
+ ByteMatrix input = code.getMatrix();
+ int inputWidth = input.getWidth();
+ int inputHeight = input.getHeight();
+ int qrWidth = inputWidth + (QUIET_ZONE_SIZE << 1);
+ int qrHeight = inputHeight + (QUIET_ZONE_SIZE << 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/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java b/src/com/google/zxing/qrcode/decoder/BitMatrixParser.java
new file mode 100644
index 0000000..dfe6fd3
--- /dev/null
+++ b/src/com/google/zxing/qrcode/decoder/BitMatrixParser.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.FormatException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * @author Sean Owen
+ */
+final class BitMatrixParser {
+
+ private final BitMatrix bitMatrix;
+ private Version parsedVersion;
+ private FormatInformation parsedFormatInfo;
+
+ /**
+ * @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 iMin = dimension - 8;
+ for (int i = dimension - 1; i >= iMin; i--) {
+ formatInfoBits2 = copyBit(i, 8, formatInfoBits2);
+ }
+ for (int j = dimension - 7; j < dimension; j++) {
+ formatInfoBits2 = copyBit(8, j, 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);
+ }
+ }
+
+ parsedVersion = Version.decodeVersionInformation(versionBits);
+ if (parsedVersion != null && parsedVersion.getDimensionForVersion() == dimension) {
+ return parsedVersion;
+ }
+
+ // 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);
+ }
+ }
+
+ parsedVersion = Version.decodeVersionInformation(versionBits);
+ if (parsedVersion != null && parsedVersion.getDimensionForVersion() == dimension) {
+ return parsedVersion;
+ }
+ throw FormatException.getFormatInstance();
+ }
+
+ private int copyBit(int i, int j, int versionBits) {
+ return bitMatrix.get(i, j) ? (versionBits << 1) | 0x1 : versionBits << 1;
+ }
+
+ /**
+ * Reads the bits in the {@link BitMatrix} representing the finder pattern in the
+ * correct order in order to reconstitute 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((int) 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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/qrcode/decoder/DataBlock.java b/src/com/google/zxing/qrcode/decoder/DataBlock.java
new file mode 100644
index 0000000..215f6c0
--- /dev/null
+++ b/src/com/google/zxing/qrcode/decoder/DataBlock.java
@@ -0,0 +1,123 @@
+/*
+ * 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 {@link DataBlock}s 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 (int i = 0; i < ecBlockArray.length; i++) {
+ totalBlocks += ecBlockArray[i].getCount();
+ }
+
+ // Now establish DataBlocks of the appropriate size and number of data codewords
+ DataBlock[] result = new DataBlock[totalBlocks];
+ int numResultBlocks = 0;
+ for (int j = 0; j < ecBlockArray.length; j++) {
+ Version.ECB ecBlock = ecBlockArray[j];
+ 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/src/com/google/zxing/qrcode/decoder/DataMask.java b/src/com/google/zxing/qrcode/decoder/DataMask.java
new file mode 100644
index 0000000..48036b1
--- /dev/null
+++ b/src/com/google/zxing/qrcode/decoder/DataMask.java
@@ -0,0 +1,155 @@
+/*
+ * 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 {@link 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 class DataMask000 extends DataMask {
+ boolean isMasked(int i, int j) {
+ return ((i + j) & 0x01) == 0;
+ }
+ }
+
+ /**
+ * 001: mask bits for which x mod 2 == 0
+ */
+ private static class DataMask001 extends DataMask {
+ boolean isMasked(int i, int j) {
+ return (i & 0x01) == 0;
+ }
+ }
+
+ /**
+ * 010: mask bits for which y mod 3 == 0
+ */
+ private static class DataMask010 extends DataMask {
+ boolean isMasked(int i, int j) {
+ return j % 3 == 0;
+ }
+ }
+
+ /**
+ * 011: mask bits for which (x + y) mod 3 == 0
+ */
+ private static class DataMask011 extends DataMask {
+ 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 class DataMask100 extends DataMask {
+ 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 class DataMask101 extends DataMask {
+ 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 class DataMask110 extends DataMask {
+ 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 class DataMask111 extends DataMask {
+ boolean isMasked(int i, int j) {
+ return ((((i + j) & 0x01) + ((i * j) % 3)) & 0x01) == 0;
+ }
+ }
+}
diff --git a/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java b/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java
new file mode 100644
index 0000000..7064b8b
--- /dev/null
+++ b/src/com/google/zxing/qrcode/decoder/DecodedBitStreamParser.java
@@ -0,0 +1,261 @@
+/*
+ * 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.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.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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 DecodedBitStreamParser() {
+ }
+
+ static DecoderResult decode(byte[] bytes, Version version, ErrorCorrectionLevel ecLevel, Hashtable hints)
+ throws FormatException {
+ BitSource bits = new BitSource(bytes);
+ StringBuffer result = new StringBuffer(50);
+ CharacterSetECI currentCharacterSetECI = null;
+ boolean fc1InEffect = false;
+ Vector byteSegments = new Vector(1);
+ 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 {
+ try {
+ mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
+ } catch (IllegalArgumentException iae) {
+ throw FormatException.getFormatInstance();
+ }
+ }
+ if (!mode.equals(Mode.TERMINATOR)) {
+ if (mode.equals(Mode.FNC1_FIRST_POSITION) || mode.equals(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.equals(Mode.STRUCTURED_APPEND)) {
+ // not really supported; all we do is ignore it
+ // Read next 8 bits (symbol sequence #) and 8 bits (parity data), then continue
+ bits.readBits(16);
+ } else if (mode.equals(Mode.ECI)) {
+ // Count doesn't apply to ECI
+ int value = parseECIValue(bits);
+ currentCharacterSetECI = CharacterSetECI.getCharacterSetECIByValue(value);
+ if (currentCharacterSetECI == null) {
+ throw FormatException.getFormatInstance();
+ }
+ } else {
+ // How many characters will follow, encoded in this mode?
+ int count = bits.readBits(mode.getCharacterCountBits(version));
+ if (mode.equals(Mode.NUMERIC)) {
+ decodeNumericSegment(bits, result, count);
+ } else if (mode.equals(Mode.ALPHANUMERIC)) {
+ decodeAlphanumericSegment(bits, result, count, fc1InEffect);
+ } else if (mode.equals(Mode.BYTE)) {
+ decodeByteSegment(bits, result, count, currentCharacterSetECI, byteSegments, hints);
+ } else if (mode.equals(Mode.KANJI)) {
+ decodeKanjiSegment(bits, result, count);
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ }
+ }
+ } while (!mode.equals(Mode.TERMINATOR));
+
+ return new DecoderResult(bytes, result.toString(), byteSegments.isEmpty() ? null : byteSegments, ecLevel);
+ }
+
+ private static void decodeKanjiSegment(BitSource bits,
+ StringBuffer result,
+ int count) throws FormatException {
+ // 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 uee) {
+ throw FormatException.getFormatInstance();
+ }
+ }
+
+ private static void decodeByteSegment(BitSource bits,
+ StringBuffer result,
+ int count,
+ CharacterSetECI currentCharacterSetECI,
+ Vector byteSegments,
+ Hashtable hints) throws FormatException {
+ byte[] readBytes = new byte[count];
+ if (count << 3 > bits.available()) {
+ throw FormatException.getFormatInstance();
+ }
+ 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.getEncodingName();
+ }
+ try {
+ result.append(new String(readBytes, encoding));
+ } catch (UnsupportedEncodingException uce) {
+ throw FormatException.getFormatInstance();
+ }
+ byteSegments.addElement(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,
+ StringBuffer result,
+ int count,
+ boolean fc1InEffect) throws FormatException {
+ // Read two characters at a time
+ int start = result.length();
+ while (count > 1) {
+ 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
+ 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,
+ StringBuffer result,
+ int count) throws FormatException {
+ // Read three digits at a time
+ while (count >= 3) {
+ // Each 10 bits encodes three digits
+ 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
+ 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
+ int digitBits = bits.readBits(4);
+ if (digitBits >= 10) {
+ throw FormatException.getFormatInstance();
+ }
+ result.append(toAlphaNumericChar(digitBits));
+ }
+ }
+
+ private static int parseECIValue(BitSource bits) {
+ int firstByte = bits.readBits(8);
+ if ((firstByte & 0x80) == 0) {
+ // just one byte
+ return firstByte & 0x7F;
+ } else if ((firstByte & 0xC0) == 0x80) {
+ // two bytes
+ int secondByte = bits.readBits(8);
+ return ((firstByte & 0x3F) << 8) | secondByte;
+ } else if ((firstByte & 0xE0) == 0xC0) {
+ // three bytes
+ int secondThirdBytes = bits.readBits(16);
+ return ((firstByte & 0x1F) << 16) | secondThirdBytes;
+ }
+ throw new IllegalArgumentException("Bad ECI bits starting with byte " + firstByte);
+ }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/Decoder.java b/src/com/google/zxing/qrcode/decoder/Decoder.java
new file mode 100644
index 0000000..3aad15e
--- /dev/null
+++ b/src/com/google/zxing/qrcode/decoder/Decoder.java
@@ -0,0 +1,148 @@
+/*
+ * 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.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.reedsolomon.GF256;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+import java.util.Hashtable;
+
+/**
+ * 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(GF256.QR_CODE_FIELD);
+ }
+
+ public DecoderResult decode(boolean[][] image)
+ throws ChecksumException, FormatException, NotFoundException {
+ 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
+ * @return text and bytes encoded within the QR Code
+ * @throws NotFoundException if the QR Code cannot be found
+ * @throws FormatException if the QR Code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ public DecoderResult decode(boolean[][] image, Hashtable hints)
+ throws ChecksumException, FormatException, NotFoundException {
+ 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, NotFoundException {
+ 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
+ * @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, Hashtable hints) throws FormatException, ChecksumException {
+
+ // Construct a parser and read version, error-correction level
+ BitMatrixParser parser = new BitMatrixParser(bits);
+ 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 (int i = 0; i < dataBlocks.length; i++) {
+ totalBytes += dataBlocks[i].getNumDataCodewords();
+ }
+ byte[] resultBytes = new byte[totalBytes];
+ int resultOffset = 0;
+
+ // Error-correct and copy data blocks together into a stream of bytes
+ for (int j = 0; j < dataBlocks.length; j++) {
+ DataBlock dataBlock = dataBlocks[j];
+ 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 rse) {
+ 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/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java b/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java
new file mode 100644
index 0000000..400611a
--- /dev/null
+++ b/src/com/google/zxing/qrcode/decoder/ErrorCorrectionLevel.java
@@ -0,0 +1,86 @@
+/*
+ * 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 final class ErrorCorrectionLevel {
+
+ // No, we can't use an enum here. J2ME doesn't support it.
+
+ /**
+ * L = ~7% correction
+ */
+ public static final ErrorCorrectionLevel L = new ErrorCorrectionLevel(0, 0x01, "L");
+ /**
+ * M = ~15% correction
+ */
+ public static final ErrorCorrectionLevel M = new ErrorCorrectionLevel(1, 0x00, "M");
+ /**
+ * Q = ~25% correction
+ */
+ public static final ErrorCorrectionLevel Q = new ErrorCorrectionLevel(2, 0x03, "Q");
+ /**
+ * H = ~30% correction
+ */
+ public static final ErrorCorrectionLevel H = new ErrorCorrectionLevel(3, 0x02, "H");
+
+ private static final ErrorCorrectionLevel[] FOR_BITS = {M, L, H, Q};
+
+ private final int ordinal;
+ private final int bits;
+ private final String name;
+
+ private ErrorCorrectionLevel(int ordinal, int bits, String name) {
+ this.ordinal = ordinal;
+ this.bits = bits;
+ this.name = name;
+ }
+
+ public int ordinal() {
+ return ordinal;
+ }
+
+ public int getBits() {
+ return bits;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * @param bits int containing the two bits encoding a QR Code's error correction level
+ * @return {@link 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/src/com/google/zxing/qrcode/decoder/FormatInformation.java b/src/com/google/zxing/qrcode/decoder/FormatInformation.java
new file mode 100644
index 0000000..1b76b0d
--- /dev/null
+++ b/src/com/google/zxing/qrcode/decoder/FormatInformation.java
@@ -0,0 +1,171 @@
+/*
+ * 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 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 i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) {
+ int[] decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i];
+ 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;
+ }
+
+ public int hashCode() {
+ return (errorCorrectionLevel.ordinal() << 3) | (int) dataMask;
+ }
+
+ 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/src/com/google/zxing/qrcode/decoder/Mode.java b/src/com/google/zxing/qrcode/decoder/Mode.java
new file mode 100644
index 0000000..24117dc
--- /dev/null
+++ b/src/com/google/zxing/qrcode/decoder/Mode.java
@@ -0,0 +1,112 @@
+/*
+ * 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 final class Mode {
+
+ // No, we can't use an enum here. J2ME doesn't support it.
+
+ public static final Mode TERMINATOR = new Mode(new int[]{0, 0, 0}, 0x00, "TERMINATOR"); // Not really a mode...
+ public static final Mode NUMERIC = new Mode(new int[]{10, 12, 14}, 0x01, "NUMERIC");
+ public static final Mode ALPHANUMERIC = new Mode(new int[]{9, 11, 13}, 0x02, "ALPHANUMERIC");
+ public static final Mode STRUCTURED_APPEND = new Mode(new int[]{0, 0, 0}, 0x03, "STRUCTURED_APPEND"); // Not supported
+ public static final Mode BYTE = new Mode(new int[]{8, 16, 16}, 0x04, "BYTE");
+ public static final Mode ECI = new Mode(null, 0x07, "ECI"); // character counts don't apply
+ public static final Mode KANJI = new Mode(new int[]{8, 10, 12}, 0x08, "KANJI");
+ public static final Mode FNC1_FIRST_POSITION = new Mode(null, 0x05, "FNC1_FIRST_POSITION");
+ public static final Mode FNC1_SECOND_POSITION = new Mode(null, 0x09, "FNC1_SECOND_POSITION");
+
+ private final int[] characterCountBitsForVersions;
+ private final int bits;
+ private final String name;
+
+ private Mode(int[] characterCountBitsForVersions, int bits, String name) {
+ this.characterCountBitsForVersions = characterCountBitsForVersions;
+ this.bits = bits;
+ this.name = name;
+ }
+
+ /**
+ * @param bits four bits encoding a QR Code data mode
+ * @return {@link 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;
+ 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 {@link Mode}
+ */
+ public int getCharacterCountBits(Version version) {
+ if (characterCountBitsForVersions == null) {
+ throw new IllegalArgumentException("Character count doesn't apply to this mode");
+ }
+ 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;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String toString() {
+ return name;
+ }
+
+}
diff --git a/src/com/google/zxing/qrcode/decoder/Version.java b/src/com/google/zxing/qrcode/decoder/Version.java
new file mode 100644
index 0000000..e496005
--- /dev/null
+++ b/src/com/google/zxing/qrcode/decoder/Version.java
@@ -0,0 +1,586 @@
+/*
+ * 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 ecBlocks1,
+ ECBlocks ecBlocks2,
+ ECBlocks ecBlocks3,
+ ECBlocks ecBlocks4) {
+ this.versionNumber = versionNumber;
+ this.alignmentPatternCenters = alignmentPatternCenters;
+ this.ecBlocks = new ECBlocks[]{ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4};
+ int total = 0;
+ int ecCodewords = ecBlocks1.getECCodewordsPerBlock();
+ ECB[] ecbArray = ecBlocks1.getECBlocks();
+ for (int i = 0; i < ecbArray.length; i++) {
+ ECB ecBlock = ecbArray[i];
+ 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 {@link 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 iae) {
+ 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 = new ECB[]{ecBlocks};
+ }
+
+ ECBlocks(int ecCodewordsPerBlock, ECB ecBlocks1, ECB ecBlocks2) {
+ this.ecCodewordsPerBlock = ecCodewordsPerBlock;
+ this.ecBlocks = new ECB[]{ecBlocks1, ecBlocks2};
+ }
+
+ public int getECCodewordsPerBlock() {
+ return ecCodewordsPerBlock;
+ }
+
+ public int getNumBlocks() {
+ int total = 0;
+ for (int i = 0; i < ecBlocks.length; i++) {
+ total += ecBlocks[i].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;
+ }
+ }
+
+ 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/src/com/google/zxing/qrcode/detector/AlignmentPattern.java b/src/com/google/zxing/qrcode/detector/AlignmentPattern.java
new file mode 100644
index 0000000..6fc1a2c
--- /dev/null
+++ b/src/com/google/zxing/qrcode/detector/AlignmentPattern.java
@@ -0,0 +1,48 @@
+/*
+ * 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 <= 1.0f;
+ }
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java b/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java
new file mode 100644
index 0000000..6d5d2fe
--- /dev/null
+++ b/src/com/google/zxing/qrcode/detector/AlignmentPatternFinder.java
@@ -0,0 +1,279 @@
+/*
+ * 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.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Vector;
+
+/**
+ * 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 Vector 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 Vector(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 (AlignmentPattern) possibleCenters.elementAt(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;
+ int max = possibleCenters.size();
+ for (int index = 0; index < max; index++) {
+ AlignmentPattern center = (AlignmentPattern) possibleCenters.elementAt(index);
+ // Look for about the same center and module size:
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ return new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+ }
+ }
+ // Hadn't found this before; save it
+ ResultPoint point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+ possibleCenters.addElement(point);
+ if (resultPointCallback != null) {
+ resultPointCallback.foundPossibleResultPoint(point);
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/src/com/google/zxing/qrcode/detector/Detector.java b/src/com/google/zxing/qrcode/detector/Detector.java
new file mode 100644
index 0000000..e6b860b
--- /dev/null
+++ b/src/com/google/zxing/qrcode/detector/Detector.java
@@ -0,0 +1,400 @@
+/*
+ * 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.qrcode.decoder.Version;
+
+import java.util.Hashtable;
+
+/**
+ * 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 BitMatrix getImage() {
+ return image;
+ }
+
+ protected ResultPointCallback getResultPointCallback() {
+ return resultPointCallback;
+ }
+
+ /**
+ * Detects a QR Code in an image, simply.
+ *
+ * @return {@link DetectorResult} encapsulating results of detecting a QR Code
+ * @throws NotFoundException if no QR Code can be found
+ */
+ public DetectorResult detect() throws NotFoundException, FormatException {
+ return detect(null);
+ }
+
+ /**
+ * Detects a QR Code in an image, simply.
+ *
+ * @param hints optional hints to detector
+ * @return {@link NotFoundException} 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(Hashtable 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 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);
+ }
+
+ public 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 = sourceBottomRightY = dimMinusThree - 3.0f;
+ } 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 = 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, 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.
+ */
+ protected static int computeDimension(ResultPoint topLeft,
+ ResultPoint topRight,
+ ResultPoint bottomLeft,
+ float moduleSize) throws NotFoundException {
+ int tltrCentersDimension = round(ResultPoint.distance(topLeft, topRight) / moduleSize);
+ int tlblCentersDimension = 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.
+ */
+ protected 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() - fromX) / (float) (otherToX - fromX);
+ otherToX = image.getWidth();
+ }
+ 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() - fromY) / (float) (otherToY - fromY);
+ otherToY = image.getHeight();
+ }
+ otherToX = (int) (fromX + (otherToX - fromX) * scale);
+
+ result += sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);
+ return result;
+ }
+
+ /**
+ * 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 ystep = fromY < toY ? 1 : -1;
+ int xstep = fromX < toX ? 1 : -1;
+ int state = 0; // In black pixels, looking for white, first or second time
+ for (int x = fromX, y = fromY; x != toX; x += xstep) {
+
+ int realX = steep ? y : x;
+ int realY = steep ? x : y;
+ if (state == 1) { // In white pixels, looking for black
+ if (image.get(realX, realY)) {
+ state++;
+ }
+ } else {
+ if (!image.get(realX, realY)) {
+ state++;
+ }
+ }
+
+ if (state == 3) { // Found black, white, black, and stumbled back onto white; done
+ int diffX = x - fromX;
+ int diffY = y - fromY;
+ if (xstep < 0) {
+ diffX++;
+ }
+ return (float) Math.sqrt((double) (diffX * diffX + diffY * diffY));
+ }
+ error += dy;
+ if (error > 0) {
+ if (y == toY) {
+ break;
+ }
+ y += ystep;
+ error -= dx;
+ }
+ }
+ int diffX = toX - fromX;
+ int diffY = toY - fromY;
+ return (float) Math.sqrt((double) (diffX * diffX + diffY * diffY));
+ }
+
+ /**
+ * 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 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();
+ }
+
+ /**
+ * Ends up being a bit faster than Math.round(). This merely rounds its argument to the nearest int,
+ * where x.5 rounds up.
+ */
+ private static int round(float d) {
+ return (int) (d + 0.5f);
+ }
+}
diff --git a/src/com/google/zxing/qrcode/detector/FinderPattern.java b/src/com/google/zxing/qrcode/detector/FinderPattern.java
new file mode 100644
index 0000000..7a9914d
--- /dev/null
+++ b/src/com/google/zxing/qrcode/detector/FinderPattern.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.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 int count;
+
+ FinderPattern(float posX, float posY, float estimatedModuleSize) {
+ super(posX, posY);
+ this.estimatedModuleSize = estimatedModuleSize;
+ this.count = 1;
+ }
+
+ 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 <= 1.0f;
+ }
+ return false;
+ }
+
+}
diff --git a/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java b/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java
new file mode 100644
index 0000000..2b4078b
--- /dev/null
+++ b/src/com/google/zxing/qrcode/detector/FinderPatternFinder.java
@@ -0,0 +1,585 @@
+/*
+ * 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 com.google.zxing.common.Collections;
+import com.google.zxing.common.Comparator;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+/**
+ * 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 Vector 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 Vector();
+ this.crossCheckStateCount = new int[5];
+ this.resultPointCallback = resultPointCallback;
+ }
+
+ protected BitMatrix getImage() {
+ return image;
+ }
+
+ protected Vector getPossibleCenters() {
+ return possibleCenters;
+ }
+
+ FinderPatternInfo find(Hashtable hints) throws NotFoundException {
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ 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);
+ 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);
+ 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 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.
+ *
+ * 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
+ * @return true if a finder pattern candidate was found this time
+ */
+ protected boolean handlePossibleCenter(int[] stateCount, int i, int j) {
+ 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)) {
+ float estimatedModuleSize = (float) stateCountTotal / 7.0f;
+ boolean found = false;
+ int max = possibleCenters.size();
+ for (int index = 0; index < max; index++) {
+ FinderPattern center = (FinderPattern) possibleCenters.elementAt(index);
+ // Look for about the same center and module size:
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ center.incrementCount();
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ ResultPoint point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
+ possibleCenters.addElement(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;
+ }
+ FinderPattern firstConfirmedCenter = null;
+ for (int i = 0; i < max; i++) {
+ FinderPattern center = (FinderPattern) possibleCenters.elementAt(i);
+ 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 (int i = 0; i < max; i++) {
+ FinderPattern pattern = (FinderPattern) possibleCenters.elementAt(i);
+ 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 (int i = 0; i < max; i++) {
+ FinderPattern pattern = (FinderPattern) possibleCenters.elementAt(i);
+ 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 (int i = 0; i < startSize; i++) {
+ float size = ((FinderPattern) possibleCenters.elementAt(i)).getEstimatedModuleSize();
+ totalModuleSize += size;
+ square += size * size;
+ }
+ float average = totalModuleSize / (float) startSize;
+ float stdDev = (float) Math.sqrt(square / startSize - average * average);
+
+ Collections.insertionSort(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 = (FinderPattern) possibleCenters.elementAt(i);
+ if (Math.abs(pattern.getEstimatedModuleSize() - average) > limit) {
+ possibleCenters.removeElementAt(i);
+ i--;
+ }
+ }
+ }
+
+ if (possibleCenters.size() > 3) {
+ // Throw away all but those first size candidate points we found.
+
+ float totalModuleSize = 0.0f;
+ for (int i = 0; i < possibleCenters.size(); i++) {
+ totalModuleSize += ((FinderPattern) possibleCenters.elementAt(i)).getEstimatedModuleSize();
+ }
+
+ float average = totalModuleSize / (float) possibleCenters.size();
+
+ Collections.insertionSort(possibleCenters, new CenterComparator(average));
+
+ possibleCenters.setSize(3);
+ }
+
+ return new FinderPattern[]{
+ (FinderPattern) possibleCenters.elementAt(0),
+ (FinderPattern) possibleCenters.elementAt(1),
+ (FinderPattern) possibleCenters.elementAt(2)
+ };
+ }
+
+ /**
+ *
Orders by furthest from average
+ */
+ private static class FurthestFromAverageComparator implements Comparator {
+ private final float average;
+ public FurthestFromAverageComparator(float f) {
+ average = f;
+ }
+ public int compare(Object center1, Object center2) {
+ float dA = Math.abs(((FinderPattern) center2).getEstimatedModuleSize() - average);
+ float dB = Math.abs(((FinderPattern) center1).getEstimatedModuleSize() - average);
+ return dA < dB ? -1 : (dA == dB ? 0 : 1);
+ }
+ }
+
+ /**
+ * Orders by {@link FinderPattern#getCount()}, descending.
+ */
+ private static class CenterComparator implements Comparator {
+ private final float average;
+ public CenterComparator(float f) {
+ average = f;
+ }
+ public int compare(Object center1, Object center2) {
+ if (((FinderPattern) center2).getCount() != ((FinderPattern) center1).getCount()) {
+ return ((FinderPattern) center2).getCount() - ((FinderPattern) center1).getCount();
+ } else {
+ float dA = Math.abs(((FinderPattern) center2).getEstimatedModuleSize() - average);
+ float dB = Math.abs(((FinderPattern) center1).getEstimatedModuleSize() - average);
+ return dA < dB ? 1 : (dA == dB ? 0 : -1);
+ }
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java b/src/com/google/zxing/qrcode/detector/FinderPatternInfo.java
new file mode 100644
index 0000000..3c34010
--- /dev/null
+++ b/src/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/src/com/google/zxing/qrcode/encoder/BlockPair.java b/src/com/google/zxing/qrcode/encoder/BlockPair.java
new file mode 100644
index 0000000..5714d9c
--- /dev/null
+++ b/src/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/src/com/google/zxing/qrcode/encoder/ByteMatrix.java b/src/com/google/zxing/qrcode/encoder/ByteMatrix.java
new file mode 100644
index 0000000..eb248a2
--- /dev/null
+++ b/src/com/google/zxing/qrcode/encoder/ByteMatrix.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.qrcode.encoder;
+
+/**
+ * A class which wraps a 2D array of bytes. The default usage is signed. If you want to use it as a
+ * unsigned container, it's up to you to do byteValue & 0xff at each location.
+ *
+ * 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];
+ }
+
+ 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;
+ }
+ }
+ }
+
+ public String toString() {
+ StringBuffer result = new StringBuffer(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/src/com/google/zxing/qrcode/encoder/Encoder.java b/src/com/google/zxing/qrcode/encoder/Encoder.java
new file mode 100644
index 0000000..e93eb0b
--- /dev/null
+++ b/src/com/google/zxing/qrcode/encoder/Encoder.java
@@ -0,0 +1,557 @@
+/*
+ * 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.ECI;
+import com.google.zxing.common.reedsolomon.GF256;
+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.Hashtable;
+import java.util.Vector;
+
+/**
+ * @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 = "UTF-8";
+
+ 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) {
+ int penalty = 0;
+ penalty += MaskUtil.applyMaskPenaltyRule1(matrix);
+ penalty += MaskUtil.applyMaskPenaltyRule2(matrix);
+ penalty += MaskUtil.applyMaskPenaltyRule3(matrix);
+ penalty += MaskUtil.applyMaskPenaltyRule4(matrix);
+ return penalty;
+ }
+
+ /**
+ * Encode "bytes" with the error correction level "ecLevel". The encoding mode will be chosen
+ * internally by chooseMode(). On success, store the result in "qrCode".
+ *
+ * We recommend you to use QRCode.EC_LEVEL_L (the lowest level) for
+ * "getECLevel" since our primary use is to show QR code on desktop screens. We don't need very
+ * strong error correction for this purpose.
+ *
+ * Note that there is no way to encode bytes in MODE_KANJI. We might want to add EncodeWithMode()
+ * with which clients can specify the encoding mode. For now, we don't need the functionality.
+ */
+ public static void encode(String content, ErrorCorrectionLevel ecLevel, QRCode qrCode)
+ throws WriterException {
+ encode(content, ecLevel, null, qrCode);
+ }
+
+ public static void encode(String content, ErrorCorrectionLevel ecLevel, Hashtable hints,
+ QRCode qrCode) throws WriterException {
+
+ String encoding = hints == null ? null : (String) hints.get(EncodeHintType.CHARACTER_SET);
+ if (encoding == null) {
+ encoding = DEFAULT_BYTE_MODE_ENCODING;
+ }
+
+ // Step 1: Choose the mode (encoding).
+ Mode mode = chooseMode(content, encoding);
+
+ // Step 2: Append "bytes" into "dataBits" in appropriate encoding.
+ BitArray dataBits = new BitArray();
+ appendBytes(content, mode, dataBits, encoding);
+ // Step 3: Initialize QR code that can contain "dataBits".
+ int numInputBytes = dataBits.getSizeInBytes();
+ initQRCode(numInputBytes, ecLevel, mode, qrCode);
+
+ // Step 4: Build another bit vector that contains header and data.
+ BitArray headerAndDataBits = new BitArray();
+
+ // Step 4.5: Append ECI message if applicable
+ if (mode == Mode.BYTE && !DEFAULT_BYTE_MODE_ENCODING.equals(encoding)) {
+ CharacterSetECI eci = CharacterSetECI.getCharacterSetECIByName(encoding);
+ if (eci != null) {
+ appendECI(eci, headerAndDataBits);
+ }
+ }
+
+ appendModeInfo(mode, headerAndDataBits);
+
+ int numLetters = mode.equals(Mode.BYTE) ? dataBits.getSizeInBytes() : content.length();
+ appendLengthInfo(numLetters, qrCode.getVersion(), mode, headerAndDataBits);
+ headerAndDataBits.appendBitArray(dataBits);
+
+ // Step 5: Terminate the bits properly.
+ terminateBits(qrCode.getNumDataBytes(), headerAndDataBits);
+
+ // Step 6: Interleave data bits with error correction code.
+ BitArray finalBits = new BitArray();
+ interleaveWithECBytes(headerAndDataBits, qrCode.getNumTotalBytes(), qrCode.getNumDataBytes(),
+ qrCode.getNumRSBlocks(), finalBits);
+
+ // Step 7: Choose the mask pattern and set to "qrCode".
+ ByteMatrix matrix = new ByteMatrix(qrCode.getMatrixWidth(), qrCode.getMatrixWidth());
+ qrCode.setMaskPattern(chooseMaskPattern(finalBits, qrCode.getECLevel(), qrCode.getVersion(),
+ matrix));
+
+ // Step 8. Build the matrix and set it to "qrCode".
+ MatrixUtil.buildMatrix(finalBits, qrCode.getECLevel(), qrCode.getVersion(),
+ qrCode.getMaskPattern(), matrix);
+ qrCode.setMatrix(matrix);
+ // Step 9. Make sure we have a valid QR Code.
+ if (!qrCode.isValid()) {
+ throw new WriterException("Invalid QR code: " + qrCode.toString());
+ }
+ }
+
+ /**
+ * @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}.
+ */
+ public 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;
+ } else if (hasNumeric) {
+ return Mode.NUMERIC;
+ }
+ return Mode.BYTE;
+ }
+
+ private static boolean isOnlyDoubleByteKanji(String content) {
+ byte[] bytes;
+ try {
+ bytes = content.getBytes("Shift_JIS");
+ } catch (UnsupportedEncodingException uee) {
+ 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, int 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;
+ }
+
+ /**
+ * Initialize "qrCode" according to "numInputBytes", "ecLevel", and "mode". On success,
+ * modify "qrCode".
+ */
+ private static void initQRCode(int numInputBytes, ErrorCorrectionLevel ecLevel, Mode mode,
+ QRCode qrCode) throws WriterException {
+ qrCode.setECLevel(ecLevel);
+ qrCode.setMode(mode);
+
+ // 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();
+ // getNumRSBlocks = 5
+ int numRSBlocks = ecBlocks.getNumBlocks();
+ // getNumDataBytes = 196 - 130 = 66
+ int numDataBytes = numBytes - numEcBytes;
+ // We want to choose the smallest version which can contain data of "numInputBytes" + some
+ // extra bits for the header (mode info and length info). The header can be three bytes
+ // (precisely 4 + 16 bits) at most. Hence we do +3 here.
+ if (numDataBytes >= numInputBytes + 3) {
+ // Yay, we found the proper rs block info!
+ qrCode.setVersion(versionNum);
+ qrCode.setNumTotalBytes(numBytes);
+ qrCode.setNumDataBytes(numDataBytes);
+ qrCode.setNumRSBlocks(numRSBlocks);
+ // getNumECBytes = 196 - 66 = 130
+ qrCode.setNumECBytes(numEcBytes);
+ // matrix width = 21 + 6 * 4 = 45
+ qrCode.setMatrixWidth(version.getDimensionForVersion());
+ return;
+ }
+ }
+ throw new WriterException("Cannot find proper rs block info (input 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 void interleaveWithECBytes(BitArray bits, int numTotalBytes,
+ int numDataBytes, int numRSBlocks, BitArray result) 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.
+ Vector blocks = new Vector(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.addElement(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");
+ }
+
+ // First, place data blocks.
+ for (int i = 0; i < maxNumDataBytes; ++i) {
+ for (int j = 0; j < blocks.size(); ++j) {
+ byte[] dataBytes = ((BlockPair) blocks.elementAt(j)).getDataBytes();
+ if (i < dataBytes.length) {
+ result.appendBits(dataBytes[i], 8);
+ }
+ }
+ }
+ // Then, place error correction blocks.
+ for (int i = 0; i < maxNumEcBytes; ++i) {
+ for (int j = 0; j < blocks.size(); ++j) {
+ byte[] ecBytes = ((BlockPair) blocks.elementAt(j)).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.");
+ }
+ }
+
+ 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(GF256.QR_CODE_FIELD).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, int version, Mode mode, BitArray bits)
+ throws WriterException {
+ int numBits = mode.getCharacterCountBits(Version.getVersionForNumber(version));
+ if (numLetters > ((1 << numBits) - 1)) {
+ 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 {
+ if (mode.equals(Mode.NUMERIC)) {
+ appendNumericBytes(content, bits);
+ } else if (mode.equals(Mode.ALPHANUMERIC)) {
+ appendAlphanumericBytes(content, bits);
+ } else if (mode.equals(Mode.BYTE)) {
+ append8BitBytes(content, bits, encoding);
+ } else if (mode.equals(Mode.KANJI)) {
+ appendKanjiBytes(content, bits);
+ } else {
+ throw new WriterException("Invalid mode: " + mode);
+ }
+ }
+
+ static void appendNumericBytes(String 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(String 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.toString());
+ }
+ for (int i = 0; i < bytes.length; ++i) {
+ bits.appendBits(bytes[i], 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.toString());
+ }
+ 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(ECI 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/src/com/google/zxing/qrcode/encoder/MaskUtil.java b/src/com/google/zxing/qrcode/encoder/MaskUtil.java
new file mode 100644
index 0000000..c7f3c48
--- /dev/null
+++ b/src/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 satorux@google.com (Satoru Takabayashi) - creator
+ * @author dswitkin@google.com (Daniel Switkin) - ported from C++
+ */
+public final class MaskUtil {
+
+ 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.
+ public 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.
+ public 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 += 3;
+ }
+ }
+ }
+ return penalty;
+ }
+
+ // Apply mask penalty rule 3 and return the penalty. Find consecutive cells of 00001011101 or
+ // 10111010000, and give penalty to them. If we find patterns like 000010111010000, we give
+ // penalties twice (i.e. 40 * 2).
+ public static int applyMaskPenaltyRule3(ByteMatrix matrix) {
+ int penalty = 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) {
+ // Tried to simplify following conditions but failed.
+ if (x + 6 < width &&
+ array[y][x] == 1 &&
+ array[y][x + 1] == 0 &&
+ array[y][x + 2] == 1 &&
+ array[y][x + 3] == 1 &&
+ array[y][x + 4] == 1 &&
+ array[y][x + 5] == 0 &&
+ array[y][x + 6] == 1 &&
+ ((x + 10 < width &&
+ array[y][x + 7] == 0 &&
+ array[y][x + 8] == 0 &&
+ array[y][x + 9] == 0 &&
+ array[y][x + 10] == 0) ||
+ (x - 4 >= 0 &&
+ array[y][x - 1] == 0 &&
+ array[y][x - 2] == 0 &&
+ array[y][x - 3] == 0 &&
+ array[y][x - 4] == 0))) {
+ penalty += 40;
+ }
+ 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 &&
+ ((y + 10 < height &&
+ array[y + 7][x] == 0 &&
+ array[y + 8][x] == 0 &&
+ array[y + 9][x] == 0 &&
+ array[y + 10][x] == 0) ||
+ (y - 4 >= 0 &&
+ array[y - 1][x] == 0 &&
+ array[y - 2][x] == 0 &&
+ array[y - 3][x] == 0 &&
+ array[y - 4][x] == 0))) {
+ penalty += 40;
+ }
+ }
+ }
+ return penalty;
+ }
+
+ // 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. Examples:
+ // - 0% => 100
+ // - 40% => 20
+ // - 45% => 10
+ // - 50% => 0
+ // - 55% => 10
+ // - 55% => 20
+ // - 100% => 100
+ public 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) {
+ for (int x = 0; x < width; ++x) {
+ if (array[y][x] == 1) {
+ numDarkCells += 1;
+ }
+ }
+ }
+ int numTotalCells = matrix.getHeight() * matrix.getWidth();
+ double darkRatio = (double) numDarkCells / numTotalCells;
+ return Math.abs((int) (darkRatio * 100 - 50)) / 5 * 10;
+ }
+
+ // Return the mask bit for "getMaskPattern" at "x" and "y". See 8.8 of JISX0510:2004 for mask
+ // pattern conditions.
+ public static boolean getDataMaskBit(int maskPattern, int x, int y) {
+ if (!QRCode.isValidMaskPattern(maskPattern)) {
+ throw new IllegalArgumentException("Invalid mask pattern");
+ }
+ int intermediate, 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 numSameBitCells = 0;
+ int prevBit = -1;
+ // Horizontal mode:
+ // for (int i = 0; i < matrix.height(); ++i) {
+ // for (int j = 0; j < matrix.width(); ++j) {
+ // int bit = matrix.get(i, j);
+ // Vertical mode:
+ // for (int i = 0; i < matrix.width(); ++i) {
+ // for (int j = 0; j < matrix.height(); ++j) {
+ // int bit = matrix.get(j, i);
+ 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) {
+ for (int j = 0; j < jLimit; ++j) {
+ int bit = isHorizontal ? array[i][j] : array[j][i];
+ if (bit == prevBit) {
+ numSameBitCells += 1;
+ // Found five repetitive cells with the same color (bit).
+ // We'll give penalty of 3.
+ if (numSameBitCells == 5) {
+ penalty += 3;
+ } else if (numSameBitCells > 5) {
+ // After five repetitive cells, we'll add the penalty one
+ // by one.
+ penalty += 1;
+ }
+ } else {
+ numSameBitCells = 1; // Include the cell itself.
+ prevBit = bit;
+ }
+ }
+ numSameBitCells = 0; // Clear at each row/column.
+ }
+ return penalty;
+ }
+
+}
diff --git a/src/com/google/zxing/qrcode/encoder/MatrixUtil.java b/src/com/google/zxing/qrcode/encoder/MatrixUtil.java
new file mode 100644
index 0000000..76bdb18
--- /dev/null
+++ b/src/com/google/zxing/qrcode/encoder/MatrixUtil.java
@@ -0,0 +1,524 @@
+/*
+ * 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;
+
+/**
+ * @author satorux@google.com (Satoru Takabayashi) - creator
+ * @author dswitkin@google.com (Daniel Switkin) - ported from C++
+ */
+public 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[][] HORIZONTAL_SEPARATION_PATTERN = {
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ };
+
+ private static final int[][] VERTICAL_SEPARATION_PATTERN = {
+ {0}, {0}, {0}, {0}, {0}, {0}, {0},
+ };
+
+ 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.
+ public 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.
+ public static void buildMatrix(BitArray dataBits, ErrorCorrectionLevel ecLevel, int 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
+ public static void embedBasicPatterns(int 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.
+ public 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.
+ public static void maybeEmbedVersionInfo(int version, ByteMatrix matrix) throws WriterException {
+ if (version < 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.
+ public 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) {
+ if (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
+ public 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.
+ public 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.
+ public 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.
+ public static void makeVersionInfoBits(int version, BitArray bits) throws WriterException {
+ bits.appendBits(version, 6);
+ int bchCode = calculateBCHCode(version, 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;
+ }
+
+ // Check if "value" is valid.
+ private static boolean isValidValue(int value) {
+ return (value == -1 || // Empty.
+ value == 0 || // Light (white).
+ value == 1); // Dark (black).
+ }
+
+ private static void embedTimingPatterns(ByteMatrix matrix) throws WriterException {
+ // -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 (!isValidValue(matrix.get(i, 6))) {
+ throw new WriterException();
+ }
+ if (isEmpty(matrix.get(i, 6))) {
+ matrix.set(i, 6, bit);
+ }
+ // Vertical line.
+ if (!isValidValue(matrix.get(6, i))) {
+ throw new WriterException();
+ }
+ 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 {
+ // We know the width and height.
+ if (HORIZONTAL_SEPARATION_PATTERN[0].length != 8 || HORIZONTAL_SEPARATION_PATTERN.length != 1) {
+ throw new WriterException("Bad horizontal separation pattern");
+ }
+ for (int x = 0; x < 8; ++x) {
+ if (!isEmpty(matrix.get(xStart + x, yStart))) {
+ throw new WriterException();
+ }
+ matrix.set(xStart + x, yStart, HORIZONTAL_SEPARATION_PATTERN[0][x]);
+ }
+ }
+
+ private static void embedVerticalSeparationPattern(int xStart, int yStart,
+ ByteMatrix matrix) throws WriterException {
+ // We know the width and height.
+ if (VERTICAL_SEPARATION_PATTERN[0].length != 1 || VERTICAL_SEPARATION_PATTERN.length != 7) {
+ throw new WriterException("Bad vertical separation pattern");
+ }
+ for (int y = 0; y < 7; ++y) {
+ if (!isEmpty(matrix.get(xStart, yStart + y))) {
+ throw new WriterException();
+ }
+ matrix.set(xStart, yStart + y, VERTICAL_SEPARATION_PATTERN[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) throws WriterException {
+ // We know the width and height.
+ if (POSITION_ADJUSTMENT_PATTERN[0].length != 5 || POSITION_ADJUSTMENT_PATTERN.length != 5) {
+ throw new WriterException("Bad position adjustment");
+ }
+ for (int y = 0; y < 5; ++y) {
+ for (int x = 0; x < 5; ++x) {
+ if (!isEmpty(matrix.get(xStart + x, yStart + y))) {
+ throw new WriterException();
+ }
+ matrix.set(xStart + x, yStart + y, POSITION_ADJUSTMENT_PATTERN[y][x]);
+ }
+ }
+ }
+
+ private static void embedPositionDetectionPattern(int xStart, int yStart,
+ ByteMatrix matrix) throws WriterException {
+ // We know the width and height.
+ if (POSITION_DETECTION_PATTERN[0].length != 7 || POSITION_DETECTION_PATTERN.length != 7) {
+ throw new WriterException("Bad position detection pattern");
+ }
+ for (int y = 0; y < 7; ++y) {
+ for (int x = 0; x < 7; ++x) {
+ if (!isEmpty(matrix.get(xStart + x, yStart + y))) {
+ throw new WriterException();
+ }
+ 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 = HORIZONTAL_SEPARATION_PATTERN[0].length;
+ // 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 = VERTICAL_SEPARATION_PATTERN.length;
+ // 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(int version, ByteMatrix matrix)
+ throws WriterException {
+ if (version < 2) { // The patterns appear if version >= 2
+ return;
+ }
+ int index = version - 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/src/com/google/zxing/qrcode/encoder/QRCode.java b/src/com/google/zxing/qrcode/encoder/QRCode.java
new file mode 100644
index 0000000..05c8185
--- /dev/null
+++ b/src/com/google/zxing/qrcode/encoder/QRCode.java
@@ -0,0 +1,239 @@
+/*
+ * 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;
+
+/**
+ * @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 int version;
+ private int matrixWidth;
+ private int maskPattern;
+ private int numTotalBytes;
+ private int numDataBytes;
+ private int numECBytes;
+ private int numRSBlocks;
+ private ByteMatrix matrix;
+
+ public QRCode() {
+ mode = null;
+ ecLevel = null;
+ version = -1;
+ matrixWidth = -1;
+ maskPattern = -1;
+ numTotalBytes = -1;
+ numDataBytes = -1;
+ numECBytes = -1;
+ numRSBlocks = -1;
+ matrix = null;
+ }
+
+ // Mode of the QR Code.
+ public Mode getMode() {
+ return mode;
+ }
+
+ // Error correction level of the QR Code.
+ public ErrorCorrectionLevel getECLevel() {
+ return ecLevel;
+ }
+
+ // Version of the QR Code. The bigger size, the bigger version.
+ public int getVersion() {
+ return version;
+ }
+
+ // ByteMatrix width of the QR Code.
+ public int getMatrixWidth() {
+ return matrixWidth;
+ }
+
+ // Mask pattern of the QR Code.
+ public int getMaskPattern() {
+ return maskPattern;
+ }
+
+ // Number of total bytes in the QR Code.
+ public int getNumTotalBytes() {
+ return numTotalBytes;
+ }
+
+ // Number of data bytes in the QR Code.
+ public int getNumDataBytes() {
+ return numDataBytes;
+ }
+
+ // Number of error correction bytes in the QR Code.
+ public int getNumECBytes() {
+ return numECBytes;
+ }
+
+ // Number of Reedsolomon blocks in the QR Code.
+ public int getNumRSBlocks() {
+ return numRSBlocks;
+ }
+
+ // ByteMatrix data of the QR Code.
+ public ByteMatrix getMatrix() {
+ return matrix;
+ }
+
+
+ // Return the value of the module (cell) pointed by "x" and "y" in the matrix of the QR Code. They
+ // call cells in the matrix "modules". 1 represents a black cell, and 0 represents a white cell.
+ public int at(int x, int y) {
+ // The value must be zero or one.
+ int value = matrix.get(x, y);
+ if (!(value == 0 || value == 1)) {
+ // this is really like an assert... not sure what better exception to use?
+ throw new RuntimeException("Bad value");
+ }
+ return value;
+ }
+
+ // Checks all the member variables are set properly. Returns true on success. Otherwise, returns
+ // false.
+ public boolean isValid() {
+ return
+ // First check if all version are not uninitialized.
+ mode != null &&
+ ecLevel != null &&
+ version != -1 &&
+ matrixWidth != -1 &&
+ maskPattern != -1 &&
+ numTotalBytes != -1 &&
+ numDataBytes != -1 &&
+ numECBytes != -1 &&
+ numRSBlocks != -1 &&
+ // Then check them in other ways..
+ isValidMaskPattern(maskPattern) &&
+ numTotalBytes == numDataBytes + numECBytes &&
+ // ByteMatrix stuff.
+ matrix != null &&
+ matrixWidth == matrix.getWidth() &&
+ // See 7.3.1 of JISX0510:2004 (p.5).
+ matrix.getWidth() == matrix.getHeight(); // Must be square.
+ }
+
+ // Return debug String.
+ public String toString() {
+ StringBuffer result = new StringBuffer(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 matrixWidth: ");
+ result.append(matrixWidth);
+ result.append("\n maskPattern: ");
+ result.append(maskPattern);
+ result.append("\n numTotalBytes: ");
+ result.append(numTotalBytes);
+ result.append("\n numDataBytes: ");
+ result.append(numDataBytes);
+ result.append("\n numECBytes: ");
+ result.append(numECBytes);
+ result.append("\n numRSBlocks: ");
+ result.append(numRSBlocks);
+ if (matrix == null) {
+ result.append("\n matrix: null\n");
+ } else {
+ result.append("\n matrix:\n");
+ result.append(matrix.toString());
+ }
+ result.append(">>\n");
+ return result.toString();
+ }
+
+ public void setMode(Mode value) {
+ mode = value;
+ }
+
+ public void setECLevel(ErrorCorrectionLevel value) {
+ ecLevel = value;
+ }
+
+ public void setVersion(int value) {
+ version = value;
+ }
+
+ public void setMatrixWidth(int value) {
+ matrixWidth = value;
+ }
+
+ public void setMaskPattern(int value) {
+ maskPattern = value;
+ }
+
+ public void setNumTotalBytes(int value) {
+ numTotalBytes = value;
+ }
+
+ public void setNumDataBytes(int value) {
+ numDataBytes = value;
+ }
+
+ public void setNumECBytes(int value) {
+ numECBytes = value;
+ }
+
+ public void setNumRSBlocks(int value) {
+ numRSBlocks = value;
+ }
+
+ // This takes ownership of the 2D array.
+ 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;
+ }
+
+ // Return true if the all values in the matrix are binary numbers.
+ //
+ // JAVAPORT: This is going to be super expensive and unnecessary, we should not call this in
+ // production. I'm leaving it because it may be useful for testing. It should be removed entirely
+ // if ByteMatrix is changed never to contain a -1.
+ /*
+ private static boolean EverythingIsBinary(final ByteMatrix matrix) {
+ for (int y = 0; y < matrix.height(); ++y) {
+ for (int x = 0; x < matrix.width(); ++x) {
+ int value = matrix.get(y, x);
+ if (!(value == 0 || value == 1)) {
+ // Found non zero/one value.
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ */
+
+}
diff --git a/src/zxing-icon.png b/src/zxing-icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee33194c04e70fca8e7337a3b93852fbf1aa66f1
GIT binary patch
literal 5894
zcmWkyWmFVh7@no1yFt3UrE}>{rI!|vj*pP;Tv(+$Md^@kkS+l!r4|VhrArXL@ywaI
zXU@4l-h1y;?@iFtQN_ci#s&ZYPhAaa0FLtiUQ7(|`@z|30~{be2C9lc-3;vy@CUKjyiOgeU=*-6Z654NI-2u%`PcEmyq%qIG@Y8}_${9#@~5CZ38+wTE|A)$fYBxzq^~SUeGnw+c;fOpG
zK&2E+qn!p+!i7vMMJW6G<;(d@Ez<*Ze`~&i23m*3D9;dKQ8XK)1WasuE6B(5C?=bD0+v!l5lk(IXU^8TQL@rIov6#yvZ1y(fEugt}0pV^Sq?xX|j60sQ-sv
zCq*2T1=1tfnbg!8HI^p_z)BJ|?6Ql*R04RtJ1j(Tfx+#QQNWyy-O2yp$AK166$L6@
z*|weUVsg1pQv=7Dz@iR1&5;jxzvz}G_EN)yltmpAIUOAzGv(JiOlF6ckk>10w`B^M!#lNAsv`$sHsaomu+*XtX##JcROi798v2+
z2DD1W)c2fh=}}9{%1GqyR&^E^^Hs%ZG4^S_yu4t^+hggE!{5gZMV)Q7=@j{x3feRg
zZ3&kv2CT%NSTg51stQR(#MJe=mHa!DJ{U3h`u~~RSc=6WVFO_8p4*DU7aqO0^eY{9
z?}oVq!lU{zw~{|e0DLdAX!KYS*6d2$XgSxVaQR55gGZ$+oKS_OyTh)%|Csm=uBTqB
z$_hU`9yQN41YIr*I-J?Jum9#~asRl{E9(~OZXP!b7(n@nmfNexX|eVqwIrYPt0%JY
zvBC0rBnTiD*%$8f&8dz&k+qy!`ZaFF_4Lbi)}#AHVX1G}byGDWqhl2n-y2ePobhHE
z3B5Yd*diz57FUnN|A?<+0B5YLTEbpHX?NSH6d|%E@f|A9WfjpzRq(o;VGZeC50QdmILb=@LZB$Q)o@6|+-9O-#tSxVcZO
zt!1jyxSk0l(Qx~jHf_$svLi$NhoV*mHBN?m;dO|-DQ1P%D{laJ(8kNfRNlCQ-21Ru3w8f>uik@Rw7IiMo#iMg?`??4IT3S
z@IWJ8hXCyH>!P&Hzn9%m)<%M;bX?7;VX%0Ek4|DM;H_yggqIcAfb%Lp&Bv
z|6jR9rFF5fN}iGhirtW+*JoIf0}SN6W~AqD`K`L+;j(|!f?J(t>3A$VjW(9QEV*c<
zL9j_$3erdJSk{F_MA!Sbhr&kkJbDvcm+Kg5@uLJ|+uOzMot$?1BT%!&y_gVh-lXN^
zpd-VFAe{f;dE
zx$a;6<4s>^WXk5F?Aq6_eNAN7pD3)i=1P=SnjDgbGlz$v_KuE&yS^weOnCI?g$2~U
zO@bMT57pJDBQzf2KB>LEy^nsQYr)4oadAtsEdFY+(6DEwVsK9mOq{xo3aZo7Q#Ca;
z%F8E0LPCTT6mjzX@Qn>w@d6!fLmZ5#mmVIVDySG6lNG)`Q^&=}FRiS^INzU*+dNja
zwx*Z8KT)c&?z74Z22m1lwdy1&AP}DS%)=#w4*rCblhbUo-D`)Co}PanExfRhPMj7W
zES6d;?G3V!xCs%l_480sLj&8}$LEn#4NfIYZa0#g5>SXhM817X3iN-eel933jtLk{
zr971)w;N4W4hjmI>L4K|W{INJE|Pe+I#VDOmz<0Xglh_`kdEwM-!;MpP`WSXHE#~e
z;AwezNvKcop??oza41eL7Ia-_P3o;uQfQtb?{Cjr0S<2NA&`x#=>7NiuaCaA
z5dbGs!n3fKFF6j|=jRQghis(pPlh;nc%nyl6#V^LNuxsh=v|j;24eAO=H}+YExY_}
zJUt0%=)-80uX)>0`?t`XW(&zUY~_C*d@dWCorQP2D4LBcQ-!`e_PLSPNg@p7RHM@FS)l)dlS3oZEFCk19zcl*m%bI;7SJh@
zO-UeOXA%}Bv9z@G=5_pecqoLwv45WK=V##b4xX5l#5}AO8Y;V9=QUF?CRG3Jo5gBT
zc6Qa}?(XiV{GTh0c5P%Gdl`j=Iuv{sEY?o!eulCT%Rtj#-PRg^5
zxx$=-53N$a+jm++u0|wA$Hw9puduPPYff|=SVJ$$=pn%GzQ@q+hc#DNWhbX}`@E9^
z?j^L_xY=PH;tuKKT{vor6V;SQHCAA*tD!ojJ~)8o*6DF(;DDvNJIGxE@rAoE7Q
zpCQ&Uj0AnMMp
zE-&OT77GhYaaEOC@tHj?F0RIG^XSe12rK~5%;Zr3c@HX%fZqh^1p%(w-Ok02g@c2G
zx$N`k1QN(h?LpR$=k{ni8n2?TvI>0nUd(vmqnx+4BBRT7*OmKAibKISyf>YvZ)KGVqEkdvRQYKzSWt~s4>h1?Zk`0L
ze!O>Q6kY~$hRvFTpMP{MZb=b~hWpgWoEG8xr0cNT+tVf~3U^oOOPPfJ6ye^ZgQl+T
z=-=ylfnmC5IvGP}y2R|B+jq>!d35JL7HT$Fd;o*{@UMqI>Z}?Xz4$=D=_iV9|1OYu
z8JIiB-Lova!wa^K`=w|V&za{wl=m#_1VWOtX}
zZhX0yJYz_h6`^ewV1^U-e!#Xpdk#%XQnJzahrxSTy9Hl>fpq(E?_C;5ab6ZtQ8M6U
z&9%OyvomP1%5~yCF%kP)cOVHB71c_!(`?md7&3vq!R3nvw{H!)CEjkrs^KefVOwHc
z6^lsD7lSzw5Kw=>i7GBGTvma*WaCpvB=Q85YvgBD`i!Kiy1Go3(}5@S|17S_W3|cQ
zMWZb`KAm(GmCTiirDY0;)9<^xA*_J(%~q7{vBkM`b%cpAJqjeb9k-Uo%8h#hpOAKY
zTi~1fGg*%t+5s4$pP!$2@D-1mIJ@=*0UC<9-y!M!LpanhgQ~K!64}=Gmv^bwJnoun
zV`xEF?x}HLQ`^Hs=;HFS-AulOXVk7n^o&
zG=JYcv5bt&$?a=3ZS7C3zrLQnlY5#7li%bEp&Z?iNuBoy;U45L>qT1E!UdVxK(mHF
zF{v9wC8#kF66P`0GMOjl(0Dc54Tr-=nSVY^Gq?($xUk78*uUpccq$XcB2+(al#Lla
z=}YLiZ$I?Qf9IxaG+joPCW(T7U?PKH>vGw86y&y`$AlYu7#luG92
zc{Z@u*Ihx288vF&Eocy8eFs10HjUnZhui&h2|ki7dp$X3nU2W3bY7xx<9Qzjh{FlK
zBSs6lJuMUsJ{na%%rccLiX*Y(Gq-m531n5H<1{r3D{Dz(Bax}8=}Xu1Qjkr&yu2@8
zy?Tq}VPRo=e7eeXj(C9Djy}pDF)1hIVRDu#=J(l
z__Wm-C~aKQ>}7GtdE^-g`v+sLVJuVW`T5DBu1jjFDq&%A(sXddm-2E!4ZLKd?}mi)
zh#3&wvohDTzk9B&c=P>0-vtwoMn#;heFZf3W_-d8AW3)50T`E&Ok}wX0`7BNt70A-
z$r;?wZS7fbi|TRk<4`?%E;IL*?GkNm{*WbPBhuu}ydyLCIZ?Myf_hdueAxYuR!Shn
zU0pQJ&dx$2>6T%1b=cqzhFZhXk96m5Fy3hp0ChEV0~;6v70X+2UDKB
zwl*G^lXq`g!CX-wtvp|mLxkZ7%~ID67JkM{%o%Hv}c1Ht-7z6^*0Q#Ac
zko!Lih2L}hw4vS0(t2o|*v?zJkj{VD5P!sZf7{~mtr??n+=lOZ>W+3AB4}^jk
zI%rP>Q8AMv;Mu`~X8zkP+f-GX9rBiwSoIgV(E=stBJ&84L%plg6_oFOTf
zYj-DIR0np8lZCpU6T&0=#LnUgzLSt9E|G>78t^Jn%f;n`eJm(#;(=%75i(Pm4vuUp
zgN3-x7c?U8S;VfcY`?a;fiYS*pHriWl|6FKv!>-KVku&nm~$!S$7qI^MZu&|G>>Wt
zz_1+hh$hH=UT~8_K|1qxg
zrr&1r5dMKyL9i-0M6z9o3SR0`AnB)3^_GQUb{F~0z0KF#qW3jb{lo7~hhB#F->hwo
z?In4}Fr#u(v$Cv$ut50-*+gz;ZeFoQYlESBzt$)ci(QwBKEU{fOuZdGo>mqi4*;G&
z=2=CaJ#$r0AI57Gl9i&_pDoJj58BPh@~S*cz6n_HZY?b$IN`Zs(pgtMTCZ8Sg}-_($oWI-&Oa~7KZ{O0t2Pft%(b+uARh)is3tmnC7n{;?(CEKrH
zatTC=>dLg&x_zef6hg(grmnRPEqS9u*)op9SwG2{)OD6?N-@lX|Fxy1r-bF$#6;x6
zF6jnErBeY{OpL~ETIL5~Y3bz5Oq!F{k2Y&>-;Yhqv})|YGLS1WGNj6=EoV8*M#G|7
z(G{|6FsdXIrZNhY5iYXCKVdayG#`(MG{%cF-`nes#kzb(AeQX@thRK@PyZSrrSe$C
ztjd=Q3r*LYJ2wQXnPLN=jpIIvzcb_Ky1p!zVXVCB82``~C^6@WOS)9e1kX
zT@2L>bFZ$MZJQZc9HVW3hPk1(wl-Gy5Repi=qr*zz3^J}=f@S^2}8FSN3(jocg*?_vT4zx}Y7WqF1%<>oj
literal 0
HcmV?d00001