hints) throws WriterException {
+
+ Writer writer;
+ switch (format) {
+ case EAN_8:
+ writer = new EAN8Writer();
+ break;
+ case EAN_13:
+ writer = new EAN13Writer();
+ break;
+ case UPC_A:
+ writer = new UPCAWriter();
+ break;
+ case QR_CODE:
+ writer = new QRCodeWriter();
+ break;
+ case CODE_39:
+ writer = new Code39Writer();
+ break;
+ case CODE_128:
+ writer = new Code128Writer();
+ break;
+ case ITF:
+ writer = new ITFWriter();
+ break;
+ case PDF_417:
+ writer = new PDF417Writer();
+ break;
+ case CODABAR:
+ writer = new CodaBarWriter();
+ break;
+ case DATA_MATRIX:
+ writer = new DataMatrixWriter();
+ break;
+ case AZTEC:
+ writer = new AztecWriter();
+ break;
+ default:
+ throw new IllegalArgumentException("No encoder available for format " + format);
+ }
+ return writer.encode(contents, format, width, height, hints);
+ }
+
+}
diff --git a/src/com/google/zxing/NotFoundException.java b/src/com/google/zxing/NotFoundException.java
new file mode 100644
index 0000000..a8cefbb
--- /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/PlanarYUVLuminanceSource.java b/src/com/google/zxing/PlanarYUVLuminanceSource.java
new file mode 100644
index 0000000..78568d2
--- /dev/null
+++ b/src/com/google/zxing/PlanarYUVLuminanceSource.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing;
+
+/**
+ * This object extends LuminanceSource around an array of YUV data returned from the camera driver,
+ * with the option to crop to a rectangle within the full data. This can be used to exclude
+ * superfluous pixels around the perimeter and speed up decoding.
+ *
+ * It works for any pixel format where the Y channel is planar and appears first, including
+ * YCbCr_420_SP and YCbCr_422_SP.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class PlanarYUVLuminanceSource extends LuminanceSource {
+
+ private static final int THUMBNAIL_SCALE_FACTOR = 2;
+
+ private final byte[] yuvData;
+ private final int dataWidth;
+ private final int dataHeight;
+ private final int left;
+ private final int top;
+
+ public PlanarYUVLuminanceSource(byte[] yuvData,
+ int dataWidth,
+ int dataHeight,
+ int left,
+ int top,
+ int width,
+ int height,
+ boolean reverseHorizontal) {
+ super(width, height);
+
+ if (left + width > dataWidth || top + height > dataHeight) {
+ throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
+ }
+
+ this.yuvData = yuvData;
+ this.dataWidth = dataWidth;
+ this.dataHeight = dataHeight;
+ this.left = left;
+ this.top = top;
+ if (reverseHorizontal) {
+ reverseHorizontal(width, height);
+ }
+ }
+
+ @Override
+ public byte[] getRow(int y, byte[] row) {
+ if (y < 0 || y >= getHeight()) {
+ throw new IllegalArgumentException("Requested row is outside the image: " + y);
+ }
+ int width = getWidth();
+ if (row == null || row.length < width) {
+ row = new byte[width];
+ }
+ int offset = (y + top) * dataWidth + left;
+ System.arraycopy(yuvData, offset, row, 0, width);
+ return row;
+ }
+
+ @Override
+ public byte[] getMatrix() {
+ int width = getWidth();
+ int height = getHeight();
+
+ // If the caller asks for the entire underlying image, save the copy and give them the
+ // original data. The docs specifically warn that result.length must be ignored.
+ if (width == dataWidth && height == dataHeight) {
+ return yuvData;
+ }
+
+ int area = width * height;
+ byte[] matrix = new byte[area];
+ int inputOffset = top * dataWidth + left;
+
+ // If the width matches the full width of the underlying data, perform a single copy.
+ if (width == dataWidth) {
+ System.arraycopy(yuvData, inputOffset, matrix, 0, area);
+ return matrix;
+ }
+
+ // Otherwise copy one cropped row at a time.
+ byte[] yuv = yuvData;
+ for (int y = 0; y < height; y++) {
+ int outputOffset = y * width;
+ System.arraycopy(yuv, inputOffset, matrix, outputOffset, width);
+ inputOffset += dataWidth;
+ }
+ return matrix;
+ }
+
+ @Override
+ public boolean isCropSupported() {
+ return true;
+ }
+
+ @Override
+ public LuminanceSource crop(int left, int top, int width, int height) {
+ return new PlanarYUVLuminanceSource(yuvData,
+ dataWidth,
+ dataHeight,
+ this.left + left,
+ this.top + top,
+ width,
+ height,
+ false);
+ }
+
+ public int[] renderThumbnail() {
+ int width = getWidth() / THUMBNAIL_SCALE_FACTOR;
+ int height = getHeight() / THUMBNAIL_SCALE_FACTOR;
+ int[] pixels = new int[width * height];
+ byte[] yuv = yuvData;
+ int inputOffset = top * dataWidth + left;
+
+ for (int y = 0; y < height; y++) {
+ int outputOffset = y * width;
+ for (int x = 0; x < width; x++) {
+ int grey = yuv[inputOffset + x * THUMBNAIL_SCALE_FACTOR] & 0xff;
+ pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101);
+ }
+ inputOffset += dataWidth * THUMBNAIL_SCALE_FACTOR;
+ }
+ return pixels;
+ }
+
+ /**
+ * @return width of image from {@link #renderThumbnail()}
+ */
+ public int getThumbnailWidth() {
+ return getWidth() / THUMBNAIL_SCALE_FACTOR;
+ }
+
+ /**
+ * @return height of image from {@link #renderThumbnail()}
+ */
+ public int getThumbnailHeight() {
+ return getHeight() / THUMBNAIL_SCALE_FACTOR;
+ }
+
+ private void reverseHorizontal(int width, int height) {
+ byte[] yuvData = this.yuvData;
+ for (int y = 0, rowStart = top * dataWidth + left; y < height; y++, rowStart += dataWidth) {
+ int middle = rowStart + width / 2;
+ for (int x1 = rowStart, x2 = rowStart + width - 1; x1 < middle; x1++, x2--) {
+ byte temp = yuvData[x1];
+ yuvData[x1] = yuvData[x2];
+ yuvData[x2] = temp;
+ }
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/RGBLuminanceSource.java b/src/com/google/zxing/RGBLuminanceSource.java
new file mode 100644
index 0000000..6583f39
--- /dev/null
+++ b/src/com/google/zxing/RGBLuminanceSource.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing;
+
+/**
+ * This class is used to help decode images from files which arrive as RGB data from
+ * an ARGB pixel array. It does not support rotation.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Betaminos
+ */
+public final class RGBLuminanceSource extends LuminanceSource {
+
+ private final byte[] luminances;
+ private final int dataWidth;
+ private final int dataHeight;
+ private final int left;
+ private final int top;
+
+ public RGBLuminanceSource(int width, int height, int[] pixels) {
+ super(width, height);
+
+ dataWidth = width;
+ dataHeight = height;
+ left = 0;
+ top = 0;
+
+ // In order to measure pure decoding speed, we convert the entire image to a greyscale array
+ // up front, which is the same as the Y channel of the YUVLuminanceSource in the real app.
+ luminances = new byte[width * height];
+ for (int y = 0; y < height; y++) {
+ int offset = y * width;
+ for (int x = 0; x < width; x++) {
+ int pixel = pixels[offset + x];
+ int r = (pixel >> 16) & 0xff;
+ int g = (pixel >> 8) & 0xff;
+ int b = pixel & 0xff;
+ if (r == g && g == b) {
+ // Image is already greyscale, so pick any channel.
+ luminances[offset + x] = (byte) r;
+ } else {
+ // Calculate luminance cheaply, favoring green.
+ luminances[offset + x] = (byte) ((r + 2 * g + b) / 4);
+ }
+ }
+ }
+ }
+
+ private RGBLuminanceSource(byte[] pixels,
+ int dataWidth,
+ int dataHeight,
+ int left,
+ int top,
+ int width,
+ int height) {
+ super(width, height);
+ if (left + width > dataWidth || top + height > dataHeight) {
+ throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
+ }
+ this.luminances = pixels;
+ this.dataWidth = dataWidth;
+ this.dataHeight = dataHeight;
+ this.left = left;
+ this.top = top;
+ }
+
+ @Override
+ public byte[] getRow(int y, byte[] row) {
+ if (y < 0 || y >= getHeight()) {
+ throw new IllegalArgumentException("Requested row is outside the image: " + y);
+ }
+ int width = getWidth();
+ if (row == null || row.length < width) {
+ row = new byte[width];
+ }
+ int offset = (y + top) * dataWidth + left;
+ System.arraycopy(luminances, offset, row, 0, width);
+ return row;
+ }
+
+ @Override
+ public byte[] getMatrix() {
+ int width = getWidth();
+ int height = getHeight();
+
+ // If the caller asks for the entire underlying image, save the copy and give them the
+ // original data. The docs specifically warn that result.length must be ignored.
+ if (width == dataWidth && height == dataHeight) {
+ return luminances;
+ }
+
+ int area = width * height;
+ byte[] matrix = new byte[area];
+ int inputOffset = top * dataWidth + left;
+
+ // If the width matches the full width of the underlying data, perform a single copy.
+ if (width == dataWidth) {
+ System.arraycopy(luminances, inputOffset, matrix, 0, area);
+ return matrix;
+ }
+
+ // Otherwise copy one cropped row at a time.
+ byte[] rgb = luminances;
+ for (int y = 0; y < height; y++) {
+ int outputOffset = y * width;
+ System.arraycopy(rgb, inputOffset, matrix, outputOffset, width);
+ inputOffset += dataWidth;
+ }
+ return matrix;
+ }
+
+ @Override
+ public boolean isCropSupported() {
+ return true;
+ }
+
+ @Override
+ public LuminanceSource crop(int left, int top, int width, int height) {
+ return new RGBLuminanceSource(luminances,
+ dataWidth,
+ dataHeight,
+ this.left + left,
+ this.top + top,
+ width,
+ height);
+ }
+
+}
diff --git a/src/com/google/zxing/Reader.java b/src/com/google/zxing/Reader.java
new file mode 100644
index 0000000..7d6a84e
--- /dev/null
+++ b/src/com/google/zxing/Reader.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing;
+
+import java.util.Map;
+
+/**
+ * Implementations of this interface can decode an image of a barcode in some format into
+ * the String it encodes. For example, {@link com.google.zxing.qrcode.QRCodeReader} can
+ * decode a QR code. The decoder may optionally receive hints from the caller which may help
+ * it decode more quickly or accurately.
+ *
+ * See {@link com.google.zxing.MultiFormatReader}, which attempts to determine what barcode
+ * format is present within the image as well, and then decodes it accordingly.
+ *
+ * @author Sean Owen
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public interface Reader {
+
+ /**
+ * Locates and decodes a barcode in some format within an image.
+ *
+ * @param image image of barcode to decode
+ * @return String which the barcode encodes
+ * @throws NotFoundException if no potential barcode is found
+ * @throws ChecksumException if a potential barcode is found but does not pass its checksum
+ * @throws FormatException if a potential barcode is found but format is invalid
+ */
+ Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException;
+
+ /**
+ * Locates and decodes a barcode in some format within an image. This method also accepts
+ * hints, each possibly associated to some data, which may help the implementation decode.
+ *
+ * @param image image of barcode to decode
+ * @param hints passed as a {@link Map} from {@link com.google.zxing.DecodeHintType}
+ * to arbitrary data. The
+ * meaning of the data depends upon the hint type. The implementation may or may not do
+ * anything with these hints.
+ * @return String which the barcode encodes
+ * @throws NotFoundException if no potential barcode is found
+ * @throws ChecksumException if a potential barcode is found but does not pass its checksum
+ * @throws FormatException if a potential barcode is found but format is invalid
+ */
+ Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, ChecksumException, FormatException;
+
+ /**
+ * Resets any internal state the implementation has after a decode, to prepare it
+ * for reuse.
+ */
+ void reset();
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/ReaderException.java b/src/com/google/zxing/ReaderException.java
new file mode 100644
index 0000000..b38fce9
--- /dev/null
+++ b/src/com/google/zxing/ReaderException.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;
+
+/**
+ * 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 {
+
+ // disable stack traces when not running inside test units
+ protected static final boolean isStackTrace = System.getProperty("surefire.test.class.path") != null;
+
+ ReaderException() {
+ // do nothing
+ }
+
+ ReaderException(Throwable cause) {
+ super(cause);
+ }
+
+ // Prevent stack traces from being taken
+ // srowen says: huh, my IDE is saying this is not an override. native methods can't be overridden?
+ // This, at least, does not hurt. Because we use a singleton pattern here, it doesn't matter anyhow.
+ @Override
+ public final Throwable fillInStackTrace() {
+ return null;
+ }
+
+}
diff --git a/src/com/google/zxing/Result.java b/src/com/google/zxing/Result.java
new file mode 100644
index 0000000..d1d6157
--- /dev/null
+++ b/src/com/google/zxing/Result.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * Encapsulates the result of decoding a barcode within an image.
+ *
+ * @author Sean Owen
+ */
+public final class Result {
+
+ private final String text;
+ private final byte[] rawBytes;
+ private final BarcodeFormat format;
+ private final long timestamp;
+ private ResultPoint[] resultPoints;
+ private Map resultMetadata;
+
+ public Result(String text,
+ byte[] rawBytes,
+ ResultPoint[] resultPoints,
+ BarcodeFormat format) {
+ this(text, rawBytes, resultPoints, format, System.currentTimeMillis());
+ }
+
+ public Result(String text,
+ byte[] rawBytes,
+ ResultPoint[] resultPoints,
+ BarcodeFormat format,
+ long timestamp) {
+ this.text = text;
+ this.rawBytes = rawBytes;
+ this.resultPoints = resultPoints;
+ this.format = format;
+ this.resultMetadata = null;
+ this.timestamp = timestamp;
+ }
+
+ /**
+ * @return raw text encoded by the barcode
+ */
+ public String getText() {
+ return text;
+ }
+
+ /**
+ * @return raw bytes encoded by the barcode, if applicable, otherwise {@code null}
+ */
+ public byte[] getRawBytes() {
+ return rawBytes;
+ }
+
+ /**
+ * @return points related to the barcode in the image. These are typically points
+ * identifying finder patterns or the corners of the barcode. The exact meaning is
+ * specific to the type of barcode that was decoded.
+ */
+ public ResultPoint[] getResultPoints() {
+ return resultPoints;
+ }
+
+ /**
+ * @return {@link BarcodeFormat} representing the format of the barcode that was decoded
+ */
+ public BarcodeFormat getBarcodeFormat() {
+ return format;
+ }
+
+ /**
+ * @return {@link Map} mapping {@link ResultMetadataType} keys to values. May be
+ * {@code null}. This contains optional metadata about what was detected about the barcode,
+ * like orientation.
+ */
+ public Map getResultMetadata() {
+ return resultMetadata;
+ }
+
+ public void putMetadata(ResultMetadataType type, Object value) {
+ if (resultMetadata == null) {
+ resultMetadata = new EnumMap<>(ResultMetadataType.class);
+ }
+ resultMetadata.put(type, value);
+ }
+
+ public void putAllMetadata(Map metadata) {
+ if (metadata != null) {
+ if (resultMetadata == null) {
+ resultMetadata = metadata;
+ } else {
+ resultMetadata.putAll(metadata);
+ }
+ }
+ }
+
+ public void addResultPoints(ResultPoint[] newPoints) {
+ ResultPoint[] oldPoints = resultPoints;
+ if (oldPoints == null) {
+ resultPoints = newPoints;
+ } else if (newPoints != null && newPoints.length > 0) {
+ ResultPoint[] allPoints = new ResultPoint[oldPoints.length + newPoints.length];
+ System.arraycopy(oldPoints, 0, allPoints, 0, oldPoints.length);
+ System.arraycopy(newPoints, 0, allPoints, oldPoints.length, newPoints.length);
+ resultPoints = allPoints;
+ }
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public String toString() {
+ return text;
+ }
+
+}
diff --git a/src/com/google/zxing/ResultMetadataType.java b/src/com/google/zxing/ResultMetadataType.java
new file mode 100644
index 0000000..c2f1ba7
--- /dev/null
+++ b/src/com/google/zxing/ResultMetadataType.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing;
+
+/**
+ * Represents some type of metadata about the result of the decoding that the decoder
+ * wishes to communicate back to the caller.
+ *
+ * @author Sean Owen
+ */
+public enum ResultMetadataType {
+
+ /**
+ * Unspecified, application-specific metadata. Maps to an unspecified {@link Object}.
+ */
+ OTHER,
+
+ /**
+ * Denotes the likely approximate orientation of the barcode in the image. This value
+ * is given as degrees rotated clockwise from the normal, upright orientation.
+ * For example a 1D barcode which was found by reading top-to-bottom would be
+ * said to have orientation "90". This key maps to an {@link Integer} whose
+ * value is in the range [0,360).
+ */
+ ORIENTATION,
+
+ /**
+ * 2D barcode formats typically encode text, but allow for a sort of 'byte mode'
+ * which is sometimes used to encode binary data. While {@link Result} makes available
+ * the complete raw bytes in the barcode for these formats, it does not offer the bytes
+ * from the byte segments alone.
+ *
+ *
This maps to a {@link java.util.List} of byte arrays corresponding to the
+ * raw bytes in the byte segments in the barcode, in order.
+ */
+ BYTE_SEGMENTS,
+
+ /**
+ * Error correction level used, if applicable. The value type depends on the
+ * format, but is typically a String.
+ */
+ ERROR_CORRECTION_LEVEL,
+
+ /**
+ * For some periodicals, indicates the issue number as an {@link Integer}.
+ */
+ ISSUE_NUMBER,
+
+ /**
+ * For some products, indicates the suggested retail price in the barcode as a
+ * formatted {@link String}.
+ */
+ SUGGESTED_PRICE,
+
+ /**
+ * For some products, the possible country of manufacture as a {@link String} denoting the
+ * ISO country code. Some map to multiple possible countries, like "US/CA".
+ */
+ POSSIBLE_COUNTRY,
+
+ /**
+ * For some products, the extension text
+ */
+ UPC_EAN_EXTENSION,
+
+ /**
+ * PDF417-specific metadata
+ */
+ PDF417_EXTRA_METADATA,
+
+ /**
+ * If the code format supports structured append and the current scanned code is part of one then the
+ * sequence number is given with it.
+ */
+ STRUCTURED_APPEND_SEQUENCE,
+
+ /**
+ * If the code format supports structured append and the current scanned code is part of one then the
+ * parity is given with it.
+ */
+ STRUCTURED_APPEND_PARITY,
+
+}
diff --git a/src/com/google/zxing/ResultPoint.java b/src/com/google/zxing/ResultPoint.java
new file mode 100644
index 0000000..e35a518
--- /dev/null
+++ b/src/com/google/zxing/ResultPoint.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing;
+
+import com.google.zxing.common.detector.MathUtils;
+
+/**
+ * Encapsulates a point of interest in an image containing a barcode. Typically, this
+ * would be the location of a finder pattern or the corner of the barcode, for example.
+ *
+ * @author Sean Owen
+ */
+public class ResultPoint {
+
+ private final float x;
+ private final float y;
+
+ public ResultPoint(float x, float y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ /**
+ * Orders an array of three ResultPoints in an order [A,B,C] such that AB is less than AC
+ * and BC is less than AC, and the angle between BC and BA is less than 180 degrees.
+ *
+ * @param patterns array of three {@code ResultPoint} to order
+ */
+ public static void orderBestPatterns(ResultPoint[] patterns) {
+
+ // Find distances between pattern centers
+ float zeroOneDistance = distance(patterns[0], patterns[1]);
+ float oneTwoDistance = distance(patterns[1], patterns[2]);
+ float zeroTwoDistance = distance(patterns[0], patterns[2]);
+
+ ResultPoint pointA;
+ ResultPoint pointB;
+ ResultPoint pointC;
+ // Assume one closest to other two is B; A and C will just be guesses at first
+ if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) {
+ pointB = patterns[0];
+ pointA = patterns[1];
+ pointC = patterns[2];
+ } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) {
+ pointB = patterns[1];
+ pointA = patterns[0];
+ pointC = patterns[2];
+ } else {
+ pointB = patterns[2];
+ pointA = patterns[0];
+ pointC = patterns[1];
+ }
+
+ // Use cross product to figure out whether A and C are correct or flipped.
+ // This asks whether BC x BA has a positive z component, which is the arrangement
+ // we want for A, B, C. If it's negative, then we've got it flipped around and
+ // should swap A and C.
+ if (crossProductZ(pointA, pointB, pointC) < 0.0f) {
+ ResultPoint temp = pointA;
+ pointA = pointC;
+ pointC = temp;
+ }
+
+ patterns[0] = pointA;
+ patterns[1] = pointB;
+ patterns[2] = pointC;
+ }
+
+ /**
+ * @param pattern1 first pattern
+ * @param pattern2 second pattern
+ * @return distance between two points
+ */
+ public static float distance(ResultPoint pattern1, ResultPoint pattern2) {
+ return MathUtils.distance(pattern1.x, pattern1.y, pattern2.x, pattern2.y);
+ }
+
+ /**
+ * Returns the z component of the cross product between vectors BC and BA.
+ */
+ private static float crossProductZ(ResultPoint pointA,
+ ResultPoint pointB,
+ ResultPoint pointC) {
+ float bX = pointB.x;
+ float bY = pointB.y;
+ return ((pointC.x - bX) * (pointA.y - bY)) - ((pointC.y - bY) * (pointA.x - bX));
+ }
+
+ public final float getX() {
+ return x;
+ }
+
+ public final float getY() {
+ return y;
+ }
+
+ @Override
+ public final boolean equals(Object other) {
+ if (other instanceof ResultPoint) {
+ ResultPoint otherPoint = (ResultPoint) other;
+ return x == otherPoint.x && y == otherPoint.y;
+ }
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return 31 * Float.floatToIntBits(x) + Float.floatToIntBits(y);
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder result = new StringBuilder(25);
+ result.append('(');
+ result.append(x);
+ result.append(',');
+ result.append(y);
+ result.append(')');
+ return result.toString();
+ }
+
+
+}
diff --git a/src/com/google/zxing/ResultPointCallback.java b/src/com/google/zxing/ResultPointCallback.java
new file mode 100644
index 0000000..f31216c
--- /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/StringsResourceTranslator.java b/src/com/google/zxing/StringsResourceTranslator.java
new file mode 100644
index 0000000..84b0559
--- /dev/null
+++ b/src/com/google/zxing/StringsResourceTranslator.java
@@ -0,0 +1,230 @@
+/*
+ * 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;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Writer;
+import java.net.URI;
+import java.net.URLConnection;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.*;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A utility which auto-translates English strings in Android string resources using
+ * Google Translate.
+ *
+ *
Pass the Android client res/ directory as first argument, and optionally message keys
+ * who should be forced to retranslate.
+ * Usage: {@code StringsResourceTranslator android/res/ [key_1 ...]}
+ *
+ *
You must set your Google Translate API key into the environment with -DtranslateAPI.key=...
+ *
+ * @author Sean Owen
+ */
+public final class StringsResourceTranslator {
+
+ private static final String API_KEY = System.getProperty("translateAPI.key");
+
+ static {
+ if (API_KEY == null) {
+ throw new IllegalArgumentException("translateAPI.key is not specified");
+ }
+ }
+
+ private static final Pattern ENTRY_PATTERN = Pattern.compile("([^<]+)");
+ private static final Pattern STRINGS_FILE_NAME_PATTERN = Pattern.compile("values-(.+)");
+ private static final Pattern TRANSLATE_RESPONSE_PATTERN = Pattern.compile("translatedText\":\\s*\"([^\"]+)\"");
+ private static final Pattern VALUES_DIR_PATTERN = Pattern.compile("values-[a-z]{2}(-[a-zA-Z]{2,3})?");
+
+ private static final String APACHE_2_LICENSE =
+ "\n";
+
+ private static final Map LANGUAGE_CODE_MASSAGINGS = new HashMap<>(3);
+
+ static {
+ LANGUAGE_CODE_MASSAGINGS.put("zh-rCN", "zh-cn");
+ LANGUAGE_CODE_MASSAGINGS.put("zh-rTW", "zh-tw");
+ }
+
+ private StringsResourceTranslator() {
+ }
+
+ public static void main(String[] args) throws IOException {
+ Path resDir = Paths.get(args[0]);
+ Path valueDir = resDir.resolve("values");
+ Path stringsFile = valueDir.resolve("strings.xml");
+ Collection forceRetranslation = Arrays.asList(args).subList(1, args.length);
+
+ DirectoryStream.Filter filter = new DirectoryStream.Filter() {
+ @Override
+ public boolean accept(Path entry) {
+ return Files.isDirectory(entry) && !Files.isSymbolicLink(entry) &&
+ VALUES_DIR_PATTERN.matcher(entry.getFileName().toString()).matches();
+ }
+ };
+ try (DirectoryStream dirs = Files.newDirectoryStream(resDir, filter)) {
+ for (Path dir : dirs) {
+ translate(stringsFile, dir.resolve("strings.xml"), forceRetranslation);
+ }
+ }
+ }
+
+ private static void translate(Path englishFile,
+ Path translatedFile,
+ Collection forceRetranslation) throws IOException {
+
+ Map english = readLines(englishFile);
+ Map translated = readLines(translatedFile);
+ String parentName = translatedFile.getParent().getFileName().toString();
+
+ Matcher stringsFileNameMatcher = STRINGS_FILE_NAME_PATTERN.matcher(parentName);
+ if (!stringsFileNameMatcher.find()) {
+ throw new IllegalArgumentException("Invalid parent dir: " + parentName);
+ }
+ String language = stringsFileNameMatcher.group(1);
+ String massagedLanguage = LANGUAGE_CODE_MASSAGINGS.get(language);
+ if (massagedLanguage != null) {
+ language = massagedLanguage;
+ }
+
+ System.out.println("Translating " + language);
+
+ Path resultTempFile = Files.createTempFile(null, null);
+
+ boolean anyChange = false;
+ try (Writer out = Files.newBufferedWriter(resultTempFile, StandardCharsets.UTF_8)) {
+ out.write("\n");
+ out.write(APACHE_2_LICENSE);
+ out.write("\n");
+
+ for (Map.Entry englishEntry : english.entrySet()) {
+ String key = englishEntry.getKey();
+ String value = englishEntry.getValue();
+ out.write(" ');
+
+ String translatedString = translated.get(key);
+ if (translatedString == null || forceRetranslation.contains(key)) {
+ anyChange = true;
+ translatedString = translateString(value, language);
+ // Specially for string resources, escape ' with \
+ translatedString = translatedString.replaceAll("'", "\\\\'");
+ }
+ out.write(translatedString);
+
+ out.write("\n");
+ }
+
+ out.write("\n");
+ out.flush();
+ }
+
+ if (anyChange) {
+ System.out.println(" Writing translations");
+ Files.move(resultTempFile, translatedFile, StandardCopyOption.REPLACE_EXISTING);
+ } else {
+ Files.delete(resultTempFile);
+ }
+ }
+
+ static String translateString(String english, String language) throws IOException {
+ if ("en".equals(language)) {
+ return english;
+ }
+ String massagedLanguage = LANGUAGE_CODE_MASSAGINGS.get(language);
+ if (massagedLanguage != null) {
+ language = massagedLanguage;
+ }
+ System.out.println(" Need translation for " + english);
+
+ URI translateURI = URI.create(
+ "https://www.googleapis.com/language/translate/v2?key=" + API_KEY + "&q=" +
+ URLEncoder.encode(english, "UTF-8") +
+ "&source=en&target=" + language);
+ CharSequence translateResult = fetch(translateURI);
+ Matcher m = TRANSLATE_RESPONSE_PATTERN.matcher(translateResult);
+ if (!m.find()) {
+ System.err.println("No translate result");
+ System.err.println(translateResult);
+ return english;
+ }
+ String translation = m.group(1);
+
+ // This is a little crude; unescape some common escapes in the raw response
+ translation = translation.replaceAll("&(amp;)?quot;", "\"");
+ translation = translation.replaceAll("&(amp;)?#39;", "'");
+
+ System.out.println(" Got translation " + translation);
+ return translation;
+ }
+
+ private static CharSequence fetch(URI translateURI) throws IOException {
+ URLConnection connection = translateURI.toURL().openConnection();
+ connection.connect();
+ StringBuilder translateResult = new StringBuilder(200);
+ try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
+ char[] buffer = new char[8192];
+ int charsRead;
+ while ((charsRead = in.read(buffer)) > 0) {
+ translateResult.append(buffer, 0, charsRead);
+ }
+ }
+ return translateResult;
+ }
+
+ private static Map readLines(Path file) throws IOException {
+ if (Files.exists(file)) {
+ Map entries = new TreeMap<>();
+ for (String line : Files.readAllLines(file, StandardCharsets.UTF_8)) {
+ Matcher m = ENTRY_PATTERN.matcher(line);
+ if (m.find()) {
+ entries.put(m.group(1), m.group(2));
+ }
+ }
+ return entries;
+ } else {
+ return Collections.emptyMap();
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/Writer.java b/src/com/google/zxing/Writer.java
new file mode 100644
index 0000000..d1de41a
--- /dev/null
+++ b/src/com/google/zxing/Writer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing;
+
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * The base class for all objects which encode/generate a barcode image.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public interface Writer {
+
+ /**
+ * Encode a barcode using the default settings.
+ *
+ * @param contents The contents to encode in the barcode
+ * @param format The barcode format to generate
+ * @param width The preferred width in pixels
+ * @param height The preferred height in pixels
+ * @return {@link BitMatrix} representing encoded barcode image
+ * @throws WriterException if contents cannot be encoded legally in a format
+ */
+ BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
+ throws WriterException;
+
+ /**
+ * @param contents The contents to encode in the barcode
+ * @param format The barcode format to generate
+ * @param width The preferred width in pixels
+ * @param height The preferred height in pixels
+ * @param hints Additional parameters to supply to the encoder
+ * @return {@link BitMatrix} representing encoded barcode image
+ * @throws WriterException if contents cannot be encoded legally in a format
+ */
+ BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints)
+ throws WriterException;
+
+}
diff --git a/src/com/google/zxing/WriterException.java b/src/com/google/zxing/WriterException.java
new file mode 100644
index 0000000..b5331d2
--- /dev/null
+++ b/src/com/google/zxing/WriterException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing;
+
+/**
+ * A base class which covers the range of exceptions which may occur when encoding a barcode using
+ * the Writer framework.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class WriterException extends Exception {
+
+ public WriterException() {
+ }
+
+ public WriterException(String message) {
+ super(message);
+ }
+
+ public WriterException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/com/google/zxing/aztec/AztecDetectorResult.java b/src/com/google/zxing/aztec/AztecDetectorResult.java
new file mode 100644
index 0000000..bd281bf
--- /dev/null
+++ b/src/com/google/zxing/aztec/AztecDetectorResult.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec;
+
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DetectorResult;
+
+public final class AztecDetectorResult extends DetectorResult {
+
+ private final boolean compact;
+ private final int nbDatablocks;
+ private final int nbLayers;
+
+ public AztecDetectorResult(BitMatrix bits,
+ ResultPoint[] points,
+ boolean compact,
+ int nbDatablocks,
+ int nbLayers) {
+ super(bits, points);
+ this.compact = compact;
+ this.nbDatablocks = nbDatablocks;
+ this.nbLayers = nbLayers;
+ }
+
+ public int getNbLayers() {
+ return nbLayers;
+ }
+
+ public int getNbDatablocks() {
+ return nbDatablocks;
+ }
+
+ public boolean isCompact() {
+ return compact;
+ }
+
+}
diff --git a/src/com/google/zxing/aztec/AztecReader.java b/src/com/google/zxing/aztec/AztecReader.java
new file mode 100644
index 0000000..6c59594
--- /dev/null
+++ b/src/com/google/zxing/aztec/AztecReader.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec;
+
+import com.google.zxing.*;
+import com.google.zxing.aztec.decoder.Decoder;
+import com.google.zxing.aztec.detector.Detector;
+import com.google.zxing.common.DecoderResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode Aztec codes in an image.
+ *
+ * @author David Olivier
+ */
+public final class AztecReader implements Reader {
+
+ /**
+ * Locates and decodes a Data Matrix code in an image.
+ *
+ * @return a String representing the content encoded by the Data Matrix code
+ * @throws NotFoundException if a Data Matrix code cannot be found
+ * @throws FormatException if a Data Matrix code cannot be decoded
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, FormatException {
+
+ NotFoundException notFoundException = null;
+ FormatException formatException = null;
+ Detector detector = new Detector(image.getBlackMatrix());
+ ResultPoint[] points = null;
+ DecoderResult decoderResult = null;
+ try {
+ AztecDetectorResult detectorResult = detector.detect(false);
+ points = detectorResult.getPoints();
+ decoderResult = new Decoder().decode(detectorResult);
+ } catch (NotFoundException e) {
+ notFoundException = e;
+ } catch (FormatException e) {
+ formatException = e;
+ }
+ if (decoderResult == null) {
+ try {
+ AztecDetectorResult detectorResult = detector.detect(true);
+ points = detectorResult.getPoints();
+ decoderResult = new Decoder().decode(detectorResult);
+ } catch (NotFoundException | FormatException e) {
+ if (notFoundException != null) {
+ throw notFoundException;
+ }
+ if (formatException != null) {
+ throw formatException;
+ }
+ throw e;
+ }
+ }
+
+ if (hints != null) {
+ ResultPointCallback rpcb = (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+ if (rpcb != null) {
+ for (ResultPoint point : points) {
+ rpcb.foundPossibleResultPoint(point);
+ }
+ }
+ }
+
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.AZTEC);
+
+ List byteSegments = decoderResult.getByteSegments();
+ if (byteSegments != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
+ }
+ String ecLevel = decoderResult.getECLevel();
+ if (ecLevel != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ // do nothing
+ }
+
+}
diff --git a/src/com/google/zxing/aztec/AztecWriter.java b/src/com/google/zxing/aztec/AztecWriter.java
new file mode 100644
index 0000000..21303c4
--- /dev/null
+++ b/src/com/google/zxing/aztec/AztecWriter.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.Writer;
+import com.google.zxing.aztec.encoder.AztecCode;
+import com.google.zxing.aztec.encoder.Encoder;
+import com.google.zxing.common.BitMatrix;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+
+public final class AztecWriter implements Writer {
+
+ private static final Charset DEFAULT_CHARSET = Charset.forName("ISO-8859-1");
+
+ private static BitMatrix encode(String contents, BarcodeFormat format,
+ int width, int height,
+ Charset charset, int eccPercent, int layers) {
+ if (format != BarcodeFormat.AZTEC) {
+ throw new IllegalArgumentException("Can only encode AZTEC, but got " + format);
+ }
+ AztecCode aztec = Encoder.encode(contents.getBytes(charset), eccPercent, layers);
+ return renderResult(aztec, width, height);
+ }
+
+ private static BitMatrix renderResult(AztecCode code, int width, int height) {
+ BitMatrix input = code.getMatrix();
+ if (input == null) {
+ throw new IllegalStateException();
+ }
+ int inputWidth = input.getWidth();
+ int inputHeight = input.getHeight();
+ int outputWidth = Math.max(width, inputWidth);
+ int outputHeight = Math.max(height, inputHeight);
+
+ int multiple = Math.min(outputWidth / inputWidth, outputHeight / inputHeight);
+ int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
+ int topPadding = (outputHeight - (inputHeight * multiple)) / 2;
+
+ BitMatrix output = new BitMatrix(outputWidth, outputHeight);
+
+ for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) {
+ // Write the contents of this row of the barcode
+ for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
+ if (input.get(inputX, inputY)) {
+ output.setRegion(outputX, outputY, multiple, multiple);
+ }
+ }
+ }
+ return output;
+ }
+
+ @Override
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
+ return encode(contents, format, width, height, null);
+ }
+
+ @Override
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Map hints) {
+ String charset = hints == null ? null : (String) hints.get(EncodeHintType.CHARACTER_SET);
+ Number eccPercent = hints == null ? null : (Number) hints.get(EncodeHintType.ERROR_CORRECTION);
+ Number layers = hints == null ? null : (Number) hints.get(EncodeHintType.AZTEC_LAYERS);
+ return encode(contents,
+ format,
+ width,
+ height,
+ charset == null ? DEFAULT_CHARSET : Charset.forName(charset),
+ eccPercent == null ? Encoder.DEFAULT_EC_PERCENT : eccPercent.intValue(),
+ layers == null ? Encoder.DEFAULT_AZTEC_LAYERS : layers.intValue());
+ }
+}
diff --git a/src/com/google/zxing/aztec/decoder/Decoder.java b/src/com/google/zxing/aztec/decoder/Decoder.java
new file mode 100644
index 0000000..6f22e76
--- /dev/null
+++ b/src/com/google/zxing/aztec/decoder/Decoder.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.aztec.AztecDetectorResult;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+import java.util.Arrays;
+
+/**
+ * The main class which implements Aztec Code decoding -- as opposed to locating and extracting
+ * the Aztec Code from an image.
+ *
+ * @author David Olivier
+ */
+public final class Decoder {
+
+ private static final String[] UPPER_TABLE = {
+ "CTRL_PS", " ", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
+ "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "CTRL_LL", "CTRL_ML", "CTRL_DL", "CTRL_BS"
+ };
+ private static final String[] LOWER_TABLE = {
+ "CTRL_PS", " ", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p",
+ "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "CTRL_US", "CTRL_ML", "CTRL_DL", "CTRL_BS"
+ };
+ private static final String[] MIXED_TABLE = {
+ "CTRL_PS", " ", "\1", "\2", "\3", "\4", "\5", "\6", "\7", "\b", "\t", "\n",
+ "\13", "\f", "\r", "\33", "\34", "\35", "\36", "\37", "@", "\\", "^", "_",
+ "`", "|", "~", "\177", "CTRL_LL", "CTRL_UL", "CTRL_PL", "CTRL_BS"
+ };
+ private static final String[] PUNCT_TABLE = {
+ "", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
+ "*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL"
+ };
+ private static final String[] DIGIT_TABLE = {
+ "CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US"
+ };
+ private AztecDetectorResult ddata;
+
+ // This method is used for testing the high-level encoder
+ public static String highLevelDecode(boolean[] correctedBits) {
+ return getEncodedData(correctedBits);
+ }
+
+ /**
+ * Gets the string encoded in the aztec code bits
+ *
+ * @return the decoded string
+ */
+ private static String getEncodedData(boolean[] correctedBits) {
+ int endIndex = correctedBits.length;
+ Table latchTable = Table.UPPER; // table most recently latched to
+ Table shiftTable = Table.UPPER; // table to use for the next read
+ StringBuilder result = new StringBuilder(20);
+ int index = 0;
+ while (index < endIndex) {
+ if (shiftTable == Table.BINARY) {
+ if (endIndex - index < 5) {
+ break;
+ }
+ int length = readCode(correctedBits, index, 5);
+ index += 5;
+ if (length == 0) {
+ if (endIndex - index < 11) {
+ break;
+ }
+ length = readCode(correctedBits, index, 11) + 31;
+ index += 11;
+ }
+ for (int charCount = 0; charCount < length; charCount++) {
+ if (endIndex - index < 8) {
+ index = endIndex; // Force outer loop to exit
+ break;
+ }
+ int code = readCode(correctedBits, index, 8);
+ result.append((char) code);
+ index += 8;
+ }
+ // Go back to whatever mode we had been in
+ shiftTable = latchTable;
+ } else {
+ int size = shiftTable == Table.DIGIT ? 4 : 5;
+ if (endIndex - index < size) {
+ break;
+ }
+ int code = readCode(correctedBits, index, size);
+ index += size;
+ String str = getCharacter(shiftTable, code);
+ if (str.startsWith("CTRL_")) {
+ // Table changes
+ shiftTable = getTable(str.charAt(5));
+ if (str.charAt(6) == 'L') {
+ latchTable = shiftTable;
+ }
+ } else {
+ result.append(str);
+ // Go back to whatever mode we had been in
+ shiftTable = latchTable;
+ }
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * gets the table corresponding to the char passed
+ */
+ private static Table getTable(char t) {
+ switch (t) {
+ case 'L':
+ return Table.LOWER;
+ case 'P':
+ return Table.PUNCT;
+ case 'M':
+ return Table.MIXED;
+ case 'D':
+ return Table.DIGIT;
+ case 'B':
+ return Table.BINARY;
+ case 'U':
+ default:
+ return Table.UPPER;
+ }
+ }
+
+ /**
+ * Gets the character (or string) corresponding to the passed code in the given table
+ *
+ * @param table the table used
+ * @param code the code of the character
+ */
+ private static String getCharacter(Table table, int code) {
+ switch (table) {
+ case UPPER:
+ return UPPER_TABLE[code];
+ case LOWER:
+ return LOWER_TABLE[code];
+ case MIXED:
+ return MIXED_TABLE[code];
+ case PUNCT:
+ return PUNCT_TABLE[code];
+ case DIGIT:
+ return DIGIT_TABLE[code];
+ default:
+ // Should not reach here.
+ throw new IllegalStateException("Bad table");
+ }
+ }
+
+ /**
+ * Reads a code of given length and at given index in an array of bits
+ */
+ private static int readCode(boolean[] rawbits, int startIndex, int length) {
+ int res = 0;
+ for (int i = startIndex; i < startIndex + length; i++) {
+ res <<= 1;
+ if (rawbits[i]) {
+ res |= 0x01;
+ }
+ }
+ return res;
+ }
+
+ private static int totalBitsInLayer(int layers, boolean compact) {
+ return ((compact ? 88 : 112) + 16 * layers) * layers;
+ }
+
+ public DecoderResult decode(AztecDetectorResult detectorResult) throws FormatException {
+ ddata = detectorResult;
+ BitMatrix matrix = detectorResult.getBits();
+ boolean[] rawbits = extractBits(matrix);
+ boolean[] correctedBits = correctBits(rawbits);
+ String result = getEncodedData(correctedBits);
+ return new DecoderResult(null, result, null, null);
+ }
+
+ /**
+ * Performs RS error correction on an array of bits.
+ *
+ * @return the corrected array
+ * @throws FormatException if the input contains too many errors
+ */
+ private boolean[] correctBits(boolean[] rawbits) throws FormatException {
+ GenericGF gf;
+ int codewordSize;
+
+ if (ddata.getNbLayers() <= 2) {
+ codewordSize = 6;
+ gf = GenericGF.AZTEC_DATA_6;
+ } else if (ddata.getNbLayers() <= 8) {
+ codewordSize = 8;
+ gf = GenericGF.AZTEC_DATA_8;
+ } else if (ddata.getNbLayers() <= 22) {
+ codewordSize = 10;
+ gf = GenericGF.AZTEC_DATA_10;
+ } else {
+ codewordSize = 12;
+ gf = GenericGF.AZTEC_DATA_12;
+ }
+
+ int numDataCodewords = ddata.getNbDatablocks();
+ int numCodewords = rawbits.length / codewordSize;
+ if (numCodewords < numDataCodewords) {
+ throw FormatException.getFormatInstance();
+ }
+ int offset = rawbits.length % codewordSize;
+ int numECCodewords = numCodewords - numDataCodewords;
+
+ int[] dataWords = new int[numCodewords];
+ for (int i = 0; i < numCodewords; i++, offset += codewordSize) {
+ dataWords[i] = readCode(rawbits, offset, codewordSize);
+ }
+
+ try {
+ ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(gf);
+ rsDecoder.decode(dataWords, numECCodewords);
+ } catch (ReedSolomonException ex) {
+ throw FormatException.getFormatInstance(ex);
+ }
+
+ // Now perform the unstuffing operation.
+ // First, count how many bits are going to be thrown out as stuffing
+ int mask = (1 << codewordSize) - 1;
+ int stuffedBits = 0;
+ for (int i = 0; i < numDataCodewords; i++) {
+ int dataWord = dataWords[i];
+ if (dataWord == 0 || dataWord == mask) {
+ throw FormatException.getFormatInstance();
+ } else if (dataWord == 1 || dataWord == mask - 1) {
+ stuffedBits++;
+ }
+ }
+ // Now, actually unpack the bits and remove the stuffing
+ boolean[] correctedBits = new boolean[numDataCodewords * codewordSize - stuffedBits];
+ int index = 0;
+ for (int i = 0; i < numDataCodewords; i++) {
+ int dataWord = dataWords[i];
+ if (dataWord == 1 || dataWord == mask - 1) {
+ // next codewordSize-1 bits are all zeros or all ones
+ Arrays.fill(correctedBits, index, index + codewordSize - 1, dataWord > 1);
+ index += codewordSize - 1;
+ } else {
+ for (int bit = codewordSize - 1; bit >= 0; --bit) {
+ correctedBits[index++] = (dataWord & (1 << bit)) != 0;
+ }
+ }
+ }
+ return correctedBits;
+ }
+
+ /**
+ * Gets the array of bits from an Aztec Code matrix
+ *
+ * @return the array of bits
+ */
+ boolean[] extractBits(BitMatrix matrix) {
+ boolean compact = ddata.isCompact();
+ int layers = ddata.getNbLayers();
+ int baseMatrixSize = compact ? 11 + layers * 4 : 14 + layers * 4; // not including alignment lines
+ int[] alignmentMap = new int[baseMatrixSize];
+ boolean[] rawbits = new boolean[totalBitsInLayer(layers, compact)];
+
+ if (compact) {
+ for (int i = 0; i < alignmentMap.length; i++) {
+ alignmentMap[i] = i;
+ }
+ } else {
+ int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
+ int origCenter = baseMatrixSize / 2;
+ int center = matrixSize / 2;
+ for (int i = 0; i < origCenter; i++) {
+ int newOffset = i + i / 15;
+ alignmentMap[origCenter - i - 1] = center - newOffset - 1;
+ alignmentMap[origCenter + i] = center + newOffset + 1;
+ }
+ }
+ for (int i = 0, rowOffset = 0; i < layers; i++) {
+ int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
+ // The top-left most point of this layer is (not including alignment lines)
+ int low = i * 2;
+ // The bottom-right most point of this layer is (not including alignment lines)
+ int high = baseMatrixSize - 1 - low;
+ // We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
+ for (int j = 0; j < rowSize; j++) {
+ int columnOffset = j * 2;
+ for (int k = 0; k < 2; k++) {
+ // left column
+ rawbits[rowOffset + columnOffset + k] =
+ matrix.get(alignmentMap[low + k], alignmentMap[low + j]);
+ // bottom row
+ rawbits[rowOffset + 2 * rowSize + columnOffset + k] =
+ matrix.get(alignmentMap[low + j], alignmentMap[high - k]);
+ // right column
+ rawbits[rowOffset + 4 * rowSize + columnOffset + k] =
+ matrix.get(alignmentMap[high - k], alignmentMap[high - j]);
+ // top row
+ rawbits[rowOffset + 6 * rowSize + columnOffset + k] =
+ matrix.get(alignmentMap[high - j], alignmentMap[low + k]);
+ }
+ }
+ rowOffset += rowSize * 8;
+ }
+ return rawbits;
+ }
+
+ private enum Table {
+ UPPER,
+ LOWER,
+ MIXED,
+ DIGIT,
+ PUNCT,
+ BINARY
+ }
+}
diff --git a/src/com/google/zxing/aztec/detector/Detector.java b/src/com/google/zxing/aztec/detector/Detector.java
new file mode 100644
index 0000000..1843d5e
--- /dev/null
+++ b/src/com/google/zxing/aztec/detector/Detector.java
@@ -0,0 +1,598 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.aztec.AztecDetectorResult;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.GridSampler;
+import com.google.zxing.common.detector.MathUtils;
+import com.google.zxing.common.detector.WhiteRectangleDetector;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+/**
+ * Encapsulates logic that can detect an Aztec Code in an image, even if the Aztec Code
+ * is rotated or skewed, or partially obscured.
+ *
+ * @author David Olivier
+ * @author Frank Yellin
+ */
+public final class Detector {
+
+ private static final int[] EXPECTED_CORNER_BITS = {
+ 0xee0, // 07340 XXX .XX X.. ...
+ 0x1dc, // 00734 ... XXX .XX X..
+ 0x83b, // 04073 X.. ... XXX .XX
+ 0x707, // 03407 .XX X.. ... XXX
+ };
+ private final BitMatrix image;
+ private boolean compact;
+ private int nbLayers;
+ private int nbDataBlocks;
+ private int nbCenterLayers;
+ private int shift;
+
+ public Detector(BitMatrix image) {
+ this.image = image;
+ }
+
+ private static int getRotation(int[] sides, int length) throws NotFoundException {
+ // In a normal pattern, we expect to See
+ // ** .* D A
+ // * *
+ //
+ // . *
+ // .. .. C B
+ //
+ // Grab the 3 bits from each of the sides the form the locator pattern and concatenate
+ // into a 12-bit integer. Start with the bit at A
+ int cornerBits = 0;
+ for (int side : sides) {
+ // XX......X where X's are orientation marks
+ int t = ((side >> (length - 2)) << 1) + (side & 1);
+ cornerBits = (cornerBits << 3) + t;
+ }
+ // Mov the bottom bit to the top, so that the three bits of the locator pattern at A are
+ // together. cornerBits is now:
+ // 3 orientation bits at A || 3 orientation bits at B || ... || 3 orientation bits at D
+ cornerBits = ((cornerBits & 1) << 11) + (cornerBits >> 1);
+ // The result shift indicates which element of BullsEyeCorners[] goes into the top-left
+ // corner. Since the four rotation values have a Hamming distance of 8, we
+ // can easily tolerate two errors.
+ for (int shift = 0; shift < 4; shift++) {
+ if (Integer.bitCount(cornerBits ^ EXPECTED_CORNER_BITS[shift]) <= 2) {
+ return shift;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /**
+ * Corrects the parameter bits using Reed-Solomon algorithm.
+ *
+ * @param parameterData parameter bits
+ * @param compact true if this is a compact Aztec code
+ * @throws NotFoundException if the array contains too many errors
+ */
+ private static int getCorrectedParameterData(long parameterData, boolean compact) throws NotFoundException {
+ int numCodewords;
+ int numDataCodewords;
+
+ if (compact) {
+ numCodewords = 7;
+ numDataCodewords = 2;
+ } else {
+ numCodewords = 10;
+ numDataCodewords = 4;
+ }
+
+ int numECCodewords = numCodewords - numDataCodewords;
+ int[] parameterWords = new int[numCodewords];
+ for (int i = numCodewords - 1; i >= 0; --i) {
+ parameterWords[i] = (int) parameterData & 0xF;
+ parameterData >>= 4;
+ }
+ try {
+ ReedSolomonDecoder rsDecoder = new ReedSolomonDecoder(GenericGF.AZTEC_PARAM);
+ rsDecoder.decode(parameterWords, numECCodewords);
+ } catch (ReedSolomonException ignored) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ // Toss the error correction. Just return the data as an integer
+ int result = 0;
+ for (int i = 0; i < numDataCodewords; i++) {
+ result = (result << 4) + parameterWords[i];
+ }
+ return result;
+ }
+
+ /**
+ * Expand the square represented by the corner points by pushing out equally in all directions
+ *
+ * @param cornerPoints the corners of the square, which has the bull's eye at its center
+ * @param oldSide the original length of the side of the square in the target bit matrix
+ * @param newSide the new length of the size of the square in the target bit matrix
+ * @return the corners of the expanded square
+ */
+ private static ResultPoint[] expandSquare(ResultPoint[] cornerPoints, float oldSide, float newSide) {
+ float ratio = newSide / (2 * oldSide);
+ float dx = cornerPoints[0].getX() - cornerPoints[2].getX();
+ float dy = cornerPoints[0].getY() - cornerPoints[2].getY();
+ float centerx = (cornerPoints[0].getX() + cornerPoints[2].getX()) / 2.0f;
+ float centery = (cornerPoints[0].getY() + cornerPoints[2].getY()) / 2.0f;
+
+ ResultPoint result0 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy);
+ ResultPoint result2 = new ResultPoint(centerx - ratio * dx, centery - ratio * dy);
+
+ dx = cornerPoints[1].getX() - cornerPoints[3].getX();
+ dy = cornerPoints[1].getY() - cornerPoints[3].getY();
+ centerx = (cornerPoints[1].getX() + cornerPoints[3].getX()) / 2.0f;
+ centery = (cornerPoints[1].getY() + cornerPoints[3].getY()) / 2.0f;
+ ResultPoint result1 = new ResultPoint(centerx + ratio * dx, centery + ratio * dy);
+ ResultPoint result3 = new ResultPoint(centerx - ratio * dx, centery - ratio * dy);
+
+ return new ResultPoint[]{result0, result1, result2, result3};
+ }
+
+ private static float distance(Point a, Point b) {
+ return MathUtils.distance(a.getX(), a.getY(), b.getX(), b.getY());
+ }
+
+ private static float distance(ResultPoint a, ResultPoint b) {
+ return MathUtils.distance(a.getX(), a.getY(), b.getX(), b.getY());
+ }
+
+ public AztecDetectorResult detect() throws NotFoundException {
+ return detect(false);
+ }
+
+ /**
+ * Detects an Aztec Code in an image.
+ *
+ * @param isMirror if true, image is a mirror-image of original
+ * @return {@link AztecDetectorResult} encapsulating results of detecting an Aztec Code
+ * @throws NotFoundException if no Aztec Code can be found
+ */
+ public AztecDetectorResult detect(boolean isMirror) throws NotFoundException {
+
+ // 1. Get the center of the aztec matrix
+ Point pCenter = getMatrixCenter();
+
+ // 2. Get the center points of the four diagonal points just outside the bull's eye
+ // [topRight, bottomRight, bottomLeft, topLeft]
+ ResultPoint[] bullsEyeCorners = getBullsEyeCorners(pCenter);
+
+ if (isMirror) {
+ ResultPoint temp = bullsEyeCorners[0];
+ bullsEyeCorners[0] = bullsEyeCorners[2];
+ bullsEyeCorners[2] = temp;
+ }
+
+ // 3. Get the size of the matrix and other parameters from the bull's eye
+ extractParameters(bullsEyeCorners);
+
+ // 4. Sample the grid
+ BitMatrix bits = sampleGrid(image,
+ bullsEyeCorners[shift % 4],
+ bullsEyeCorners[(shift + 1) % 4],
+ bullsEyeCorners[(shift + 2) % 4],
+ bullsEyeCorners[(shift + 3) % 4]);
+
+ // 5. Get the corners of the matrix.
+ ResultPoint[] corners = getMatrixCornerPoints(bullsEyeCorners);
+
+ return new AztecDetectorResult(bits, corners, compact, nbDataBlocks, nbLayers);
+ }
+
+ /**
+ * Extracts the number of data layers and data blocks from the layer around the bull's eye.
+ *
+ * @param bullsEyeCorners the array of bull's eye corners
+ * @throws NotFoundException in case of too many errors or invalid parameters
+ */
+ private void extractParameters(ResultPoint[] bullsEyeCorners) throws NotFoundException {
+ if (!isValid(bullsEyeCorners[0]) || !isValid(bullsEyeCorners[1]) ||
+ !isValid(bullsEyeCorners[2]) || !isValid(bullsEyeCorners[3])) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int length = 2 * nbCenterLayers;
+ // Get the bits around the bull's eye
+ int[] sides = {
+ sampleLine(bullsEyeCorners[0], bullsEyeCorners[1], length), // Right side
+ sampleLine(bullsEyeCorners[1], bullsEyeCorners[2], length), // Bottom
+ sampleLine(bullsEyeCorners[2], bullsEyeCorners[3], length), // Left side
+ sampleLine(bullsEyeCorners[3], bullsEyeCorners[0], length) // Top
+ };
+
+ // bullsEyeCorners[shift] is the corner of the bulls'eye that has three
+ // orientation marks.
+ // sides[shift] is the row/column that goes from the corner with three
+ // orientation marks to the corner with two.
+ shift = getRotation(sides, length);
+
+ // Flatten the parameter bits into a single 28- or 40-bit long
+ long parameterData = 0;
+ for (int i = 0; i < 4; i++) {
+ int side = sides[(shift + i) % 4];
+ if (compact) {
+ // Each side of the form ..XXXXXXX. where Xs are parameter data
+ parameterData <<= 7;
+ parameterData += (side >> 1) & 0x7F;
+ } else {
+ // Each side of the form ..XXXXX.XXXXX. where Xs are parameter data
+ parameterData <<= 10;
+ parameterData += ((side >> 2) & (0x1f << 5)) + ((side >> 1) & 0x1F);
+ }
+ }
+
+ // Corrects parameter data using RS. Returns just the data portion
+ // without the error correction.
+ int correctedData = getCorrectedParameterData(parameterData, compact);
+
+ if (compact) {
+ // 8 bits: 2 bits layers and 6 bits data blocks
+ nbLayers = (correctedData >> 6) + 1;
+ nbDataBlocks = (correctedData & 0x3F) + 1;
+ } else {
+ // 16 bits: 5 bits layers and 11 bits data blocks
+ nbLayers = (correctedData >> 11) + 1;
+ nbDataBlocks = (correctedData & 0x7FF) + 1;
+ }
+ }
+
+ /**
+ * Finds the corners of a bull-eye centered on the passed point.
+ * This returns the centers of the diagonal points just outside the bull's eye
+ * Returns [topRight, bottomRight, bottomLeft, topLeft]
+ *
+ * @param pCenter Center point
+ * @return The corners of the bull-eye
+ * @throws NotFoundException If no valid bull-eye can be found
+ */
+ private ResultPoint[] getBullsEyeCorners(Point pCenter) throws NotFoundException {
+
+ Point pina = pCenter;
+ Point pinb = pCenter;
+ Point pinc = pCenter;
+ Point pind = pCenter;
+
+ boolean color = true;
+
+ for (nbCenterLayers = 1; nbCenterLayers < 9; nbCenterLayers++) {
+ Point pouta = getFirstDifferent(pina, color, 1, -1);
+ Point poutb = getFirstDifferent(pinb, color, 1, 1);
+ Point poutc = getFirstDifferent(pinc, color, -1, 1);
+ Point poutd = getFirstDifferent(pind, color, -1, -1);
+
+ //d a
+ //
+ //c b
+
+ if (nbCenterLayers > 2) {
+ float q = distance(poutd, pouta) * nbCenterLayers / (distance(pind, pina) * (nbCenterLayers + 2));
+ if (q < 0.75 || q > 1.25 || !isWhiteOrBlackRectangle(pouta, poutb, poutc, poutd)) {
+ break;
+ }
+ }
+
+ pina = pouta;
+ pinb = poutb;
+ pinc = poutc;
+ pind = poutd;
+
+ color = !color;
+ }
+
+ if (nbCenterLayers != 5 && nbCenterLayers != 7) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ compact = nbCenterLayers == 5;
+
+ // Expand the square by .5 pixel in each direction so that we're on the border
+ // between the white square and the black square
+ ResultPoint pinax = new ResultPoint(pina.getX() + 0.5f, pina.getY() - 0.5f);
+ ResultPoint pinbx = new ResultPoint(pinb.getX() + 0.5f, pinb.getY() + 0.5f);
+ ResultPoint pincx = new ResultPoint(pinc.getX() - 0.5f, pinc.getY() + 0.5f);
+ ResultPoint pindx = new ResultPoint(pind.getX() - 0.5f, pind.getY() - 0.5f);
+
+ // Expand the square so that its corners are the centers of the points
+ // just outside the bull's eye.
+ return expandSquare(new ResultPoint[]{pinax, pinbx, pincx, pindx},
+ 2 * nbCenterLayers - 3,
+ 2 * nbCenterLayers);
+ }
+
+ /**
+ * Finds a candidate center point of an Aztec code from an image
+ *
+ * @return the center point
+ */
+ private Point getMatrixCenter() {
+
+ ResultPoint pointA;
+ ResultPoint pointB;
+ ResultPoint pointC;
+ ResultPoint pointD;
+
+ //Get a white rectangle that can be the border of the matrix in center bull's eye or
+ try {
+
+ ResultPoint[] cornerPoints = new WhiteRectangleDetector(image).detect();
+ pointA = cornerPoints[0];
+ pointB = cornerPoints[1];
+ pointC = cornerPoints[2];
+ pointD = cornerPoints[3];
+
+ } catch (NotFoundException e) {
+
+ // This exception can be in case the initial rectangle is white
+ // In that case, surely in the bull's eye, we try to expand the rectangle.
+ int cx = image.getWidth() / 2;
+ int cy = image.getHeight() / 2;
+ pointA = getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint();
+ pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
+ pointC = getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint();
+ pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
+
+ }
+
+ //Compute the center of the rectangle
+ int cx = MathUtils.round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX()) / 4.0f);
+ int cy = MathUtils.round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY()) / 4.0f);
+
+ // Redetermine the white rectangle starting from previously computed center.
+ // This will ensure that we end up with a white rectangle in center bull's eye
+ // in order to compute a more accurate center.
+ try {
+ ResultPoint[] cornerPoints = new WhiteRectangleDetector(image, 15, cx, cy).detect();
+ pointA = cornerPoints[0];
+ pointB = cornerPoints[1];
+ pointC = cornerPoints[2];
+ pointD = cornerPoints[3];
+ } catch (NotFoundException e) {
+ // This exception can be in case the initial rectangle is white
+ // In that case we try to expand the rectangle.
+ pointA = getFirstDifferent(new Point(cx + 7, cy - 7), false, 1, -1).toResultPoint();
+ pointB = getFirstDifferent(new Point(cx + 7, cy + 7), false, 1, 1).toResultPoint();
+ pointC = getFirstDifferent(new Point(cx - 7, cy + 7), false, -1, 1).toResultPoint();
+ pointD = getFirstDifferent(new Point(cx - 7, cy - 7), false, -1, -1).toResultPoint();
+ }
+
+ // Recompute the center of the rectangle
+ cx = MathUtils.round((pointA.getX() + pointD.getX() + pointB.getX() + pointC.getX()) / 4.0f);
+ cy = MathUtils.round((pointA.getY() + pointD.getY() + pointB.getY() + pointC.getY()) / 4.0f);
+
+ return new Point(cx, cy);
+ }
+
+ /**
+ * Gets the Aztec code corners from the bull's eye corners and the parameters.
+ *
+ * @param bullsEyeCorners the array of bull's eye corners
+ * @return the array of aztec code corners
+ */
+ private ResultPoint[] getMatrixCornerPoints(ResultPoint[] bullsEyeCorners) {
+ return expandSquare(bullsEyeCorners, 2 * nbCenterLayers, getDimension());
+ }
+
+ /**
+ * Creates a BitMatrix by sampling the provided image.
+ * topLeft, topRight, bottomRight, and bottomLeft are the centers of the squares on the
+ * diagonal just outside the bull's eye.
+ */
+ private BitMatrix sampleGrid(BitMatrix image,
+ ResultPoint topLeft,
+ ResultPoint topRight,
+ ResultPoint bottomRight,
+ ResultPoint bottomLeft) throws NotFoundException {
+
+ GridSampler sampler = GridSampler.getInstance();
+ int dimension = getDimension();
+
+ float low = dimension / 2.0f - nbCenterLayers;
+ float high = dimension / 2.0f + nbCenterLayers;
+
+ return sampler.sampleGrid(image,
+ dimension,
+ dimension,
+ low, low, // topleft
+ high, low, // topright
+ high, high, // bottomright
+ low, high, // bottomleft
+ topLeft.getX(), topLeft.getY(),
+ topRight.getX(), topRight.getY(),
+ bottomRight.getX(), bottomRight.getY(),
+ bottomLeft.getX(), bottomLeft.getY());
+ }
+
+ /**
+ * Samples a line.
+ *
+ * @param p1 start point (inclusive)
+ * @param p2 end point (exclusive)
+ * @param size number of bits
+ * @return the array of bits as an int (first bit is high-order bit of result)
+ */
+ private int sampleLine(ResultPoint p1, ResultPoint p2, int size) {
+ int result = 0;
+
+ float d = distance(p1, p2);
+ float moduleSize = d / size;
+ float px = p1.getX();
+ float py = p1.getY();
+ float dx = moduleSize * (p2.getX() - p1.getX()) / d;
+ float dy = moduleSize * (p2.getY() - p1.getY()) / d;
+ for (int i = 0; i < size; i++) {
+ if (image.get(MathUtils.round(px + i * dx), MathUtils.round(py + i * dy))) {
+ result |= 1 << (size - i - 1);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return true if the border of the rectangle passed in parameter is compound of white points only
+ * or black points only
+ */
+ private boolean isWhiteOrBlackRectangle(Point p1,
+ Point p2,
+ Point p3,
+ Point p4) {
+
+ int corr = 3;
+
+ p1 = new Point(p1.getX() - corr, p1.getY() + corr);
+ p2 = new Point(p2.getX() - corr, p2.getY() - corr);
+ p3 = new Point(p3.getX() + corr, p3.getY() - corr);
+ p4 = new Point(p4.getX() + corr, p4.getY() + corr);
+
+ int cInit = getColor(p4, p1);
+
+ if (cInit == 0) {
+ return false;
+ }
+
+ int c = getColor(p1, p2);
+
+ if (c != cInit) {
+ return false;
+ }
+
+ c = getColor(p2, p3);
+
+ if (c != cInit) {
+ return false;
+ }
+
+ c = getColor(p3, p4);
+
+ return c == cInit;
+
+ }
+
+ /**
+ * Gets the color of a segment
+ *
+ * @return 1 if segment more than 90% black, -1 if segment is more than 90% white, 0 else
+ */
+ private int getColor(Point p1, Point p2) {
+ float d = distance(p1, p2);
+ float dx = (p2.getX() - p1.getX()) / d;
+ float dy = (p2.getY() - p1.getY()) / d;
+ int error = 0;
+
+ float px = p1.getX();
+ float py = p1.getY();
+
+ boolean colorModel = image.get(p1.getX(), p1.getY());
+
+ for (int i = 0; i < d; i++) {
+ px += dx;
+ py += dy;
+ if (image.get(MathUtils.round(px), MathUtils.round(py)) != colorModel) {
+ error++;
+ }
+ }
+
+ float errRatio = error / d;
+
+ if (errRatio > 0.1f && errRatio < 0.9f) {
+ return 0;
+ }
+
+ return (errRatio <= 0.1f) == colorModel ? 1 : -1;
+ }
+
+ /**
+ * Gets the coordinate of the first point with a different color in the given direction
+ */
+ private Point getFirstDifferent(Point init, boolean color, int dx, int dy) {
+ int x = init.getX() + dx;
+ int y = init.getY() + dy;
+
+ while (isValid(x, y) && image.get(x, y) == color) {
+ x += dx;
+ y += dy;
+ }
+
+ x -= dx;
+ y -= dy;
+
+ while (isValid(x, y) && image.get(x, y) == color) {
+ x += dx;
+ }
+ x -= dx;
+
+ while (isValid(x, y) && image.get(x, y) == color) {
+ y += dy;
+ }
+ y -= dy;
+
+ return new Point(x, y);
+ }
+
+ private boolean isValid(int x, int y) {
+ return x >= 0 && x < image.getWidth() && y > 0 && y < image.getHeight();
+ }
+
+ private boolean isValid(ResultPoint point) {
+ int x = MathUtils.round(point.getX());
+ int y = MathUtils.round(point.getY());
+ return isValid(x, y);
+ }
+
+ private int getDimension() {
+ if (compact) {
+ return 4 * nbLayers + 11;
+ }
+ if (nbLayers <= 4) {
+ return 4 * nbLayers + 15;
+ }
+ return 4 * nbLayers + 2 * ((nbLayers - 4) / 8 + 1) + 15;
+ }
+
+ static final class Point {
+ private final int x;
+ private final int y;
+
+ Point(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ ResultPoint toResultPoint() {
+ return new ResultPoint(getX(), getY());
+ }
+
+ int getX() {
+ return x;
+ }
+
+ int getY() {
+ return y;
+ }
+
+ @Override
+ public String toString() {
+ return "<" + x + ' ' + y + '>';
+ }
+ }
+}
diff --git a/src/com/google/zxing/aztec/encoder/AztecCode.java b/src/com/google/zxing/aztec/encoder/AztecCode.java
new file mode 100644
index 0000000..4812d8a
--- /dev/null
+++ b/src/com/google/zxing/aztec/encoder/AztecCode.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.encoder;
+
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * Aztec 2D code representation
+ *
+ * @author Rustam Abdullaev
+ */
+public final class AztecCode {
+
+ private boolean compact;
+ private int size;
+ private int layers;
+ private int codeWords;
+ private BitMatrix matrix;
+
+ /**
+ * @return {@code true} if compact instead of full mode
+ */
+ public boolean isCompact() {
+ return compact;
+ }
+
+ public void setCompact(boolean compact) {
+ this.compact = compact;
+ }
+
+ /**
+ * @return size in pixels (width and height)
+ */
+ public int getSize() {
+ return size;
+ }
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ /**
+ * @return number of levels
+ */
+ public int getLayers() {
+ return layers;
+ }
+
+ public void setLayers(int layers) {
+ this.layers = layers;
+ }
+
+ /**
+ * @return number of data codewords
+ */
+ public int getCodeWords() {
+ return codeWords;
+ }
+
+ public void setCodeWords(int codeWords) {
+ this.codeWords = codeWords;
+ }
+
+ /**
+ * @return the symbol image
+ */
+ public BitMatrix getMatrix() {
+ return matrix;
+ }
+
+ public void setMatrix(BitMatrix matrix) {
+ this.matrix = matrix;
+ }
+
+}
diff --git a/src/com/google/zxing/aztec/encoder/BinaryShiftToken.java b/src/com/google/zxing/aztec/encoder/BinaryShiftToken.java
new file mode 100644
index 0000000..3af268d
--- /dev/null
+++ b/src/com/google/zxing/aztec/encoder/BinaryShiftToken.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+
+final class BinaryShiftToken extends Token {
+
+ private final short binaryShiftStart;
+ private final short binaryShiftByteCount;
+
+ BinaryShiftToken(Token previous,
+ int binaryShiftStart,
+ int binaryShiftByteCount) {
+ super(previous);
+ this.binaryShiftStart = (short) binaryShiftStart;
+ this.binaryShiftByteCount = (short) binaryShiftByteCount;
+ }
+
+ @Override
+ public void appendTo(BitArray bitArray, byte[] text) {
+ for (int i = 0; i < binaryShiftByteCount; i++) {
+ if (i == 0 || (i == 31 && binaryShiftByteCount <= 62)) {
+ // We need a header before the first character, and before
+ // character 31 when the total byte code is <= 62
+ bitArray.appendBits(31, 5); // BINARY_SHIFT
+ if (binaryShiftByteCount > 62) {
+ bitArray.appendBits(binaryShiftByteCount - 31, 16);
+ } else if (i == 0) {
+ // 1 <= binaryShiftByteCode <= 62
+ bitArray.appendBits(Math.min(binaryShiftByteCount, 31), 5);
+ } else {
+ // 32 <= binaryShiftCount <= 62 and i == 31
+ bitArray.appendBits(binaryShiftByteCount - 31, 5);
+ }
+ }
+ bitArray.appendBits(text[binaryShiftStart + i], 8);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "<" + binaryShiftStart + "::" + (binaryShiftStart + binaryShiftByteCount - 1) + '>';
+ }
+
+}
diff --git a/src/com/google/zxing/aztec/encoder/Encoder.java b/src/com/google/zxing/aztec/encoder/Encoder.java
new file mode 100644
index 0000000..e88a5eb
--- /dev/null
+++ b/src/com/google/zxing/aztec/encoder/Encoder.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonEncoder;
+
+/**
+ * Generates Aztec 2D barcodes.
+ *
+ * @author Rustam Abdullaev
+ */
+public final class Encoder {
+
+ public static final int DEFAULT_EC_PERCENT = 33; // default minimal percentage of error check words
+ public static final int DEFAULT_AZTEC_LAYERS = 0;
+ private static final int MAX_NB_BITS = 32;
+ private static final int MAX_NB_BITS_COMPACT = 4;
+
+ private static final int[] WORD_SIZE = {
+ 4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
+ 12, 12, 12, 12, 12, 12, 12, 12, 12, 12
+ };
+
+ private Encoder() {
+ }
+
+ /**
+ * Encodes the given binary content as an Aztec symbol
+ *
+ * @param data input data string
+ * @return Aztec symbol matrix with metadata
+ */
+ public static AztecCode encode(byte[] data) {
+ return encode(data, DEFAULT_EC_PERCENT, DEFAULT_AZTEC_LAYERS);
+ }
+
+ /**
+ * Encodes the given binary content as an Aztec symbol
+ *
+ * @param data input data string
+ * @param minECCPercent minimal percentage of error check words (According to ISO/IEC 24778:2008,
+ * a minimum of 23% + 3 words is recommended)
+ * @param userSpecifiedLayers if non-zero, a user-specified value for the number of layers
+ * @return Aztec symbol matrix with metadata
+ */
+ public static AztecCode encode(byte[] data, int minECCPercent, int userSpecifiedLayers) {
+ // High-level encode
+ BitArray bits = new HighLevelEncoder(data).encode();
+
+ // stuff bits and choose symbol size
+ int eccBits = bits.getSize() * minECCPercent / 100 + 11;
+ int totalSizeBits = bits.getSize() + eccBits;
+ boolean compact;
+ int layers;
+ int totalBitsInLayer;
+ int wordSize;
+ BitArray stuffedBits;
+ if (userSpecifiedLayers != DEFAULT_AZTEC_LAYERS) {
+ compact = userSpecifiedLayers < 0;
+ layers = Math.abs(userSpecifiedLayers);
+ if (layers > (compact ? MAX_NB_BITS_COMPACT : MAX_NB_BITS)) {
+ throw new IllegalArgumentException(
+ String.format("Illegal value %s for layers", userSpecifiedLayers));
+ }
+ totalBitsInLayer = totalBitsInLayer(layers, compact);
+ wordSize = WORD_SIZE[layers];
+ int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
+ stuffedBits = stuffBits(bits, wordSize);
+ if (stuffedBits.getSize() + eccBits > usableBitsInLayers) {
+ throw new IllegalArgumentException("Data to large for user specified layer");
+ }
+ if (compact && stuffedBits.getSize() > wordSize * 64) {
+ // Compact format only allows 64 data words, though C4 can hold more words than that
+ throw new IllegalArgumentException("Data to large for user specified layer");
+ }
+ } else {
+ wordSize = 0;
+ stuffedBits = null;
+ // We look at the possible table sizes in the order Compact1, Compact2, Compact3,
+ // Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1)
+ // is the same size, but has more data.
+ for (int i = 0; ; i++) {
+ if (i > MAX_NB_BITS) {
+ throw new IllegalArgumentException("Data too large for an Aztec code");
+ }
+ compact = i <= 3;
+ layers = compact ? i + 1 : i;
+ totalBitsInLayer = totalBitsInLayer(layers, compact);
+ if (totalSizeBits > totalBitsInLayer) {
+ continue;
+ }
+ // [Re]stuff the bits if this is the first opportunity, or if the
+ // wordSize has changed
+ if (wordSize != WORD_SIZE[layers]) {
+ wordSize = WORD_SIZE[layers];
+ stuffedBits = stuffBits(bits, wordSize);
+ }
+ int usableBitsInLayers = totalBitsInLayer - (totalBitsInLayer % wordSize);
+ if (compact && stuffedBits.getSize() > wordSize * 64) {
+ // Compact format only allows 64 data words, though C4 can hold more words than that
+ continue;
+ }
+ if (stuffedBits.getSize() + eccBits <= usableBitsInLayers) {
+ break;
+ }
+ }
+ }
+ BitArray messageBits = generateCheckWords(stuffedBits, totalBitsInLayer, wordSize);
+
+ // generate mode message
+ int messageSizeInWords = stuffedBits.getSize() / wordSize;
+ BitArray modeMessage = generateModeMessage(compact, layers, messageSizeInWords);
+
+ // allocate symbol
+ int baseMatrixSize = compact ? 11 + layers * 4 : 14 + layers * 4; // not including alignment lines
+ int[] alignmentMap = new int[baseMatrixSize];
+ int matrixSize;
+ if (compact) {
+ // no alignment marks in compact mode, alignmentMap is a no-op
+ matrixSize = baseMatrixSize;
+ for (int i = 0; i < alignmentMap.length; i++) {
+ alignmentMap[i] = i;
+ }
+ } else {
+ matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
+ int origCenter = baseMatrixSize / 2;
+ int center = matrixSize / 2;
+ for (int i = 0; i < origCenter; i++) {
+ int newOffset = i + i / 15;
+ alignmentMap[origCenter - i - 1] = center - newOffset - 1;
+ alignmentMap[origCenter + i] = center + newOffset + 1;
+ }
+ }
+ BitMatrix matrix = new BitMatrix(matrixSize);
+
+ // draw data bits
+ for (int i = 0, rowOffset = 0; i < layers; i++) {
+ int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
+ for (int j = 0; j < rowSize; j++) {
+ int columnOffset = j * 2;
+ for (int k = 0; k < 2; k++) {
+ if (messageBits.get(rowOffset + columnOffset + k)) {
+ matrix.set(alignmentMap[i * 2 + k], alignmentMap[i * 2 + j]);
+ }
+ if (messageBits.get(rowOffset + rowSize * 2 + columnOffset + k)) {
+ matrix.set(alignmentMap[i * 2 + j], alignmentMap[baseMatrixSize - 1 - i * 2 - k]);
+ }
+ if (messageBits.get(rowOffset + rowSize * 4 + columnOffset + k)) {
+ matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - k], alignmentMap[baseMatrixSize - 1 - i * 2 - j]);
+ }
+ if (messageBits.get(rowOffset + rowSize * 6 + columnOffset + k)) {
+ matrix.set(alignmentMap[baseMatrixSize - 1 - i * 2 - j], alignmentMap[i * 2 + k]);
+ }
+ }
+ }
+ rowOffset += rowSize * 8;
+ }
+
+ // draw mode message
+ drawModeMessage(matrix, compact, matrixSize, modeMessage);
+
+ // draw alignment marks
+ if (compact) {
+ drawBullsEye(matrix, matrixSize / 2, 5);
+ } else {
+ drawBullsEye(matrix, matrixSize / 2, 7);
+ for (int i = 0, j = 0; i < baseMatrixSize / 2 - 1; i += 15, j += 16) {
+ for (int k = (matrixSize / 2) & 1; k < matrixSize; k += 2) {
+ matrix.set(matrixSize / 2 - j, k);
+ matrix.set(matrixSize / 2 + j, k);
+ matrix.set(k, matrixSize / 2 - j);
+ matrix.set(k, matrixSize / 2 + j);
+ }
+ }
+ }
+
+ AztecCode aztec = new AztecCode();
+ aztec.setCompact(compact);
+ aztec.setSize(matrixSize);
+ aztec.setLayers(layers);
+ aztec.setCodeWords(messageSizeInWords);
+ aztec.setMatrix(matrix);
+ return aztec;
+ }
+
+ private static void drawBullsEye(BitMatrix matrix, int center, int size) {
+ for (int i = 0; i < size; i += 2) {
+ for (int j = center - i; j <= center + i; j++) {
+ matrix.set(j, center - i);
+ matrix.set(j, center + i);
+ matrix.set(center - i, j);
+ matrix.set(center + i, j);
+ }
+ }
+ matrix.set(center - size, center - size);
+ matrix.set(center - size + 1, center - size);
+ matrix.set(center - size, center - size + 1);
+ matrix.set(center + size, center - size);
+ matrix.set(center + size, center - size + 1);
+ matrix.set(center + size, center + size - 1);
+ }
+
+ static BitArray generateModeMessage(boolean compact, int layers, int messageSizeInWords) {
+ BitArray modeMessage = new BitArray();
+ if (compact) {
+ modeMessage.appendBits(layers - 1, 2);
+ modeMessage.appendBits(messageSizeInWords - 1, 6);
+ modeMessage = generateCheckWords(modeMessage, 28, 4);
+ } else {
+ modeMessage.appendBits(layers - 1, 5);
+ modeMessage.appendBits(messageSizeInWords - 1, 11);
+ modeMessage = generateCheckWords(modeMessage, 40, 4);
+ }
+ return modeMessage;
+ }
+
+ private static void drawModeMessage(BitMatrix matrix, boolean compact, int matrixSize, BitArray modeMessage) {
+ int center = matrixSize / 2;
+ if (compact) {
+ for (int i = 0; i < 7; i++) {
+ int offset = center - 3 + i;
+ if (modeMessage.get(i)) {
+ matrix.set(offset, center - 5);
+ }
+ if (modeMessage.get(i + 7)) {
+ matrix.set(center + 5, offset);
+ }
+ if (modeMessage.get(20 - i)) {
+ matrix.set(offset, center + 5);
+ }
+ if (modeMessage.get(27 - i)) {
+ matrix.set(center - 5, offset);
+ }
+ }
+ } else {
+ for (int i = 0; i < 10; i++) {
+ int offset = center - 5 + i + i / 5;
+ if (modeMessage.get(i)) {
+ matrix.set(offset, center - 7);
+ }
+ if (modeMessage.get(i + 10)) {
+ matrix.set(center + 7, offset);
+ }
+ if (modeMessage.get(29 - i)) {
+ matrix.set(offset, center + 7);
+ }
+ if (modeMessage.get(39 - i)) {
+ matrix.set(center - 7, offset);
+ }
+ }
+ }
+ }
+
+ private static BitArray generateCheckWords(BitArray bitArray, int totalBits, int wordSize) {
+ // bitArray is guaranteed to be a multiple of the wordSize, so no padding needed
+ int messageSizeInWords = bitArray.getSize() / wordSize;
+ ReedSolomonEncoder rs = new ReedSolomonEncoder(getGF(wordSize));
+ int totalWords = totalBits / wordSize;
+ int[] messageWords = bitsToWords(bitArray, wordSize, totalWords);
+ rs.encode(messageWords, totalWords - messageSizeInWords);
+ int startPad = totalBits % wordSize;
+ BitArray messageBits = new BitArray();
+ messageBits.appendBits(0, startPad);
+ for (int messageWord : messageWords) {
+ messageBits.appendBits(messageWord, wordSize);
+ }
+ return messageBits;
+ }
+
+ private static int[] bitsToWords(BitArray stuffedBits, int wordSize, int totalWords) {
+ int[] message = new int[totalWords];
+ int i;
+ int n;
+ for (i = 0, n = stuffedBits.getSize() / wordSize; i < n; i++) {
+ int value = 0;
+ for (int j = 0; j < wordSize; j++) {
+ value |= stuffedBits.get(i * wordSize + j) ? (1 << wordSize - j - 1) : 0;
+ }
+ message[i] = value;
+ }
+ return message;
+ }
+
+ private static GenericGF getGF(int wordSize) {
+ switch (wordSize) {
+ case 4:
+ return GenericGF.AZTEC_PARAM;
+ case 6:
+ return GenericGF.AZTEC_DATA_6;
+ case 8:
+ return GenericGF.AZTEC_DATA_8;
+ case 10:
+ return GenericGF.AZTEC_DATA_10;
+ case 12:
+ return GenericGF.AZTEC_DATA_12;
+ default:
+ return null;
+ }
+ }
+
+ static BitArray stuffBits(BitArray bits, int wordSize) {
+ BitArray out = new BitArray();
+
+ int n = bits.getSize();
+ int mask = (1 << wordSize) - 2;
+ for (int i = 0; i < n; i += wordSize) {
+ int word = 0;
+ for (int j = 0; j < wordSize; j++) {
+ if (i + j >= n || bits.get(i + j)) {
+ word |= 1 << (wordSize - 1 - j);
+ }
+ }
+ if ((word & mask) == mask) {
+ out.appendBits(word & mask, wordSize);
+ i--;
+ } else if ((word & mask) == 0) {
+ out.appendBits(word | 1, wordSize);
+ i--;
+ } else {
+ out.appendBits(word, wordSize);
+ }
+ }
+ return out;
+ }
+
+ private static int totalBitsInLayer(int layers, boolean compact) {
+ return ((compact ? 88 : 112) + 16 * layers) * layers;
+ }
+}
diff --git a/src/com/google/zxing/aztec/encoder/HighLevelEncoder.java b/src/com/google/zxing/aztec/encoder/HighLevelEncoder.java
new file mode 100644
index 0000000..5b618fd
--- /dev/null
+++ b/src/com/google/zxing/aztec/encoder/HighLevelEncoder.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+
+import java.util.*;
+
+/**
+ * This produces nearly optimal encodings of text into the first-level of
+ * encoding used by Aztec code.
+ *
+ * It uses a dynamic algorithm. For each prefix of the string, it determines
+ * a set of encodings that could lead to this prefix. We repeatedly add a
+ * character and generate a new set of optimal encodings until we have read
+ * through the entire input.
+ *
+ * @author Frank Yellin
+ * @author Rustam Abdullaev
+ */
+public final class HighLevelEncoder {
+
+ static final String[] MODE_NAMES = {"UPPER", "LOWER", "DIGIT", "MIXED", "PUNCT"};
+
+ static final int MODE_UPPER = 0; // 5 bits
+ static final int MODE_LOWER = 1; // 5 bits
+ static final int MODE_DIGIT = 2; // 4 bits
+ static final int MODE_MIXED = 3; // 5 bits
+ static final int MODE_PUNCT = 4; // 5 bits
+
+ // The Latch Table shows, for each pair of Modes, the optimal method for
+ // getting from one mode to another. In the worst possible case, this can
+ // be up to 14 bits. In the best possible case, we are already there!
+ // The high half-word of each entry gives the number of bits.
+ // The low half-word of each entry are the actual bits necessary to change
+ static final int[][] LATCH_TABLE = {
+ {
+ 0,
+ (5 << 16) + 28, // UPPER -> LOWER
+ (5 << 16) + 30, // UPPER -> DIGIT
+ (5 << 16) + 29, // UPPER -> MIXED
+ (10 << 16) + (29 << 5) + 30, // UPPER -> MIXED -> PUNCT
+ },
+ {
+ (9 << 16) + (30 << 4) + 14, // LOWER -> DIGIT -> UPPER
+ 0,
+ (5 << 16) + 30, // LOWER -> DIGIT
+ (5 << 16) + 29, // LOWER -> MIXED
+ (10 << 16) + (29 << 5) + 30, // LOWER -> MIXED -> PUNCT
+ },
+ {
+ (4 << 16) + 14, // DIGIT -> UPPER
+ (9 << 16) + (14 << 5) + 28, // DIGIT -> UPPER -> LOWER
+ 0,
+ (9 << 16) + (14 << 5) + 29, // DIGIT -> UPPER -> MIXED
+ (14 << 16) + (14 << 10) + (29 << 5) + 30,
+ // DIGIT -> UPPER -> MIXED -> PUNCT
+ },
+ {
+ (5 << 16) + 29, // MIXED -> UPPER
+ (5 << 16) + 28, // MIXED -> LOWER
+ (10 << 16) + (29 << 5) + 30, // MIXED -> UPPER -> DIGIT
+ 0,
+ (5 << 16) + 30, // MIXED -> PUNCT
+ },
+ {
+ (5 << 16) + 31, // PUNCT -> UPPER
+ (10 << 16) + (31 << 5) + 28, // PUNCT -> UPPER -> LOWER
+ (10 << 16) + (31 << 5) + 30, // PUNCT -> UPPER -> DIGIT
+ (10 << 16) + (31 << 5) + 29, // PUNCT -> UPPER -> MIXED
+ 0,
+ },
+ };
+ // A map showing the available shift codes. (The shifts to BINARY are not
+ // shown
+ static final int[][] SHIFT_TABLE = new int[6][6]; // mode shift codes, per table
+ static {
+ for (int[] table : SHIFT_TABLE) {
+ Arrays.fill(table, -1);
+ }
+ SHIFT_TABLE[MODE_UPPER][MODE_PUNCT] = 0;
+
+ SHIFT_TABLE[MODE_LOWER][MODE_PUNCT] = 0;
+ SHIFT_TABLE[MODE_LOWER][MODE_UPPER] = 28;
+
+ SHIFT_TABLE[MODE_MIXED][MODE_PUNCT] = 0;
+
+ SHIFT_TABLE[MODE_DIGIT][MODE_PUNCT] = 0;
+ SHIFT_TABLE[MODE_DIGIT][MODE_UPPER] = 15;
+ }
+ // A reverse mapping from [mode][char] to the encoding for that character
+ // in that mode. An entry of 0 indicates no mapping exists.
+ private static final int[][] CHAR_MAP = new int[5][256];
+ static {
+ CHAR_MAP[MODE_UPPER][' '] = 1;
+ for (int c = 'A'; c <= 'Z'; c++) {
+ CHAR_MAP[MODE_UPPER][c] = c - 'A' + 2;
+ }
+ CHAR_MAP[MODE_LOWER][' '] = 1;
+ for (int c = 'a'; c <= 'z'; c++) {
+ CHAR_MAP[MODE_LOWER][c] = c - 'a' + 2;
+ }
+ CHAR_MAP[MODE_DIGIT][' '] = 1;
+ for (int c = '0'; c <= '9'; c++) {
+ CHAR_MAP[MODE_DIGIT][c] = c - '0' + 2;
+ }
+ CHAR_MAP[MODE_DIGIT][','] = 12;
+ CHAR_MAP[MODE_DIGIT]['.'] = 13;
+ int[] mixedTable = {
+ '\0', ' ', '\1', '\2', '\3', '\4', '\5', '\6', '\7', '\b', '\t', '\n',
+ '\13', '\f', '\r', '\33', '\34', '\35', '\36', '\37', '@', '\\', '^',
+ '_', '`', '|', '~', '\177'
+ };
+ for (int i = 0; i < mixedTable.length; i++) {
+ CHAR_MAP[MODE_MIXED][mixedTable[i]] = i;
+ }
+ int[] punctTable = {
+ '\0', '\r', '\0', '\0', '\0', '\0', '!', '\'', '#', '$', '%', '&', '\'',
+ '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?',
+ '[', ']', '{', '}'
+ };
+ for (int i = 0; i < punctTable.length; i++) {
+ if (punctTable[i] > 0) {
+ CHAR_MAP[MODE_PUNCT][punctTable[i]] = i;
+ }
+ }
+ }
+ private final byte[] text;
+
+ public HighLevelEncoder(byte[] text) {
+ this.text = text;
+ }
+
+ private static Collection updateStateListForPair(Iterable states, int index, int pairCode) {
+ Collection result = new LinkedList<>();
+ for (State state : states) {
+ updateStateForPair(state, index, pairCode, result);
+ }
+ return simplifyStates(result);
+ }
+
+ private static void updateStateForPair(State state, int index, int pairCode, Collection result) {
+ State stateNoBinary = state.endBinaryShift(index);
+ // Possibility 1. Latch to MODE_PUNCT, and then append this code
+ result.add(stateNoBinary.latchAndAppend(MODE_PUNCT, pairCode));
+ if (state.getMode() != MODE_PUNCT) {
+ // Possibility 2. Shift to MODE_PUNCT, and then append this code.
+ // Every state except MODE_PUNCT (handled above) can shift
+ result.add(stateNoBinary.shiftAndAppend(MODE_PUNCT, pairCode));
+ }
+ if (pairCode == 3 || pairCode == 4) {
+ // both characters are in DIGITS. Sometimes better to just add two digits
+ State digit_state = stateNoBinary
+ .latchAndAppend(MODE_DIGIT, 16 - pairCode) // period or comma in DIGIT
+ .latchAndAppend(MODE_DIGIT, 1); // space in DIGIT
+ result.add(digit_state);
+ }
+ if (state.getBinaryShiftByteCount() > 0) {
+ // It only makes sense to do the characters as binary if we're already
+ // in binary mode.
+ State binaryState = state.addBinaryShiftChar(index).addBinaryShiftChar(index + 1);
+ result.add(binaryState);
+ }
+ }
+
+ private static Collection simplifyStates(Iterable states) {
+ List result = new LinkedList<>();
+ for (State newState : states) {
+ boolean add = true;
+ for (Iterator iterator = result.iterator(); iterator.hasNext(); ) {
+ State oldState = iterator.next();
+ if (oldState.isBetterThanOrEqualTo(newState)) {
+ add = false;
+ break;
+ }
+ if (newState.isBetterThanOrEqualTo(oldState)) {
+ iterator.remove();
+ }
+ }
+ if (add) {
+ result.add(newState);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * @return text represented by this encoder encoded as a {@link BitArray}
+ */
+ public BitArray encode() {
+ Collection states = Collections.singletonList(State.INITIAL_STATE);
+ for (int index = 0; index < text.length; index++) {
+ int pairCode;
+ int nextChar = index + 1 < text.length ? text[index + 1] : 0;
+ switch (text[index]) {
+ case '\r':
+ pairCode = nextChar == '\n' ? 2 : 0;
+ break;
+ case '.':
+ pairCode = nextChar == ' ' ? 3 : 0;
+ break;
+ case ',':
+ pairCode = nextChar == ' ' ? 4 : 0;
+ break;
+ case ':':
+ pairCode = nextChar == ' ' ? 5 : 0;
+ break;
+ default:
+ pairCode = 0;
+ }
+ if (pairCode > 0) {
+ // We have one of the four special PUNCT pairs. Treat them specially.
+ // Get a new set of states for the two new characters.
+ states = updateStateListForPair(states, index, pairCode);
+ index++;
+ } else {
+ // Get a new set of states for the new character.
+ states = updateStateListForChar(states, index);
+ }
+ }
+ // We are left with a set of states. Find the shortest one.
+ State minState = Collections.min(states, new Comparator() {
+ @Override
+ public int compare(State a, State b) {
+ return a.getBitCount() - b.getBitCount();
+ }
+ });
+ // Convert it to a bit array, and return.
+ return minState.toBitArray(text);
+ }
+
+ // We update a set of states for a new character by updating each state
+ // for the new character, merging the results, and then removing the
+ // non-optimal states.
+ private Collection updateStateListForChar(Iterable states, int index) {
+ Collection result = new LinkedList<>();
+ for (State state : states) {
+ updateStateForChar(state, index, result);
+ }
+ return simplifyStates(result);
+ }
+
+ // Return a set of states that represent the possible ways of updating this
+ // state for the next character. The resulting set of states are added to
+ // the "result" list.
+ private void updateStateForChar(State state, int index, Collection result) {
+ char ch = (char) (text[index] & 0xFF);
+ boolean charInCurrentTable = CHAR_MAP[state.getMode()][ch] > 0;
+ State stateNoBinary = null;
+ for (int mode = 0; mode <= MODE_PUNCT; mode++) {
+ int charInMode = CHAR_MAP[mode][ch];
+ if (charInMode > 0) {
+ if (stateNoBinary == null) {
+ // Only create stateNoBinary the first time it's required.
+ stateNoBinary = state.endBinaryShift(index);
+ }
+ // Try generating the character by latching to its mode
+ if (!charInCurrentTable || mode == state.getMode() || mode == MODE_DIGIT) {
+ // If the character is in the current table, we don't want to latch to
+ // any other mode except possibly digit (which uses only 4 bits). Any
+ // other latch would be equally successful *after* this character, and
+ // so wouldn't save any bits.
+ State latch_state = stateNoBinary.latchAndAppend(mode, charInMode);
+ result.add(latch_state);
+ }
+ // Try generating the character by switching to its mode.
+ if (!charInCurrentTable && SHIFT_TABLE[state.getMode()][mode] >= 0) {
+ // It never makes sense to temporarily shift to another mode if the
+ // character exists in the current mode. That can never save bits.
+ State shift_state = stateNoBinary.shiftAndAppend(mode, charInMode);
+ result.add(shift_state);
+ }
+ }
+ }
+ if (state.getBinaryShiftByteCount() > 0 || CHAR_MAP[state.getMode()][ch] == 0) {
+ // It's never worthwhile to go into binary shift mode if you're not already
+ // in binary shift mode, and the character exists in your current mode.
+ // That can never save bits over just outputting the char in the current mode.
+ State binaryState = state.addBinaryShiftChar(index);
+ result.add(binaryState);
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/aztec/encoder/SimpleToken.java b/src/com/google/zxing/aztec/encoder/SimpleToken.java
new file mode 100644
index 0000000..38b0e43
--- /dev/null
+++ b/src/com/google/zxing/aztec/encoder/SimpleToken.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+
+final class SimpleToken extends Token {
+
+ // For normal words, indicates value and bitCount
+ private final short value;
+ private final short bitCount;
+
+ SimpleToken(Token previous, int value, int bitCount) {
+ super(previous);
+ this.value = (short) value;
+ this.bitCount = (short) bitCount;
+ }
+
+ @Override
+ void appendTo(BitArray bitArray, byte[] text) {
+ bitArray.appendBits(value, bitCount);
+ }
+
+ @Override
+ public String toString() {
+ int value = this.value & ((1 << bitCount) - 1);
+ value |= 1 << bitCount;
+ return '<' + Integer.toBinaryString(value | (1 << bitCount)).substring(1) + '>';
+ }
+
+}
diff --git a/src/com/google/zxing/aztec/encoder/State.java b/src/com/google/zxing/aztec/encoder/State.java
new file mode 100644
index 0000000..c8ffa68
--- /dev/null
+++ b/src/com/google/zxing/aztec/encoder/State.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+
+import java.util.Deque;
+import java.util.LinkedList;
+
+/**
+ * State represents all information about a sequence necessary to generate the current output.
+ * Note that a state is immutable.
+ */
+final class State {
+
+ static final State INITIAL_STATE = new State(Token.EMPTY, HighLevelEncoder.MODE_UPPER, 0, 0);
+
+ // The current mode of the encoding (or the mode to which we'll return if
+ // we're in Binary Shift mode.
+ private final int mode;
+ // The list of tokens that we output. If we are in Binary Shift mode, this
+ // token list does *not* yet included the token for those bytes
+ private final Token token;
+ // If non-zero, the number of most recent bytes that should be output
+ // in Binary Shift mode.
+ private final int binaryShiftByteCount;
+ // The total number of bits generated (including Binary Shift).
+ private final int bitCount;
+
+ private State(Token token, int mode, int binaryBytes, int bitCount) {
+ this.token = token;
+ this.mode = mode;
+ this.binaryShiftByteCount = binaryBytes;
+ this.bitCount = bitCount;
+ // Make sure we match the token
+ //int binaryShiftBitCount = (binaryShiftByteCount * 8) +
+ // (binaryShiftByteCount == 0 ? 0 :
+ // binaryShiftByteCount <= 31 ? 10 :
+ // binaryShiftByteCount <= 62 ? 20 : 21);
+ //assert this.bitCount == token.getTotalBitCount() + binaryShiftBitCount;
+ }
+
+ int getMode() {
+ return mode;
+ }
+
+ Token getToken() {
+ return token;
+ }
+
+ int getBinaryShiftByteCount() {
+ return binaryShiftByteCount;
+ }
+
+ int getBitCount() {
+ return bitCount;
+ }
+
+ // Create a new state representing this state with a latch to a (not
+ // necessary different) mode, and then a code.
+ State latchAndAppend(int mode, int value) {
+ //assert binaryShiftByteCount == 0;
+ int bitCount = this.bitCount;
+ Token token = this.token;
+ if (mode != this.mode) {
+ int latch = HighLevelEncoder.LATCH_TABLE[this.mode][mode];
+ token = token.add(latch & 0xFFFF, latch >> 16);
+ bitCount += latch >> 16;
+ }
+ int latchModeBitCount = mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5;
+ token = token.add(value, latchModeBitCount);
+ return new State(token, mode, 0, bitCount + latchModeBitCount);
+ }
+
+ // Create a new state representing this state, with a temporary shift
+ // to a different mode to output a single value.
+ State shiftAndAppend(int mode, int value) {
+ //assert binaryShiftByteCount == 0 && this.mode != mode;
+ Token token = this.token;
+ int thisModeBitCount = this.mode == HighLevelEncoder.MODE_DIGIT ? 4 : 5;
+ // Shifts exist only to UPPER and PUNCT, both with tokens size 5.
+ token = token.add(HighLevelEncoder.SHIFT_TABLE[this.mode][mode], thisModeBitCount);
+ token = token.add(value, 5);
+ return new State(token, this.mode, 0, this.bitCount + thisModeBitCount + 5);
+ }
+
+ // Create a new state representing this state, but an additional character
+ // output in Binary Shift mode.
+ State addBinaryShiftChar(int index) {
+ Token token = this.token;
+ int mode = this.mode;
+ int bitCount = this.bitCount;
+ if (this.mode == HighLevelEncoder.MODE_PUNCT || this.mode == HighLevelEncoder.MODE_DIGIT) {
+ //assert binaryShiftByteCount == 0;
+ int latch = HighLevelEncoder.LATCH_TABLE[mode][HighLevelEncoder.MODE_UPPER];
+ token = token.add(latch & 0xFFFF, latch >> 16);
+ bitCount += latch >> 16;
+ mode = HighLevelEncoder.MODE_UPPER;
+ }
+ int deltaBitCount =
+ (binaryShiftByteCount == 0 || binaryShiftByteCount == 31) ? 18 :
+ (binaryShiftByteCount == 62) ? 9 : 8;
+ State result = new State(token, mode, binaryShiftByteCount + 1, bitCount + deltaBitCount);
+ if (result.binaryShiftByteCount == 2047 + 31) {
+ // The string is as long as it's allowed to be. We should end it.
+ result = result.endBinaryShift(index + 1);
+ }
+ return result;
+ }
+
+ // Create the state identical to this one, but we are no longer in
+ // Binary Shift mode.
+ State endBinaryShift(int index) {
+ if (binaryShiftByteCount == 0) {
+ return this;
+ }
+ Token token = this.token;
+ token = token.addBinaryShift(index - binaryShiftByteCount, binaryShiftByteCount);
+ //assert token.getTotalBitCount() == this.bitCount;
+ return new State(token, mode, 0, this.bitCount);
+ }
+
+ // Returns true if "this" state is better (or equal) to be in than "that"
+ // state under all possible circumstances.
+ boolean isBetterThanOrEqualTo(State other) {
+ int mySize = this.bitCount + (HighLevelEncoder.LATCH_TABLE[this.mode][other.mode] >> 16);
+ if (other.binaryShiftByteCount > 0 &&
+ (this.binaryShiftByteCount == 0 || this.binaryShiftByteCount > other.binaryShiftByteCount)) {
+ mySize += 10; // Cost of entering Binary Shift mode.
+ }
+ return mySize <= other.bitCount;
+ }
+
+ BitArray toBitArray(byte[] text) {
+ // Reverse the tokens, so that they are in the order that they should
+ // be output
+ Deque symbols = new LinkedList<>();
+ for (Token token = endBinaryShift(text.length).token; token != null; token = token.getPrevious()) {
+ symbols.addFirst(token);
+ }
+ BitArray bitArray = new BitArray();
+ // Add each token to the result.
+ for (Token symbol : symbols) {
+ symbol.appendTo(bitArray, text);
+ }
+ //assert bitArray.getSize() == this.bitCount;
+ return bitArray;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s bits=%d bytes=%d", HighLevelEncoder.MODE_NAMES[mode], bitCount, binaryShiftByteCount);
+ }
+
+}
diff --git a/src/com/google/zxing/aztec/encoder/Token.java b/src/com/google/zxing/aztec/encoder/Token.java
new file mode 100644
index 0000000..2eccf91
--- /dev/null
+++ b/src/com/google/zxing/aztec/encoder/Token.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.aztec.encoder;
+
+import com.google.zxing.common.BitArray;
+
+abstract class Token {
+
+ static final Token EMPTY = new SimpleToken(null, 0, 0);
+
+ private final Token previous;
+
+ Token(Token previous) {
+ this.previous = previous;
+ }
+
+ final Token getPrevious() {
+ return previous;
+ }
+
+ final Token add(int value, int bitCount) {
+ return new SimpleToken(this, value, bitCount);
+ }
+
+ final Token addBinaryShift(int start, int byteCount) {
+ //int bitCount = (byteCount * 8) + (byteCount <= 31 ? 10 : byteCount <= 62 ? 20 : 21);
+ return new BinaryShiftToken(this, start, byteCount);
+ }
+
+ abstract void appendTo(BitArray bitArray, byte[] text);
+
+}
diff --git a/src/com/google/zxing/client/j2se/BufferedImageLuminanceSource.java b/src/com/google/zxing/client/j2se/BufferedImageLuminanceSource.java
new file mode 100644
index 0000000..0288f60
--- /dev/null
+++ b/src/com/google/zxing/client/j2se/BufferedImageLuminanceSource.java
@@ -0,0 +1,182 @@
+/*
+ * 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.j2se;
+
+import com.google.zxing.LuminanceSource;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.awt.image.WritableRaster;
+
+/**
+ * This LuminanceSource implementation is meant for J2SE clients and our blackbox unit tests.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ * @author code@elektrowolle.de (Wolfgang Jung)
+ */
+public final class BufferedImageLuminanceSource extends LuminanceSource {
+
+ private static final double MINUS_45_IN_RADIANS = -0.7853981633974483; // Math.toRadians(-45.0)
+
+ private final BufferedImage image;
+ private final int left;
+ private final int top;
+
+ public BufferedImageLuminanceSource(BufferedImage image) {
+ this(image, 0, 0, image.getWidth(), image.getHeight());
+ }
+
+ public BufferedImageLuminanceSource(BufferedImage image, int left, int top, int width, int height) {
+ super(width, height);
+
+ if (image.getType() == BufferedImage.TYPE_BYTE_GRAY) {
+ this.image = image;
+ } else {
+ int sourceWidth = image.getWidth();
+ int sourceHeight = image.getHeight();
+ if (left + width > sourceWidth || top + height > sourceHeight) {
+ throw new IllegalArgumentException("Crop rectangle does not fit within image data.");
+ }
+
+ this.image = new BufferedImage(sourceWidth, sourceHeight, BufferedImage.TYPE_BYTE_GRAY);
+
+ WritableRaster raster = this.image.getRaster();
+ int[] buffer = new int[width];
+ for (int y = top; y < top + height; y++) {
+ image.getRGB(left, y, width, 1, buffer, 0, sourceWidth);
+ for (int x = 0; x < width; x++) {
+ int pixel = buffer[x];
+
+ // The color of fully-transparent pixels is irrelevant. They are often, technically, fully-transparent
+ // black (0 alpha, and then 0 RGB). They are often used, of course as the "white" area in a
+ // barcode image. Force any such pixel to be white:
+ if ((pixel & 0xFF000000) == 0) {
+ pixel = 0xFFFFFFFF; // = white
+ }
+
+ // .229R + 0.587G + 0.114B (YUV/YIQ for PAL and NTSC)
+ buffer[x] =
+ (306 * ((pixel >> 16) & 0xFF) +
+ 601 * ((pixel >> 8) & 0xFF) +
+ 117 * (pixel & 0xFF) +
+ 0x200) >> 10;
+ }
+ raster.setPixels(left, y, width, 1, buffer);
+ }
+
+ }
+ this.left = left;
+ this.top = top;
+ }
+
+ @Override
+ public byte[] getRow(int y, byte[] row) {
+ if (y < 0 || y >= getHeight()) {
+ throw new IllegalArgumentException("Requested row is outside the image: " + y);
+ }
+ int width = getWidth();
+ if (row == null || row.length < width) {
+ row = new byte[width];
+ }
+ // The underlying raster of image consists of bytes with the luminance values
+ image.getRaster().getDataElements(left, top + y, width, 1, row);
+ return row;
+ }
+
+ @Override
+ public byte[] getMatrix() {
+ int width = getWidth();
+ int height = getHeight();
+ int area = width * height;
+ byte[] matrix = new byte[area];
+ // The underlying raster of image consists of area bytes with the luminance values
+ image.getRaster().getDataElements(left, top, width, height, matrix);
+ return matrix;
+ }
+
+ @Override
+ public boolean isCropSupported() {
+ return true;
+ }
+
+ @Override
+ public LuminanceSource crop(int left, int top, int width, int height) {
+ return new BufferedImageLuminanceSource(image, this.left + left, this.top + top, width, height);
+ }
+
+ /**
+ * This is always true, since the image is a gray-scale image.
+ *
+ * @return true
+ */
+ @Override
+ public boolean isRotateSupported() {
+ return true;
+ }
+
+ @Override
+ public LuminanceSource rotateCounterClockwise() {
+ int sourceWidth = image.getWidth();
+ int sourceHeight = image.getHeight();
+
+ // Rotate 90 degrees counterclockwise.
+ AffineTransform transform = new AffineTransform(0.0, -1.0, 1.0, 0.0, 0.0, sourceWidth);
+
+ // Note width/height are flipped since we are rotating 90 degrees.
+ BufferedImage rotatedImage = new BufferedImage(sourceHeight, sourceWidth, BufferedImage.TYPE_BYTE_GRAY);
+
+ // Draw the original image into rotated, via transformation
+ Graphics2D g = rotatedImage.createGraphics();
+ g.drawImage(image, transform, null);
+ g.dispose();
+
+ // Maintain the cropped region, but rotate it too.
+ int width = getWidth();
+ return new BufferedImageLuminanceSource(rotatedImage, top, sourceWidth - (left + width), getHeight(), width);
+ }
+
+ @Override
+ public LuminanceSource rotateCounterClockwise45() {
+ int width = getWidth();
+ int height = getHeight();
+
+ int oldCenterX = left + width / 2;
+ int oldCenterY = top + height / 2;
+
+ // Rotate 45 degrees counterclockwise.
+ AffineTransform transform = AffineTransform.getRotateInstance(MINUS_45_IN_RADIANS, oldCenterX, oldCenterY);
+
+ int sourceDimension = Math.max(image.getWidth(), image.getHeight());
+ BufferedImage rotatedImage = new BufferedImage(sourceDimension, sourceDimension, BufferedImage.TYPE_BYTE_GRAY);
+
+ // Draw the original image into rotated, via transformation
+ Graphics2D g = rotatedImage.createGraphics();
+ g.drawImage(image, transform, null);
+ g.dispose();
+
+ int halfDimension = Math.max(width, height) / 2;
+ int newLeft = Math.max(0, oldCenterX - halfDimension);
+ int newTop = Math.max(0, oldCenterY - halfDimension);
+ int newRight = Math.min(sourceDimension - 1, oldCenterX + halfDimension);
+ int newBottom = Math.min(sourceDimension - 1, oldCenterY + halfDimension);
+
+ return new BufferedImageLuminanceSource(rotatedImage, newLeft, newTop, newRight - newLeft, newBottom - newTop);
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2se/CommandLineEncoder.java b/src/com/google/zxing/client/j2se/CommandLineEncoder.java
new file mode 100644
index 0000000..2b3690c
--- /dev/null
+++ b/src/com/google/zxing/client/j2se/CommandLineEncoder.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.j2se;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.common.BitMatrix;
+
+import java.nio.file.Paths;
+import java.util.Locale;
+
+/**
+ * Command line utility for encoding barcodes.
+ *
+ * @author Sean Owen
+ */
+public final class CommandLineEncoder {
+
+ private static final BarcodeFormat DEFAULT_BARCODE_FORMAT = BarcodeFormat.QR_CODE;
+ private static final String DEFAULT_IMAGE_FORMAT = "PNG";
+ private static final String DEFAULT_OUTPUT_FILE = "out";
+ private static final int DEFAULT_WIDTH = 300;
+ private static final int DEFAULT_HEIGHT = 300;
+
+ private CommandLineEncoder() {
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length == 0) {
+ printUsage();
+ return;
+ }
+
+ BarcodeFormat barcodeFormat = DEFAULT_BARCODE_FORMAT;
+ String imageFormat = DEFAULT_IMAGE_FORMAT;
+ String outFileString = DEFAULT_OUTPUT_FILE;
+ int width = DEFAULT_WIDTH;
+ int height = DEFAULT_HEIGHT;
+ String contents = null;
+
+ for (String arg : args) {
+ String[] argValue = arg.split("=");
+ switch (argValue[0]) {
+ case "--barcode_format":
+ barcodeFormat = BarcodeFormat.valueOf(argValue[1]);
+ break;
+ case "--image_format":
+ imageFormat = argValue[1];
+ break;
+ case "--output":
+ outFileString = argValue[1];
+ break;
+ case "--width":
+ width = Integer.parseInt(argValue[1]);
+ break;
+ case "--height":
+ height = Integer.parseInt(argValue[1]);
+ break;
+ default:
+ if (arg.startsWith("-")) {
+ System.err.println("Unknown command line option " + arg);
+ printUsage();
+ return;
+ }
+ contents = arg;
+ break;
+ }
+ }
+
+ if (contents == null) {
+ printUsage();
+ return;
+ }
+
+ if (DEFAULT_OUTPUT_FILE.equals(outFileString)) {
+ outFileString += '.' + imageFormat.toLowerCase(Locale.ENGLISH);
+ }
+
+ BitMatrix matrix = new MultiFormatWriter().encode(contents, barcodeFormat, width, height);
+ MatrixToImageWriter.writeToPath(matrix, imageFormat, Paths.get(outFileString));
+ }
+
+ private static void printUsage() {
+ System.err.println("Encodes barcode images using the ZXing library\n");
+ System.err.println("usage: CommandLineEncoder [ options ] content_to_encode");
+ System.err.println(" --barcode_format=format: Format to encode, from BarcodeFormat class. " +
+ "Not all formats are supported. Defaults to QR_CODE.");
+ System.err.println(" --image_format=format: image output format, such as PNG, JPG, GIF. Defaults to PNG");
+ System.err.println(" --output=filename: File to write to. Defaults to out.png");
+ System.err.println(" --width=pixels: Image width. Defaults to 300");
+ System.err.println(" --height=pixels: Image height. Defaults to 300");
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2se/CommandLineRunner.java b/src/com/google/zxing/client/j2se/CommandLineRunner.java
new file mode 100644
index 0000000..106e019
--- /dev/null
+++ b/src/com/google/zxing/client/j2se/CommandLineRunner.java
@@ -0,0 +1,222 @@
+/*
+ * 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.j2se;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.DecodeHintType;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.regex.Pattern;
+
+/**
+ * This simple command line utility decodes files, directories of files, or URIs which are passed
+ * as arguments. By default it uses the normal decoding algorithms, but you can pass --try_harder
+ * to request that hint. The raw text of each barcode is printed, and when running against
+ * directories, summary statistics are also displayed.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class CommandLineRunner {
+
+ private static final Pattern COMMA = Pattern.compile(",");
+
+ private CommandLineRunner() {
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args.length == 0) {
+ printUsage();
+ return;
+ }
+
+ Config config = new Config();
+ Queue inputs = new ConcurrentLinkedQueue<>();
+
+ for (String arg : args) {
+ String[] argValue = arg.split("=");
+ switch (argValue[0]) {
+ case "--try_harder":
+ config.setTryHarder(true);
+ break;
+ case "--pure_barcode":
+ config.setPureBarcode(true);
+ break;
+ case "--products_only":
+ config.setProductsOnly(true);
+ break;
+ case "--dump_results":
+ config.setDumpResults(true);
+ break;
+ case "--dump_black_point":
+ config.setDumpBlackPoint(true);
+ break;
+ case "--multi":
+ config.setMulti(true);
+ break;
+ case "--brief":
+ config.setBrief(true);
+ break;
+ case "--recursive":
+ config.setRecursive(true);
+ break;
+ case "--crop":
+ int[] crop = new int[4];
+ String[] tokens = COMMA.split(argValue[1]);
+ for (int i = 0; i < crop.length; i++) {
+ crop[i] = Integer.parseInt(tokens[i]);
+ }
+ config.setCrop(crop);
+ break;
+ case "--possibleFormats":
+ config.setPossibleFormats(COMMA.split(argValue[1]));
+ break;
+ default:
+ if (arg.startsWith("-")) {
+ System.err.println("Unknown command line option " + arg);
+ printUsage();
+ return;
+ }
+ addArgumentToInputs(Paths.get(arg), config, inputs);
+ break;
+ }
+ }
+ config.setHints(buildHints(config));
+
+ int numThreads = Math.min(inputs.size(), Runtime.getRuntime().availableProcessors());
+ int successful = 0;
+ if (numThreads > 1) {
+ ExecutorService executor = Executors.newFixedThreadPool(numThreads);
+ Collection> futures = new ArrayList<>(numThreads);
+ for (int x = 0; x < numThreads; x++) {
+ futures.add(executor.submit(new DecodeWorker(config, inputs)));
+ }
+ executor.shutdown();
+ for (Future future : futures) {
+ successful += future.get();
+ }
+ } else {
+ successful += new DecodeWorker(config, inputs).call();
+ }
+
+ int total = inputs.size();
+ if (total > 1) {
+ System.out.println("\nDecoded " + successful + " files out of " + total +
+ " successfully (" + (successful * 100 / total) + "%)\n");
+ }
+ }
+
+ // Build all the inputs up front into a single flat list, so the threads can atomically pull
+ // paths/URLs off the queue.
+ private static void addArgumentToInputs(Path inputFile, Config config, Queue inputs) throws IOException {
+ if (Files.isDirectory(inputFile)) {
+ try (DirectoryStream paths = Files.newDirectoryStream(inputFile)) {
+ for (Path singleFile : paths) {
+ String filename = singleFile.getFileName().toString().toLowerCase(Locale.ENGLISH);
+ // Skip hidden files and directories (e.g. svn stuff).
+ if (filename.startsWith(".")) {
+ continue;
+ }
+ // Recur on nested directories if requested, otherwise skip them.
+ if (Files.isDirectory(singleFile)) {
+ if (config.isRecursive()) {
+ addArgumentToInputs(singleFile, config, inputs);
+ }
+ continue;
+ }
+ // Skip text files and the results of dumping the black point.
+ if (filename.endsWith(".txt") || filename.contains(".mono.png")) {
+ continue;
+ }
+ inputs.add(singleFile);
+ }
+ }
+ } else {
+ inputs.add(inputFile);
+ }
+ }
+
+ // Manually turn on all formats, even those not yet considered production quality.
+ private static Map buildHints(Config config) {
+ Collection possibleFormats = new ArrayList<>();
+ String[] possibleFormatsNames = config.getPossibleFormats();
+ if (possibleFormatsNames != null && possibleFormatsNames.length > 0) {
+ for (String format : possibleFormatsNames) {
+ possibleFormats.add(BarcodeFormat.valueOf(format));
+ }
+ } else {
+ possibleFormats.add(BarcodeFormat.UPC_A);
+ possibleFormats.add(BarcodeFormat.UPC_E);
+ possibleFormats.add(BarcodeFormat.EAN_13);
+ possibleFormats.add(BarcodeFormat.EAN_8);
+ possibleFormats.add(BarcodeFormat.RSS_14);
+ possibleFormats.add(BarcodeFormat.RSS_EXPANDED);
+ if (!config.isProductsOnly()) {
+ possibleFormats.add(BarcodeFormat.CODE_39);
+ possibleFormats.add(BarcodeFormat.CODE_93);
+ possibleFormats.add(BarcodeFormat.CODE_128);
+ possibleFormats.add(BarcodeFormat.ITF);
+ possibleFormats.add(BarcodeFormat.QR_CODE);
+ possibleFormats.add(BarcodeFormat.DATA_MATRIX);
+ possibleFormats.add(BarcodeFormat.AZTEC);
+ possibleFormats.add(BarcodeFormat.PDF_417);
+ possibleFormats.add(BarcodeFormat.CODABAR);
+ possibleFormats.add(BarcodeFormat.MAXICODE);
+ }
+ }
+ Map hints = new EnumMap<>(DecodeHintType.class);
+ hints.put(DecodeHintType.POSSIBLE_FORMATS, possibleFormats);
+ if (config.isTryHarder()) {
+ hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
+ }
+ if (config.isPureBarcode()) {
+ hints.put(DecodeHintType.PURE_BARCODE, Boolean.TRUE);
+ }
+ return hints;
+ }
+
+ private static void printUsage() {
+ System.err.println("Decode barcode images using the ZXing library");
+ System.err.println();
+ System.err.println("usage: CommandLineRunner { file | dir | url } [ options ]");
+ System.err.println(" --try_harder: Use the TRY_HARDER hint, default is normal (mobile) mode");
+ System.err.println(" --pure_barcode: Input image is a pure monochrome barcode image, not a photo");
+ System.err.println(" --products_only: Only decode the UPC and EAN families of barcodes");
+ System.err.println(" --dump_results: Write the decoded contents to input.txt");
+ System.err.println(" --dump_black_point: Compare black point algorithms as input.mono.png");
+ System.err.println(" --multi: Scans image for multiple barcodes");
+ System.err.println(" --brief: Only output one line per file, omitting the contents");
+ System.err.println(" --recursive: Descend into subdirectories");
+ System.err.println(" --crop=left,top,width,height: Only examine cropped region of input image(s)");
+ StringBuilder builder = new StringBuilder();
+ builder.append(" --possibleFormats=barcodeFormat[,barcodeFormat2...] where barcodeFormat is any of: ");
+ for (BarcodeFormat format : BarcodeFormat.values()) {
+ builder.append(format).append(',');
+ }
+ builder.setLength(builder.length() - 1);
+ System.err.println(builder);
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2se/Config.java b/src/com/google/zxing/client/j2se/Config.java
new file mode 100644
index 0000000..07586e9
--- /dev/null
+++ b/src/com/google/zxing/client/j2se/Config.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.j2se;
+
+import com.google.zxing.DecodeHintType;
+
+import java.util.Map;
+
+final class Config {
+
+ private Map hints;
+ private boolean tryHarder;
+ private boolean pureBarcode;
+ private boolean productsOnly;
+ private boolean dumpResults;
+ private boolean dumpBlackPoint;
+ private boolean multi;
+ private boolean brief;
+ private boolean recursive;
+ private int[] crop;
+ private String[] possibleFormats;
+
+ Map getHints() {
+ return hints;
+ }
+
+ void setHints(Map hints) {
+ this.hints = hints;
+ }
+
+ boolean isTryHarder() {
+ return tryHarder;
+ }
+
+ void setTryHarder(boolean tryHarder) {
+ this.tryHarder = tryHarder;
+ }
+
+ boolean isPureBarcode() {
+ return pureBarcode;
+ }
+
+ void setPureBarcode(boolean pureBarcode) {
+ this.pureBarcode = pureBarcode;
+ }
+
+ boolean isProductsOnly() {
+ return productsOnly;
+ }
+
+ void setProductsOnly(boolean productsOnly) {
+ this.productsOnly = productsOnly;
+ }
+
+ boolean isDumpResults() {
+ return dumpResults;
+ }
+
+ void setDumpResults(boolean dumpResults) {
+ this.dumpResults = dumpResults;
+ }
+
+ boolean isDumpBlackPoint() {
+ return dumpBlackPoint;
+ }
+
+ void setDumpBlackPoint(boolean dumpBlackPoint) {
+ this.dumpBlackPoint = dumpBlackPoint;
+ }
+
+ boolean isMulti() {
+ return multi;
+ }
+
+ void setMulti(boolean multi) {
+ this.multi = multi;
+ }
+
+ boolean isBrief() {
+ return brief;
+ }
+
+ void setBrief(boolean brief) {
+ this.brief = brief;
+ }
+
+ boolean isRecursive() {
+ return recursive;
+ }
+
+ void setRecursive(boolean recursive) {
+ this.recursive = recursive;
+ }
+
+ int[] getCrop() {
+ return crop;
+ }
+
+ void setCrop(int[] crop) {
+ this.crop = crop;
+ }
+
+ String[] getPossibleFormats() {
+ return possibleFormats;
+ }
+
+ void setPossibleFormats(String[] possibleFormats) {
+ this.possibleFormats = possibleFormats;
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2se/DecodeWorker.java b/src/com/google/zxing/client/j2se/DecodeWorker.java
new file mode 100644
index 0000000..651066f
--- /dev/null
+++ b/src/com/google/zxing/client/j2se/DecodeWorker.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.j2se;
+
+import com.google.zxing.*;
+import com.google.zxing.client.result.ParsedResult;
+import com.google.zxing.client.result.ResultParser;
+import com.google.zxing.common.BitArray;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.HybridBinarizer;
+import com.google.zxing.multi.GenericMultipleBarcodeReader;
+import com.google.zxing.multi.MultipleBarcodeReader;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.concurrent.Callable;
+
+/**
+ * One of a pool of threads which pulls images off the Inputs queue and decodes them in parallel.
+ *
+ * @see CommandLineRunner
+ */
+final class DecodeWorker implements Callable {
+
+ private static final int RED = 0xFFFF0000;
+ private static final int BLACK = 0xFF000000;
+ private static final int WHITE = 0xFFFFFFFF;
+
+ private final Config config;
+ private final Queue inputs;
+
+ DecodeWorker(Config config, Queue inputs) {
+ this.config = config;
+ this.inputs = inputs;
+ }
+
+ private static void dumpResult(Path input, Result result) throws IOException {
+ String name = input.getFileName().toString();
+ int pos = name.lastIndexOf('.');
+ if (pos > 0) {
+ name = name.substring(0, pos) + ".txt";
+ }
+ Path dumpFile = input.getParent().resolve(name);
+ Files.write(dumpFile, Collections.singleton(result.getText()), StandardCharsets.UTF_8);
+ }
+
+ private static void dumpResultMulti(Path input, Result[] results) throws IOException {
+ String name = input.getFileName().toString();
+ int pos = name.lastIndexOf('.');
+ if (pos > 0) {
+ name = name.substring(0, pos) + ".txt";
+ }
+ Path dumpFile = input.getParent().resolve(name);
+ Collection resultTexts = new ArrayList<>();
+ for (Result result : results) {
+ resultTexts.add(result.getText());
+ }
+ Files.write(dumpFile, resultTexts, StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Writes out a single PNG which is three times the width of the input image, containing from left
+ * to right: the original image, the row sampling monochrome version, and the 2D sampling
+ * monochrome version.
+ */
+ private static void dumpBlackPoint(URI uri, BufferedImage image, BinaryBitmap bitmap) {
+ if (uri.getPath().contains(".mono.png")) {
+ return;
+ }
+
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+ int stride = width * 3;
+ int[] pixels = new int[stride * height];
+
+ // The original image
+ int[] argb = new int[width];
+ for (int y = 0; y < height; y++) {
+ image.getRGB(0, y, width, 1, argb, 0, width);
+ System.arraycopy(argb, 0, pixels, y * stride, width);
+ }
+
+ // Row sampling
+ BitArray row = new BitArray(width);
+ for (int y = 0; y < height; y++) {
+ try {
+ row = bitmap.getBlackRow(y, row);
+ } catch (NotFoundException nfe) {
+ // If fetching the row failed, draw a red line and keep going.
+ int offset = y * stride + width;
+ Arrays.fill(pixels, offset, offset + width, RED);
+ continue;
+ }
+
+ int offset = y * stride + width;
+ for (int x = 0; x < width; x++) {
+ pixels[offset + x] = row.get(x) ? BLACK : WHITE;
+ }
+ }
+
+ // 2D sampling
+ try {
+ for (int y = 0; y < height; y++) {
+ BitMatrix matrix = bitmap.getBlackMatrix();
+ int offset = y * stride + width * 2;
+ for (int x = 0; x < width; x++) {
+ pixels[offset + x] = matrix.get(x, y) ? BLACK : WHITE;
+ }
+ }
+ } catch (NotFoundException ignored) {
+ // continue
+ }
+
+ writeResultImage(stride, height, pixels, uri, ".mono.png");
+ }
+
+ private static void writeResultImage(int stride,
+ int height,
+ int[] pixels,
+ URI uri,
+ String suffix) {
+ BufferedImage result = new BufferedImage(stride, height, BufferedImage.TYPE_INT_ARGB);
+ result.setRGB(0, 0, stride, height, pixels, 0, stride);
+
+ // Use the current working directory for URLs
+ String resultName = uri.getPath();
+ if ("http".equals(uri.getScheme())) {
+ int pos = resultName.lastIndexOf('/');
+ if (pos > 0) {
+ resultName = '.' + resultName.substring(pos);
+ }
+ }
+ int pos = resultName.lastIndexOf('.');
+ if (pos > 0) {
+ resultName = resultName.substring(0, pos);
+ }
+ resultName += suffix;
+ try {
+ if (!ImageIO.write(result, "png", Paths.get(resultName).toFile())) {
+ System.err.println("Could not encode an image to " + resultName);
+ }
+ } catch (IOException ignored) {
+ System.err.println("Could not write to " + resultName);
+ }
+ }
+
+ @Override
+ public Integer call() throws IOException {
+ int successful = 0;
+ Path input;
+ while ((input = inputs.poll()) != null) {
+ if (Files.exists(input)) {
+ if (config.isMulti()) {
+ Result[] results = decodeMulti(input.toUri(), config.getHints());
+ if (results != null) {
+ successful++;
+ if (config.isDumpResults()) {
+ dumpResultMulti(input, results);
+ }
+ }
+ } else {
+ Result result = decode(input.toUri(), config.getHints());
+ if (result != null) {
+ successful++;
+ if (config.isDumpResults()) {
+ dumpResult(input, result);
+ }
+ }
+ }
+ } else {
+ if (decode(input.toUri(), config.getHints()) != null) {
+ successful++;
+ }
+ }
+ }
+ return successful;
+ }
+
+ private Result decode(URI uri, Map hints) throws IOException {
+ BufferedImage image = ImageReader.readImage(uri);
+ try {
+ LuminanceSource source;
+ if (config.getCrop() == null) {
+ source = new BufferedImageLuminanceSource(image);
+ } else {
+ int[] crop = config.getCrop();
+ source = new BufferedImageLuminanceSource(image, crop[0], crop[1], crop[2], crop[3]);
+ }
+ BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+ if (config.isDumpBlackPoint()) {
+ dumpBlackPoint(uri, image, bitmap);
+ }
+ Result result = new MultiFormatReader().decode(bitmap, hints);
+ if (config.isBrief()) {
+ System.out.println(uri + ": Success");
+ } else {
+ ParsedResult parsedResult = ResultParser.parseResult(result);
+ System.out.println(uri + " (format: " + result.getBarcodeFormat() + ", type: " +
+ parsedResult.getType() + "):\nRaw result:\n" + result.getText() + "\nParsed result:\n" +
+ parsedResult.getDisplayResult());
+
+ System.out.println("Found " + result.getResultPoints().length + " result points.");
+ for (int i = 0; i < result.getResultPoints().length; i++) {
+ ResultPoint rp = result.getResultPoints()[i];
+ if (rp != null) {
+ System.out.println(" Point " + i + ": (" + rp.getX() + ',' + rp.getY() + ')');
+ }
+ }
+ }
+
+ return result;
+ } catch (NotFoundException ignored) {
+ System.out.println(uri + ": No barcode found");
+ return null;
+ }
+ }
+
+ private Result[] decodeMulti(URI uri, Map hints) throws IOException {
+ BufferedImage image = ImageReader.readImage(uri);
+ try {
+ LuminanceSource source;
+ if (config.getCrop() == null) {
+ source = new BufferedImageLuminanceSource(image);
+ } else {
+ int[] crop = config.getCrop();
+ source = new BufferedImageLuminanceSource(image, crop[0], crop[1], crop[2], crop[3]);
+ }
+ BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+ if (config.isDumpBlackPoint()) {
+ dumpBlackPoint(uri, image, bitmap);
+ }
+
+ MultiFormatReader multiFormatReader = new MultiFormatReader();
+ MultipleBarcodeReader reader = new GenericMultipleBarcodeReader(multiFormatReader);
+ Result[] results = reader.decodeMultiple(bitmap, hints);
+
+ if (config.isBrief()) {
+ System.out.println(uri + ": Success");
+ } else {
+ for (Result result : results) {
+ ParsedResult parsedResult = ResultParser.parseResult(result);
+ System.out.println(uri + " (format: "
+ + result.getBarcodeFormat() + ", type: "
+ + parsedResult.getType() + "):\nRaw result:\n"
+ + result.getText() + "\nParsed result:\n"
+ + parsedResult.getDisplayResult());
+ System.out.println("Found " + result.getResultPoints().length + " result points.");
+ for (int i = 0; i < result.getResultPoints().length; i++) {
+ ResultPoint rp = result.getResultPoints()[i];
+ System.out.println(" Point " + i + ": (" + rp.getX() + ',' + rp.getY() + ')');
+ }
+ }
+ }
+ return results;
+ } catch (NotFoundException ignored) {
+ System.out.println(uri + ": No barcode found");
+ return null;
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2se/GUIRunner.java b/src/com/google/zxing/client/j2se/GUIRunner.java
new file mode 100644
index 0000000..a311800
--- /dev/null
+++ b/src/com/google/zxing/client/j2se/GUIRunner.java
@@ -0,0 +1,93 @@
+/*
+ * 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.j2se;
+
+import com.google.zxing.*;
+import com.google.zxing.common.HybridBinarizer;
+
+import javax.swing.*;
+import javax.swing.text.JTextComponent;
+import java.awt.*;
+import java.awt.Dimension;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.nio.file.Path;
+
+/**
+ * Simple GUI frontend to the library. Right now, only decodes a local file.
+ * This definitely needs some improvement. Just throwing something down to start.
+ *
+ * @author Sean Owen
+ */
+public final class GUIRunner extends JFrame {
+
+ private final JLabel imageLabel;
+ private final JTextComponent textArea;
+
+ private GUIRunner() {
+ imageLabel = new JLabel();
+ textArea = new JTextArea();
+ textArea.setEditable(false);
+ textArea.setMaximumSize(new Dimension(400, 200));
+ Container panel = new JPanel();
+ panel.setLayout(new FlowLayout());
+ panel.add(imageLabel);
+ panel.add(textArea);
+ setTitle("ZXing");
+ setSize(400, 400);
+ setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
+ setContentPane(panel);
+ setLocationRelativeTo(null);
+ }
+
+ public static void main(String[] args) throws MalformedURLException {
+ GUIRunner runner = new GUIRunner();
+ runner.setVisible(true);
+ runner.chooseImage();
+ }
+
+ private static String getDecodeText(Path file) {
+ BufferedImage image;
+ try {
+ image = ImageReader.readImage(file.toUri());
+ } catch (IOException ioe) {
+ return ioe.toString();
+ }
+ LuminanceSource source = new BufferedImageLuminanceSource(image);
+ BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
+ Result result;
+ try {
+ result = new MultiFormatReader().decode(bitmap);
+ } catch (ReaderException re) {
+ return re.toString();
+ }
+ return String.valueOf(result.getText());
+ }
+
+ private void chooseImage() throws MalformedURLException {
+ JFileChooser fileChooser = new JFileChooser();
+ fileChooser.showOpenDialog(this);
+ Path file = fileChooser.getSelectedFile().toPath();
+ Icon imageIcon = new ImageIcon(file.toUri().toURL());
+ setSize(imageIcon.getIconWidth(), imageIcon.getIconHeight() + 100);
+ imageLabel.setIcon(imageIcon);
+ String decodeText = getDecodeText(file);
+ textArea.setText(decodeText);
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2se/ImageReader.java b/src/com/google/zxing/client/j2se/ImageReader.java
new file mode 100644
index 0000000..12b0517
--- /dev/null
+++ b/src/com/google/zxing/client/j2se/ImageReader.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.j2se;
+
+import javax.imageio.ImageIO;
+import javax.xml.bind.DatatypeConverter;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URLDecoder;
+
+/**
+ * Encapsulates reading URIs as images.
+ *
+ * @author Sean Owen
+ */
+public final class ImageReader {
+
+ private static final String BASE64TOKEN = "base64,";
+
+ private ImageReader() {
+ }
+
+ public static BufferedImage readImage(URI uri) throws IOException {
+ if ("data".equals(uri.getScheme())) {
+ return readDataURIImage(uri);
+ }
+ BufferedImage result;
+ try {
+ result = ImageIO.read(uri.toURL());
+ } catch (IllegalArgumentException iae) {
+ throw new IOException("Resource not found: " + uri, iae);
+ }
+ if (result == null) {
+ throw new IOException("Could not load " + uri);
+ }
+ return result;
+ }
+
+ public static BufferedImage readDataURIImage(URI uri) throws IOException {
+ String uriString = uri.toString();
+ if (!uriString.startsWith("data:image/")) {
+ throw new IOException("Unsupported data URI MIME type");
+ }
+ int base64Start = uriString.indexOf(BASE64TOKEN);
+ if (base64Start < 0) {
+ throw new IOException("Unsupported data URI encoding");
+ }
+ String base64DataEncoded = uriString.substring(base64Start + BASE64TOKEN.length());
+ String base64Data = URLDecoder.decode(base64DataEncoded, "UTF-8");
+ byte[] imageBytes = DatatypeConverter.parseBase64Binary(base64Data);
+ return ImageIO.read(new ByteArrayInputStream(imageBytes));
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2se/MatrixToImageConfig.java b/src/com/google/zxing/client/j2se/MatrixToImageConfig.java
new file mode 100644
index 0000000..08a11ec
--- /dev/null
+++ b/src/com/google/zxing/client/j2se/MatrixToImageConfig.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.j2se;
+
+import java.awt.image.BufferedImage;
+
+/**
+ * Encapsulates custom configuration used in methods of {@link MatrixToImageWriter}.
+ */
+public final class MatrixToImageConfig {
+
+ public static final int BLACK = 0xFF000000;
+ public static final int WHITE = 0xFFFFFFFF;
+
+ private final int onColor;
+ private final int offColor;
+
+ /**
+ * Creates a default config with on color {@link #BLACK} and off color {@link #WHITE}, generating normal
+ * black-on-white barcodes.
+ */
+ public MatrixToImageConfig() {
+ this(BLACK, WHITE);
+ }
+
+ /**
+ * @param onColor pixel on color, specified as an ARGB value as an int
+ * @param offColor pixel off color, specified as an ARGB value as an int
+ */
+ public MatrixToImageConfig(int onColor, int offColor) {
+ this.onColor = onColor;
+ this.offColor = offColor;
+ }
+
+ private static boolean hasTransparency(int argb) {
+ return (argb & 0xFF000000) != 0xFF000000;
+ }
+
+ public int getPixelOnColor() {
+ return onColor;
+ }
+
+ public int getPixelOffColor() {
+ return offColor;
+ }
+
+ int getBufferedImageColorModel() {
+ if (onColor == BLACK && offColor == WHITE) {
+ // Use faster BINARY if colors match default
+ return BufferedImage.TYPE_BYTE_BINARY;
+ }
+ if (hasTransparency(onColor) || hasTransparency(offColor)) {
+ // Use ARGB representation if colors specify non-opaque alpha
+ return BufferedImage.TYPE_INT_ARGB;
+ }
+ // Default otherwise to RGB representation with ignored alpha channel
+ return BufferedImage.TYPE_INT_RGB;
+ }
+
+}
diff --git a/src/com/google/zxing/client/j2se/MatrixToImageWriter.java b/src/com/google/zxing/client/j2se/MatrixToImageWriter.java
new file mode 100644
index 0000000..18ff9d8
--- /dev/null
+++ b/src/com/google/zxing/client/j2se/MatrixToImageWriter.java
@@ -0,0 +1,160 @@
+/*
+ * 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.j2se;
+
+import com.google.zxing.common.BitMatrix;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Path;
+
+/**
+ * Writes a {@link BitMatrix} to {@link BufferedImage},
+ * file or stream. Provided here instead of core since it depends on
+ * Java SE libraries.
+ *
+ * @author Sean Owen
+ */
+public final class MatrixToImageWriter {
+
+ private static final MatrixToImageConfig DEFAULT_CONFIG = new MatrixToImageConfig();
+
+ private MatrixToImageWriter() {
+ }
+
+ /**
+ * Renders a {@link BitMatrix} as an image, where "false" bits are rendered
+ * as white, and "true" bits are rendered as black. Uses default configuration.
+ *
+ * @param matrix {@link BitMatrix} to write
+ * @return {@link BufferedImage} representation of the input
+ */
+ public static BufferedImage toBufferedImage(BitMatrix matrix) {
+ return toBufferedImage(matrix, DEFAULT_CONFIG);
+ }
+
+ /**
+ * As {@link #toBufferedImage(BitMatrix)}, but allows customization of the output.
+ *
+ * @param matrix {@link BitMatrix} to write
+ * @param config output configuration
+ * @return {@link BufferedImage} representation of the input
+ */
+ public static BufferedImage toBufferedImage(BitMatrix matrix, MatrixToImageConfig config) {
+ int width = matrix.getWidth();
+ int height = matrix.getHeight();
+ BufferedImage image = new BufferedImage(width, height, config.getBufferedImageColorModel());
+ int onColor = config.getPixelOnColor();
+ int offColor = config.getPixelOffColor();
+ for (int x = 0; x < width; x++) {
+ for (int y = 0; y < height; y++) {
+ image.setRGB(x, y, matrix.get(x, y) ? onColor : offColor);
+ }
+ }
+ return image;
+ }
+
+ /**
+ * @param matrix {@link BitMatrix} to write
+ * @param format image format
+ * @param file file {@link File} to write image to
+ * @throws IOException if writes to the file fail
+ * @deprecated use {@link #writeToPath(BitMatrix, String, Path)}
+ */
+ @Deprecated
+ public static void writeToFile(BitMatrix matrix, String format, File file) throws IOException {
+ writeToPath(matrix, format, file.toPath());
+ }
+
+ /**
+ * Writes a {@link BitMatrix} to a file with default configuration.
+ *
+ * @param matrix {@link BitMatrix} to write
+ * @param format image format
+ * @param file file {@link Path} to write image to
+ * @throws IOException if writes to the stream fail
+ * @see #toBufferedImage(BitMatrix)
+ */
+ public static void writeToPath(BitMatrix matrix, String format, Path file) throws IOException {
+ writeToPath(matrix, format, file, DEFAULT_CONFIG);
+ }
+
+ /**
+ * @param matrix {@link BitMatrix} to write
+ * @param format image format
+ * @param file file {@link File} to write image to
+ * @param config output configuration
+ * @throws IOException if writes to the file fail
+ * @deprecated use {@link #writeToPath(BitMatrix, String, Path, MatrixToImageConfig)}
+ */
+ @Deprecated
+ public static void writeToFile(BitMatrix matrix, String format, File file, MatrixToImageConfig config)
+ throws IOException {
+ writeToPath(matrix, format, file.toPath(), config);
+ }
+
+ /**
+ * As {@link #writeToFile(BitMatrix, String, File)}, but allows customization of the output.
+ *
+ * @param matrix {@link BitMatrix} to write
+ * @param format image format
+ * @param file file {@link Path} to write image to
+ * @param config output configuration
+ * @throws IOException if writes to the file fail
+ */
+ public static void writeToPath(BitMatrix matrix, String format, Path file, MatrixToImageConfig config)
+ throws IOException {
+ BufferedImage image = toBufferedImage(matrix, config);
+ if (!ImageIO.write(image, format, file.toFile())) {
+ throw new IOException("Could not write an image of format " + format + " to " + file);
+ }
+ }
+
+ /**
+ * Writes a {@link BitMatrix} to a stream with default configuration.
+ *
+ * @param matrix {@link BitMatrix} to write
+ * @param format image format
+ * @param stream {@link OutputStream} to write image to
+ * @throws IOException if writes to the stream fail
+ * @see #toBufferedImage(BitMatrix)
+ */
+ public static void writeToStream(BitMatrix matrix, String format, OutputStream stream) throws IOException {
+ writeToStream(matrix, format, stream, DEFAULT_CONFIG);
+ }
+
+ /**
+ * As {@link #writeToStream(BitMatrix, String, OutputStream)}, but allows customization of the output.
+ *
+ * @param matrix {@link BitMatrix} to write
+ * @param format image format
+ * @param stream {@link OutputStream} to write image to
+ * @param config output configuration
+ * @throws IOException if writes to the stream fail
+ */
+ public static void writeToStream(BitMatrix matrix, String format, OutputStream stream, MatrixToImageConfig config)
+ throws IOException {
+ BufferedImage image = toBufferedImage(matrix, config);
+ if (!ImageIO.write(image, format, stream)) {
+ throw new IOException("Could not write an image of format " + format);
+ }
+ }
+
+}
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..91cd4eb
--- /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..8f2fbf0
--- /dev/null
+++ b/src/com/google/zxing/client/result/AddressBookAUResultParser.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements KDDI AU's address book format. See
+ *
+ * http://www.au.kddi.com/ezfactory/tec/two_dimensions/index.html.
+ * (Thanks to Yuzo for translating!)
+ *
+ * @author Sean Owen
+ */
+public final class AddressBookAUResultParser extends ResultParser {
+
+ private static String[] matchMultipleValuePrefix(String prefix,
+ int max,
+ String rawText,
+ boolean trim) {
+ List values = null;
+ for (int i = 1; i <= max; i++) {
+ String value = matchSinglePrefixedField(prefix + i + ':', rawText, '\r', trim);
+ if (value == null) {
+ break;
+ }
+ if (values == null) {
+ values = new ArrayList<>(max); // lazy init
+ }
+ values.add(value);
+ }
+ if (values == null) {
+ return null;
+ }
+ return values.toArray(new String[values.size()]);
+ }
+
+ @Override
+ public AddressBookParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ // MEMORY is mandatory; seems like a decent indicator, as does end-of-record separator CR/LF
+ if (!rawText.contains("MEMORY") || !rawText.contains("\r\n")) {
+ return null;
+ }
+
+ // NAME1 and NAME2 have specific uses, namely written name and pronunciation, respectively.
+ // Therefore we treat them specially instead of as an array of names.
+ String name = matchSinglePrefixedField("NAME1:", rawText, '\r', true);
+ String pronunciation = matchSinglePrefixedField("NAME2:", rawText, '\r', true);
+
+ String[] phoneNumbers = matchMultipleValuePrefix("TEL", 3, rawText, true);
+ String[] emails = matchMultipleValuePrefix("MAIL", 3, rawText, true);
+ String note = matchSinglePrefixedField("MEMORY:", rawText, '\r', false);
+ String address = matchSinglePrefixedField("ADD:", rawText, '\r', true);
+ String[] addresses = address == null ? null : new String[]{address};
+ return new AddressBookParsedResult(maybeWrap(name),
+ null,
+ pronunciation,
+ phoneNumbers,
+ null,
+ emails,
+ null,
+ null,
+ note,
+ addresses,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+
+}
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..50582e9
--- /dev/null
+++ b/src/com/google/zxing/client/result/AddressBookDoCoMoResultParser.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Implements the "MECARD" address book entry format.
+ *
+ * Supported keys: N, SOUND, TEL, EMAIL, NOTE, ADR, BDAY, URL, plus ORG
+ * Unsupported keys: TEL-AV, NICKNAME
+ *
+ * Except for TEL, multiple values for keys are also not supported;
+ * the first one found takes precedence.
+ *
+ * Our understanding of the MECARD format is based on this document:
+ *
+ * http://www.mobicode.org.tw/files/OMIA%20Mobile%20Bar%20Code%20Standard%20v3.2.1.doc
+ *
+ * @author Sean Owen
+ */
+public final class AddressBookDoCoMoResultParser extends AbstractDoCoMoResultParser {
+
+ 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;
+ }
+
+ @Override
+ public AddressBookParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("MECARD:")) {
+ return null;
+ }
+ String[] rawName = matchDoCoMoPrefixedField("N:", rawText, true);
+ if (rawName == null) {
+ return null;
+ }
+ String name = parseName(rawName[0]);
+ String pronunciation = matchSingleDoCoMoPrefixedField("SOUND:", rawText, true);
+ String[] phoneNumbers = matchDoCoMoPrefixedField("TEL:", rawText, true);
+ String[] emails = matchDoCoMoPrefixedField("EMAIL:", rawText, true);
+ String note = matchSingleDoCoMoPrefixedField("NOTE:", rawText, false);
+ String[] addresses = matchDoCoMoPrefixedField("ADR:", rawText, true);
+ String birthday = matchSingleDoCoMoPrefixedField("BDAY:", rawText, true);
+ if (!isStringOfDigits(birthday, 8)) {
+ // No reason to throw out the whole card because the birthday is formatted wrong.
+ birthday = null;
+ }
+ String[] urls = matchDoCoMoPrefixedField("URL:", rawText, true);
+
+ // Although ORG may not be strictly legal in MECARD, it does exist in VCARD and we might as well
+ // honor it when found in the wild.
+ String org = matchSingleDoCoMoPrefixedField("ORG:", rawText, true);
+
+ return new AddressBookParsedResult(maybeWrap(name),
+ null,
+ pronunciation,
+ phoneNumbers,
+ null,
+ emails,
+ null,
+ null,
+ note,
+ addresses,
+ null,
+ org,
+ birthday,
+ null,
+ urls,
+ null);
+ }
+
+}
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..ad976c9
--- /dev/null
+++ b/src/com/google/zxing/client/result/AddressBookParsedResult.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class AddressBookParsedResult extends ParsedResult {
+
+ private final String[] names;
+ private final String[] nicknames;
+ private final String pronunciation;
+ private final String[] phoneNumbers;
+ private final String[] phoneTypes;
+ private final String[] emails;
+ private final String[] emailTypes;
+ private final String instantMessenger;
+ private final String note;
+ private final String[] addresses;
+ private final String[] addressTypes;
+ private final String org;
+ private final String birthday;
+ private final String title;
+ private final String[] urls;
+ private final String[] geo;
+
+ public AddressBookParsedResult(String[] names,
+ String[] phoneNumbers,
+ String[] phoneTypes,
+ String[] emails,
+ String[] emailTypes,
+ String[] addresses,
+ String[] addressTypes) {
+ this(names,
+ null,
+ null,
+ phoneNumbers,
+ phoneTypes,
+ emails,
+ emailTypes,
+ null,
+ null,
+ addresses,
+ addressTypes,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ public AddressBookParsedResult(String[] names,
+ String[] nicknames,
+ String pronunciation,
+ String[] phoneNumbers,
+ String[] phoneTypes,
+ String[] emails,
+ String[] emailTypes,
+ String instantMessenger,
+ String note,
+ String[] addresses,
+ String[] addressTypes,
+ String org,
+ String birthday,
+ String title,
+ String[] urls,
+ String[] geo) {
+ super(ParsedResultType.ADDRESSBOOK);
+ this.names = names;
+ this.nicknames = nicknames;
+ this.pronunciation = pronunciation;
+ this.phoneNumbers = phoneNumbers;
+ this.phoneTypes = phoneTypes;
+ this.emails = emails;
+ this.emailTypes = emailTypes;
+ this.instantMessenger = instantMessenger;
+ this.note = note;
+ this.addresses = addresses;
+ this.addressTypes = addressTypes;
+ this.org = org;
+ this.birthday = birthday;
+ this.title = title;
+ this.urls = urls;
+ this.geo = geo;
+ }
+
+ public String[] getNames() {
+ return names;
+ }
+
+ public String[] getNicknames() {
+ return nicknames;
+ }
+
+ /**
+ * In Japanese, the name is written in kanji, which can have multiple readings. Therefore a hint
+ * is often provided, called furigana, which spells the name phonetically.
+ *
+ * @return The pronunciation of the getNames() field, often in hiragana or katakana.
+ */
+ public String getPronunciation() {
+ return pronunciation;
+ }
+
+ public String[] getPhoneNumbers() {
+ return phoneNumbers;
+ }
+
+ /**
+ * @return optional descriptions of the type of each phone number. It could be like "HOME", but,
+ * there is no guaranteed or standard format.
+ */
+ public String[] getPhoneTypes() {
+ return phoneTypes;
+ }
+
+ public String[] getEmails() {
+ return emails;
+ }
+
+ /**
+ * @return optional descriptions of the type of each e-mail. It could be like "WORK", but,
+ * there is no guaranteed or standard format.
+ */
+ public String[] getEmailTypes() {
+ return emailTypes;
+ }
+
+ public String getInstantMessenger() {
+ return instantMessenger;
+ }
+
+ public String getNote() {
+ return note;
+ }
+
+ public String[] getAddresses() {
+ return addresses;
+ }
+
+ /**
+ * @return optional descriptions of the type of each e-mail. It could be like "WORK", but,
+ * there is no guaranteed or standard format.
+ */
+ public String[] getAddressTypes() {
+ return addressTypes;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getOrg() {
+ return org;
+ }
+
+ public String[] getURLs() {
+ return urls;
+ }
+
+ /**
+ * @return birthday formatted as yyyyMMdd (e.g. 19780917)
+ */
+ public String getBirthday() {
+ return birthday;
+ }
+
+ /**
+ * @return a location as a latitude/longitude pair
+ */
+ public String[] getGeo() {
+ return geo;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(100);
+ maybeAppend(names, result);
+ maybeAppend(nicknames, result);
+ maybeAppend(pronunciation, result);
+ maybeAppend(title, result);
+ maybeAppend(org, result);
+ maybeAppend(addresses, result);
+ maybeAppend(phoneNumbers, result);
+ maybeAppend(emails, result);
+ maybeAppend(instantMessenger, result);
+ maybeAppend(urls, result);
+ maybeAppend(birthday, result);
+ maybeAppend(geo, result);
+ maybeAppend(note, result);
+ return result.toString();
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/BizcardResultParser.java b/src/com/google/zxing/client/result/BizcardResultParser.java
new file mode 100644
index 0000000..5ef1af8
--- /dev/null
+++ b/src/com/google/zxing/client/result/BizcardResultParser.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements the "BIZCARD" address book entry format, though this has been
+ * largely reverse-engineered from examples observed in the wild -- still
+ * looking for a definitive reference.
+ *
+ * @author Sean Owen
+ */
+public final class BizcardResultParser extends AbstractDoCoMoResultParser {
+
+ // Yes, we extend AbstractDoCoMoResultParser since the format is very much
+ // like the DoCoMo MECARD format, but this is not technically one of
+ // DoCoMo's proposed formats
+
+ private static String[] buildPhoneNumbers(String number1,
+ String number2,
+ String number3) {
+ List numbers = new ArrayList<>(3);
+ if (number1 != null) {
+ numbers.add(number1);
+ }
+ if (number2 != null) {
+ numbers.add(number2);
+ }
+ if (number3 != null) {
+ numbers.add(number3);
+ }
+ int size = numbers.size();
+ if (size == 0) {
+ return null;
+ }
+ return numbers.toArray(new String[size]);
+ }
+
+ private static String buildName(String firstName, String lastName) {
+ if (firstName == null) {
+ return lastName;
+ } else {
+ return lastName == null ? firstName : firstName + ' ' + lastName;
+ }
+ }
+
+ @Override
+ public AddressBookParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("BIZCARD:")) {
+ return null;
+ }
+ String firstName = matchSingleDoCoMoPrefixedField("N:", rawText, true);
+ String lastName = matchSingleDoCoMoPrefixedField("X:", rawText, true);
+ String fullName = buildName(firstName, lastName);
+ String title = matchSingleDoCoMoPrefixedField("T:", rawText, true);
+ String org = matchSingleDoCoMoPrefixedField("C:", rawText, true);
+ String[] addresses = matchDoCoMoPrefixedField("A:", rawText, true);
+ String phoneNumber1 = matchSingleDoCoMoPrefixedField("B:", rawText, true);
+ String phoneNumber2 = matchSingleDoCoMoPrefixedField("M:", rawText, true);
+ String phoneNumber3 = matchSingleDoCoMoPrefixedField("F:", rawText, true);
+ String email = matchSingleDoCoMoPrefixedField("E:", rawText, true);
+
+ return new AddressBookParsedResult(maybeWrap(fullName),
+ null,
+ null,
+ buildPhoneNumbers(phoneNumber1, phoneNumber2, phoneNumber3),
+ null,
+ maybeWrap(email),
+ null,
+ null,
+ null,
+ addresses,
+ null,
+ org,
+ null,
+ title,
+ null,
+ null);
+ }
+
+}
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..fb5a0c3
--- /dev/null
+++ b/src/com/google/zxing/client/result/BookmarkDoCoMoResultParser.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * @author Sean Owen
+ */
+public final class BookmarkDoCoMoResultParser extends AbstractDoCoMoResultParser {
+
+ @Override
+ public URIParsedResult parse(Result result) {
+ String rawText = result.getText();
+ if (!rawText.startsWith("MEBKM:")) {
+ return null;
+ }
+ String title = matchSingleDoCoMoPrefixedField("TITLE:", rawText, true);
+ String[] rawUri = matchDoCoMoPrefixedField("URL:", rawText, true);
+ if (rawUri == null) {
+ return null;
+ }
+ String uri = rawUri[0];
+ return URIResultParser.isBasicallyValidURI(uri) ? new URIParsedResult(uri, title) : null;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/CalendarParsedResult.java b/src/com/google/zxing/client/result/CalendarParsedResult.java
new file mode 100644
index 0000000..ae3d074
--- /dev/null
+++ b/src/com/google/zxing/client/result/CalendarParsedResult.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Sean Owen
+ */
+public final class CalendarParsedResult extends ParsedResult {
+
+ private static final Pattern RFC2445_DURATION =
+ Pattern.compile("P(?:(\\d+)W)?(?:(\\d+)D)?(?:T(?:(\\d+)H)?(?:(\\d+)M)?(?:(\\d+)S)?)?");
+ private static final long[] RFC2445_DURATION_FIELD_UNITS = {
+ 7 * 24 * 60 * 60 * 1000L, // 1 week
+ 24 * 60 * 60 * 1000L, // 1 day
+ 60 * 60 * 1000L, // 1 hour
+ 60 * 1000L, // 1 minute
+ 1000L, // 1 second
+ };
+
+ private static final Pattern DATE_TIME = Pattern.compile("[0-9]{8}(T[0-9]{6}Z?)?");
+
+ private final String summary;
+ private final Date start;
+ private final boolean startAllDay;
+ private final Date end;
+ private final boolean endAllDay;
+ private final String location;
+ private final String organizer;
+ private final String[] attendees;
+ private final String description;
+ private final double latitude;
+ private final double longitude;
+
+ public CalendarParsedResult(String summary,
+ String startString,
+ String endString,
+ String durationString,
+ String location,
+ String organizer,
+ String[] attendees,
+ String description,
+ double latitude,
+ double longitude) {
+ super(ParsedResultType.CALENDAR);
+ this.summary = summary;
+
+ try {
+ this.start = parseDate(startString);
+ } catch (ParseException pe) {
+ throw new IllegalArgumentException(pe.toString());
+ }
+
+ if (endString == null) {
+ long durationMS = parseDurationMS(durationString);
+ end = durationMS < 0L ? null : new Date(start.getTime() + durationMS);
+ } else {
+ try {
+ this.end = parseDate(endString);
+ } catch (ParseException pe) {
+ throw new IllegalArgumentException(pe.toString());
+ }
+ }
+
+ this.startAllDay = startString.length() == 8;
+ this.endAllDay = endString != null && endString.length() == 8;
+
+ this.location = location;
+ this.organizer = organizer;
+ this.attendees = attendees;
+ this.description = description;
+ this.latitude = latitude;
+ this.longitude = longitude;
+ }
+
+ /**
+ * Parses a string as a date. RFC 2445 allows the start and end fields to be of type DATE (e.g. 20081021)
+ * or DATE-TIME (e.g. 20081021T123000 for local time, or 20081021T123000Z for UTC).
+ *
+ * @param when The string to parse
+ * @throws ParseException if not able to parse as a date
+ */
+ private static Date parseDate(String when) throws ParseException {
+ if (!DATE_TIME.matcher(when).matches()) {
+ throw new ParseException(when, 0);
+ }
+ if (when.length() == 8) {
+ // Show only year/month/day
+ return buildDateFormat().parse(when);
+ } else {
+ // The when string can be local time, or UTC if it ends with a Z
+ Date date;
+ if (when.length() == 16 && when.charAt(15) == 'Z') {
+ date = buildDateTimeFormat().parse(when.substring(0, 15));
+ Calendar calendar = new GregorianCalendar();
+ long milliseconds = date.getTime();
+ // Account for time zone difference
+ milliseconds += calendar.get(Calendar.ZONE_OFFSET);
+ // Might need to correct for daylight savings time, but use target time since
+ // now might be in DST but not then, or vice versa
+ calendar.setTime(new Date(milliseconds));
+ milliseconds += calendar.get(Calendar.DST_OFFSET);
+ date = new Date(milliseconds);
+ } else {
+ date = buildDateTimeFormat().parse(when);
+ }
+ return date;
+ }
+ }
+
+ private static String format(boolean allDay, Date date) {
+ if (date == null) {
+ return null;
+ }
+ DateFormat format = allDay
+ ? DateFormat.getDateInstance(DateFormat.MEDIUM)
+ : DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
+ return format.format(date);
+ }
+
+ private static long parseDurationMS(CharSequence durationString) {
+ if (durationString == null) {
+ return -1L;
+ }
+ Matcher m = RFC2445_DURATION.matcher(durationString);
+ if (!m.matches()) {
+ return -1L;
+ }
+ long durationMS = 0L;
+ for (int i = 0; i < RFC2445_DURATION_FIELD_UNITS.length; i++) {
+ String fieldValue = m.group(i + 1);
+ if (fieldValue != null) {
+ durationMS += RFC2445_DURATION_FIELD_UNITS[i] * Integer.parseInt(fieldValue);
+ }
+ }
+ return durationMS;
+ }
+
+ private static DateFormat buildDateFormat() {
+ DateFormat format = new SimpleDateFormat("yyyyMMdd", Locale.ENGLISH);
+ // For dates without a time, for purposes of interacting with Android, the resulting timestamp
+ // needs to be midnight of that day in GMT. See:
+ // http://code.google.com/p/android/issues/detail?id=8330
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ return format;
+ }
+
+ private static DateFormat buildDateTimeFormat() {
+ return new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.ENGLISH);
+ }
+
+ public String getSummary() {
+ return summary;
+ }
+
+ /**
+ * @return start time
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * @return true if start time was specified as a whole day
+ */
+ public boolean isStartAllDay() {
+ return startAllDay;
+ }
+
+ /**
+ * @return event end {@link Date}, or {@code null} if event has no duration
+ * @see #getStart()
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ /**
+ * @return true if end time was specified as a whole day
+ */
+ public boolean isEndAllDay() {
+ return endAllDay;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public String getOrganizer() {
+ return organizer;
+ }
+
+ public String[] getAttendees() {
+ return attendees;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public double getLatitude() {
+ return latitude;
+ }
+
+ public double getLongitude() {
+ return longitude;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(100);
+ maybeAppend(summary, result);
+ maybeAppend(format(startAllDay, start), result);
+ maybeAppend(format(endAllDay, end), result);
+ maybeAppend(location, result);
+ maybeAppend(organizer, result);
+ maybeAppend(attendees, result);
+ maybeAppend(description, result);
+ return result.toString();
+ }
+
+}
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..793b606
--- /dev/null
+++ b/src/com/google/zxing/client/result/EmailAddressParsedResult.java
@@ -0,0 +1,96 @@
+/*
+ * 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[] tos;
+ private final String[] ccs;
+ private final String[] bccs;
+ private final String subject;
+ private final String body;
+
+ EmailAddressParsedResult(String to) {
+ this(new String[]{to}, null, null, null, null);
+ }
+
+ EmailAddressParsedResult(String[] tos,
+ String[] ccs,
+ String[] bccs,
+ String subject,
+ String body) {
+ super(ParsedResultType.EMAIL_ADDRESS);
+ this.tos = tos;
+ this.ccs = ccs;
+ this.bccs = bccs;
+ this.subject = subject;
+ this.body = body;
+ }
+
+ /**
+ * @return first elements of {@link #getTos()} or {@code null} if none
+ * @deprecated use {@link #getTos()}
+ */
+ @Deprecated
+ public String getEmailAddress() {
+ return tos == null || tos.length == 0 ? null : tos[0];
+ }
+
+ public String[] getTos() {
+ return tos;
+ }
+
+ public String[] getCCs() {
+ return ccs;
+ }
+
+ public String[] getBCCs() {
+ return bccs;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ /**
+ * @return "mailto:"
+ * @deprecated without replacement
+ */
+ @Deprecated
+ public String getMailtoURI() {
+ return "mailto:";
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(30);
+ maybeAppend(tos, result);
+ maybeAppend(ccs, result);
+ maybeAppend(bccs, 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..3d00d6c
--- /dev/null
+++ b/src/com/google/zxing/client/result/EmailAddressResultParser.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.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Represents a result that encodes an e-mail address, either as a plain address
+ * like "joe@example.org" or a mailto: URL like "mailto:joe@example.org".
+ *
+ * @author Sean Owen
+ */
+public final class EmailAddressResultParser extends ResultParser {
+
+ private static final Pattern COMMA = Pattern.compile(",");
+
+ @Override
+ public EmailAddressParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (rawText.startsWith("mailto:") || rawText.startsWith("MAILTO:")) {
+ // If it starts with mailto:, assume it is definitely trying to be an email address
+ String hostEmail = rawText.substring(7);
+ int queryStart = hostEmail.indexOf('?');
+ if (queryStart >= 0) {
+ hostEmail = hostEmail.substring(0, queryStart);
+ }
+ hostEmail = urlDecode(hostEmail);
+ String[] tos = null;
+ if (!hostEmail.isEmpty()) {
+ tos = COMMA.split(hostEmail);
+ }
+ Map nameValues = parseNameValuePairs(rawText);
+ String[] ccs = null;
+ String[] bccs = null;
+ String subject = null;
+ String body = null;
+ if (nameValues != null) {
+ if (tos == null) {
+ String tosString = nameValues.get("to");
+ if (tosString != null) {
+ tos = COMMA.split(tosString);
+ }
+ }
+ String ccString = nameValues.get("cc");
+ if (ccString != null) {
+ ccs = COMMA.split(ccString);
+ }
+ String bccString = nameValues.get("bcc");
+ if (bccString != null) {
+ bccs = COMMA.split(bccString);
+ }
+ subject = nameValues.get("subject");
+ body = nameValues.get("body");
+ }
+ return new EmailAddressParsedResult(tos, ccs, bccs, subject, body);
+ } else {
+ if (!EmailDoCoMoResultParser.isBasicallyValidEmailAddress(rawText)) {
+ return null;
+ }
+ return new EmailAddressParsedResult(rawText);
+ }
+ }
+
+}
\ 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..2797d66
--- /dev/null
+++ b/src/com/google/zxing/client/result/EmailDoCoMoResultParser.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.regex.Pattern;
+
+/**
+ * Implements the "MATMSG" email message entry format.
+ *
+ * Supported keys: TO, SUB, BODY
+ *
+ * @author Sean Owen
+ */
+public final class EmailDoCoMoResultParser extends AbstractDoCoMoResultParser {
+
+ private static final Pattern ATEXT_ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9@.!#$%&'*+\\-/=?^_`{|}~]+");
+
+ /**
+ * This implements only the most basic checking for an email address's validity -- that it contains
+ * an '@' and contains no characters disallowed by RFC 2822. This is an overly lenient definition of
+ * validity. We want to generally be lenient here since this class is only intended to encapsulate what's
+ * in a barcode, not "judge" it.
+ */
+ static boolean isBasicallyValidEmailAddress(String email) {
+ return email != null && ATEXT_ALPHANUMERIC.matcher(email).matches() && email.indexOf('@') >= 0;
+ }
+
+ @Override
+ public EmailAddressParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("MATMSG:")) {
+ return null;
+ }
+ String[] tos = matchDoCoMoPrefixedField("TO:", rawText, true);
+ if (tos == null) {
+ return null;
+ }
+ for (String to : tos) {
+ if (!isBasicallyValidEmailAddress(to)) {
+ return null;
+ }
+ }
+ String subject = matchSingleDoCoMoPrefixedField("SUB:", rawText, false);
+ String body = matchSingleDoCoMoPrefixedField("BODY:", rawText, false);
+ return new EmailAddressParsedResult(tos, null, null, subject, body);
+ }
+
+}
\ 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..d496480
--- /dev/null
+++ b/src/com/google/zxing/client/result/ExpandedProductParsedResult.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.client.result;
+
+import java.util.Map;
+
+/**
+ * @author Antonio Manuel Benjumea Conde, Servinform, S.A.
+ * @author AgustÃn Delgado, Servinform, S.A.
+ */
+public final class ExpandedProductParsedResult extends ParsedResult {
+
+ public static final String KILOGRAM = "KG";
+ public static final String POUND = "LB";
+
+ private final String rawText;
+ private final String productID;
+ private final String sscc;
+ private final String lotNumber;
+ private final String productionDate;
+ private final String packagingDate;
+ private final String bestBeforeDate;
+ private final String expirationDate;
+ private final String weight;
+ private final String weightType;
+ private final String weightIncrement;
+ private final String price;
+ private final String priceIncrement;
+ private final String priceCurrency;
+ // For AIS that not exist in this object
+ private final Map uncommonAIs;
+
+ public ExpandedProductParsedResult(String rawText,
+ String productID,
+ String sscc,
+ String lotNumber,
+ String productionDate,
+ String packagingDate,
+ String bestBeforeDate,
+ String expirationDate,
+ String weight,
+ String weightType,
+ String weightIncrement,
+ String price,
+ String priceIncrement,
+ String priceCurrency,
+ Map uncommonAIs) {
+ super(ParsedResultType.PRODUCT);
+ this.rawText = rawText;
+ this.productID = productID;
+ this.sscc = sscc;
+ this.lotNumber = lotNumber;
+ this.productionDate = productionDate;
+ this.packagingDate = packagingDate;
+ this.bestBeforeDate = bestBeforeDate;
+ this.expirationDate = expirationDate;
+ this.weight = weight;
+ this.weightType = weightType;
+ this.weightIncrement = weightIncrement;
+ this.price = price;
+ this.priceIncrement = priceIncrement;
+ this.priceCurrency = priceCurrency;
+ this.uncommonAIs = uncommonAIs;
+ }
+
+ private static boolean equalsOrNull(Object o1, Object o2) {
+ return o1 == null ? o2 == null : o1.equals(o2);
+ }
+
+ private static int hashNotNull(Object o) {
+ return o == null ? 0 : o.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ExpandedProductParsedResult)) {
+ return false;
+ }
+
+ ExpandedProductParsedResult other = (ExpandedProductParsedResult) o;
+
+ return equalsOrNull(productID, other.productID)
+ && equalsOrNull(sscc, other.sscc)
+ && equalsOrNull(lotNumber, other.lotNumber)
+ && equalsOrNull(productionDate, other.productionDate)
+ && equalsOrNull(bestBeforeDate, other.bestBeforeDate)
+ && equalsOrNull(expirationDate, other.expirationDate)
+ && equalsOrNull(weight, other.weight)
+ && equalsOrNull(weightType, other.weightType)
+ && equalsOrNull(weightIncrement, other.weightIncrement)
+ && equalsOrNull(price, other.price)
+ && equalsOrNull(priceIncrement, other.priceIncrement)
+ && equalsOrNull(priceCurrency, other.priceCurrency)
+ && equalsOrNull(uncommonAIs, other.uncommonAIs);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash ^= hashNotNull(productID);
+ hash ^= hashNotNull(sscc);
+ hash ^= hashNotNull(lotNumber);
+ hash ^= hashNotNull(productionDate);
+ hash ^= hashNotNull(bestBeforeDate);
+ hash ^= hashNotNull(expirationDate);
+ hash ^= hashNotNull(weight);
+ hash ^= hashNotNull(weightType);
+ hash ^= hashNotNull(weightIncrement);
+ hash ^= hashNotNull(price);
+ hash ^= hashNotNull(priceIncrement);
+ hash ^= hashNotNull(priceCurrency);
+ hash ^= hashNotNull(uncommonAIs);
+ return hash;
+ }
+
+ public String getRawText() {
+ return rawText;
+ }
+
+ public String getProductID() {
+ return productID;
+ }
+
+ public String getSscc() {
+ return sscc;
+ }
+
+ public String getLotNumber() {
+ return lotNumber;
+ }
+
+ public String getProductionDate() {
+ return productionDate;
+ }
+
+ public String getPackagingDate() {
+ return packagingDate;
+ }
+
+ public String getBestBeforeDate() {
+ return bestBeforeDate;
+ }
+
+ public String getExpirationDate() {
+ return expirationDate;
+ }
+
+ public String getWeight() {
+ return weight;
+ }
+
+ public String getWeightType() {
+ return weightType;
+ }
+
+ public String getWeightIncrement() {
+ return weightIncrement;
+ }
+
+ public String getPrice() {
+ return price;
+ }
+
+ public String getPriceIncrement() {
+ return priceIncrement;
+ }
+
+ public String getPriceCurrency() {
+ return priceCurrency;
+ }
+
+ public Map getUncommonAIs() {
+ return uncommonAIs;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ return String.valueOf(rawText);
+ }
+}
diff --git a/src/com/google/zxing/client/result/ExpandedProductResultParser.java b/src/com/google/zxing/client/result/ExpandedProductResultParser.java
new file mode 100644
index 0000000..6866ccf
--- /dev/null
+++ b/src/com/google/zxing/client/result/ExpandedProductResultParser.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Parses strings of digits that represent a RSS Extended code.
+ *
+ * @author Antonio Manuel Benjumea Conde, Servinform, S.A.
+ * @author AgustÃn Delgado, Servinform, S.A.
+ */
+public final class ExpandedProductResultParser extends ResultParser {
+
+ private static String findAIvalue(int i, String rawText) {
+ char c = rawText.charAt(i);
+ // First character must be a open parenthesis.If not, ERROR
+ if (c != '(') {
+ return null;
+ }
+
+ CharSequence rawTextAux = rawText.substring(i + 1);
+
+ StringBuilder buf = new StringBuilder();
+ for (int index = 0; index < rawTextAux.length(); index++) {
+ char currentChar = rawTextAux.charAt(index);
+ if (currentChar == ')') {
+ return buf.toString();
+ } else if (currentChar >= '0' && currentChar <= '9') {
+ buf.append(currentChar);
+ } else {
+ return null;
+ }
+ }
+ return buf.toString();
+ }
+
+ private static String findValue(int i, String rawText) {
+ StringBuilder buf = new StringBuilder();
+ String rawTextAux = rawText.substring(i);
+
+ for (int index = 0; index < rawTextAux.length(); index++) {
+ char c = rawTextAux.charAt(index);
+ if (c == '(') {
+ // We look for a new AI. If it doesn't exist (ERROR), we coninue
+ // with the iteration
+ if (findAIvalue(index, rawTextAux) == null) {
+ buf.append('(');
+ } else {
+ break;
+ }
+ } else {
+ buf.append(c);
+ }
+ }
+ return buf.toString();
+ }
+
+ @Override
+ public ExpandedProductParsedResult parse(Result result) {
+ BarcodeFormat format = result.getBarcodeFormat();
+ if (format != BarcodeFormat.RSS_EXPANDED) {
+ // ExtendedProductParsedResult NOT created. Not a RSS Expanded barcode
+ return null;
+ }
+ String rawText = getMassagedText(result);
+
+ String productID = null;
+ String sscc = null;
+ String lotNumber = null;
+ String productionDate = null;
+ String packagingDate = null;
+ String bestBeforeDate = null;
+ String expirationDate = null;
+ String weight = null;
+ String weightType = null;
+ String weightIncrement = null;
+ String price = null;
+ String priceIncrement = null;
+ String priceCurrency = null;
+ Map uncommonAIs = new HashMap<>();
+
+ int i = 0;
+
+ while (i < rawText.length()) {
+ String ai = findAIvalue(i, rawText);
+ if (ai == null) {
+ // Error. Code doesn't match with RSS expanded pattern
+ // ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern
+ return null;
+ }
+ i += ai.length() + 2;
+ String value = findValue(i, rawText);
+ i += value.length();
+
+ switch (ai) {
+ case "00":
+ sscc = value;
+ break;
+ case "01":
+ productID = value;
+ break;
+ case "10":
+ lotNumber = value;
+ break;
+ case "11":
+ productionDate = value;
+ break;
+ case "13":
+ packagingDate = value;
+ break;
+ case "15":
+ bestBeforeDate = value;
+ break;
+ case "17":
+ expirationDate = value;
+ break;
+ case "3100":
+ case "3101":
+ case "3102":
+ case "3103":
+ case "3104":
+ case "3105":
+ case "3106":
+ case "3107":
+ case "3108":
+ case "3109":
+ weight = value;
+ weightType = ExpandedProductParsedResult.KILOGRAM;
+ weightIncrement = ai.substring(3);
+ break;
+ case "3200":
+ case "3201":
+ case "3202":
+ case "3203":
+ case "3204":
+ case "3205":
+ case "3206":
+ case "3207":
+ case "3208":
+ case "3209":
+ weight = value;
+ weightType = ExpandedProductParsedResult.POUND;
+ weightIncrement = ai.substring(3);
+ break;
+ case "3920":
+ case "3921":
+ case "3922":
+ case "3923":
+ price = value;
+ priceIncrement = ai.substring(3);
+ break;
+ case "3930":
+ case "3931":
+ case "3932":
+ case "3933":
+ if (value.length() < 4) {
+ // The value must have more of 3 symbols (3 for currency and
+ // 1 at least for the price)
+ // ExtendedProductParsedResult NOT created. Not match with RSS Expanded pattern
+ return null;
+ }
+ price = value.substring(3);
+ priceCurrency = value.substring(0, 3);
+ priceIncrement = ai.substring(3);
+ break;
+ default:
+ // No match with common AIs
+ uncommonAIs.put(ai, value);
+ break;
+ }
+ }
+
+ return new ExpandedProductParsedResult(rawText,
+ productID,
+ sscc,
+ lotNumber,
+ productionDate,
+ packagingDate,
+ bestBeforeDate,
+ expirationDate,
+ weight,
+ weightType,
+ weightIncrement,
+ price,
+ priceIncrement,
+ priceCurrency,
+ uncommonAIs);
+ }
+}
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..d4af105
--- /dev/null
+++ b/src/com/google/zxing/client/result/GeoParsedResult.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class GeoParsedResult extends ParsedResult {
+
+ private final double latitude;
+ private final double longitude;
+ private final double altitude;
+ private final String query;
+
+ GeoParsedResult(double latitude, double longitude, double altitude, String query) {
+ super(ParsedResultType.GEO);
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.altitude = altitude;
+ this.query = query;
+ }
+
+ public String getGeoURI() {
+ StringBuilder result = new StringBuilder();
+ result.append("geo:");
+ result.append(latitude);
+ result.append(',');
+ result.append(longitude);
+ if (altitude > 0) {
+ result.append(',');
+ result.append(altitude);
+ }
+ if (query != null) {
+ result.append('?');
+ result.append(query);
+ }
+ return result.toString();
+ }
+
+ /**
+ * @return latitude in degrees
+ */
+ public double getLatitude() {
+ return latitude;
+ }
+
+ /**
+ * @return longitude in degrees
+ */
+ public double getLongitude() {
+ return longitude;
+ }
+
+ /**
+ * @return altitude in meters. If not specified, in the geo URI, returns 0.0
+ */
+ public double getAltitude() {
+ return altitude;
+ }
+
+ /**
+ * @return query string associated with geo URI or null if none exists
+ */
+ public String getQuery() {
+ return query;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(20);
+ result.append(latitude);
+ result.append(", ");
+ result.append(longitude);
+ if (altitude > 0.0) {
+ result.append(", ");
+ result.append(altitude);
+ result.append('m');
+ }
+ if (query != null) {
+ result.append(" (");
+ result.append(query);
+ result.append(')');
+ }
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/GeoResultParser.java b/src/com/google/zxing/client/result/GeoResultParser.java
new file mode 100644
index 0000000..bbd8761
--- /dev/null
+++ b/src/com/google/zxing/client/result/GeoResultParser.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses a "geo:" URI result, which specifies a location on the surface of
+ * the Earth as well as an optional altitude above the surface. See
+ *
+ * http://tools.ietf.org/html/draft-mayrhofer-geo-uri-00.
+ *
+ * @author Sean Owen
+ */
+public final class GeoResultParser extends ResultParser {
+
+ private static final Pattern GEO_URL_PATTERN =
+ Pattern.compile("geo:([\\-0-9.]+),([\\-0-9.]+)(?:,([\\-0-9.]+))?(?:\\?(.*))?", Pattern.CASE_INSENSITIVE);
+
+ @Override
+ public GeoParsedResult parse(Result result) {
+ CharSequence rawText = getMassagedText(result);
+ Matcher matcher = GEO_URL_PATTERN.matcher(rawText);
+ if (!matcher.matches()) {
+ return null;
+ }
+
+ String query = matcher.group(4);
+
+ double latitude;
+ double longitude;
+ double altitude;
+ try {
+ latitude = Double.parseDouble(matcher.group(1));
+ if (latitude > 90.0 || latitude < -90.0) {
+ return null;
+ }
+ longitude = Double.parseDouble(matcher.group(2));
+ if (longitude > 180.0 || longitude < -180.0) {
+ return null;
+ }
+ if (matcher.group(3) == null) {
+ altitude = 0.0;
+ } else {
+ altitude = Double.parseDouble(matcher.group(3));
+ if (altitude < 0.0) {
+ return null;
+ }
+ }
+ } catch (NumberFormatException ignored) {
+ return null;
+ }
+ return new GeoParsedResult(latitude, longitude, altitude, query);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/ISBNParsedResult.java b/src/com/google/zxing/client/result/ISBNParsedResult.java
new file mode 100644
index 0000000..b864e27
--- /dev/null
+++ b/src/com/google/zxing/client/result/ISBNParsedResult.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * @author jbreiden@google.com (Jeff Breidenbach)
+ */
+public final class ISBNParsedResult extends ParsedResult {
+
+ private final String isbn;
+
+ ISBNParsedResult(String isbn) {
+ super(ParsedResultType.ISBN);
+ this.isbn = isbn;
+ }
+
+ public String getISBN() {
+ return isbn;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ return isbn;
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/ISBNResultParser.java b/src/com/google/zxing/client/result/ISBNResultParser.java
new file mode 100644
index 0000000..97e8fb2
--- /dev/null
+++ b/src/com/google/zxing/client/result/ISBNResultParser.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+
+/**
+ * Parses strings of digits that represent a ISBN.
+ *
+ * @author jbreiden@google.com (Jeff Breidenbach)
+ */
+public final class ISBNResultParser extends ResultParser {
+
+ /**
+ * See ISBN-13 For Dummies
+ */
+ @Override
+ public ISBNParsedResult parse(Result result) {
+ BarcodeFormat format = result.getBarcodeFormat();
+ if (format != BarcodeFormat.EAN_13) {
+ return null;
+ }
+ String rawText = getMassagedText(result);
+ int length = rawText.length();
+ if (length != 13) {
+ return null;
+ }
+ if (!rawText.startsWith("978") && !rawText.startsWith("979")) {
+ return null;
+ }
+
+ return new ISBNParsedResult(rawText);
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/ParsedResult.java b/src/com/google/zxing/client/result/ParsedResult.java
new file mode 100644
index 0000000..75e05c6
--- /dev/null
+++ b/src/com/google/zxing/client/result/ParsedResult.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * Abstract class representing the result of decoding a barcode, as more than
+ * a String -- as some type of structured data. This might be a subclass which represents
+ * a URL, or an e-mail address. {@link ResultParser#parseResult(com.google.zxing.Result)} will turn a raw
+ * decoded string into the most appropriate type of structured representation.
+ *
+ *
Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
+ * on exception-based mechanisms during parsing.
+ *
+ * @author Sean Owen
+ */
+public abstract class ParsedResult {
+
+ private final ParsedResultType type;
+
+ protected ParsedResult(ParsedResultType type) {
+ this.type = type;
+ }
+
+ public static void maybeAppend(String value, StringBuilder result) {
+ if (value != null && !value.isEmpty()) {
+ // Don't add a newline before the first value
+ if (result.length() > 0) {
+ result.append('\n');
+ }
+ result.append(value);
+ }
+ }
+
+ public static void maybeAppend(String[] values, StringBuilder result) {
+ if (values != null) {
+ for (String value : values) {
+ maybeAppend(value, result);
+ }
+ }
+ }
+
+ public final ParsedResultType getType() {
+ return type;
+ }
+
+ public abstract String getDisplayResult();
+
+ @Override
+ public final String toString() {
+ return getDisplayResult();
+ }
+
+}
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..cfbfcee
--- /dev/null
+++ b/src/com/google/zxing/client/result/ParsedResultType.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * Represents the type of data encoded by a barcode -- from plain text, to a
+ * URI, to an e-mail address, etc.
+ *
+ * @author Sean Owen
+ */
+public enum ParsedResultType {
+
+ ADDRESSBOOK,
+ EMAIL_ADDRESS,
+ PRODUCT,
+ URI,
+ TEXT,
+ GEO,
+ TEL,
+ SMS,
+ CALENDAR,
+ WIFI,
+ ISBN,
+ VIN,
+
+}
diff --git a/src/com/google/zxing/client/result/ProductParsedResult.java b/src/com/google/zxing/client/result/ProductParsedResult.java
new file mode 100644
index 0000000..2f9dd04
--- /dev/null
+++ b/src/com/google/zxing/client/result/ProductParsedResult.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class ProductParsedResult extends ParsedResult {
+
+ private final String productID;
+ private final String normalizedProductID;
+
+ ProductParsedResult(String productID) {
+ this(productID, productID);
+ }
+
+ ProductParsedResult(String productID, String normalizedProductID) {
+ super(ParsedResultType.PRODUCT);
+ this.productID = productID;
+ this.normalizedProductID = normalizedProductID;
+ }
+
+ public String getProductID() {
+ return productID;
+ }
+
+ public String getNormalizedProductID() {
+ return normalizedProductID;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ return productID;
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/ProductResultParser.java b/src/com/google/zxing/client/result/ProductResultParser.java
new file mode 100644
index 0000000..6ab988f
--- /dev/null
+++ b/src/com/google/zxing/client/result/ProductResultParser.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+import com.google.zxing.oned.UPCEReader;
+
+/**
+ * Parses strings of digits that represent a UPC code.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class ProductResultParser extends ResultParser {
+
+ // Treat all UPC and EAN variants as UPCs, in the sense that they are all product barcodes.
+ @Override
+ public ProductParsedResult parse(Result result) {
+ BarcodeFormat format = result.getBarcodeFormat();
+ if (!(format == BarcodeFormat.UPC_A || format == BarcodeFormat.UPC_E ||
+ format == BarcodeFormat.EAN_8 || format == BarcodeFormat.EAN_13)) {
+ return null;
+ }
+ String rawText = getMassagedText(result);
+ if (!isStringOfDigits(rawText, rawText.length())) {
+ return null;
+ }
+ // Not actually checking the checksum again here
+
+ String normalizedProductID;
+ // Expand UPC-E for purposes of searching
+ if (format == BarcodeFormat.UPC_E && rawText.length() == 8) {
+ normalizedProductID = UPCEReader.convertUPCEtoUPCA(rawText);
+ } else {
+ normalizedProductID = rawText;
+ }
+
+ return new ProductParsedResult(rawText, normalizedProductID);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/ResultParser.java b/src/com/google/zxing/client/result/ResultParser.java
new file mode 100644
index 0000000..9057fe8
--- /dev/null
+++ b/src/com/google/zxing/client/result/ResultParser.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Abstract class representing the result of decoding a barcode, as more than
+ * a String -- as some type of structured data. This might be a subclass which represents
+ * a URL, or an e-mail address. {@link #parseResult(Result)} will turn a raw
+ * decoded string into the most appropriate type of structured representation.
+ *
+ *
Thanks to Jeff Griffin for proposing rewrite of these classes that relies less
+ * on exception-based mechanisms during parsing.
+ *
+ * @author Sean Owen
+ */
+public abstract class ResultParser {
+
+ private static final ResultParser[] PARSERS = {
+ new BookmarkDoCoMoResultParser(),
+ new AddressBookDoCoMoResultParser(),
+ new EmailDoCoMoResultParser(),
+ new AddressBookAUResultParser(),
+ new VCardResultParser(),
+ new BizcardResultParser(),
+ new VEventResultParser(),
+ new EmailAddressResultParser(),
+ new SMTPResultParser(),
+ new TelResultParser(),
+ new SMSMMSResultParser(),
+ new SMSTOMMSTOResultParser(),
+ new GeoResultParser(),
+ new WifiResultParser(),
+ new URLTOResultParser(),
+ new URIResultParser(),
+ new ISBNResultParser(),
+ new ProductResultParser(),
+ new ExpandedProductResultParser(),
+ new VINResultParser(),
+ };
+
+ private static final Pattern DIGITS = Pattern.compile("\\d+");
+ private static final Pattern AMPERSAND = Pattern.compile("&");
+ private static final Pattern EQUALS = Pattern.compile("=");
+ private static final String BYTE_ORDER_MARK = "\ufeff";
+
+ protected static String getMassagedText(Result result) {
+ String text = result.getText();
+ if (text.startsWith(BYTE_ORDER_MARK)) {
+ text = text.substring(1);
+ }
+ return text;
+ }
+
+ public static ParsedResult parseResult(Result theResult) {
+ for (ResultParser parser : PARSERS) {
+ ParsedResult result = parser.parse(theResult);
+ if (result != null) {
+ return result;
+ }
+ }
+ return new TextParsedResult(theResult.getText(), null);
+ }
+
+ protected static void maybeAppend(String value, StringBuilder result) {
+ if (value != null) {
+ result.append('\n');
+ result.append(value);
+ }
+ }
+
+ protected static void maybeAppend(String[] value, StringBuilder result) {
+ if (value != null) {
+ for (String s : value) {
+ result.append('\n');
+ result.append(s);
+ }
+ }
+ }
+
+ protected static String[] maybeWrap(String value) {
+ return value == null ? null : new String[]{value};
+ }
+
+ protected static String unescapeBackslash(String escaped) {
+ int backslash = escaped.indexOf('\\');
+ if (backslash < 0) {
+ return escaped;
+ }
+ int max = escaped.length();
+ StringBuilder unescaped = new StringBuilder(max - 1);
+ unescaped.append(escaped.toCharArray(), 0, backslash);
+ boolean nextIsEscaped = false;
+ for (int i = backslash; i < max; i++) {
+ char c = escaped.charAt(i);
+ if (nextIsEscaped || c != '\\') {
+ unescaped.append(c);
+ nextIsEscaped = false;
+ } else {
+ nextIsEscaped = true;
+ }
+ }
+ return unescaped.toString();
+ }
+
+ protected static int parseHexDigit(char c) {
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ }
+ if (c >= 'a' && c <= 'f') {
+ return 10 + (c - 'a');
+ }
+ if (c >= 'A' && c <= 'F') {
+ return 10 + (c - 'A');
+ }
+ return -1;
+ }
+
+ protected static boolean isStringOfDigits(CharSequence value, int length) {
+ return value != null && length > 0 && length == value.length() && DIGITS.matcher(value).matches();
+ }
+
+ protected static boolean isSubstringOfDigits(CharSequence value, int offset, int length) {
+ if (value == null || length <= 0) {
+ return false;
+ }
+ int max = offset + length;
+ return value.length() >= max && DIGITS.matcher(value.subSequence(offset, max)).matches();
+ }
+
+ static Map parseNameValuePairs(String uri) {
+ int paramStart = uri.indexOf('?');
+ if (paramStart < 0) {
+ return null;
+ }
+ Map result = new HashMap<>(3);
+ for (String keyValue : AMPERSAND.split(uri.substring(paramStart + 1))) {
+ appendKeyValue(keyValue, result);
+ }
+ return result;
+ }
+
+ private static void appendKeyValue(CharSequence keyValue, Map result) {
+ String[] keyValueTokens = EQUALS.split(keyValue, 2);
+ if (keyValueTokens.length == 2) {
+ String key = keyValueTokens[0];
+ String value = keyValueTokens[1];
+ try {
+ value = urlDecode(value);
+ result.put(key, value);
+ } catch (IllegalArgumentException iae) {
+ // continue; invalid data such as an escape like %0t
+ }
+ }
+ }
+
+ static String urlDecode(String encoded) {
+ try {
+ return URLDecoder.decode(encoded, "UTF-8");
+ } catch (UnsupportedEncodingException uee) {
+ throw new IllegalStateException(uee); // can't happen
+ }
+ }
+
+ static String[] matchPrefixedField(String prefix, String rawText, char endChar, boolean trim) {
+ List matches = null;
+ int i = 0;
+ int max = rawText.length();
+ while (i < max) {
+ i = rawText.indexOf(prefix, i);
+ if (i < 0) {
+ break;
+ }
+ i += prefix.length(); // Skip past this prefix we found to start
+ int start = i; // Found the start of a match here
+ boolean more = true;
+ while (more) {
+ i = rawText.indexOf(endChar, i);
+ if (i < 0) {
+ // No terminating end character? uh, done. Set i such that loop terminates and break
+ i = rawText.length();
+ more = false;
+ } else if (countPrecedingBackslashes(rawText, i) % 2 != 0) {
+ // semicolon was escaped (odd count of preceding backslashes) so continue
+ i++;
+ } else {
+ // found a match
+ if (matches == null) {
+ matches = new ArrayList<>(3); // lazy init
+ }
+ String element = unescapeBackslash(rawText.substring(start, i));
+ if (trim) {
+ element = element.trim();
+ }
+ if (!element.isEmpty()) {
+ matches.add(element);
+ }
+ i++;
+ more = false;
+ }
+ }
+ }
+ if (matches == null || matches.isEmpty()) {
+ return null;
+ }
+ return matches.toArray(new String[matches.size()]);
+ }
+
+ private static int countPrecedingBackslashes(CharSequence s, int pos) {
+ int count = 0;
+ for (int i = pos - 1; i >= 0; i--) {
+ if (s.charAt(i) == '\\') {
+ count++;
+ } else {
+ break;
+ }
+ }
+ return count;
+ }
+
+ static String matchSinglePrefixedField(String prefix, String rawText, char endChar, boolean trim) {
+ String[] matches = matchPrefixedField(prefix, rawText, endChar, trim);
+ return matches == null ? null : matches[0];
+ }
+
+ /**
+ * Attempts to parse the raw {@link Result}'s contents as a particular type
+ * of information (email, URL, etc.) and return a {@link ParsedResult} encapsulating
+ * the result of parsing.
+ *
+ * @param theResult the raw {@link Result} to parse
+ * @return {@link ParsedResult} encapsulating the parsing result
+ */
+ public abstract ParsedResult parse(Result theResult);
+
+}
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..178824a
--- /dev/null
+++ b/src/com/google/zxing/client/result/SMSMMSResultParser.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Parses an "sms:" URI result, which specifies a number to SMS.
+ * See RFC 5724 on this.
+ *
+ *
This class supports "via" syntax for numbers, which is not part of the spec.
+ * For example "+12125551212;via=+12124440101" may appear as a number.
+ * It also supports a "subject" query parameter, which is not mentioned in the spec.
+ * These are included since they were mentioned in earlier IETF drafts and might be
+ * used.
+ *
+ *
This actually also parses URIs starting with "mms:" and treats them all the same way,
+ * and effectively converts them to an "sms:" URI for purposes of forwarding to the platform.
+ *
+ * @author Sean Owen
+ */
+public final class SMSMMSResultParser extends ResultParser {
+
+ private static void addNumberVia(Collection numbers,
+ Collection vias,
+ String numberPart) {
+ int numberEnd = numberPart.indexOf(';');
+ if (numberEnd < 0) {
+ numbers.add(numberPart);
+ vias.add(null);
+ } else {
+ numbers.add(numberPart.substring(0, numberEnd));
+ String maybeVia = numberPart.substring(numberEnd + 1);
+ String via;
+ if (maybeVia.startsWith("via=")) {
+ via = maybeVia.substring(4);
+ } else {
+ via = null;
+ }
+ vias.add(via);
+ }
+ }
+
+ @Override
+ public SMSParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!(rawText.startsWith("sms:") || rawText.startsWith("SMS:") ||
+ rawText.startsWith("mms:") || rawText.startsWith("MMS:"))) {
+ return null;
+ }
+
+ // Check up front if this is a URI syntax string with query arguments
+ Map nameValuePairs = parseNameValuePairs(rawText);
+ String subject = null;
+ String body = null;
+ boolean querySyntax = false;
+ if (nameValuePairs != null && !nameValuePairs.isEmpty()) {
+ subject = nameValuePairs.get("subject");
+ body = nameValuePairs.get("body");
+ querySyntax = true;
+ }
+
+ // Drop sms, query portion
+ int queryStart = rawText.indexOf('?', 4);
+ String smsURIWithoutQuery;
+ // If it's not query syntax, the question mark is part of the subject or message
+ if (queryStart < 0 || !querySyntax) {
+ smsURIWithoutQuery = rawText.substring(4);
+ } else {
+ smsURIWithoutQuery = rawText.substring(4, queryStart);
+ }
+
+ int lastComma = -1;
+ int comma;
+ List numbers = new ArrayList<>(1);
+ List vias = new ArrayList<>(1);
+ while ((comma = smsURIWithoutQuery.indexOf(',', lastComma + 1)) > lastComma) {
+ String numberPart = smsURIWithoutQuery.substring(lastComma + 1, comma);
+ addNumberVia(numbers, vias, numberPart);
+ lastComma = comma;
+ }
+ addNumberVia(numbers, vias, smsURIWithoutQuery.substring(lastComma + 1));
+
+ return new SMSParsedResult(numbers.toArray(new String[numbers.size()]),
+ vias.toArray(new String[vias.size()]),
+ subject,
+ body);
+ }
+
+}
\ 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..d8649bc
--- /dev/null
+++ b/src/com/google/zxing/client/result/SMSParsedResult.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class SMSParsedResult extends ParsedResult {
+
+ private final String[] numbers;
+ private final String[] vias;
+ private final String subject;
+ private final String body;
+
+ public SMSParsedResult(String number,
+ String via,
+ String subject,
+ String body) {
+ super(ParsedResultType.SMS);
+ this.numbers = new String[]{number};
+ this.vias = new String[]{via};
+ this.subject = subject;
+ this.body = body;
+ }
+
+ public SMSParsedResult(String[] numbers,
+ String[] vias,
+ String subject,
+ String body) {
+ super(ParsedResultType.SMS);
+ this.numbers = numbers;
+ this.vias = vias;
+ this.subject = subject;
+ this.body = body;
+ }
+
+ public String getSMSURI() {
+ StringBuilder result = new StringBuilder();
+ result.append("sms:");
+ boolean first = true;
+ for (int i = 0; i < numbers.length; i++) {
+ if (first) {
+ first = false;
+ } else {
+ result.append(',');
+ }
+ result.append(numbers[i]);
+ if (vias != null && vias[i] != null) {
+ result.append(";via=");
+ result.append(vias[i]);
+ }
+ }
+ boolean hasBody = body != null;
+ boolean hasSubject = subject != null;
+ if (hasBody || hasSubject) {
+ result.append('?');
+ if (hasBody) {
+ result.append("body=");
+ result.append(body);
+ }
+ if (hasSubject) {
+ if (hasBody) {
+ result.append('&');
+ }
+ result.append("subject=");
+ result.append(subject);
+ }
+ }
+ return result.toString();
+ }
+
+ public String[] getNumbers() {
+ return numbers;
+ }
+
+ public String[] getVias() {
+ return vias;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(100);
+ maybeAppend(numbers, result);
+ maybeAppend(subject, result);
+ maybeAppend(body, result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java b/src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java
new file mode 100644
index 0000000..554c873
--- /dev/null
+++ b/src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses an "smsto:" URI result, whose format is not standardized but appears to be like:
+ * {@code smsto:number(:body)}.
+ *
+ *
This actually also parses URIs starting with "smsto:", "mmsto:", "SMSTO:", and
+ * "MMSTO:", and treats them all the same way, and effectively converts them to an "sms:" URI
+ * for purposes of forwarding to the platform.
+ *
+ * @author Sean Owen
+ */
+public final class SMSTOMMSTOResultParser extends ResultParser {
+
+ @Override
+ public SMSParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!(rawText.startsWith("smsto:") || rawText.startsWith("SMSTO:") ||
+ rawText.startsWith("mmsto:") || rawText.startsWith("MMSTO:"))) {
+ return null;
+ }
+ // Thanks to dominik.wild for suggesting this enhancement to support
+ // smsto:number:body URIs
+ String number = rawText.substring(6);
+ String body = null;
+ int bodyStart = number.indexOf(':');
+ if (bodyStart >= 0) {
+ body = number.substring(bodyStart + 1);
+ number = number.substring(0, bodyStart);
+ }
+ return new SMSParsedResult(number, null, null, body);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/SMTPResultParser.java b/src/com/google/zxing/client/result/SMTPResultParser.java
new file mode 100644
index 0000000..e247a28
--- /dev/null
+++ b/src/com/google/zxing/client/result/SMTPResultParser.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses an "smtp:" URI result, whose format is not standardized but appears to be like:
+ * {@code smtp[:subject[:body]]}.
+ *
+ * @author Sean Owen
+ */
+public final class SMTPResultParser extends ResultParser {
+
+ @Override
+ public EmailAddressParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!(rawText.startsWith("smtp:") || rawText.startsWith("SMTP:"))) {
+ return null;
+ }
+ String emailAddress = rawText.substring(5);
+ String subject = null;
+ String body = null;
+ int colon = emailAddress.indexOf(':');
+ if (colon >= 0) {
+ subject = emailAddress.substring(colon + 1);
+ emailAddress = emailAddress.substring(0, colon);
+ colon = subject.indexOf(':');
+ if (colon >= 0) {
+ body = subject.substring(colon + 1);
+ subject = subject.substring(0, colon);
+ }
+ }
+ return new EmailAddressParsedResult(new String[]{emailAddress},
+ null,
+ null,
+ subject,
+ body);
+ }
+}
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..cc8ef6f
--- /dev/null
+++ b/src/com/google/zxing/client/result/TelParsedResult.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * @author Sean Owen
+ */
+public final class TelParsedResult extends ParsedResult {
+
+ private final String number;
+ private final String telURI;
+ private final String title;
+
+ public TelParsedResult(String number, String telURI, String title) {
+ super(ParsedResultType.TEL);
+ this.number = number;
+ this.telURI = telURI;
+ this.title = title;
+ }
+
+ public String getNumber() {
+ return number;
+ }
+
+ public String getTelURI() {
+ return telURI;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(20);
+ maybeAppend(number, result);
+ maybeAppend(title, result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/TelResultParser.java b/src/com/google/zxing/client/result/TelResultParser.java
new file mode 100644
index 0000000..13e10c4
--- /dev/null
+++ b/src/com/google/zxing/client/result/TelResultParser.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses a "tel:" URI result, which specifies a phone number.
+ *
+ * @author Sean Owen
+ */
+public final class TelResultParser extends ResultParser {
+
+ @Override
+ public TelParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("tel:") && !rawText.startsWith("TEL:")) {
+ return null;
+ }
+ // Normalize "TEL:" to "tel:"
+ String telURI = rawText.startsWith("TEL:") ? "tel:" + rawText.substring(4) : rawText;
+ // Drop tel, query portion
+ int queryStart = rawText.indexOf('?', 4);
+ String number = queryStart < 0 ? rawText.substring(4) : rawText.substring(4, queryStart);
+ return new TelParsedResult(number, telURI, null);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/TextParsedResult.java b/src/com/google/zxing/client/result/TextParsedResult.java
new file mode 100644
index 0000000..9ec4715
--- /dev/null
+++ b/src/com/google/zxing/client/result/TextParsedResult.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * A simple result type encapsulating a string that has no further
+ * interpretation.
+ *
+ * @author Sean Owen
+ */
+public final class TextParsedResult extends ParsedResult {
+
+ private final String text;
+ private final String language;
+
+ public TextParsedResult(String text, String language) {
+ super(ParsedResultType.TEXT);
+ this.text = text;
+ this.language = language;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ return text;
+ }
+
+}
diff --git a/src/com/google/zxing/client/result/URIParsedResult.java b/src/com/google/zxing/client/result/URIParsedResult.java
new file mode 100644
index 0000000..54a80df
--- /dev/null
+++ b/src/com/google/zxing/client/result/URIParsedResult.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import java.util.regex.Pattern;
+
+/**
+ * @author Sean Owen
+ */
+public final class URIParsedResult extends ParsedResult {
+
+ private static final Pattern USER_IN_HOST = Pattern.compile(":/*([^/@]+)@[^/]+");
+
+ private final String uri;
+ private final String title;
+
+ public URIParsedResult(String uri, String title) {
+ super(ParsedResultType.URI);
+ this.uri = massageURI(uri);
+ this.title = title;
+ }
+
+ /**
+ * Transforms a string that represents a URI into something more proper, by adding or canonicalizing
+ * the protocol.
+ */
+ private static String massageURI(String uri) {
+ uri = uri.trim();
+ int protocolEnd = uri.indexOf(':');
+ if (protocolEnd < 0) {
+ // No protocol, assume http
+ uri = "http://" + uri;
+ } else if (isColonFollowedByPortNumber(uri, protocolEnd)) {
+ // Found a colon, but it looks like it is after the host, so the protocol is still missing
+ uri = "http://" + uri;
+ }
+ return uri;
+ }
+
+ private static boolean isColonFollowedByPortNumber(String uri, int protocolEnd) {
+ int start = protocolEnd + 1;
+ int nextSlash = uri.indexOf('/', start);
+ if (nextSlash < 0) {
+ nextSlash = uri.length();
+ }
+ return ResultParser.isSubstringOfDigits(uri, start, nextSlash - start);
+ }
+
+ public String getURI() {
+ return uri;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * @return true if the URI contains suspicious patterns that may suggest it intends to
+ * mislead the user about its true nature. At the moment this looks for the presence
+ * of user/password syntax in the host/authority portion of a URI which may be used
+ * in attempts to make the URI's host appear to be other than it is. Example:
+ * http://yourbank.com@phisher.com This URI connects to phisher.com but may appear
+ * to connect to yourbank.com at first glance.
+ */
+ public boolean isPossiblyMaliciousURI() {
+ return USER_IN_HOST.matcher(uri).find();
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(30);
+ maybeAppend(title, result);
+ maybeAppend(uri, result);
+ return result.toString();
+ }
+
+
+}
\ 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..f474c02
--- /dev/null
+++ b/src/com/google/zxing/client/result/URIResultParser.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Tries to parse results that are a URI of some kind.
+ *
+ * @author Sean Owen
+ */
+public final class URIResultParser extends ResultParser {
+
+ // See http://www.ietf.org/rfc/rfc2396.txt
+ private static final Pattern URL_WITH_PROTOCOL_PATTERN = Pattern.compile("[a-zA-Z][a-zA-Z0-9+-.]+:");
+ private static final Pattern URL_WITHOUT_PROTOCOL_PATTERN = Pattern.compile(
+ "([a-zA-Z0-9\\-]+\\.)+[a-zA-Z]{2,}" + // host name elements
+ "(:\\d{1,5})?" + // maybe port
+ "(/|\\?|$)"); // query, path or nothing
+
+ static boolean isBasicallyValidURI(String uri) {
+ if (uri.contains(" ")) {
+ // Quick hack check for a common case
+ return false;
+ }
+ Matcher m = URL_WITH_PROTOCOL_PATTERN.matcher(uri);
+ if (m.find() && m.start() == 0) { // match at start only
+ return true;
+ }
+ m = URL_WITHOUT_PROTOCOL_PATTERN.matcher(uri);
+ return m.find() && m.start() == 0;
+ }
+
+ @Override
+ public URIParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ // We specifically handle the odd "URL" scheme here for simplicity and add "URI" for fun
+ // Assume anything starting this way really means to be a URI
+ if (rawText.startsWith("URL:") || rawText.startsWith("URI:")) {
+ return new URIParsedResult(rawText.substring(4).trim(), null);
+ }
+ rawText = rawText.trim();
+ return isBasicallyValidURI(rawText) ? new URIParsedResult(rawText, null) : null;
+ }
+
+}
\ 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..5a0b87b
--- /dev/null
+++ b/src/com/google/zxing/client/result/URLTOResultParser.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses the "URLTO" result format, which is of the form "URLTO:[title]:[url]".
+ * This seems to be used sometimes, but I am not able to find documentation
+ * on its origin or official format?
+ *
+ * @author Sean Owen
+ */
+public final class URLTOResultParser extends ResultParser {
+
+ @Override
+ public URIParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("urlto:") && !rawText.startsWith("URLTO:")) {
+ return null;
+ }
+ int titleEnd = rawText.indexOf(':', 6);
+ if (titleEnd < 0) {
+ return null;
+ }
+ String title = titleEnd <= 6 ? null : rawText.substring(6, titleEnd);
+ String uri = rawText.substring(titleEnd + 1);
+ return new URIParsedResult(uri, title);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/VCardResultParser.java b/src/com/google/zxing/client/result/VCardResultParser.java
new file mode 100644
index 0000000..c0eb9d2
--- /dev/null
+++ b/src/com/google/zxing/client/result/VCardResultParser.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Parses contact information formatted according to the VCard (2.1) format. This is not a complete
+ * implementation but should parse information as commonly encoded in 2D barcodes.
+ *
+ * @author Sean Owen
+ */
+public final class VCardResultParser extends ResultParser {
+
+ private static final Pattern BEGIN_VCARD = Pattern.compile("BEGIN:VCARD", Pattern.CASE_INSENSITIVE);
+ private static final Pattern VCARD_LIKE_DATE = Pattern.compile("\\d{4}-?\\d{2}-?\\d{2}");
+ private static final Pattern CR_LF_SPACE_TAB = Pattern.compile("\r\n[ \t]");
+ private static final Pattern NEWLINE_ESCAPE = Pattern.compile("\\\\[nN]");
+ private static final Pattern VCARD_ESCAPES = Pattern.compile("\\\\([,;\\\\])");
+ private static final Pattern EQUALS = Pattern.compile("=");
+ private static final Pattern SEMICOLON = Pattern.compile(";");
+ private static final Pattern UNESCAPED_SEMICOLONS = Pattern.compile("(?> matchVCardPrefixedField(CharSequence prefix,
+ String rawText,
+ boolean trim,
+ boolean parseFieldDivider) {
+ List> matches = null;
+ int i = 0;
+ int max = rawText.length();
+
+ while (i < max) {
+
+ // At start or after newline, match prefix, followed by optional metadata
+ // (led by ;) ultimately ending in colon
+ Matcher matcher = Pattern.compile("(?:^|\n)" + prefix + "(?:;([^:]*))?:",
+ Pattern.CASE_INSENSITIVE).matcher(rawText);
+ if (i > 0) {
+ i--; // Find from i-1 not i since looking at the preceding character
+ }
+ if (!matcher.find(i)) {
+ break;
+ }
+ i = matcher.end(0); // group 0 = whole pattern; end(0) is past final colon
+
+ String metadataString = matcher.group(1); // group 1 = metadata substring
+ List metadata = null;
+ boolean quotedPrintable = false;
+ String quotedPrintableCharset = null;
+ if (metadataString != null) {
+ for (String metadatum : SEMICOLON.split(metadataString)) {
+ if (metadata == null) {
+ metadata = new ArrayList<>(1);
+ }
+ metadata.add(metadatum);
+ String[] metadatumTokens = EQUALS.split(metadatum, 2);
+ if (metadatumTokens.length > 1) {
+ String key = metadatumTokens[0];
+ String value = metadatumTokens[1];
+ if ("ENCODING".equalsIgnoreCase(key) && "QUOTED-PRINTABLE".equalsIgnoreCase(value)) {
+ quotedPrintable = true;
+ } else if ("CHARSET".equalsIgnoreCase(key)) {
+ quotedPrintableCharset = value;
+ }
+ }
+ }
+ }
+
+ int matchStart = i; // Found the start of a match here
+
+ while ((i = rawText.indexOf((int) '\n', i)) >= 0) { // Really, end in \r\n
+ if (i < rawText.length() - 1 && // But if followed by tab or space,
+ (rawText.charAt(i + 1) == ' ' || // this is only a continuation
+ rawText.charAt(i + 1) == '\t')) {
+ i += 2; // Skip \n and continutation whitespace
+ } else if (quotedPrintable && // If preceded by = in quoted printable
+ ((i >= 1 && rawText.charAt(i - 1) == '=') || // this is a continuation
+ (i >= 2 && rawText.charAt(i - 2) == '='))) {
+ i++; // Skip \n
+ } else {
+ break;
+ }
+ }
+
+ if (i < 0) {
+ // No terminating end character? uh, done. Set i such that loop terminates and break
+ i = max;
+ } else if (i > matchStart) {
+ // found a match
+ if (matches == null) {
+ matches = new ArrayList<>(1); // lazy init
+ }
+ if (i >= 1 && rawText.charAt(i - 1) == '\r') {
+ i--; // Back up over \r, which really should be there
+ }
+ String element = rawText.substring(matchStart, i);
+ if (trim) {
+ element = element.trim();
+ }
+ if (quotedPrintable) {
+ element = decodeQuotedPrintable(element, quotedPrintableCharset);
+ if (parseFieldDivider) {
+ element = UNESCAPED_SEMICOLONS.matcher(element).replaceAll("\n").trim();
+ }
+ } else {
+ if (parseFieldDivider) {
+ element = UNESCAPED_SEMICOLONS.matcher(element).replaceAll("\n").trim();
+ }
+ element = CR_LF_SPACE_TAB.matcher(element).replaceAll("");
+ element = NEWLINE_ESCAPE.matcher(element).replaceAll("\n");
+ element = VCARD_ESCAPES.matcher(element).replaceAll("$1");
+ }
+ if (metadata == null) {
+ List match = new ArrayList<>(1);
+ match.add(element);
+ matches.add(match);
+ } else {
+ metadata.add(0, element);
+ matches.add(metadata);
+ }
+ i++;
+ } else {
+ i++;
+ }
+
+ }
+
+ return matches;
+ }
+
+ private static String decodeQuotedPrintable(CharSequence value, String charset) {
+ int length = value.length();
+ StringBuilder result = new StringBuilder(length);
+ ByteArrayOutputStream fragmentBuffer = new ByteArrayOutputStream();
+ for (int i = 0; i < length; i++) {
+ char c = value.charAt(i);
+ switch (c) {
+ case '\r':
+ case '\n':
+ break;
+ case '=':
+ if (i < length - 2) {
+ char nextChar = value.charAt(i + 1);
+ if (nextChar != '\r' && nextChar != '\n') {
+ char nextNextChar = value.charAt(i + 2);
+ int firstDigit = parseHexDigit(nextChar);
+ int secondDigit = parseHexDigit(nextNextChar);
+ if (firstDigit >= 0 && secondDigit >= 0) {
+ fragmentBuffer.write((firstDigit << 4) + secondDigit);
+ } // else ignore it, assume it was incorrectly encoded
+ i += 2;
+ }
+ }
+ break;
+ default:
+ maybeAppendFragment(fragmentBuffer, charset, result);
+ result.append(c);
+ }
+ }
+ maybeAppendFragment(fragmentBuffer, charset, result);
+ return result.toString();
+ }
+
+ private static void maybeAppendFragment(ByteArrayOutputStream fragmentBuffer,
+ String charset,
+ StringBuilder result) {
+ if (fragmentBuffer.size() > 0) {
+ byte[] fragmentBytes = fragmentBuffer.toByteArray();
+ String fragment;
+ if (charset == null) {
+ fragment = new String(fragmentBytes, Charset.forName("UTF-8"));
+ } else {
+ try {
+ fragment = new String(fragmentBytes, charset);
+ } catch (UnsupportedEncodingException e) {
+ fragment = new String(fragmentBytes, Charset.forName("UTF-8"));
+ }
+ }
+ fragmentBuffer.reset();
+ result.append(fragment);
+ }
+ }
+
+ static List matchSingleVCardPrefixedField(CharSequence prefix,
+ String rawText,
+ boolean trim,
+ boolean parseFieldDivider) {
+ List> values = matchVCardPrefixedField(prefix, rawText, trim, parseFieldDivider);
+ return values == null || values.isEmpty() ? null : values.get(0);
+ }
+
+ private static String toPrimaryValue(List list) {
+ return list == null || list.isEmpty() ? null : list.get(0);
+ }
+
+ private static String[] toPrimaryValues(Collection> lists) {
+ if (lists == null || lists.isEmpty()) {
+ return null;
+ }
+ List result = new ArrayList<>(lists.size());
+ for (List list : lists) {
+ String value = list.get(0);
+ if (value != null && !value.isEmpty()) {
+ result.add(value);
+ }
+ }
+ return result.toArray(new String[lists.size()]);
+ }
+
+ private static String[] toTypes(Collection> lists) {
+ if (lists == null || lists.isEmpty()) {
+ return null;
+ }
+ List result = new ArrayList<>(lists.size());
+ for (List list : lists) {
+ String type = null;
+ for (int i = 1; i < list.size(); i++) {
+ String metadatum = list.get(i);
+ int equals = metadatum.indexOf('=');
+ if (equals < 0) {
+ // take the whole thing as a usable label
+ type = metadatum;
+ break;
+ }
+ if ("TYPE".equalsIgnoreCase(metadatum.substring(0, equals))) {
+ type = metadatum.substring(equals + 1);
+ break;
+ }
+ }
+ result.add(type);
+ }
+ return result.toArray(new String[lists.size()]);
+ }
+
+ private static boolean isLikeVCardDate(CharSequence value) {
+ return value == null || VCARD_LIKE_DATE.matcher(value).matches();
+ }
+
+ /**
+ * Formats name fields of the form "Public;John;Q.;Reverend;III" into a form like
+ * "Reverend John Q. Public III".
+ *
+ * @param names name values to format, in place
+ */
+ private static void formatNames(Iterable> names) {
+ if (names != null) {
+ for (List list : names) {
+ String name = list.get(0);
+ String[] components = new String[5];
+ int start = 0;
+ int end;
+ int componentIndex = 0;
+ while (componentIndex < components.length - 1 && (end = name.indexOf(';', start)) >= 0) {
+ components[componentIndex] = name.substring(start, end);
+ componentIndex++;
+ start = end + 1;
+ }
+ components[componentIndex] = name.substring(start);
+ StringBuilder newName = new StringBuilder(100);
+ maybeAppendComponent(components, 3, newName);
+ maybeAppendComponent(components, 1, newName);
+ maybeAppendComponent(components, 2, newName);
+ maybeAppendComponent(components, 0, newName);
+ maybeAppendComponent(components, 4, newName);
+ list.set(0, newName.toString().trim());
+ }
+ }
+ }
+
+ private static void maybeAppendComponent(String[] components, int i, StringBuilder newName) {
+ if (components[i] != null && !components[i].isEmpty()) {
+ if (newName.length() > 0) {
+ newName.append(' ');
+ }
+ newName.append(components[i]);
+ }
+ }
+
+ @Override
+ public 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 = getMassagedText(result);
+ Matcher m = BEGIN_VCARD.matcher(rawText);
+ if (!m.find() || m.start() != 0) {
+ return null;
+ }
+ List> names = matchVCardPrefixedField("FN", rawText, true, false);
+ if (names == null) {
+ // If no display names found, look for regular name fields and format them
+ names = matchVCardPrefixedField("N", rawText, true, false);
+ formatNames(names);
+ }
+ List nicknameString = matchSingleVCardPrefixedField("NICKNAME", rawText, true, false);
+ String[] nicknames = nicknameString == null ? null : COMMA.split(nicknameString.get(0));
+ List> phoneNumbers = matchVCardPrefixedField("TEL", rawText, true, false);
+ List> emails = matchVCardPrefixedField("EMAIL", rawText, true, false);
+ List note = matchSingleVCardPrefixedField("NOTE", rawText, false, false);
+ List> addresses = matchVCardPrefixedField("ADR", rawText, true, true);
+ List org = matchSingleVCardPrefixedField("ORG", rawText, true, true);
+ List birthday = matchSingleVCardPrefixedField("BDAY", rawText, true, false);
+ if (birthday != null && !isLikeVCardDate(birthday.get(0))) {
+ birthday = null;
+ }
+ List title = matchSingleVCardPrefixedField("TITLE", rawText, true, false);
+ List> urls = matchVCardPrefixedField("URL", rawText, true, false);
+ List instantMessenger = matchSingleVCardPrefixedField("IMPP", rawText, true, false);
+ List geoString = matchSingleVCardPrefixedField("GEO", rawText, true, false);
+ String[] geo = geoString == null ? null : SEMICOLON_OR_COMMA.split(geoString.get(0));
+ if (geo != null && geo.length != 2) {
+ geo = null;
+ }
+ return new AddressBookParsedResult(toPrimaryValues(names),
+ nicknames,
+ null,
+ toPrimaryValues(phoneNumbers),
+ toTypes(phoneNumbers),
+ toPrimaryValues(emails),
+ toTypes(emails),
+ toPrimaryValue(instantMessenger),
+ toPrimaryValue(note),
+ toPrimaryValues(addresses),
+ toTypes(addresses),
+ toPrimaryValue(org),
+ toPrimaryValue(birthday),
+ toPrimaryValue(title),
+ toPrimaryValues(urls),
+ geo);
+ }
+
+}
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..e655d9e
--- /dev/null
+++ b/src/com/google/zxing/client/result/VEventResultParser.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+import java.util.List;
+
+/**
+ * Partially implements the iCalendar format's "VEVENT" format for specifying a
+ * calendar event. See RFC 2445. This supports SUMMARY, LOCATION, GEO, DTSTART and DTEND fields.
+ *
+ * @author Sean Owen
+ */
+public final class VEventResultParser extends ResultParser {
+
+ private static String matchSingleVCardPrefixedField(CharSequence prefix,
+ String rawText,
+ boolean trim) {
+ List values = VCardResultParser.matchSingleVCardPrefixedField(prefix, rawText, trim, false);
+ return values == null || values.isEmpty() ? null : values.get(0);
+ }
+
+ private static String[] matchVCardPrefixedField(CharSequence prefix, String rawText, boolean trim) {
+ List> values = VCardResultParser.matchVCardPrefixedField(prefix, rawText, trim, false);
+ if (values == null || values.isEmpty()) {
+ return null;
+ }
+ int size = values.size();
+ String[] result = new String[size];
+ for (int i = 0; i < size; i++) {
+ result[i] = values.get(i).get(0);
+ }
+ return result;
+ }
+
+ private static String stripMailto(String s) {
+ if (s != null && (s.startsWith("mailto:") || s.startsWith("MAILTO:"))) {
+ s = s.substring(7);
+ }
+ return s;
+ }
+
+ @Override
+ public CalendarParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ int vEventStart = rawText.indexOf("BEGIN:VEVENT");
+ if (vEventStart < 0) {
+ return null;
+ }
+
+ String summary = matchSingleVCardPrefixedField("SUMMARY", rawText, true);
+ String start = matchSingleVCardPrefixedField("DTSTART", rawText, true);
+ if (start == null) {
+ return null;
+ }
+ String end = matchSingleVCardPrefixedField("DTEND", rawText, true);
+ String duration = matchSingleVCardPrefixedField("DURATION", rawText, true);
+ String location = matchSingleVCardPrefixedField("LOCATION", rawText, true);
+ String organizer = stripMailto(matchSingleVCardPrefixedField("ORGANIZER", rawText, true));
+
+ String[] attendees = matchVCardPrefixedField("ATTENDEE", rawText, true);
+ if (attendees != null) {
+ for (int i = 0; i < attendees.length; i++) {
+ attendees[i] = stripMailto(attendees[i]);
+ }
+ }
+ String description = matchSingleVCardPrefixedField("DESCRIPTION", rawText, true);
+
+ String geoString = matchSingleVCardPrefixedField("GEO", rawText, true);
+ double latitude;
+ double longitude;
+ if (geoString == null) {
+ latitude = Double.NaN;
+ longitude = Double.NaN;
+ } else {
+ int semicolon = geoString.indexOf(';');
+ if (semicolon < 0) {
+ return null;
+ }
+ try {
+ latitude = Double.parseDouble(geoString.substring(0, semicolon));
+ longitude = Double.parseDouble(geoString.substring(semicolon + 1));
+ } catch (NumberFormatException ignored) {
+ return null;
+ }
+ }
+
+ try {
+ return new CalendarParsedResult(summary,
+ start,
+ end,
+ duration,
+ location,
+ organizer,
+ attendees,
+ description,
+ latitude,
+ longitude);
+ } catch (IllegalArgumentException ignored) {
+ return null;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/VINParsedResult.java b/src/com/google/zxing/client/result/VINParsedResult.java
new file mode 100644
index 0000000..c110e2b
--- /dev/null
+++ b/src/com/google/zxing/client/result/VINParsedResult.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2014 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.google.zxing.client.result;
+
+
+public final class VINParsedResult extends ParsedResult {
+
+ private final String vin;
+ private final String worldManufacturerID;
+ private final String vehicleDescriptorSection;
+ private final String vehicleIdentifierSection;
+ private final String countryCode;
+ private final String vehicleAttributes;
+ private final int modelYear;
+ private final char plantCode;
+ private final String sequentialNumber;
+
+ public VINParsedResult(String vin,
+ String worldManufacturerID,
+ String vehicleDescriptorSection,
+ String vehicleIdentifierSection,
+ String countryCode,
+ String vehicleAttributes,
+ int modelYear,
+ char plantCode,
+ String sequentialNumber) {
+ super(ParsedResultType.VIN);
+ this.vin = vin;
+ this.worldManufacturerID = worldManufacturerID;
+ this.vehicleDescriptorSection = vehicleDescriptorSection;
+ this.vehicleIdentifierSection = vehicleIdentifierSection;
+ this.countryCode = countryCode;
+ this.vehicleAttributes = vehicleAttributes;
+ this.modelYear = modelYear;
+ this.plantCode = plantCode;
+ this.sequentialNumber = sequentialNumber;
+ }
+
+ public String getVIN() {
+ return vin;
+ }
+
+ public String getWorldManufacturerID() {
+ return worldManufacturerID;
+ }
+
+ public String getVehicleDescriptorSection() {
+ return vehicleDescriptorSection;
+ }
+
+ public String getVehicleIdentifierSection() {
+ return vehicleIdentifierSection;
+ }
+
+ public String getCountryCode() {
+ return countryCode;
+ }
+
+ public String getVehicleAttributes() {
+ return vehicleAttributes;
+ }
+
+ public int getModelYear() {
+ return modelYear;
+ }
+
+ public char getPlantCode() {
+ return plantCode;
+ }
+
+ public String getSequentialNumber() {
+ return sequentialNumber;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(50);
+ result.append(worldManufacturerID).append(' ');
+ result.append(vehicleDescriptorSection).append(' ');
+ result.append(vehicleIdentifierSection).append('\n');
+ if (countryCode != null) {
+ result.append(countryCode).append(' ');
+ }
+ result.append(modelYear).append(' ');
+ result.append(plantCode).append(' ');
+ result.append(sequentialNumber).append('\n');
+ return result.toString();
+ }
+}
diff --git a/src/com/google/zxing/client/result/VINResultParser.java b/src/com/google/zxing/client/result/VINResultParser.java
new file mode 100644
index 0000000..6e2a6b3
--- /dev/null
+++ b/src/com/google/zxing/client/result/VINResultParser.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2014 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Result;
+
+import java.util.regex.Pattern;
+
+/**
+ * Detects a result that is likely a vehicle identification number.
+ *
+ * @author Sean Owen
+ */
+public final class VINResultParser extends ResultParser {
+
+ private static final Pattern IOQ = Pattern.compile("[IOQ]");
+ private static final Pattern AZ09 = Pattern.compile("[A-Z0-9]{17}");
+
+ private static boolean checkChecksum(CharSequence vin) {
+ int sum = 0;
+ for (int i = 0; i < vin.length(); i++) {
+ sum += vinPositionWeight(i + 1) * vinCharValue(vin.charAt(i));
+ }
+ char checkChar = vin.charAt(8);
+ char expectedCheckChar = checkChar(sum % 11);
+ return checkChar == expectedCheckChar;
+ }
+
+ private static int vinCharValue(char c) {
+ if (c >= 'A' && c <= 'I') {
+ return (c - 'A') + 1;
+ }
+ if (c >= 'J' && c <= 'R') {
+ return (c - 'J') + 1;
+ }
+ if (c >= 'S' && c <= 'Z') {
+ return (c - 'S') + 2;
+ }
+ if (c >= '0' && c <= '9') {
+ return c - '0';
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static int vinPositionWeight(int position) {
+ if (position >= 1 && position <= 7) {
+ return 9 - position;
+ }
+ if (position == 8) {
+ return 10;
+ }
+ if (position == 9) {
+ return 0;
+ }
+ if (position >= 10 && position <= 17) {
+ return 19 - position;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static char checkChar(int remainder) {
+ if (remainder < 10) {
+ return (char) ('0' + remainder);
+ }
+ if (remainder == 10) {
+ return 'X';
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static int modelYear(char c) {
+ if (c >= 'E' && c <= 'H') {
+ return (c - 'E') + 1984;
+ }
+ if (c >= 'J' && c <= 'N') {
+ return (c - 'J') + 1988;
+ }
+ if (c == 'P') {
+ return 1993;
+ }
+ if (c >= 'R' && c <= 'T') {
+ return (c - 'R') + 1994;
+ }
+ if (c >= 'V' && c <= 'Y') {
+ return (c - 'V') + 1997;
+ }
+ if (c >= '1' && c <= '9') {
+ return (c - '1') + 2001;
+ }
+ if (c >= 'A' && c <= 'D') {
+ return (c - 'A') + 2010;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ private static String countryCode(CharSequence wmi) {
+ char c1 = wmi.charAt(0);
+ char c2 = wmi.charAt(1);
+ switch (c1) {
+ case '1':
+ case '4':
+ case '5':
+ return "US";
+ case '2':
+ return "CA";
+ case '3':
+ if (c2 >= 'A' && c2 <= 'W') {
+ return "MX";
+ }
+ break;
+ case '9':
+ if ((c2 >= 'A' && c2 <= 'E') || (c2 >= '3' && c2 <= '9')) {
+ return "BR";
+ }
+ break;
+ case 'J':
+ if (c2 >= 'A' && c2 <= 'T') {
+ return "JP";
+ }
+ break;
+ case 'K':
+ if (c2 >= 'L' && c2 <= 'R') {
+ return "KO";
+ }
+ break;
+ case 'L':
+ return "CN";
+ case 'M':
+ if (c2 >= 'A' && c2 <= 'E') {
+ return "IN";
+ }
+ break;
+ case 'S':
+ if (c2 >= 'A' && c2 <= 'M') {
+ return "UK";
+ }
+ if (c2 >= 'N' && c2 <= 'T') {
+ return "DE";
+ }
+ break;
+ case 'V':
+ if (c2 >= 'F' && c2 <= 'R') {
+ return "FR";
+ }
+ if (c2 >= 'S' && c2 <= 'W') {
+ return "ES";
+ }
+ break;
+ case 'W':
+ return "DE";
+ case 'X':
+ if (c2 == '0' || (c2 >= '3' && c2 <= '9')) {
+ return "RU";
+ }
+ break;
+ case 'Z':
+ if (c2 >= 'A' && c2 <= 'R') {
+ return "IT";
+ }
+ break;
+ }
+ return null;
+ }
+
+ @Override
+ public VINParsedResult parse(Result result) {
+ if (result.getBarcodeFormat() != BarcodeFormat.CODE_39) {
+ return null;
+ }
+ String rawText = result.getText();
+ rawText = IOQ.matcher(rawText).replaceAll("").trim();
+ if (!AZ09.matcher(rawText).matches()) {
+ return null;
+ }
+ try {
+ if (!checkChecksum(rawText)) {
+ return null;
+ }
+ String wmi = rawText.substring(0, 3);
+ return new VINParsedResult(rawText,
+ wmi,
+ rawText.substring(3, 9),
+ rawText.substring(9, 17),
+ countryCode(wmi),
+ rawText.substring(3, 8),
+ modelYear(rawText.charAt(9)),
+ rawText.charAt(10),
+ rawText.substring(11));
+ } catch (IllegalArgumentException iae) {
+ return null;
+ }
+ }
+
+}
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..cedff9b
--- /dev/null
+++ b/src/com/google/zxing/client/result/WifiParsedResult.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+/**
+ * @author Vikram Aggarwal
+ */
+public final class WifiParsedResult extends ParsedResult {
+
+ private final String ssid;
+ private final String networkEncryption;
+ private final String password;
+ private final boolean hidden;
+
+ public WifiParsedResult(String networkEncryption, String ssid, String password) {
+ this(networkEncryption, ssid, password, false);
+ }
+
+ public WifiParsedResult(String networkEncryption, String ssid, String password, boolean hidden) {
+ super(ParsedResultType.WIFI);
+ this.ssid = ssid;
+ this.networkEncryption = networkEncryption;
+ this.password = password;
+ this.hidden = hidden;
+ }
+
+ public String getSsid() {
+ return ssid;
+ }
+
+ public String getNetworkEncryption() {
+ return networkEncryption;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public boolean isHidden() {
+ return hidden;
+ }
+
+ @Override
+ public String getDisplayResult() {
+ StringBuilder result = new StringBuilder(80);
+ maybeAppend(ssid, result);
+ maybeAppend(networkEncryption, result);
+ maybeAppend(password, result);
+ maybeAppend(Boolean.toString(hidden), result);
+ return result.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/client/result/WifiResultParser.java b/src/com/google/zxing/client/result/WifiResultParser.java
new file mode 100644
index 0000000..3e2bee7
--- /dev/null
+++ b/src/com/google/zxing/client/result/WifiResultParser.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.client.result;
+
+import com.google.zxing.Result;
+
+/**
+ * Parses a WIFI configuration string. Strings will be of the form:
+ *
+ *
{@code WIFI:T:[network type];S:[network SSID];P:[network password];H:[hidden?];;}
+ *
+ *
The fields can appear in any order. Only "S:" is required.
+ *
+ * @author Vikram Aggarwal
+ * @author Sean Owen
+ */
+public final class WifiResultParser extends ResultParser {
+
+ @Override
+ public WifiParsedResult parse(Result result) {
+ String rawText = getMassagedText(result);
+ if (!rawText.startsWith("WIFI:")) {
+ return null;
+ }
+ String ssid = matchSinglePrefixedField("S:", rawText, ';', false);
+ if (ssid == null || ssid.isEmpty()) {
+ return null;
+ }
+ String pass = matchSinglePrefixedField("P:", rawText, ';', false);
+ String type = matchSinglePrefixedField("T:", rawText, ';', false);
+ if (type == null) {
+ type = "nopass";
+ }
+ boolean hidden = Boolean.parseBoolean(matchSinglePrefixedField("H:", rawText, ';', false));
+ return new WifiParsedResult(type, ssid, pass, hidden);
+ }
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/BitArray.java b/src/com/google/zxing/common/BitArray.java
new file mode 100644
index 0000000..0589461
--- /dev/null
+++ b/src/com/google/zxing/common/BitArray.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+import java.util.Arrays;
+
+/**
+ * A simple, fast array of bits, represented compactly by an array of ints internally.
+ *
+ * @author Sean Owen
+ */
+public final class BitArray implements Cloneable {
+
+ private int[] bits;
+ private int size;
+
+ public BitArray() {
+ this.size = 0;
+ this.bits = new int[1];
+ }
+
+ public BitArray(int size) {
+ this.size = size;
+ this.bits = makeArray(size);
+ }
+
+ // For testing only
+ BitArray(int[] bits, int size) {
+ this.bits = bits;
+ this.size = size;
+ }
+
+ private static int[] makeArray(int size) {
+ return new int[(size + 31) / 32];
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public int getSizeInBytes() {
+ return (size + 7) / 8;
+ }
+
+ private void ensureCapacity(int size) {
+ if (size > bits.length * 32) {
+ int[] newBits = makeArray(size);
+ System.arraycopy(bits, 0, newBits, 0, bits.length);
+ this.bits = newBits;
+ }
+ }
+
+ /**
+ * @param i bit to get
+ * @return true iff bit i is set
+ */
+ public boolean get(int i) {
+ return (bits[i / 32] & (1 << (i & 0x1F))) != 0;
+ }
+
+ /**
+ * Sets bit i.
+ *
+ * @param i bit to set
+ */
+ public void set(int i) {
+ bits[i / 32] |= 1 << (i & 0x1F);
+ }
+
+ /**
+ * Flips bit i.
+ *
+ * @param i bit to set
+ */
+ public void flip(int i) {
+ bits[i / 32] ^= 1 << (i & 0x1F);
+ }
+
+ /**
+ * @param from first bit to check
+ * @return index of first bit that is set, starting from the given index, or size if none are set
+ * at or beyond this given index
+ * @see #getNextUnset(int)
+ */
+ public int getNextSet(int from) {
+ if (from >= size) {
+ return size;
+ }
+ int bitsOffset = from / 32;
+ int currentBits = bits[bitsOffset];
+ // mask off lesser bits first
+ currentBits &= ~((1 << (from & 0x1F)) - 1);
+ while (currentBits == 0) {
+ if (++bitsOffset == bits.length) {
+ return size;
+ }
+ currentBits = bits[bitsOffset];
+ }
+ int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits);
+ return result > size ? size : result;
+ }
+
+ /**
+ * @param from index to start looking for unset bit
+ * @return index of next unset bit, or {@code size} if none are unset until the end
+ * @see #getNextSet(int)
+ */
+ public int getNextUnset(int from) {
+ if (from >= size) {
+ return size;
+ }
+ int bitsOffset = from / 32;
+ int currentBits = ~bits[bitsOffset];
+ // mask off lesser bits first
+ currentBits &= ~((1 << (from & 0x1F)) - 1);
+ while (currentBits == 0) {
+ if (++bitsOffset == bits.length) {
+ return size;
+ }
+ currentBits = ~bits[bitsOffset];
+ }
+ int result = (bitsOffset * 32) + Integer.numberOfTrailingZeros(currentBits);
+ return result > size ? size : result;
+ }
+
+ /**
+ * Sets a block of 32 bits, starting at bit i.
+ *
+ * @param i first bit to set
+ * @param newBits the new value of the next 32 bits. Note again that the least-significant bit
+ * corresponds to bit i, the next-least-significant to i+1, and so on.
+ */
+ public void setBulk(int i, int newBits) {
+ bits[i / 32] = newBits;
+ }
+
+ /**
+ * Sets a range of bits.
+ *
+ * @param start start of range, inclusive.
+ * @param end end of range, exclusive
+ */
+ public void setRange(int start, int end) {
+ if (end < start) {
+ throw new IllegalArgumentException();
+ }
+ if (end == start) {
+ return;
+ }
+ end--; // will be easier to treat this as the last actually set bit -- inclusive
+ int firstInt = start / 32;
+ int lastInt = end / 32;
+ for (int i = firstInt; i <= lastInt; i++) {
+ int firstBit = i > firstInt ? 0 : start & 0x1F;
+ int lastBit = i < lastInt ? 31 : end & 0x1F;
+ int mask;
+ if (firstBit == 0 && lastBit == 31) {
+ mask = -1;
+ } else {
+ mask = 0;
+ for (int j = firstBit; j <= lastBit; j++) {
+ mask |= 1 << j;
+ }
+ }
+ bits[i] |= mask;
+ }
+ }
+
+ /**
+ * Clears all bits (sets to false).
+ */
+ public void clear() {
+ int max = bits.length;
+ for (int i = 0; i < max; i++) {
+ bits[i] = 0;
+ }
+ }
+
+ /**
+ * Efficient method to check if a range of bits is set, or not set.
+ *
+ * @param start start of range, inclusive.
+ * @param end end of range, exclusive
+ * @param value if true, checks that bits in range are set, otherwise checks that they are not set
+ * @return true iff all bits are set or not set in range, according to value argument
+ * @throws IllegalArgumentException if end is less than or equal to start
+ */
+ public boolean isRange(int start, int end, boolean value) {
+ if (end < start) {
+ throw new IllegalArgumentException();
+ }
+ if (end == start) {
+ return true; // empty range matches
+ }
+ end--; // will be easier to treat this as the last actually set bit -- inclusive
+ int firstInt = start / 32;
+ int lastInt = end / 32;
+ for (int i = firstInt; i <= lastInt; i++) {
+ int firstBit = i > firstInt ? 0 : start & 0x1F;
+ int lastBit = i < lastInt ? 31 : end & 0x1F;
+ int mask;
+ if (firstBit == 0 && lastBit == 31) {
+ mask = -1;
+ } else {
+ mask = 0;
+ for (int j = firstBit; j <= lastBit; j++) {
+ mask |= 1 << j;
+ }
+ }
+
+ // Return false if we're looking for 1s and the masked bits[i] isn't all 1s (that is,
+ // equals the mask, or we're looking for 0s and the masked portion is not all 0s
+ if ((bits[i] & mask) != (value ? mask : 0)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void appendBit(boolean bit) {
+ ensureCapacity(size + 1);
+ if (bit) {
+ bits[size / 32] |= 1 << (size & 0x1F);
+ }
+ size++;
+ }
+
+ /**
+ * Appends the least-significant bits, from value, in order from most-significant to
+ * least-significant. For example, appending 6 bits from 0x000001E will append the bits
+ * 0, 1, 1, 1, 1, 0 in that order.
+ *
+ * @param value {@code int} containing bits to append
+ * @param numBits bits from value to append
+ */
+ public void appendBits(int value, int numBits) {
+ if (numBits < 0 || numBits > 32) {
+ throw new IllegalArgumentException("Num bits must be between 0 and 32");
+ }
+ ensureCapacity(size + numBits);
+ for (int numBitsLeft = numBits; numBitsLeft > 0; numBitsLeft--) {
+ appendBit(((value >> (numBitsLeft - 1)) & 0x01) == 1);
+ }
+ }
+
+ public void appendBitArray(BitArray other) {
+ int otherSize = other.size;
+ ensureCapacity(size + otherSize);
+ for (int i = 0; i < otherSize; i++) {
+ appendBit(other.get(i));
+ }
+ }
+
+ public void xor(BitArray other) {
+ if (bits.length != other.bits.length) {
+ throw new IllegalArgumentException("Sizes don't match");
+ }
+ for (int i = 0; i < bits.length; i++) {
+ // The last byte could be incomplete (i.e. not have 8 bits in
+ // it) but there is no problem since 0 XOR 0 == 0.
+ bits[i] ^= other.bits[i];
+ }
+ }
+
+ /**
+ * @param bitOffset first bit to start writing
+ * @param array array to write into. Bytes are written most-significant byte first. This is the opposite
+ * of the internal representation, which is exposed by {@link #getBitArray()}
+ * @param offset position in array to start writing
+ * @param numBytes how many bytes to write
+ */
+ public void toBytes(int bitOffset, byte[] array, int offset, int numBytes) {
+ for (int i = 0; i < numBytes; i++) {
+ int theByte = 0;
+ for (int j = 0; j < 8; j++) {
+ if (get(bitOffset)) {
+ theByte |= 1 << (7 - j);
+ }
+ bitOffset++;
+ }
+ array[offset + i] = (byte) theByte;
+ }
+ }
+
+ /**
+ * @return underlying array of ints. The first element holds the first 32 bits, and the least
+ * significant bit is bit 0.
+ */
+ public int[] getBitArray() {
+ return bits;
+ }
+
+ /**
+ * Reverses all bits in the array.
+ */
+ public void reverse() {
+ int[] newBits = new int[bits.length];
+ // reverse all int's first
+ int len = ((size - 1) / 32);
+ int oldBitsLen = len + 1;
+ for (int i = 0; i < oldBitsLen; i++) {
+ long x = (long) bits[i];
+ x = ((x >> 1) & 0x55555555L) | ((x & 0x55555555L) << 1);
+ x = ((x >> 2) & 0x33333333L) | ((x & 0x33333333L) << 2);
+ x = ((x >> 4) & 0x0f0f0f0fL) | ((x & 0x0f0f0f0fL) << 4);
+ x = ((x >> 8) & 0x00ff00ffL) | ((x & 0x00ff00ffL) << 8);
+ x = ((x >> 16) & 0x0000ffffL) | ((x & 0x0000ffffL) << 16);
+ newBits[len - i] = (int) x;
+ }
+ // now correct the int's if the bit size isn't a multiple of 32
+ if (size != oldBitsLen * 32) {
+ int leftOffset = oldBitsLen * 32 - size;
+ int mask = 1;
+ for (int i = 0; i < 31 - leftOffset; i++) {
+ mask = (mask << 1) | 1;
+ }
+ int currentInt = (newBits[0] >> leftOffset) & mask;
+ for (int i = 1; i < oldBitsLen; i++) {
+ int nextInt = newBits[i];
+ currentInt |= nextInt << (32 - leftOffset);
+ newBits[i - 1] = currentInt;
+ currentInt = (nextInt >> leftOffset) & mask;
+ }
+ newBits[oldBitsLen - 1] = currentInt;
+ }
+ bits = newBits;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BitArray)) {
+ return false;
+ }
+ BitArray other = (BitArray) o;
+ return size == other.size && Arrays.equals(bits, other.bits);
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * size + Arrays.hashCode(bits);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder(size);
+ for (int i = 0; i < size; i++) {
+ if ((i & 0x07) == 0) {
+ result.append(' ');
+ }
+ result.append(get(i) ? 'X' : '.');
+ }
+ return result.toString();
+ }
+
+ @Override
+ public BitArray clone() {
+ return new BitArray(bits.clone(), size);
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/BitMatrix.java b/src/com/google/zxing/common/BitMatrix.java
new file mode 100644
index 0000000..a1b5c6c
--- /dev/null
+++ b/src/com/google/zxing/common/BitMatrix.java
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+import java.util.Arrays;
+
+/**
+ * Represents a 2D matrix of bits. In function arguments below, and throughout the common
+ * module, x is the column position, and y is the row position. The ordering is always x, y.
+ * The origin is at the top-left.
+ *
+ *
Internally the bits are represented in a 1-D array of 32-bit ints. However, each row begins
+ * with a new int. This is done intentionally so that we can copy out a row into a BitArray very
+ * efficiently.
+ *
+ *
The ordering of bits is row-major. Within each int, the least significant bits are used first,
+ * meaning they represent lower x values. This is compatible with BitArray's implementation.
+ *
+ * @author Sean Owen
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class BitMatrix implements Cloneable {
+
+ private final int width;
+ private final int height;
+ private final int rowSize;
+ private final int[] bits;
+
+ // A helper to construct a square matrix.
+ public BitMatrix(int dimension) {
+ this(dimension, dimension);
+ }
+
+ public BitMatrix(int width, int height) {
+ if (width < 1 || height < 1) {
+ throw new IllegalArgumentException("Both dimensions must be greater than 0");
+ }
+ this.width = width;
+ this.height = height;
+ this.rowSize = (width + 31) / 32;
+ bits = new int[rowSize * height];
+ }
+
+ private BitMatrix(int width, int height, int rowSize, int[] bits) {
+ this.width = width;
+ this.height = height;
+ this.rowSize = rowSize;
+ this.bits = bits;
+ }
+
+ public static BitMatrix parse(String stringRepresentation, String setString, String unsetString) {
+ if (stringRepresentation == null) {
+ throw new IllegalArgumentException();
+ }
+
+ boolean[] bits = new boolean[stringRepresentation.length()];
+ int bitsPos = 0;
+ int rowStartPos = 0;
+ int rowLength = -1;
+ int nRows = 0;
+ int pos = 0;
+ while (pos < stringRepresentation.length()) {
+ if (stringRepresentation.charAt(pos) == '\n' ||
+ stringRepresentation.charAt(pos) == '\r') {
+ if (bitsPos > rowStartPos) {
+ if (rowLength == -1) {
+ rowLength = bitsPos - rowStartPos;
+ } else if (bitsPos - rowStartPos != rowLength) {
+ throw new IllegalArgumentException("row lengths do not match");
+ }
+ rowStartPos = bitsPos;
+ nRows++;
+ }
+ pos++;
+ } else if (stringRepresentation.substring(pos, pos + setString.length()).equals(setString)) {
+ pos += setString.length();
+ bits[bitsPos] = true;
+ bitsPos++;
+ } else if (stringRepresentation.substring(pos, pos + unsetString.length()).equals(unsetString)) {
+ pos += unsetString.length();
+ bits[bitsPos] = false;
+ bitsPos++;
+ } else {
+ throw new IllegalArgumentException(
+ "illegal character encountered: " + stringRepresentation.substring(pos));
+ }
+ }
+
+ // no EOL at end?
+ if (bitsPos > rowStartPos) {
+ if (rowLength == -1) {
+ rowLength = bitsPos - rowStartPos;
+ } else if (bitsPos - rowStartPos != rowLength) {
+ throw new IllegalArgumentException("row lengths do not match");
+ }
+ nRows++;
+ }
+
+ BitMatrix matrix = new BitMatrix(rowLength, nRows);
+ for (int i = 0; i < bitsPos; i++) {
+ if (bits[i]) {
+ matrix.set(i % rowLength, i / rowLength);
+ }
+ }
+ return matrix;
+ }
+
+ /**
+ * 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 / 32);
+ 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 / 32);
+ bits[offset] |= 1 << (x & 0x1f);
+ }
+
+ public void unset(int x, int y) {
+ int offset = y * rowSize + (x / 32);
+ 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 / 32);
+ bits[offset] ^= 1 << (x & 0x1f);
+ }
+
+ /**
+ * Exclusive-or (XOR): Flip the bit in this {@code BitMatrix} if the corresponding
+ * mask bit is set.
+ *
+ * @param mask XOR mask
+ */
+ public void xor(BitMatrix mask) {
+ if (width != mask.getWidth() || height != mask.getHeight()
+ || rowSize != mask.getRowSize()) {
+ throw new IllegalArgumentException("input matrix dimensions do not match");
+ }
+ BitArray rowArray = new BitArray(width / 32 + 1);
+ for (int y = 0; y < height; y++) {
+ int offset = y * rowSize;
+ int[] row = mask.getRow(y, rowArray).getBitArray();
+ for (int x = 0; x < rowSize; x++) {
+ bits[offset + x] ^= row[x];
+ }
+ }
+ }
+
+ /**
+ * 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 / 32)] |= 1 << (x & 0x1f);
+ }
+ }
+ }
+
+ /**
+ * A fast method to retrieve one row of data from the matrix as a BitArray.
+ *
+ * @param y The row to retrieve
+ * @param row An optional caller-allocated BitArray, will be allocated if null or too small
+ * @return The resulting BitArray - this reference should always be used even when passing
+ * your own row
+ */
+ public BitArray getRow(int y, BitArray row) {
+ if (row == null || row.getSize() < width) {
+ row = new BitArray(width);
+ } else {
+ row.clear();
+ }
+ int offset = y * rowSize;
+ for (int x = 0; x < rowSize; x++) {
+ row.setBulk(x * 32, bits[offset + x]);
+ }
+ return row;
+ }
+
+ /**
+ * @param y row to set
+ * @param row {@link BitArray} to copy from
+ */
+ public void setRow(int y, BitArray row) {
+ System.arraycopy(row.getBitArray(), 0, bits, y * rowSize, rowSize);
+ }
+
+ /**
+ * Modifies this {@code BitMatrix} to represent the same but rotated 180 degrees
+ */
+ public void rotate180() {
+ int width = getWidth();
+ int height = getHeight();
+ BitArray topRow = new BitArray(width);
+ BitArray bottomRow = new BitArray(width);
+ for (int i = 0; i < (height + 1) / 2; i++) {
+ topRow = getRow(i, topRow);
+ bottomRow = getRow(height - 1 - i, bottomRow);
+ topRow.reverse();
+ bottomRow.reverse();
+ setRow(i, bottomRow);
+ setRow(height - 1 - i, topRow);
+ }
+ }
+
+ /**
+ * This is useful in detecting the enclosing rectangle of a 'pure' barcode.
+ *
+ * @return {@code left, top, width, height} enclosing rectangle of all 1 bits, or null if it is all white
+ */
+ public int[] getEnclosingRectangle() {
+ int left = width;
+ int top = height;
+ int right = -1;
+ int bottom = -1;
+
+ for (int y = 0; y < height; y++) {
+ for (int x32 = 0; x32 < rowSize; x32++) {
+ int theBits = bits[y * rowSize + x32];
+ if (theBits != 0) {
+ if (y < top) {
+ top = y;
+ }
+ if (y > bottom) {
+ bottom = y;
+ }
+ if (x32 * 32 < left) {
+ int bit = 0;
+ while ((theBits << (31 - bit)) == 0) {
+ bit++;
+ }
+ if ((x32 * 32 + bit) < left) {
+ left = x32 * 32 + bit;
+ }
+ }
+ if (x32 * 32 + 31 > right) {
+ int bit = 31;
+ while ((theBits >>> bit) == 0) {
+ bit--;
+ }
+ if ((x32 * 32 + bit) > right) {
+ right = x32 * 32 + bit;
+ }
+ }
+ }
+ }
+ }
+
+ int width = right - left;
+ int height = bottom - top;
+
+ if (width < 0 || height < 0) {
+ return null;
+ }
+
+ return new int[]{left, top, width, height};
+ }
+
+ /**
+ * This is useful in detecting a corner of a 'pure' barcode.
+ *
+ * @return {@code x, y} coordinate of top-left-most 1 bit, or null if it is all white
+ */
+ public int[] getTopLeftOnBit() {
+ int bitsOffset = 0;
+ while (bitsOffset < bits.length && bits[bitsOffset] == 0) {
+ bitsOffset++;
+ }
+ if (bitsOffset == bits.length) {
+ return null;
+ }
+ int y = bitsOffset / rowSize;
+ int x = (bitsOffset % rowSize) * 32;
+
+ int theBits = bits[bitsOffset];
+ int bit = 0;
+ while ((theBits << (31 - bit)) == 0) {
+ bit++;
+ }
+ x += bit;
+ return new int[]{x, y};
+ }
+
+ public int[] getBottomRightOnBit() {
+ int bitsOffset = bits.length - 1;
+ while (bitsOffset >= 0 && bits[bitsOffset] == 0) {
+ bitsOffset--;
+ }
+ if (bitsOffset < 0) {
+ return null;
+ }
+
+ int y = bitsOffset / rowSize;
+ int x = (bitsOffset % rowSize) * 32;
+
+ int theBits = bits[bitsOffset];
+ int bit = 31;
+ while ((theBits >>> bit) == 0) {
+ bit--;
+ }
+ x += bit;
+
+ return new int[]{x, y};
+ }
+
+ /**
+ * @return The width of the matrix
+ */
+ public int getWidth() {
+ return width;
+ }
+
+ /**
+ * @return The height of the matrix
+ */
+ public int getHeight() {
+ return height;
+ }
+
+ /**
+ * @return The row size of the matrix
+ */
+ public int getRowSize() {
+ return rowSize;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof BitMatrix)) {
+ return false;
+ }
+ BitMatrix other = (BitMatrix) o;
+ return width == other.width && height == other.height && rowSize == other.rowSize &&
+ Arrays.equals(bits, other.bits);
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = width;
+ hash = 31 * hash + width;
+ hash = 31 * hash + height;
+ hash = 31 * hash + rowSize;
+ hash = 31 * hash + Arrays.hashCode(bits);
+ return hash;
+ }
+
+ @Override
+ public String toString() {
+ return toString("X ", " ");
+ }
+
+ public String toString(String setString, String unsetString) {
+ return toString(setString, unsetString, System.lineSeparator());
+ }
+
+ public String toString(String setString, String unsetString, String lineSeparator) {
+ StringBuilder result = new StringBuilder(height * (width + 1));
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ result.append(get(x, y) ? setString : unsetString);
+ }
+ result.append(lineSeparator);
+ }
+ return result.toString();
+ }
+
+ @Override
+ public BitMatrix clone() {
+ return new BitMatrix(width, height, rowSize, bits.clone());
+ }
+
+}
diff --git a/src/com/google/zxing/common/BitSource.java b/src/com/google/zxing/common/BitSource.java
new file mode 100644
index 0000000..8396a7c
--- /dev/null
+++ b/src/com/google/zxing/common/BitSource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+/**
+ * This provides an easy abstraction to read bits at a time from a sequence of bytes, where the
+ * number of bits read is not often a multiple of 8.
+ *
+ *
This class is thread-safe but not reentrant -- unless the caller modifies the bytes array
+ * it passed in, in which case all bets are off.
+ *
+ * @author Sean Owen
+ */
+public final class BitSource {
+
+ private final byte[] bytes;
+ private int byteOffset;
+ private int bitOffset;
+
+ /**
+ * @param bytes bytes from which this will read bits. Bits will be read from the first byte first.
+ * Bits are read within a byte from most-significant to least-significant bit.
+ */
+ public BitSource(byte[] bytes) {
+ this.bytes = bytes;
+ }
+
+ /**
+ * @return index of next bit in current byte which would be read by the next call to {@link #readBits(int)}.
+ */
+ public int getBitOffset() {
+ return bitOffset;
+ }
+
+ /**
+ * @return index of next byte in input byte array which would be read by the next call to {@link #readBits(int)}.
+ */
+ public int getByteOffset() {
+ return byteOffset;
+ }
+
+ /**
+ * @param numBits number of bits to read
+ * @return int representing the bits read. The bits will appear as the least-significant
+ * bits of the int
+ * @throws IllegalArgumentException if numBits isn't in [1,32] or more than is available
+ */
+ public int readBits(int numBits) {
+ if (numBits < 1 || numBits > 32 || numBits > available()) {
+ throw new IllegalArgumentException(String.valueOf(numBits));
+ }
+
+ int result = 0;
+
+ // First, read remainder from current byte
+ if (bitOffset > 0) {
+ int bitsLeft = 8 - bitOffset;
+ int toRead = numBits < bitsLeft ? numBits : bitsLeft;
+ int bitsToNotRead = bitsLeft - toRead;
+ int mask = (0xFF >> (8 - toRead)) << bitsToNotRead;
+ result = (bytes[byteOffset] & mask) >> bitsToNotRead;
+ numBits -= toRead;
+ bitOffset += toRead;
+ if (bitOffset == 8) {
+ bitOffset = 0;
+ byteOffset++;
+ }
+ }
+
+ // Next read whole bytes
+ if (numBits > 0) {
+ while (numBits >= 8) {
+ result = (result << 8) | (bytes[byteOffset] & 0xFF);
+ byteOffset++;
+ numBits -= 8;
+ }
+
+ // Finally read a partial byte
+ if (numBits > 0) {
+ int bitsToNotRead = 8 - numBits;
+ int mask = (0xFF >> bitsToNotRead) << bitsToNotRead;
+ result = (result << numBits) | ((bytes[byteOffset] & mask) >> bitsToNotRead);
+ bitOffset += numBits;
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * @return number of bits that can be read successfully
+ */
+ public int available() {
+ return 8 * (bytes.length - byteOffset) - bitOffset;
+ }
+
+}
diff --git a/src/com/google/zxing/common/CharacterSetECI.java b/src/com/google/zxing/common/CharacterSetECI.java
new file mode 100644
index 0000000..89afd33
--- /dev/null
+++ b/src/com/google/zxing/common/CharacterSetECI.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+import com.google.zxing.FormatException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Encapsulates a Character Set ECI, according to "Extended Channel Interpretations" 5.3.1.1
+ * of ISO 18004.
+ *
+ * @author Sean Owen
+ */
+public enum CharacterSetECI {
+
+ // Enum name is a Java encoding valid for java.lang and java.io
+ Cp437(new int[]{0, 2}),
+ ISO8859_1(new int[]{1, 3}, "ISO-8859-1"),
+ ISO8859_2(4, "ISO-8859-2"),
+ ISO8859_3(5, "ISO-8859-3"),
+ ISO8859_4(6, "ISO-8859-4"),
+ ISO8859_5(7, "ISO-8859-5"),
+ ISO8859_6(8, "ISO-8859-6"),
+ ISO8859_7(9, "ISO-8859-7"),
+ ISO8859_8(10, "ISO-8859-8"),
+ ISO8859_9(11, "ISO-8859-9"),
+ ISO8859_10(12, "ISO-8859-10"),
+ ISO8859_11(13, "ISO-8859-11"),
+ ISO8859_13(15, "ISO-8859-13"),
+ ISO8859_14(16, "ISO-8859-14"),
+ ISO8859_15(17, "ISO-8859-15"),
+ ISO8859_16(18, "ISO-8859-16"),
+ SJIS(20, "Shift_JIS"),
+ Cp1250(21, "windows-1250"),
+ Cp1251(22, "windows-1251"),
+ Cp1252(23, "windows-1252"),
+ Cp1256(24, "windows-1256"),
+ UnicodeBigUnmarked(25, "UTF-16BE", "UnicodeBig"),
+ UTF8(26, "UTF-8"),
+ ASCII(new int[]{27, 170}, "US-ASCII"),
+ Big5(28),
+ GB18030(29, "GB2312", "EUC_CN", "GBK"),
+ EUC_KR(30, "EUC-KR");
+
+ private static final Map VALUE_TO_ECI = new HashMap<>();
+ private static final Map NAME_TO_ECI = new HashMap<>();
+
+ static {
+ for (CharacterSetECI eci : values()) {
+ for (int value : eci.values) {
+ VALUE_TO_ECI.put(value, eci);
+ }
+ NAME_TO_ECI.put(eci.name(), eci);
+ for (String name : eci.otherEncodingNames) {
+ NAME_TO_ECI.put(name, eci);
+ }
+ }
+ }
+
+ private final int[] values;
+ private final String[] otherEncodingNames;
+
+ CharacterSetECI(int value) {
+ this(new int[]{value});
+ }
+
+ CharacterSetECI(int value, String... otherEncodingNames) {
+ this.values = new int[]{value};
+ this.otherEncodingNames = otherEncodingNames;
+ }
+
+ CharacterSetECI(int[] values, String... otherEncodingNames) {
+ this.values = values;
+ this.otherEncodingNames = otherEncodingNames;
+ }
+
+ /**
+ * @param value character set ECI value
+ * @return {@code CharacterSetECI} representing ECI of given value, or null if it is legal but
+ * unsupported
+ * @throws FormatException if ECI value is invalid
+ */
+ public static CharacterSetECI getCharacterSetECIByValue(int value) throws FormatException {
+ if (value < 0 || value >= 900) {
+ throw FormatException.getFormatInstance();
+ }
+ return VALUE_TO_ECI.get(value);
+ }
+
+ /**
+ * @param name character set ECI encoding name
+ * @return CharacterSetECI representing ECI for character encoding, or null if it is legal
+ * but unsupported
+ */
+ public static CharacterSetECI getCharacterSetECIByName(String name) {
+ return NAME_TO_ECI.get(name);
+ }
+
+ public int getValue() {
+ return values[0];
+ }
+
+}
diff --git a/src/com/google/zxing/common/DecoderResult.java b/src/com/google/zxing/common/DecoderResult.java
new file mode 100644
index 0000000..f6f6e87
--- /dev/null
+++ b/src/com/google/zxing/common/DecoderResult.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+import java.util.List;
+
+/**
+ * Encapsulates the result of decoding a matrix of bits. This typically
+ * applies to 2D barcode formats. For now it contains the raw bytes obtained,
+ * as well as a String interpretation of those bytes, if applicable.
+ *
+ * @author Sean Owen
+ */
+public final class DecoderResult {
+
+ private final byte[] rawBytes;
+ private final String text;
+ private final List byteSegments;
+ private final String ecLevel;
+ private final int structuredAppendParity;
+ private final int structuredAppendSequenceNumber;
+ private Integer errorsCorrected;
+ private Integer erasures;
+ private Object other;
+
+ public DecoderResult(byte[] rawBytes,
+ String text,
+ List byteSegments,
+ String ecLevel) {
+ this(rawBytes, text, byteSegments, ecLevel, -1, -1);
+ }
+
+ public DecoderResult(byte[] rawBytes,
+ String text,
+ List byteSegments,
+ String ecLevel,
+ int saSequence,
+ int saParity) {
+ this.rawBytes = rawBytes;
+ this.text = text;
+ this.byteSegments = byteSegments;
+ this.ecLevel = ecLevel;
+ this.structuredAppendParity = saParity;
+ this.structuredAppendSequenceNumber = saSequence;
+ }
+
+ public byte[] getRawBytes() {
+ return rawBytes;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public List getByteSegments() {
+ return byteSegments;
+ }
+
+ public String getECLevel() {
+ return ecLevel;
+ }
+
+ public Integer getErrorsCorrected() {
+ return errorsCorrected;
+ }
+
+ public void setErrorsCorrected(Integer errorsCorrected) {
+ this.errorsCorrected = errorsCorrected;
+ }
+
+ public Integer getErasures() {
+ return erasures;
+ }
+
+ public void setErasures(Integer erasures) {
+ this.erasures = erasures;
+ }
+
+ public Object getOther() {
+ return other;
+ }
+
+ public void setOther(Object other) {
+ this.other = other;
+ }
+
+ public boolean hasStructuredAppend() {
+ return structuredAppendParity >= 0 && structuredAppendSequenceNumber >= 0;
+ }
+
+ public int getStructuredAppendParity() {
+ return structuredAppendParity;
+ }
+
+ public int getStructuredAppendSequenceNumber() {
+ return structuredAppendSequenceNumber;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/DefaultGridSampler.java b/src/com/google/zxing/common/DefaultGridSampler.java
new file mode 100644
index 0000000..a3db348
--- /dev/null
+++ b/src/com/google/zxing/common/DefaultGridSampler.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+import com.google.zxing.NotFoundException;
+
+/**
+ * @author Sean Owen
+ */
+public final class DefaultGridSampler extends GridSampler {
+
+ @Override
+ public BitMatrix sampleGrid(BitMatrix image,
+ int dimensionX,
+ int dimensionY,
+ float p1ToX, float p1ToY,
+ float p2ToX, float p2ToY,
+ float p3ToX, float p3ToY,
+ float p4ToX, float p4ToY,
+ float p1FromX, float p1FromY,
+ float p2FromX, float p2FromY,
+ float p3FromX, float p3FromY,
+ float p4FromX, float p4FromY) throws NotFoundException {
+
+ PerspectiveTransform transform = PerspectiveTransform.quadrilateralToQuadrilateral(
+ p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY,
+ p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY);
+
+ return sampleGrid(image, dimensionX, dimensionY, transform);
+ }
+
+ @Override
+ public BitMatrix sampleGrid(BitMatrix image,
+ int dimensionX,
+ int dimensionY,
+ PerspectiveTransform transform) throws NotFoundException {
+ if (dimensionX <= 0 || dimensionY <= 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ BitMatrix bits = new BitMatrix(dimensionX, dimensionY);
+ float[] points = new float[2 * dimensionX];
+ for (int y = 0; y < dimensionY; y++) {
+ int max = points.length;
+ float iValue = (float) y + 0.5f;
+ for (int x = 0; x < max; x += 2) {
+ points[x] = (float) (x / 2) + 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 / 2, 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..1f2ece3
--- /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 class DetectorResult {
+
+ private final BitMatrix bits;
+ private final ResultPoint[] points;
+
+ public DetectorResult(BitMatrix bits, ResultPoint[] points) {
+ this.bits = bits;
+ this.points = points;
+ }
+
+ public final BitMatrix getBits() {
+ return bits;
+ }
+
+ public final ResultPoint[] getPoints() {
+ return points;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/common/GlobalHistogramBinarizer.java b/src/com/google/zxing/common/GlobalHistogramBinarizer.java
new file mode 100644
index 0000000..2ad9641
--- /dev/null
+++ b/src/com/google/zxing/common/GlobalHistogramBinarizer.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+import com.google.zxing.Binarizer;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.NotFoundException;
+
+/**
+ * This Binarizer implementation uses the old ZXing global histogram approach. It is suitable
+ * for low-end mobile devices which don't have enough CPU or memory to use a local thresholding
+ * algorithm. However, because it picks a global black point, it cannot handle difficult shadows
+ * and gradients.
+ *
+ * Faster mobile devices and all desktop applications should probably use HybridBinarizer instead.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ */
+public class GlobalHistogramBinarizer extends Binarizer {
+
+ private static final int LUMINANCE_BITS = 5;
+ private static final int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS;
+ private static final int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS;
+ private static final byte[] EMPTY = new byte[0];
+ private final int[] buckets;
+ private byte[] luminances;
+
+ public GlobalHistogramBinarizer(LuminanceSource source) {
+ super(source);
+ luminances = EMPTY;
+ buckets = new int[LUMINANCE_BUCKETS];
+ }
+
+ private static int estimateBlackPoint(int[] buckets) throws NotFoundException {
+ // Find the tallest peak in the histogram.
+ int numBuckets = buckets.length;
+ int maxBucketCount = 0;
+ int firstPeak = 0;
+ int firstPeakSize = 0;
+ for (int x = 0; x < numBuckets; x++) {
+ if (buckets[x] > firstPeakSize) {
+ firstPeak = x;
+ firstPeakSize = buckets[x];
+ }
+ if (buckets[x] > maxBucketCount) {
+ maxBucketCount = buckets[x];
+ }
+ }
+
+ // Find the second-tallest peak which is somewhat far from the tallest peak.
+ int secondPeak = 0;
+ int secondPeakScore = 0;
+ for (int x = 0; x < numBuckets; x++) {
+ int distanceToBiggest = x - firstPeak;
+ // Encourage more distant second peaks by multiplying by square of distance.
+ int score = buckets[x] * distanceToBiggest * distanceToBiggest;
+ if (score > secondPeakScore) {
+ secondPeak = x;
+ secondPeakScore = score;
+ }
+ }
+
+ // Make sure firstPeak corresponds to the black peak.
+ if (firstPeak > secondPeak) {
+ int temp = firstPeak;
+ firstPeak = secondPeak;
+ secondPeak = temp;
+ }
+
+ // If there is too little contrast in the image to pick a meaningful black point, throw rather
+ // than waste time trying to decode the image, and risk false positives.
+ if (secondPeak - firstPeak <= numBuckets / 16) {
+ 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;
+ }
+
+ // Applies simple sharpening to the row data to improve performance of the 1D Readers.
+ @Override
+ public BitArray getBlackRow(int y, BitArray row) throws NotFoundException {
+ LuminanceSource source = getLuminanceSource();
+ int width = source.getWidth();
+ if (row == null || row.getSize() < width) {
+ row = new BitArray(width);
+ } else {
+ row.clear();
+ }
+
+ initArrays(width);
+ byte[] localLuminances = source.getRow(y, luminances);
+ int[] localBuckets = buckets;
+ for (int x = 0; x < width; x++) {
+ int pixel = localLuminances[x] & 0xff;
+ localBuckets[pixel >> LUMINANCE_SHIFT]++;
+ }
+ int blackPoint = estimateBlackPoint(localBuckets);
+
+ int left = localLuminances[0] & 0xff;
+ int center = localLuminances[1] & 0xff;
+ for (int x = 1; x < width - 1; x++) {
+ int right = localLuminances[x + 1] & 0xff;
+ // A simple -1 4 -1 box filter with a weight of 2.
+ int luminance = ((center * 4) - left - right) / 2;
+ if (luminance < blackPoint) {
+ row.set(x);
+ }
+ left = center;
+ center = right;
+ }
+ return row;
+ }
+
+ // Does not sharpen the data, as this call is intended to only be used by 2D Readers.
+ @Override
+ public BitMatrix getBlackMatrix() throws NotFoundException {
+ LuminanceSource source = getLuminanceSource();
+ int width = source.getWidth();
+ int height = source.getHeight();
+ BitMatrix matrix = new BitMatrix(width, height);
+
+ // Quickly calculates the histogram by sampling four rows from the image. This proved to be
+ // more robust on the blackbox tests than sampling a diagonal as we used to do.
+ initArrays(width);
+ int[] localBuckets = buckets;
+ for (int y = 1; y < 5; y++) {
+ int row = height * y / 5;
+ byte[] localLuminances = source.getRow(row, luminances);
+ int right = (width * 4) / 5;
+ for (int x = width / 5; x < right; x++) {
+ int pixel = localLuminances[x] & 0xff;
+ localBuckets[pixel >> LUMINANCE_SHIFT]++;
+ }
+ }
+ int blackPoint = estimateBlackPoint(localBuckets);
+
+ // We delay reading the entire image luminance until the black point estimation succeeds.
+ // Although we end up reading four rows twice, it is consistent with our motto of
+ // "fail quickly" which is necessary for continuous scanning.
+ byte[] localLuminances = source.getMatrix();
+ for (int y = 0; y < height; y++) {
+ int offset = y * width;
+ for (int x = 0; x < width; x++) {
+ int pixel = localLuminances[offset + x] & 0xff;
+ if (pixel < blackPoint) {
+ matrix.set(x, y);
+ }
+ }
+ }
+
+ return matrix;
+ }
+
+ @Override
+ public Binarizer createBinarizer(LuminanceSource source) {
+ return new GlobalHistogramBinarizer(source);
+ }
+
+ private void initArrays(int luminanceSize) {
+ if (luminances.length < luminanceSize) {
+ luminances = new byte[luminanceSize];
+ }
+ for (int x = 0; x < LUMINANCE_BUCKETS; x++) {
+ buckets[x] = 0;
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/common/GridSampler.java b/src/com/google/zxing/common/GridSampler.java
new file mode 100644
index 0000000..7b77348
--- /dev/null
+++ b/src/com/google/zxing/common/GridSampler.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+import com.google.zxing.NotFoundException;
+
+/**
+ * Implementations of this class can, given locations of finder patterns for a QR code in an
+ * image, sample the right points in the image to reconstruct the QR code, accounting for
+ * perspective distortion. It is abstracted since it is relatively expensive and should be allowed
+ * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced
+ * Imaging library, but which may not be available in other environments such as J2ME, and vice
+ * versa.
+ *
+ * The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)}
+ * with an instance of a class which implements this interface.
+ *
+ * @author Sean Owen
+ */
+public abstract class GridSampler {
+
+ private static GridSampler gridSampler = new DefaultGridSampler();
+
+ /**
+ * Sets the implementation of GridSampler used by the library. One global
+ * instance is stored, which may sound problematic. But, the implementation provided
+ * ought to be appropriate for the entire platform, and all uses of this library
+ * in the whole lifetime of the JVM. For instance, an Android activity can swap in
+ * an implementation that takes advantage of native platform libraries.
+ *
+ * @param newGridSampler The platform-specific object to install.
+ */
+ public static void setGridSampler(GridSampler newGridSampler) {
+ gridSampler = newGridSampler;
+ }
+
+ /**
+ * @return the current implementation of GridSampler
+ */
+ public static GridSampler getInstance() {
+ return gridSampler;
+ }
+
+ /**
+ *
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;
+ }
+ }
+ }
+
+ /**
+ * Samples an image for a rectangular matrix of bits of the given dimension. The sampling
+ * transformation is determined by the coordinates of 4 points, in the original and transformed
+ * image space.
+ *
+ * @param image image to sample
+ * @param dimensionX width of {@link BitMatrix} to sample from image
+ * @param dimensionY height of {@link BitMatrix} to sample from image
+ * @param p1ToX point 1 preimage X
+ * @param p1ToY point 1 preimage Y
+ * @param p2ToX point 2 preimage X
+ * @param p2ToY point 2 preimage Y
+ * @param p3ToX point 3 preimage X
+ * @param p3ToY point 3 preimage Y
+ * @param p4ToX point 4 preimage X
+ * @param p4ToY point 4 preimage Y
+ * @param p1FromX point 1 image X
+ * @param p1FromY point 1 image Y
+ * @param p2FromX point 2 image X
+ * @param p2FromY point 2 image Y
+ * @param p3FromX point 3 image X
+ * @param p3FromY point 3 image Y
+ * @param p4FromX point 4 image X
+ * @param p4FromY point 4 image Y
+ * @return {@link BitMatrix} representing a grid of points sampled from the image within a region
+ * defined by the "from" parameters
+ * @throws NotFoundException if image can't be sampled, for example, if the transformation defined
+ * by the given points is invalid or results in sampling outside the image boundaries
+ */
+ public abstract BitMatrix sampleGrid(BitMatrix image,
+ int dimensionX,
+ int dimensionY,
+ float p1ToX, float p1ToY,
+ float p2ToX, float p2ToY,
+ float p3ToX, float p3ToY,
+ float p4ToX, float p4ToY,
+ float p1FromX, float p1FromY,
+ float p2FromX, float p2FromY,
+ float p3FromX, float p3FromY,
+ float p4FromX, float p4FromY) throws NotFoundException;
+
+ public abstract BitMatrix sampleGrid(BitMatrix image,
+ int dimensionX,
+ int dimensionY,
+ PerspectiveTransform transform) throws NotFoundException;
+
+}
diff --git a/src/com/google/zxing/common/HybridBinarizer.java b/src/com/google/zxing/common/HybridBinarizer.java
new file mode 100644
index 0000000..302f38a
--- /dev/null
+++ b/src/com/google/zxing/common/HybridBinarizer.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+import com.google.zxing.Binarizer;
+import com.google.zxing.LuminanceSource;
+import com.google.zxing.NotFoundException;
+
+/**
+ * This class implements a local thresholding algorithm, which while slower than the
+ * GlobalHistogramBinarizer, is fairly efficient for what it does. It is designed for
+ * high frequency images of barcodes with black data on white backgrounds. For this application,
+ * it does a much better job than a global blackpoint with severe shadows and gradients.
+ * However it tends to produce artifacts on lower frequency images and is therefore not
+ * a good general purpose binarizer for uses outside ZXing.
+ *
+ * This class extends GlobalHistogramBinarizer, using the older histogram approach for 1D readers,
+ * and the newer local approach for 2D readers. 1D decoding using a per-row histogram is already
+ * inherently local, and only fails for horizontal gradients. We can revisit that problem later,
+ * but for now it was not a win to use local blocks for 1D.
+ *
+ * This Binarizer is the default for the unit tests and the recommended class for library users.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ */
+public final class HybridBinarizer extends GlobalHistogramBinarizer {
+
+ // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels.
+ // So this is the smallest dimension in each axis we can accept.
+ private static final int BLOCK_SIZE_POWER = 3;
+ private static final int BLOCK_SIZE = 1 << BLOCK_SIZE_POWER; // ...0100...00
+ private static final int BLOCK_SIZE_MASK = BLOCK_SIZE - 1; // ...0011...11
+ private static final int MINIMUM_DIMENSION = BLOCK_SIZE * 5;
+ private static final int MIN_DYNAMIC_RANGE = 24;
+
+ private BitMatrix matrix;
+
+ public HybridBinarizer(LuminanceSource source) {
+ super(source);
+ }
+
+ /**
+ * For each block in the image, calculate the average black point using a 5x5 grid
+ * of the blocks around it. Also handles the corner cases (fractional blocks are computed based
+ * on the last pixels in the row/column which are also used in the previous block).
+ */
+ private static void calculateThresholdForBlock(byte[] luminances,
+ int subWidth,
+ int subHeight,
+ int width,
+ int height,
+ int[][] blackPoints,
+ BitMatrix matrix) {
+ for (int y = 0; y < subHeight; y++) {
+ int yoffset = y << BLOCK_SIZE_POWER;
+ int maxYOffset = height - BLOCK_SIZE;
+ if (yoffset > maxYOffset) {
+ yoffset = maxYOffset;
+ }
+ for (int x = 0; x < subWidth; x++) {
+ int xoffset = x << BLOCK_SIZE_POWER;
+ int maxXOffset = width - BLOCK_SIZE;
+ if (xoffset > maxXOffset) {
+ xoffset = maxXOffset;
+ }
+ int left = cap(x, 2, subWidth - 3);
+ int top = cap(y, 2, subHeight - 3);
+ int sum = 0;
+ for (int z = -2; z <= 2; z++) {
+ int[] blackRow = blackPoints[top + z];
+ sum += blackRow[left - 2] + blackRow[left - 1] + blackRow[left] + blackRow[left + 1] + blackRow[left + 2];
+ }
+ int average = sum / 25;
+ thresholdBlock(luminances, xoffset, yoffset, average, width, matrix);
+ }
+ }
+ }
+
+ private static int cap(int value, int min, int max) {
+ return value < min ? min : value > max ? max : value;
+ }
+
+ /**
+ * Applies a single threshold to a block of pixels.
+ */
+ private static void thresholdBlock(byte[] luminances,
+ int xoffset,
+ int yoffset,
+ int threshold,
+ int stride,
+ BitMatrix matrix) {
+ for (int y = 0, offset = yoffset * stride + xoffset; y < BLOCK_SIZE; y++, offset += stride) {
+ for (int x = 0; x < BLOCK_SIZE; x++) {
+ // Comparison needs to be <= so that black == 0 pixels are black even if the threshold is 0.
+ if ((luminances[offset + x] & 0xFF) <= threshold) {
+ matrix.set(xoffset + x, yoffset + y);
+ }
+ }
+ }
+ }
+
+ /**
+ * Calculates a single black point for each block of pixels and saves it away.
+ * See the following thread for a discussion of this algorithm:
+ * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0
+ */
+ private static int[][] calculateBlackPoints(byte[] luminances,
+ int subWidth,
+ int subHeight,
+ int width,
+ int height) {
+ int[][] blackPoints = new int[subHeight][subWidth];
+ for (int y = 0; y < subHeight; y++) {
+ int yoffset = y << BLOCK_SIZE_POWER;
+ int maxYOffset = height - BLOCK_SIZE;
+ if (yoffset > maxYOffset) {
+ yoffset = maxYOffset;
+ }
+ for (int x = 0; x < subWidth; x++) {
+ int xoffset = x << BLOCK_SIZE_POWER;
+ int maxXOffset = width - BLOCK_SIZE;
+ if (xoffset > maxXOffset) {
+ xoffset = maxXOffset;
+ }
+ int sum = 0;
+ int min = 0xFF;
+ int max = 0;
+ for (int yy = 0, offset = yoffset * width + xoffset; yy < BLOCK_SIZE; yy++, offset += width) {
+ for (int xx = 0; xx < BLOCK_SIZE; xx++) {
+ int pixel = luminances[offset + xx] & 0xFF;
+ sum += pixel;
+ // still looking for good contrast
+ if (pixel < min) {
+ min = pixel;
+ }
+ if (pixel > max) {
+ max = pixel;
+ }
+ }
+ // short-circuit min/max tests once dynamic range is met
+ if (max - min > MIN_DYNAMIC_RANGE) {
+ // finish the rest of the rows quickly
+ for (yy++, offset += width; yy < BLOCK_SIZE; yy++, offset += width) {
+ for (int xx = 0; xx < BLOCK_SIZE; xx++) {
+ sum += luminances[offset + xx] & 0xFF;
+ }
+ }
+ }
+ }
+
+ // The default estimate is the average of the values in the block.
+ int average = sum >> (BLOCK_SIZE_POWER * 2);
+ if (max - min <= MIN_DYNAMIC_RANGE) {
+ // If variation within the block is low, assume this is a block with only light or only
+ // dark pixels. In that case we do not want to use the average, as it would divide this
+ // low contrast area into black and white pixels, essentially creating data out of noise.
+ //
+ // The default assumption is that the block is light/background. Since no estimate for
+ // the level of dark pixels exists locally, use half the min for the block.
+ average = min / 2;
+
+ if (y > 0 && x > 0) {
+ // Correct the "white background" assumption for blocks that have neighbors by comparing
+ // the pixels in this block to the previously calculated black points. This is based on
+ // the fact that dark barcode symbology is always surrounded by some amount of light
+ // background for which reasonable black point estimates were made. The bp estimated at
+ // the boundaries is used for the interior.
+
+ // The (min < bp) is arbitrary but works better than other heuristics that were tried.
+ int averageNeighborBlackPoint =
+ (blackPoints[y - 1][x] + (2 * blackPoints[y][x - 1]) + blackPoints[y - 1][x - 1]) / 4;
+ if (min < averageNeighborBlackPoint) {
+ average = averageNeighborBlackPoint;
+ }
+ }
+ }
+ blackPoints[y][x] = average;
+ }
+ }
+ return blackPoints;
+ }
+
+ /**
+ * Calculates the final BitMatrix once for all requests. This could be called once from the
+ * constructor instead, but there are some advantages to doing it lazily, such as making
+ * profiling easier, and not doing heavy lifting when callers don't expect it.
+ */
+ @Override
+ public BitMatrix getBlackMatrix() throws NotFoundException {
+ if (matrix != null) {
+ return matrix;
+ }
+ LuminanceSource source = getLuminanceSource();
+ int width = source.getWidth();
+ int height = source.getHeight();
+ if (width >= MINIMUM_DIMENSION && height >= MINIMUM_DIMENSION) {
+ byte[] luminances = source.getMatrix();
+ int subWidth = width >> BLOCK_SIZE_POWER;
+ if ((width & BLOCK_SIZE_MASK) != 0) {
+ subWidth++;
+ }
+ int subHeight = height >> BLOCK_SIZE_POWER;
+ if ((height & BLOCK_SIZE_MASK) != 0) {
+ subHeight++;
+ }
+ int[][] blackPoints = calculateBlackPoints(luminances, subWidth, subHeight, width, height);
+
+ BitMatrix newMatrix = new BitMatrix(width, height);
+ calculateThresholdForBlock(luminances, subWidth, subHeight, width, height, blackPoints, newMatrix);
+ matrix = newMatrix;
+ } else {
+ // If the image is too small, fall back to the global histogram approach.
+ matrix = super.getBlackMatrix();
+ }
+ return matrix;
+ }
+
+ @Override
+ public Binarizer createBinarizer(LuminanceSource source) {
+ return new HybridBinarizer(source);
+ }
+
+}
diff --git a/src/com/google/zxing/common/PerspectiveTransform.java b/src/com/google/zxing/common/PerspectiveTransform.java
new file mode 100644
index 0000000..68c278d
--- /dev/null
+++ b/src/com/google/zxing/common/PerspectiveTransform.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common;
+
+/**
+ *
This class implements a perspective transform in two dimensions. Given four source and four
+ * destination points, it will compute the transformation implied between them. The code is based
+ * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.
+ *
+ * @author Sean Owen
+ */
+public final class PerspectiveTransform {
+
+ private final float a11;
+ private final float a12;
+ private final float a13;
+ private final float a21;
+ private final float a22;
+ private final float a23;
+ private final float a31;
+ private final float a32;
+ private final float a33;
+
+ private PerspectiveTransform(float a11, float a21, float a31,
+ float a12, float a22, float a32,
+ float a13, float a23, float a33) {
+ this.a11 = a11;
+ this.a12 = a12;
+ this.a13 = a13;
+ this.a21 = a21;
+ this.a22 = a22;
+ this.a23 = a23;
+ this.a31 = a31;
+ this.a32 = a32;
+ this.a33 = a33;
+ }
+
+ public static PerspectiveTransform quadrilateralToQuadrilateral(float x0, float y0,
+ float x1, float y1,
+ float x2, float y2,
+ float x3, float y3,
+ float x0p, float y0p,
+ float x1p, float y1p,
+ float x2p, float y2p,
+ float x3p, float y3p) {
+
+ PerspectiveTransform qToS = quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
+ PerspectiveTransform sToQ = squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
+ return sToQ.times(qToS);
+ }
+
+ public static PerspectiveTransform squareToQuadrilateral(float x0, float y0,
+ float x1, float y1,
+ float x2, float y2,
+ float x3, float y3) {
+ float dx3 = x0 - x1 + x2 - x3;
+ float dy3 = y0 - y1 + y2 - y3;
+ if (dx3 == 0.0f && dy3 == 0.0f) {
+ // Affine
+ return new PerspectiveTransform(x1 - x0, x2 - x1, x0,
+ y1 - y0, y2 - y1, y0,
+ 0.0f, 0.0f, 1.0f);
+ } else {
+ float dx1 = x1 - x2;
+ float dx2 = x3 - x2;
+ float dy1 = y1 - y2;
+ float dy2 = y3 - y2;
+ float denominator = dx1 * dy2 - dx2 * dy1;
+ float a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
+ float a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
+ return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0,
+ y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0,
+ a13, a23, 1.0f);
+ }
+ }
+
+ public static PerspectiveTransform quadrilateralToSquare(float x0, float y0,
+ float x1, float y1,
+ float x2, float y2,
+ float x3, float y3) {
+ // Here, the adjoint serves as the inverse:
+ return squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();
+ }
+
+ public void transformPoints(float[] points) {
+ int max = points.length;
+ float a11 = this.a11;
+ float a12 = this.a12;
+ float a13 = this.a13;
+ float a21 = this.a21;
+ float a22 = this.a22;
+ float a23 = this.a23;
+ float a31 = this.a31;
+ float a32 = this.a32;
+ float a33 = this.a33;
+ for (int i = 0; i < max; i += 2) {
+ float x = points[i];
+ float y = points[i + 1];
+ float denominator = a13 * x + a23 * y + a33;
+ points[i] = (a11 * x + a21 * y + a31) / denominator;
+ points[i + 1] = (a12 * x + a22 * y + a32) / denominator;
+ }
+ }
+
+ public void transformPoints(float[] xValues, float[] yValues) {
+ int n = xValues.length;
+ for (int i = 0; i < n; i++) {
+ float x = xValues[i];
+ float y = yValues[i];
+ float denominator = a13 * x + a23 * y + a33;
+ xValues[i] = (a11 * x + a21 * y + a31) / denominator;
+ yValues[i] = (a12 * x + a22 * y + a32) / denominator;
+ }
+ }
+
+ 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..8057fba
--- /dev/null
+++ b/src/com/google/zxing/common/StringUtils.java
@@ -0,0 +1,214 @@
+/*
+ * 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 com.google.zxing.DecodeHintType;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+
+/**
+ * Common string-related functions.
+ *
+ * @author Sean Owen
+ * @author Alex Dupre
+ */
+public final class StringUtils {
+
+ public static final String SHIFT_JIS = "SJIS";
+ public static final String GB2312 = "GB2312";
+ private static final String PLATFORM_DEFAULT_ENCODING = Charset.defaultCharset().name();
+ private static final String EUC_JP = "EUC_JP";
+ private static final String UTF8 = "UTF8";
+ private static final String ISO88591 = "ISO8859_1";
+ private static final boolean ASSUME_SHIFT_JIS =
+ SHIFT_JIS.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING) ||
+ EUC_JP.equalsIgnoreCase(PLATFORM_DEFAULT_ENCODING);
+
+ private StringUtils() {
+ }
+
+ /**
+ * @param bytes bytes encoding a string, whose encoding should be guessed
+ * @param hints decode hints if applicable
+ * @return name of guessed encoding; at the moment will only guess one of:
+ * {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform
+ * default encoding if none of these can possibly be correct
+ */
+ public static String guessEncoding(byte[] bytes, Map hints) {
+ if (hints != null) {
+ String characterSet = (String) hints.get(DecodeHintType.CHARACTER_SET);
+ if (characterSet != null) {
+ return characterSet;
+ }
+ }
+ // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
+ // which should be by far the most common encodings.
+ int length = bytes.length;
+ boolean canBeISO88591 = true;
+ boolean canBeShiftJIS = true;
+ boolean canBeUTF8 = true;
+ int utf8BytesLeft = 0;
+ //int utf8LowChars = 0;
+ int utf2BytesChars = 0;
+ int utf3BytesChars = 0;
+ int utf4BytesChars = 0;
+ int sjisBytesLeft = 0;
+ //int sjisLowChars = 0;
+ int sjisKatakanaChars = 0;
+ //int sjisDoubleBytesChars = 0;
+ int sjisCurKatakanaWordLength = 0;
+ int sjisCurDoubleBytesWordLength = 0;
+ int sjisMaxKatakanaWordLength = 0;
+ int sjisMaxDoubleBytesWordLength = 0;
+ //int isoLowChars = 0;
+ //int isoHighChars = 0;
+ int isoHighOther = 0;
+
+ boolean utf8bom = bytes.length > 3 &&
+ bytes[0] == (byte) 0xEF &&
+ bytes[1] == (byte) 0xBB &&
+ bytes[2] == (byte) 0xBF;
+
+ for (int i = 0;
+ i < length && (canBeISO88591 || canBeShiftJIS || canBeUTF8);
+ i++) {
+
+ int value = bytes[i] & 0xFF;
+
+ // UTF-8 stuff
+ if (canBeUTF8) {
+ if (utf8BytesLeft > 0) {
+ if ((value & 0x80) == 0) {
+ canBeUTF8 = false;
+ } else {
+ utf8BytesLeft--;
+ }
+ } else if ((value & 0x80) != 0) {
+ if ((value & 0x40) == 0) {
+ canBeUTF8 = false;
+ } else {
+ utf8BytesLeft++;
+ if ((value & 0x20) == 0) {
+ utf2BytesChars++;
+ } else {
+ utf8BytesLeft++;
+ if ((value & 0x10) == 0) {
+ utf3BytesChars++;
+ } else {
+ utf8BytesLeft++;
+ if ((value & 0x08) == 0) {
+ utf4BytesChars++;
+ } else {
+ canBeUTF8 = false;
+ }
+ }
+ }
+ }
+ } //else {
+ //utf8LowChars++;
+ //}
+ }
+
+ // ISO-8859-1 stuff
+ if (canBeISO88591) {
+ if (value > 0x7F && value < 0xA0) {
+ canBeISO88591 = false;
+ } else if (value > 0x9F) {
+ if (value < 0xC0 || value == 0xD7 || value == 0xF7) {
+ isoHighOther++;
+ } //else {
+ //isoHighChars++;
+ //}
+ } //else {
+ //isoLowChars++;
+ //}
+ }
+
+ // Shift_JIS stuff
+ if (canBeShiftJIS) {
+ if (sjisBytesLeft > 0) {
+ if (value < 0x40 || value == 0x7F || value > 0xFC) {
+ canBeShiftJIS = false;
+ } else {
+ sjisBytesLeft--;
+ }
+ } else if (value == 0x80 || value == 0xA0 || value > 0xEF) {
+ canBeShiftJIS = false;
+ } else if (value > 0xA0 && value < 0xE0) {
+ sjisKatakanaChars++;
+ sjisCurDoubleBytesWordLength = 0;
+ sjisCurKatakanaWordLength++;
+ if (sjisCurKatakanaWordLength > sjisMaxKatakanaWordLength) {
+ sjisMaxKatakanaWordLength = sjisCurKatakanaWordLength;
+ }
+ } else if (value > 0x7F) {
+ sjisBytesLeft++;
+ //sjisDoubleBytesChars++;
+ sjisCurKatakanaWordLength = 0;
+ sjisCurDoubleBytesWordLength++;
+ if (sjisCurDoubleBytesWordLength > sjisMaxDoubleBytesWordLength) {
+ sjisMaxDoubleBytesWordLength = sjisCurDoubleBytesWordLength;
+ }
+ } else {
+ //sjisLowChars++;
+ sjisCurKatakanaWordLength = 0;
+ sjisCurDoubleBytesWordLength = 0;
+ }
+ }
+ }
+
+ if (canBeUTF8 && utf8BytesLeft > 0) {
+ canBeUTF8 = false;
+ }
+ if (canBeShiftJIS && sjisBytesLeft > 0) {
+ canBeShiftJIS = false;
+ }
+
+ // Easy -- if there is BOM or at least 1 valid not-single byte character (and no evidence it can't be UTF-8), done
+ if (canBeUTF8 && (utf8bom || utf2BytesChars + utf3BytesChars + utf4BytesChars > 0)) {
+ return UTF8;
+ }
+ // Easy -- if assuming Shift_JIS or at least 3 valid consecutive not-ascii characters (and no evidence it can't be), done
+ if (canBeShiftJIS && (ASSUME_SHIFT_JIS || sjisMaxKatakanaWordLength >= 3 || sjisMaxDoubleBytesWordLength >= 3)) {
+ return SHIFT_JIS;
+ }
+ // Distinguishing Shift_JIS and ISO-8859-1 can be a little tough for short words. The crude heuristic is:
+ // - If we saw
+ // - only two consecutive katakana chars in the whole text, or
+ // - at least 10% of bytes that could be "upper" not-alphanumeric Latin1,
+ // - then we conclude Shift_JIS, else ISO-8859-1
+ if (canBeISO88591 && canBeShiftJIS) {
+ return (sjisMaxKatakanaWordLength == 2 && sjisKatakanaChars == 2) || isoHighOther * 10 >= length
+ ? SHIFT_JIS : ISO88591;
+ }
+
+ // Otherwise, try in order ISO-8859-1, Shift JIS, UTF-8 and fall back to default platform encoding
+ if (canBeISO88591) {
+ return ISO88591;
+ }
+ if (canBeShiftJIS) {
+ return SHIFT_JIS;
+ }
+ if (canBeUTF8) {
+ return UTF8;
+ }
+ // Otherwise, we take a wild guess with platform encoding
+ return PLATFORM_DEFAULT_ENCODING;
+ }
+
+}
diff --git a/src/com/google/zxing/common/detector/MathUtils.java b/src/com/google/zxing/common/detector/MathUtils.java
new file mode 100644
index 0000000..857fe87
--- /dev/null
+++ b/src/com/google/zxing/common/detector/MathUtils.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2012 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common.detector;
+
+public final class MathUtils {
+
+ private MathUtils() {
+ }
+
+ /**
+ * Ends up being a bit faster than {@link Math#round(float)}. This merely rounds its
+ * argument to the nearest int, where x.5 rounds up to x+1. Semantics of this shortcut
+ * differ slightly from {@link Math#round(float)} in that half rounds down for negative
+ * values. -2.5 rounds to -3, not -2. For purposes here it makes no difference.
+ *
+ * @param d real value to round
+ * @return nearest {@code int}
+ */
+ public static int round(float d) {
+ return (int) (d + (d < 0.0f ? -0.5f : 0.5f));
+ }
+
+ public static float distance(float aX, float aY, float bX, float bY) {
+ float xDiff = aX - bX;
+ float yDiff = aY - bY;
+ return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+ }
+
+ public static float distance(int aX, int aY, int bX, int bY) {
+ int xDiff = aX - bX;
+ int yDiff = aY - bY;
+ return (float) Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+ }
+
+}
diff --git a/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java b/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java
new file mode 100644
index 0000000..0296131
--- /dev/null
+++ b/src/com/google/zxing/common/detector/MonochromeRectangleDetector.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * A somewhat generic detector that looks for a barcode-like rectangular region within an image.
+ * It looks within a mostly white region of an image for a region of black and white, but mostly
+ * black. It returns the four corners of the region, as best it can determine.
+ *
+ * @author Sean Owen
+ */
+public final class MonochromeRectangleDetector {
+
+ private static final int MAX_MODULES = 32;
+
+ private final BitMatrix image;
+
+ public MonochromeRectangleDetector(BitMatrix image) {
+ this.image = image;
+ }
+
+ /**
+ * Detects a rectangular region of black and white -- mostly black -- with a region of mostly
+ * white, in an image.
+ *
+ * @return {@link ResultPoint}[] describing the corners of the rectangular region. The first and
+ * last points are opposed on the diagonal, as are the second and third. The first point will be
+ * the topmost point and the last, the bottommost. The second point will be leftmost and the
+ * third, the rightmost
+ * @throws NotFoundException if no Data Matrix Code can be found
+ */
+ public ResultPoint[] detect() throws NotFoundException {
+ int height = image.getHeight();
+ int width = image.getWidth();
+ int halfHeight = height / 2;
+ int halfWidth = width / 2;
+ int deltaY = Math.max(1, height / (MAX_MODULES * 8));
+ int deltaX = Math.max(1, width / (MAX_MODULES * 8));
+
+ int top = 0;
+ int bottom = height;
+ int left = 0;
+ int right = width;
+ ResultPoint pointA = findCornerFromCenter(halfWidth, 0, left, right,
+ halfHeight, -deltaY, top, bottom, halfWidth / 2);
+ top = (int) pointA.getY() - 1;
+ ResultPoint pointB = findCornerFromCenter(halfWidth, -deltaX, left, right,
+ halfHeight, 0, top, bottom, halfHeight / 2);
+ left = (int) pointB.getX() - 1;
+ ResultPoint pointC = findCornerFromCenter(halfWidth, deltaX, left, right,
+ halfHeight, 0, top, bottom, halfHeight / 2);
+ right = (int) pointC.getX() + 1;
+ ResultPoint pointD = findCornerFromCenter(halfWidth, 0, left, right,
+ halfHeight, deltaY, top, bottom, halfWidth / 2);
+ 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 / 4);
+
+ 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) / 2;
+
+ // 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..b716357
--- /dev/null
+++ b/src/com/google/zxing/common/detector/WhiteRectangleDetector.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ *
+ * Detects a candidate barcode-like rectangular region within an image. It
+ * starts around the center of the image, increases the size of the candidate
+ * region until it finds a white rectangular region. By keeping track of the
+ * last black points it encountered, it determines the corners of the barcode.
+ *
+ *
+ * @author David Olivier
+ */
+public final class WhiteRectangleDetector {
+
+ private static final int INIT_SIZE = 10;
+ private static final int CORR = 1;
+
+ private final BitMatrix image;
+ private final int height;
+ private final int width;
+ private final int leftInit;
+ private final int rightInit;
+ private final int downInit;
+ private final int upInit;
+
+ public WhiteRectangleDetector(BitMatrix image) throws NotFoundException {
+ this(image, INIT_SIZE, image.getWidth() / 2, image.getHeight() / 2);
+ }
+
+ /**
+ * @param image barcode image to find a rectangle in
+ * @param initSize initial size of search area around center
+ * @param x x position of search center
+ * @param y y position of search center
+ * @throws NotFoundException if image is too small to accommodate {@code initSize}
+ */
+ public WhiteRectangleDetector(BitMatrix image, int initSize, int x, int y) throws NotFoundException {
+ this.image = image;
+ height = image.getHeight();
+ width = image.getWidth();
+ int halfsize = initSize / 2;
+ leftInit = x - halfsize;
+ rightInit = x + halfsize;
+ upInit = y - halfsize;
+ downInit = y + halfsize;
+ if (upInit < 0 || leftInit < 0 || downInit >= height || rightInit >= width) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ /**
+ *
+ * Detects a candidate barcode-like rectangular region within an image. It
+ * starts around the center of the image, increases the size of the candidate
+ * region until it finds a white rectangular region.
+ *
+ *
+ * @return {@link ResultPoint}[] describing the corners of the rectangular
+ * region. The first and last points are opposed on the diagonal, as
+ * are the second and third. The first point will be the topmost
+ * point and the last, the bottommost. The second point will be
+ * leftmost and the third, the rightmost
+ * @throws NotFoundException if no Data Matrix Code can be found
+ */
+ public ResultPoint[] detect() throws NotFoundException {
+
+ int left = leftInit;
+ int right = rightInit;
+ int up = upInit;
+ int down = downInit;
+ boolean sizeExceeded = false;
+ boolean aBlackPointFoundOnBorder = true;
+ boolean atLeastOneBlackPointFoundOnBorder = false;
+
+ boolean atLeastOneBlackPointFoundOnRight = false;
+ boolean atLeastOneBlackPointFoundOnBottom = false;
+ boolean atLeastOneBlackPointFoundOnLeft = false;
+ boolean atLeastOneBlackPointFoundOnTop = false;
+
+ while (aBlackPointFoundOnBorder) {
+
+ aBlackPointFoundOnBorder = false;
+
+ // .....
+ // . |
+ // .....
+ boolean rightBorderNotWhite = true;
+ while ((rightBorderNotWhite || !atLeastOneBlackPointFoundOnRight) && right < width) {
+ rightBorderNotWhite = containsBlackPoint(up, down, right, false);
+ if (rightBorderNotWhite) {
+ right++;
+ aBlackPointFoundOnBorder = true;
+ atLeastOneBlackPointFoundOnRight = true;
+ } else if (!atLeastOneBlackPointFoundOnRight) {
+ right++;
+ }
+ }
+
+ if (right >= width) {
+ sizeExceeded = true;
+ break;
+ }
+
+ // .....
+ // . .
+ // .___.
+ boolean bottomBorderNotWhite = true;
+ while ((bottomBorderNotWhite || !atLeastOneBlackPointFoundOnBottom) && down < height) {
+ bottomBorderNotWhite = containsBlackPoint(left, right, down, true);
+ if (bottomBorderNotWhite) {
+ down++;
+ aBlackPointFoundOnBorder = true;
+ atLeastOneBlackPointFoundOnBottom = true;
+ } else if (!atLeastOneBlackPointFoundOnBottom) {
+ down++;
+ }
+ }
+
+ if (down >= height) {
+ sizeExceeded = true;
+ break;
+ }
+
+ // .....
+ // | .
+ // .....
+ boolean leftBorderNotWhite = true;
+ while ((leftBorderNotWhite || !atLeastOneBlackPointFoundOnLeft) && left >= 0) {
+ leftBorderNotWhite = containsBlackPoint(up, down, left, false);
+ if (leftBorderNotWhite) {
+ left--;
+ aBlackPointFoundOnBorder = true;
+ atLeastOneBlackPointFoundOnLeft = true;
+ } else if (!atLeastOneBlackPointFoundOnLeft) {
+ left--;
+ }
+ }
+
+ if (left < 0) {
+ sizeExceeded = true;
+ break;
+ }
+
+ // .___.
+ // . .
+ // .....
+ boolean topBorderNotWhite = true;
+ while ((topBorderNotWhite || !atLeastOneBlackPointFoundOnTop) && up >= 0) {
+ topBorderNotWhite = containsBlackPoint(left, right, up, true);
+ if (topBorderNotWhite) {
+ up--;
+ aBlackPointFoundOnBorder = true;
+ atLeastOneBlackPointFoundOnTop = true;
+ } else if (!atLeastOneBlackPointFoundOnTop) {
+ up--;
+ }
+ }
+
+ if (up < 0) {
+ sizeExceeded = true;
+ break;
+ }
+
+ if (aBlackPointFoundOnBorder) {
+ atLeastOneBlackPointFoundOnBorder = true;
+ }
+
+ }
+
+ if (!sizeExceeded && atLeastOneBlackPointFoundOnBorder) {
+
+ int maxSize = right - left;
+
+ ResultPoint z = null;
+ for (int i = 1; i < maxSize; i++) {
+ z = getBlackPointOnSegment(left, down - i, left + i, down);
+ if (z != null) {
+ break;
+ }
+ }
+
+ if (z == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ ResultPoint t = null;
+ //go down right
+ for (int i = 1; i < maxSize; i++) {
+ t = getBlackPointOnSegment(left, up + i, left + i, up);
+ if (t != null) {
+ break;
+ }
+ }
+
+ if (t == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ ResultPoint x = null;
+ //go down left
+ for (int i = 1; i < maxSize; i++) {
+ x = getBlackPointOnSegment(right, up + i, right - i, up);
+ if (x != null) {
+ break;
+ }
+ }
+
+ if (x == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ ResultPoint y = null;
+ //go up left
+ for (int i = 1; i < maxSize; i++) {
+ y = getBlackPointOnSegment(right, down - i, right - i, down);
+ if (y != null) {
+ break;
+ }
+ }
+
+ if (y == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ return centerEdges(y, z, x, t);
+
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ private ResultPoint getBlackPointOnSegment(float aX, float aY, float bX, float bY) {
+ int dist = MathUtils.round(MathUtils.distance(aX, aY, bX, bY));
+ float xStep = (bX - aX) / dist;
+ float yStep = (bY - aY) / dist;
+
+ for (int i = 0; i < dist; i++) {
+ int x = MathUtils.round(aX + i * xStep);
+ int y = MathUtils.round(aY + i * yStep);
+ if (image.get(x, y)) {
+ return new ResultPoint(x, y);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * recenters the points of a constant distance towards the center
+ *
+ * @param y bottom most point
+ * @param z left most point
+ * @param x right most point
+ * @param t top most point
+ * @return {@link ResultPoint}[] describing the corners of the rectangular
+ * region. The first and last points are opposed on the diagonal, as
+ * are the second and third. The first point will be the topmost
+ * point and the last, the bottommost. The second point will be
+ * leftmost and the third, the rightmost
+ */
+ private ResultPoint[] centerEdges(ResultPoint y, ResultPoint z,
+ ResultPoint x, ResultPoint t) {
+
+ //
+ // t t
+ // z x
+ // x OR z
+ // y y
+ //
+
+ float yi = y.getX();
+ float yj = y.getY();
+ float zi = z.getX();
+ float zj = z.getY();
+ float xi = x.getX();
+ float xj = x.getY();
+ float ti = t.getX();
+ float tj = t.getY();
+
+ if (yi < width / 2.0f) {
+ return new ResultPoint[]{
+ new ResultPoint(ti - CORR, tj + CORR),
+ new ResultPoint(zi + CORR, zj + CORR),
+ new ResultPoint(xi - CORR, xj - CORR),
+ new ResultPoint(yi + CORR, yj - CORR)};
+ } else {
+ return new ResultPoint[]{
+ new ResultPoint(ti + CORR, tj + CORR),
+ new ResultPoint(zi + CORR, zj - CORR),
+ new ResultPoint(xi - CORR, xj + CORR),
+ new ResultPoint(yi - CORR, yj - CORR)};
+ }
+ }
+
+ /**
+ * Determines whether a segment contains a black point
+ *
+ * @param a min value of the scanned coordinate
+ * @param b max value of the scanned coordinate
+ * @param fixed value of fixed coordinate
+ * @param horizontal set to true if scan must be horizontal, false if vertical
+ * @return true if a black point has been found, else false.
+ */
+ private boolean containsBlackPoint(int a, int b, int fixed, boolean horizontal) {
+
+ if (horizontal) {
+ for (int x = a; x <= b; x++) {
+ if (image.get(x, fixed)) {
+ return true;
+ }
+ }
+ } else {
+ for (int y = a; y <= b; y++) {
+ if (image.get(fixed, y)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/GenericGF.java b/src/com/google/zxing/common/reedsolomon/GenericGF.java
new file mode 100644
index 0000000..f0c5853
--- /dev/null
+++ b/src/com/google/zxing/common/reedsolomon/GenericGF.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.common.reedsolomon;
+
+/**
+ * This class contains utility methods for performing mathematical operations over
+ * the Galois Fields. Operations use a given primitive polynomial in calculations.
+ *
+ *
Throughout this package, elements of the GF are represented as an {@code int}
+ * for convenience and speed (but at the cost of memory).
+ *
+ *
+ * @author Sean Owen
+ * @author David Olivier
+ */
+public final class GenericGF {
+
+ public static final GenericGF AZTEC_DATA_12 = new GenericGF(0x1069, 4096, 1); // x^12 + x^6 + x^5 + x^3 + 1
+ public static final GenericGF AZTEC_DATA_10 = new GenericGF(0x409, 1024, 1); // x^10 + x^3 + 1
+ public static final GenericGF AZTEC_DATA_6 = new GenericGF(0x43, 64, 1); // x^6 + x + 1
+ public static final GenericGF AZTEC_PARAM = new GenericGF(0x13, 16, 1); // x^4 + x + 1
+ public static final GenericGF QR_CODE_FIELD_256 = new GenericGF(0x011D, 256, 0); // x^8 + x^4 + x^3 + x^2 + 1
+ public static final GenericGF DATA_MATRIX_FIELD_256 = new GenericGF(0x012D, 256, 1); // x^8 + x^5 + x^3 + x^2 + 1
+ public static final GenericGF AZTEC_DATA_8 = DATA_MATRIX_FIELD_256;
+ public static final GenericGF MAXICODE_FIELD_64 = AZTEC_DATA_6;
+
+ private final int[] expTable;
+ private final int[] logTable;
+ private final GenericGFPoly zero;
+ private final GenericGFPoly one;
+ private final int size;
+ private final int primitive;
+ private final int generatorBase;
+
+ /**
+ * Create a representation of GF(size) using the given primitive polynomial.
+ *
+ * @param primitive irreducible polynomial whose coefficients are represented by
+ * the bits of an int, where the least-significant bit represents the constant
+ * coefficient
+ * @param size the size of the field
+ * @param b the factor b in the generator polynomial can be 0- or 1-based
+ * (g(x) = (x+a^b)(x+a^(b+1))...(x+a^(b+2t-1))).
+ * In most cases it should be 1, but for QR code it is 0.
+ */
+ public GenericGF(int primitive, int size, int b) {
+ this.primitive = primitive;
+ this.size = size;
+ this.generatorBase = b;
+
+ expTable = new int[size];
+ logTable = new int[size];
+ int x = 1;
+ for (int i = 0; i < size; i++) {
+ expTable[i] = x;
+ x *= 2; // we're assuming the generator alpha is 2
+ if (x >= size) {
+ x ^= primitive;
+ x &= size - 1;
+ }
+ }
+ for (int i = 0; i < size - 1; i++) {
+ logTable[expTable[i]] = i;
+ }
+ // logTable[0] == 0 but this should never be used
+ zero = new GenericGFPoly(this, new int[]{0});
+ one = new GenericGFPoly(this, new int[]{1});
+ }
+
+ /**
+ * Implements both addition and subtraction -- they are the same in GF(size).
+ *
+ * @return sum/difference of a and b
+ */
+ static int addOrSubtract(int a, int b) {
+ return a ^ b;
+ }
+
+ GenericGFPoly getZero() {
+ return zero;
+ }
+
+ GenericGFPoly getOne() {
+ return one;
+ }
+
+ /**
+ * @return the monomial representing coefficient * x^degree
+ */
+ GenericGFPoly 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 GenericGFPoly(this, coefficients);
+ }
+
+ /**
+ * @return 2 to the power of a in GF(size)
+ */
+ int exp(int a) {
+ return expTable[a];
+ }
+
+ /**
+ * @return base 2 log of a in GF(size)
+ */
+ 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[size - logTable[a] - 1];
+ }
+
+ /**
+ * @return product of a and b in GF(size)
+ */
+ int multiply(int a, int b) {
+ if (a == 0 || b == 0) {
+ return 0;
+ }
+ return expTable[(logTable[a] + logTable[b]) % (size - 1)];
+ }
+
+ public int getSize() {
+ return size;
+ }
+
+ public int getGeneratorBase() {
+ return generatorBase;
+ }
+
+ @Override
+ public String toString() {
+ return "GF(0x" + Integer.toHexString(primitive) + ',' + size + ')';
+ }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java b/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java
new file mode 100644
index 0000000..5cc91d7
--- /dev/null
+++ b/src/com/google/zxing/common/reedsolomon/GenericGFPoly.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common.reedsolomon;
+
+/**
+ * Represents a polynomial whose coefficients are elements of a GF.
+ * Instances of this class are immutable.
+ *
+ *
Much credit is due to William Rucklidge since portions of this code are an indirect
+ * port of his C++ Reed-Solomon implementation.
+ *
+ * @author Sean Owen
+ */
+final class GenericGFPoly {
+
+ private final GenericGF field;
+ private final int[] coefficients;
+
+ /**
+ * @param field the {@link GenericGF} instance representing the field to use
+ * to perform computations
+ * @param coefficients coefficients as ints representing elements of GF(size), arranged
+ * from most significant (highest-power term) coefficient to least significant
+ * @throws IllegalArgumentException if argument is null or empty,
+ * or if leading coefficient is 0 and this is not a
+ * constant polynomial (that is, it is not the monomial "0")
+ */
+ GenericGFPoly(GenericGF field, int[] coefficients) {
+ if (coefficients.length == 0) {
+ throw new IllegalArgumentException();
+ }
+ this.field = field;
+ int coefficientsLength = coefficients.length;
+ if (coefficientsLength > 1 && coefficients[0] == 0) {
+ // Leading term must be non-zero for anything except the constant polynomial "0"
+ int firstNonZero = 1;
+ while (firstNonZero < coefficientsLength && coefficients[firstNonZero] == 0) {
+ firstNonZero++;
+ }
+ if (firstNonZero == coefficientsLength) {
+ this.coefficients = new int[]{0};
+ } else {
+ this.coefficients = new int[coefficientsLength - firstNonZero];
+ System.arraycopy(coefficients,
+ firstNonZero,
+ this.coefficients,
+ 0,
+ this.coefficients.length);
+ }
+ } else {
+ this.coefficients = coefficients;
+ }
+ }
+
+ int[] getCoefficients() {
+ return coefficients;
+ }
+
+ /**
+ * @return degree of this polynomial
+ */
+ int getDegree() {
+ return coefficients.length - 1;
+ }
+
+ /**
+ * @return true iff this polynomial is the monomial "0"
+ */
+ boolean isZero() {
+ return coefficients[0] == 0;
+ }
+
+ /**
+ * @return coefficient of x^degree term in this polynomial
+ */
+ int getCoefficient(int degree) {
+ return coefficients[coefficients.length - 1 - degree];
+ }
+
+ /**
+ * @return evaluation of this polynomial at a given point
+ */
+ int evaluateAt(int a) {
+ if (a == 0) {
+ // Just return the x^0 coefficient
+ return getCoefficient(0);
+ }
+ int size = coefficients.length;
+ if (a == 1) {
+ // Just the sum of the coefficients
+ int result = 0;
+ for (int coefficient : coefficients) {
+ result = GenericGF.addOrSubtract(result, coefficient);
+ }
+ return result;
+ }
+ int result = coefficients[0];
+ for (int i = 1; i < size; i++) {
+ result = GenericGF.addOrSubtract(field.multiply(a, result), coefficients[i]);
+ }
+ return result;
+ }
+
+ GenericGFPoly addOrSubtract(GenericGFPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
+ }
+ if (isZero()) {
+ return other;
+ }
+ if (other.isZero()) {
+ return this;
+ }
+
+ int[] smallerCoefficients = this.coefficients;
+ int[] largerCoefficients = other.coefficients;
+ if (smallerCoefficients.length > largerCoefficients.length) {
+ int[] temp = smallerCoefficients;
+ smallerCoefficients = largerCoefficients;
+ largerCoefficients = temp;
+ }
+ int[] sumDiff = new int[largerCoefficients.length];
+ int lengthDiff = largerCoefficients.length - smallerCoefficients.length;
+ // Copy high-order terms only found in higher-degree polynomial's coefficients
+ System.arraycopy(largerCoefficients, 0, sumDiff, 0, lengthDiff);
+
+ for (int i = lengthDiff; i < largerCoefficients.length; i++) {
+ sumDiff[i] = GenericGF.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
+ }
+
+ return new GenericGFPoly(field, sumDiff);
+ }
+
+ GenericGFPoly multiply(GenericGFPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
+ }
+ if (isZero() || other.isZero()) {
+ return field.getZero();
+ }
+ int[] aCoefficients = this.coefficients;
+ int aLength = aCoefficients.length;
+ int[] bCoefficients = other.coefficients;
+ int bLength = bCoefficients.length;
+ int[] product = new int[aLength + bLength - 1];
+ for (int i = 0; i < aLength; i++) {
+ int aCoeff = aCoefficients[i];
+ for (int j = 0; j < bLength; j++) {
+ product[i + j] = GenericGF.addOrSubtract(product[i + j],
+ field.multiply(aCoeff, bCoefficients[j]));
+ }
+ }
+ return new GenericGFPoly(field, product);
+ }
+
+ GenericGFPoly multiply(int scalar) {
+ if (scalar == 0) {
+ return field.getZero();
+ }
+ if (scalar == 1) {
+ return this;
+ }
+ int size = coefficients.length;
+ int[] product = new int[size];
+ for (int i = 0; i < size; i++) {
+ product[i] = field.multiply(coefficients[i], scalar);
+ }
+ return new GenericGFPoly(field, product);
+ }
+
+ GenericGFPoly multiplyByMonomial(int degree, int coefficient) {
+ if (degree < 0) {
+ throw new IllegalArgumentException();
+ }
+ if (coefficient == 0) {
+ return field.getZero();
+ }
+ int size = coefficients.length;
+ int[] product = new int[size + degree];
+ for (int i = 0; i < size; i++) {
+ product[i] = field.multiply(coefficients[i], coefficient);
+ }
+ return new GenericGFPoly(field, product);
+ }
+
+ GenericGFPoly[] divide(GenericGFPoly other) {
+ if (!field.equals(other.field)) {
+ throw new IllegalArgumentException("GenericGFPolys do not have same GenericGF field");
+ }
+ if (other.isZero()) {
+ throw new IllegalArgumentException("Divide by 0");
+ }
+
+ GenericGFPoly quotient = field.getZero();
+ GenericGFPoly remainder = this;
+
+ int denominatorLeadingTerm = other.getCoefficient(other.getDegree());
+ int inverseDenominatorLeadingTerm = field.inverse(denominatorLeadingTerm);
+
+ while (remainder.getDegree() >= other.getDegree() && !remainder.isZero()) {
+ int degreeDifference = remainder.getDegree() - other.getDegree();
+ int scale = field.multiply(remainder.getCoefficient(remainder.getDegree()), inverseDenominatorLeadingTerm);
+ GenericGFPoly term = other.multiplyByMonomial(degreeDifference, scale);
+ GenericGFPoly iterationQuotient = field.buildMonomial(degreeDifference, scale);
+ quotient = quotient.addOrSubtract(iterationQuotient);
+ remainder = remainder.addOrSubtract(term);
+ }
+
+ return new GenericGFPoly[]{quotient, remainder};
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder(8 * getDegree());
+ for (int degree = getDegree(); degree >= 0; degree--) {
+ int coefficient = getCoefficient(degree);
+ if (coefficient != 0) {
+ if (coefficient < 0) {
+ result.append(" - ");
+ coefficient = -coefficient;
+ } else {
+ if (result.length() > 0) {
+ result.append(" + ");
+ }
+ }
+ if (degree == 0 || coefficient != 1) {
+ int alphaPower = field.log(coefficient);
+ if (alphaPower == 0) {
+ result.append('1');
+ } else if (alphaPower == 1) {
+ result.append('a');
+ } else {
+ result.append("a^");
+ result.append(alphaPower);
+ }
+ }
+ if (degree != 0) {
+ if (degree == 1) {
+ result.append('x');
+ } else {
+ result.append("x^");
+ result.append(degree);
+ }
+ }
+ }
+ }
+ return result.toString();
+ }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
new file mode 100644
index 0000000..9ff7764
--- /dev/null
+++ b/src/com/google/zxing/common/reedsolomon/ReedSolomonDecoder.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common.reedsolomon;
+
+/**
+ * Implements Reed-Solomon decoding, as the name implies.
+ *
+ *
The algorithm will not be explained here, but the following references were helpful
+ * in creating this implementation:
+ *
+ *
+ *
+ *
Much credit is due to William Rucklidge since portions of this code are an indirect
+ * port of his C++ Reed-Solomon implementation.
+ *
+ * @author Sean Owen
+ * @author William Rucklidge
+ * @author sanfordsquires
+ */
+public final class ReedSolomonDecoder {
+
+ private final GenericGF field;
+
+ public ReedSolomonDecoder(GenericGF field) {
+ this.field = field;
+ }
+
+ /**
+ * Decodes given set of received codewords, which include both data and error-correction
+ * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
+ * in the input.
+ *
+ * @param received data and error-correction codewords
+ * @param twoS number of error-correction codewords available
+ * @throws ReedSolomonException if decoding fails for any reason
+ */
+ public void decode(int[] received, int twoS) throws ReedSolomonException {
+ GenericGFPoly poly = new GenericGFPoly(field, received);
+ int[] syndromeCoefficients = new int[twoS];
+ boolean noError = true;
+ for (int i = 0; i < twoS; i++) {
+ int eval = poly.evaluateAt(field.exp(i + field.getGeneratorBase()));
+ syndromeCoefficients[syndromeCoefficients.length - 1 - i] = eval;
+ if (eval != 0) {
+ noError = false;
+ }
+ }
+ if (noError) {
+ return;
+ }
+ GenericGFPoly syndrome = new GenericGFPoly(field, syndromeCoefficients);
+ GenericGFPoly[] sigmaOmega =
+ runEuclideanAlgorithm(field.buildMonomial(twoS, 1), syndrome, twoS);
+ GenericGFPoly sigma = sigmaOmega[0];
+ GenericGFPoly omega = sigmaOmega[1];
+ int[] errorLocations = findErrorLocations(sigma);
+ int[] errorMagnitudes = findErrorMagnitudes(omega, errorLocations);
+ for (int i = 0; i < errorLocations.length; i++) {
+ int position = received.length - 1 - field.log(errorLocations[i]);
+ if (position < 0) {
+ throw new ReedSolomonException("Bad error location");
+ }
+ received[position] = GenericGF.addOrSubtract(received[position], errorMagnitudes[i]);
+ }
+ }
+
+ private GenericGFPoly[] runEuclideanAlgorithm(GenericGFPoly a, GenericGFPoly b, int R)
+ throws ReedSolomonException {
+ // Assume a's degree is >= b's
+ if (a.getDegree() < b.getDegree()) {
+ GenericGFPoly temp = a;
+ a = b;
+ b = temp;
+ }
+
+ GenericGFPoly rLast = a;
+ GenericGFPoly r = b;
+ GenericGFPoly tLast = field.getZero();
+ GenericGFPoly t = field.getOne();
+
+ // Run Euclidean algorithm until r's degree is less than R/2
+ while (r.getDegree() >= R / 2) {
+ GenericGFPoly rLastLast = rLast;
+ GenericGFPoly tLastLast = tLast;
+ rLast = r;
+ tLast = t;
+
+ // Divide rLastLast by rLast, with quotient in q and remainder in r
+ if (rLast.isZero()) {
+ // Oops, Euclidean algorithm already terminated?
+ throw new ReedSolomonException("r_{i-1} was zero");
+ }
+ r = rLastLast;
+ GenericGFPoly q = field.getZero();
+ int denominatorLeadingTerm = rLast.getCoefficient(rLast.getDegree());
+ int dltInverse = field.inverse(denominatorLeadingTerm);
+ while (r.getDegree() >= rLast.getDegree() && !r.isZero()) {
+ int degreeDiff = r.getDegree() - rLast.getDegree();
+ int scale = field.multiply(r.getCoefficient(r.getDegree()), dltInverse);
+ q = q.addOrSubtract(field.buildMonomial(degreeDiff, scale));
+ r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale));
+ }
+
+ t = q.multiply(tLast).addOrSubtract(tLastLast);
+
+ if (r.getDegree() >= rLast.getDegree()) {
+ throw new IllegalStateException("Division algorithm failed to reduce polynomial?");
+ }
+ }
+
+ int sigmaTildeAtZero = t.getCoefficient(0);
+ if (sigmaTildeAtZero == 0) {
+ throw new ReedSolomonException("sigmaTilde(0) was zero");
+ }
+
+ int inverse = field.inverse(sigmaTildeAtZero);
+ GenericGFPoly sigma = t.multiply(inverse);
+ GenericGFPoly omega = r.multiply(inverse);
+ return new GenericGFPoly[]{sigma, omega};
+ }
+
+ private int[] findErrorLocations(GenericGFPoly errorLocator) throws ReedSolomonException {
+ // This is a direct application of Chien's search
+ int numErrors = errorLocator.getDegree();
+ if (numErrors == 1) { // shortcut
+ return new int[]{errorLocator.getCoefficient(1)};
+ }
+ int[] result = new int[numErrors];
+ int e = 0;
+ for (int i = 1; i < field.getSize() && e < numErrors; i++) {
+ if (errorLocator.evaluateAt(i) == 0) {
+ result[e] = field.inverse(i);
+ e++;
+ }
+ }
+ if (e != numErrors) {
+ throw new ReedSolomonException("Error locator degree does not match number of roots");
+ }
+ return result;
+ }
+
+ private int[] findErrorMagnitudes(GenericGFPoly errorEvaluator, int[] errorLocations) {
+ // This is directly applying Forney's Formula
+ int s = errorLocations.length;
+ int[] result = new int[s];
+ for (int i = 0; i < s; i++) {
+ int xiInverse = field.inverse(errorLocations[i]);
+ int denominator = 1;
+ for (int j = 0; j < s; j++) {
+ if (i != j) {
+ //denominator = field.multiply(denominator,
+ // GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse)));
+ // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
+ // Below is a funny-looking workaround from Steven Parkes
+ int term = field.multiply(errorLocations[j], xiInverse);
+ int termPlus1 = (term & 0x1) == 0 ? term | 1 : term & ~1;
+ denominator = field.multiply(denominator, termPlus1);
+ }
+ }
+ result[i] = field.multiply(errorEvaluator.evaluateAt(xiInverse),
+ field.inverse(denominator));
+ if (field.getGeneratorBase() != 0) {
+ result[i] = field.multiply(result[i], xiInverse);
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java
new file mode 100644
index 0000000..9428f14
--- /dev/null
+++ b/src/com/google/zxing/common/reedsolomon/ReedSolomonEncoder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.common.reedsolomon;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implements Reed-Solomon enbcoding, as the name implies.
+ *
+ * @author Sean Owen
+ * @author William Rucklidge
+ */
+public final class ReedSolomonEncoder {
+
+ private final GenericGF field;
+ private final List cachedGenerators;
+
+ public ReedSolomonEncoder(GenericGF field) {
+ this.field = field;
+ this.cachedGenerators = new ArrayList<>();
+ cachedGenerators.add(new GenericGFPoly(field, new int[]{1}));
+ }
+
+ private GenericGFPoly buildGenerator(int degree) {
+ if (degree >= cachedGenerators.size()) {
+ GenericGFPoly lastGenerator = cachedGenerators.get(cachedGenerators.size() - 1);
+ for (int d = cachedGenerators.size(); d <= degree; d++) {
+ GenericGFPoly nextGenerator = lastGenerator.multiply(
+ new GenericGFPoly(field, new int[]{1, field.exp(d - 1 + field.getGeneratorBase())}));
+ cachedGenerators.add(nextGenerator);
+ lastGenerator = nextGenerator;
+ }
+ }
+ return cachedGenerators.get(degree);
+ }
+
+ public void encode(int[] toEncode, int ecBytes) {
+ if (ecBytes == 0) {
+ throw new IllegalArgumentException("No error correction bytes");
+ }
+ int dataBytes = toEncode.length - ecBytes;
+ if (dataBytes <= 0) {
+ throw new IllegalArgumentException("No data bytes provided");
+ }
+ GenericGFPoly generator = buildGenerator(ecBytes);
+ int[] infoCoefficients = new int[dataBytes];
+ System.arraycopy(toEncode, 0, infoCoefficients, 0, dataBytes);
+ GenericGFPoly info = new GenericGFPoly(field, infoCoefficients);
+ info = info.multiplyByMonomial(ecBytes, 1);
+ GenericGFPoly remainder = info.divide(generator)[1];
+ int[] coefficients = remainder.getCoefficients();
+ int numZeroCoefficients = ecBytes - coefficients.length;
+ for (int i = 0; i < numZeroCoefficients; i++) {
+ toEncode[dataBytes + i] = 0;
+ }
+ System.arraycopy(coefficients, 0, toEncode, dataBytes + numZeroCoefficients, coefficients.length);
+ }
+
+}
diff --git a/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java b/src/com/google/zxing/common/reedsolomon/ReedSolomonException.java
new file mode 100644
index 0000000..41142ea
--- /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..98df4bf
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/DataMatrixReader.java
@@ -0,0 +1,152 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.datamatrix.decoder.Decoder;
+import com.google.zxing.datamatrix.detector.Detector;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode Data Matrix codes in an image.
+ *
+ * @author bbrown@google.com (Brian Brown)
+ */
+public final class DataMatrixReader implements Reader {
+
+ private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
+
+ private final Decoder decoder = new Decoder();
+
+ /**
+ * This method detects a code in a "pure" image -- that is, pure monochrome image
+ * which contains only an unrotated, unskewed, image of a code, with some white border
+ * around it. This is a specialized method that works exceptionally fast in this special
+ * case.
+ *
+ * @see com.google.zxing.qrcode.QRCodeReader#extractPureBits(BitMatrix)
+ */
+ private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException {
+
+ int[] leftTopBlack = image.getTopLeftOnBit();
+ int[] rightBottomBlack = image.getBottomRightOnBit();
+ if (leftTopBlack == null || rightBottomBlack == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int moduleSize = moduleSize(leftTopBlack, image);
+
+ int top = leftTopBlack[1];
+ int bottom = rightBottomBlack[1];
+ int left = leftTopBlack[0];
+ int right = rightBottomBlack[0];
+
+ int matrixWidth = (right - left + 1) / moduleSize;
+ int matrixHeight = (bottom - top + 1) / moduleSize;
+ if (matrixWidth <= 0 || matrixHeight <= 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Push in the "border" by half the module width so that we start
+ // sampling in the middle of the module. Just in case the image is a
+ // little off, this will help recover.
+ int nudge = moduleSize / 2;
+ top += nudge;
+ left += nudge;
+
+ // Now just read off the bits
+ BitMatrix bits = new BitMatrix(matrixWidth, matrixHeight);
+ for (int y = 0; y < matrixHeight; y++) {
+ int iOffset = top + y * moduleSize;
+ for (int x = 0; x < matrixWidth; x++) {
+ if (image.get(left + x * moduleSize, iOffset)) {
+ bits.set(x, y);
+ }
+ }
+ }
+ return bits;
+ }
+
+ private static int moduleSize(int[] leftTopBlack, BitMatrix image) throws NotFoundException {
+ int width = image.getWidth();
+ int x = leftTopBlack[0];
+ int y = leftTopBlack[1];
+ while (x < width && image.get(x, y)) {
+ x++;
+ }
+ if (x == width) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int moduleSize = x - leftTopBlack[0];
+ if (moduleSize == 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ return moduleSize;
+ }
+
+ /**
+ * Locates and decodes a Data Matrix code in an image.
+ *
+ * @return a String representing the content encoded by the Data Matrix code
+ * @throws NotFoundException if a Data Matrix code cannot be found
+ * @throws FormatException if a Data Matrix code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+ DecoderResult decoderResult;
+ ResultPoint[] points;
+ if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
+ BitMatrix bits = extractPureBits(image.getBlackMatrix());
+ decoderResult = decoder.decode(bits);
+ points = NO_POINTS;
+ } else {
+ DetectorResult detectorResult = new Detector(image.getBlackMatrix()).detect();
+ decoderResult = decoder.decode(detectorResult.getBits());
+ points = detectorResult.getPoints();
+ }
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points,
+ BarcodeFormat.DATA_MATRIX);
+ List byteSegments = decoderResult.getByteSegments();
+ if (byteSegments != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
+ }
+ String ecLevel = decoderResult.getECLevel();
+ if (ecLevel != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ // do nothing
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/datamatrix/DataMatrixWriter.java b/src/com/google/zxing/datamatrix/DataMatrixWriter.java
new file mode 100644
index 0000000..5a71210
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/DataMatrixWriter.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.Dimension;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.Writer;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.datamatrix.encoder.*;
+import com.google.zxing.qrcode.encoder.ByteMatrix;
+
+import java.util.Map;
+
+/**
+ * This object renders a Data Matrix code as a BitMatrix 2D array of greyscale values.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Guillaume Le Biller Added to zxing lib.
+ */
+public final class DataMatrixWriter implements Writer {
+
+ /**
+ * Encode the given symbol info to a bit matrix.
+ *
+ * @param placement The DataMatrix placement.
+ * @param symbolInfo The symbol info to encode.
+ * @return The bit matrix generated.
+ */
+ private static BitMatrix encodeLowLevel(DefaultPlacement placement, SymbolInfo symbolInfo) {
+ int symbolWidth = symbolInfo.getSymbolDataWidth();
+ int symbolHeight = symbolInfo.getSymbolDataHeight();
+
+ ByteMatrix matrix = new ByteMatrix(symbolInfo.getSymbolWidth(), symbolInfo.getSymbolHeight());
+
+ int matrixY = 0;
+
+ for (int y = 0; y < symbolHeight; y++) {
+ // Fill the top edge with alternate 0 / 1
+ int matrixX;
+ if ((y % symbolInfo.matrixHeight) == 0) {
+ matrixX = 0;
+ for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) {
+ matrix.set(matrixX, matrixY, (x % 2) == 0);
+ matrixX++;
+ }
+ matrixY++;
+ }
+ matrixX = 0;
+ for (int x = 0; x < symbolWidth; x++) {
+ // Fill the right edge with full 1
+ if ((x % symbolInfo.matrixWidth) == 0) {
+ matrix.set(matrixX, matrixY, true);
+ matrixX++;
+ }
+ matrix.set(matrixX, matrixY, placement.getBit(x, y));
+ matrixX++;
+ // Fill the right edge with alternate 0 / 1
+ if ((x % symbolInfo.matrixWidth) == symbolInfo.matrixWidth - 1) {
+ matrix.set(matrixX, matrixY, (y % 2) == 0);
+ matrixX++;
+ }
+ }
+ matrixY++;
+ // Fill the bottom edge with full 1
+ if ((y % symbolInfo.matrixHeight) == symbolInfo.matrixHeight - 1) {
+ matrixX = 0;
+ for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) {
+ matrix.set(matrixX, matrixY, true);
+ matrixX++;
+ }
+ matrixY++;
+ }
+ }
+
+ return convertByteMatrixToBitMatrix(matrix);
+ }
+
+ /**
+ * Convert the ByteMatrix to BitMatrix.
+ *
+ * @param matrix The input matrix.
+ * @return The output matrix.
+ */
+ private static BitMatrix convertByteMatrixToBitMatrix(ByteMatrix matrix) {
+ int matrixWidgth = matrix.getWidth();
+ int matrixHeight = matrix.getHeight();
+
+ BitMatrix output = new BitMatrix(matrixWidgth, matrixHeight);
+ output.clear();
+ for (int i = 0; i < matrixWidgth; i++) {
+ for (int j = 0; j < matrixHeight; j++) {
+ // Zero is white in the bytematrix
+ if (matrix.get(i, j) == 1) {
+ output.set(i, j);
+ }
+ }
+ }
+
+ return output;
+ }
+
+ @Override
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height) {
+ return encode(contents, format, width, height, null);
+ }
+
+ @Override
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height, Map hints) {
+
+ if (contents.isEmpty()) {
+ throw new IllegalArgumentException("Found empty contents");
+ }
+
+ if (format != BarcodeFormat.DATA_MATRIX) {
+ throw new IllegalArgumentException("Can only encode DATA_MATRIX, but got " + format);
+ }
+
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Requested dimensions are too small: " + width + 'x' + height);
+ }
+
+ // Try to get force shape & min / max size
+ SymbolShapeHint shape = SymbolShapeHint.FORCE_NONE;
+ Dimension minSize = new Dimension(width, height);
+ Dimension maxSize = null;
+ if (hints != null) {
+ SymbolShapeHint requestedShape = (SymbolShapeHint) hints.get(EncodeHintType.DATA_MATRIX_SHAPE);
+ if (requestedShape != null) {
+ shape = requestedShape;
+ }
+ @SuppressWarnings("deprecation")
+ Dimension requestedMinSize = (Dimension) hints.get(EncodeHintType.MIN_SIZE);
+ if (requestedMinSize != null) {
+ minSize = requestedMinSize;
+ }
+ @SuppressWarnings("deprecation")
+ Dimension requestedMaxSize = (Dimension) hints.get(EncodeHintType.MAX_SIZE);
+ if (requestedMaxSize != null) {
+ maxSize = requestedMaxSize;
+ }
+ }
+
+
+ //1. step: Data encodation
+ String encoded = HighLevelEncoder.encodeHighLevel(contents, shape, minSize, maxSize);
+
+ SymbolInfo symbolInfo = SymbolInfo.lookup(encoded.length(), shape, minSize, maxSize, true);
+
+ //2. step: ECC generation
+ String codewords = ErrorCorrection.encodeECC200(encoded, symbolInfo);
+
+ //3. step: Module placement in Matrix
+ DefaultPlacement placement =
+ new DefaultPlacement(codewords, symbolInfo.getSymbolDataWidth(), symbolInfo.getSymbolDataHeight());
+ placement.place();
+
+ //4. step: low-level encoding
+ return encodeLowLevel(placement, symbolInfo);
+ }
+
+}
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..d8d3f3e
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/decoder/BitMatrixParser.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * @author bbrown@google.com (Brian Brown)
+ */
+final class BitMatrixParser {
+
+ private final BitMatrix mappingBitMatrix;
+ private final BitMatrix readMappingMatrix;
+ private final Version version;
+
+ /**
+ * @param bitMatrix {@link BitMatrix} to parse
+ * @throws FormatException if dimension is < 8 or > 144 or not 0 mod 2
+ */
+ BitMatrixParser(BitMatrix bitMatrix) throws FormatException {
+ int dimension = bitMatrix.getHeight();
+ if (dimension < 8 || dimension > 144 || (dimension & 0x01) != 0) {
+ throw FormatException.getFormatInstance();
+ }
+
+ version = readVersion(bitMatrix);
+ this.mappingBitMatrix = extractDataRegion(bitMatrix);
+ this.readMappingMatrix = new BitMatrix(this.mappingBitMatrix.getWidth(), this.mappingBitMatrix.getHeight());
+ }
+
+ /**
+ * Creates the version object based on the dimension of the original bit matrix from
+ * the datamatrix code.
+ *
+ *
See ISO 16022:2006 Table 7 - ECC 200 symbol attributes
+ *
+ * @param bitMatrix Original {@link BitMatrix} including alignment patterns
+ * @return {@link Version} encapsulating the Data Matrix Code's "version"
+ * @throws FormatException if the dimensions of the mapping matrix are not valid
+ * Data Matrix dimensions.
+ */
+ private static Version readVersion(BitMatrix bitMatrix) throws FormatException {
+ int numRows = bitMatrix.getHeight();
+ int numColumns = bitMatrix.getWidth();
+ return Version.getVersionForDimensions(numRows, numColumns);
+ }
+
+ Version getVersion() {
+ return version;
+ }
+
+ /**
+ * Reads the bits in the {@link BitMatrix} representing the mapping matrix (No alignment patterns)
+ * in the correct order in order to reconstitute the codewords bytes contained within the
+ * Data Matrix Code.
+ *
+ * @return bytes encoded within the Data Matrix Code
+ * @throws FormatException if the exact number of bytes expected is not read
+ */
+ byte[] readCodewords() throws FormatException {
+
+ byte[] result = new byte[version.getTotalCodewords()];
+ int resultOffset = 0;
+
+ int row = 4;
+ int column = 0;
+
+ int numRows = mappingBitMatrix.getHeight();
+ int numColumns = mappingBitMatrix.getWidth();
+
+ boolean corner1Read = false;
+ boolean corner2Read = false;
+ boolean corner3Read = false;
+ boolean corner4Read = false;
+
+ // Read all of the codewords
+ do {
+ // Check the four corner cases
+ if ((row == numRows) && (column == 0) && !corner1Read) {
+ result[resultOffset++] = (byte) readCorner1(numRows, numColumns);
+ row -= 2;
+ column += 2;
+ corner1Read = true;
+ } else if ((row == numRows - 2) && (column == 0) && ((numColumns & 0x03) != 0) && !corner2Read) {
+ result[resultOffset++] = (byte) readCorner2(numRows, numColumns);
+ row -= 2;
+ column += 2;
+ corner2Read = true;
+ } else if ((row == numRows + 4) && (column == 2) && ((numColumns & 0x07) == 0) && !corner3Read) {
+ result[resultOffset++] = (byte) readCorner3(numRows, numColumns);
+ row -= 2;
+ column += 2;
+ corner3Read = true;
+ } else if ((row == numRows - 2) && (column == 0) && ((numColumns & 0x07) == 4) && !corner4Read) {
+ result[resultOffset++] = (byte) readCorner4(numRows, numColumns);
+ row -= 2;
+ column += 2;
+ corner4Read = true;
+ } else {
+ // Sweep upward diagonally to the right
+ do {
+ if ((row < numRows) && (column >= 0) && !readMappingMatrix.get(column, row)) {
+ result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns);
+ }
+ row -= 2;
+ column += 2;
+ } while ((row >= 0) && (column < numColumns));
+ row += 1;
+ column += 3;
+
+ // Sweep downward diagonally to the left
+ do {
+ if ((row >= 0) && (column < numColumns) && !readMappingMatrix.get(column, row)) {
+ result[resultOffset++] = (byte) readUtah(row, column, numRows, numColumns);
+ }
+ row += 2;
+ column -= 2;
+ } while ((row < numRows) && (column >= 0));
+ row += 3;
+ column += 1;
+ }
+ } while ((row < numRows) || (column < numColumns));
+
+ if (resultOffset != version.getTotalCodewords()) {
+ throw FormatException.getFormatInstance();
+ }
+ return result;
+ }
+
+ /**
+ * Reads a bit of the mapping matrix accounting for boundary wrapping.
+ *
+ * @param row Row to read in the mapping matrix
+ * @param column Column to read in the mapping matrix
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return value of the given bit in the mapping matrix
+ */
+ boolean readModule(int row, int column, int numRows, int numColumns) {
+ // Adjust the row and column indices based on boundary wrapping
+ if (row < 0) {
+ row += numRows;
+ column += 4 - ((numRows + 4) & 0x07);
+ }
+ if (column < 0) {
+ column += numColumns;
+ row += 4 - ((numColumns + 4) & 0x07);
+ }
+ readMappingMatrix.set(column, row);
+ return mappingBitMatrix.get(column, row);
+ }
+
+ /**
+ * Reads the 8 bits of the standard Utah-shaped pattern.
+ *
+ *
See ISO 16022:2006, 5.8.1 Figure 6
+ *
+ * @param row Current row in the mapping matrix, anchored at the 8th bit (LSB) of the pattern
+ * @param column Current column in the mapping matrix, anchored at the 8th bit (LSB) of the pattern
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the utah shape
+ */
+ int readUtah(int row, int column, int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(row - 2, column - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row - 2, column - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row - 1, column - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row - 1, column - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row - 1, column, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row, column - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row, column - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(row, column, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Reads the 8 bits of the special corner condition 1.
+ *
+ *
See ISO 16022:2006, Figure F.3
+ *
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the Corner condition 1
+ */
+ int readCorner1(int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(numRows - 1, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(2, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(3, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Reads the 8 bits of the special corner condition 2.
+ *
+ *
See ISO 16022:2006, Figure F.4
+ *
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the Corner condition 2
+ */
+ int readCorner2(int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(numRows - 3, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 2, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 4, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 3, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Reads the 8 bits of the special corner condition 3.
+ *
+ *
See ISO 16022:2006, Figure F.5
+ *
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the Corner condition 3
+ */
+ int readCorner3(int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(numRows - 1, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 3, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 3, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Reads the 8 bits of the special corner condition 4.
+ *
+ *
See ISO 16022:2006, Figure F.6
+ *
+ * @param numRows Number of rows in the mapping matrix
+ * @param numColumns Number of columns in the mapping matrix
+ * @return byte from the Corner condition 4
+ */
+ int readCorner4(int numRows, int numColumns) {
+ int currentByte = 0;
+ if (readModule(numRows - 3, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 2, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(numRows - 1, 0, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 2, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(0, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(1, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(2, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ currentByte <<= 1;
+ if (readModule(3, numColumns - 1, numRows, numColumns)) {
+ currentByte |= 1;
+ }
+ return currentByte;
+ }
+
+ /**
+ * Extracts the data region from a {@link BitMatrix} that contains
+ * alignment patterns.
+ *
+ * @param bitMatrix Original {@link BitMatrix} with alignment patterns
+ * @return BitMatrix that has the alignment patterns removed
+ */
+ BitMatrix extractDataRegion(BitMatrix bitMatrix) {
+ int symbolSizeRows = version.getSymbolSizeRows();
+ int symbolSizeColumns = version.getSymbolSizeColumns();
+
+ if (bitMatrix.getHeight() != symbolSizeRows) {
+ throw new IllegalArgumentException("Dimension of bitMarix must match the version size");
+ }
+
+ int dataRegionSizeRows = version.getDataRegionSizeRows();
+ int dataRegionSizeColumns = version.getDataRegionSizeColumns();
+
+ int numDataRegionsRow = symbolSizeRows / dataRegionSizeRows;
+ int numDataRegionsColumn = symbolSizeColumns / dataRegionSizeColumns;
+
+ int sizeDataRegionRow = numDataRegionsRow * dataRegionSizeRows;
+ int sizeDataRegionColumn = numDataRegionsColumn * dataRegionSizeColumns;
+
+ BitMatrix bitMatrixWithoutAlignment = new BitMatrix(sizeDataRegionColumn, sizeDataRegionRow);
+ for (int dataRegionRow = 0; dataRegionRow < numDataRegionsRow; ++dataRegionRow) {
+ int dataRegionRowOffset = dataRegionRow * dataRegionSizeRows;
+ for (int dataRegionColumn = 0; dataRegionColumn < numDataRegionsColumn; ++dataRegionColumn) {
+ int dataRegionColumnOffset = dataRegionColumn * dataRegionSizeColumns;
+ for (int i = 0; i < dataRegionSizeRows; ++i) {
+ int readRowOffset = dataRegionRow * (dataRegionSizeRows + 2) + 1 + i;
+ int writeRowOffset = dataRegionRowOffset + i;
+ for (int j = 0; j < dataRegionSizeColumns; ++j) {
+ int readColumnOffset = dataRegionColumn * (dataRegionSizeColumns + 2) + 1 + j;
+ if (bitMatrix.get(readColumnOffset, readRowOffset)) {
+ int writeColumnOffset = dataRegionColumnOffset + j;
+ bitMatrixWithoutAlignment.set(writeColumnOffset, writeRowOffset);
+ }
+ }
+ }
+ }
+ }
+ return bitMatrixWithoutAlignment;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/datamatrix/decoder/DataBlock.java b/src/com/google/zxing/datamatrix/decoder/DataBlock.java
new file mode 100644
index 0000000..81daca7
--- /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 DataBlocks containing original bytes, "de-interleaved" from representation in the
+ * Data Matrix Code
+ */
+ static DataBlock[] getDataBlocks(byte[] rawCodewords,
+ Version version) {
+ // Figure out the number and size of data blocks used by this version
+ Version.ECBlocks ecBlocks = version.getECBlocks();
+
+ // First count the total number of data blocks
+ int totalBlocks = 0;
+ Version.ECB[] ecBlockArray = ecBlocks.getECBlocks();
+ for (Version.ECB ecBlock : ecBlockArray) {
+ totalBlocks += ecBlock.getCount();
+ }
+
+ // Now establish DataBlocks of the appropriate size and number of data codewords
+ DataBlock[] result = new DataBlock[totalBlocks];
+ int numResultBlocks = 0;
+ for (Version.ECB ecBlock : ecBlockArray) {
+ for (int i = 0; i < ecBlock.getCount(); i++) {
+ int numDataCodewords = ecBlock.getDataCodewords();
+ int numBlockCodewords = ecBlocks.getECCodewords() + numDataCodewords;
+ result[numResultBlocks++] = new DataBlock(numDataCodewords, new byte[numBlockCodewords]);
+ }
+ }
+
+ // All blocks have the same amount of data, except that the last n
+ // (where n may be 0) have 1 less byte. Figure out where these start.
+ // TODO(bbrown): There is only one case where there is a difference for Data Matrix for size 144
+ int longerBlocksTotalCodewords = result[0].codewords.length;
+ //int shorterBlocksTotalCodewords = longerBlocksTotalCodewords - 1;
+
+ int longerBlocksNumDataCodewords = longerBlocksTotalCodewords - ecBlocks.getECCodewords();
+ int shorterBlocksNumDataCodewords = longerBlocksNumDataCodewords - 1;
+ // The last elements of result may be 1 element shorter for 144 matrix
+ // first fill out as many elements as all of them have minus 1
+ int rawCodewordsOffset = 0;
+ for (int i = 0; i < shorterBlocksNumDataCodewords; i++) {
+ for (int j = 0; j < numResultBlocks; j++) {
+ result[j].codewords[i] = rawCodewords[rawCodewordsOffset++];
+ }
+ }
+
+ // Fill out the last data block in the longer ones
+ boolean specialVersion = version.getVersionNumber() == 24;
+ int numLongerBlocks = specialVersion ? 8 : numResultBlocks;
+ for (int j = 0; j < numLongerBlocks; j++) {
+ result[j].codewords[longerBlocksNumDataCodewords - 1] = rawCodewords[rawCodewordsOffset++];
+ }
+
+ // Now add in error correction blocks
+ int max = result[0].codewords.length;
+ for (int i = longerBlocksNumDataCodewords; i < max; i++) {
+ for (int j = 0; j < numResultBlocks; j++) {
+ int jOffset = specialVersion ? (j + 8) % numResultBlocks : j;
+ int iOffset = specialVersion && jOffset > 7 ? i - 1 : i;
+ result[jOffset].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..3a3b2a6
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/decoder/DecodedBitStreamParser.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.decoder;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitSource;
+import com.google.zxing.common.DecoderResult;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Data Matrix Codes can encode text as bits in one of several modes, and can use multiple modes
+ * in one Data Matrix Code. This class decodes the bits back into text.
+ *
+ *
See ISO 16022:2006, 5.2.1 - 5.2.9.2
+ *
+ * @author bbrown@google.com (Brian Brown)
+ * @author Sean Owen
+ */
+final class DecodedBitStreamParser {
+
+ /**
+ * 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'
+ };
+ // Shift 2 for Text is the same encoding as C40
+ private static final char[] TEXT_SHIFT2_SET_CHARS = C40_SHIFT2_SET_CHARS;
+ private static final char[] TEXT_SHIFT3_SET_CHARS = {
+ '`', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
+ 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '{', '|', '}', '~', (char) 127
+ };
+
+ private DecodedBitStreamParser() {
+ }
+
+ static DecoderResult decode(byte[] bytes) throws FormatException {
+ BitSource bits = new BitSource(bytes);
+ StringBuilder result = new StringBuilder(100);
+ StringBuilder resultTrailer = new StringBuilder(0);
+ List byteSegments = new ArrayList<>(1);
+ Mode mode = Mode.ASCII_ENCODE;
+ do {
+ if (mode == Mode.ASCII_ENCODE) {
+ mode = decodeAsciiSegment(bits, result, resultTrailer);
+ } else {
+ switch (mode) {
+ case C40_ENCODE:
+ decodeC40Segment(bits, result);
+ break;
+ case TEXT_ENCODE:
+ decodeTextSegment(bits, result);
+ break;
+ case ANSIX12_ENCODE:
+ decodeAnsiX12Segment(bits, result);
+ break;
+ case EDIFACT_ENCODE:
+ decodeEdifactSegment(bits, result);
+ break;
+ case BASE256_ENCODE:
+ decodeBase256Segment(bits, result, byteSegments);
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+ mode = Mode.ASCII_ENCODE;
+ }
+ } while (mode != Mode.PAD_ENCODE && bits.available() > 0);
+ if (resultTrailer.length() > 0) {
+ result.append(resultTrailer);
+ }
+ return new DecoderResult(bytes, result.toString(), byteSegments.isEmpty() ? null : byteSegments, null);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.3 and Annex C, Table C.2
+ */
+ private static Mode decodeAsciiSegment(BitSource bits,
+ StringBuilder result,
+ StringBuilder resultTrailer) throws FormatException {
+ boolean upperShift = false;
+ do {
+ int oneByte = bits.readBits(8);
+ if (oneByte == 0) {
+ throw FormatException.getFormatInstance();
+ } else if (oneByte <= 128) { // ASCII data (ASCII value + 1)
+ if (upperShift) {
+ oneByte += 128;
+ //upperShift = false;
+ }
+ result.append((char) (oneByte - 1));
+ return Mode.ASCII_ENCODE;
+ } else if (oneByte == 129) { // Pad
+ return Mode.PAD_ENCODE;
+ } else if (oneByte <= 229) { // 2-digit data 00-99 (Numeric Value + 130)
+ int value = oneByte - 130;
+ if (value < 10) { // pad with '0' for single digit values
+ result.append('0');
+ }
+ result.append(value);
+ } else if (oneByte == 230) { // Latch to C40 encodation
+ return Mode.C40_ENCODE;
+ } else if (oneByte == 231) { // Latch to Base 256 encodation
+ return Mode.BASE256_ENCODE;
+ } else if (oneByte == 232) {
+ // FNC1
+ result.append((char) 29); // translate as ASCII 29
+ } else if (oneByte == 233 || oneByte == 234) {
+ // Structured Append, Reader Programming
+ // Ignore these symbols for now
+ //throw ReaderException.getInstance();
+ } else if (oneByte == 235) { // Upper Shift (shift to Extended ASCII)
+ upperShift = true;
+ } else if (oneByte == 236) { // 05 Macro
+ result.append("[)>\u001E05\u001D");
+ resultTrailer.insert(0, "\u001E\u0004");
+ } else if (oneByte == 237) { // 06 Macro
+ result.append("[)>\u001E06\u001D");
+ resultTrailer.insert(0, "\u001E\u0004");
+ } else if (oneByte == 238) { // Latch to ANSI X12 encodation
+ return Mode.ANSIX12_ENCODE;
+ } else if (oneByte == 239) { // Latch to Text encodation
+ return Mode.TEXT_ENCODE;
+ } else if (oneByte == 240) { // Latch to EDIFACT encodation
+ return Mode.EDIFACT_ENCODE;
+ } else if (oneByte == 241) { // ECI Character
+ // TODO(bbrown): I think we need to support ECI
+ //throw ReaderException.getInstance();
+ // Ignore this symbol for now
+ } else if (oneByte >= 242) { // Not to be used in ASCII encodation
+ // ... but work around encoders that end with 254, latch back to ASCII
+ if (oneByte != 254 || bits.available() != 0) {
+ throw FormatException.getFormatInstance();
+ }
+ }
+ } while (bits.available() > 0);
+ return Mode.ASCII_ENCODE;
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.5 and Annex C, Table C.1
+ */
+ private static void decodeC40Segment(BitSource bits, StringBuilder result) throws FormatException {
+ // Three C40 values are encoded in a 16-bit value as
+ // (1600 * C1) + (40 * C2) + C3 + 1
+ // TODO(bbrown): The Upper Shift with C40 doesn't work in the 4 value scenario all the time
+ boolean upperShift = false;
+
+ int[] cValues = new int[3];
+ int shift = 0;
+
+ do {
+ // If there is only one byte left then it will be encoded as ASCII
+ if (bits.available() == 8) {
+ return;
+ }
+ int firstByte = bits.readBits(8);
+ if (firstByte == 254) { // Unlatch codeword
+ return;
+ }
+
+ parseTwoBytes(firstByte, bits.readBits(8), cValues);
+
+ for (int i = 0; i < 3; i++) {
+ int cValue = cValues[i];
+ switch (shift) {
+ case 0:
+ if (cValue < 3) {
+ shift = cValue + 1;
+ } else if (cValue < C40_BASIC_SET_CHARS.length) {
+ char c40char = C40_BASIC_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (c40char + 128));
+ upperShift = false;
+ } else {
+ result.append(c40char);
+ }
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case 1:
+ if (upperShift) {
+ result.append((char) (cValue + 128));
+ upperShift = false;
+ } else {
+ result.append((char) cValue);
+ }
+ shift = 0;
+ break;
+ case 2:
+ if (cValue < C40_SHIFT2_SET_CHARS.length) {
+ char c40char = C40_SHIFT2_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (c40char + 128));
+ upperShift = false;
+ } else {
+ result.append(c40char);
+ }
+ } else if (cValue == 27) { // FNC1
+ result.append((char) 29); // translate as ASCII 29
+ } else if (cValue == 30) { // Upper Shift
+ upperShift = true;
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ shift = 0;
+ break;
+ case 3:
+ if (upperShift) {
+ result.append((char) (cValue + 224));
+ upperShift = false;
+ } else {
+ result.append((char) (cValue + 96));
+ }
+ shift = 0;
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+ }
+ } while (bits.available() > 0);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.6 and Annex C, Table C.2
+ */
+ private static void decodeTextSegment(BitSource bits, StringBuilder result) throws FormatException {
+ // Three Text values are encoded in a 16-bit value as
+ // (1600 * C1) + (40 * C2) + C3 + 1
+ // TODO(bbrown): The Upper Shift with Text doesn't work in the 4 value scenario all the time
+ boolean upperShift = false;
+
+ int[] cValues = new int[3];
+ int shift = 0;
+ do {
+ // If there is only one byte left then it will be encoded as ASCII
+ if (bits.available() == 8) {
+ return;
+ }
+ int firstByte = bits.readBits(8);
+ if (firstByte == 254) { // Unlatch codeword
+ return;
+ }
+
+ parseTwoBytes(firstByte, bits.readBits(8), cValues);
+
+ for (int i = 0; i < 3; i++) {
+ int cValue = cValues[i];
+ switch (shift) {
+ case 0:
+ if (cValue < 3) {
+ shift = cValue + 1;
+ } else if (cValue < TEXT_BASIC_SET_CHARS.length) {
+ char textChar = TEXT_BASIC_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (textChar + 128));
+ upperShift = false;
+ } else {
+ result.append(textChar);
+ }
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case 1:
+ if (upperShift) {
+ result.append((char) (cValue + 128));
+ upperShift = false;
+ } else {
+ result.append((char) cValue);
+ }
+ shift = 0;
+ break;
+ case 2:
+ // Shift 2 for Text is the same encoding as C40
+ if (cValue < TEXT_SHIFT2_SET_CHARS.length) {
+ char textChar = TEXT_SHIFT2_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (textChar + 128));
+ upperShift = false;
+ } else {
+ result.append(textChar);
+ }
+ } else if (cValue == 27) { // FNC1
+ result.append((char) 29); // translate as ASCII 29
+ } else if (cValue == 30) { // Upper Shift
+ upperShift = true;
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ shift = 0;
+ break;
+ case 3:
+ if (cValue < TEXT_SHIFT3_SET_CHARS.length) {
+ char textChar = TEXT_SHIFT3_SET_CHARS[cValue];
+ if (upperShift) {
+ result.append((char) (textChar + 128));
+ upperShift = false;
+ } else {
+ result.append(textChar);
+ }
+ shift = 0;
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+ }
+ } while (bits.available() > 0);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.7
+ */
+ private static void decodeAnsiX12Segment(BitSource bits,
+ StringBuilder result) throws FormatException {
+ // Three ANSI X12 values are encoded in a 16-bit value as
+ // (1600 * C1) + (40 * C2) + C3 + 1
+
+ int[] cValues = new int[3];
+ do {
+ // If there is only one byte left then it will be encoded as ASCII
+ if (bits.available() == 8) {
+ return;
+ }
+ int firstByte = bits.readBits(8);
+ if (firstByte == 254) { // Unlatch codeword
+ return;
+ }
+
+ parseTwoBytes(firstByte, bits.readBits(8), cValues);
+
+ for (int i = 0; i < 3; i++) {
+ int cValue = cValues[i];
+ if (cValue == 0) { // X12 segment terminator
+ result.append('\r');
+ } else if (cValue == 1) { // X12 segment separator *
+ result.append('*');
+ } else if (cValue == 2) { // X12 sub-element separator >
+ result.append('>');
+ } else if (cValue == 3) { // space
+ result.append(' ');
+ } else if (cValue < 14) { // 0 - 9
+ result.append((char) (cValue + 44));
+ } else if (cValue < 40) { // A - Z
+ result.append((char) (cValue + 51));
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ }
+ } while (bits.available() > 0);
+ }
+
+ private static void parseTwoBytes(int firstByte, int secondByte, int[] result) {
+ int fullBitValue = (firstByte << 8) + secondByte - 1;
+ int temp = fullBitValue / 1600;
+ result[0] = temp;
+ fullBitValue -= temp * 1600;
+ temp = fullBitValue / 40;
+ result[1] = temp;
+ result[2] = fullBitValue - temp * 40;
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.8 and Annex C Table C.3
+ */
+ private static void decodeEdifactSegment(BitSource bits, StringBuilder result) {
+ do {
+ // If there is only two or less bytes left then it will be encoded as ASCII
+ if (bits.available() <= 16) {
+ return;
+ }
+
+ for (int i = 0; i < 4; i++) {
+ int edifactValue = bits.readBits(6);
+
+ // Check for the unlatch character
+ if (edifactValue == 0x1F) { // 011111
+ // Read rest of byte, which should be 0, and stop
+ int bitsLeft = 8 - bits.getBitOffset();
+ if (bitsLeft != 8) {
+ bits.readBits(bitsLeft);
+ }
+ return;
+ }
+
+ if ((edifactValue & 0x20) == 0) { // no 1 in the leading (6th) bit
+ edifactValue |= 0x40; // Add a leading 01 to the 6 bit binary value
+ }
+ result.append((char) edifactValue);
+ }
+ } while (bits.available() > 0);
+ }
+
+ /**
+ * See ISO 16022:2006, 5.2.9 and Annex B, B.2
+ */
+ private static void decodeBase256Segment(BitSource bits,
+ StringBuilder result,
+ Collection byteSegments)
+ throws FormatException {
+ // Figure out how long the Base 256 Segment is.
+ int codewordPosition = 1 + bits.getByteOffset(); // position is 1-indexed
+ int d1 = unrandomize255State(bits.readBits(8), codewordPosition++);
+ int count;
+ if (d1 == 0) { // Read the remainder of the symbol
+ count = bits.available() / 8;
+ } else if (d1 < 250) {
+ count = d1;
+ } else {
+ count = 250 * (d1 - 249) + unrandomize255State(bits.readBits(8), codewordPosition++);
+ }
+
+ // We're seeing NegativeArraySizeException errors from users.
+ if (count < 0) {
+ throw FormatException.getFormatInstance();
+ }
+
+ byte[] bytes = new byte[count];
+ for (int i = 0; i < count; i++) {
+ // Have seen this particular error in the wild, such as at
+ // http://www.bcgen.com/demo/IDAutomationStreamingDataMatrix.aspx?MODE=3&D=Fred&PFMT=3&PT=F&X=0.3&O=0&LM=0.2
+ if (bits.available() < 8) {
+ throw FormatException.getFormatInstance();
+ }
+ bytes[i] = (byte) unrandomize255State(bits.readBits(8), codewordPosition++);
+ }
+ byteSegments.add(bytes);
+ try {
+ result.append(new String(bytes, "ISO8859_1"));
+ } catch (UnsupportedEncodingException uee) {
+ throw new IllegalStateException("Platform does not support required encoding: " + uee);
+ }
+ }
+
+ /**
+ * See ISO 16022:2006, Annex B, B.2
+ */
+ private static int unrandomize255State(int randomizedBase256Codeword,
+ int base256CodewordPosition) {
+ int pseudoRandomNumber = ((149 * base256CodewordPosition) % 255) + 1;
+ int tempVariable = randomizedBase256Codeword - pseudoRandomNumber;
+ return tempVariable >= 0 ? tempVariable : tempVariable + 256;
+ }
+
+ private enum Mode {
+ PAD_ENCODE, // Not really a mode
+ ASCII_ENCODE,
+ C40_ENCODE,
+ TEXT_ENCODE,
+ ANSIX12_ENCODE,
+ EDIFACT_ENCODE,
+ BASE256_ENCODE
+ }
+
+}
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..43b1e9e
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/decoder/Decoder.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.decoder;
+
+import com.google.zxing.ChecksumException;
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+/**
+ * The main class which implements Data Matrix Code decoding -- as opposed to locating and extracting
+ * the Data Matrix Code from an image.
+ *
+ * @author bbrown@google.com (Brian Brown)
+ */
+public final class Decoder {
+
+ private final ReedSolomonDecoder rsDecoder;
+
+ public Decoder() {
+ rsDecoder = new ReedSolomonDecoder(GenericGF.DATA_MATRIX_FIELD_256);
+ }
+
+ /**
+ * Convenience method that can decode a Data Matrix Code represented as a 2D array of booleans.
+ * "true" is taken to mean a black module.
+ *
+ * @param image booleans representing white/black Data Matrix Code modules
+ * @return text and bytes encoded within the Data Matrix Code
+ * @throws FormatException if the Data Matrix Code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ public DecoderResult decode(boolean[][] image) throws FormatException, ChecksumException {
+ int dimension = image.length;
+ BitMatrix bits = new BitMatrix(dimension);
+ for (int i = 0; i < dimension; i++) {
+ for (int j = 0; j < dimension; j++) {
+ if (image[i][j]) {
+ bits.set(j, i);
+ }
+ }
+ }
+ return decode(bits);
+ }
+
+ /**
+ * Decodes a Data Matrix Code represented as a {@link BitMatrix}. A 1 or "true" is taken
+ * to mean a black module.
+ *
+ * @param bits booleans representing white/black Data Matrix Code modules
+ * @return text and bytes encoded within the Data Matrix Code
+ * @throws FormatException if the Data Matrix Code cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ public DecoderResult decode(BitMatrix bits) throws FormatException, ChecksumException {
+
+ // Construct a parser and read version, error-correction level
+ BitMatrixParser parser = new BitMatrixParser(bits);
+ Version version = parser.getVersion();
+
+ // Read codewords
+ byte[] codewords = parser.readCodewords();
+ // Separate into data blocks
+ DataBlock[] dataBlocks = DataBlock.getDataBlocks(codewords, version);
+
+ int dataBlocksCount = dataBlocks.length;
+
+ // Count total number of data bytes
+ int totalBytes = 0;
+ for (DataBlock db : dataBlocks) {
+ totalBytes += db.getNumDataCodewords();
+ }
+ byte[] resultBytes = new byte[totalBytes];
+
+ // Error-correct and copy data blocks together into a stream of bytes
+ for (int j = 0; j < dataBlocksCount; j++) {
+ DataBlock dataBlock = dataBlocks[j];
+ byte[] codewordBytes = dataBlock.getCodewords();
+ int numDataCodewords = dataBlock.getNumDataCodewords();
+ correctErrors(codewordBytes, numDataCodewords);
+ for (int i = 0; i < numDataCodewords; i++) {
+ // De-interlace data blocks.
+ resultBytes[i * dataBlocksCount + j] = codewordBytes[i];
+ }
+ }
+
+ // Decode the contents of that stream of bytes
+ return DecodedBitStreamParser.decode(resultBytes);
+ }
+
+ /**
+ * Given data and error-correction codewords received, possibly corrupted by errors, attempts to
+ * correct the errors in-place using Reed-Solomon error correction.
+ *
+ * @param codewordBytes data and error correction codewords
+ * @param numDataCodewords number of codewords that are data bytes
+ * @throws ChecksumException if error correction fails
+ */
+ private void correctErrors(byte[] codewordBytes, int numDataCodewords) throws ChecksumException {
+ int numCodewords = codewordBytes.length;
+ // First read into an array of ints
+ int[] codewordsInts = new int[numCodewords];
+ for (int i = 0; i < numCodewords; i++) {
+ codewordsInts[i] = codewordBytes[i] & 0xFF;
+ }
+ int numECCodewords = codewordBytes.length - numDataCodewords;
+ try {
+ rsDecoder.decode(codewordsInts, numECCodewords);
+ } catch (ReedSolomonException ignored) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ // Copy back into array of bytes -- only need to worry about the bytes that were data
+ // We don't care about errors in the error-correction codewords
+ for (int i = 0; i < numDataCodewords; i++) {
+ codewordBytes[i] = (byte) codewordsInts[i];
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/decoder/Version.java b/src/com/google/zxing/datamatrix/decoder/Version.java
new file mode 100644
index 0000000..bc4e5b4
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/decoder/Version.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2007 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.decoder;
+
+import com.google.zxing.FormatException;
+
+/**
+ * The Version object encapsulates attributes about a particular
+ * size Data Matrix Code.
+ *
+ * @author bbrown@google.com (Brian Brown)
+ */
+public final class Version {
+
+ private static final Version[] VERSIONS = buildVersions();
+
+ private final int versionNumber;
+ private final int symbolSizeRows;
+ private final int symbolSizeColumns;
+ private final int dataRegionSizeRows;
+ private final int dataRegionSizeColumns;
+ private final ECBlocks ecBlocks;
+ private final int totalCodewords;
+
+ private Version(int versionNumber,
+ int symbolSizeRows,
+ int symbolSizeColumns,
+ int dataRegionSizeRows,
+ int dataRegionSizeColumns,
+ ECBlocks ecBlocks) {
+ this.versionNumber = versionNumber;
+ this.symbolSizeRows = symbolSizeRows;
+ this.symbolSizeColumns = symbolSizeColumns;
+ this.dataRegionSizeRows = dataRegionSizeRows;
+ this.dataRegionSizeColumns = dataRegionSizeColumns;
+ this.ecBlocks = ecBlocks;
+
+ // Calculate the total number of codewords
+ int total = 0;
+ int ecCodewords = ecBlocks.getECCodewords();
+ ECB[] ecbArray = ecBlocks.getECBlocks();
+ for (ECB ecBlock : ecbArray) {
+ total += ecBlock.getCount() * (ecBlock.getDataCodewords() + ecCodewords);
+ }
+ this.totalCodewords = total;
+ }
+
+ /**
+ * Deduces version information from Data Matrix dimensions.
+ *
+ * @param numRows Number of rows in modules
+ * @param numColumns Number of columns in modules
+ * @return Version for a Data Matrix Code of those dimensions
+ * @throws FormatException if dimensions do correspond to a valid Data Matrix size
+ */
+ public static Version getVersionForDimensions(int numRows, int numColumns) throws FormatException {
+ if ((numRows & 0x01) != 0 || (numColumns & 0x01) != 0) {
+ throw FormatException.getFormatInstance();
+ }
+
+ for (Version version : VERSIONS) {
+ if (version.symbolSizeRows == numRows && version.symbolSizeColumns == numColumns) {
+ return version;
+ }
+ }
+
+ throw FormatException.getFormatInstance();
+ }
+
+ /**
+ * See ISO 16022:2006 5.5.1 Table 7
+ */
+ private static Version[] buildVersions() {
+ return new Version[]{
+ new Version(1, 10, 10, 8, 8,
+ new ECBlocks(5, new ECB(1, 3))),
+ new Version(2, 12, 12, 10, 10,
+ new ECBlocks(7, new ECB(1, 5))),
+ new Version(3, 14, 14, 12, 12,
+ new ECBlocks(10, new ECB(1, 8))),
+ new Version(4, 16, 16, 14, 14,
+ new ECBlocks(12, new ECB(1, 12))),
+ new Version(5, 18, 18, 16, 16,
+ new ECBlocks(14, new ECB(1, 18))),
+ new Version(6, 20, 20, 18, 18,
+ new ECBlocks(18, new ECB(1, 22))),
+ new Version(7, 22, 22, 20, 20,
+ new ECBlocks(20, new ECB(1, 30))),
+ new Version(8, 24, 24, 22, 22,
+ new ECBlocks(24, new ECB(1, 36))),
+ new Version(9, 26, 26, 24, 24,
+ new ECBlocks(28, new ECB(1, 44))),
+ new Version(10, 32, 32, 14, 14,
+ new ECBlocks(36, new ECB(1, 62))),
+ new Version(11, 36, 36, 16, 16,
+ new ECBlocks(42, new ECB(1, 86))),
+ new Version(12, 40, 40, 18, 18,
+ new ECBlocks(48, new ECB(1, 114))),
+ new Version(13, 44, 44, 20, 20,
+ new ECBlocks(56, new ECB(1, 144))),
+ new Version(14, 48, 48, 22, 22,
+ new ECBlocks(68, new ECB(1, 174))),
+ new Version(15, 52, 52, 24, 24,
+ new ECBlocks(42, new ECB(2, 102))),
+ new Version(16, 64, 64, 14, 14,
+ new ECBlocks(56, new ECB(2, 140))),
+ new Version(17, 72, 72, 16, 16,
+ new ECBlocks(36, new ECB(4, 92))),
+ new Version(18, 80, 80, 18, 18,
+ new ECBlocks(48, new ECB(4, 114))),
+ new Version(19, 88, 88, 20, 20,
+ new ECBlocks(56, new ECB(4, 144))),
+ new Version(20, 96, 96, 22, 22,
+ new ECBlocks(68, new ECB(4, 174))),
+ new Version(21, 104, 104, 24, 24,
+ new ECBlocks(56, new ECB(6, 136))),
+ new Version(22, 120, 120, 18, 18,
+ new ECBlocks(68, new ECB(6, 175))),
+ new Version(23, 132, 132, 20, 20,
+ new ECBlocks(62, new ECB(8, 163))),
+ new Version(24, 144, 144, 22, 22,
+ new ECBlocks(62, new ECB(8, 156), new ECB(2, 155))),
+ new Version(25, 8, 18, 6, 16,
+ new ECBlocks(7, new ECB(1, 5))),
+ new Version(26, 8, 32, 6, 14,
+ new ECBlocks(11, new ECB(1, 10))),
+ new Version(27, 12, 26, 10, 24,
+ new ECBlocks(14, new ECB(1, 16))),
+ new Version(28, 12, 36, 10, 16,
+ new ECBlocks(18, new ECB(1, 22))),
+ new Version(29, 16, 36, 14, 16,
+ new ECBlocks(24, new ECB(1, 32))),
+ new Version(30, 16, 48, 14, 22,
+ new ECBlocks(28, new ECB(1, 49)))
+ };
+ }
+
+ 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;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(versionNumber);
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+}
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..08afebf
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/detector/Detector.java
@@ -0,0 +1,435 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.detector;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.common.GridSampler;
+import com.google.zxing.common.detector.MathUtils;
+import com.google.zxing.common.detector.WhiteRectangleDetector;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * Encapsulates logic that can detect a Data Matrix Code in an image, even if the Data Matrix Code
+ * is rotated or skewed, or partially obscured.
+ *
+ * @author Sean Owen
+ */
+public final class Detector {
+
+ private final BitMatrix image;
+ private final WhiteRectangleDetector rectangleDetector;
+
+ public Detector(BitMatrix image) throws NotFoundException {
+ this.image = image;
+ rectangleDetector = new WhiteRectangleDetector(image);
+ }
+
+ private static int distance(ResultPoint a, ResultPoint b) {
+ return MathUtils.round(ResultPoint.distance(a, b));
+ }
+
+ /**
+ * Increments the Integer associated with a key by one.
+ */
+ private static void increment(Map table, ResultPoint key) {
+ Integer value = table.get(key);
+ table.put(key, value == null ? 1 : value + 1);
+ }
+
+ private static BitMatrix sampleGrid(BitMatrix image,
+ ResultPoint topLeft,
+ ResultPoint bottomLeft,
+ ResultPoint bottomRight,
+ ResultPoint topRight,
+ int dimensionX,
+ int dimensionY) throws NotFoundException {
+
+ GridSampler sampler = GridSampler.getInstance();
+
+ return sampler.sampleGrid(image,
+ dimensionX,
+ dimensionY,
+ 0.5f,
+ 0.5f,
+ dimensionX - 0.5f,
+ 0.5f,
+ dimensionX - 0.5f,
+ dimensionY - 0.5f,
+ 0.5f,
+ dimensionY - 0.5f,
+ topLeft.getX(),
+ topLeft.getY(),
+ topRight.getX(),
+ topRight.getY(),
+ bottomRight.getX(),
+ bottomRight.getY(),
+ bottomLeft.getX(),
+ bottomLeft.getY());
+ }
+
+ /**
+ * Detects a Data Matrix Code in an image.
+ *
+ * @return {@link DetectorResult} encapsulating results of detecting a Data Matrix Code
+ * @throws NotFoundException if no Data Matrix Code can be found
+ */
+ public DetectorResult detect() throws NotFoundException {
+
+ ResultPoint[] cornerPoints = rectangleDetector.detect();
+ ResultPoint pointA = cornerPoints[0];
+ ResultPoint pointB = cornerPoints[1];
+ ResultPoint pointC = cornerPoints[2];
+ ResultPoint pointD = cornerPoints[3];
+
+ // Point A and D are across the diagonal from one another,
+ // as are B and C. Figure out which are the solid black lines
+ // by counting transitions
+ List transitions = new ArrayList<>(4);
+ transitions.add(transitionsBetween(pointA, pointB));
+ transitions.add(transitionsBetween(pointA, pointC));
+ transitions.add(transitionsBetween(pointB, pointD));
+ transitions.add(transitionsBetween(pointC, pointD));
+ Collections.sort(transitions, new ResultPointsAndTransitionsComparator());
+
+ // Sort by number of transitions. First two will be the two solid sides; last two
+ // will be the two alternating black/white sides
+ ResultPointsAndTransitions lSideOne = transitions.get(0);
+ ResultPointsAndTransitions lSideTwo = transitions.get(1);
+
+ // Figure out which point is their intersection by tallying up the number of times we see the
+ // endpoints in the four endpoints. One will show up twice.
+ Map pointCount = new HashMap<>();
+ increment(pointCount, lSideOne.getFrom());
+ increment(pointCount, lSideOne.getTo());
+ increment(pointCount, lSideTwo.getFrom());
+ increment(pointCount, lSideTwo.getTo());
+
+ ResultPoint maybeTopLeft = null;
+ ResultPoint bottomLeft = null;
+ ResultPoint maybeBottomRight = null;
+ for (Map.Entry entry : pointCount.entrySet()) {
+ ResultPoint point = entry.getKey();
+ Integer value = entry.getValue();
+ if (value == 2) {
+ bottomLeft = point; // this is definitely the bottom left, then -- end of two L sides
+ } else {
+ // Otherwise it's either top left or bottom right -- just assign the two arbitrarily now
+ if (maybeTopLeft == null) {
+ maybeTopLeft = point;
+ } else {
+ maybeBottomRight = point;
+ }
+ }
+ }
+
+ if (maybeTopLeft == null || bottomLeft == null || maybeBottomRight == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Bottom left is correct but top left and bottom right might be switched
+ ResultPoint[] corners = {maybeTopLeft, bottomLeft, maybeBottomRight};
+ // Use the dot product trick to sort them out
+ ResultPoint.orderBestPatterns(corners);
+
+ // Now we know which is which:
+ ResultPoint bottomRight = corners[0];
+ bottomLeft = corners[1];
+ ResultPoint topLeft = corners[2];
+
+ // Which point didn't we find in relation to the "L" sides? that's the top right corner
+ ResultPoint topRight;
+ if (!pointCount.containsKey(pointA)) {
+ topRight = pointA;
+ } else if (!pointCount.containsKey(pointB)) {
+ topRight = pointB;
+ } else if (!pointCount.containsKey(pointC)) {
+ topRight = pointC;
+ } else {
+ topRight = pointD;
+ }
+
+ // Next determine the dimension by tracing along the top or right side and counting black/white
+ // transitions. Since we start inside a black module, we should see a number of transitions
+ // equal to 1 less than the code dimension. Well, actually 2 less, because we are going to
+ // end on a black module:
+
+ // The top right point is actually the corner of a module, which is one of the two black modules
+ // adjacent to the white module at the top right. Tracing to that corner from either the top left
+ // or bottom right should work here.
+
+ int dimensionTop = transitionsBetween(topLeft, topRight).getTransitions();
+ int dimensionRight = transitionsBetween(bottomRight, topRight).getTransitions();
+
+ if ((dimensionTop & 0x01) == 1) {
+ // it can't be odd, so, round... up?
+ dimensionTop++;
+ }
+ dimensionTop += 2;
+
+ if ((dimensionRight & 0x01) == 1) {
+ // it can't be odd, so, round... up?
+ dimensionRight++;
+ }
+ dimensionRight += 2;
+
+ BitMatrix bits;
+ ResultPoint correctedTopRight;
+
+ // Rectanguar symbols are 6x16, 6x28, 10x24, 10x32, 14x32, or 14x44. If one dimension is more
+ // than twice the other, it's certainly rectangular, but to cut a bit more slack we accept it as
+ // rectangular if the bigger side is at least 7/4 times the other:
+ if (4 * dimensionTop >= 7 * dimensionRight || 4 * dimensionRight >= 7 * dimensionTop) {
+ // The matrix is rectangular
+
+ correctedTopRight =
+ correctTopRightRectangular(bottomLeft, bottomRight, topLeft, topRight, dimensionTop, dimensionRight);
+ if (correctedTopRight == null) {
+ correctedTopRight = topRight;
+ }
+
+ dimensionTop = transitionsBetween(topLeft, correctedTopRight).getTransitions();
+ dimensionRight = transitionsBetween(bottomRight, correctedTopRight).getTransitions();
+
+ if ((dimensionTop & 0x01) == 1) {
+ // it can't be odd, so, round... up?
+ dimensionTop++;
+ }
+
+ if ((dimensionRight & 0x01) == 1) {
+ // it can't be odd, so, round... up?
+ dimensionRight++;
+ }
+
+ bits = sampleGrid(image, topLeft, bottomLeft, bottomRight, correctedTopRight, dimensionTop, dimensionRight);
+
+ } else {
+ // The matrix is square
+
+ int dimension = Math.min(dimensionRight, dimensionTop);
+ // correct top right point to match the white module
+ correctedTopRight = correctTopRight(bottomLeft, bottomRight, topLeft, topRight, dimension);
+ if (correctedTopRight == null) {
+ correctedTopRight = topRight;
+ }
+
+ // Redetermine the dimension using the corrected top right point
+ int dimensionCorrected = Math.max(transitionsBetween(topLeft, correctedTopRight).getTransitions(),
+ transitionsBetween(bottomRight, correctedTopRight).getTransitions());
+ dimensionCorrected++;
+ if ((dimensionCorrected & 0x01) == 1) {
+ dimensionCorrected++;
+ }
+
+ bits = sampleGrid(image,
+ topLeft,
+ bottomLeft,
+ bottomRight,
+ correctedTopRight,
+ dimensionCorrected,
+ dimensionCorrected);
+ }
+
+ return new DetectorResult(bits, new ResultPoint[]{topLeft, bottomLeft, bottomRight, correctedTopRight});
+ }
+
+ /**
+ * Calculates the position of the white top right module using the output of the rectangle detector
+ * for a rectangular matrix
+ */
+ private ResultPoint correctTopRightRectangular(ResultPoint bottomLeft,
+ ResultPoint bottomRight,
+ ResultPoint topLeft,
+ ResultPoint topRight,
+ int dimensionTop,
+ int dimensionRight) {
+
+ float corr = distance(bottomLeft, bottomRight) / (float) dimensionTop;
+ int norm = distance(topLeft, topRight);
+ float cos = (topRight.getX() - topLeft.getX()) / norm;
+ float sin = (topRight.getY() - topLeft.getY()) / norm;
+
+ ResultPoint c1 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin);
+
+ corr = distance(bottomLeft, topLeft) / (float) dimensionRight;
+ norm = distance(bottomRight, topRight);
+ cos = (topRight.getX() - bottomRight.getX()) / norm;
+ sin = (topRight.getY() - bottomRight.getY()) / norm;
+
+ ResultPoint c2 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin);
+
+ if (!isValid(c1)) {
+ if (isValid(c2)) {
+ return c2;
+ }
+ return null;
+ }
+ if (!isValid(c2)) {
+ return c1;
+ }
+
+ int l1 = Math.abs(dimensionTop - transitionsBetween(topLeft, c1).getTransitions()) +
+ Math.abs(dimensionRight - transitionsBetween(bottomRight, c1).getTransitions());
+ int l2 = Math.abs(dimensionTop - transitionsBetween(topLeft, c2).getTransitions()) +
+ Math.abs(dimensionRight - transitionsBetween(bottomRight, c2).getTransitions());
+
+ if (l1 <= l2) {
+ return c1;
+ }
+
+ return c2;
+ }
+
+ /**
+ * Calculates the position of the white top right module using the output of the rectangle detector
+ * for a square matrix
+ */
+ private ResultPoint correctTopRight(ResultPoint bottomLeft,
+ ResultPoint bottomRight,
+ ResultPoint topLeft,
+ ResultPoint topRight,
+ int dimension) {
+
+ float corr = distance(bottomLeft, bottomRight) / (float) dimension;
+ int norm = distance(topLeft, topRight);
+ float cos = (topRight.getX() - topLeft.getX()) / norm;
+ float sin = (topRight.getY() - topLeft.getY()) / norm;
+
+ ResultPoint c1 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin);
+
+ corr = distance(bottomLeft, topLeft) / (float) dimension;
+ norm = distance(bottomRight, topRight);
+ cos = (topRight.getX() - bottomRight.getX()) / norm;
+ sin = (topRight.getY() - bottomRight.getY()) / norm;
+
+ ResultPoint c2 = new ResultPoint(topRight.getX() + corr * cos, topRight.getY() + corr * sin);
+
+ if (!isValid(c1)) {
+ if (isValid(c2)) {
+ return c2;
+ }
+ return null;
+ }
+ if (!isValid(c2)) {
+ return c1;
+ }
+
+ int l1 = Math.abs(transitionsBetween(topLeft, c1).getTransitions() -
+ transitionsBetween(bottomRight, c1).getTransitions());
+ int l2 = Math.abs(transitionsBetween(topLeft, c2).getTransitions() -
+ transitionsBetween(bottomRight, c2).getTransitions());
+
+ return l1 <= l2 ? c1 : c2;
+ }
+
+ private boolean isValid(ResultPoint p) {
+ return p.getX() >= 0 && p.getX() < image.getWidth() && p.getY() > 0 && p.getY() < image.getHeight();
+ }
+
+ /**
+ * 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 / 2;
+ int ystep = fromY < toY ? 1 : -1;
+ int xstep = fromX < toX ? 1 : -1;
+ int transitions = 0;
+ boolean inBlack = image.get(steep ? fromY : fromX, steep ? fromX : fromY);
+ for (int x = fromX, y = fromY; x != toX; x += xstep) {
+ boolean isBlack = image.get(steep ? y : x, steep ? x : y);
+ if (isBlack != inBlack) {
+ transitions++;
+ inBlack = isBlack;
+ }
+ error += dy;
+ if (error > 0) {
+ if (y == toY) {
+ break;
+ }
+ y += ystep;
+ error -= dx;
+ }
+ }
+ return new ResultPointsAndTransitions(from, to, transitions);
+ }
+
+ /**
+ * Simply encapsulates two points and a number of transitions between them.
+ */
+ private static final class ResultPointsAndTransitions {
+
+ private final ResultPoint from;
+ private final ResultPoint to;
+ private final int transitions;
+
+ private ResultPointsAndTransitions(ResultPoint from, ResultPoint to, int transitions) {
+ this.from = from;
+ this.to = to;
+ this.transitions = transitions;
+ }
+
+ ResultPoint getFrom() {
+ return from;
+ }
+
+ ResultPoint getTo() {
+ return to;
+ }
+
+ public int getTransitions() {
+ return transitions;
+ }
+
+ @Override
+ public String toString() {
+ return from + "/" + to + '/' + transitions;
+ }
+ }
+
+ /**
+ * Orders ResultPointsAndTransitions by number of transitions, ascending.
+ */
+ private static final class ResultPointsAndTransitionsComparator
+ implements Comparator, Serializable {
+ @Override
+ public int compare(ResultPointsAndTransitions o1, ResultPointsAndTransitions o2) {
+ return o1.getTransitions() - o2.getTransitions();
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/ASCIIEncoder.java b/src/com/google/zxing/datamatrix/encoder/ASCIIEncoder.java
new file mode 100644
index 0000000..95590e1
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/ASCIIEncoder.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+final class ASCIIEncoder implements Encoder {
+
+ private static char encodeASCIIDigits(char digit1, char digit2) {
+ if (HighLevelEncoder.isDigit(digit1) && HighLevelEncoder.isDigit(digit2)) {
+ int num = (digit1 - 48) * 10 + (digit2 - 48);
+ return (char) (num + 130);
+ }
+ throw new IllegalArgumentException("not digits: " + digit1 + digit2);
+ }
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.ASCII_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ //step B
+ int n = HighLevelEncoder.determineConsecutiveDigitCount(context.getMessage(), context.pos);
+ if (n >= 2) {
+ context.writeCodeword(encodeASCIIDigits(context.getMessage().charAt(context.pos),
+ context.getMessage().charAt(context.pos + 1)));
+ context.pos += 2;
+ } else {
+ char c = context.getCurrentChar();
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ switch (newMode) {
+ case HighLevelEncoder.BASE256_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_BASE256);
+ context.signalEncoderChange(HighLevelEncoder.BASE256_ENCODATION);
+ return;
+ case HighLevelEncoder.C40_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_C40);
+ context.signalEncoderChange(HighLevelEncoder.C40_ENCODATION);
+ return;
+ case HighLevelEncoder.X12_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_ANSIX12);
+ context.signalEncoderChange(HighLevelEncoder.X12_ENCODATION);
+ break;
+ case HighLevelEncoder.TEXT_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_TEXT);
+ context.signalEncoderChange(HighLevelEncoder.TEXT_ENCODATION);
+ break;
+ case HighLevelEncoder.EDIFACT_ENCODATION:
+ context.writeCodeword(HighLevelEncoder.LATCH_TO_EDIFACT);
+ context.signalEncoderChange(HighLevelEncoder.EDIFACT_ENCODATION);
+ break;
+ default:
+ throw new IllegalStateException("Illegal mode: " + newMode);
+ }
+ } else if (HighLevelEncoder.isExtendedASCII(c)) {
+ context.writeCodeword(HighLevelEncoder.UPPER_SHIFT);
+ context.writeCodeword((char) (c - 128 + 1));
+ context.pos++;
+ } else {
+ context.writeCodeword((char) (c + 1));
+ context.pos++;
+ }
+
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/Base256Encoder.java b/src/com/google/zxing/datamatrix/encoder/Base256Encoder.java
new file mode 100644
index 0000000..1ef7e07
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/Base256Encoder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+final class Base256Encoder implements Encoder {
+
+ private static char randomize255State(char ch, int codewordPosition) {
+ int pseudoRandom = ((149 * codewordPosition) % 255) + 1;
+ int tempVariable = ch + pseudoRandom;
+ if (tempVariable <= 255) {
+ return (char) tempVariable;
+ } else {
+ return (char) (tempVariable - 256);
+ }
+ }
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.BASE256_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append('\0'); //Initialize length field
+ while (context.hasMoreCharacters()) {
+ char c = context.getCurrentChar();
+ buffer.append(c);
+
+ context.pos++;
+
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ context.signalEncoderChange(newMode);
+ break;
+ }
+ }
+ int dataCount = buffer.length() - 1;
+ int lengthFieldSize = 1;
+ int currentSize = context.getCodewordCount() + dataCount + lengthFieldSize;
+ context.updateSymbolInfo(currentSize);
+ boolean mustPad = (context.getSymbolInfo().getDataCapacity() - currentSize) > 0;
+ if (context.hasMoreCharacters() || mustPad) {
+ if (dataCount <= 249) {
+ buffer.setCharAt(0, (char) dataCount);
+ } else if (dataCount > 249 && dataCount <= 1555) {
+ buffer.setCharAt(0, (char) ((dataCount / 250) + 249));
+ buffer.insert(1, (char) (dataCount % 250));
+ } else {
+ throw new IllegalStateException(
+ "Message length not in valid ranges: " + dataCount);
+ }
+ }
+ for (int i = 0, c = buffer.length(); i < c; i++) {
+ context.writeCodeword(randomize255State(
+ buffer.charAt(i), context.getCodewordCount() + 1));
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/C40Encoder.java b/src/com/google/zxing/datamatrix/encoder/C40Encoder.java
new file mode 100644
index 0000000..69d52b3
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/C40Encoder.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+class C40Encoder implements Encoder {
+
+ static void writeNextTriplet(EncoderContext context, StringBuilder buffer) {
+ context.writeCodewords(encodeToCodewords(buffer, 0));
+ buffer.delete(0, 3);
+ }
+
+ private static String encodeToCodewords(CharSequence sb, int startPos) {
+ char c1 = sb.charAt(startPos);
+ char c2 = sb.charAt(startPos + 1);
+ char c3 = sb.charAt(startPos + 2);
+ int v = (1600 * c1) + (40 * c2) + c3 + 1;
+ char cw1 = (char) (v / 256);
+ char cw2 = (char) (v % 256);
+ return new String(new char[]{cw1, cw2});
+ }
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.C40_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ //step C
+ StringBuilder buffer = new StringBuilder();
+ while (context.hasMoreCharacters()) {
+ char c = context.getCurrentChar();
+ context.pos++;
+
+ int lastCharSize = encodeChar(c, buffer);
+
+ int unwritten = (buffer.length() / 3) * 2;
+
+ int curCodewordCount = context.getCodewordCount() + unwritten;
+ context.updateSymbolInfo(curCodewordCount);
+ int available = context.getSymbolInfo().getDataCapacity() - curCodewordCount;
+
+ if (!context.hasMoreCharacters()) {
+ //Avoid having a single C40 value in the last triplet
+ StringBuilder removed = new StringBuilder();
+ if ((buffer.length() % 3) == 2) {
+ if (available < 2 || available > 2) {
+ lastCharSize = backtrackOneCharacter(context, buffer, removed,
+ lastCharSize);
+ }
+ }
+ while ((buffer.length() % 3) == 1
+ && ((lastCharSize <= 3 && available != 1) || lastCharSize > 3)) {
+ lastCharSize = backtrackOneCharacter(context, buffer, removed, lastCharSize);
+ }
+ break;
+ }
+
+ int count = buffer.length();
+ if ((count % 3) == 0) {
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ context.signalEncoderChange(newMode);
+ break;
+ }
+ }
+ }
+ handleEOD(context, buffer);
+ }
+
+ private int backtrackOneCharacter(EncoderContext context,
+ StringBuilder buffer, StringBuilder removed, int lastCharSize) {
+ int count = buffer.length();
+ buffer.delete(count - lastCharSize, count);
+ context.pos--;
+ char c = context.getCurrentChar();
+ lastCharSize = encodeChar(c, removed);
+ context.resetSymbolInfo(); //Deal with possible reduction in symbol size
+ return lastCharSize;
+ }
+
+ /**
+ * Handle "end of data" situations
+ *
+ * @param context the encoder context
+ * @param buffer the buffer with the remaining encoded characters
+ */
+ void handleEOD(EncoderContext context, StringBuilder buffer) {
+ int unwritten = (buffer.length() / 3) * 2;
+ int rest = buffer.length() % 3;
+
+ int curCodewordCount = context.getCodewordCount() + unwritten;
+ context.updateSymbolInfo(curCodewordCount);
+ int available = context.getSymbolInfo().getDataCapacity() - curCodewordCount;
+
+ if (rest == 2) {
+ buffer.append('\0'); //Shift 1
+ while (buffer.length() >= 3) {
+ writeNextTriplet(context, buffer);
+ }
+ if (context.hasMoreCharacters()) {
+ context.writeCodeword(HighLevelEncoder.C40_UNLATCH);
+ }
+ } else if (available == 1 && rest == 1) {
+ while (buffer.length() >= 3) {
+ writeNextTriplet(context, buffer);
+ }
+ if (context.hasMoreCharacters()) {
+ context.writeCodeword(HighLevelEncoder.C40_UNLATCH);
+ }
+ // else no unlatch
+ context.pos--;
+ } else if (rest == 0) {
+ while (buffer.length() >= 3) {
+ writeNextTriplet(context, buffer);
+ }
+ if (available > 0 || context.hasMoreCharacters()) {
+ context.writeCodeword(HighLevelEncoder.C40_UNLATCH);
+ }
+ } else {
+ throw new IllegalStateException("Unexpected case. Please report!");
+ }
+ context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION);
+ }
+
+ int encodeChar(char c, StringBuilder sb) {
+ if (c == ' ') {
+ sb.append('\3');
+ return 1;
+ } else if (c >= '0' && c <= '9') {
+ sb.append((char) (c - 48 + 4));
+ return 1;
+ } else if (c >= 'A' && c <= 'Z') {
+ sb.append((char) (c - 65 + 14));
+ return 1;
+ } else if (c >= '\0' && c <= '\u001f') {
+ sb.append('\0'); //Shift 1 Set
+ sb.append(c);
+ return 2;
+ } else if (c >= '!' && c <= '/') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 33));
+ return 2;
+ } else if (c >= ':' && c <= '@') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 58 + 15));
+ return 2;
+ } else if (c >= '[' && c <= '_') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 91 + 22));
+ return 2;
+ } else if (c >= '\u0060' && c <= '\u007f') {
+ sb.append('\2'); //Shift 3 Set
+ sb.append((char) (c - 96));
+ return 2;
+ } else if (c >= '\u0080') {
+ sb.append("\1\u001e"); //Shift 2, Upper Shift
+ int len = 2;
+ len += encodeChar((char) (c - 128), sb);
+ return len;
+ } else {
+ throw new IllegalArgumentException("Illegal character: " + c);
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/DataMatrixSymbolInfo144.java b/src/com/google/zxing/datamatrix/encoder/DataMatrixSymbolInfo144.java
new file mode 100644
index 0000000..b7dc6f0
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/DataMatrixSymbolInfo144.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2006 Jeremias Maerki
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+final class DataMatrixSymbolInfo144 extends SymbolInfo {
+
+ DataMatrixSymbolInfo144() {
+ super(false, 1558, 620, 22, 22, 36, -1, 62);
+ }
+
+ @Override
+ public int getInterleavedBlockCount() {
+ return 10;
+ }
+
+ @Override
+ public int getDataLengthForInterleavedBlock(int index) {
+ return (index <= 8) ? 156 : 155;
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/DefaultPlacement.java b/src/com/google/zxing/datamatrix/encoder/DefaultPlacement.java
new file mode 100644
index 0000000..eab1090
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/DefaultPlacement.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2006 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+import java.util.Arrays;
+
+/**
+ * Symbol Character Placement Program. Adapted from Annex M.1 in ISO/IEC 16022:2000(E).
+ */
+public class DefaultPlacement {
+
+ private final CharSequence codewords;
+ private final int numrows;
+ private final int numcols;
+ private final byte[] bits;
+
+ /**
+ * Main constructor
+ *
+ * @param codewords the codewords to place
+ * @param numcols the number of columns
+ * @param numrows the number of rows
+ */
+ public DefaultPlacement(CharSequence codewords, int numcols, int numrows) {
+ this.codewords = codewords;
+ this.numcols = numcols;
+ this.numrows = numrows;
+ this.bits = new byte[numcols * numrows];
+ Arrays.fill(this.bits, (byte) -1); //Initialize with "not set" value
+ }
+
+ final int getNumrows() {
+ return numrows;
+ }
+
+ final int getNumcols() {
+ return numcols;
+ }
+
+ final byte[] getBits() {
+ return bits;
+ }
+
+ public final boolean getBit(int col, int row) {
+ return bits[row * numcols + col] == 1;
+ }
+
+ final void setBit(int col, int row, boolean bit) {
+ bits[row * numcols + col] = bit ? (byte) 1 : (byte) 0;
+ }
+
+ final boolean hasBit(int col, int row) {
+ return bits[row * numcols + col] >= 0;
+ }
+
+ public final void place() {
+ int pos = 0;
+ int row = 4;
+ int col = 0;
+
+ do {
+ /* repeatedly first check for one of the special corner cases, then... */
+ if ((row == numrows) && (col == 0)) {
+ corner1(pos++);
+ }
+ if ((row == numrows - 2) && (col == 0) && ((numcols % 4) != 0)) {
+ corner2(pos++);
+ }
+ if ((row == numrows - 2) && (col == 0) && (numcols % 8 == 4)) {
+ corner3(pos++);
+ }
+ if ((row == numrows + 4) && (col == 2) && ((numcols % 8) == 0)) {
+ corner4(pos++);
+ }
+ /* sweep upward diagonally, inserting successive characters... */
+ do {
+ if ((row < numrows) && (col >= 0) && !hasBit(col, row)) {
+ utah(row, col, pos++);
+ }
+ row -= 2;
+ col += 2;
+ } while (row >= 0 && (col < numcols));
+ row++;
+ col += 3;
+
+ /* and then sweep downward diagonally, inserting successive characters, ... */
+ do {
+ if ((row >= 0) && (col < numcols) && !hasBit(col, row)) {
+ utah(row, col, pos++);
+ }
+ row += 2;
+ col -= 2;
+ } while ((row < numrows) && (col >= 0));
+ row += 3;
+ col++;
+
+ /* ...until the entire array is scanned */
+ } while ((row < numrows) || (col < numcols));
+
+ /* Lastly, if the lower righthand corner is untouched, fill in fixed pattern */
+ if (!hasBit(numcols - 1, numrows - 1)) {
+ setBit(numcols - 1, numrows - 1, true);
+ setBit(numcols - 2, numrows - 2, true);
+ }
+ }
+
+ private void module(int row, int col, int pos, int bit) {
+ if (row < 0) {
+ row += numrows;
+ col += 4 - ((numrows + 4) % 8);
+ }
+ if (col < 0) {
+ col += numcols;
+ row += 4 - ((numcols + 4) % 8);
+ }
+ // Note the conversion:
+ int v = codewords.charAt(pos);
+ v &= 1 << (8 - bit);
+ setBit(col, row, v != 0);
+ }
+
+ /**
+ * Places the 8 bits of a utah-shaped symbol character in ECC200.
+ *
+ * @param row the row
+ * @param col the column
+ * @param pos character position
+ */
+ private void utah(int row, int col, int pos) {
+ module(row - 2, col - 2, pos, 1);
+ module(row - 2, col - 1, pos, 2);
+ module(row - 1, col - 2, pos, 3);
+ module(row - 1, col - 1, pos, 4);
+ module(row - 1, col, pos, 5);
+ module(row, col - 2, pos, 6);
+ module(row, col - 1, pos, 7);
+ module(row, col, pos, 8);
+ }
+
+ private void corner1(int pos) {
+ module(numrows - 1, 0, pos, 1);
+ module(numrows - 1, 1, pos, 2);
+ module(numrows - 1, 2, pos, 3);
+ module(0, numcols - 2, pos, 4);
+ module(0, numcols - 1, pos, 5);
+ module(1, numcols - 1, pos, 6);
+ module(2, numcols - 1, pos, 7);
+ module(3, numcols - 1, pos, 8);
+ }
+
+ private void corner2(int pos) {
+ module(numrows - 3, 0, pos, 1);
+ module(numrows - 2, 0, pos, 2);
+ module(numrows - 1, 0, pos, 3);
+ module(0, numcols - 4, pos, 4);
+ module(0, numcols - 3, pos, 5);
+ module(0, numcols - 2, pos, 6);
+ module(0, numcols - 1, pos, 7);
+ module(1, numcols - 1, pos, 8);
+ }
+
+ private void corner3(int pos) {
+ module(numrows - 3, 0, pos, 1);
+ module(numrows - 2, 0, pos, 2);
+ module(numrows - 1, 0, pos, 3);
+ module(0, numcols - 2, pos, 4);
+ module(0, numcols - 1, pos, 5);
+ module(1, numcols - 1, pos, 6);
+ module(2, numcols - 1, pos, 7);
+ module(3, numcols - 1, pos, 8);
+ }
+
+ private void corner4(int pos) {
+ module(numrows - 1, 0, pos, 1);
+ module(numrows - 1, numcols - 1, pos, 2);
+ module(0, numcols - 3, pos, 3);
+ module(0, numcols - 2, pos, 4);
+ module(0, numcols - 1, pos, 5);
+ module(1, numcols - 3, pos, 6);
+ module(1, numcols - 2, pos, 7);
+ module(1, numcols - 1, pos, 8);
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/EdifactEncoder.java b/src/com/google/zxing/datamatrix/encoder/EdifactEncoder.java
new file mode 100644
index 0000000..85bff7c
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/EdifactEncoder.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+final class EdifactEncoder implements Encoder {
+
+ /**
+ * Handle "end of data" situations
+ *
+ * @param context the encoder context
+ * @param buffer the buffer with the remaining encoded characters
+ */
+ private static void handleEOD(EncoderContext context, CharSequence buffer) {
+ try {
+ int count = buffer.length();
+ if (count == 0) {
+ return; //Already finished
+ }
+ if (count == 1) {
+ //Only an unlatch at the end
+ context.updateSymbolInfo();
+ int available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount();
+ int remaining = context.getRemainingCharacters();
+ if (remaining == 0 && available <= 2) {
+ return; //No unlatch
+ }
+ }
+
+ if (count > 4) {
+ throw new IllegalStateException("Count must not exceed 4");
+ }
+ int restChars = count - 1;
+ String encoded = encodeToCodewords(buffer, 0);
+ boolean endOfSymbolReached = !context.hasMoreCharacters();
+ boolean restInAscii = endOfSymbolReached && restChars <= 2;
+
+ if (restChars <= 2) {
+ context.updateSymbolInfo(context.getCodewordCount() + restChars);
+ int available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount();
+ if (available >= 3) {
+ restInAscii = false;
+ context.updateSymbolInfo(context.getCodewordCount() + encoded.length());
+ //available = context.symbolInfo.dataCapacity - context.getCodewordCount();
+ }
+ }
+
+ if (restInAscii) {
+ context.resetSymbolInfo();
+ context.pos -= restChars;
+ } else {
+ context.writeCodewords(encoded);
+ }
+ } finally {
+ context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION);
+ }
+ }
+
+ private static void encodeChar(char c, StringBuilder sb) {
+ if (c >= ' ' && c <= '?') {
+ sb.append(c);
+ } else if (c >= '@' && c <= '^') {
+ sb.append((char) (c - 64));
+ } else {
+ HighLevelEncoder.illegalCharacter(c);
+ }
+ }
+
+ private static String encodeToCodewords(CharSequence sb, int startPos) {
+ int len = sb.length() - startPos;
+ if (len == 0) {
+ throw new IllegalStateException("StringBuilder must not be empty");
+ }
+ char c1 = sb.charAt(startPos);
+ char c2 = len >= 2 ? sb.charAt(startPos + 1) : 0;
+ char c3 = len >= 3 ? sb.charAt(startPos + 2) : 0;
+ char c4 = len >= 4 ? sb.charAt(startPos + 3) : 0;
+
+ int v = (c1 << 18) + (c2 << 12) + (c3 << 6) + c4;
+ char cw1 = (char) ((v >> 16) & 255);
+ char cw2 = (char) ((v >> 8) & 255);
+ char cw3 = (char) (v & 255);
+ StringBuilder res = new StringBuilder(3);
+ res.append(cw1);
+ if (len >= 2) {
+ res.append(cw2);
+ }
+ if (len >= 3) {
+ res.append(cw3);
+ }
+ return res.toString();
+ }
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.EDIFACT_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ //step F
+ StringBuilder buffer = new StringBuilder();
+ while (context.hasMoreCharacters()) {
+ char c = context.getCurrentChar();
+ encodeChar(c, buffer);
+ context.pos++;
+
+ int count = buffer.length();
+ if (count >= 4) {
+ context.writeCodewords(encodeToCodewords(buffer, 0));
+ buffer.delete(0, 4);
+
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION);
+ break;
+ }
+ }
+ }
+ buffer.append((char) 31); //Unlatch
+ handleEOD(context, buffer);
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/Encoder.java b/src/com/google/zxing/datamatrix/encoder/Encoder.java
new file mode 100644
index 0000000..e0c9b18
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/Encoder.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+interface Encoder {
+
+ int getEncodingMode();
+
+ void encode(EncoderContext context);
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/EncoderContext.java b/src/com/google/zxing/datamatrix/encoder/EncoderContext.java
new file mode 100644
index 0000000..10cfae8
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/EncoderContext.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+import com.google.zxing.Dimension;
+
+import java.nio.charset.Charset;
+
+final class EncoderContext {
+
+ private final String msg;
+ private final StringBuilder codewords;
+ int pos;
+ private SymbolShapeHint shape;
+ private Dimension minSize;
+ private Dimension maxSize;
+ private int newEncoding;
+ private SymbolInfo symbolInfo;
+ private int skipAtEnd;
+
+ EncoderContext(String msg) {
+ //From this point on Strings are not Unicode anymore!
+ byte[] msgBinary = msg.getBytes(Charset.forName("ISO-8859-1"));
+ StringBuilder sb = new StringBuilder(msgBinary.length);
+ for (int i = 0, c = msgBinary.length; i < c; i++) {
+ char ch = (char) (msgBinary[i] & 0xff);
+ if (ch == '?' && msg.charAt(i) != '?') {
+ throw new IllegalArgumentException("Message contains characters outside ISO-8859-1 encoding.");
+ }
+ sb.append(ch);
+ }
+ this.msg = sb.toString(); //Not Unicode here!
+ shape = SymbolShapeHint.FORCE_NONE;
+ this.codewords = new StringBuilder(msg.length());
+ newEncoding = -1;
+ }
+
+ public void setSymbolShape(SymbolShapeHint shape) {
+ this.shape = shape;
+ }
+
+ public void setSizeConstraints(Dimension minSize, Dimension maxSize) {
+ this.minSize = minSize;
+ this.maxSize = maxSize;
+ }
+
+ public String getMessage() {
+ return this.msg;
+ }
+
+ public void setSkipAtEnd(int count) {
+ this.skipAtEnd = count;
+ }
+
+ public char getCurrentChar() {
+ return msg.charAt(pos);
+ }
+
+ public char getCurrent() {
+ return msg.charAt(pos);
+ }
+
+ public StringBuilder getCodewords() {
+ return codewords;
+ }
+
+ public void writeCodewords(String codewords) {
+ this.codewords.append(codewords);
+ }
+
+ public void writeCodeword(char codeword) {
+ this.codewords.append(codeword);
+ }
+
+ public int getCodewordCount() {
+ return this.codewords.length();
+ }
+
+ public int getNewEncoding() {
+ return newEncoding;
+ }
+
+ public void signalEncoderChange(int encoding) {
+ this.newEncoding = encoding;
+ }
+
+ public void resetEncoderSignal() {
+ this.newEncoding = -1;
+ }
+
+ public boolean hasMoreCharacters() {
+ return pos < getTotalMessageCharCount();
+ }
+
+ private int getTotalMessageCharCount() {
+ return msg.length() - skipAtEnd;
+ }
+
+ public int getRemainingCharacters() {
+ return getTotalMessageCharCount() - pos;
+ }
+
+ public SymbolInfo getSymbolInfo() {
+ return symbolInfo;
+ }
+
+ public void updateSymbolInfo() {
+ updateSymbolInfo(getCodewordCount());
+ }
+
+ public void updateSymbolInfo(int len) {
+ if (this.symbolInfo == null || len > this.symbolInfo.getDataCapacity()) {
+ this.symbolInfo = SymbolInfo.lookup(len, shape, minSize, maxSize, true);
+ }
+ }
+
+ public void resetSymbolInfo() {
+ this.symbolInfo = null;
+ }
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/ErrorCorrection.java b/src/com/google/zxing/datamatrix/encoder/ErrorCorrection.java
new file mode 100644
index 0000000..a591049
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/ErrorCorrection.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2006 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+/**
+ * Error Correction Code for ECC200.
+ */
+public final class ErrorCorrection {
+
+ /**
+ * Lookup table which factors to use for which number of error correction codewords.
+ * See FACTORS.
+ */
+ private static final int[] FACTOR_SETS
+ = {5, 7, 10, 11, 12, 14, 18, 20, 24, 28, 36, 42, 48, 56, 62, 68};
+
+ /**
+ * Precomputed polynomial factors for ECC 200.
+ */
+ private static final int[][] FACTORS = {
+ {228, 48, 15, 111, 62},
+ {23, 68, 144, 134, 240, 92, 254},
+ {28, 24, 185, 166, 223, 248, 116, 255, 110, 61},
+ {175, 138, 205, 12, 194, 168, 39, 245, 60, 97, 120},
+ {41, 153, 158, 91, 61, 42, 142, 213, 97, 178, 100, 242},
+ {156, 97, 192, 252, 95, 9, 157, 119, 138, 45, 18, 186, 83, 185},
+ {83, 195, 100, 39, 188, 75, 66, 61, 241, 213, 109, 129, 94, 254, 225, 48, 90, 188},
+ {15, 195, 244, 9, 233, 71, 168, 2, 188, 160, 153, 145, 253, 79, 108, 82, 27, 174, 186, 172},
+ {52, 190, 88, 205, 109, 39, 176, 21, 155, 197, 251, 223, 155, 21, 5, 172,
+ 254, 124, 12, 181, 184, 96, 50, 193},
+ {211, 231, 43, 97, 71, 96, 103, 174, 37, 151, 170, 53, 75, 34, 249, 121,
+ 17, 138, 110, 213, 141, 136, 120, 151, 233, 168, 93, 255},
+ {245, 127, 242, 218, 130, 250, 162, 181, 102, 120, 84, 179, 220, 251, 80, 182,
+ 229, 18, 2, 4, 68, 33, 101, 137, 95, 119, 115, 44, 175, 184, 59, 25,
+ 225, 98, 81, 112},
+ {77, 193, 137, 31, 19, 38, 22, 153, 247, 105, 122, 2, 245, 133, 242, 8,
+ 175, 95, 100, 9, 167, 105, 214, 111, 57, 121, 21, 1, 253, 57, 54, 101,
+ 248, 202, 69, 50, 150, 177, 226, 5, 9, 5},
+ {245, 132, 172, 223, 96, 32, 117, 22, 238, 133, 238, 231, 205, 188, 237, 87,
+ 191, 106, 16, 147, 118, 23, 37, 90, 170, 205, 131, 88, 120, 100, 66, 138,
+ 186, 240, 82, 44, 176, 87, 187, 147, 160, 175, 69, 213, 92, 253, 225, 19},
+ {175, 9, 223, 238, 12, 17, 220, 208, 100, 29, 175, 170, 230, 192, 215, 235,
+ 150, 159, 36, 223, 38, 200, 132, 54, 228, 146, 218, 234, 117, 203, 29, 232,
+ 144, 238, 22, 150, 201, 117, 62, 207, 164, 13, 137, 245, 127, 67, 247, 28,
+ 155, 43, 203, 107, 233, 53, 143, 46},
+ {242, 93, 169, 50, 144, 210, 39, 118, 202, 188, 201, 189, 143, 108, 196, 37,
+ 185, 112, 134, 230, 245, 63, 197, 190, 250, 106, 185, 221, 175, 64, 114, 71,
+ 161, 44, 147, 6, 27, 218, 51, 63, 87, 10, 40, 130, 188, 17, 163, 31,
+ 176, 170, 4, 107, 232, 7, 94, 166, 224, 124, 86, 47, 11, 204},
+ {220, 228, 173, 89, 251, 149, 159, 56, 89, 33, 147, 244, 154, 36, 73, 127,
+ 213, 136, 248, 180, 234, 197, 158, 177, 68, 122, 93, 213, 15, 160, 227, 236,
+ 66, 139, 153, 185, 202, 167, 179, 25, 220, 232, 96, 210, 231, 136, 223, 239,
+ 181, 241, 59, 52, 172, 25, 49, 232, 211, 189, 64, 54, 108, 153, 132, 63,
+ 96, 103, 82, 186}};
+
+ private static final int MODULO_VALUE = 0x12D;
+
+ private static final int[] LOG;
+ private static final int[] ALOG;
+
+ static {
+ //Create log and antilog table
+ LOG = new int[256];
+ ALOG = new int[255];
+
+ int p = 1;
+ for (int i = 0; i < 255; i++) {
+ ALOG[i] = p;
+ LOG[p] = i;
+ p *= 2;
+ if (p >= 256) {
+ p ^= MODULO_VALUE;
+ }
+ }
+ }
+
+ private ErrorCorrection() {
+ }
+
+ /**
+ * Creates the ECC200 error correction for an encoded message.
+ *
+ * @param codewords the codewords
+ * @param symbolInfo information about the symbol to be encoded
+ * @return the codewords with interleaved error correction.
+ */
+ public static String encodeECC200(String codewords, SymbolInfo symbolInfo) {
+ if (codewords.length() != symbolInfo.getDataCapacity()) {
+ throw new IllegalArgumentException(
+ "The number of codewords does not match the selected symbol");
+ }
+ StringBuilder sb = new StringBuilder(symbolInfo.getDataCapacity() + symbolInfo.getErrorCodewords());
+ sb.append(codewords);
+ int blockCount = symbolInfo.getInterleavedBlockCount();
+ if (blockCount == 1) {
+ String ecc = createECCBlock(codewords, symbolInfo.getErrorCodewords());
+ sb.append(ecc);
+ } else {
+ sb.setLength(sb.capacity());
+ int[] dataSizes = new int[blockCount];
+ int[] errorSizes = new int[blockCount];
+ int[] startPos = new int[blockCount];
+ for (int i = 0; i < blockCount; i++) {
+ dataSizes[i] = symbolInfo.getDataLengthForInterleavedBlock(i + 1);
+ errorSizes[i] = symbolInfo.getErrorLengthForInterleavedBlock(i + 1);
+ startPos[i] = 0;
+ if (i > 0) {
+ startPos[i] = startPos[i - 1] + dataSizes[i];
+ }
+ }
+ for (int block = 0; block < blockCount; block++) {
+ StringBuilder temp = new StringBuilder(dataSizes[block]);
+ for (int d = block; d < symbolInfo.getDataCapacity(); d += blockCount) {
+ temp.append(codewords.charAt(d));
+ }
+ String ecc = createECCBlock(temp.toString(), errorSizes[block]);
+ int pos = 0;
+ for (int e = block; e < errorSizes[block] * blockCount; e += blockCount) {
+ sb.setCharAt(symbolInfo.getDataCapacity() + e, ecc.charAt(pos++));
+ }
+ }
+ }
+ return sb.toString();
+
+ }
+
+ private static String createECCBlock(CharSequence codewords, int numECWords) {
+ return createECCBlock(codewords, 0, codewords.length(), numECWords);
+ }
+
+ private static String createECCBlock(CharSequence codewords, int start, int len, int numECWords) {
+ int table = -1;
+ for (int i = 0; i < FACTOR_SETS.length; i++) {
+ if (FACTOR_SETS[i] == numECWords) {
+ table = i;
+ break;
+ }
+ }
+ if (table < 0) {
+ throw new IllegalArgumentException(
+ "Illegal number of error correction codewords specified: " + numECWords);
+ }
+ int[] poly = FACTORS[table];
+ char[] ecc = new char[numECWords];
+ for (int i = 0; i < numECWords; i++) {
+ ecc[i] = 0;
+ }
+ for (int i = start; i < start + len; i++) {
+ int m = ecc[numECWords - 1] ^ codewords.charAt(i);
+ for (int k = numECWords - 1; k > 0; k--) {
+ if (m != 0 && poly[k] != 0) {
+ ecc[k] = (char) (ecc[k - 1] ^ ALOG[(LOG[m] + LOG[poly[k]]) % 255]);
+ } else {
+ ecc[k] = ecc[k - 1];
+ }
+ }
+ if (m != 0 && poly[0] != 0) {
+ ecc[0] = (char) ALOG[(LOG[m] + LOG[poly[0]]) % 255];
+ } else {
+ ecc[0] = 0;
+ }
+ }
+ char[] eccReversed = new char[numECWords];
+ for (int i = 0; i < numECWords; i++) {
+ eccReversed[i] = ecc[numECWords - i - 1];
+ }
+ return String.valueOf(eccReversed);
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/HighLevelEncoder.java b/src/com/google/zxing/datamatrix/encoder/HighLevelEncoder.java
new file mode 100644
index 0000000..6ddb756
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/HighLevelEncoder.java
@@ -0,0 +1,445 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+import com.google.zxing.Dimension;
+
+import java.util.Arrays;
+
+/**
+ * DataMatrix ECC 200 data encoder following the algorithm described in ISO/IEC 16022:200(E) in
+ * annex S.
+ */
+public final class HighLevelEncoder {
+
+ /**
+ * mode latch to C40 encodation mode
+ */
+ static final char LATCH_TO_C40 = 230;
+ /**
+ * mode latch to Base 256 encodation mode
+ */
+ static final char LATCH_TO_BASE256 = 231;
+ /**
+ * Upper Shift
+ */
+ static final char UPPER_SHIFT = 235;
+ /**
+ * FNC1 Codeword
+ */
+ //private static final char FNC1 = 232;
+ /**
+ * Structured Append Codeword
+ */
+ //private static final char STRUCTURED_APPEND = 233;
+ /**
+ * Reader Programming
+ */
+ //private static final char READER_PROGRAMMING = 234;
+ /**
+ * mode latch to ANSI X.12 encodation mode
+ */
+ static final char LATCH_TO_ANSIX12 = 238;
+ /**
+ * mode latch to Text encodation mode
+ */
+ static final char LATCH_TO_TEXT = 239;
+ /**
+ * mode latch to EDIFACT encodation mode
+ */
+ static final char LATCH_TO_EDIFACT = 240;
+ /**
+ * Unlatch from C40 encodation
+ */
+ static final char C40_UNLATCH = 254;
+ /**
+ * Unlatch from X12 encodation
+ */
+ static final char X12_UNLATCH = 254;
+ static final int ASCII_ENCODATION = 0;
+ /**
+ * ECI character (Extended Channel Interpretation)
+ */
+ //private static final char ECI = 241;
+ static final int C40_ENCODATION = 1;
+ static final int TEXT_ENCODATION = 2;
+ static final int X12_ENCODATION = 3;
+ static final int EDIFACT_ENCODATION = 4;
+ static final int BASE256_ENCODATION = 5;
+ /**
+ * Padding character
+ */
+ private static final char PAD = 129;
+ /**
+ * 05 Macro
+ */
+ private static final char MACRO_05 = 236;
+ /**
+ * 06 Macro
+ */
+ private static final char MACRO_06 = 237;
+ /**
+ * 05 Macro header
+ */
+ private static final String MACRO_05_HEADER = "[)>\u001E05\u001D";
+ /**
+ * 06 Macro header
+ */
+ private static final String MACRO_06_HEADER = "[)>\u001E06\u001D";
+ /**
+ * Macro trailer
+ */
+ private static final String MACRO_TRAILER = "\u001E\u0004";
+
+ private HighLevelEncoder() {
+ }
+
+ /*
+ * Converts the message to a byte array using the default encoding (cp437) as defined by the
+ * specification
+ *
+ * @param msg the message
+ * @return the byte array of the message
+ */
+
+ /*
+ public static byte[] getBytesForMessage(String msg) {
+ return msg.getBytes(Charset.forName("cp437")); //See 4.4.3 and annex B of ISO/IEC 15438:2001(E)
+ }
+ */
+
+ private static char randomize253State(char ch, int codewordPosition) {
+ int pseudoRandom = ((149 * codewordPosition) % 253) + 1;
+ int tempVariable = ch + pseudoRandom;
+ return tempVariable <= 254 ? (char) tempVariable : (char) (tempVariable - 254);
+ }
+
+ /**
+ * Performs message encoding of a DataMatrix message using the algorithm described in annex P
+ * of ISO/IEC 16022:2000(E).
+ *
+ * @param msg the message
+ * @return the encoded message (the char values range from 0 to 255)
+ */
+ public static String encodeHighLevel(String msg) {
+ return encodeHighLevel(msg, SymbolShapeHint.FORCE_NONE, null, null);
+ }
+
+ /**
+ * Performs message encoding of a DataMatrix message using the algorithm described in annex P
+ * of ISO/IEC 16022:2000(E).
+ *
+ * @param msg the message
+ * @param shape requested shape. May be {@code SymbolShapeHint.FORCE_NONE},
+ * {@code SymbolShapeHint.FORCE_SQUARE} or {@code SymbolShapeHint.FORCE_RECTANGLE}.
+ * @param minSize the minimum symbol size constraint or null for no constraint
+ * @param maxSize the maximum symbol size constraint or null for no constraint
+ * @return the encoded message (the char values range from 0 to 255)
+ */
+ public static String encodeHighLevel(String msg,
+ SymbolShapeHint shape,
+ Dimension minSize,
+ Dimension maxSize) {
+ //the codewords 0..255 are encoded as Unicode characters
+ Encoder[] encoders = {
+ new ASCIIEncoder(), new C40Encoder(), new TextEncoder(),
+ new X12Encoder(), new EdifactEncoder(), new Base256Encoder()
+ };
+
+ EncoderContext context = new EncoderContext(msg);
+ context.setSymbolShape(shape);
+ context.setSizeConstraints(minSize, maxSize);
+
+ if (msg.startsWith(MACRO_05_HEADER) && msg.endsWith(MACRO_TRAILER)) {
+ context.writeCodeword(MACRO_05);
+ context.setSkipAtEnd(2);
+ context.pos += MACRO_05_HEADER.length();
+ } else if (msg.startsWith(MACRO_06_HEADER) && msg.endsWith(MACRO_TRAILER)) {
+ context.writeCodeword(MACRO_06);
+ context.setSkipAtEnd(2);
+ context.pos += MACRO_06_HEADER.length();
+ }
+
+ int encodingMode = ASCII_ENCODATION; //Default mode
+ while (context.hasMoreCharacters()) {
+ encoders[encodingMode].encode(context);
+ if (context.getNewEncoding() >= 0) {
+ encodingMode = context.getNewEncoding();
+ context.resetEncoderSignal();
+ }
+ }
+ int len = context.getCodewordCount();
+ context.updateSymbolInfo();
+ int capacity = context.getSymbolInfo().getDataCapacity();
+ if (len < capacity) {
+ if (encodingMode != ASCII_ENCODATION && encodingMode != BASE256_ENCODATION) {
+ context.writeCodeword('\u00fe'); //Unlatch (254)
+ }
+ }
+ //Padding
+ StringBuilder codewords = context.getCodewords();
+ if (codewords.length() < capacity) {
+ codewords.append(PAD);
+ }
+ while (codewords.length() < capacity) {
+ codewords.append(randomize253State(PAD, codewords.length() + 1));
+ }
+
+ return context.getCodewords().toString();
+ }
+
+ static int lookAheadTest(CharSequence msg, int startpos, int currentMode) {
+ if (startpos >= msg.length()) {
+ return currentMode;
+ }
+ float[] charCounts;
+ //step J
+ if (currentMode == ASCII_ENCODATION) {
+ charCounts = new float[]{0, 1, 1, 1, 1, 1.25f};
+ } else {
+ charCounts = new float[]{1, 2, 2, 2, 2, 2.25f};
+ charCounts[currentMode] = 0;
+ }
+
+ int charsProcessed = 0;
+ while (true) {
+ //step K
+ if ((startpos + charsProcessed) == msg.length()) {
+ int min = Integer.MAX_VALUE;
+ byte[] mins = new byte[6];
+ int[] intCharCounts = new int[6];
+ min = findMinimums(charCounts, intCharCounts, min, mins);
+ int minCount = getMinimumCount(mins);
+
+ if (intCharCounts[ASCII_ENCODATION] == min) {
+ return ASCII_ENCODATION;
+ }
+ if (minCount == 1 && mins[BASE256_ENCODATION] > 0) {
+ return BASE256_ENCODATION;
+ }
+ if (minCount == 1 && mins[EDIFACT_ENCODATION] > 0) {
+ return EDIFACT_ENCODATION;
+ }
+ if (minCount == 1 && mins[TEXT_ENCODATION] > 0) {
+ return TEXT_ENCODATION;
+ }
+ if (minCount == 1 && mins[X12_ENCODATION] > 0) {
+ return X12_ENCODATION;
+ }
+ return C40_ENCODATION;
+ }
+
+ char c = msg.charAt(startpos + charsProcessed);
+ charsProcessed++;
+
+ //step L
+ if (isDigit(c)) {
+ charCounts[ASCII_ENCODATION] += 0.5;
+ } else if (isExtendedASCII(c)) {
+ charCounts[ASCII_ENCODATION] = (int) Math.ceil(charCounts[ASCII_ENCODATION]);
+ charCounts[ASCII_ENCODATION] += 2;
+ } else {
+ charCounts[ASCII_ENCODATION] = (int) Math.ceil(charCounts[ASCII_ENCODATION]);
+ charCounts[ASCII_ENCODATION]++;
+ }
+
+ //step M
+ if (isNativeC40(c)) {
+ charCounts[C40_ENCODATION] += 2.0f / 3.0f;
+ } else if (isExtendedASCII(c)) {
+ charCounts[C40_ENCODATION] += 8.0f / 3.0f;
+ } else {
+ charCounts[C40_ENCODATION] += 4.0f / 3.0f;
+ }
+
+ //step N
+ if (isNativeText(c)) {
+ charCounts[TEXT_ENCODATION] += 2.0f / 3.0f;
+ } else if (isExtendedASCII(c)) {
+ charCounts[TEXT_ENCODATION] += 8.0f / 3.0f;
+ } else {
+ charCounts[TEXT_ENCODATION] += 4.0f / 3.0f;
+ }
+
+ //step O
+ if (isNativeX12(c)) {
+ charCounts[X12_ENCODATION] += 2.0f / 3.0f;
+ } else if (isExtendedASCII(c)) {
+ charCounts[X12_ENCODATION] += 13.0f / 3.0f;
+ } else {
+ charCounts[X12_ENCODATION] += 10.0f / 3.0f;
+ }
+
+ //step P
+ if (isNativeEDIFACT(c)) {
+ charCounts[EDIFACT_ENCODATION] += 3.0f / 4.0f;
+ } else if (isExtendedASCII(c)) {
+ charCounts[EDIFACT_ENCODATION] += 17.0f / 4.0f;
+ } else {
+ charCounts[EDIFACT_ENCODATION] += 13.0f / 4.0f;
+ }
+
+ // step Q
+ if (isSpecialB256(c)) {
+ charCounts[BASE256_ENCODATION] += 4;
+ } else {
+ charCounts[BASE256_ENCODATION]++;
+ }
+
+ //step R
+ if (charsProcessed >= 4) {
+ int[] intCharCounts = new int[6];
+ byte[] mins = new byte[6];
+ findMinimums(charCounts, intCharCounts, Integer.MAX_VALUE, mins);
+ int minCount = getMinimumCount(mins);
+
+ if (intCharCounts[ASCII_ENCODATION] < intCharCounts[BASE256_ENCODATION]
+ && intCharCounts[ASCII_ENCODATION] < intCharCounts[C40_ENCODATION]
+ && intCharCounts[ASCII_ENCODATION] < intCharCounts[TEXT_ENCODATION]
+ && intCharCounts[ASCII_ENCODATION] < intCharCounts[X12_ENCODATION]
+ && intCharCounts[ASCII_ENCODATION] < intCharCounts[EDIFACT_ENCODATION]) {
+ return ASCII_ENCODATION;
+ }
+ if (intCharCounts[BASE256_ENCODATION] < intCharCounts[ASCII_ENCODATION]
+ || (mins[C40_ENCODATION] + mins[TEXT_ENCODATION] + mins[X12_ENCODATION] + mins[EDIFACT_ENCODATION]) == 0) {
+ return BASE256_ENCODATION;
+ }
+ if (minCount == 1 && mins[EDIFACT_ENCODATION] > 0) {
+ return EDIFACT_ENCODATION;
+ }
+ if (minCount == 1 && mins[TEXT_ENCODATION] > 0) {
+ return TEXT_ENCODATION;
+ }
+ if (minCount == 1 && mins[X12_ENCODATION] > 0) {
+ return X12_ENCODATION;
+ }
+ if (intCharCounts[C40_ENCODATION] + 1 < intCharCounts[ASCII_ENCODATION]
+ && intCharCounts[C40_ENCODATION] + 1 < intCharCounts[BASE256_ENCODATION]
+ && intCharCounts[C40_ENCODATION] + 1 < intCharCounts[EDIFACT_ENCODATION]
+ && intCharCounts[C40_ENCODATION] + 1 < intCharCounts[TEXT_ENCODATION]) {
+ if (intCharCounts[C40_ENCODATION] < intCharCounts[X12_ENCODATION]) {
+ return C40_ENCODATION;
+ }
+ if (intCharCounts[C40_ENCODATION] == intCharCounts[X12_ENCODATION]) {
+ int p = startpos + charsProcessed + 1;
+ while (p < msg.length()) {
+ char tc = msg.charAt(p);
+ if (isX12TermSep(tc)) {
+ return X12_ENCODATION;
+ }
+ if (!isNativeX12(tc)) {
+ break;
+ }
+ p++;
+ }
+ return C40_ENCODATION;
+ }
+ }
+ }
+ }
+ }
+
+ private static int findMinimums(float[] charCounts, int[] intCharCounts, int min, byte[] mins) {
+ Arrays.fill(mins, (byte) 0);
+ for (int i = 0; i < 6; i++) {
+ intCharCounts[i] = (int) Math.ceil(charCounts[i]);
+ int current = intCharCounts[i];
+ if (min > current) {
+ min = current;
+ Arrays.fill(mins, (byte) 0);
+ }
+ if (min == current) {
+ mins[i]++;
+
+ }
+ }
+ return min;
+ }
+
+ private static int getMinimumCount(byte[] mins) {
+ int minCount = 0;
+ for (int i = 0; i < 6; i++) {
+ minCount += mins[i];
+ }
+ return minCount;
+ }
+
+ static boolean isDigit(char ch) {
+ return ch >= '0' && ch <= '9';
+ }
+
+ static boolean isExtendedASCII(char ch) {
+ return ch >= 128 && ch <= 255;
+ }
+
+ private static boolean isNativeC40(char ch) {
+ return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z');
+ }
+
+ private static boolean isNativeText(char ch) {
+ return (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z');
+ }
+
+ private static boolean isNativeX12(char ch) {
+ return isX12TermSep(ch) || (ch == ' ') || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z');
+ }
+
+ private static boolean isX12TermSep(char ch) {
+ return (ch == '\r') //CR
+ || (ch == '*')
+ || (ch == '>');
+ }
+
+ private static boolean isNativeEDIFACT(char ch) {
+ return ch >= ' ' && ch <= '^';
+ }
+
+ private static boolean isSpecialB256(char ch) {
+ return false; //TODO NOT IMPLEMENTED YET!!!
+ }
+
+ /**
+ * Determines the number of consecutive characters that are encodable using numeric compaction.
+ *
+ * @param msg the message
+ * @param startpos the start position within the message
+ * @return the requested character count
+ */
+ public static int determineConsecutiveDigitCount(CharSequence msg, int startpos) {
+ int count = 0;
+ int len = msg.length();
+ int idx = startpos;
+ if (idx < len) {
+ char ch = msg.charAt(idx);
+ while (isDigit(ch) && idx < len) {
+ count++;
+ idx++;
+ if (idx < len) {
+ ch = msg.charAt(idx);
+ }
+ }
+ }
+ return count;
+ }
+
+ static void illegalCharacter(char c) {
+ String hex = Integer.toHexString(c);
+ hex = "0000".substring(0, 4 - hex.length()) + hex;
+ throw new IllegalArgumentException("Illegal character: " + c + " (0x" + hex + ')');
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/SymbolInfo.java b/src/com/google/zxing/datamatrix/encoder/SymbolInfo.java
new file mode 100644
index 0000000..98064b7
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/SymbolInfo.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2006 Jeremias Maerki
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+import com.google.zxing.Dimension;
+
+/**
+ * Symbol info table for DataMatrix.
+ *
+ * @version $Id$
+ */
+public class SymbolInfo {
+
+ static final SymbolInfo[] PROD_SYMBOLS = {
+ new SymbolInfo(false, 3, 5, 8, 8, 1),
+ new SymbolInfo(false, 5, 7, 10, 10, 1),
+ /*rect*/new SymbolInfo(true, 5, 7, 16, 6, 1),
+ new SymbolInfo(false, 8, 10, 12, 12, 1),
+ /*rect*/new SymbolInfo(true, 10, 11, 14, 6, 2),
+ new SymbolInfo(false, 12, 12, 14, 14, 1),
+ /*rect*/new SymbolInfo(true, 16, 14, 24, 10, 1),
+
+ new SymbolInfo(false, 18, 14, 16, 16, 1),
+ new SymbolInfo(false, 22, 18, 18, 18, 1),
+ /*rect*/new SymbolInfo(true, 22, 18, 16, 10, 2),
+ new SymbolInfo(false, 30, 20, 20, 20, 1),
+ /*rect*/new SymbolInfo(true, 32, 24, 16, 14, 2),
+ new SymbolInfo(false, 36, 24, 22, 22, 1),
+ new SymbolInfo(false, 44, 28, 24, 24, 1),
+ /*rect*/new SymbolInfo(true, 49, 28, 22, 14, 2),
+
+ new SymbolInfo(false, 62, 36, 14, 14, 4),
+ new SymbolInfo(false, 86, 42, 16, 16, 4),
+ new SymbolInfo(false, 114, 48, 18, 18, 4),
+ new SymbolInfo(false, 144, 56, 20, 20, 4),
+ new SymbolInfo(false, 174, 68, 22, 22, 4),
+
+ new SymbolInfo(false, 204, 84, 24, 24, 4, 102, 42),
+ new SymbolInfo(false, 280, 112, 14, 14, 16, 140, 56),
+ new SymbolInfo(false, 368, 144, 16, 16, 16, 92, 36),
+ new SymbolInfo(false, 456, 192, 18, 18, 16, 114, 48),
+ new SymbolInfo(false, 576, 224, 20, 20, 16, 144, 56),
+ new SymbolInfo(false, 696, 272, 22, 22, 16, 174, 68),
+ new SymbolInfo(false, 816, 336, 24, 24, 16, 136, 56),
+ new SymbolInfo(false, 1050, 408, 18, 18, 36, 175, 68),
+ new SymbolInfo(false, 1304, 496, 20, 20, 36, 163, 62),
+ new DataMatrixSymbolInfo144(),
+ };
+
+ private static SymbolInfo[] symbols = PROD_SYMBOLS;
+ public final int matrixWidth;
+ public final int matrixHeight;
+ private final boolean rectangular;
+ private final int dataCapacity;
+ private final int errorCodewords;
+ private final int dataRegions;
+ private final int rsBlockData;
+ private final int rsBlockError;
+ public SymbolInfo(boolean rectangular, int dataCapacity, int errorCodewords,
+ int matrixWidth, int matrixHeight, int dataRegions) {
+ this(rectangular, dataCapacity, errorCodewords, matrixWidth, matrixHeight, dataRegions,
+ dataCapacity, errorCodewords);
+ }
+
+ SymbolInfo(boolean rectangular, int dataCapacity, int errorCodewords,
+ int matrixWidth, int matrixHeight, int dataRegions,
+ int rsBlockData, int rsBlockError) {
+ this.rectangular = rectangular;
+ this.dataCapacity = dataCapacity;
+ this.errorCodewords = errorCodewords;
+ this.matrixWidth = matrixWidth;
+ this.matrixHeight = matrixHeight;
+ this.dataRegions = dataRegions;
+ this.rsBlockData = rsBlockData;
+ this.rsBlockError = rsBlockError;
+ }
+
+ /**
+ * Overrides the symbol info set used by this class. Used for testing purposes.
+ *
+ * @param override the symbol info set to use
+ */
+ public static void overrideSymbolSet(SymbolInfo[] override) {
+ symbols = override;
+ }
+
+ public static SymbolInfo lookup(int dataCodewords) {
+ return lookup(dataCodewords, SymbolShapeHint.FORCE_NONE, true);
+ }
+
+ public static SymbolInfo lookup(int dataCodewords, SymbolShapeHint shape) {
+ return lookup(dataCodewords, shape, true);
+ }
+
+ public static SymbolInfo lookup(int dataCodewords, boolean allowRectangular, boolean fail) {
+ SymbolShapeHint shape = allowRectangular
+ ? SymbolShapeHint.FORCE_NONE : SymbolShapeHint.FORCE_SQUARE;
+ return lookup(dataCodewords, shape, fail);
+ }
+
+ private static SymbolInfo lookup(int dataCodewords, SymbolShapeHint shape, boolean fail) {
+ return lookup(dataCodewords, shape, null, null, fail);
+ }
+
+ public static SymbolInfo lookup(int dataCodewords,
+ SymbolShapeHint shape,
+ Dimension minSize,
+ Dimension maxSize,
+ boolean fail) {
+ for (SymbolInfo symbol : symbols) {
+ if (shape == SymbolShapeHint.FORCE_SQUARE && symbol.rectangular) {
+ continue;
+ }
+ if (shape == SymbolShapeHint.FORCE_RECTANGLE && !symbol.rectangular) {
+ continue;
+ }
+ if (minSize != null
+ && (symbol.getSymbolWidth() < minSize.getWidth()
+ || symbol.getSymbolHeight() < minSize.getHeight())) {
+ continue;
+ }
+ if (maxSize != null
+ && (symbol.getSymbolWidth() > maxSize.getWidth()
+ || symbol.getSymbolHeight() > maxSize.getHeight())) {
+ continue;
+ }
+ if (dataCodewords <= symbol.dataCapacity) {
+ return symbol;
+ }
+ }
+ if (fail) {
+ throw new IllegalArgumentException(
+ "Can't find a symbol arrangement that matches the message. Data codewords: "
+ + dataCodewords);
+ }
+ return null;
+ }
+
+ final int getHorizontalDataRegions() {
+ switch (dataRegions) {
+ case 1:
+ return 1;
+ case 2:
+ return 2;
+ case 4:
+ return 2;
+ case 16:
+ return 4;
+ case 36:
+ return 6;
+ default:
+ throw new IllegalStateException("Cannot handle this number of data regions");
+ }
+ }
+
+ final int getVerticalDataRegions() {
+ switch (dataRegions) {
+ case 1:
+ return 1;
+ case 2:
+ return 1;
+ case 4:
+ return 2;
+ case 16:
+ return 4;
+ case 36:
+ return 6;
+ default:
+ throw new IllegalStateException("Cannot handle this number of data regions");
+ }
+ }
+
+ public final int getSymbolDataWidth() {
+ return getHorizontalDataRegions() * matrixWidth;
+ }
+
+ public final int getSymbolDataHeight() {
+ return getVerticalDataRegions() * matrixHeight;
+ }
+
+ public final int getSymbolWidth() {
+ return getSymbolDataWidth() + (getHorizontalDataRegions() * 2);
+ }
+
+ public final int getSymbolHeight() {
+ return getSymbolDataHeight() + (getVerticalDataRegions() * 2);
+ }
+
+ public int getCodewordCount() {
+ return dataCapacity + errorCodewords;
+ }
+
+ public int getInterleavedBlockCount() {
+ return dataCapacity / rsBlockData;
+ }
+
+ public final int getDataCapacity() {
+ return dataCapacity;
+ }
+
+ public final int getErrorCodewords() {
+ return errorCodewords;
+ }
+
+ public int getDataLengthForInterleavedBlock(int index) {
+ return rsBlockData;
+ }
+
+ public final int getErrorLengthForInterleavedBlock(int index) {
+ return rsBlockError;
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(rectangular ? "Rectangular Symbol:" : "Square Symbol:");
+ sb.append(" data region ").append(matrixWidth).append('x').append(matrixHeight);
+ sb.append(", symbol size ").append(getSymbolWidth()).append('x').append(getSymbolHeight());
+ sb.append(", symbol data size ").append(getSymbolDataWidth()).append('x').append(getSymbolDataHeight());
+ sb.append(", codewords ").append(dataCapacity).append('+').append(errorCodewords);
+ return sb.toString();
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/SymbolShapeHint.java b/src/com/google/zxing/datamatrix/encoder/SymbolShapeHint.java
new file mode 100644
index 0000000..6ea5b51
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/SymbolShapeHint.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+/**
+ * Enumeration for DataMatrix symbol shape hint. It can be used to force square or rectangular
+ * symbols.
+ */
+public enum SymbolShapeHint {
+
+ FORCE_NONE,
+ FORCE_SQUARE,
+ FORCE_RECTANGLE,
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/TextEncoder.java b/src/com/google/zxing/datamatrix/encoder/TextEncoder.java
new file mode 100644
index 0000000..dcf17f6
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/TextEncoder.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+final class TextEncoder extends C40Encoder {
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.TEXT_ENCODATION;
+ }
+
+ @Override
+ int encodeChar(char c, StringBuilder sb) {
+ if (c == ' ') {
+ sb.append('\3');
+ return 1;
+ }
+ if (c >= '0' && c <= '9') {
+ sb.append((char) (c - 48 + 4));
+ return 1;
+ }
+ if (c >= 'a' && c <= 'z') {
+ sb.append((char) (c - 97 + 14));
+ return 1;
+ }
+ if (c >= '\0' && c <= '\u001f') {
+ sb.append('\0'); //Shift 1 Set
+ sb.append(c);
+ return 2;
+ }
+ if (c >= '!' && c <= '/') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 33));
+ return 2;
+ }
+ if (c >= ':' && c <= '@') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 58 + 15));
+ return 2;
+ }
+ if (c >= '[' && c <= '_') {
+ sb.append('\1'); //Shift 2 Set
+ sb.append((char) (c - 91 + 22));
+ return 2;
+ }
+ if (c == '\u0060') {
+ sb.append('\2'); //Shift 3 Set
+ sb.append((char) (c - 96));
+ return 2;
+ }
+ if (c >= 'A' && c <= 'Z') {
+ sb.append('\2'); //Shift 3 Set
+ sb.append((char) (c - 65 + 1));
+ return 2;
+ }
+ if (c >= '{' && c <= '\u007f') {
+ sb.append('\2'); //Shift 3 Set
+ sb.append((char) (c - 123 + 27));
+ return 2;
+ }
+ if (c >= '\u0080') {
+ sb.append("\1\u001e"); //Shift 2, Upper Shift
+ int len = 2;
+ len += encodeChar((char) (c - 128), sb);
+ return len;
+ }
+ HighLevelEncoder.illegalCharacter(c);
+ return -1;
+ }
+
+}
diff --git a/src/com/google/zxing/datamatrix/encoder/X12Encoder.java b/src/com/google/zxing/datamatrix/encoder/X12Encoder.java
new file mode 100644
index 0000000..9d93bb3
--- /dev/null
+++ b/src/com/google/zxing/datamatrix/encoder/X12Encoder.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2006-2007 Jeremias Maerki.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.datamatrix.encoder;
+
+final class X12Encoder extends C40Encoder {
+
+ @Override
+ public int getEncodingMode() {
+ return HighLevelEncoder.X12_ENCODATION;
+ }
+
+ @Override
+ public void encode(EncoderContext context) {
+ //step C
+ StringBuilder buffer = new StringBuilder();
+ while (context.hasMoreCharacters()) {
+ char c = context.getCurrentChar();
+ context.pos++;
+
+ encodeChar(c, buffer);
+
+ int count = buffer.length();
+ if ((count % 3) == 0) {
+ writeNextTriplet(context, buffer);
+
+ int newMode = HighLevelEncoder.lookAheadTest(context.getMessage(), context.pos, getEncodingMode());
+ if (newMode != getEncodingMode()) {
+ context.signalEncoderChange(newMode);
+ break;
+ }
+ }
+ }
+ handleEOD(context, buffer);
+ }
+
+ @Override
+ int encodeChar(char c, StringBuilder sb) {
+ if (c == '\r') {
+ sb.append('\0');
+ } else if (c == '*') {
+ sb.append('\1');
+ } else if (c == '>') {
+ sb.append('\2');
+ } else if (c == ' ') {
+ sb.append('\3');
+ } else if (c >= '0' && c <= '9') {
+ sb.append((char) (c - 48 + 4));
+ } else if (c >= 'A' && c <= 'Z') {
+ sb.append((char) (c - 65 + 14));
+ } else {
+ HighLevelEncoder.illegalCharacter(c);
+ }
+ return 1;
+ }
+
+ @Override
+ void handleEOD(EncoderContext context, StringBuilder buffer) {
+ context.updateSymbolInfo();
+ int available = context.getSymbolInfo().getDataCapacity() - context.getCodewordCount();
+ int count = buffer.length();
+ context.pos -= count;
+ if (context.getRemainingCharacters() > 1 || available > 1 ||
+ context.getRemainingCharacters() != available) {
+ context.writeCodeword(HighLevelEncoder.X12_UNLATCH);
+ }
+ if (context.getNewEncoding() < 0) {
+ context.signalEncoderChange(HighLevelEncoder.ASCII_ENCODATION);
+ }
+ }
+}
diff --git a/src/com/google/zxing/maxicode/MaxiCodeReader.java b/src/com/google/zxing/maxicode/MaxiCodeReader.java
new file mode 100644
index 0000000..8492702
--- /dev/null
+++ b/src/com/google/zxing/maxicode/MaxiCodeReader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.maxicode;
+
+import com.google.zxing.*;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.maxicode.decoder.Decoder;
+
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode a MaxiCode in an image.
+ */
+public final class MaxiCodeReader implements Reader {
+
+ private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
+ private static final int MATRIX_WIDTH = 30;
+ private static final int MATRIX_HEIGHT = 33;
+
+ private final Decoder decoder = new Decoder();
+
+ /*
+ Decoder getDecoder() {
+ return decoder;
+ }
+ */
+
+ /**
+ * This method detects a code in a "pure" image -- that is, pure monochrome image
+ * which contains only an unrotated, unskewed, image of a code, with some white border
+ * around it. This is a specialized method that works exceptionally fast in this special
+ * case.
+ *
+ * @see com.google.zxing.datamatrix.DataMatrixReader#extractPureBits(BitMatrix)
+ * @see com.google.zxing.qrcode.QRCodeReader#extractPureBits(BitMatrix)
+ */
+ private static BitMatrix extractPureBits(BitMatrix image) throws NotFoundException {
+
+ int[] enclosingRectangle = image.getEnclosingRectangle();
+ if (enclosingRectangle == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int left = enclosingRectangle[0];
+ int top = enclosingRectangle[1];
+ int width = enclosingRectangle[2];
+ int height = enclosingRectangle[3];
+
+ // Now just read off the bits
+ BitMatrix bits = new BitMatrix(MATRIX_WIDTH, MATRIX_HEIGHT);
+ for (int y = 0; y < MATRIX_HEIGHT; y++) {
+ int iy = top + (y * height + height / 2) / MATRIX_HEIGHT;
+ for (int x = 0; x < MATRIX_WIDTH; x++) {
+ int ix = left + (x * width + width / 2 + (y & 0x01) * width / 2) / MATRIX_WIDTH;
+ if (image.get(ix, iy)) {
+ bits.set(x, y);
+ }
+ }
+ }
+ return bits;
+ }
+
+ /**
+ * Locates and decodes a MaxiCode in an image.
+ *
+ * @return a String representing the content encoded by the MaxiCode
+ * @throws NotFoundException if a MaxiCode cannot be found
+ * @throws FormatException if a MaxiCode cannot be decoded
+ * @throws ChecksumException if error correction fails
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+ DecoderResult decoderResult;
+ if (hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE)) {
+ BitMatrix bits = extractPureBits(image.getBlackMatrix());
+ decoderResult = decoder.decode(bits, hints);
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ ResultPoint[] points = NO_POINTS;
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.MAXICODE);
+
+ String ecLevel = decoderResult.getECLevel();
+ if (ecLevel != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+ return result;
+ }
+
+ @Override
+ public void reset() {
+ // do nothing
+ }
+
+}
diff --git a/src/com/google/zxing/maxicode/decoder/BitMatrixParser.java b/src/com/google/zxing/maxicode/decoder/BitMatrixParser.java
new file mode 100644
index 0000000..9cc2b11
--- /dev/null
+++ b/src/com/google/zxing/maxicode/decoder/BitMatrixParser.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.maxicode.decoder;
+
+import com.google.zxing.common.BitMatrix;
+
+/**
+ * @author mike32767
+ * @author Manuel Kasten
+ */
+final class BitMatrixParser {
+
+ private static final int[][] BITNR = {
+ {121, 120, 127, 126, 133, 132, 139, 138, 145, 144, 151, 150, 157, 156, 163, 162, 169, 168, 175, 174, 181, 180, 187, 186, 193, 192, 199, 198, -2, -2},
+ {123, 122, 129, 128, 135, 134, 141, 140, 147, 146, 153, 152, 159, 158, 165, 164, 171, 170, 177, 176, 183, 182, 189, 188, 195, 194, 201, 200, 816, -3},
+ {125, 124, 131, 130, 137, 136, 143, 142, 149, 148, 155, 154, 161, 160, 167, 166, 173, 172, 179, 178, 185, 184, 191, 190, 197, 196, 203, 202, 818, 817},
+ {283, 282, 277, 276, 271, 270, 265, 264, 259, 258, 253, 252, 247, 246, 241, 240, 235, 234, 229, 228, 223, 222, 217, 216, 211, 210, 205, 204, 819, -3},
+ {285, 284, 279, 278, 273, 272, 267, 266, 261, 260, 255, 254, 249, 248, 243, 242, 237, 236, 231, 230, 225, 224, 219, 218, 213, 212, 207, 206, 821, 820},
+ {287, 286, 281, 280, 275, 274, 269, 268, 263, 262, 257, 256, 251, 250, 245, 244, 239, 238, 233, 232, 227, 226, 221, 220, 215, 214, 209, 208, 822, -3},
+ {289, 288, 295, 294, 301, 300, 307, 306, 313, 312, 319, 318, 325, 324, 331, 330, 337, 336, 343, 342, 349, 348, 355, 354, 361, 360, 367, 366, 824, 823},
+ {291, 290, 297, 296, 303, 302, 309, 308, 315, 314, 321, 320, 327, 326, 333, 332, 339, 338, 345, 344, 351, 350, 357, 356, 363, 362, 369, 368, 825, -3},
+ {293, 292, 299, 298, 305, 304, 311, 310, 317, 316, 323, 322, 329, 328, 335, 334, 341, 340, 347, 346, 353, 352, 359, 358, 365, 364, 371, 370, 827, 826},
+ {409, 408, 403, 402, 397, 396, 391, 390, 79, 78, -2, -2, 13, 12, 37, 36, 2, -1, 44, 43, 109, 108, 385, 384, 379, 378, 373, 372, 828, -3},
+ {411, 410, 405, 404, 399, 398, 393, 392, 81, 80, 40, -2, 15, 14, 39, 38, 3, -1, -1, 45, 111, 110, 387, 386, 381, 380, 375, 374, 830, 829},
+ {413, 412, 407, 406, 401, 400, 395, 394, 83, 82, 41, -3, -3, -3, -3, -3, 5, 4, 47, 46, 113, 112, 389, 388, 383, 382, 377, 376, 831, -3},
+ {415, 414, 421, 420, 427, 426, 103, 102, 55, 54, 16, -3, -3, -3, -3, -3, -3, -3, 20, 19, 85, 84, 433, 432, 439, 438, 445, 444, 833, 832},
+ {417, 416, 423, 422, 429, 428, 105, 104, 57, 56, -3, -3, -3, -3, -3, -3, -3, -3, 22, 21, 87, 86, 435, 434, 441, 440, 447, 446, 834, -3},
+ {419, 418, 425, 424, 431, 430, 107, 106, 59, 58, -3, -3, -3, -3, -3, -3, -3, -3, -3, 23, 89, 88, 437, 436, 443, 442, 449, 448, 836, 835},
+ {481, 480, 475, 474, 469, 468, 48, -2, 30, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, 0, 53, 52, 463, 462, 457, 456, 451, 450, 837, -3},
+ {483, 482, 477, 476, 471, 470, 49, -1, -2, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, -2, -1, 465, 464, 459, 458, 453, 452, 839, 838},
+ {485, 484, 479, 478, 473, 472, 51, 50, 31, -3, -3, -3, -3, -3, -3, -3, -3, -3, -3, 1, -2, 42, 467, 466, 461, 460, 455, 454, 840, -3},
+ {487, 486, 493, 492, 499, 498, 97, 96, 61, 60, -3, -3, -3, -3, -3, -3, -3, -3, -3, 26, 91, 90, 505, 504, 511, 510, 517, 516, 842, 841},
+ {489, 488, 495, 494, 501, 500, 99, 98, 63, 62, -3, -3, -3, -3, -3, -3, -3, -3, 28, 27, 93, 92, 507, 506, 513, 512, 519, 518, 843, -3},
+ {491, 490, 497, 496, 503, 502, 101, 100, 65, 64, 17, -3, -3, -3, -3, -3, -3, -3, 18, 29, 95, 94, 509, 508, 515, 514, 521, 520, 845, 844},
+ {559, 558, 553, 552, 547, 546, 541, 540, 73, 72, 32, -3, -3, -3, -3, -3, -3, 10, 67, 66, 115, 114, 535, 534, 529, 528, 523, 522, 846, -3},
+ {561, 560, 555, 554, 549, 548, 543, 542, 75, 74, -2, -1, 7, 6, 35, 34, 11, -2, 69, 68, 117, 116, 537, 536, 531, 530, 525, 524, 848, 847},
+ {563, 562, 557, 556, 551, 550, 545, 544, 77, 76, -2, 33, 9, 8, 25, 24, -1, -2, 71, 70, 119, 118, 539, 538, 533, 532, 527, 526, 849, -3},
+ {565, 564, 571, 570, 577, 576, 583, 582, 589, 588, 595, 594, 601, 600, 607, 606, 613, 612, 619, 618, 625, 624, 631, 630, 637, 636, 643, 642, 851, 850},
+ {567, 566, 573, 572, 579, 578, 585, 584, 591, 590, 597, 596, 603, 602, 609, 608, 615, 614, 621, 620, 627, 626, 633, 632, 639, 638, 645, 644, 852, -3},
+ {569, 568, 575, 574, 581, 580, 587, 586, 593, 592, 599, 598, 605, 604, 611, 610, 617, 616, 623, 622, 629, 628, 635, 634, 641, 640, 647, 646, 854, 853},
+ {727, 726, 721, 720, 715, 714, 709, 708, 703, 702, 697, 696, 691, 690, 685, 684, 679, 678, 673, 672, 667, 666, 661, 660, 655, 654, 649, 648, 855, -3},
+ {729, 728, 723, 722, 717, 716, 711, 710, 705, 704, 699, 698, 693, 692, 687, 686, 681, 680, 675, 674, 669, 668, 663, 662, 657, 656, 651, 650, 857, 856},
+ {731, 730, 725, 724, 719, 718, 713, 712, 707, 706, 701, 700, 695, 694, 689, 688, 683, 682, 677, 676, 671, 670, 665, 664, 659, 658, 653, 652, 858, -3},
+ {733, 732, 739, 738, 745, 744, 751, 750, 757, 756, 763, 762, 769, 768, 775, 774, 781, 780, 787, 786, 793, 792, 799, 798, 805, 804, 811, 810, 860, 859},
+ {735, 734, 741, 740, 747, 746, 753, 752, 759, 758, 765, 764, 771, 770, 777, 776, 783, 782, 789, 788, 795, 794, 801, 800, 807, 806, 813, 812, 861, -3},
+ {737, 736, 743, 742, 749, 748, 755, 754, 761, 760, 767, 766, 773, 772, 779, 778, 785, 784, 791, 790, 797, 796, 803, 802, 809, 808, 815, 814, 863, 862}
+ };
+
+ private final BitMatrix bitMatrix;
+
+ /**
+ * @param bitMatrix {@link BitMatrix} to parse
+ */
+ BitMatrixParser(BitMatrix bitMatrix) {
+ this.bitMatrix = bitMatrix;
+ }
+
+ byte[] readCodewords() {
+ byte[] result = new byte[144];
+ int height = bitMatrix.getHeight();
+ int width = bitMatrix.getWidth();
+ for (int y = 0; y < height; y++) {
+ int[] bitnrRow = BITNR[y];
+ for (int x = 0; x < width; x++) {
+ int bit = bitnrRow[x];
+ if (bit >= 0 && bitMatrix.get(x, y)) {
+ result[bit / 6] |= (byte) (1 << (5 - (bit % 6)));
+ }
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java b/src/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java
new file mode 100644
index 0000000..8add73f
--- /dev/null
+++ b/src/com/google/zxing/maxicode/decoder/DecodedBitStreamParser.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.maxicode.decoder;
+
+import com.google.zxing.common.DecoderResult;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+/**
+ * MaxiCodes can encode text or structured information as bits in one of several modes,
+ * with multiple character sets in one code. This class decodes the bits back into text.
+ *
+ * @author mike32767
+ * @author Manuel Kasten
+ */
+final class DecodedBitStreamParser {
+
+ private static final char SHIFTA = '\uFFF0';
+ private static final char SHIFTB = '\uFFF1';
+ private static final char SHIFTC = '\uFFF2';
+ private static final char SHIFTD = '\uFFF3';
+ private static final char SHIFTE = '\uFFF4';
+ private static final char TWOSHIFTA = '\uFFF5';
+ private static final char THREESHIFTA = '\uFFF6';
+ private static final char LATCHA = '\uFFF7';
+ private static final char LATCHB = '\uFFF8';
+ private static final char LOCK = '\uFFF9';
+ private static final char ECI = '\uFFFA';
+ private static final char NS = '\uFFFB';
+ private static final char PAD = '\uFFFC';
+ private static final char FS = '\u001C';
+ private static final char GS = '\u001D';
+ private static final char RS = '\u001E';
+ private static final NumberFormat NINE_DIGITS = new DecimalFormat("000000000");
+ private static final NumberFormat THREE_DIGITS = new DecimalFormat("000");
+
+ private static final String[] SETS = {
+ "\nABCDEFGHIJKLMNOPQRSTUVWXYZ" + ECI + FS + GS + RS + NS + ' ' + PAD + "\"#$%&'()*+,-./0123456789:" + SHIFTB + SHIFTC + SHIFTD + SHIFTE + LATCHB,
+ "`abcdefghijklmnopqrstuvwxyz" + ECI + FS + GS + RS + NS + '{' + PAD + "}~\u007F;<=>?[\\]^_ ,./:@!|" + PAD + TWOSHIFTA + THREESHIFTA + PAD + SHIFTA + SHIFTC + SHIFTD + SHIFTE + LATCHA,
+ "\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA" + ECI + FS + GS + RS + "\u00DB\u00DC\u00DD\u00DE\u00DF\u00AA\u00AC\u00B1\u00B2\u00B3\u00B5\u00B9\u00BA\u00BC\u00BD\u00BE\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089" + LATCHA + ' ' + LOCK + SHIFTD + SHIFTE + LATCHB,
+ "\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA" + ECI + FS + GS + RS + NS + "\u00FB\u00FC\u00FD\u00FE\u00FF\u00A1\u00A8\u00AB\u00AF\u00B0\u00B4\u00B7\u00B8\u00BB\u00BF\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094" + LATCHA + ' ' + SHIFTC + LOCK + SHIFTE + LATCHB,
+ "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A" + ECI + PAD + PAD + '\u001B' + NS + FS + GS + RS + "\u001F\u009F\u00A0\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A9\u00AD\u00AE\u00B6\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E" + LATCHA + ' ' + SHIFTC + SHIFTD + LOCK + LATCHB,
+ "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\n\u000B\u000C\r\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u0020\u0021\"\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F"
+ };
+
+ private DecodedBitStreamParser() {
+ }
+
+ static DecoderResult decode(byte[] bytes, int mode) {
+ StringBuilder result = new StringBuilder(144);
+ switch (mode) {
+ case 2:
+ case 3:
+ String postcode;
+ if (mode == 2) {
+ int pc = getPostCode2(bytes);
+ NumberFormat df = new DecimalFormat("0000000000".substring(0, getPostCode2Length(bytes)));
+ postcode = df.format(pc);
+ } else {
+ postcode = getPostCode3(bytes);
+ }
+ String country = THREE_DIGITS.format(getCountry(bytes));
+ String service = THREE_DIGITS.format(getServiceClass(bytes));
+ result.append(getMessage(bytes, 10, 84));
+ if (result.toString().startsWith("[)>" + RS + "01" + GS)) {
+ result.insert(9, postcode + GS + country + GS + service + GS);
+ } else {
+ result.insert(0, postcode + GS + country + GS + service + GS);
+ }
+ break;
+ case 4:
+ result.append(getMessage(bytes, 1, 93));
+ break;
+ case 5:
+ result.append(getMessage(bytes, 1, 77));
+ break;
+ }
+ return new DecoderResult(bytes, result.toString(), null, String.valueOf(mode));
+ }
+
+ private static int getBit(int bit, byte[] bytes) {
+ bit--;
+ return (bytes[bit / 6] & (1 << (5 - (bit % 6)))) == 0 ? 0 : 1;
+ }
+
+ private static int getInt(byte[] bytes, byte[] x) {
+ if (x.length == 0) {
+ throw new IllegalArgumentException();
+ }
+ int val = 0;
+ for (int i = 0; i < x.length; i++) {
+ val += getBit(x[i], bytes) << (x.length - i - 1);
+ }
+ return val;
+ }
+
+ private static int getCountry(byte[] bytes) {
+ return getInt(bytes, new byte[]{53, 54, 43, 44, 45, 46, 47, 48, 37, 38});
+ }
+
+ private static int getServiceClass(byte[] bytes) {
+ return getInt(bytes, new byte[]{55, 56, 57, 58, 59, 60, 49, 50, 51, 52});
+ }
+
+ private static int getPostCode2Length(byte[] bytes) {
+ return getInt(bytes, new byte[]{39, 40, 41, 42, 31, 32});
+ }
+
+ private static int getPostCode2(byte[] bytes) {
+ return getInt(bytes, new byte[]{33, 34, 35, 36, 25, 26, 27, 28, 29, 30, 19,
+ 20, 21, 22, 23, 24, 13, 14, 15, 16, 17, 18, 7, 8, 9, 10, 11, 12, 1, 2});
+ }
+
+ private static String getPostCode3(byte[] bytes) {
+ return String.valueOf(
+ new char[]{
+ SETS[0].charAt(getInt(bytes, new byte[]{39, 40, 41, 42, 31, 32})),
+ SETS[0].charAt(getInt(bytes, new byte[]{33, 34, 35, 36, 25, 26})),
+ SETS[0].charAt(getInt(bytes, new byte[]{27, 28, 29, 30, 19, 20})),
+ SETS[0].charAt(getInt(bytes, new byte[]{21, 22, 23, 24, 13, 14})),
+ SETS[0].charAt(getInt(bytes, new byte[]{15, 16, 17, 18, 7, 8})),
+ SETS[0].charAt(getInt(bytes, new byte[]{9, 10, 11, 12, 1, 2})),
+ }
+ );
+ }
+
+ private static String getMessage(byte[] bytes, int start, int len) {
+ StringBuilder sb = new StringBuilder();
+ int shift = -1;
+ int set = 0;
+ int lastset = 0;
+ for (int i = start; i < start + len; i++) {
+ char c = SETS[set].charAt(bytes[i]);
+ switch (c) {
+ case LATCHA:
+ set = 0;
+ shift = -1;
+ break;
+ case LATCHB:
+ set = 1;
+ shift = -1;
+ break;
+ case SHIFTA:
+ case SHIFTB:
+ case SHIFTC:
+ case SHIFTD:
+ case SHIFTE:
+ lastset = set;
+ set = c - SHIFTA;
+ shift = 1;
+ break;
+ case TWOSHIFTA:
+ lastset = set;
+ set = 0;
+ shift = 2;
+ break;
+ case THREESHIFTA:
+ lastset = set;
+ set = 0;
+ shift = 3;
+ break;
+ case NS:
+ int nsval = (bytes[++i] << 24) + (bytes[++i] << 18) + (bytes[++i] << 12) + (bytes[++i] << 6) + bytes[++i];
+ sb.append(NINE_DIGITS.format(nsval));
+ break;
+ case LOCK:
+ shift = -1;
+ break;
+ default:
+ sb.append(c);
+ }
+ if (shift-- == 0) {
+ set = lastset;
+ }
+ }
+ while (sb.length() > 0 && sb.charAt(sb.length() - 1) == PAD) {
+ sb.setLength(sb.length() - 1);
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/src/com/google/zxing/maxicode/decoder/Decoder.java b/src/com/google/zxing/maxicode/decoder/Decoder.java
new file mode 100644
index 0000000..0f2f078
--- /dev/null
+++ b/src/com/google/zxing/maxicode/decoder/Decoder.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.maxicode.decoder;
+
+import com.google.zxing.ChecksumException;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.reedsolomon.GenericGF;
+import com.google.zxing.common.reedsolomon.ReedSolomonDecoder;
+import com.google.zxing.common.reedsolomon.ReedSolomonException;
+
+import java.util.Map;
+
+/**
+ * The main class which implements MaxiCode decoding -- as opposed to locating and extracting
+ * the MaxiCode from an image.
+ *
+ * @author Manuel Kasten
+ */
+public final class Decoder {
+
+ private static final int ALL = 0;
+ private static final int EVEN = 1;
+ private static final int ODD = 2;
+
+ private final ReedSolomonDecoder rsDecoder;
+
+ public Decoder() {
+ rsDecoder = new ReedSolomonDecoder(GenericGF.MAXICODE_FIELD_64);
+ }
+
+ public DecoderResult decode(BitMatrix bits) throws ChecksumException, FormatException {
+ return decode(bits, null);
+ }
+
+ public DecoderResult decode(BitMatrix bits,
+ Map hints) throws FormatException, ChecksumException {
+ BitMatrixParser parser = new BitMatrixParser(bits);
+ byte[] codewords = parser.readCodewords();
+
+ correctErrors(codewords, 0, 10, 10, ALL);
+ int mode = codewords[0] & 0x0F;
+ byte[] datawords;
+ switch (mode) {
+ case 2:
+ case 3:
+ case 4:
+ correctErrors(codewords, 20, 84, 40, EVEN);
+ correctErrors(codewords, 20, 84, 40, ODD);
+ datawords = new byte[94];
+ break;
+ case 5:
+ correctErrors(codewords, 20, 68, 56, EVEN);
+ correctErrors(codewords, 20, 68, 56, ODD);
+ datawords = new byte[78];
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+
+ System.arraycopy(codewords, 0, datawords, 0, 10);
+ System.arraycopy(codewords, 20, datawords, 10, datawords.length - 10);
+
+ return DecodedBitStreamParser.decode(datawords, mode);
+ }
+
+ private void correctErrors(byte[] codewordBytes,
+ int start,
+ int dataCodewords,
+ int ecCodewords,
+ int mode) throws ChecksumException {
+ int codewords = dataCodewords + ecCodewords;
+
+ // in EVEN or ODD mode only half the codewords
+ int divisor = mode == ALL ? 1 : 2;
+
+ // First read into an array of ints
+ int[] codewordsInts = new int[codewords / divisor];
+ for (int i = 0; i < codewords; i++) {
+ if ((mode == ALL) || (i % 2 == (mode - 1))) {
+ codewordsInts[i / divisor] = codewordBytes[i + start] & 0xFF;
+ }
+ }
+ try {
+ rsDecoder.decode(codewordsInts, ecCodewords / divisor);
+ } catch (ReedSolomonException ignored) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ // Copy back into array of bytes -- only need to worry about the bytes that were data
+ // We don't care about errors in the error-correction codewords
+ for (int i = 0; i < dataCodewords; i++) {
+ if ((mode == ALL) || (i % 2 == (mode - 1))) {
+ codewordBytes[i + start] = (byte) codewordsInts[i / divisor];
+ }
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/multi/ByQuadrantReader.java b/src/com/google/zxing/multi/ByQuadrantReader.java
new file mode 100644
index 0000000..df7a0cc
--- /dev/null
+++ b/src/com/google/zxing/multi/ByQuadrantReader.java
@@ -0,0 +1,108 @@
+/*
+ * 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.*;
+
+import java.util.Map;
+
+/**
+ * This class attempts to decode a barcode from an image, not by scanning the whole image,
+ * but by scanning subsets of the image. This is important when there may be multiple barcodes in
+ * an image, and detecting a barcode may find parts of multiple barcode and fail to decode
+ * (e.g. QR Codes). Instead this scans the four quadrants of the image -- and also the center
+ * 'quadrant' to cover the case where a barcode is found in the center.
+ *
+ * @see GenericMultipleBarcodeReader
+ */
+public final class ByQuadrantReader implements Reader {
+
+ private final Reader delegate;
+
+ public ByQuadrantReader(Reader delegate) {
+ this.delegate = delegate;
+ }
+
+ private static void makeAbsolute(ResultPoint[] points, int leftOffset, int topOffset) {
+ if (points != null) {
+ for (int i = 0; i < points.length; i++) {
+ ResultPoint relative = points[i];
+ points[i] = new ResultPoint(relative.getX() + leftOffset, relative.getY() + topOffset);
+ }
+ }
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image)
+ throws NotFoundException, ChecksumException, FormatException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+
+ int width = image.getWidth();
+ int height = image.getHeight();
+ int halfWidth = width / 2;
+ int halfHeight = height / 2;
+
+ try {
+ // No need to call makeAbsolute as results will be relative to original top left here
+ return delegate.decode(image.crop(0, 0, halfWidth, halfHeight), hints);
+ } catch (NotFoundException re) {
+ // continue
+ }
+
+ try {
+ Result result = delegate.decode(image.crop(halfWidth, 0, halfWidth, halfHeight), hints);
+ makeAbsolute(result.getResultPoints(), halfWidth, 0);
+ return result;
+ } catch (NotFoundException re) {
+ // continue
+ }
+
+ try {
+ Result result = delegate.decode(image.crop(0, halfHeight, halfWidth, halfHeight), hints);
+ makeAbsolute(result.getResultPoints(), 0, halfHeight);
+ return result;
+ } catch (NotFoundException re) {
+ // continue
+ }
+
+ try {
+ Result result = delegate.decode(image.crop(halfWidth, halfHeight, halfWidth, halfHeight), hints);
+ makeAbsolute(result.getResultPoints(), halfWidth, halfHeight);
+ return result;
+ } catch (NotFoundException re) {
+ // continue
+ }
+
+ int quarterWidth = halfWidth / 2;
+ int quarterHeight = halfHeight / 2;
+ BinaryBitmap center = image.crop(quarterWidth, quarterHeight, halfWidth, halfHeight);
+ Result result = delegate.decode(center, hints);
+ makeAbsolute(result.getResultPoints(), quarterWidth, quarterHeight);
+ return result;
+ }
+
+ @Override
+ 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..c14db85
--- /dev/null
+++ b/src/com/google/zxing/multi/GenericMultipleBarcodeReader.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.multi;
+
+import com.google.zxing.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Attempts to locate multiple barcodes in an image by repeatedly decoding portion of the image.
+ * After one barcode is found, the areas left, above, right and below the barcode's
+ * {@link ResultPoint}s are scanned, recursively.
+ *
+ *
A caller may want to also employ {@link ByQuadrantReader} when attempting to find multiple
+ * 2D barcodes, like QR Codes, in an image, where the presence of multiple barcodes might prevent
+ * detecting any one of them.
+ *
+ *
That is, instead of passing a {@link Reader} a caller might pass
+ * {@code new ByQuadrantReader(reader)}.
+ *
+ * @author Sean Owen
+ */
+public final class GenericMultipleBarcodeReader implements MultipleBarcodeReader {
+
+ private static final int MIN_DIMENSION_TO_RECUR = 100;
+ private static final int MAX_DEPTH = 4;
+
+ private final Reader delegate;
+
+ public GenericMultipleBarcodeReader(Reader delegate) {
+ this.delegate = delegate;
+ }
+
+ private static Result translateResultPoints(Result result, int xOffset, int yOffset) {
+ ResultPoint[] oldResultPoints = result.getResultPoints();
+ if (oldResultPoints == null) {
+ return result;
+ }
+ ResultPoint[] newResultPoints = new ResultPoint[oldResultPoints.length];
+ for (int i = 0; i < oldResultPoints.length; i++) {
+ ResultPoint oldPoint = oldResultPoints[i];
+ if (oldPoint != null) {
+ newResultPoints[i] = new ResultPoint(oldPoint.getX() + xOffset, oldPoint.getY() + yOffset);
+ }
+ }
+ Result newResult = new Result(result.getText(), result.getRawBytes(), newResultPoints, result.getBarcodeFormat());
+ newResult.putAllMetadata(result.getResultMetadata());
+ return newResult;
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
+ return decodeMultiple(image, null);
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image, Map hints)
+ throws NotFoundException {
+ List results = new ArrayList<>();
+ doDecodeMultiple(image, hints, results, 0, 0, 0);
+ if (results.isEmpty()) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ return results.toArray(new Result[results.size()]);
+ }
+
+ private void doDecodeMultiple(BinaryBitmap image,
+ Map hints,
+ List results,
+ int xOffset,
+ int yOffset,
+ int currentDepth) {
+ if (currentDepth > MAX_DEPTH) {
+ return;
+ }
+
+ Result result;
+ try {
+ result = delegate.decode(image, hints);
+ } catch (ReaderException ignored) {
+ return;
+ }
+ boolean alreadyFound = false;
+ for (Result existingResult : results) {
+ if (existingResult.getText().equals(result.getText())) {
+ alreadyFound = true;
+ break;
+ }
+ }
+ if (!alreadyFound) {
+ results.add(translateResultPoints(result, xOffset, yOffset));
+ }
+ ResultPoint[] resultPoints = result.getResultPoints();
+ if (resultPoints == null || resultPoints.length == 0) {
+ return;
+ }
+ int width = image.getWidth();
+ int height = image.getHeight();
+ float minX = width;
+ float minY = height;
+ float maxX = 0.0f;
+ float maxY = 0.0f;
+ for (ResultPoint point : resultPoints) {
+ if (point == null) {
+ continue;
+ }
+ float x = point.getX();
+ float y = point.getY();
+ if (x < minX) {
+ minX = x;
+ }
+ if (y < minY) {
+ minY = y;
+ }
+ if (x > maxX) {
+ maxX = x;
+ }
+ if (y > maxY) {
+ maxY = y;
+ }
+ }
+
+ // Decode left of barcode
+ if (minX > MIN_DIMENSION_TO_RECUR) {
+ doDecodeMultiple(image.crop(0, 0, (int) minX, height),
+ hints, results,
+ xOffset, yOffset,
+ currentDepth + 1);
+ }
+ // Decode above barcode
+ if (minY > MIN_DIMENSION_TO_RECUR) {
+ doDecodeMultiple(image.crop(0, 0, width, (int) minY),
+ hints, results,
+ xOffset, yOffset,
+ currentDepth + 1);
+ }
+ // Decode right of barcode
+ if (maxX < width - MIN_DIMENSION_TO_RECUR) {
+ doDecodeMultiple(image.crop((int) maxX, 0, width - (int) maxX, height),
+ hints, results,
+ xOffset + (int) maxX, yOffset,
+ currentDepth + 1);
+ }
+ // Decode below barcode
+ if (maxY < height - MIN_DIMENSION_TO_RECUR) {
+ doDecodeMultiple(image.crop(0, (int) maxY, width, height - (int) maxY),
+ hints, results,
+ xOffset, yOffset + (int) maxY,
+ currentDepth + 1);
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/multi/MultipleBarcodeReader.java b/src/com/google/zxing/multi/MultipleBarcodeReader.java
new file mode 100644
index 0000000..5b100a9
--- /dev/null
+++ b/src/com/google/zxing/multi/MultipleBarcodeReader.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.multi;
+
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.Result;
+
+import java.util.Map;
+
+/**
+ * Implementation of this interface attempt to read several barcodes from one image.
+ *
+ * @author Sean Owen
+ * @see com.google.zxing.Reader
+ */
+public interface MultipleBarcodeReader {
+
+ Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException;
+
+ Result[] decodeMultiple(BinaryBitmap image,
+ Map 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..0515f86
--- /dev/null
+++ b/src/com/google/zxing/multi/qrcode/QRCodeMultiReader.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.multi.qrcode;
+
+import com.google.zxing.*;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.multi.MultipleBarcodeReader;
+import com.google.zxing.multi.qrcode.detector.MultiDetector;
+import com.google.zxing.qrcode.QRCodeReader;
+import com.google.zxing.qrcode.decoder.QRCodeDecoderMetaData;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * This implementation can detect and decode multiple QR Codes in an image.
+ *
+ * @author Sean Owen
+ * @author Hannes Erven
+ */
+public final class QRCodeMultiReader extends QRCodeReader implements MultipleBarcodeReader {
+
+ private static final Result[] EMPTY_RESULT_ARRAY = new Result[0];
+ private static final ResultPoint[] NO_POINTS = new ResultPoint[0];
+
+ private static List processStructuredAppend(List results) {
+ boolean hasSA = false;
+
+ // first, check, if there is at least on SA result in the list
+ for (Result result : results) {
+ if (result.getResultMetadata().containsKey(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) {
+ hasSA = true;
+ break;
+ }
+ }
+ if (!hasSA) {
+ return results;
+ }
+
+ // it is, second, split the lists and built a new result list
+ List newResults = new ArrayList<>();
+ List saResults = new ArrayList<>();
+ for (Result result : results) {
+ newResults.add(result);
+ if (result.getResultMetadata().containsKey(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE)) {
+ saResults.add(result);
+ }
+ }
+ // sort and concatenate the SA list items
+ Collections.sort(saResults, new SAComparator());
+ StringBuilder concatedText = new StringBuilder();
+ int rawBytesLen = 0;
+ int byteSegmentLength = 0;
+ for (Result saResult : saResults) {
+ concatedText.append(saResult.getText());
+ rawBytesLen += saResult.getRawBytes().length;
+ if (saResult.getResultMetadata().containsKey(ResultMetadataType.BYTE_SEGMENTS)) {
+ @SuppressWarnings("unchecked")
+ Iterable byteSegments =
+ (Iterable) saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS);
+ for (byte[] segment : byteSegments) {
+ byteSegmentLength += segment.length;
+ }
+ }
+ }
+ byte[] newRawBytes = new byte[rawBytesLen];
+ byte[] newByteSegment = new byte[byteSegmentLength];
+ int newRawBytesIndex = 0;
+ int byteSegmentIndex = 0;
+ for (Result saResult : saResults) {
+ System.arraycopy(saResult.getRawBytes(), 0, newRawBytes, newRawBytesIndex, saResult.getRawBytes().length);
+ newRawBytesIndex += saResult.getRawBytes().length;
+ if (saResult.getResultMetadata().containsKey(ResultMetadataType.BYTE_SEGMENTS)) {
+ @SuppressWarnings("unchecked")
+ Iterable byteSegments =
+ (Iterable) saResult.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS);
+ for (byte[] segment : byteSegments) {
+ System.arraycopy(segment, 0, newByteSegment, byteSegmentIndex, segment.length);
+ byteSegmentIndex += segment.length;
+ }
+ }
+ }
+ Result newResult = new Result(concatedText.toString(), newRawBytes, NO_POINTS, BarcodeFormat.QR_CODE);
+ if (byteSegmentLength > 0) {
+ Collection byteSegmentList = new ArrayList<>();
+ byteSegmentList.add(newByteSegment);
+ newResult.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegmentList);
+ }
+ newResults.add(newResult);
+ return newResults;
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
+ return decodeMultiple(image, null);
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image, Map hints) throws NotFoundException {
+ List results = new ArrayList<>();
+ DetectorResult[] detectorResults = new MultiDetector(image.getBlackMatrix()).detectMulti(hints);
+ for (DetectorResult detectorResult : detectorResults) {
+ try {
+ DecoderResult decoderResult = getDecoder().decode(detectorResult.getBits(), hints);
+ ResultPoint[] points = detectorResult.getPoints();
+ // If the code was mirrored: swap the bottom-left and the top-right points.
+ if (decoderResult.getOther() instanceof QRCodeDecoderMetaData) {
+ ((QRCodeDecoderMetaData) decoderResult.getOther()).applyMirroredCorrection(points);
+ }
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points,
+ BarcodeFormat.QR_CODE);
+ List byteSegments = decoderResult.getByteSegments();
+ if (byteSegments != null) {
+ result.putMetadata(ResultMetadataType.BYTE_SEGMENTS, byteSegments);
+ }
+ String ecLevel = decoderResult.getECLevel();
+ if (ecLevel != null) {
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, ecLevel);
+ }
+ if (decoderResult.hasStructuredAppend()) {
+ result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE,
+ decoderResult.getStructuredAppendSequenceNumber());
+ result.putMetadata(ResultMetadataType.STRUCTURED_APPEND_PARITY,
+ decoderResult.getStructuredAppendParity());
+ }
+ results.add(result);
+ } catch (ReaderException re) {
+ // ignore and continue
+ }
+ }
+ if (results.isEmpty()) {
+ return EMPTY_RESULT_ARRAY;
+ } else {
+ results = processStructuredAppend(results);
+ return results.toArray(new Result[results.size()]);
+ }
+ }
+
+ private static final class SAComparator implements Comparator, Serializable {
+ @Override
+ public int compare(Result a, Result b) {
+ int aNumber = (int) (a.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE));
+ int bNumber = (int) (b.getResultMetadata().get(ResultMetadataType.STRUCTURED_APPEND_SEQUENCE));
+ if (aNumber < bNumber) {
+ return -1;
+ }
+ if (aNumber > bNumber) {
+ return 1;
+ }
+ return 0;
+ }
+ }
+
+}
diff --git a/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..7a1669a
--- /dev/null
+++ b/src/com/google/zxing/multi/qrcode/detector/MultiDetector.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.multi.qrcode.detector;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ReaderException;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.common.DetectorResult;
+import com.google.zxing.qrcode.detector.Detector;
+import com.google.zxing.qrcode.detector.FinderPatternInfo;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Encapsulates logic that can detect one or more QR Codes in an image, even if the QR Code
+ * is rotated or skewed, or partially obscured.
+ *
+ * @author Sean Owen
+ * @author Hannes Erven
+ */
+public final class MultiDetector extends Detector {
+
+ private static final DetectorResult[] EMPTY_DETECTOR_RESULTS = new DetectorResult[0];
+
+ public MultiDetector(BitMatrix image) {
+ super(image);
+ }
+
+ public DetectorResult[] detectMulti(Map hints) throws NotFoundException {
+ BitMatrix image = getImage();
+ ResultPointCallback resultPointCallback =
+ hints == null ? null : (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+ MultiFinderPatternFinder finder = new MultiFinderPatternFinder(image, resultPointCallback);
+ FinderPatternInfo[] infos = finder.findMulti(hints);
+
+ if (infos.length == 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ List result = new ArrayList<>();
+ for (FinderPatternInfo info : infos) {
+ try {
+ result.add(processFinderPatternInfo(info));
+ } catch (ReaderException e) {
+ // ignore
+ }
+ }
+ if (result.isEmpty()) {
+ return EMPTY_DETECTOR_RESULTS;
+ } else {
+ return result.toArray(new DetectorResult[result.size()]);
+ }
+ }
+
+}
diff --git a/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..0fe8821
--- /dev/null
+++ b/src/com/google/zxing/multi/qrcode/detector/MultiFinderPatternFinder.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.multi.qrcode.detector;
+
+import com.google.zxing.DecodeHintType;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ResultPoint;
+import com.google.zxing.ResultPointCallback;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.detector.FinderPattern;
+import com.google.zxing.qrcode.detector.FinderPatternFinder;
+import com.google.zxing.qrcode.detector.FinderPatternInfo;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * 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;
+
+
+ /**
+ * Creates a finder that will search the image for three finder patterns.
+ *
+ * @param image image to search
+ */
+ MultiFinderPatternFinder(BitMatrix image) {
+ super(image);
+ }
+
+ MultiFinderPatternFinder(BitMatrix image, ResultPointCallback resultPointCallback) {
+ super(image, resultPointCallback);
+ }
+
+ /**
+ * @return the 3 best {@link FinderPattern}s from our list of candidates. The "best" are
+ * those that have been detected at least {@link #CENTER_QUORUM} times, and whose module
+ * size differs from the average among those patterns the least
+ * @throws NotFoundException if 3 such finder patterns do not exist
+ */
+ private FinderPattern[][] selectMutipleBestPatterns() throws NotFoundException {
+ List possibleCenters = getPossibleCenters();
+ int size = possibleCenters.size();
+
+ if (size < 3) {
+ // Couldn't find enough finder patterns
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /*
+ * Begin HE modifications to safely detect multiple codes of equal size
+ */
+ if (size == 3) {
+ return new FinderPattern[][]{
+ new FinderPattern[]{
+ possibleCenters.get(0),
+ possibleCenters.get(1),
+ possibleCenters.get(2)
+ }
+ };
+ }
+
+ // Sort by estimated module size to speed up the upcoming checks
+ Collections.sort(possibleCenters, new ModuleSizeComparator());
+
+ /*
+ * Now lets start: build a list of tuples of three finder locations that
+ * - feature similar module sizes
+ * - are placed in a distance so the estimated module count is within the QR specification
+ * - have similar distance between upper left/right and left top/bottom finder patterns
+ * - form a triangle with 90° angle (checked by comparing top right/bottom left distance
+ * with pythagoras)
+ *
+ * Note: we allow each point to be used for more than one code region: this might seem
+ * counterintuitive at first, but the performance penalty is not that big. At this point,
+ * we cannot make a good quality decision whether the three finders actually represent
+ * a QR code, or are just by chance layouted so it looks like there might be a QR code there.
+ * So, if the layout seems right, lets have the decoder try to decode.
+ */
+
+ List results = new ArrayList<>(); // holder for the results
+
+ for (int i1 = 0; i1 < (size - 2); i1++) {
+ FinderPattern p1 = possibleCenters.get(i1);
+ if (p1 == null) {
+ continue;
+ }
+
+ for (int i2 = i1 + 1; i2 < (size - 1); i2++) {
+ FinderPattern p2 = possibleCenters.get(i2);
+ if (p2 == null) {
+ continue;
+ }
+
+ // Compare the expected module sizes; if they are really off, skip
+ float vModSize12 = (p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize()) /
+ Math.min(p1.getEstimatedModuleSize(), p2.getEstimatedModuleSize());
+ float vModSize12A = Math.abs(p1.getEstimatedModuleSize() - p2.getEstimatedModuleSize());
+ if (vModSize12A > DIFF_MODSIZE_CUTOFF && vModSize12 >= DIFF_MODSIZE_CUTOFF_PERCENT) {
+ // break, since elements are ordered by the module size deviation there cannot be
+ // any more interesting elements for the given p1.
+ break;
+ }
+
+ for (int i3 = i2 + 1; i3 < size; i3++) {
+ FinderPattern p3 = possibleCenters.get(i3);
+ if (p3 == null) {
+ continue;
+ }
+
+ // Compare the expected module sizes; if they are really off, skip
+ float vModSize23 = (p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize()) /
+ Math.min(p2.getEstimatedModuleSize(), p3.getEstimatedModuleSize());
+ float vModSize23A = Math.abs(p2.getEstimatedModuleSize() - p3.getEstimatedModuleSize());
+ if (vModSize23A > DIFF_MODSIZE_CUTOFF && vModSize23 >= DIFF_MODSIZE_CUTOFF_PERCENT) {
+ // break, since elements are ordered by the module size deviation there cannot be
+ // any more interesting elements for the given p1.
+ break;
+ }
+
+ FinderPattern[] test = {p1, p2, p3};
+ ResultPoint.orderBestPatterns(test);
+
+ // Calculate the distances: a = topleft-bottomleft, b=topleft-topright, c = diagonal
+ FinderPatternInfo info = new FinderPatternInfo(test);
+ float dA = ResultPoint.distance(info.getTopLeft(), info.getBottomLeft());
+ float dC = ResultPoint.distance(info.getTopRight(), info.getBottomLeft());
+ float dB = ResultPoint.distance(info.getTopLeft(), info.getTopRight());
+
+ // Check the sizes
+ float estimatedModuleCount = (dA + dB) / (p1.getEstimatedModuleSize() * 2.0f);
+ if (estimatedModuleCount > MAX_MODULE_COUNT_PER_EDGE ||
+ estimatedModuleCount < MIN_MODULE_COUNT_PER_EDGE) {
+ continue;
+ }
+
+ // Calculate the difference of the edge lengths in percent
+ float vABBC = Math.abs((dA - dB) / Math.min(dA, dB));
+ if (vABBC >= 0.1f) {
+ continue;
+ }
+
+ // Calculate the diagonal length by assuming a 90° angle at topleft
+ float dCpy = (float) Math.sqrt(dA * dA + dB * dB);
+ // Compare to the real distance in %
+ float vPyC = Math.abs((dC - dCpy) / Math.min(dC, dCpy));
+
+ if (vPyC >= 0.1f) {
+ continue;
+ }
+
+ // All tests passed!
+ results.add(test);
+ } // end iterate p3
+ } // end iterate p2
+ } // end iterate p1
+
+ if (!results.isEmpty()) {
+ return results.toArray(new FinderPattern[results.size()][]);
+ }
+
+ // Nothing found!
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ public FinderPatternInfo[] findMulti(Map hints) throws NotFoundException {
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ boolean pureBarcode = hints != null && hints.containsKey(DecodeHintType.PURE_BARCODE);
+ BitMatrix image = getImage();
+ int maxI = image.getHeight();
+ int maxJ = image.getWidth();
+ // We are looking for black/white/black/white/black modules in
+ // 1:1:3:1:1 ratio; this tracks the number of such modules seen so far
+
+ // Let's assume that the maximum version QR Code we support takes up 1/4 the height of the
+ // image, and then account for the center being 3 modules in size. This gives the smallest
+ // number of pixels the center could be, so skip this often. When trying harder, look for all
+ // QR versions regardless of how dense they are.
+ int iSkip = (int) (maxI / (MAX_MODULES * 4.0f) * 3);
+ if (iSkip < MIN_SKIP || tryHarder) {
+ iSkip = MIN_SKIP;
+ }
+
+ int[] stateCount = new int[5];
+ for (int i = iSkip - 1; i < maxI; i += iSkip) {
+ // Get a row of black/white values
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ int currentState = 0;
+ for (int j = 0; j < maxJ; j++) {
+ if (image.get(j, i)) {
+ // Black pixel
+ if ((currentState & 1) == 1) { // Counting white pixels
+ currentState++;
+ }
+ stateCount[currentState]++;
+ } else { // White pixel
+ if ((currentState & 1) == 0) { // Counting black pixels
+ if (currentState == 4) { // A winner?
+ if (foundPatternCross(stateCount) && handlePossibleCenter(stateCount, i, j, pureBarcode)) { // Yes
+ // Clear state to start looking again
+ currentState = 0;
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ } else { // No, shift counts back by two
+ stateCount[0] = stateCount[2];
+ stateCount[1] = stateCount[3];
+ stateCount[2] = stateCount[4];
+ stateCount[3] = 1;
+ stateCount[4] = 0;
+ currentState = 3;
+ }
+ } else {
+ stateCount[++currentState]++;
+ }
+ } else { // Counting white pixels
+ stateCount[currentState]++;
+ }
+ }
+ } // for j=...
+
+ if (foundPatternCross(stateCount)) {
+ handlePossibleCenter(stateCount, i, maxJ, pureBarcode);
+ } // end if foundPatternCross
+ } // for i=iSkip-1 ...
+ FinderPattern[][] patternInfo = selectMutipleBestPatterns();
+ List result = new ArrayList<>();
+ for (FinderPattern[] pattern : patternInfo) {
+ ResultPoint.orderBestPatterns(pattern);
+ result.add(new FinderPatternInfo(pattern));
+ }
+
+ if (result.isEmpty()) {
+ return EMPTY_RESULT_ARRAY;
+ } else {
+ return result.toArray(new FinderPatternInfo[result.size()]);
+ }
+ }
+
+ /**
+ * A comparator that orders FinderPatterns by their estimated module size.
+ */
+ private static final class ModuleSizeComparator implements Comparator, Serializable {
+ @Override
+ public int compare(FinderPattern center1, FinderPattern center2) {
+ float value = center2.getEstimatedModuleSize() - center1.getEstimatedModuleSize();
+ return value < 0.0 ? -1 : value > 0.0 ? 1 : 0;
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/oned/CodaBarReader.java b/src/com/google/zxing/oned/CodaBarReader.java
new file mode 100644
index 0000000..4f63502
--- /dev/null
+++ b/src/com/google/zxing/oned/CodaBarReader.java
@@ -0,0 +1,339 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Decodes Codabar barcodes.
+ *
+ * @author Bas Vijfwinkel
+ * @author David Walker
+ */
+public final class CodaBarReader extends OneDReader {
+
+ /**
+ * These represent the encodings of characters, as patterns of wide and narrow bars. The 7 least-significant bits of
+ * each int correspond to the pattern of wide and narrow, with 1s representing "wide" and 0s representing narrow.
+ */
+ static final int[] CHARACTER_ENCODINGS = {
+ 0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048, // 0-9
+ 0x00c, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E, // -$:/.+ABCD
+ };
+ // These values are critical for determining how permissive the decoding
+ // will be. All stripe sizes must be within the window these define, as
+ // compared to the average stripe size.
+ private static final float MAX_ACCEPTABLE = 2.0f;
+ private static final float PADDING = 1.5f;
+ private static final String ALPHABET_STRING = "0123456789-$:/.+ABCD";
+ static final char[] ALPHABET = ALPHABET_STRING.toCharArray();
+ // minimal number of characters that should be present (inclusing start and stop characters)
+ // under normal circumstances this should be set to 3, but can be set higher
+ // as a last-ditch attempt to reduce false positives.
+ private static final int MIN_CHARACTER_LENGTH = 3;
+
+ // official start and end patterns
+ private static final char[] STARTEND_ENCODING = {'A', 'B', 'C', 'D'};
+ // some codabar generator allow the codabar string to be closed by every
+ // character. This will cause lots of false positives!
+
+ // some industries use a checksum standard but this is not part of the original codabar standard
+ // for more information see : http://www.mecsw.com/specs/codabar.html
+
+ // Keep some instance variables to avoid reallocations
+ private final StringBuilder decodeRowResult;
+ private int[] counters;
+ private int counterLength;
+
+ public CodaBarReader() {
+ decodeRowResult = new StringBuilder(20);
+ counters = new int[80];
+ counterLength = 0;
+ }
+
+ static boolean arrayContains(char[] array, char key) {
+ if (array != null) {
+ for (char c : array) {
+ if (c == key) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber, BitArray row, Map hints) throws NotFoundException {
+
+ Arrays.fill(counters, 0);
+ setCounters(row);
+ int startOffset = findStartPattern();
+ int nextStart = startOffset;
+
+ decodeRowResult.setLength(0);
+ do {
+ int charOffset = toNarrowWidePattern(nextStart);
+ if (charOffset == -1) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ // Hack: We store the position in the alphabet table into a
+ // StringBuilder, so that we can access the decoded patterns in
+ // validatePattern. We'll translate to the actual characters later.
+ decodeRowResult.append((char) charOffset);
+ nextStart += 8;
+ // Stop as soon as we see the end character.
+ if (decodeRowResult.length() > 1 &&
+ arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) {
+ break;
+ }
+ } while (nextStart < counterLength); // no fixed end pattern so keep on reading while data is available
+
+ // Look for whitespace after pattern:
+ int trailingWhitespace = counters[nextStart - 1];
+ int lastPatternSize = 0;
+ for (int i = -8; i < -1; i++) {
+ lastPatternSize += counters[nextStart + i];
+ }
+
+ // We need to see whitespace equal to 50% of the last pattern size,
+ // otherwise this is probably a false positive. The exception is if we are
+ // at the end of the row. (I.e. the barcode barely fits.)
+ if (nextStart < counterLength && trailingWhitespace < lastPatternSize / 2) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ validatePattern(startOffset);
+
+ // Translate character table offsets to actual characters.
+ for (int i = 0; i < decodeRowResult.length(); i++) {
+ decodeRowResult.setCharAt(i, ALPHABET[decodeRowResult.charAt(i)]);
+ }
+ // Ensure a valid start and end character
+ char startchar = decodeRowResult.charAt(0);
+ if (!arrayContains(STARTEND_ENCODING, startchar)) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ char endchar = decodeRowResult.charAt(decodeRowResult.length() - 1);
+ if (!arrayContains(STARTEND_ENCODING, endchar)) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // remove stop/start characters character and check if a long enough string is contained
+ if (decodeRowResult.length() <= MIN_CHARACTER_LENGTH) {
+ // Almost surely a false positive ( start + stop + at least 1 character)
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ if (hints == null || !hints.containsKey(DecodeHintType.RETURN_CODABAR_START_END)) {
+ decodeRowResult.deleteCharAt(decodeRowResult.length() - 1);
+ decodeRowResult.deleteCharAt(0);
+ }
+
+ int runningCount = 0;
+ for (int i = 0; i < startOffset; i++) {
+ runningCount += counters[i];
+ }
+ float left = (float) runningCount;
+ for (int i = startOffset; i < nextStart - 1; i++) {
+ runningCount += counters[i];
+ }
+ float right = (float) runningCount;
+ return new Result(
+ decodeRowResult.toString(),
+ null,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODABAR);
+ }
+
+ void validatePattern(int start) throws NotFoundException {
+ // First, sum up the total size of our four categories of stripe sizes;
+ int[] sizes = {0, 0, 0, 0};
+ int[] counts = {0, 0, 0, 0};
+ int end = decodeRowResult.length() - 1;
+
+ // We break out of this loop in the middle, in order to handle
+ // inter-character spaces properly.
+ int pos = start;
+ for (int i = 0; true; i++) {
+ int pattern = CHARACTER_ENCODINGS[decodeRowResult.charAt(i)];
+ for (int j = 6; j >= 0; j--) {
+ // Even j = bars, while odd j = spaces. Categories 2 and 3 are for
+ // long stripes, while 0 and 1 are for short stripes.
+ int category = (j & 1) + (pattern & 1) * 2;
+ sizes[category] += counters[pos + j];
+ counts[category]++;
+ pattern >>= 1;
+ }
+ if (i >= end) {
+ break;
+ }
+ // We ignore the inter-character space - it could be of any size.
+ pos += 8;
+ }
+
+ // Calculate our allowable size thresholds using fixed-point math.
+ float[] maxes = new float[4];
+ float[] mins = new float[4];
+ // Define the threshold of acceptability to be the midpoint between the
+ // average small stripe and the average large stripe. No stripe lengths
+ // should be on the "wrong" side of that line.
+ for (int i = 0; i < 2; i++) {
+ mins[i] = 0.0f; // Accept arbitrarily small "short" stripes.
+ mins[i + 2] = ((float) sizes[i] / counts[i] + (float) sizes[i + 2] / counts[i + 2]) / 2.0f;
+ maxes[i] = mins[i + 2];
+ maxes[i + 2] = (sizes[i + 2] * MAX_ACCEPTABLE + PADDING) / counts[i + 2];
+ }
+
+ // Now verify that all of the stripes are within the thresholds.
+ pos = start;
+ for (int i = 0; true; i++) {
+ int pattern = CHARACTER_ENCODINGS[decodeRowResult.charAt(i)];
+ for (int j = 6; j >= 0; j--) {
+ // Even j = bars, while odd j = spaces. Categories 2 and 3 are for
+ // long stripes, while 0 and 1 are for short stripes.
+ int category = (j & 1) + (pattern & 1) * 2;
+ int size = counters[pos + j];
+ if (size < mins[category] || size > maxes[category]) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ pattern >>= 1;
+ }
+ if (i >= end) {
+ break;
+ }
+ pos += 8;
+ }
+ }
+
+ /**
+ * Records the size of all runs of white and black pixels, starting with white.
+ * This is just like recordPattern, except it records all the counters, and
+ * uses our builtin "counters" member for storage.
+ *
+ * @param row row to count from
+ */
+ private void setCounters(BitArray row) throws NotFoundException {
+ counterLength = 0;
+ // Start from the first white bit.
+ int i = row.getNextUnset(0);
+ int end = row.getSize();
+ if (i >= end) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ boolean isWhite = true;
+ int count = 0;
+ while (i < end) {
+ if (row.get(i) ^ isWhite) { // that is, exactly one is true
+ count++;
+ } else {
+ counterAppend(count);
+ count = 1;
+ isWhite = !isWhite;
+ }
+ i++;
+ }
+ counterAppend(count);
+ }
+
+ private void counterAppend(int e) {
+ counters[counterLength] = e;
+ counterLength++;
+ if (counterLength >= counters.length) {
+ int[] temp = new int[counterLength * 2];
+ System.arraycopy(counters, 0, temp, 0, counterLength);
+ counters = temp;
+ }
+ }
+
+ private int findStartPattern() throws NotFoundException {
+ for (int i = 1; i < counterLength; i += 2) {
+ int charOffset = toNarrowWidePattern(i);
+ if (charOffset != -1 && arrayContains(STARTEND_ENCODING, ALPHABET[charOffset])) {
+ // Look for whitespace before start pattern, >= 50% of width of start pattern
+ // We make an exception if the whitespace is the first element.
+ int patternSize = 0;
+ for (int j = i; j < i + 7; j++) {
+ patternSize += counters[j];
+ }
+ if (i == 1 || counters[i - 1] >= patternSize / 2) {
+ return i;
+ }
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Assumes that counters[position] is a bar.
+ private int toNarrowWidePattern(int position) {
+ int end = position + 7;
+ if (end >= counterLength) {
+ return -1;
+ }
+
+ int[] theCounters = counters;
+
+ int maxBar = 0;
+ int minBar = Integer.MAX_VALUE;
+ for (int j = position; j < end; j += 2) {
+ int currentCounter = theCounters[j];
+ if (currentCounter < minBar) {
+ minBar = currentCounter;
+ }
+ if (currentCounter > maxBar) {
+ maxBar = currentCounter;
+ }
+ }
+ int thresholdBar = (minBar + maxBar) / 2;
+
+ int maxSpace = 0;
+ int minSpace = Integer.MAX_VALUE;
+ for (int j = position + 1; j < end; j += 2) {
+ int currentCounter = theCounters[j];
+ if (currentCounter < minSpace) {
+ minSpace = currentCounter;
+ }
+ if (currentCounter > maxSpace) {
+ maxSpace = currentCounter;
+ }
+ }
+ int thresholdSpace = (minSpace + maxSpace) / 2;
+
+ int bitmask = 1 << 7;
+ int pattern = 0;
+ for (int i = 0; i < 7; i++) {
+ int threshold = (i & 1) == 0 ? thresholdBar : thresholdSpace;
+ bitmask >>= 1;
+ if (theCounters[position + i] > threshold) {
+ pattern |= bitmask;
+ }
+ }
+
+ for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
+ if (CHARACTER_ENCODINGS[i] == pattern) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/CodaBarWriter.java b/src/com/google/zxing/oned/CodaBarWriter.java
new file mode 100644
index 0000000..211b5b6
--- /dev/null
+++ b/src/com/google/zxing/oned/CodaBarWriter.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+/**
+ * This class renders CodaBar as {@code boolean[]}.
+ *
+ * @author dsbnatut@gmail.com (Kazuki Nishiura)
+ */
+public final class CodaBarWriter extends OneDimensionalCodeWriter {
+
+ private static final char[] START_END_CHARS = {'A', 'B', 'C', 'D'};
+ private static final char[] ALT_START_END_CHARS = {'T', 'N', '*', 'E'};
+ private static final char[] CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED = {'/', ':', '+', '.'};
+ private static final char DEFAULT_GUARD = START_END_CHARS[0];
+
+ @Override
+ public boolean[] encode(String contents) {
+
+ if (contents.length() < 2) {
+ // Can't have a start/end guard, so tentatively add default guards
+ contents = DEFAULT_GUARD + contents + DEFAULT_GUARD;
+ } else {
+ // Verify input and calculate decoded length.
+ char firstChar = Character.toUpperCase(contents.charAt(0));
+ char lastChar = Character.toUpperCase(contents.charAt(contents.length() - 1));
+ boolean startsNormal = CodaBarReader.arrayContains(START_END_CHARS, firstChar);
+ boolean endsNormal = CodaBarReader.arrayContains(START_END_CHARS, lastChar);
+ boolean startsAlt = CodaBarReader.arrayContains(ALT_START_END_CHARS, firstChar);
+ boolean endsAlt = CodaBarReader.arrayContains(ALT_START_END_CHARS, lastChar);
+ if (startsNormal) {
+ if (!endsNormal) {
+ throw new IllegalArgumentException("Invalid start/end guards: " + contents);
+ }
+ // else already has valid start/end
+ } else if (startsAlt) {
+ if (!endsAlt) {
+ throw new IllegalArgumentException("Invalid start/end guards: " + contents);
+ }
+ // else already has valid start/end
+ } else {
+ // Doesn't start with a guard
+ if (endsNormal || endsAlt) {
+ throw new IllegalArgumentException("Invalid start/end guards: " + contents);
+ }
+ // else doesn't end with guard either, so add a default
+ contents = DEFAULT_GUARD + contents + DEFAULT_GUARD;
+ }
+ }
+
+ // The start character and the end character are decoded to 10 length each.
+ int resultLength = 20;
+ for (int i = 1; i < contents.length() - 1; i++) {
+ if (Character.isDigit(contents.charAt(i)) || contents.charAt(i) == '-' || contents.charAt(i) == '$') {
+ resultLength += 9;
+ } else if (CodaBarReader.arrayContains(CHARS_WHICH_ARE_TEN_LENGTH_EACH_AFTER_DECODED, contents.charAt(i))) {
+ resultLength += 10;
+ } else {
+ throw new IllegalArgumentException("Cannot encode : '" + contents.charAt(i) + '\'');
+ }
+ }
+ // A blank is placed between each character.
+ resultLength += contents.length() - 1;
+
+ boolean[] result = new boolean[resultLength];
+ int position = 0;
+ for (int index = 0; index < contents.length(); index++) {
+ char c = Character.toUpperCase(contents.charAt(index));
+ if (index == 0 || index == contents.length() - 1) {
+ // The start/end chars are not in the CodaBarReader.ALPHABET.
+ switch (c) {
+ case 'T':
+ c = 'A';
+ break;
+ case 'N':
+ c = 'B';
+ break;
+ case '*':
+ c = 'C';
+ break;
+ case 'E':
+ c = 'D';
+ break;
+ }
+ }
+ int code = 0;
+ for (int i = 0; i < CodaBarReader.ALPHABET.length; i++) {
+ // Found any, because I checked above.
+ if (c == CodaBarReader.ALPHABET[i]) {
+ code = CodaBarReader.CHARACTER_ENCODINGS[i];
+ break;
+ }
+ }
+ boolean color = true;
+ int counter = 0;
+ int bit = 0;
+ while (bit < 7) { // A character consists of 7 digit.
+ result[position] = color;
+ position++;
+ if (((code >> (6 - bit)) & 1) == 0 || counter == 1) {
+ color = !color; // Flip the color.
+ bit++;
+ counter = 0;
+ } else {
+ counter++;
+ }
+ }
+ if (index < contents.length() - 1) {
+ result[position] = false;
+ position++;
+ }
+ }
+ return result;
+ }
+}
+
diff --git a/src/com/google/zxing/oned/Code128Reader.java b/src/com/google/zxing/oned/Code128Reader.java
new file mode 100644
index 0000000..730f626
--- /dev/null
+++ b/src/com/google/zxing/oned/Code128Reader.java
@@ -0,0 +1,533 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Decodes Code 128 barcodes.
+ *
+ * @author Sean Owen
+ */
+public final class Code128Reader extends OneDReader {
+
+ static final int[][] CODE_PATTERNS = {
+ {2, 1, 2, 2, 2, 2}, // 0
+ {2, 2, 2, 1, 2, 2},
+ {2, 2, 2, 2, 2, 1},
+ {1, 2, 1, 2, 2, 3},
+ {1, 2, 1, 3, 2, 2},
+ {1, 3, 1, 2, 2, 2}, // 5
+ {1, 2, 2, 2, 1, 3},
+ {1, 2, 2, 3, 1, 2},
+ {1, 3, 2, 2, 1, 2},
+ {2, 2, 1, 2, 1, 3},
+ {2, 2, 1, 3, 1, 2}, // 10
+ {2, 3, 1, 2, 1, 2},
+ {1, 1, 2, 2, 3, 2},
+ {1, 2, 2, 1, 3, 2},
+ {1, 2, 2, 2, 3, 1},
+ {1, 1, 3, 2, 2, 2}, // 15
+ {1, 2, 3, 1, 2, 2},
+ {1, 2, 3, 2, 2, 1},
+ {2, 2, 3, 2, 1, 1},
+ {2, 2, 1, 1, 3, 2},
+ {2, 2, 1, 2, 3, 1}, // 20
+ {2, 1, 3, 2, 1, 2},
+ {2, 2, 3, 1, 1, 2},
+ {3, 1, 2, 1, 3, 1},
+ {3, 1, 1, 2, 2, 2},
+ {3, 2, 1, 1, 2, 2}, // 25
+ {3, 2, 1, 2, 2, 1},
+ {3, 1, 2, 2, 1, 2},
+ {3, 2, 2, 1, 1, 2},
+ {3, 2, 2, 2, 1, 1},
+ {2, 1, 2, 1, 2, 3}, // 30
+ {2, 1, 2, 3, 2, 1},
+ {2, 3, 2, 1, 2, 1},
+ {1, 1, 1, 3, 2, 3},
+ {1, 3, 1, 1, 2, 3},
+ {1, 3, 1, 3, 2, 1}, // 35
+ {1, 1, 2, 3, 1, 3},
+ {1, 3, 2, 1, 1, 3},
+ {1, 3, 2, 3, 1, 1},
+ {2, 1, 1, 3, 1, 3},
+ {2, 3, 1, 1, 1, 3}, // 40
+ {2, 3, 1, 3, 1, 1},
+ {1, 1, 2, 1, 3, 3},
+ {1, 1, 2, 3, 3, 1},
+ {1, 3, 2, 1, 3, 1},
+ {1, 1, 3, 1, 2, 3}, // 45
+ {1, 1, 3, 3, 2, 1},
+ {1, 3, 3, 1, 2, 1},
+ {3, 1, 3, 1, 2, 1},
+ {2, 1, 1, 3, 3, 1},
+ {2, 3, 1, 1, 3, 1}, // 50
+ {2, 1, 3, 1, 1, 3},
+ {2, 1, 3, 3, 1, 1},
+ {2, 1, 3, 1, 3, 1},
+ {3, 1, 1, 1, 2, 3},
+ {3, 1, 1, 3, 2, 1}, // 55
+ {3, 3, 1, 1, 2, 1},
+ {3, 1, 2, 1, 1, 3},
+ {3, 1, 2, 3, 1, 1},
+ {3, 3, 2, 1, 1, 1},
+ {3, 1, 4, 1, 1, 1}, // 60
+ {2, 2, 1, 4, 1, 1},
+ {4, 3, 1, 1, 1, 1},
+ {1, 1, 1, 2, 2, 4},
+ {1, 1, 1, 4, 2, 2},
+ {1, 2, 1, 1, 2, 4}, // 65
+ {1, 2, 1, 4, 2, 1},
+ {1, 4, 1, 1, 2, 2},
+ {1, 4, 1, 2, 2, 1},
+ {1, 1, 2, 2, 1, 4},
+ {1, 1, 2, 4, 1, 2}, // 70
+ {1, 2, 2, 1, 1, 4},
+ {1, 2, 2, 4, 1, 1},
+ {1, 4, 2, 1, 1, 2},
+ {1, 4, 2, 2, 1, 1},
+ {2, 4, 1, 2, 1, 1}, // 75
+ {2, 2, 1, 1, 1, 4},
+ {4, 1, 3, 1, 1, 1},
+ {2, 4, 1, 1, 1, 2},
+ {1, 3, 4, 1, 1, 1},
+ {1, 1, 1, 2, 4, 2}, // 80
+ {1, 2, 1, 1, 4, 2},
+ {1, 2, 1, 2, 4, 1},
+ {1, 1, 4, 2, 1, 2},
+ {1, 2, 4, 1, 1, 2},
+ {1, 2, 4, 2, 1, 1}, // 85
+ {4, 1, 1, 2, 1, 2},
+ {4, 2, 1, 1, 1, 2},
+ {4, 2, 1, 2, 1, 1},
+ {2, 1, 2, 1, 4, 1},
+ {2, 1, 4, 1, 2, 1}, // 90
+ {4, 1, 2, 1, 2, 1},
+ {1, 1, 1, 1, 4, 3},
+ {1, 1, 1, 3, 4, 1},
+ {1, 3, 1, 1, 4, 1},
+ {1, 1, 4, 1, 1, 3}, // 95
+ {1, 1, 4, 3, 1, 1},
+ {4, 1, 1, 1, 1, 3},
+ {4, 1, 1, 3, 1, 1},
+ {1, 1, 3, 1, 4, 1},
+ {1, 1, 4, 1, 3, 1}, // 100
+ {3, 1, 1, 1, 4, 1},
+ {4, 1, 1, 1, 3, 1},
+ {2, 1, 1, 4, 1, 2},
+ {2, 1, 1, 2, 1, 4},
+ {2, 1, 1, 2, 3, 2}, // 105
+ {2, 3, 3, 1, 1, 1, 2}
+ };
+
+ private static final float MAX_AVG_VARIANCE = 0.25f;
+ private static final float MAX_INDIVIDUAL_VARIANCE = 0.7f;
+
+ private static final int CODE_SHIFT = 98;
+
+ private static final int CODE_CODE_C = 99;
+ private static final int CODE_CODE_B = 100;
+ private static final int CODE_CODE_A = 101;
+
+ private static final int CODE_FNC_1 = 102;
+ private static final int CODE_FNC_2 = 97;
+ private static final int CODE_FNC_3 = 96;
+ private static final int CODE_FNC_4_A = 101;
+ private static final int CODE_FNC_4_B = 100;
+
+ private static final int CODE_START_A = 103;
+ private static final int CODE_START_B = 104;
+ private static final int CODE_START_C = 105;
+ private static final int CODE_STOP = 106;
+
+ private static int[] findStartPattern(BitArray row) throws NotFoundException {
+ int width = row.getSize();
+ int rowOffset = row.getNextSet(0);
+
+ int counterPosition = 0;
+ int[] counters = new int[6];
+ int patternStart = rowOffset;
+ boolean isWhite = false;
+ int patternLength = counters.length;
+
+ for (int i = rowOffset; i < width; i++) {
+ if (row.get(i) ^ isWhite) {
+ counters[counterPosition]++;
+ } else {
+ if (counterPosition == patternLength - 1) {
+ float bestVariance = MAX_AVG_VARIANCE;
+ int bestMatch = -1;
+ for (int startCode = CODE_START_A; startCode <= CODE_START_C; startCode++) {
+ float variance = patternMatchVariance(counters, CODE_PATTERNS[startCode],
+ MAX_INDIVIDUAL_VARIANCE);
+ if (variance < bestVariance) {
+ bestVariance = variance;
+ bestMatch = startCode;
+ }
+ }
+ // Look for whitespace before start pattern, >= 50% of width of start pattern
+ if (bestMatch >= 0 &&
+ row.isRange(Math.max(0, patternStart - (i - patternStart) / 2), patternStart, false)) {
+ return new int[]{patternStart, i, bestMatch};
+ }
+ patternStart += counters[0] + counters[1];
+ System.arraycopy(counters, 2, counters, 0, patternLength - 2);
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private static int decodeCode(BitArray row, int[] counters, int rowOffset)
+ throws NotFoundException {
+ recordPattern(row, rowOffset, counters);
+ float 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];
+ float variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
+ if (variance < bestVariance) {
+ bestVariance = variance;
+ bestMatch = d;
+ }
+ }
+ // TODO We're overlooking the fact that the STOP pattern has 7 values, not 6.
+ if (bestMatch >= 0) {
+ return bestMatch;
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber, BitArray row, Map hints)
+ throws NotFoundException, FormatException, ChecksumException {
+
+ boolean convertFNC1 = hints != null && hints.containsKey(DecodeHintType.ASSUME_GS1);
+
+ int[] startPatternInfo = findStartPattern(row);
+ int startCode = startPatternInfo[2];
+
+ List rawCodes = new ArrayList<>(20);
+ rawCodes.add((byte) startCode);
+
+ int codeSet;
+ switch (startCode) {
+ case CODE_START_A:
+ codeSet = CODE_CODE_A;
+ break;
+ case CODE_START_B:
+ codeSet = CODE_CODE_B;
+ break;
+ case CODE_START_C:
+ codeSet = CODE_CODE_C;
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+
+ boolean done = false;
+ boolean isNextShifted = false;
+
+ StringBuilder result = new StringBuilder(20);
+
+ int lastStart = startPatternInfo[0];
+ int nextStart = startPatternInfo[1];
+ int[] counters = new int[6];
+
+ int lastCode = 0;
+ int code = 0;
+ int checksumTotal = startCode;
+ int multiplier = 0;
+ boolean lastCharacterWasPrintable = true;
+ boolean upperMode = false;
+ boolean shiftUpperMode = false;
+
+ while (!done) {
+
+ boolean unshift = isNextShifted;
+ isNextShifted = false;
+
+ // Save off last code
+ lastCode = code;
+
+ // Decode another code from image
+ code = decodeCode(row, counters, nextStart);
+
+ rawCodes.add((byte) code);
+
+ // Remember whether the last code was printable or not (excluding CODE_STOP)
+ if (code != CODE_STOP) {
+ lastCharacterWasPrintable = true;
+ }
+
+ // Add to checksum computation (if not CODE_STOP of course)
+ if (code != CODE_STOP) {
+ multiplier++;
+ checksumTotal += multiplier * code;
+ }
+
+ // Advance to where the next code will to start
+ lastStart = nextStart;
+ for (int counter : counters) {
+ nextStart += counter;
+ }
+
+ // Take care of illegal start codes
+ switch (code) {
+ case CODE_START_A:
+ case CODE_START_B:
+ case CODE_START_C:
+ throw FormatException.getFormatInstance();
+ }
+
+ switch (codeSet) {
+
+ case CODE_CODE_A:
+ if (code < 64) {
+ if (shiftUpperMode == upperMode) {
+ result.append((char) (' ' + code));
+ } else {
+ result.append((char) (' ' + code + 128));
+ }
+ shiftUpperMode = false;
+ } else if (code < 96) {
+ if (shiftUpperMode == upperMode) {
+ result.append((char) (code - 64));
+ } else {
+ result.append((char) (code + 64));
+ }
+ shiftUpperMode = false;
+ } else {
+ // Don't let CODE_STOP, which always appears, affect whether whether we think the last
+ // code was printable or not.
+ if (code != CODE_STOP) {
+ lastCharacterWasPrintable = false;
+ }
+ switch (code) {
+ case CODE_FNC_1:
+ if (convertFNC1) {
+ if (result.length() == 0) {
+ // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
+ // is FNC1 then this is GS1-128. We add the symbology identifier.
+ result.append("]C1");
+ } else {
+ // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
+ result.append((char) 29);
+ }
+ }
+ break;
+ case CODE_FNC_2:
+ case CODE_FNC_3:
+ // do nothing?
+ break;
+ case CODE_FNC_4_A:
+ if (!upperMode && shiftUpperMode) {
+ upperMode = true;
+ shiftUpperMode = false;
+ } else if (upperMode && shiftUpperMode) {
+ upperMode = false;
+ shiftUpperMode = false;
+ } else {
+ shiftUpperMode = true;
+ }
+ break;
+ case CODE_SHIFT:
+ isNextShifted = true;
+ codeSet = CODE_CODE_B;
+ break;
+ case CODE_CODE_B:
+ codeSet = CODE_CODE_B;
+ break;
+ case CODE_CODE_C:
+ codeSet = CODE_CODE_C;
+ break;
+ case CODE_STOP:
+ done = true;
+ break;
+ }
+ }
+ break;
+ case CODE_CODE_B:
+ if (code < 96) {
+ if (shiftUpperMode == upperMode) {
+ result.append((char) (' ' + code));
+ } else {
+ result.append((char) (' ' + code + 128));
+ }
+ shiftUpperMode = false;
+ } else {
+ if (code != CODE_STOP) {
+ lastCharacterWasPrintable = false;
+ }
+ switch (code) {
+ case CODE_FNC_1:
+ if (convertFNC1) {
+ if (result.length() == 0) {
+ // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
+ // is FNC1 then this is GS1-128. We add the symbology identifier.
+ result.append("]C1");
+ } else {
+ // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
+ result.append((char) 29);
+ }
+ }
+ break;
+ case CODE_FNC_2:
+ case CODE_FNC_3:
+ // do nothing?
+ break;
+ case CODE_FNC_4_B:
+ if (!upperMode && shiftUpperMode) {
+ upperMode = true;
+ shiftUpperMode = false;
+ } else if (upperMode && shiftUpperMode) {
+ upperMode = false;
+ shiftUpperMode = false;
+ } else {
+ shiftUpperMode = true;
+ }
+ break;
+ case CODE_SHIFT:
+ isNextShifted = true;
+ codeSet = CODE_CODE_A;
+ break;
+ case CODE_CODE_A:
+ codeSet = CODE_CODE_A;
+ break;
+ case CODE_CODE_C:
+ codeSet = CODE_CODE_C;
+ break;
+ case CODE_STOP:
+ done = true;
+ break;
+ }
+ }
+ break;
+ case CODE_CODE_C:
+ if (code < 100) {
+ if (code < 10) {
+ result.append('0');
+ }
+ result.append(code);
+ } else {
+ if (code != CODE_STOP) {
+ lastCharacterWasPrintable = false;
+ }
+ switch (code) {
+ case CODE_FNC_1:
+ if (convertFNC1) {
+ if (result.length() == 0) {
+ // GS1 specification 5.4.3.7. and 5.4.6.4. If the first char after the start code
+ // is FNC1 then this is GS1-128. We add the symbology identifier.
+ result.append("]C1");
+ } else {
+ // GS1 specification 5.4.7.5. Every subsequent FNC1 is returned as ASCII 29 (GS)
+ result.append((char) 29);
+ }
+ }
+ break;
+ case CODE_CODE_A:
+ codeSet = CODE_CODE_A;
+ break;
+ case CODE_CODE_B:
+ codeSet = CODE_CODE_B;
+ break;
+ case CODE_STOP:
+ done = true;
+ break;
+ }
+ }
+ break;
+ }
+
+ // Unshift back to another code set if we were shifted
+ if (unshift) {
+ codeSet = codeSet == CODE_CODE_A ? CODE_CODE_B : CODE_CODE_A;
+ }
+
+ }
+
+ int lastPatternSize = nextStart - lastStart;
+
+ // Check for ample whitespace following pattern, but, to do this we first need to remember that
+ // we fudged decoding CODE_STOP since it actually has 7 bars, not 6. There is a black bar left
+ // to read off. Would be slightly better to properly read. Here we just skip it:
+ nextStart = row.getNextUnset(nextStart);
+ if (!row.isRange(nextStart,
+ Math.min(row.getSize(), nextStart + (nextStart - lastStart) / 2),
+ false)) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Pull out from sum the value of the penultimate check code
+ checksumTotal -= multiplier * lastCode;
+ // lastCode is the checksum then:
+ if (checksumTotal % 103 != lastCode) {
+ throw ChecksumException.getChecksumInstance();
+ }
+
+ // Need to pull out the check digits from string
+ int resultLength = result.length();
+ if (resultLength == 0) {
+ // false positive
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // Only bother if the result had at least one character, and if the checksum digit happened to
+ // be a printable character. If it was just interpreted as a control code, nothing to remove.
+ if (resultLength > 0 && lastCharacterWasPrintable) {
+ if (codeSet == CODE_CODE_C) {
+ result.delete(resultLength - 2, resultLength);
+ } else {
+ result.delete(resultLength - 1, resultLength);
+ }
+ }
+
+ float left = (float) (startPatternInfo[1] + startPatternInfo[0]) / 2.0f;
+ float right = lastStart + lastPatternSize / 2.0f;
+
+ int rawCodesSize = rawCodes.size();
+ byte[] rawBytes = new byte[rawCodesSize];
+ for (int i = 0; i < rawCodesSize; i++) {
+ rawBytes[i] = rawCodes.get(i);
+ }
+
+ return new Result(
+ result.toString(),
+ rawBytes,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODE_128);
+
+ }
+
+}
diff --git a/src/com/google/zxing/oned/Code128Writer.java b/src/com/google/zxing/oned/Code128Writer.java
new file mode 100644
index 0000000..06b5e8e
--- /dev/null
+++ b/src/com/google/zxing/oned/Code128Writer.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * This object renders a CODE128 code as a {@link BitMatrix}.
+ *
+ * @author erik.barbara@gmail.com (Erik Barbara)
+ */
+public final class Code128Writer extends OneDimensionalCodeWriter {
+
+ private static final int CODE_START_B = 104;
+ private static final int CODE_START_C = 105;
+ private static final int CODE_CODE_B = 100;
+ private static final int CODE_CODE_C = 99;
+ private static final int CODE_STOP = 106;
+
+ // Dummy characters used to specify control characters in input
+ private static final char ESCAPE_FNC_1 = '\u00f1';
+ private static final char ESCAPE_FNC_2 = '\u00f2';
+ private static final char ESCAPE_FNC_3 = '\u00f3';
+ private static final char ESCAPE_FNC_4 = '\u00f4';
+
+ private static final int CODE_FNC_1 = 102; // Code A, Code B, Code C
+ private static final int CODE_FNC_2 = 97; // Code A, Code B
+ private static final int CODE_FNC_3 = 96; // Code A, Code B
+ private static final int CODE_FNC_4_B = 100; // Code B
+
+ private static boolean isDigits(CharSequence value, int start, int length) {
+ int end = start + length;
+ int last = value.length();
+ for (int i = start; i < end && i < last; i++) {
+ char c = value.charAt(i);
+ if (c < '0' || c > '9') {
+ if (c != ESCAPE_FNC_1) {
+ return false;
+ }
+ end++; // ignore FNC_1
+ }
+ }
+ return end <= last; // end > last if we've run out of string
+ }
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.CODE_128) {
+ throw new IllegalArgumentException("Can only encode CODE_128, but got " + format);
+ }
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ @Override
+ public boolean[] encode(String contents) {
+ int length = contents.length();
+ // Check length
+ if (length < 1 || length > 80) {
+ throw new IllegalArgumentException(
+ "Contents length should be between 1 and 80 characters, but got " + length);
+ }
+ // Check content
+ for (int i = 0; i < length; i++) {
+ char c = contents.charAt(i);
+ if (c < ' ' || c > '~') {
+ switch (c) {
+ case ESCAPE_FNC_1:
+ case ESCAPE_FNC_2:
+ case ESCAPE_FNC_3:
+ case ESCAPE_FNC_4:
+ break;
+ default:
+ throw new IllegalArgumentException("Bad character in input: " + c);
+ }
+ }
+ }
+
+ Collection patterns = new ArrayList<>(); // temporary storage for patterns
+ int checkSum = 0;
+ int checkWeight = 1;
+ int codeSet = 0; // selected code (CODE_CODE_B or CODE_CODE_C)
+ int position = 0; // position in contents
+
+ while (position < length) {
+ //Select code to use
+ int requiredDigitCount = codeSet == CODE_CODE_C ? 2 : 4;
+ int newCodeSet;
+ if (isDigits(contents, position, requiredDigitCount)) {
+ newCodeSet = CODE_CODE_C;
+ } else {
+ newCodeSet = CODE_CODE_B;
+ }
+
+ //Get the pattern index
+ int patternIndex;
+ if (newCodeSet == codeSet) {
+ // Encode the current character
+ // First handle escapes
+ switch (contents.charAt(position)) {
+ case ESCAPE_FNC_1:
+ patternIndex = CODE_FNC_1;
+ break;
+ case ESCAPE_FNC_2:
+ patternIndex = CODE_FNC_2;
+ break;
+ case ESCAPE_FNC_3:
+ patternIndex = CODE_FNC_3;
+ break;
+ case ESCAPE_FNC_4:
+ patternIndex = CODE_FNC_4_B; // FIXME if this ever outputs Code A
+ break;
+ default:
+ // Then handle normal characters otherwise
+ if (codeSet == CODE_CODE_B) {
+ patternIndex = contents.charAt(position) - ' ';
+ } else { // CODE_CODE_C
+ patternIndex = Integer.parseInt(contents.substring(position, position + 2));
+ position++; // Also incremented below
+ }
+ }
+ position++;
+ } else {
+ // Should we change the current code?
+ // Do we have a code set?
+ if (codeSet == 0) {
+ // No, we don't have a code set
+ if (newCodeSet == CODE_CODE_B) {
+ patternIndex = CODE_START_B;
+ } else {
+ // CODE_CODE_C
+ patternIndex = CODE_START_C;
+ }
+ } else {
+ // Yes, we have a code set
+ patternIndex = newCodeSet;
+ }
+ codeSet = newCodeSet;
+ }
+
+ // Get the pattern
+ patterns.add(Code128Reader.CODE_PATTERNS[patternIndex]);
+
+ // Compute checksum
+ checkSum += patternIndex * checkWeight;
+ if (position != 0) {
+ checkWeight++;
+ }
+ }
+
+ // Compute and append checksum
+ checkSum %= 103;
+ patterns.add(Code128Reader.CODE_PATTERNS[checkSum]);
+
+ // Append stop code
+ patterns.add(Code128Reader.CODE_PATTERNS[CODE_STOP]);
+
+ // Compute code width
+ int codeWidth = 0;
+ for (int[] pattern : patterns) {
+ for (int width : pattern) {
+ codeWidth += width;
+ }
+ }
+
+ // Compute result
+ boolean[] result = new boolean[codeWidth];
+ int pos = 0;
+ for (int[] pattern : patterns) {
+ pos += appendPattern(result, pos, pattern, true);
+ }
+
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/Code39Reader.java b/src/com/google/zxing/oned/Code39Reader.java
new file mode 100644
index 0000000..aabe156
--- /dev/null
+++ b/src/com/google/zxing/oned/Code39Reader.java
@@ -0,0 +1,315 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Decodes Code 39 barcodes. This does not support "Full ASCII Code 39" yet.
+ *
+ * @author Sean Owen
+ * @see Code93Reader
+ */
+public final class Code39Reader extends OneDReader {
+
+ static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. *$/+%";
+ /**
+ * 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 char[] ALPHABET = ALPHABET_STRING.toCharArray();
+ private static final int ASTERISK_ENCODING = CHARACTER_ENCODINGS[39];
+
+ private final boolean usingCheckDigit;
+ private final boolean extendedMode;
+ private final StringBuilder decodeRowResult;
+ private final int[] counters;
+
+ /**
+ * Creates a reader that assumes all encoded data is data, and does not treat the final
+ * character as a check digit. It will not decoded "extended Code 39" sequences.
+ */
+ public Code39Reader() {
+ this(false);
+ }
+
+ /**
+ * Creates a reader that can be configured to check the last character as a check digit.
+ * It will not decoded "extended Code 39" sequences.
+ *
+ * @param usingCheckDigit if true, treat the last data character as a check digit, not
+ * data, and verify that the checksum passes.
+ */
+ public Code39Reader(boolean usingCheckDigit) {
+ this(usingCheckDigit, false);
+ }
+
+ /**
+ * Creates a reader that can be configured to check the last character as a check digit,
+ * or optionally attempt to decode "extended Code 39" sequences that are used to encode
+ * the full ASCII character set.
+ *
+ * @param usingCheckDigit if true, treat the last data character as a check digit, not
+ * data, and verify that the checksum passes.
+ * @param extendedMode if true, will attempt to decode extended Code 39 sequences in the
+ * text.
+ */
+ public Code39Reader(boolean usingCheckDigit, boolean extendedMode) {
+ this.usingCheckDigit = usingCheckDigit;
+ this.extendedMode = extendedMode;
+ decodeRowResult = new StringBuilder(20);
+ counters = new int[9];
+ }
+
+ private static int[] findAsteriskPattern(BitArray row, int[] counters) throws NotFoundException {
+ int width = row.getSize();
+ int rowOffset = row.getNextSet(0);
+
+ int counterPosition = 0;
+ int patternStart = rowOffset;
+ boolean isWhite = false;
+ int patternLength = counters.length;
+
+ for (int i = rowOffset; i < width; i++) {
+ if (row.get(i) ^ isWhite) {
+ counters[counterPosition]++;
+ } else {
+ if (counterPosition == patternLength - 1) {
+ // Look for whitespace before start pattern, >= 50% of width of start pattern
+ if (toNarrowWidePattern(counters) == ASTERISK_ENCODING &&
+ row.isRange(Math.max(0, patternStart - ((i - patternStart) / 2)), patternStart, false)) {
+ return new int[]{patternStart, i};
+ }
+ patternStart += counters[0] + counters[1];
+ System.arraycopy(counters, 2, counters, 0, patternLength - 2);
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ // For efficiency, returns -1 on failure. Not throwing here saved as many as 700 exceptions
+ // per image when using some of our blackbox images.
+ private static int toNarrowWidePattern(int[] counters) {
+ int numCounters = counters.length;
+ int maxNarrowCounter = 0;
+ int wideCounters;
+ do {
+ int minCounter = Integer.MAX_VALUE;
+ for (int counter : counters) {
+ if (counter < minCounter && counter > maxNarrowCounter) {
+ minCounter = counter;
+ }
+ }
+ maxNarrowCounter = minCounter;
+ wideCounters = 0;
+ int totalWideCountersWidth = 0;
+ int pattern = 0;
+ for (int i = 0; i < numCounters; i++) {
+ int counter = counters[i];
+ if (counter > maxNarrowCounter) {
+ pattern |= 1 << (numCounters - 1 - i);
+ wideCounters++;
+ totalWideCountersWidth += counter;
+ }
+ }
+ if (wideCounters == 3) {
+ // Found 3 wide counters, but are they close enough in width?
+ // We can perform a cheap, conservative check to see if any individual
+ // counter is more than 1.5 times the average:
+ for (int i = 0; i < numCounters && wideCounters > 0; i++) {
+ int counter = counters[i];
+ if (counter > maxNarrowCounter) {
+ wideCounters--;
+ // totalWideCountersWidth = 3 * average, so this checks if counter >= 3/2 * average
+ if ((counter * 2) >= totalWideCountersWidth) {
+ return -1;
+ }
+ }
+ }
+ return pattern;
+ }
+ } while (wideCounters > 3);
+ return -1;
+ }
+
+ private static char patternToChar(int pattern) throws NotFoundException {
+ for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
+ if (CHARACTER_ENCODINGS[i] == pattern) {
+ return ALPHABET[i];
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private static String decodeExtended(CharSequence encoded) throws FormatException {
+ int length = encoded.length();
+ StringBuilder decoded = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ char c = encoded.charAt(i);
+ if (c == '+' || c == '$' || c == '%' || c == '/') {
+ char next = encoded.charAt(i + 1);
+ char decodedChar = '\0';
+ switch (c) {
+ case '+':
+ // +A to +Z map to a to z
+ if (next >= 'A' && next <= 'Z') {
+ decodedChar = (char) (next + 32);
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case '$':
+ // $A to $Z map to control codes SH to SB
+ if (next >= 'A' && next <= 'Z') {
+ decodedChar = (char) (next - 64);
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case '%':
+ // %A to %E map to control codes ESC to US
+ if (next >= 'A' && next <= 'E') {
+ decodedChar = (char) (next - 38);
+ } else if (next >= 'F' && next <= 'W') {
+ decodedChar = (char) (next - 11);
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case '/':
+ // /A to /O map to ! to , and /Z maps to :
+ if (next >= 'A' && next <= 'O') {
+ decodedChar = (char) (next - 32);
+ } else if (next == 'Z') {
+ decodedChar = ':';
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ }
+ decoded.append(decodedChar);
+ // bump up i again since we read two characters
+ i++;
+ } else {
+ decoded.append(c);
+ }
+ }
+ return decoded.toString();
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber, BitArray row, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+
+ int[] theCounters = counters;
+ Arrays.fill(theCounters, 0);
+ StringBuilder result = decodeRowResult;
+ result.setLength(0);
+
+ int[] start = findAsteriskPattern(row, theCounters);
+ // Read off white space
+ int nextStart = row.getNextSet(start[1]);
+ int end = row.getSize();
+
+ char decodedChar;
+ int lastStart;
+ do {
+ recordPattern(row, nextStart, theCounters);
+ int pattern = toNarrowWidePattern(theCounters);
+ if (pattern < 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decodedChar = patternToChar(pattern);
+ result.append(decodedChar);
+ lastStart = nextStart;
+ for (int counter : theCounters) {
+ nextStart += counter;
+ }
+ // Read off white space
+ nextStart = row.getNextSet(nextStart);
+ } while (decodedChar != '*');
+ result.setLength(result.length() - 1); // remove asterisk
+
+ // Look for whitespace after pattern:
+ int lastPatternSize = 0;
+ for (int counter : theCounters) {
+ lastPatternSize += counter;
+ }
+ int whiteSpaceAfterEnd = nextStart - lastStart - lastPatternSize;
+ // If 50% of last pattern size, following last pattern, is not whitespace, fail
+ // (but if it's whitespace to the very end of the image, that's OK)
+ if (nextStart != end && (whiteSpaceAfterEnd * 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(decodeRowResult.charAt(i));
+ }
+ if (result.charAt(max) != ALPHABET[total % 43]) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ result.setLength(max);
+ }
+
+ if (result.length() == 0) {
+ // false positive
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String resultString;
+ if (extendedMode) {
+ resultString = decodeExtended(result);
+ } else {
+ resultString = result.toString();
+ }
+
+ float left = (float) (start[1] + start[0]) / 2.0f;
+ float right = lastStart + lastPatternSize / 2.0f;
+ return new Result(
+ resultString,
+ null,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODE_39);
+
+ }
+
+}
diff --git a/src/com/google/zxing/oned/Code39Writer.java b/src/com/google/zxing/oned/Code39Writer.java
new file mode 100644
index 0000000..066f6ed
--- /dev/null
+++ b/src/com/google/zxing/oned/Code39Writer.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * This object renders a CODE39 code as a {@link BitMatrix}.
+ *
+ * @author erik.barbara@gmail.com (Erik Barbara)
+ */
+public final class Code39Writer extends OneDimensionalCodeWriter {
+
+ private static void toIntArray(int a, int[] toReturn) {
+ for (int i = 0; i < 9; i++) {
+ int temp = a & (1 << (8 - i));
+ toReturn[i] = temp == 0 ? 1 : 2;
+ }
+ }
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.CODE_39) {
+ throw new IllegalArgumentException("Can only encode CODE_39, but got " + format);
+ }
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ @Override
+ public boolean[] encode(String contents) {
+ int length = contents.length();
+ if (length > 80) {
+ throw new IllegalArgumentException(
+ "Requested contents should be less than 80 digits long, but got " + length);
+ }
+
+ int[] widths = new int[9];
+ int codeWidth = 24 + 1 + length;
+ for (int i = 0; i < length; i++) {
+ int indexInString = Code39Reader.ALPHABET_STRING.indexOf(contents.charAt(i));
+ if (indexInString < 0) {
+ throw new IllegalArgumentException("Bad contents: " + contents);
+ }
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[indexInString], widths);
+ for (int width : widths) {
+ codeWidth += width;
+ }
+ }
+ boolean[] result = new boolean[codeWidth];
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[39], widths);
+ int pos = appendPattern(result, 0, widths, true);
+ int[] narrowWhite = {1};
+ pos += appendPattern(result, pos, narrowWhite, false);
+ //append next character to byte matrix
+ for (int i = 0; i < length; i++) {
+ int indexInString = Code39Reader.ALPHABET_STRING.indexOf(contents.charAt(i));
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[indexInString], widths);
+ pos += appendPattern(result, pos, widths, true);
+ pos += appendPattern(result, pos, narrowWhite, false);
+ }
+ toIntArray(Code39Reader.CHARACTER_ENCODINGS[39], widths);
+ appendPattern(result, pos, widths, true);
+ return result;
+ }
+
+}
\ 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..873400d
--- /dev/null
+++ b/src/com/google/zxing/oned/Code93Reader.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Decodes Code 93 barcodes.
+ *
+ * @author Sean Owen
+ * @see Code39Reader
+ */
+public final class Code93Reader extends OneDReader {
+
+ // Note that 'abcd' are dummy characters in place of control characters.
+ private static final String ALPHABET_STRING = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%abcd*";
+ private static final char[] ALPHABET = ALPHABET_STRING.toCharArray();
+
+ /**
+ * These represent the encodings of characters, as patterns of wide and narrow bars.
+ * The 9 least-significant bits of each int correspond to the pattern of wide and narrow.
+ */
+ private static final int[] CHARACTER_ENCODINGS = {
+ 0x114, 0x148, 0x144, 0x142, 0x128, 0x124, 0x122, 0x150, 0x112, 0x10A, // 0-9
+ 0x1A8, 0x1A4, 0x1A2, 0x194, 0x192, 0x18A, 0x168, 0x164, 0x162, 0x134, // A-J
+ 0x11A, 0x158, 0x14C, 0x146, 0x12C, 0x116, 0x1B4, 0x1B2, 0x1AC, 0x1A6, // K-T
+ 0x196, 0x19A, 0x16C, 0x166, 0x136, 0x13A, // U-Z
+ 0x12E, 0x1D4, 0x1D2, 0x1CA, 0x16E, 0x176, 0x1AE, // - - %
+ 0x126, 0x1DA, 0x1D6, 0x132, 0x15E, // Control chars? $-*
+ };
+ private static final int ASTERISK_ENCODING = CHARACTER_ENCODINGS[47];
+
+ private final StringBuilder decodeRowResult;
+ private final int[] counters;
+
+ public Code93Reader() {
+ decodeRowResult = new StringBuilder(20);
+ counters = new int[6];
+ }
+
+ private static int toPattern(int[] counters) {
+ int max = counters.length;
+ int sum = 0;
+ for (int counter : counters) {
+ sum += counter;
+ }
+ int pattern = 0;
+ for (int i = 0; i < max; i++) {
+ int scaled = Math.round(counters[i] * 9.0f / sum);
+ if (scaled < 1 || scaled > 4) {
+ return -1;
+ }
+ if ((i & 0x01) == 0) {
+ for (int j = 0; j < scaled; j++) {
+ pattern = (pattern << 1) | 0x01;
+ }
+ } else {
+ pattern <<= scaled;
+ }
+ }
+ return pattern;
+ }
+
+ private static char patternToChar(int pattern) throws NotFoundException {
+ for (int i = 0; i < CHARACTER_ENCODINGS.length; i++) {
+ if (CHARACTER_ENCODINGS[i] == pattern) {
+ return ALPHABET[i];
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private static String decodeExtended(CharSequence encoded) throws FormatException {
+ int length = encoded.length();
+ StringBuilder decoded = new StringBuilder(length);
+ for (int i = 0; i < length; i++) {
+ char c = encoded.charAt(i);
+ if (c >= 'a' && c <= 'd') {
+ if (i >= length - 1) {
+ throw FormatException.getFormatInstance();
+ }
+ char next = encoded.charAt(i + 1);
+ char decodedChar = '\0';
+ switch (c) {
+ case 'd':
+ // +A to +Z map to a to z
+ if (next >= 'A' && next <= 'Z') {
+ decodedChar = (char) (next + 32);
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case 'a':
+ // $A to $Z map to control codes SH to SB
+ if (next >= 'A' && next <= 'Z') {
+ decodedChar = (char) (next - 64);
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case 'b':
+ // %A to %E map to control codes ESC to US
+ if (next >= 'A' && next <= 'E') {
+ decodedChar = (char) (next - 38);
+ } else if (next >= 'F' && next <= 'W') {
+ decodedChar = (char) (next - 11);
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ case 'c':
+ // /A to /O map to ! to , and /Z maps to :
+ if (next >= 'A' && next <= 'O') {
+ decodedChar = (char) (next - 32);
+ } else if (next == 'Z') {
+ decodedChar = ':';
+ } else {
+ throw FormatException.getFormatInstance();
+ }
+ break;
+ }
+ decoded.append(decodedChar);
+ // bump up i again since we read two characters
+ i++;
+ } else {
+ decoded.append(c);
+ }
+ }
+ return decoded.toString();
+ }
+
+ private static void checkChecksums(CharSequence result) throws ChecksumException {
+ int length = result.length();
+ checkOneChecksum(result, length - 2, 20);
+ checkOneChecksum(result, length - 1, 15);
+ }
+
+ private static void checkOneChecksum(CharSequence result, int checkPosition, int weightMax)
+ throws ChecksumException {
+ int weight = 1;
+ int total = 0;
+ for (int i = checkPosition - 1; i >= 0; i--) {
+ total += weight * ALPHABET_STRING.indexOf(result.charAt(i));
+ if (++weight > weightMax) {
+ weight = 1;
+ }
+ }
+ if (result.charAt(checkPosition) != ALPHABET[total % 47]) {
+ throw ChecksumException.getChecksumInstance();
+ }
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber, BitArray row, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+
+ int[] start = findAsteriskPattern(row);
+ // Read off white space
+ int nextStart = row.getNextSet(start[1]);
+ int end = row.getSize();
+
+ int[] theCounters = counters;
+ Arrays.fill(theCounters, 0);
+ StringBuilder result = decodeRowResult;
+ result.setLength(0);
+
+ char decodedChar;
+ int lastStart;
+ do {
+ recordPattern(row, nextStart, theCounters);
+ int pattern = toPattern(theCounters);
+ if (pattern < 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decodedChar = patternToChar(pattern);
+ result.append(decodedChar);
+ lastStart = nextStart;
+ for (int counter : theCounters) {
+ nextStart += counter;
+ }
+ // Read off white space
+ nextStart = row.getNextSet(nextStart);
+ } while (decodedChar != '*');
+ result.deleteCharAt(result.length() - 1); // remove asterisk
+
+ int lastPatternSize = 0;
+ for (int counter : theCounters) {
+ lastPatternSize += counter;
+ }
+
+ // Should be at least one more black module
+ if (nextStart == end || !row.get(nextStart)) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ if (result.length() < 2) {
+ // false positive -- need at least 2 checksum digits
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ checkChecksums(result);
+ // Remove checksum digits
+ result.setLength(result.length() - 2);
+
+ String resultString = decodeExtended(result);
+
+ float left = (float) (start[1] + start[0]) / 2.0f;
+ float right = lastStart + lastPatternSize / 2.0f;
+ return new Result(
+ resultString,
+ null,
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ BarcodeFormat.CODE_93);
+
+ }
+
+ private int[] findAsteriskPattern(BitArray row) throws NotFoundException {
+ int width = row.getSize();
+ int rowOffset = row.getNextSet(0);
+
+ Arrays.fill(counters, 0);
+ int[] theCounters = counters;
+ int patternStart = rowOffset;
+ boolean isWhite = false;
+ int patternLength = theCounters.length;
+
+ int counterPosition = 0;
+ for (int i = rowOffset; i < width; i++) {
+ if (row.get(i) ^ isWhite) {
+ theCounters[counterPosition]++;
+ } else {
+ if (counterPosition == patternLength - 1) {
+ if (toPattern(theCounters) == ASTERISK_ENCODING) {
+ return new int[]{patternStart, i};
+ }
+ patternStart += theCounters[0] + theCounters[1];
+ System.arraycopy(theCounters, 2, theCounters, 0, patternLength - 2);
+ theCounters[patternLength - 2] = 0;
+ theCounters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ theCounters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+}
diff --git a/src/com/google/zxing/oned/EAN13Reader.java b/src/com/google/zxing/oned/EAN13Reader.java
new file mode 100644
index 0000000..11ba4a7
--- /dev/null
+++ b/src/com/google/zxing/oned/EAN13Reader.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * Implements decoding of the EAN-13 format.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ * @author alasdair@google.com (Alasdair Mackintosh)
+ */
+public final class EAN13Reader extends UPCEANReader {
+
+ // For an EAN-13 barcode, the first digit is represented by the parities used
+ // to encode the next six digits, according to the table below. For example,
+ // if the barcode is 5 123456 789012 then the value of the first digit is
+ // signified by using odd for '1', even for '2', even for '3', odd for '4',
+ // odd for '5', and even for '6'. See http://en.wikipedia.org/wiki/EAN-13
+ //
+ // Parity of next 6 digits
+ // Digit 0 1 2 3 4 5
+ // 0 Odd Odd Odd Odd Odd Odd
+ // 1 Odd Odd Even Odd Even Even
+ // 2 Odd Odd Even Even Odd Even
+ // 3 Odd Odd Even Even Even Odd
+ // 4 Odd Even Odd Odd Even Even
+ // 5 Odd Even Even Odd Odd Even
+ // 6 Odd Even Even Even Odd Odd
+ // 7 Odd Even Odd Even Odd Even
+ // 8 Odd Even Odd Even Even Odd
+ // 9 Odd Even Even Odd Even Odd
+ //
+ // Note that the encoding for '0' uses the same parity as a UPC barcode. Hence
+ // a UPC barcode can be converted to an EAN-13 barcode by prepending a 0.
+ //
+ // The encoding is represented by the following array, which is a bit pattern
+ // using Odd = 0 and Even = 1. For example, 5 is represented by:
+ //
+ // Odd Even Even Odd Odd Even
+ // in binary:
+ // 0 1 1 0 0 1 == 0x19
+ //
+ static final int[] FIRST_DIGIT_ENCODINGS = {
+ 0x00, 0x0B, 0x0D, 0xE, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A
+ };
+
+ private final int[] decodeMiddleCounters;
+
+ public EAN13Reader() {
+ decodeMiddleCounters = new int[4];
+ }
+
+ /**
+ * Based on pattern of odd-even ('L' and 'G') patterns used to encoded the explicitly-encoded
+ * digits in a barcode, determines the implicitly encoded first digit and adds it to the
+ * result string.
+ *
+ * @param resultString string to insert decoded first digit into
+ * @param lgPatternFound int whose bits indicates the pattern of odd/even L/G patterns used to
+ * encode digits
+ * @throws NotFoundException if first digit cannot be determined
+ */
+ private static void determineFirstDigit(StringBuilder resultString, int lgPatternFound)
+ throws NotFoundException {
+ for (int d = 0; d < 10; d++) {
+ if (lgPatternFound == FIRST_DIGIT_ENCODINGS[d]) {
+ resultString.insert(0, (char) ('0' + d));
+ return;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ @Override
+ protected int decodeMiddle(BitArray row,
+ int[] startRange,
+ StringBuilder resultString) throws NotFoundException {
+ int[] counters = decodeMiddleCounters;
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+ int end = row.getSize();
+ int rowOffset = startRange[1];
+
+ int lgPatternFound = 0;
+
+ for (int x = 0; x < 6 && rowOffset < end; x++) {
+ int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS);
+ resultString.append((char) ('0' + bestMatch % 10));
+ for (int counter : counters) {
+ rowOffset += counter;
+ }
+ if (bestMatch >= 10) {
+ lgPatternFound |= 1 << (5 - x);
+ }
+ }
+
+ determineFirstDigit(resultString, lgPatternFound);
+
+ int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
+ rowOffset = middleRange[1];
+
+ for (int x = 0; x < 6 && rowOffset < end; x++) {
+ int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
+ resultString.append((char) ('0' + bestMatch));
+ for (int counter : counters) {
+ rowOffset += counter;
+ }
+ }
+
+ return rowOffset;
+ }
+
+ @Override
+ BarcodeFormat getBarcodeFormat() {
+ return BarcodeFormat.EAN_13;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/EAN13Writer.java b/src/com/google/zxing/oned/EAN13Writer.java
new file mode 100644
index 0000000..9288f3f
--- /dev/null
+++ b/src/com/google/zxing/oned/EAN13Writer.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.FormatException;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * This object renders an EAN13 code as a {@link BitMatrix}.
+ *
+ * @author aripollak@gmail.com (Ari Pollak)
+ */
+public final class EAN13Writer extends UPCEANWriter {
+
+ private static final int CODE_WIDTH = 3 + // start guard
+ (7 * 6) + // left bars
+ 5 + // middle guard
+ (7 * 6) + // right bars
+ 3; // end guard
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.EAN_13) {
+ throw new IllegalArgumentException("Can only encode EAN_13, but got " + format);
+ }
+
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ @Override
+ public boolean[] encode(String contents) {
+ if (contents.length() != 13) {
+ throw new IllegalArgumentException(
+ "Requested contents should be 13 digits long, but got " + contents.length());
+ }
+ try {
+ if (!UPCEANReader.checkStandardUPCEANChecksum(contents)) {
+ throw new IllegalArgumentException("Contents do not pass checksum");
+ }
+ } catch (FormatException ignored) {
+ throw new IllegalArgumentException("Illegal contents");
+ }
+
+ int firstDigit = Integer.parseInt(contents.substring(0, 1));
+ int parities = EAN13Reader.FIRST_DIGIT_ENCODINGS[firstDigit];
+ boolean[] result = new boolean[CODE_WIDTH];
+ int pos = 0;
+
+ pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
+
+ // See {@link #EAN13Reader} for a description of how the first digit & left bars are encoded
+ for (int i = 1; i <= 6; i++) {
+ int digit = Integer.parseInt(contents.substring(i, i + 1));
+ if ((parities >> (6 - i) & 1) == 1) {
+ digit += 10;
+ }
+ pos += appendPattern(result, pos, UPCEANReader.L_AND_G_PATTERNS[digit], false);
+ }
+
+ pos += appendPattern(result, pos, UPCEANReader.MIDDLE_PATTERN, false);
+
+ for (int i = 7; i <= 12; i++) {
+ int digit = Integer.parseInt(contents.substring(i, i + 1));
+ pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], true);
+ }
+ appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
+
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/EAN8Reader.java b/src/com/google/zxing/oned/EAN8Reader.java
new file mode 100644
index 0000000..51cdeb8
--- /dev/null
+++ b/src/com/google/zxing/oned/EAN8Reader.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * Implements decoding of the EAN-8 format.
+ *
+ * @author Sean Owen
+ */
+public final class EAN8Reader extends UPCEANReader {
+
+ private final int[] decodeMiddleCounters;
+
+ public EAN8Reader() {
+ decodeMiddleCounters = new int[4];
+ }
+
+ @Override
+ protected int decodeMiddle(BitArray row,
+ int[] startRange,
+ StringBuilder result) throws NotFoundException {
+ int[] counters = decodeMiddleCounters;
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+ int end = row.getSize();
+ int rowOffset = startRange[1];
+
+ for (int x = 0; x < 4 && rowOffset < end; x++) {
+ int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
+ result.append((char) ('0' + bestMatch));
+ for (int counter : counters) {
+ rowOffset += counter;
+ }
+ }
+
+ int[] middleRange = findGuardPattern(row, rowOffset, true, MIDDLE_PATTERN);
+ rowOffset = middleRange[1];
+
+ for (int x = 0; x < 4 && rowOffset < end; x++) {
+ int bestMatch = decodeDigit(row, counters, rowOffset, L_PATTERNS);
+ result.append((char) ('0' + bestMatch));
+ for (int counter : counters) {
+ rowOffset += counter;
+ }
+ }
+
+ return rowOffset;
+ }
+
+ @Override
+ BarcodeFormat getBarcodeFormat() {
+ return BarcodeFormat.EAN_8;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/EAN8Writer.java b/src/com/google/zxing/oned/EAN8Writer.java
new file mode 100644
index 0000000..a79a6dd
--- /dev/null
+++ b/src/com/google/zxing/oned/EAN8Writer.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * This object renders an EAN8 code as a {@link BitMatrix}.
+ *
+ * @author aripollak@gmail.com (Ari Pollak)
+ */
+public final class EAN8Writer extends UPCEANWriter {
+
+ private static final int CODE_WIDTH = 3 + // start guard
+ (7 * 4) + // left bars
+ 5 + // middle guard
+ (7 * 4) + // right bars
+ 3; // end guard
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.EAN_8) {
+ throw new IllegalArgumentException("Can only encode EAN_8, but got "
+ + format);
+ }
+
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ /**
+ * @return a byte array of horizontal pixels (false = white, true = black)
+ */
+ @Override
+ public boolean[] encode(String contents) {
+ if (contents.length() != 8) {
+ throw new IllegalArgumentException(
+ "Requested contents should be 8 digits long, but got " + contents.length());
+ }
+
+ boolean[] result = new boolean[CODE_WIDTH];
+ int pos = 0;
+
+ pos += appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
+
+ for (int i = 0; i <= 3; i++) {
+ int digit = Integer.parseInt(contents.substring(i, i + 1));
+ pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], false);
+ }
+
+ pos += appendPattern(result, pos, UPCEANReader.MIDDLE_PATTERN, false);
+
+ for (int i = 4; i <= 7; i++) {
+ int digit = Integer.parseInt(contents.substring(i, i + 1));
+ pos += appendPattern(result, pos, UPCEANReader.L_PATTERNS[digit], true);
+ }
+ appendPattern(result, pos, UPCEANReader.START_END_PATTERN, true);
+
+ return result;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/EANManufacturerOrgSupport.java b/src/com/google/zxing/oned/EANManufacturerOrgSupport.java
new file mode 100644
index 0000000..d3fc0c0
--- /dev/null
+++ b/src/com/google/zxing/oned/EANManufacturerOrgSupport.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Records EAN prefix to GS1 Member Organization, where the member organization
+ * correlates strongly with a country. This is an imperfect means of identifying
+ * a country of origin by EAN-13 barcode value. See
+ *
+ * http://en.wikipedia.org/wiki/List_of_GS1_country_codes.
+ *
+ * @author Sean Owen
+ */
+final class EANManufacturerOrgSupport {
+
+ private final List ranges = new ArrayList<>();
+ private final List countryIdentifiers = new ArrayList<>();
+
+ String lookupCountryIdentifier(String productCode) {
+ initIfNeeded();
+ int prefix = Integer.parseInt(productCode.substring(0, 3));
+ int max = ranges.size();
+ for (int i = 0; i < max; i++) {
+ int[] range = ranges.get(i);
+ int start = range[0];
+ if (prefix < start) {
+ return null;
+ }
+ int end = range.length == 1 ? start : range[1];
+ if (prefix <= end) {
+ return countryIdentifiers.get(i);
+ }
+ }
+ return null;
+ }
+
+ private void add(int[] range, String id) {
+ ranges.add(range);
+ countryIdentifiers.add(id);
+ }
+
+ private synchronized void initIfNeeded() {
+ if (!ranges.isEmpty()) {
+ return;
+ }
+ add(new int[]{0, 19}, "US/CA");
+ add(new int[]{30, 39}, "US");
+ add(new int[]{60, 139}, "US/CA");
+ add(new int[]{300, 379}, "FR");
+ add(new int[]{380}, "BG");
+ add(new int[]{383}, "SI");
+ add(new int[]{385}, "HR");
+ add(new int[]{387}, "BA");
+ add(new int[]{400, 440}, "DE");
+ add(new int[]{450, 459}, "JP");
+ add(new int[]{460, 469}, "RU");
+ add(new int[]{471}, "TW");
+ add(new int[]{474}, "EE");
+ add(new int[]{475}, "LV");
+ add(new int[]{476}, "AZ");
+ add(new int[]{477}, "LT");
+ add(new int[]{478}, "UZ");
+ add(new int[]{479}, "LK");
+ add(new int[]{480}, "PH");
+ add(new int[]{481}, "BY");
+ add(new int[]{482}, "UA");
+ add(new int[]{484}, "MD");
+ add(new int[]{485}, "AM");
+ add(new int[]{486}, "GE");
+ add(new int[]{487}, "KZ");
+ add(new int[]{489}, "HK");
+ add(new int[]{490, 499}, "JP");
+ add(new int[]{500, 509}, "GB");
+ add(new int[]{520}, "GR");
+ add(new int[]{528}, "LB");
+ add(new int[]{529}, "CY");
+ add(new int[]{531}, "MK");
+ add(new int[]{535}, "MT");
+ add(new int[]{539}, "IE");
+ add(new int[]{540, 549}, "BE/LU");
+ add(new int[]{560}, "PT");
+ add(new int[]{569}, "IS");
+ add(new int[]{570, 579}, "DK");
+ add(new int[]{590}, "PL");
+ add(new int[]{594}, "RO");
+ add(new int[]{599}, "HU");
+ add(new int[]{600, 601}, "ZA");
+ add(new int[]{603}, "GH");
+ add(new int[]{608}, "BH");
+ add(new int[]{609}, "MU");
+ add(new int[]{611}, "MA");
+ add(new int[]{613}, "DZ");
+ add(new int[]{616}, "KE");
+ add(new int[]{618}, "CI");
+ add(new int[]{619}, "TN");
+ add(new int[]{621}, "SY");
+ add(new int[]{622}, "EG");
+ add(new int[]{624}, "LY");
+ add(new int[]{625}, "JO");
+ add(new int[]{626}, "IR");
+ add(new int[]{627}, "KW");
+ add(new int[]{628}, "SA");
+ add(new int[]{629}, "AE");
+ add(new int[]{640, 649}, "FI");
+ add(new int[]{690, 695}, "CN");
+ add(new int[]{700, 709}, "NO");
+ add(new int[]{729}, "IL");
+ add(new int[]{730, 739}, "SE");
+ add(new int[]{740}, "GT");
+ add(new int[]{741}, "SV");
+ add(new int[]{742}, "HN");
+ add(new int[]{743}, "NI");
+ add(new int[]{744}, "CR");
+ add(new int[]{745}, "PA");
+ add(new int[]{746}, "DO");
+ add(new int[]{750}, "MX");
+ add(new int[]{754, 755}, "CA");
+ add(new int[]{759}, "VE");
+ add(new int[]{760, 769}, "CH");
+ add(new int[]{770}, "CO");
+ add(new int[]{773}, "UY");
+ add(new int[]{775}, "PE");
+ add(new int[]{777}, "BO");
+ add(new int[]{779}, "AR");
+ add(new int[]{780}, "CL");
+ add(new int[]{784}, "PY");
+ add(new int[]{785}, "PE");
+ add(new int[]{786}, "EC");
+ add(new int[]{789, 790}, "BR");
+ add(new int[]{800, 839}, "IT");
+ add(new int[]{840, 849}, "ES");
+ add(new int[]{850}, "CU");
+ add(new int[]{858}, "SK");
+ add(new int[]{859}, "CZ");
+ add(new int[]{860}, "YU");
+ add(new int[]{865}, "MN");
+ add(new int[]{867}, "KP");
+ add(new int[]{868, 869}, "TR");
+ add(new int[]{870, 879}, "NL");
+ add(new int[]{880}, "KR");
+ add(new int[]{885}, "TH");
+ add(new int[]{888}, "SG");
+ add(new int[]{890}, "IN");
+ add(new int[]{893}, "VN");
+ add(new int[]{896}, "PK");
+ add(new int[]{899}, "ID");
+ add(new int[]{900, 919}, "AT");
+ add(new int[]{930, 939}, "AU");
+ add(new int[]{940, 949}, "AZ");
+ add(new int[]{955}, "MY");
+ add(new int[]{958}, "MO");
+ }
+
+}
diff --git a/src/com/google/zxing/oned/ITFReader.java b/src/com/google/zxing/oned/ITFReader.java
new file mode 100644
index 0000000..c5c0ffe
--- /dev/null
+++ b/src/com/google/zxing/oned/ITFReader.java
@@ -0,0 +1,350 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.Map;
+
+/**
+ * Implements decoding of the ITF format, or Interleaved Two of Five.
+ *
+ *
This Reader will scan ITF barcodes of certain lengths only.
+ * At the moment it reads length 6, 8, 10, 12, 14, 16, 18, 20, 24, and 44 as these have appeared "in the wild". Not all
+ * lengths are scanned, especially shorter ones, to avoid false positives. This in turn is due to a lack of
+ * required checksum function.
+ *
+ *
The checksum is optional and is not applied by this Reader. The consumer of the decoded
+ * value will have to apply a checksum if required.
+ *
+ *
http://en.wikipedia.org/wiki/Interleaved_2_of_5
+ * is a great reference for Interleaved 2 of 5 information.
+ *
+ * @author kevin.osullivan@sita.aero, SITA Lab.
+ */
+public final class ITFReader extends OneDReader {
+
+ private static final float MAX_AVG_VARIANCE = 0.38f;
+ private static final float MAX_INDIVIDUAL_VARIANCE = 0.78f;
+
+ private static final int W = 3; // Pixel width of a wide line
+ private static final int N = 1; // Pixed width of a narrow line
+ /**
+ * 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
+ };
+ /**
+ * Valid ITF lengths. Anything longer than the largest value is also allowed.
+ */
+ private static final int[] DEFAULT_ALLOWED_LENGTHS = {6, 8, 10, 12, 14};
+ /**
+ * 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};
+ // Stores the actual narrow line width of the image being decoded.
+ private int narrowLineWidth = -1;
+
+ /**
+ * @param row row of black/white values to search
+ * @param payloadStart offset of start pattern
+ * @param resultString {@link StringBuilder} to append decoded chars to
+ * @throws NotFoundException if decoding could not complete successfully
+ */
+ private static void decodeMiddle(BitArray row,
+ int payloadStart,
+ int payloadEnd,
+ StringBuilder resultString) throws NotFoundException {
+
+ // Digits are interleaved in pairs - 5 black lines for one digit, and the
+ // 5
+ // interleaved white lines for the second digit.
+ // Therefore, need to scan 10 lines and then
+ // split these into two arrays
+ int[] counterDigitPair = new int[10];
+ int[] counterBlack = new int[5];
+ int[] counterWhite = new int[5];
+
+ while (payloadStart < payloadEnd) {
+
+ // Get 10 runs of black/white.
+ recordPattern(row, payloadStart, counterDigitPair);
+ // Split them into each array
+ for (int k = 0; k < 5; k++) {
+ int twoK = 2 * k;
+ counterBlack[k] = counterDigitPair[twoK];
+ counterWhite[k] = counterDigitPair[twoK + 1];
+ }
+
+ int bestMatch = decodeDigit(counterBlack);
+ resultString.append((char) ('0' + bestMatch));
+ bestMatch = decodeDigit(counterWhite);
+ resultString.append((char) ('0' + bestMatch));
+
+ for (int counterDigit : counterDigitPair) {
+ payloadStart += counterDigit;
+ }
+ }
+ }
+
+ /**
+ * Skip all whitespace until we get to the first black line.
+ *
+ * @param row row of black/white values to search
+ * @return index of the first black line.
+ * @throws NotFoundException Throws exception if no black lines are found in the row
+ */
+ private static int skipWhiteSpace(BitArray row) throws NotFoundException {
+ int width = row.getSize();
+ int endStart = row.getNextSet(0);
+ if (endStart == width) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ return endStart;
+ }
+
+ /**
+ * @param row row of black/white values to search
+ * @param rowOffset position to start search
+ * @param pattern pattern of counts of number of black and white pixels that are
+ * being searched for as a pattern
+ * @return start/end horizontal offset of guard pattern, as an array of two
+ * ints
+ * @throws NotFoundException if pattern is not found
+ */
+ private static int[] findGuardPattern(BitArray row,
+ int rowOffset,
+ int[] pattern) throws NotFoundException {
+
+ // TODO: This is very similar to implementation in UPCEANReader. Consider if they can be
+ // merged to a single method.
+ int patternLength = pattern.length;
+ int[] counters = new int[patternLength];
+ int width = row.getSize();
+ boolean isWhite = false;
+
+ int counterPosition = 0;
+ int patternStart = rowOffset;
+ for (int x = rowOffset; x < width; x++) {
+ if (row.get(x) ^ isWhite) {
+ counters[counterPosition]++;
+ } else {
+ if (counterPosition == patternLength - 1) {
+ if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
+ return new int[]{patternStart, x};
+ }
+ patternStart += counters[0] + counters[1];
+ System.arraycopy(counters, 2, counters, 0, patternLength - 2);
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /**
+ * Attempts to decode a sequence of ITF black/white lines into single
+ * digit.
+ *
+ * @param counters the counts of runs of observed black/white/black/... values
+ * @return The decoded digit
+ * @throws NotFoundException if digit cannot be decoded
+ */
+ private static int decodeDigit(int[] counters) throws NotFoundException {
+ float 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];
+ float variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
+ if (variance < bestVariance) {
+ bestVariance = variance;
+ bestMatch = i;
+ }
+ }
+ if (bestMatch >= 0) {
+ return bestMatch;
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber, BitArray row, Map hints)
+ throws FormatException, NotFoundException {
+
+ // Find out where the Middle section (payload) starts & ends
+ int[] startRange = decodeStart(row);
+ int[] endRange = decodeEnd(row);
+
+ StringBuilder result = new StringBuilder(20);
+ decodeMiddle(row, startRange[1], endRange[0], result);
+ String resultString = result.toString();
+
+ int[] allowedLengths = null;
+ if (hints != null) {
+ allowedLengths = (int[]) hints.get(DecodeHintType.ALLOWED_LENGTHS);
+
+ }
+ if (allowedLengths == null) {
+ allowedLengths = DEFAULT_ALLOWED_LENGTHS;
+ }
+
+ // To avoid false positives with 2D barcodes (and other patterns), make
+ // an assumption that the decoded string must be a 'standard' length if it's short
+ int length = resultString.length();
+ boolean lengthOK = false;
+ int maxAllowedLength = 0;
+ for (int allowedLength : allowedLengths) {
+ if (length == allowedLength) {
+ lengthOK = true;
+ break;
+ }
+ if (allowedLength > maxAllowedLength) {
+ maxAllowedLength = allowedLength;
+ }
+ }
+ if (!lengthOK && length > maxAllowedLength) {
+ lengthOK = true;
+ }
+ if (!lengthOK) {
+ throw FormatException.getFormatInstance();
+ }
+
+ return new Result(
+ resultString,
+ null, // no natural byte representation for these barcodes
+ new ResultPoint[]{new ResultPoint(startRange[1], (float) rowNumber),
+ new ResultPoint(endRange[0], (float) rowNumber)},
+ BarcodeFormat.ITF);
+ }
+
+ /**
+ * 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]) / 4;
+
+ validateQuietZone(row, startPattern[0]);
+
+ return startPattern;
+ }
+
+ /**
+ * The start & end patterns must be pre/post fixed by a quiet zone. This
+ * zone must be at least 10 times the width of a narrow line. Scan back until
+ * we either get to the start of the barcode or match the necessary number of
+ * quiet zone pixels.
+ *
+ * Note: Its assumed the row is reversed when using this method to find
+ * quiet zone after the end pattern.
+ *
+ * ref: http://www.barcode-1.net/i25code.html
+ *
+ * @param row bit array representing the scanned barcode.
+ * @param startPattern index into row of the start or end pattern.
+ * @throws NotFoundException if the quiet zone cannot be found, a ReaderException is thrown.
+ */
+ private void validateQuietZone(BitArray row, int startPattern) throws NotFoundException {
+
+ int quietCount = this.narrowLineWidth * 10; // expect to find this many pixels of quiet zone
+
+ // if there are not so many pixel at all let's try as many as possible
+ quietCount = quietCount < startPattern ? quietCount : startPattern;
+
+ for (int i = startPattern - 1; quietCount > 0 && i >= 0; i--) {
+ if (row.get(i)) {
+ break;
+ }
+ quietCount--;
+ }
+ if (quietCount != 0) {
+ // Unable to find the necessary number of quiet zone pixels.
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ /**
+ * 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();
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/oned/ITFWriter.java b/src/com/google/zxing/oned/ITFWriter.java
new file mode 100644
index 0000000..957e4c3
--- /dev/null
+++ b/src/com/google/zxing/oned/ITFWriter.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * This object renders a ITF code as a {@link BitMatrix}.
+ *
+ * @author erik.barbara@gmail.com (Erik Barbara)
+ */
+public final class ITFWriter extends OneDimensionalCodeWriter {
+
+ private static final int[] START_PATTERN = {1, 1, 1, 1};
+ private static final int[] END_PATTERN = {3, 1, 1};
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.ITF) {
+ throw new IllegalArgumentException("Can only encode ITF, but got " + format);
+ }
+
+ return super.encode(contents, format, width, height, hints);
+ }
+
+ @Override
+ public boolean[] encode(String contents) {
+ int length = contents.length();
+ if (length % 2 != 0) {
+ throw new IllegalArgumentException("The lenght of the input should be even");
+ }
+ if (length > 80) {
+ throw new IllegalArgumentException(
+ "Requested contents should be less than 80 digits long, but got " + length);
+ }
+ boolean[] result = new boolean[9 + 9 * length];
+ int pos = appendPattern(result, 0, START_PATTERN, true);
+ for (int i = 0; i < length; i += 2) {
+ int one = Character.digit(contents.charAt(i), 10);
+ int two = Character.digit(contents.charAt(i + 1), 10);
+ int[] encoding = new int[18];
+ for (int j = 0; j < 5; j++) {
+ encoding[2 * j] = ITFReader.PATTERNS[one][j];
+ encoding[2 * j + 1] = ITFReader.PATTERNS[two][j];
+ }
+ pos += appendPattern(result, pos, encoding, true);
+ }
+ appendPattern(result, pos, END_PATTERN, true);
+
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/src/com/google/zxing/oned/MultiFormatOneDReader.java b/src/com/google/zxing/oned/MultiFormatOneDReader.java
new file mode 100644
index 0000000..49b5b25
--- /dev/null
+++ b/src/com/google/zxing/oned/MultiFormatOneDReader.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.oned;
+
+import com.google.zxing.*;
+import com.google.zxing.common.BitArray;
+import com.google.zxing.oned.rss.RSS14Reader;
+import com.google.zxing.oned.rss.expanded.RSSExpandedReader;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ */
+public final class MultiFormatOneDReader extends OneDReader {
+
+ private final OneDReader[] readers;
+
+ public MultiFormatOneDReader(Map hints) {
+ @SuppressWarnings("unchecked")
+ Collection possibleFormats = hints == null ? null :
+ (Collection) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ boolean useCode39CheckDigit = hints != null &&
+ hints.get(DecodeHintType.ASSUME_CODE_39_CHECK_DIGIT) != null;
+ Collection readers = new ArrayList<>();
+ if (possibleFormats != null) {
+ if (possibleFormats.contains(BarcodeFormat.EAN_13) ||
+ possibleFormats.contains(BarcodeFormat.UPC_A) ||
+ possibleFormats.contains(BarcodeFormat.EAN_8) ||
+ possibleFormats.contains(BarcodeFormat.UPC_E)) {
+ readers.add(new MultiFormatUPCEANReader(hints));
+ }
+ if (possibleFormats.contains(BarcodeFormat.CODE_39)) {
+ readers.add(new Code39Reader(useCode39CheckDigit));
+ }
+ if (possibleFormats.contains(BarcodeFormat.CODE_93)) {
+ readers.add(new Code93Reader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.CODE_128)) {
+ readers.add(new Code128Reader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.ITF)) {
+ readers.add(new ITFReader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.CODABAR)) {
+ readers.add(new CodaBarReader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.RSS_14)) {
+ readers.add(new RSS14Reader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.RSS_EXPANDED)) {
+ readers.add(new RSSExpandedReader());
+ }
+ }
+ if (readers.isEmpty()) {
+ readers.add(new MultiFormatUPCEANReader(hints));
+ readers.add(new Code39Reader());
+ readers.add(new CodaBarReader());
+ readers.add(new Code93Reader());
+ readers.add(new Code128Reader());
+ readers.add(new ITFReader());
+ readers.add(new RSS14Reader());
+ readers.add(new RSSExpandedReader());
+ }
+ this.readers = readers.toArray(new OneDReader[readers.size()]);
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ Map hints) throws NotFoundException {
+ for (OneDReader reader : readers) {
+ try {
+ return reader.decodeRow(rowNumber, row, hints);
+ } catch (ReaderException re) {
+ // continue
+ }
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ @Override
+ public void reset() {
+ for (Reader reader : readers) {
+ reader.reset();
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/oned/MultiFormatUPCEANReader.java b/src/com/google/zxing/oned/MultiFormatUPCEANReader.java
new file mode 100644
index 0000000..028f105
--- /dev/null
+++ b/src/com/google/zxing/oned/MultiFormatUPCEANReader.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * A reader that can read all available UPC/EAN formats. If a caller wants to try to
+ * read all such formats, it is most efficient to use this implementation rather than invoke
+ * individual readers.
+ *
+ * @author Sean Owen
+ */
+public final class MultiFormatUPCEANReader extends OneDReader {
+
+ private final UPCEANReader[] readers;
+
+ public MultiFormatUPCEANReader(Map hints) {
+ @SuppressWarnings("unchecked")
+ Collection possibleFormats = hints == null ? null :
+ (Collection) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ Collection readers = new ArrayList<>();
+ if (possibleFormats != null) {
+ if (possibleFormats.contains(BarcodeFormat.EAN_13)) {
+ readers.add(new EAN13Reader());
+ } else if (possibleFormats.contains(BarcodeFormat.UPC_A)) {
+ readers.add(new UPCAReader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.EAN_8)) {
+ readers.add(new EAN8Reader());
+ }
+ if (possibleFormats.contains(BarcodeFormat.UPC_E)) {
+ readers.add(new UPCEReader());
+ }
+ }
+ if (readers.isEmpty()) {
+ readers.add(new EAN13Reader());
+ // UPC-A is covered by EAN-13
+ readers.add(new EAN8Reader());
+ readers.add(new UPCEReader());
+ }
+ this.readers = readers.toArray(new UPCEANReader[readers.size()]);
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ Map hints) throws NotFoundException {
+ // Compute this location once and reuse it on multiple implementations
+ int[] startGuardPattern = UPCEANReader.findStartGuardPattern(row);
+ for (UPCEANReader reader : readers) {
+ Result result;
+ try {
+ result = reader.decodeRow(rowNumber, row, startGuardPattern, hints);
+ } catch (ReaderException ignored) {
+ continue;
+ }
+ // Special case: a 12-digit code encoded in UPC-A is identical to a "0"
+ // followed by those 12 digits encoded as EAN-13. Each will recognize such a code,
+ // UPC-A as a 12-digit string and EAN-13 as a 13-digit string starting with "0".
+ // Individually these are correct and their readers will both read such a code
+ // and correctly call it EAN-13, or UPC-A, respectively.
+ //
+ // In this case, if we've been looking for both types, we'd like to call it
+ // a UPC-A code. But for efficiency we only run the EAN-13 decoder to also read
+ // UPC-A. So we special case it here, and convert an EAN-13 result to a UPC-A
+ // result if appropriate.
+ //
+ // But, don't return UPC-A if UPC-A was not a requested format!
+ boolean ean13MayBeUPCA =
+ result.getBarcodeFormat() == BarcodeFormat.EAN_13 &&
+ result.getText().charAt(0) == '0';
+ @SuppressWarnings("unchecked")
+ Collection possibleFormats =
+ hints == null ? null : (Collection) hints.get(DecodeHintType.POSSIBLE_FORMATS);
+ boolean canReturnUPCA = possibleFormats == null || possibleFormats.contains(BarcodeFormat.UPC_A);
+
+ if (ean13MayBeUPCA && canReturnUPCA) {
+ // Transfer the metdata across
+ Result resultUPCA = new Result(result.getText().substring(1),
+ result.getRawBytes(),
+ result.getResultPoints(),
+ BarcodeFormat.UPC_A);
+ resultUPCA.putAllMetadata(result.getResultMetadata());
+ return resultUPCA;
+ }
+ return result;
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ @Override
+ public void reset() {
+ for (Reader reader : readers) {
+ reader.reset();
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/oned/OneDReader.java b/src/com/google/zxing/oned/OneDReader.java
new file mode 100644
index 0000000..fce3e06
--- /dev/null
+++ b/src/com/google/zxing/oned/OneDReader.java
@@ -0,0 +1,288 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.Arrays;
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * Encapsulates functionality and implementation that is common to all families
+ * of one-dimensional barcodes.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ */
+public abstract class OneDReader implements Reader {
+
+ /**
+ * Records the size of successive runs of white and black pixels in a row, starting at a given point.
+ * The values are recorded in the given array, and the number of runs recorded is equal to the size
+ * of the array. If the row starts on a white pixel at the given start point, then the first count
+ * recorded is the run of white pixels starting from that point; likewise it is the count of a run
+ * of black pixels if the row begin on a black pixels at that point.
+ *
+ * @param row row to count from
+ * @param start offset into row to start at
+ * @param counters array into which to record counts
+ * @throws NotFoundException if counters cannot be filled entirely from row before running out
+ * of pixels
+ */
+ protected static void recordPattern(BitArray row,
+ int start,
+ int[] counters) throws NotFoundException {
+ int numCounters = counters.length;
+ Arrays.fill(counters, 0, numCounters, 0);
+ int end = row.getSize();
+ if (start >= end) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ boolean isWhite = !row.get(start);
+ int counterPosition = 0;
+ int i = start;
+ while (i < end) {
+ if (row.get(i) ^ isWhite) { // that is, exactly one is true
+ counters[counterPosition]++;
+ } else {
+ counterPosition++;
+ if (counterPosition == numCounters) {
+ break;
+ } else {
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ i++;
+ }
+ // If we read fully the last section of pixels and filled up our counters -- or filled
+ // the last counter but ran off the side of the image, OK. Otherwise, a problem.
+ if (!(counterPosition == numCounters || (counterPosition == numCounters - 1 && i == end))) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ protected static void recordPatternInReverse(BitArray row, int start, int[] counters)
+ throws NotFoundException {
+ // This could be more efficient I guess
+ int numTransitionsLeft = counters.length;
+ boolean last = row.get(start);
+ while (start > 0 && numTransitionsLeft >= 0) {
+ if (row.get(--start) != last) {
+ numTransitionsLeft--;
+ last = !last;
+ }
+ }
+ if (numTransitionsLeft >= 0) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ recordPattern(row, start + 1, counters);
+ }
+
+ /**
+ * Determines how closely a set of observed counts of runs of black/white values matches a given
+ * target pattern. This is reported as the ratio of the total variance from the expected pattern
+ * proportions across all pattern elements, to the length of the pattern.
+ *
+ * @param counters observed counters
+ * @param pattern expected pattern
+ * @param maxIndividualVariance The most any counter can differ before we give up
+ * @return ratio of total variance between counters and pattern compared to total pattern size
+ */
+ protected static float patternMatchVariance(int[] counters,
+ int[] pattern,
+ float 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 Float.POSITIVE_INFINITY;
+ }
+
+ float unitBarWidth = (float) total / patternLength;
+ maxIndividualVariance *= unitBarWidth;
+
+ float totalVariance = 0.0f;
+ for (int x = 0; x < numCounters; x++) {
+ int counter = counters[x];
+ float scaledPattern = pattern[x] * unitBarWidth;
+ float variance = counter > scaledPattern ? counter - scaledPattern : scaledPattern - counter;
+ if (variance > maxIndividualVariance) {
+ return Float.POSITIVE_INFINITY;
+ }
+ totalVariance += variance;
+ }
+ return totalVariance / total;
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
+ return decode(image, null);
+ }
+
+ // Note that we don't try rotation without the try harder flag, even if rotation was supported.
+ @Override
+ public Result decode(BinaryBitmap image,
+ Map hints) throws NotFoundException, FormatException {
+ try {
+ return doDecode(image, hints);
+ } catch (NotFoundException nfe) {
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ if (tryHarder && image.isRotateSupported()) {
+ BinaryBitmap rotatedImage = image.rotateCounterClockwise();
+ Result result = doDecode(rotatedImage, hints);
+ // Record that we found it rotated 90 degrees CCW / 270 degrees CW
+ Map metadata = result.getResultMetadata();
+ int orientation = 270;
+ if (metadata != null && metadata.containsKey(ResultMetadataType.ORIENTATION)) {
+ // But if we found it reversed in doDecode(), add in that result here:
+ orientation = (orientation +
+ (Integer) metadata.get(ResultMetadataType.ORIENTATION)) % 360;
+ }
+ result.putMetadata(ResultMetadataType.ORIENTATION, orientation);
+ // Update result points
+ ResultPoint[] points = result.getResultPoints();
+ if (points != null) {
+ int height = rotatedImage.getHeight();
+ for (int i = 0; i < points.length; i++) {
+ points[i] = new ResultPoint(height - points[i].getY() - 1, points[i].getX());
+ }
+ }
+ return result;
+ } else {
+ throw nfe;
+ }
+ }
+ }
+
+ @Override
+ public void reset() {
+ // do nothing
+ }
+
+ /**
+ * We're going to examine rows from the middle outward, searching alternately above and below the
+ * middle, and farther out each time. rowStep is the number of rows between each successive
+ * attempt above and below the middle. So we'd scan row middle, then middle - rowStep, then
+ * middle + rowStep, then middle - (2 * rowStep), etc.
+ * rowStep is bigger as the image is taller, but is always at least 1. We've somewhat arbitrarily
+ * decided that moving up and down by about 1/16 of the image is pretty good; we try more of the
+ * image if "trying harder".
+ *
+ * @param image The image to decode
+ * @param hints Any hints that were requested
+ * @return The contents of the decoded barcode
+ * @throws NotFoundException Any spontaneous errors which occur
+ */
+ private Result doDecode(BinaryBitmap image,
+ Map hints) throws NotFoundException {
+ int width = image.getWidth();
+ int height = image.getHeight();
+ BitArray row = new BitArray(width);
+
+ int middle = height >> 1;
+ boolean tryHarder = hints != null && hints.containsKey(DecodeHintType.TRY_HARDER);
+ int rowStep = Math.max(1, height >> (tryHarder ? 8 : 5));
+ int maxLines;
+ if (tryHarder) {
+ maxLines = height; // Look at the whole image, not just the center
+ } else {
+ maxLines = 15; // 15 rows spaced 1/32 apart is roughly the middle half of the image
+ }
+
+ for (int x = 0; x < maxLines; x++) {
+
+ // Scanning from the middle out. Determine which row we're looking at next:
+ int rowStepsAboveOrBelow = (x + 1) / 2;
+ boolean isAbove = (x & 0x01) == 0; // i.e. is x even?
+ int rowNumber = middle + rowStep * (isAbove ? rowStepsAboveOrBelow : -rowStepsAboveOrBelow);
+ if (rowNumber < 0 || rowNumber >= height) {
+ // Oops, if we run off the top or bottom, stop
+ break;
+ }
+
+ // Estimate black point for this row and load it:
+ try {
+ row = image.getBlackRow(rowNumber, row);
+ } catch (NotFoundException ignored) {
+ continue;
+ }
+
+ // While we have the image data in a BitArray, it's fairly cheap to reverse it in place to
+ // handle decoding upside down barcodes.
+ for (int attempt = 0; attempt < 2; attempt++) {
+ if (attempt == 1) { // trying again?
+ row.reverse(); // reverse the row and continue
+ // This means we will only ever draw result points *once* in the life of this method
+ // since we want to avoid drawing the wrong points after flipping the row, and,
+ // don't want to clutter with noise from every single row scan -- just the scans
+ // that start on the center line.
+ if (hints != null && hints.containsKey(DecodeHintType.NEED_RESULT_POINT_CALLBACK)) {
+ Map newHints = new EnumMap<>(DecodeHintType.class);
+ newHints.putAll(hints);
+ newHints.remove(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+ hints = newHints;
+ }
+ }
+ try {
+ // Look for a barcode
+ Result result = decodeRow(rowNumber, row, hints);
+ // We found our barcode
+ if (attempt == 1) {
+ // But it was upside down, so note that
+ result.putMetadata(ResultMetadataType.ORIENTATION, 180);
+ // And remember to flip the result points horizontally.
+ ResultPoint[] points = result.getResultPoints();
+ if (points != null) {
+ points[0] = new ResultPoint(width - points[0].getX() - 1, points[0].getY());
+ points[1] = new ResultPoint(width - points[1].getX() - 1, points[1].getY());
+ }
+ }
+ return result;
+ } catch (ReaderException re) {
+ // continue -- just couldn't decode this row
+ }
+ }
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /**
+ * Attempts to decode a one-dimensional barcode format given a single row of
+ * an image.
+ *
+ * @param rowNumber row number from top of the row
+ * @param row the black/white pixel data of the row
+ * @param hints decode hints
+ * @return {@link Result} containing encoded string and start/end of barcode
+ * @throws NotFoundException if no potential barcode is found
+ * @throws ChecksumException if a potential barcode is found but does not pass its checksum
+ * @throws FormatException if a potential barcode is found but format is invalid
+ */
+ public abstract Result decodeRow(int rowNumber, BitArray row, Map hints)
+ throws NotFoundException, ChecksumException, FormatException;
+
+}
diff --git a/src/com/google/zxing/oned/OneDimensionalCodeWriter.java b/src/com/google/zxing/oned/OneDimensionalCodeWriter.java
new file mode 100644
index 0000000..ed369af
--- /dev/null
+++ b/src/com/google/zxing/oned/OneDimensionalCodeWriter.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2011 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.Writer;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * Encapsulates functionality and implementation that is common to one-dimensional barcodes.
+ *
+ * @author dsbnatut@gmail.com (Kazuki Nishiura)
+ */
+public abstract class OneDimensionalCodeWriter implements Writer {
+
+ /**
+ * @return a byte array of horizontal pixels (0 = white, 1 = black)
+ */
+ private static BitMatrix renderResult(boolean[] code, int width, int height, int sidesMargin) {
+ int inputWidth = code.length;
+ // Add quiet zone on both sides.
+ int fullWidth = inputWidth + sidesMargin;
+ int outputWidth = Math.max(width, fullWidth);
+ int outputHeight = Math.max(1, height);
+
+ int multiple = outputWidth / fullWidth;
+ int leftPadding = (outputWidth - (inputWidth * multiple)) / 2;
+
+ BitMatrix output = new BitMatrix(outputWidth, outputHeight);
+ for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) {
+ if (code[inputX]) {
+ output.setRegion(outputX, 0, multiple, outputHeight);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * @param target encode black/white pattern into this array
+ * @param pos position to start encoding at in {@code target}
+ * @param pattern lengths of black/white runs to encode
+ * @param startColor starting color - false for white, true for black
+ * @return the number of elements added to target.
+ */
+ protected static int appendPattern(boolean[] target, int pos, int[] pattern, boolean startColor) {
+ boolean color = startColor;
+ int numAdded = 0;
+ for (int len : pattern) {
+ for (int j = 0; j < len; j++) {
+ target[pos++] = color;
+ }
+ numAdded += len;
+ color = !color; // flip color after each segment
+ }
+ return numAdded;
+ }
+
+ @Override
+ public final BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
+ throws WriterException {
+ return encode(contents, format, width, height, null);
+ }
+
+ /**
+ * Encode the contents following specified format.
+ * {@code width} and {@code height} are required size. This method may return bigger size
+ * {@code BitMatrix} when specified size is too small. The user can set both {@code width} and
+ * {@code height} to zero to get minimum size barcode. If negative value is set to {@code width}
+ * or {@code height}, {@code IllegalArgumentException} is thrown.
+ */
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (contents.isEmpty()) {
+ throw new IllegalArgumentException("Found empty contents");
+ }
+
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException("Negative size is not allowed. Input: "
+ + width + 'x' + height);
+ }
+
+ int sidesMargin = getDefaultMargin();
+ if (hints != null) {
+ Integer sidesMarginInt = (Integer) hints.get(EncodeHintType.MARGIN);
+ if (sidesMarginInt != null) {
+ sidesMargin = sidesMarginInt;
+ }
+ }
+
+ boolean[] code = encode(contents);
+ return renderResult(code, width, height, sidesMargin);
+ }
+
+ public int getDefaultMargin() {
+ // CodaBar spec requires a side margin to be more than ten times wider than narrow space.
+ // This seems like a decent idea for a default for all formats.
+ return 10;
+ }
+
+ /**
+ * Encode the contents to boolean array expression of one-dimensional barcode.
+ * Start code and end code should be included in result, and side margins should not be included.
+ *
+ * @param contents barcode contents to encode
+ * @return a {@code boolean[]} of horizontal pixels (false = white, true = black)
+ */
+ public abstract boolean[] encode(String contents);
+}
+
diff --git a/src/com/google/zxing/oned/UPCAReader.java b/src/com/google/zxing/oned/UPCAReader.java
new file mode 100644
index 0000000..f54d45a
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCAReader.java
@@ -0,0 +1,80 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.Map;
+
+/**
+ * Implements decoding of the UPC-A format.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ */
+public final class UPCAReader extends UPCEANReader {
+
+ private final UPCEANReader ean13Reader = new EAN13Reader();
+
+ 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();
+ }
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ int[] startGuardRange,
+ Map hints)
+ throws NotFoundException, FormatException, ChecksumException {
+ return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, startGuardRange, hints));
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber, BitArray row, Map hints)
+ throws NotFoundException, FormatException, ChecksumException {
+ return maybeReturnResult(ean13Reader.decodeRow(rowNumber, row, hints));
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, FormatException {
+ return maybeReturnResult(ean13Reader.decode(image));
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints)
+ throws NotFoundException, FormatException {
+ return maybeReturnResult(ean13Reader.decode(image, hints));
+ }
+
+ @Override
+ BarcodeFormat getBarcodeFormat() {
+ return BarcodeFormat.UPC_A;
+ }
+
+ @Override
+ protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString)
+ throws NotFoundException {
+ return ean13Reader.decodeMiddle(row, startRange, resultString);
+ }
+
+}
diff --git a/src/com/google/zxing/oned/UPCAWriter.java b/src/com/google/zxing/oned/UPCAWriter.java
new file mode 100644
index 0000000..88c7410
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCAWriter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.Writer;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+
+import java.util.Map;
+
+/**
+ * This object renders a UPC-A code as a {@link BitMatrix}.
+ *
+ * @author qwandor@google.com (Andrew Walbran)
+ */
+public final class UPCAWriter implements Writer {
+
+ private final EAN13Writer subWriter = new EAN13Writer();
+
+ /**
+ * Transform a UPC-A code into the equivalent EAN-13 code, and add a check digit if it is not
+ * already present.
+ */
+ private static String preencode(String contents) {
+ int length = contents.length();
+ if (length == 11) {
+ // No check digit present, calculate it and add it
+ int sum = 0;
+ for (int i = 0; i < 11; ++i) {
+ sum += (contents.charAt(i) - '0') * (i % 2 == 0 ? 3 : 1);
+ }
+ contents += (1000 - sum) % 10;
+ } else if (length != 12) {
+ throw new IllegalArgumentException(
+ "Requested contents should be 11 or 12 digits long, but got " + contents.length());
+ }
+ return '0' + contents;
+ }
+
+ @Override
+ public BitMatrix encode(String contents, BarcodeFormat format, int width, int height)
+ throws WriterException {
+ return encode(contents, format, width, height, null);
+ }
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map hints) throws WriterException {
+ if (format != BarcodeFormat.UPC_A) {
+ throw new IllegalArgumentException("Can only encode UPC-A, but got " + format);
+ }
+ return subWriter.encode(preencode(contents), BarcodeFormat.EAN_13, width, height, hints);
+ }
+}
diff --git a/src/com/google/zxing/oned/UPCEANExtension2Support.java b/src/com/google/zxing/oned/UPCEANExtension2Support.java
new file mode 100644
index 0000000..d717c9e
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEANExtension2Support.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2012 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * @see UPCEANExtension5Support
+ */
+final class UPCEANExtension2Support {
+
+ private final int[] decodeMiddleCounters = new int[4];
+ private final StringBuilder decodeRowStringBuffer = new StringBuilder();
+
+ /**
+ * @param raw raw content of extension
+ * @return formatted interpretation of raw content as a {@link Map} mapping
+ * one {@link ResultMetadataType} to appropriate value, or {@code null} if not known
+ */
+ private static Map parseExtensionString(String raw) {
+ if (raw.length() != 2) {
+ return null;
+ }
+ Map result = new EnumMap<>(ResultMetadataType.class);
+ result.put(ResultMetadataType.ISSUE_NUMBER, Integer.valueOf(raw));
+ return result;
+ }
+
+ Result decodeRow(int rowNumber, BitArray row, int[] extensionStartRange) throws NotFoundException {
+
+ StringBuilder result = decodeRowStringBuffer;
+ result.setLength(0);
+ int end = decodeMiddle(row, extensionStartRange, result);
+
+ String resultString = result.toString();
+ Map extensionData = parseExtensionString(resultString);
+
+ Result extensionResult =
+ new Result(resultString,
+ null,
+ new ResultPoint[]{
+ new ResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0f, (float) rowNumber),
+ new ResultPoint((float) end, (float) rowNumber),
+ },
+ BarcodeFormat.UPC_EAN_EXTENSION);
+ if (extensionData != null) {
+ extensionResult.putAllMetadata(extensionData);
+ }
+ return extensionResult;
+ }
+
+ int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) throws NotFoundException {
+ int[] counters = decodeMiddleCounters;
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+ int end = row.getSize();
+ int rowOffset = startRange[1];
+
+ int checkParity = 0;
+
+ for (int x = 0; x < 2 && rowOffset < end; x++) {
+ int bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_AND_G_PATTERNS);
+ resultString.append((char) ('0' + bestMatch % 10));
+ for (int counter : counters) {
+ rowOffset += counter;
+ }
+ if (bestMatch >= 10) {
+ checkParity |= 1 << (1 - x);
+ }
+ if (x != 1) {
+ // Read off separator if not last
+ rowOffset = row.getNextSet(rowOffset);
+ rowOffset = row.getNextUnset(rowOffset);
+ }
+ }
+
+ if (resultString.length() != 2) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ if (Integer.parseInt(resultString.toString()) % 4 != checkParity) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ return rowOffset;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/UPCEANExtension5Support.java b/src/com/google/zxing/oned/UPCEANExtension5Support.java
new file mode 100644
index 0000000..5e42b7b
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEANExtension5Support.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.EnumMap;
+import java.util.Map;
+
+/**
+ * @see UPCEANExtension2Support
+ */
+final class UPCEANExtension5Support {
+
+ private static final int[] CHECK_DIGIT_ENCODINGS = {
+ 0x18, 0x14, 0x12, 0x11, 0x0C, 0x06, 0x03, 0x0A, 0x09, 0x05
+ };
+
+ private final int[] decodeMiddleCounters = new int[4];
+ private final StringBuilder decodeRowStringBuffer = new StringBuilder();
+
+ private static int extensionChecksum(CharSequence s) {
+ int length = s.length();
+ int sum = 0;
+ for (int i = length - 2; i >= 0; i -= 2) {
+ sum += (int) s.charAt(i) - (int) '0';
+ }
+ sum *= 3;
+ for (int i = length - 1; i >= 0; i -= 2) {
+ sum += (int) s.charAt(i) - (int) '0';
+ }
+ sum *= 3;
+ return sum % 10;
+ }
+
+ private static int determineCheckDigit(int lgPatternFound)
+ throws NotFoundException {
+ for (int d = 0; d < 10; d++) {
+ if (lgPatternFound == CHECK_DIGIT_ENCODINGS[d]) {
+ return d;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /**
+ * @param raw raw content of extension
+ * @return formatted interpretation of raw content as a {@link Map} mapping
+ * one {@link ResultMetadataType} to appropriate value, or {@code null} if not known
+ */
+ private static Map parseExtensionString(String raw) {
+ if (raw.length() != 5) {
+ return null;
+ }
+ Object value = parseExtension5String(raw);
+ if (value == null) {
+ return null;
+ }
+ Map result = new EnumMap<>(ResultMetadataType.class);
+ result.put(ResultMetadataType.SUGGESTED_PRICE, value);
+ return result;
+ }
+
+ private static String parseExtension5String(String raw) {
+ String currency;
+ switch (raw.charAt(0)) {
+ case '0':
+ currency = "£";
+ break;
+ case '5':
+ currency = "$";
+ break;
+ case '9':
+ // Reference: http://www.jollytech.com
+ if ("90000".equals(raw)) {
+ // No suggested retail price
+ return null;
+ }
+ if ("99991".equals(raw)) {
+ // Complementary
+ return "0.00";
+ }
+ if ("99990".equals(raw)) {
+ return "Used";
+ }
+ // Otherwise... unknown currency?
+ currency = "";
+ break;
+ default:
+ currency = "";
+ break;
+ }
+ int rawAmount = Integer.parseInt(raw.substring(1));
+ String unitsString = String.valueOf(rawAmount / 100);
+ int hundredths = rawAmount % 100;
+ String hundredthsString = hundredths < 10 ? "0" + hundredths : String.valueOf(hundredths);
+ return currency + unitsString + '.' + hundredthsString;
+ }
+
+ Result decodeRow(int rowNumber, BitArray row, int[] extensionStartRange) throws NotFoundException {
+
+ StringBuilder result = decodeRowStringBuffer;
+ result.setLength(0);
+ int end = decodeMiddle(row, extensionStartRange, result);
+
+ String resultString = result.toString();
+ Map extensionData = parseExtensionString(resultString);
+
+ Result extensionResult =
+ new Result(resultString,
+ null,
+ new ResultPoint[]{
+ new ResultPoint((extensionStartRange[0] + extensionStartRange[1]) / 2.0f, (float) rowNumber),
+ new ResultPoint((float) end, (float) rowNumber),
+ },
+ BarcodeFormat.UPC_EAN_EXTENSION);
+ if (extensionData != null) {
+ extensionResult.putAllMetadata(extensionData);
+ }
+ return extensionResult;
+ }
+
+ int decodeMiddle(BitArray row, int[] startRange, StringBuilder resultString) throws NotFoundException {
+ int[] counters = decodeMiddleCounters;
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+ int end = row.getSize();
+ int rowOffset = startRange[1];
+
+ int lgPatternFound = 0;
+
+ for (int x = 0; x < 5 && rowOffset < end; x++) {
+ int bestMatch = UPCEANReader.decodeDigit(row, counters, rowOffset, UPCEANReader.L_AND_G_PATTERNS);
+ resultString.append((char) ('0' + bestMatch % 10));
+ for (int counter : counters) {
+ rowOffset += counter;
+ }
+ if (bestMatch >= 10) {
+ lgPatternFound |= 1 << (4 - x);
+ }
+ if (x != 4) {
+ // Read off separator if not last
+ rowOffset = row.getNextSet(rowOffset);
+ rowOffset = row.getNextUnset(rowOffset);
+ }
+ }
+
+ if (resultString.length() != 5) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int checkDigit = determineCheckDigit(lgPatternFound);
+ if (extensionChecksum(resultString.toString()) != checkDigit) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ return rowOffset;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/UPCEANExtensionSupport.java b/src/com/google/zxing/oned/UPCEANExtensionSupport.java
new file mode 100644
index 0000000..6b76545
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEANExtensionSupport.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.ReaderException;
+import com.google.zxing.Result;
+import com.google.zxing.common.BitArray;
+
+final class UPCEANExtensionSupport {
+
+ private static final int[] EXTENSION_START_PATTERN = {1, 1, 2};
+
+ private final UPCEANExtension2Support twoSupport = new UPCEANExtension2Support();
+ private final UPCEANExtension5Support fiveSupport = new UPCEANExtension5Support();
+
+ Result decodeRow(int rowNumber, BitArray row, int rowOffset) throws NotFoundException {
+ int[] extensionStartRange = UPCEANReader.findGuardPattern(row, rowOffset, false, EXTENSION_START_PATTERN);
+ try {
+ return fiveSupport.decodeRow(rowNumber, row, extensionStartRange);
+ } catch (ReaderException ignored) {
+ return twoSupport.decodeRow(rowNumber, row, extensionStartRange);
+ }
+ }
+
+}
diff --git a/src/com/google/zxing/oned/UPCEANReader.java b/src/com/google/zxing/oned/UPCEANReader.java
new file mode 100644
index 0000000..293d1fa
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEANReader.java
@@ -0,0 +1,380 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * Encapsulates functionality and implementation that is common to UPC and EAN families
+ * of one-dimensional barcodes.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
+ * @author Sean Owen
+ * @author alasdair@google.com (Alasdair Mackintosh)
+ */
+public abstract class UPCEANReader extends OneDReader {
+
+ /**
+ * Start/end guard pattern.
+ */
+ static final int[] START_END_PATTERN = {1, 1, 1,};
+ /**
+ * Pattern marking the middle of a UPC/EAN pattern, separating the two halves.
+ */
+ static final int[] MIDDLE_PATTERN = {1, 1, 1, 1, 1};
+ /**
+ * "Odd", or "L" patterns used to encode UPC/EAN digits.
+ */
+ static final int[][] L_PATTERNS = {
+ {3, 2, 1, 1}, // 0
+ {2, 2, 2, 1}, // 1
+ {2, 1, 2, 2}, // 2
+ {1, 4, 1, 1}, // 3
+ {1, 1, 3, 2}, // 4
+ {1, 2, 3, 1}, // 5
+ {1, 1, 1, 4}, // 6
+ {1, 3, 1, 2}, // 7
+ {1, 2, 1, 3}, // 8
+ {3, 1, 1, 2} // 9
+ };
+ /**
+ * As above but also including the "even", or "G" patterns used to encode UPC/EAN digits.
+ */
+ static final int[][] L_AND_G_PATTERNS;
+ static {
+ L_AND_G_PATTERNS = new int[20][];
+ System.arraycopy(L_PATTERNS, 0, L_AND_G_PATTERNS, 0, 10);
+ for (int i = 10; i < 20; i++) {
+ int[] widths = L_PATTERNS[i - 10];
+ int[] reversedWidths = new int[widths.length];
+ for (int j = 0; j < widths.length; j++) {
+ reversedWidths[j] = widths[widths.length - j - 1];
+ }
+ L_AND_G_PATTERNS[i] = reversedWidths;
+ }
+ }
+ // 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 float MAX_AVG_VARIANCE = 0.48f;
+ private static final float MAX_INDIVIDUAL_VARIANCE = 0.7f;
+ private final StringBuilder decodeRowStringBuffer;
+ private final UPCEANExtensionSupport extensionReader;
+ private final EANManufacturerOrgSupport eanManSupport;
+
+ protected UPCEANReader() {
+ decodeRowStringBuffer = new StringBuilder(20);
+ extensionReader = new UPCEANExtensionSupport();
+ eanManSupport = new EANManufacturerOrgSupport();
+ }
+
+ static int[] findStartGuardPattern(BitArray row) throws NotFoundException {
+ boolean foundStart = false;
+ int[] startRange = null;
+ int nextStart = 0;
+ int[] counters = new int[START_END_PATTERN.length];
+ while (!foundStart) {
+ Arrays.fill(counters, 0, START_END_PATTERN.length, 0);
+ startRange = findGuardPattern(row, nextStart, false, START_END_PATTERN, counters);
+ int start = startRange[0];
+ nextStart = startRange[1];
+ // Make sure there is a quiet zone at least as big as the start pattern before the barcode.
+ // If this check would run off the left edge of the image, do not accept this barcode,
+ // as it is very likely to be a false positive.
+ int quietStart = start - (nextStart - start);
+ if (quietStart >= 0) {
+ foundStart = row.isRange(quietStart, start, false);
+ }
+ }
+ return startRange;
+ }
+
+ /**
+ * Computes the UPC/EAN checksum on a string of digits, and reports
+ * whether the checksum is correct or not.
+ *
+ * @param s string of digits to check
+ * @return true iff string of digits passes the UPC/EAN checksum algorithm
+ * @throws FormatException if the string does not contain only digits
+ */
+ static boolean checkStandardUPCEANChecksum(CharSequence s) throws FormatException {
+ int length = s.length();
+ if (length == 0) {
+ return false;
+ }
+
+ int sum = 0;
+ for (int i = length - 2; i >= 0; i -= 2) {
+ int digit = (int) s.charAt(i) - (int) '0';
+ if (digit < 0 || digit > 9) {
+ throw FormatException.getFormatInstance();
+ }
+ sum += digit;
+ }
+ sum *= 3;
+ for (int i = length - 1; i >= 0; i -= 2) {
+ int digit = (int) s.charAt(i) - (int) '0';
+ if (digit < 0 || digit > 9) {
+ throw FormatException.getFormatInstance();
+ }
+ sum += digit;
+ }
+ return sum % 10 == 0;
+ }
+
+ static int[] findGuardPattern(BitArray row,
+ int rowOffset,
+ boolean whiteFirst,
+ int[] pattern) throws NotFoundException {
+ return findGuardPattern(row, rowOffset, whiteFirst, pattern, new int[pattern.length]);
+ }
+
+ /**
+ * @param row row of black/white values to search
+ * @param rowOffset position to start search
+ * @param whiteFirst if true, indicates that the pattern specifies white/black/white/...
+ * pixel counts, otherwise, it is interpreted as black/white/black/...
+ * @param pattern pattern of counts of number of black and white pixels that are being
+ * searched for as a pattern
+ * @param counters array of counters, as long as pattern, to re-use
+ * @return start/end horizontal offset of guard pattern, as an array of two ints
+ * @throws NotFoundException if pattern is not found
+ */
+ private static int[] findGuardPattern(BitArray row,
+ int rowOffset,
+ boolean whiteFirst,
+ int[] pattern,
+ int[] counters) throws NotFoundException {
+ int patternLength = pattern.length;
+ int width = row.getSize();
+ boolean isWhite = whiteFirst;
+ rowOffset = whiteFirst ? row.getNextUnset(rowOffset) : row.getNextSet(rowOffset);
+ int counterPosition = 0;
+ int patternStart = rowOffset;
+ for (int x = rowOffset; x < width; x++) {
+ if (row.get(x) ^ isWhite) {
+ counters[counterPosition]++;
+ } else {
+ if (counterPosition == patternLength - 1) {
+ if (patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE) < MAX_AVG_VARIANCE) {
+ return new int[]{patternStart, x};
+ }
+ patternStart += counters[0] + counters[1];
+ System.arraycopy(counters, 2, counters, 0, patternLength - 2);
+ counters[patternLength - 2] = 0;
+ counters[patternLength - 1] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /**
+ * Attempts to decode a single UPC/EAN-encoded digit.
+ *
+ * @param row row of black/white values to decode
+ * @param counters the counts of runs of observed black/white/black/... values
+ * @param rowOffset horizontal offset to start decoding from
+ * @param patterns the set of patterns to use to decode -- sometimes different encodings
+ * for the digits 0-9 are used, and this indicates the encodings for 0 to 9 that should
+ * be used
+ * @return horizontal offset of first pixel beyond the decoded digit
+ * @throws NotFoundException if digit cannot be decoded
+ */
+ static int decodeDigit(BitArray row, int[] counters, int rowOffset, int[][] patterns)
+ throws NotFoundException {
+ recordPattern(row, rowOffset, counters);
+ float 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];
+ float variance = patternMatchVariance(counters, pattern, MAX_INDIVIDUAL_VARIANCE);
+ if (variance < bestVariance) {
+ bestVariance = variance;
+ bestMatch = i;
+ }
+ }
+ if (bestMatch >= 0) {
+ return bestMatch;
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber, BitArray row, Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+ return decodeRow(rowNumber, row, findStartGuardPattern(row), hints);
+ }
+
+ /**
+ * Like {@link #decodeRow(int, BitArray, Map)}, but
+ * allows caller to inform method about where the UPC/EAN start pattern is
+ * found. This allows this to be computed once and reused across many implementations.
+ *
+ * @param rowNumber row index into the image
+ * @param row encoding of the row of the barcode image
+ * @param startGuardRange start/end column where the opening start pattern was found
+ * @param hints optional hints that influence decoding
+ * @return {@link Result} encapsulating the result of decoding a barcode in the row
+ * @throws NotFoundException if no potential barcode is found
+ * @throws ChecksumException if a potential barcode is found but does not pass its checksum
+ * @throws FormatException if a potential barcode is found but format is invalid
+ */
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ int[] startGuardRange,
+ Map hints)
+ throws NotFoundException, ChecksumException, FormatException {
+
+ ResultPointCallback resultPointCallback = hints == null ? null :
+ (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+
+ if (resultPointCallback != null) {
+ resultPointCallback.foundPossibleResultPoint(new ResultPoint(
+ (startGuardRange[0] + startGuardRange[1]) / 2.0f, rowNumber
+ ));
+ }
+
+ StringBuilder result = decodeRowStringBuffer;
+ result.setLength(0);
+ int endStart = decodeMiddle(row, startGuardRange, result);
+
+ if (resultPointCallback != null) {
+ resultPointCallback.foundPossibleResultPoint(new ResultPoint(
+ endStart, rowNumber
+ ));
+ }
+
+ int[] endRange = decodeEnd(row, endStart);
+
+ if (resultPointCallback != null) {
+ resultPointCallback.foundPossibleResultPoint(new ResultPoint(
+ (endRange[0] + endRange[1]) / 2.0f, rowNumber
+ ));
+ }
+
+
+ // Make sure there is a quiet zone at least as big as the end pattern after the barcode. The
+ // spec might want more whitespace, but in practice this is the maximum we can count on.
+ int end = endRange[1];
+ int quietEnd = end + (end - endRange[0]);
+ if (quietEnd >= row.getSize() || !row.isRange(end, quietEnd, false)) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String resultString = result.toString();
+ // UPC/EAN should never be less than 8 chars anyway
+ if (resultString.length() < 8) {
+ throw FormatException.getFormatInstance();
+ }
+ if (!checkChecksum(resultString)) {
+ throw ChecksumException.getChecksumInstance();
+ }
+
+ float left = (float) (startGuardRange[1] + startGuardRange[0]) / 2.0f;
+ float right = (float) (endRange[1] + endRange[0]) / 2.0f;
+ BarcodeFormat format = getBarcodeFormat();
+ Result decodeResult = new Result(resultString,
+ null, // no natural byte representation for these barcodes
+ new ResultPoint[]{
+ new ResultPoint(left, (float) rowNumber),
+ new ResultPoint(right, (float) rowNumber)},
+ format);
+
+ int extensionLength = 0;
+
+ try {
+ Result extensionResult = extensionReader.decodeRow(rowNumber, row, endRange[1]);
+ decodeResult.putMetadata(ResultMetadataType.UPC_EAN_EXTENSION, extensionResult.getText());
+ decodeResult.putAllMetadata(extensionResult.getResultMetadata());
+ decodeResult.addResultPoints(extensionResult.getResultPoints());
+ extensionLength = extensionResult.getText().length();
+ } catch (ReaderException re) {
+ // continue
+ }
+
+ int[] allowedExtensions =
+ hints == null ? null : (int[]) hints.get(DecodeHintType.ALLOWED_EAN_EXTENSIONS);
+ if (allowedExtensions != null) {
+ boolean valid = false;
+ for (int length : allowedExtensions) {
+ if (extensionLength == length) {
+ valid = true;
+ break;
+ }
+ }
+ if (!valid) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ if (format == BarcodeFormat.EAN_13 || format == BarcodeFormat.UPC_A) {
+ String countryID = eanManSupport.lookupCountryIdentifier(resultString);
+ if (countryID != null) {
+ decodeResult.putMetadata(ResultMetadataType.POSSIBLE_COUNTRY, countryID);
+ }
+ }
+
+ return decodeResult;
+ }
+
+ /**
+ * @param s string of digits to check
+ * @return {@link #checkStandardUPCEANChecksum(CharSequence)}
+ * @throws FormatException if the string does not contain only digits
+ */
+ boolean checkChecksum(String s) throws FormatException {
+ return checkStandardUPCEANChecksum(s);
+ }
+
+ int[] decodeEnd(BitArray row, int endStart) throws NotFoundException {
+ return findGuardPattern(row, endStart, false, START_END_PATTERN);
+ }
+
+ /**
+ * Get the format of this decoder.
+ *
+ * @return The 1D format.
+ */
+ abstract BarcodeFormat getBarcodeFormat();
+
+ /**
+ * Subclasses override this to decode the portion of a barcode between the start
+ * and end guard patterns.
+ *
+ * @param row row of black/white values to search
+ * @param startRange start/end offset of start guard pattern
+ * @param resultString {@link StringBuilder} to append decoded chars to
+ * @return horizontal offset of first pixel after the "middle" that was decoded
+ * @throws NotFoundException if decoding could not complete successfully
+ */
+ protected abstract int decodeMiddle(BitArray row,
+ int[] startRange,
+ StringBuilder resultString) throws NotFoundException;
+
+}
diff --git a/src/com/google/zxing/oned/UPCEANWriter.java b/src/com/google/zxing/oned/UPCEANWriter.java
new file mode 100644
index 0000000..8772890
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEANWriter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+/**
+ * Encapsulates functionality and implementation that is common to UPC and EAN families
+ * of one-dimensional barcodes.
+ *
+ * @author aripollak@gmail.com (Ari Pollak)
+ * @author dsbnatut@gmail.com (Kazuki Nishiura)
+ */
+public abstract class UPCEANWriter extends OneDimensionalCodeWriter {
+
+ @Override
+ public int getDefaultMargin() {
+ // Use a different default more appropriate for UPC/EAN
+ return UPCEANReader.START_END_PATTERN.length;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/UPCEReader.java b/src/com/google/zxing/oned/UPCEReader.java
new file mode 100644
index 0000000..f986e7e
--- /dev/null
+++ b/src/com/google/zxing/oned/UPCEReader.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2008 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * Implements decoding of the UPC-E format.
+ * This is a great reference for
+ * UPC-E information.
+ *
+ * @author Sean Owen
+ */
+public final class UPCEReader extends UPCEANReader {
+
+ /**
+ * The pattern that marks the middle, and end, of a UPC-E pattern.
+ * There is no "second half" to a UPC-E barcode.
+ */
+ private static final int[] MIDDLE_END_PATTERN = {1, 1, 1, 1, 1, 1};
+
+ /**
+ * See {@link #L_AND_G_PATTERNS}; these values similarly represent patterns of
+ * even-odd parity encodings of digits that imply both the number system (0 or 1)
+ * used, and the check digit.
+ */
+ private static final int[][] NUMSYS_AND_CHECK_DIGIT_PATTERNS = {
+ {0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25},
+ {0x07, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A}
+ };
+
+ private final int[] decodeMiddleCounters;
+
+ public UPCEReader() {
+ decodeMiddleCounters = new int[4];
+ }
+
+ private static void determineNumSysAndCheckDigit(StringBuilder resultString, int lgPatternFound)
+ throws NotFoundException {
+
+ for (int numSys = 0; numSys <= 1; numSys++) {
+ for (int d = 0; d < 10; d++) {
+ if (lgPatternFound == NUMSYS_AND_CHECK_DIGIT_PATTERNS[numSys][d]) {
+ resultString.insert(0, (char) ('0' + numSys));
+ resultString.append((char) ('0' + d));
+ return;
+ }
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ /**
+ * Expands a UPC-E value back into its full, equivalent UPC-A code value.
+ *
+ * @param upce UPC-E code as string of digits
+ * @return equivalent UPC-A code as string of digits
+ */
+ public static String convertUPCEtoUPCA(String upce) {
+ char[] upceChars = new char[6];
+ upce.getChars(1, 7, upceChars, 0);
+ StringBuilder result = new StringBuilder(12);
+ result.append(upce.charAt(0));
+ char lastChar = upceChars[5];
+ switch (lastChar) {
+ case '0':
+ case '1':
+ case '2':
+ result.append(upceChars, 0, 2);
+ result.append(lastChar);
+ result.append("0000");
+ result.append(upceChars, 2, 3);
+ break;
+ case '3':
+ result.append(upceChars, 0, 3);
+ result.append("00000");
+ result.append(upceChars, 3, 2);
+ break;
+ case '4':
+ result.append(upceChars, 0, 4);
+ result.append("00000");
+ result.append(upceChars[4]);
+ break;
+ default:
+ result.append(upceChars, 0, 5);
+ result.append("0000");
+ result.append(lastChar);
+ break;
+ }
+ result.append(upce.charAt(7));
+ return result.toString();
+ }
+
+ @Override
+ protected int decodeMiddle(BitArray row, int[] startRange, StringBuilder result)
+ throws NotFoundException {
+ int[] counters = decodeMiddleCounters;
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+ int end = row.getSize();
+ int rowOffset = startRange[1];
+
+ int lgPatternFound = 0;
+
+ for (int x = 0; x < 6 && rowOffset < end; x++) {
+ int bestMatch = decodeDigit(row, counters, rowOffset, L_AND_G_PATTERNS);
+ result.append((char) ('0' + bestMatch % 10));
+ for (int counter : counters) {
+ rowOffset += counter;
+ }
+ if (bestMatch >= 10) {
+ lgPatternFound |= 1 << (5 - x);
+ }
+ }
+
+ determineNumSysAndCheckDigit(result, lgPatternFound);
+
+ return rowOffset;
+ }
+
+ @Override
+ protected int[] decodeEnd(BitArray row, int endStart) throws NotFoundException {
+ return findGuardPattern(row, endStart, true, MIDDLE_END_PATTERN);
+ }
+
+ @Override
+ protected boolean checkChecksum(String s) throws FormatException {
+ return super.checkChecksum(convertUPCEtoUPCA(s));
+ }
+
+ @Override
+ BarcodeFormat getBarcodeFormat() {
+ return BarcodeFormat.UPC_E;
+ }
+
+}
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..d87fc4f
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/AbstractRSSReader.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned.rss;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.oned.OneDReader;
+
+public abstract class AbstractRSSReader extends OneDReader {
+
+ private static final float MAX_AVG_VARIANCE = 0.2f;
+ private static final float MAX_INDIVIDUAL_VARIANCE = 0.45f;
+
+ private static final float MIN_FINDER_PATTERN_RATIO = 9.5f / 12.0f;
+ private static final float MAX_FINDER_PATTERN_RATIO = 12.5f / 14.0f;
+
+ private final int[] decodeFinderCounters;
+ private final int[] dataCharacterCounters;
+ private final float[] oddRoundingErrors;
+ private final float[] evenRoundingErrors;
+ private final int[] oddCounts;
+ private final int[] evenCounts;
+
+ protected AbstractRSSReader() {
+ decodeFinderCounters = new int[4];
+ dataCharacterCounters = new int[8];
+ oddRoundingErrors = new float[4];
+ evenRoundingErrors = new float[4];
+ oddCounts = new int[dataCharacterCounters.length / 2];
+ evenCounts = new int[dataCharacterCounters.length / 2];
+ }
+
+ protected static int parseFinderValue(int[] counters,
+ int[][] finderPatterns) throws NotFoundException {
+ for (int value = 0; value < finderPatterns.length; value++) {
+ if (patternMatchVariance(counters, finderPatterns[value], MAX_INDIVIDUAL_VARIANCE) <
+ MAX_AVG_VARIANCE) {
+ return value;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ protected static int count(int[] array) {
+ int count = 0;
+ for (int a : array) {
+ count += a;
+ }
+ return count;
+ }
+
+ protected static void increment(int[] array, float[] errors) {
+ int index = 0;
+ float biggestError = errors[0];
+ for (int i = 1; i < array.length; i++) {
+ if (errors[i] > biggestError) {
+ biggestError = errors[i];
+ index = i;
+ }
+ }
+ array[index]++;
+ }
+
+ protected static void decrement(int[] array, float[] errors) {
+ int index = 0;
+ float biggestError = errors[0];
+ for (int i = 1; i < array.length; i++) {
+ if (errors[i] < biggestError) {
+ biggestError = errors[i];
+ index = i;
+ }
+ }
+ array[index]--;
+ }
+
+ protected static boolean isFinderPattern(int[] counters) {
+ int firstTwoSum = counters[0] + counters[1];
+ int sum = firstTwoSum + counters[2] + counters[3];
+ float ratio = (float) firstTwoSum / (float) sum;
+ if (ratio >= MIN_FINDER_PATTERN_RATIO && ratio <= MAX_FINDER_PATTERN_RATIO) {
+ // passes ratio test in spec, but see if the counts are unreasonable
+ int minCounter = Integer.MAX_VALUE;
+ int maxCounter = Integer.MIN_VALUE;
+ for (int counter : counters) {
+ if (counter > maxCounter) {
+ maxCounter = counter;
+ }
+ if (counter < minCounter) {
+ minCounter = counter;
+ }
+ }
+ return maxCounter < 10 * minCounter;
+ }
+ return false;
+ }
+
+ protected final int[] getDecodeFinderCounters() {
+ return decodeFinderCounters;
+ }
+
+ protected final int[] getDataCharacterCounters() {
+ return dataCharacterCounters;
+ }
+
+ protected final float[] getOddRoundingErrors() {
+ return oddRoundingErrors;
+ }
+
+ protected final float[] getEvenRoundingErrors() {
+ return evenRoundingErrors;
+ }
+
+ protected final int[] getOddCounts() {
+ return oddCounts;
+ }
+
+ protected final int[] getEvenCounts() {
+ return evenCounts;
+ }
+}
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..f5b028c
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/DataCharacter.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned.rss;
+
+public class DataCharacter {
+
+ private final int value;
+ private final int checksumPortion;
+
+ public DataCharacter(int value, int checksumPortion) {
+ this.value = value;
+ this.checksumPortion = checksumPortion;
+ }
+
+ public final int getValue() {
+ return value;
+ }
+
+ public final int getChecksumPortion() {
+ return checksumPortion;
+ }
+
+ @Override
+ public final String toString() {
+ return value + "(" + checksumPortion + ')';
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (!(o instanceof DataCharacter)) {
+ return false;
+ }
+ DataCharacter that = (DataCharacter) o;
+ return value == that.value && checksumPortion == that.checksumPortion;
+ }
+
+ @Override
+ public final int hashCode() {
+ return value ^ checksumPortion;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/FinderPattern.java b/src/com/google/zxing/oned/rss/FinderPattern.java
new file mode 100644
index 0000000..3287b30
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/FinderPattern.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned.rss;
+
+import com.google.zxing.ResultPoint;
+
+public final class FinderPattern {
+
+ private final int value;
+ private final int[] startEnd;
+ private final ResultPoint[] resultPoints;
+
+ public FinderPattern(int value, int[] startEnd, int start, int end, int rowNumber) {
+ this.value = value;
+ this.startEnd = startEnd;
+ this.resultPoints = new ResultPoint[]{
+ new ResultPoint((float) start, (float) rowNumber),
+ new ResultPoint((float) end, (float) rowNumber),
+ };
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public int[] getStartEnd() {
+ return startEnd;
+ }
+
+ public ResultPoint[] getResultPoints() {
+ return resultPoints;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof FinderPattern)) {
+ return false;
+ }
+ FinderPattern that = (FinderPattern) o;
+ return value == that.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return value;
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/Pair.java b/src/com/google/zxing/oned/rss/Pair.java
new file mode 100644
index 0000000..4add1c8
--- /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..7f55038
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/RSS14Reader.java
@@ -0,0 +1,473 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitArray;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Decodes RSS-14, including truncated and stacked variants. See ISO/IEC 24724:2006.
+ */
+public final class RSS14Reader extends AbstractRSSReader {
+
+ private static final int[] OUTSIDE_EVEN_TOTAL_SUBSET = {1, 10, 34, 70, 126};
+ private static final int[] INSIDE_ODD_TOTAL_SUBSET = {4, 20, 48, 81};
+ private static final int[] OUTSIDE_GSUM = {0, 161, 961, 2015, 2715};
+ private static final int[] INSIDE_GSUM = {0, 336, 1036, 1516};
+ private static final int[] OUTSIDE_ODD_WIDEST = {8, 6, 4, 3, 1};
+ private static final int[] INSIDE_ODD_WIDEST = {2, 4, 6, 8};
+
+ private static final int[][] FINDER_PATTERNS = {
+ {3, 8, 2, 1},
+ {3, 5, 5, 1},
+ {3, 3, 7, 1},
+ {3, 1, 9, 1},
+ {2, 7, 4, 1},
+ {2, 5, 6, 1},
+ {2, 3, 8, 1},
+ {1, 5, 7, 1},
+ {1, 3, 9, 1},
+ };
+
+ private final List possibleLeftPairs;
+ private final List possibleRightPairs;
+
+ public RSS14Reader() {
+ possibleLeftPairs = new ArrayList<>();
+ possibleRightPairs = new ArrayList<>();
+ }
+
+ private static void addOrTally(Collection possiblePairs, Pair pair) {
+ if (pair == null) {
+ return;
+ }
+ boolean found = false;
+ for (Pair other : possiblePairs) {
+ if (other.getValue() == pair.getValue()) {
+ other.incrementCount();
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ possiblePairs.add(pair);
+ }
+ }
+
+ private static Result constructResult(Pair leftPair, Pair rightPair) {
+ long symbolValue = 4537077L * leftPair.getValue() + rightPair.getValue();
+ String text = String.valueOf(symbolValue);
+
+ StringBuilder buffer = new StringBuilder(14);
+ for (int i = 13 - text.length(); i > 0; i--) {
+ buffer.append('0');
+ }
+ buffer.append(text);
+
+ int checkDigit = 0;
+ for (int i = 0; i < 13; i++) {
+ int digit = buffer.charAt(i) - '0';
+ checkDigit += (i & 0x01) == 0 ? 3 * digit : digit;
+ }
+ checkDigit = 10 - (checkDigit % 10);
+ if (checkDigit == 10) {
+ checkDigit = 0;
+ }
+ buffer.append(checkDigit);
+
+ ResultPoint[] leftPoints = leftPair.getFinderPattern().getResultPoints();
+ ResultPoint[] rightPoints = rightPair.getFinderPattern().getResultPoints();
+ return new Result(
+ String.valueOf(buffer.toString()),
+ null,
+ new ResultPoint[]{leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1],},
+ BarcodeFormat.RSS_14);
+ }
+
+ private static boolean checkChecksum(Pair leftPair, Pair rightPair) {
+ //int leftFPValue = leftPair.getFinderPattern().getValue();
+ //int rightFPValue = rightPair.getFinderPattern().getValue();
+ //if ((leftFPValue == 0 && rightFPValue == 8) ||
+ // (leftFPValue == 8 && rightFPValue == 0)) {
+ //}
+ int checkValue = (leftPair.getChecksumPortion() + 16 * rightPair.getChecksumPortion()) % 79;
+ int targetCheckValue =
+ 9 * leftPair.getFinderPattern().getValue() + rightPair.getFinderPattern().getValue();
+ if (targetCheckValue > 72) {
+ targetCheckValue--;
+ }
+ if (targetCheckValue > 8) {
+ targetCheckValue--;
+ }
+ return checkValue == targetCheckValue;
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ Map hints) throws NotFoundException {
+ Pair leftPair = decodePair(row, false, rowNumber, hints);
+ addOrTally(possibleLeftPairs, leftPair);
+ row.reverse();
+ Pair rightPair = decodePair(row, true, rowNumber, hints);
+ addOrTally(possibleRightPairs, rightPair);
+ row.reverse();
+ int lefSize = possibleLeftPairs.size();
+ for (int i = 0; i < lefSize; i++) {
+ Pair left = possibleLeftPairs.get(i);
+ if (left.getCount() > 1) {
+ int rightSize = possibleRightPairs.size();
+ for (int j = 0; j < rightSize; j++) {
+ Pair right = possibleRightPairs.get(j);
+ if (right.getCount() > 1) {
+ if (checkChecksum(left, right)) {
+ return constructResult(left, right);
+ }
+ }
+ }
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ @Override
+ public void reset() {
+ possibleLeftPairs.clear();
+ possibleRightPairs.clear();
+ }
+
+ private Pair decodePair(BitArray row, boolean right, int rowNumber, Map hints) {
+ try {
+ int[] startEnd = findFinderPattern(row, 0, right);
+ FinderPattern pattern = parseFoundFinderPattern(row, rowNumber, right, startEnd);
+
+ ResultPointCallback resultPointCallback = hints == null ? null :
+ (ResultPointCallback) hints.get(DecodeHintType.NEED_RESULT_POINT_CALLBACK);
+
+ if (resultPointCallback != null) {
+ float center = (startEnd[0] + startEnd[1]) / 2.0f;
+ if (right) {
+ // row is actually reversed
+ center = row.getSize() - 1 - center;
+ }
+ resultPointCallback.foundPossibleResultPoint(new ResultPoint(center, rowNumber));
+ }
+
+ DataCharacter outside = decodeDataCharacter(row, pattern, true);
+ DataCharacter inside = decodeDataCharacter(row, pattern, false);
+ return new Pair(1597 * outside.getValue() + inside.getValue(),
+ outside.getChecksumPortion() + 4 * inside.getChecksumPortion(),
+ pattern);
+ } catch (NotFoundException ignored) {
+ return null;
+ }
+ }
+
+ private DataCharacter decodeDataCharacter(BitArray row, FinderPattern pattern, boolean outsideChar)
+ throws NotFoundException {
+
+ int[] counters = getDataCharacterCounters();
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+ counters[4] = 0;
+ counters[5] = 0;
+ counters[6] = 0;
+ counters[7] = 0;
+
+ if (outsideChar) {
+ recordPatternInReverse(row, pattern.getStartEnd()[0], counters);
+ } else {
+ recordPattern(row, pattern.getStartEnd()[1] + 1, counters);
+ // reverse it
+ for (int i = 0, j = counters.length - 1; i < j; i++, j--) {
+ int temp = counters[i];
+ counters[i] = counters[j];
+ counters[j] = temp;
+ }
+ }
+
+ int numModules = outsideChar ? 16 : 15;
+ float elementWidth = (float) count(counters) / (float) numModules;
+
+ int[] oddCounts = this.getOddCounts();
+ int[] evenCounts = this.getEvenCounts();
+ float[] oddRoundingErrors = this.getOddRoundingErrors();
+ float[] evenRoundingErrors = this.getEvenRoundingErrors();
+
+ for (int i = 0; i < counters.length; i++) {
+ float value = (float) counters[i] / elementWidth;
+ int count = (int) (value + 0.5f); // Round
+ if (count < 1) {
+ count = 1;
+ } else if (count > 8) {
+ count = 8;
+ }
+ int offset = i / 2;
+ if ((i & 0x01) == 0) {
+ oddCounts[offset] = count;
+ oddRoundingErrors[offset] = value - count;
+ } else {
+ evenCounts[offset] = count;
+ evenRoundingErrors[offset] = value - count;
+ }
+ }
+
+ adjustOddEvenCounts(outsideChar, numModules);
+
+ int oddSum = 0;
+ int oddChecksumPortion = 0;
+ for (int i = oddCounts.length - 1; i >= 0; i--) {
+ oddChecksumPortion *= 9;
+ oddChecksumPortion += oddCounts[i];
+ oddSum += oddCounts[i];
+ }
+ int evenChecksumPortion = 0;
+ int evenSum = 0;
+ for (int i = evenCounts.length - 1; i >= 0; i--) {
+ evenChecksumPortion *= 9;
+ evenChecksumPortion += evenCounts[i];
+ evenSum += evenCounts[i];
+ }
+ int checksumPortion = oddChecksumPortion + 3 * evenChecksumPortion;
+
+ if (outsideChar) {
+ if ((oddSum & 0x01) != 0 || oddSum > 12 || oddSum < 4) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int group = (12 - oddSum) / 2;
+ int oddWidest = OUTSIDE_ODD_WIDEST[group];
+ int evenWidest = 9 - oddWidest;
+ int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, false);
+ int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, true);
+ int tEven = OUTSIDE_EVEN_TOTAL_SUBSET[group];
+ int gSum = OUTSIDE_GSUM[group];
+ return new DataCharacter(vOdd * tEven + vEven + gSum, checksumPortion);
+ } else {
+ if ((evenSum & 0x01) != 0 || evenSum > 10 || evenSum < 4) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ int group = (10 - evenSum) / 2;
+ int oddWidest = INSIDE_ODD_WIDEST[group];
+ int evenWidest = 9 - oddWidest;
+ int vOdd = RSSUtils.getRSSvalue(oddCounts, oddWidest, true);
+ int vEven = RSSUtils.getRSSvalue(evenCounts, evenWidest, false);
+ int tOdd = INSIDE_ODD_TOTAL_SUBSET[group];
+ int gSum = INSIDE_GSUM[group];
+ return new DataCharacter(vEven * tOdd + vOdd + gSum, checksumPortion);
+ }
+
+ }
+
+ private int[] findFinderPattern(BitArray row, int rowOffset, boolean rightFinderPattern)
+ throws NotFoundException {
+
+ int[] counters = getDecodeFinderCounters();
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+
+ int width = row.getSize();
+ boolean isWhite = false;
+ while (rowOffset < width) {
+ isWhite = !row.get(rowOffset);
+ if (rightFinderPattern == isWhite) {
+ // Will encounter white first when searching for right finder pattern
+ break;
+ }
+ rowOffset++;
+ }
+
+ int counterPosition = 0;
+ int patternStart = rowOffset;
+ for (int x = rowOffset; x < width; x++) {
+ if (row.get(x) ^ isWhite) {
+ counters[counterPosition]++;
+ } else {
+ if (counterPosition == 3) {
+ if (isFinderPattern(counters)) {
+ return new int[]{patternStart, x};
+ }
+ patternStart += counters[0] + counters[1];
+ counters[0] = counters[2];
+ counters[1] = counters[3];
+ counters[2] = 0;
+ counters[3] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+
+ }
+
+ private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, boolean right, int[] startEnd)
+ throws NotFoundException {
+ // Actually we found elements 2-5
+ boolean firstIsBlack = row.get(startEnd[0]);
+ int firstElementStart = startEnd[0] - 1;
+ // Locate element 1
+ while (firstElementStart >= 0 && firstIsBlack ^ row.get(firstElementStart)) {
+ firstElementStart--;
+ }
+ firstElementStart++;
+ int firstCounter = startEnd[0] - firstElementStart;
+ // Make 'counters' hold 1-4
+ int[] counters = getDecodeFinderCounters();
+ System.arraycopy(counters, 0, counters, 1, counters.length - 1);
+ counters[0] = firstCounter;
+ int value = parseFinderValue(counters, FINDER_PATTERNS);
+ int start = firstElementStart;
+ int end = startEnd[1];
+ if (right) {
+ // row is actually reversed
+ start = row.getSize() - 1 - start;
+ end = row.getSize() - 1 - end;
+ }
+ return new FinderPattern(value, new int[]{firstElementStart, startEnd[1]}, start, end, rowNumber);
+ }
+
+ private void adjustOddEvenCounts(boolean outsideChar, int numModules) throws NotFoundException {
+
+ int oddSum = count(getOddCounts());
+ int evenSum = count(getEvenCounts());
+ int mismatch = oddSum + evenSum - numModules;
+ boolean oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0);
+ boolean evenParityBad = (evenSum & 0x01) == 1;
+
+ boolean incrementOdd = false;
+ boolean decrementOdd = false;
+ boolean incrementEven = false;
+ boolean decrementEven = false;
+
+ if (outsideChar) {
+ if (oddSum > 12) {
+ decrementOdd = true;
+ } else if (oddSum < 4) {
+ incrementOdd = true;
+ }
+ if (evenSum > 12) {
+ decrementEven = true;
+ } else if (evenSum < 4) {
+ incrementEven = true;
+ }
+ } else {
+ if (oddSum > 11) {
+ decrementOdd = true;
+ } else if (oddSum < 5) {
+ incrementOdd = true;
+ }
+ if (evenSum > 10) {
+ decrementEven = true;
+ } else if (evenSum < 4) {
+ incrementEven = true;
+ }
+ }
+
+ /*if (mismatch == 2) {
+ if (!(oddParityBad && evenParityBad)) {
+ throw ReaderException.getInstance();
+ }
+ decrementOdd = true;
+ decrementEven = true;
+ } else if (mismatch == -2) {
+ if (!(oddParityBad && evenParityBad)) {
+ throw ReaderException.getInstance();
+ }
+ incrementOdd = true;
+ incrementEven = true;
+ } else */
+ if (mismatch == 1) {
+ if (oddParityBad) {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decrementOdd = true;
+ } else {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decrementEven = true;
+ }
+ } else if (mismatch == -1) {
+ if (oddParityBad) {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ incrementOdd = true;
+ } else {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ incrementEven = true;
+ }
+ } else if (mismatch == 0) {
+ if (oddParityBad) {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ // Both bad
+ if (oddSum < evenSum) {
+ incrementOdd = true;
+ decrementEven = true;
+ } else {
+ decrementOdd = true;
+ incrementEven = true;
+ }
+ } else {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ // Nothing to do!
+ }
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ if (incrementOdd) {
+ if (decrementOdd) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ increment(getOddCounts(), getOddRoundingErrors());
+ }
+ if (decrementOdd) {
+ decrement(getOddCounts(), getOddRoundingErrors());
+ }
+ if (incrementEven) {
+ if (decrementEven) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ increment(getEvenCounts(), getOddRoundingErrors());
+ }
+ if (decrementEven) {
+ decrement(getEvenCounts(), getEvenRoundingErrors());
+ }
+
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/RSSUtils.java b/src/com/google/zxing/oned/rss/RSSUtils.java
new file mode 100644
index 0000000..001acdb
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/RSSUtils.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned.rss;
+
+/**
+ * Adapted from listings in ISO/IEC 24724 Appendix B and Appendix G.
+ */
+public final class RSSUtils {
+
+ private RSSUtils() {
+ }
+
+ /*
+ static int[] getRSSwidths(int val, int n, int elements, int maxWidth, boolean noNarrow) {
+ int[] widths = new int[elements];
+ int bar;
+ int narrowMask = 0;
+ for (bar = 0; bar < elements - 1; bar++) {
+ narrowMask |= 1 << bar;
+ int elmWidth = 1;
+ int subVal;
+ while (true) {
+ subVal = combins(n - elmWidth - 1, elements - bar - 2);
+ if (noNarrow && (narrowMask == 0) &&
+ (n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) {
+ subVal -= combins(n - elmWidth - (elements - bar), elements - bar - 2);
+ }
+ if (elements - bar - 1 > 1) {
+ int lessVal = 0;
+ for (int mxwElement = n - elmWidth - (elements - bar - 2);
+ mxwElement > maxWidth;
+ mxwElement--) {
+ lessVal += combins(n - elmWidth - mxwElement - 1, elements - bar - 3);
+ }
+ subVal -= lessVal * (elements - 1 - bar);
+ } else if (n - elmWidth > maxWidth) {
+ subVal--;
+ }
+ val -= subVal;
+ if (val < 0) {
+ break;
+ }
+ elmWidth++;
+ narrowMask &= ~(1 << bar);
+ }
+ val += subVal;
+ n -= elmWidth;
+ widths[bar] = elmWidth;
+ }
+ widths[bar] = n;
+ return widths;
+ }
+ */
+
+ public static int getRSSvalue(int[] widths, int maxWidth, boolean noNarrow) {
+ int elements = widths.length;
+ int n = 0;
+ for (int width : widths) {
+ n += width;
+ }
+ int val = 0;
+ int narrowMask = 0;
+ for (int bar = 0; bar < elements - 1; bar++) {
+ int elmWidth;
+ for (elmWidth = 1, narrowMask |= 1 << bar;
+ elmWidth < widths[bar];
+ elmWidth++, narrowMask &= ~(1 << bar)) {
+ int subVal = combins(n - elmWidth - 1, elements - bar - 2);
+ if (noNarrow && (narrowMask == 0) &&
+ (n - elmWidth - (elements - bar - 1) >= elements - bar - 1)) {
+ subVal -= combins(n - elmWidth - (elements - bar),
+ elements - bar - 2);
+ }
+ if (elements - bar - 1 > 1) {
+ int lessVal = 0;
+ for (int mxwElement = n - elmWidth - (elements - bar - 2);
+ mxwElement > maxWidth; mxwElement--) {
+ lessVal += combins(n - elmWidth - mxwElement - 1,
+ elements - bar - 3);
+ }
+ subVal -= lessVal * (elements - 1 - bar);
+ } else if (n - elmWidth > maxWidth) {
+ subVal--;
+ }
+ val += subVal;
+ }
+ n -= elmWidth;
+ }
+ return val;
+ }
+
+ private static int combins(int n, int r) {
+ int maxDenom;
+ int minDenom;
+ if (n - r > r) {
+ minDenom = r;
+ maxDenom = n - r;
+ } else {
+ minDenom = n - r;
+ maxDenom = r;
+ }
+ int val = 1;
+ int j = 1;
+ for (int i = n; i > maxDenom; i--) {
+ val *= i;
+ if (j <= minDenom) {
+ val /= j;
+ j++;
+ }
+ }
+ while (j <= minDenom) {
+ val /= j;
+ j++;
+ }
+ return val;
+ }
+
+ /*
+ static int[] elements(int[] eDist, int N, int K) {
+ int[] widths = new int[eDist.length + 2];
+ int twoK = 2 * K;
+ 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..caea4f5
--- /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 com.google.zxing.common.BitArray;
+
+import java.util.List;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class BitArrayBuilder {
+
+ private BitArrayBuilder() {
+ }
+
+ static BitArray buildBitArray(List pairs) {
+ int charNumber = (pairs.size() * 2) - 1;
+ if (pairs.get(pairs.size() - 1).getRightChar() == null) {
+ charNumber -= 1;
+ }
+
+ int size = 12 * charNumber;
+
+ BitArray binary = new BitArray(size);
+ int accPos = 0;
+
+ ExpandedPair firstPair = pairs.get(0);
+ int firstValue = firstPair.getRightChar().getValue();
+ for (int i = 11; i >= 0; --i) {
+ if ((firstValue & (1 << i)) != 0) {
+ binary.set(accPos);
+ }
+ accPos++;
+ }
+
+ for (int i = 1; i < pairs.size(); ++i) {
+ ExpandedPair currentPair = pairs.get(i);
+
+ int leftValue = currentPair.getLeftChar().getValue();
+ for (int j = 11; j >= 0; --j) {
+ if ((leftValue & (1 << j)) != 0) {
+ binary.set(accPos);
+ }
+ accPos++;
+ }
+
+ if (currentPair.getRightChar() != null) {
+ int rightValue = currentPair.getRightChar().getValue();
+ for (int j = 11; j >= 0; --j) {
+ if ((rightValue & (1 << j)) != 0) {
+ binary.set(accPos);
+ }
+ accPos++;
+ }
+ }
+ }
+ return binary;
+ }
+}
diff --git a/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..595f93c
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/ExpandedPair.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded;
+
+import com.google.zxing.oned.rss.DataCharacter;
+import com.google.zxing.oned.rss.FinderPattern;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+final class ExpandedPair {
+
+ private final boolean mayBeLast;
+ private final DataCharacter leftChar;
+ private final DataCharacter rightChar;
+ private final FinderPattern finderPattern;
+
+ ExpandedPair(DataCharacter leftChar,
+ DataCharacter rightChar,
+ FinderPattern finderPattern,
+ boolean mayBeLast) {
+ this.leftChar = leftChar;
+ this.rightChar = rightChar;
+ this.finderPattern = finderPattern;
+ this.mayBeLast = mayBeLast;
+ }
+
+ private static boolean equalsOrNull(Object o1, Object o2) {
+ return o1 == null ? o2 == null : o1.equals(o2);
+ }
+
+ private static int hashNotNull(Object o) {
+ return o == null ? 0 : o.hashCode();
+ }
+
+ boolean mayBeLast() {
+ return this.mayBeLast;
+ }
+
+ DataCharacter getLeftChar() {
+ return this.leftChar;
+ }
+
+ DataCharacter getRightChar() {
+ return this.rightChar;
+ }
+
+ FinderPattern getFinderPattern() {
+ return this.finderPattern;
+ }
+
+ public boolean mustBeLast() {
+ return this.rightChar == null;
+ }
+
+ @Override
+ public String toString() {
+ return
+ "[ " + leftChar + " , " + rightChar + " : " +
+ (finderPattern == null ? "null" : finderPattern.getValue()) + " ]";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ExpandedPair)) {
+ return false;
+ }
+ ExpandedPair that = (ExpandedPair) o;
+ return
+ equalsOrNull(leftChar, that.leftChar) &&
+ equalsOrNull(rightChar, that.rightChar) &&
+ equalsOrNull(finderPattern, that.finderPattern);
+ }
+
+ @Override
+ public int hashCode() {
+ return hashNotNull(leftChar) ^ hashNotNull(rightChar) ^ hashNotNull(finderPattern);
+ }
+
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/ExpandedRow.java b/src/com/google/zxing/oned/rss/expanded/ExpandedRow.java
new file mode 100644
index 0000000..1b51b00
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/ExpandedRow.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.oned.rss.expanded;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * One row of an RSS Expanded Stacked symbol, consisting of 1+ expanded pairs.
+ */
+final class ExpandedRow {
+
+ private final List pairs;
+ private final int rowNumber;
+ /**
+ * Did this row of the image have to be reversed (mirrored) to recognize the pairs?
+ */
+ private final boolean wasReversed;
+
+ ExpandedRow(List pairs, int rowNumber, boolean wasReversed) {
+ this.pairs = new ArrayList<>(pairs);
+ this.rowNumber = rowNumber;
+ this.wasReversed = wasReversed;
+ }
+
+ List getPairs() {
+ return this.pairs;
+ }
+
+ int getRowNumber() {
+ return this.rowNumber;
+ }
+
+ boolean isReversed() {
+ return this.wasReversed;
+ }
+
+ boolean isEquivalent(List otherPairs) {
+ return this.pairs.equals(otherPairs);
+ }
+
+ @Override
+ public String toString() {
+ return "{ " + pairs + " }";
+ }
+
+ /**
+ * Two rows are equal if they contain the same pairs in the same order.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ExpandedRow)) {
+ return false;
+ }
+ ExpandedRow that = (ExpandedRow) o;
+ return this.pairs.equals(that.getPairs()) && wasReversed == that.wasReversed;
+ }
+
+ @Override
+ public int hashCode() {
+ return pairs.hashCode() ^ Boolean.valueOf(wasReversed).hashCode();
+ }
+
+}
diff --git a/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..d0a509c
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/RSSExpandedReader.java
@@ -0,0 +1,778 @@
+/*
+ * 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.*;
+import com.google.zxing.common.BitArray;
+import com.google.zxing.oned.rss.AbstractRSSReader;
+import com.google.zxing.oned.rss.DataCharacter;
+import com.google.zxing.oned.rss.FinderPattern;
+import com.google.zxing.oned.rss.RSSUtils;
+import com.google.zxing.oned.rss.expanded.decoders.AbstractExpandedDecoder;
+
+import java.util.*;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+public final class RSSExpandedReader extends AbstractRSSReader {
+
+ private static final int[] SYMBOL_WIDEST = {7, 5, 4, 3, 1};
+ private static final int[] EVEN_TOTAL_SUBSET = {4, 20, 52, 104, 204};
+ private static final int[] GSUM = {0, 348, 1388, 2948, 3988};
+
+ private static final int[][] FINDER_PATTERNS = {
+ {1, 8, 4, 1}, // A
+ {3, 6, 4, 1}, // B
+ {3, 4, 6, 1}, // C
+ {3, 2, 8, 1}, // D
+ {2, 6, 5, 1}, // E
+ {2, 2, 9, 1} // F
+ };
+
+ private static final int[][] WEIGHTS = {
+ {1, 3, 9, 27, 81, 32, 96, 77},
+ {20, 60, 180, 118, 143, 7, 21, 63},
+ {189, 145, 13, 39, 117, 140, 209, 205},
+ {193, 157, 49, 147, 19, 57, 171, 91},
+ {62, 186, 136, 197, 169, 85, 44, 132},
+ {185, 133, 188, 142, 4, 12, 36, 108},
+ {113, 128, 173, 97, 80, 29, 87, 50},
+ {150, 28, 84, 41, 123, 158, 52, 156},
+ {46, 138, 203, 187, 139, 206, 196, 166},
+ {76, 17, 51, 153, 37, 111, 122, 155},
+ {43, 129, 176, 106, 107, 110, 119, 146},
+ {16, 48, 144, 10, 30, 90, 59, 177},
+ {109, 116, 137, 200, 178, 112, 125, 164},
+ {70, 210, 208, 202, 184, 130, 179, 115},
+ {134, 191, 151, 31, 93, 68, 204, 190},
+ {148, 22, 66, 198, 172, 94, 71, 2},
+ {6, 18, 54, 162, 64, 192, 154, 40},
+ {120, 149, 25, 75, 14, 42, 126, 167},
+ {79, 26, 78, 23, 69, 207, 199, 175},
+ {103, 98, 83, 38, 114, 131, 182, 124},
+ {161, 61, 183, 127, 170, 88, 53, 159},
+ {55, 165, 73, 8, 24, 72, 5, 15},
+ {45, 135, 194, 160, 58, 174, 100, 89}
+ };
+
+ private static final int FINDER_PAT_A = 0;
+ private static final int FINDER_PAT_B = 1;
+ private static final int FINDER_PAT_C = 2;
+ private static final int FINDER_PAT_D = 3;
+ private static final int FINDER_PAT_E = 4;
+ private static final int FINDER_PAT_F = 5;
+
+ private static final int[][] FINDER_PATTERN_SEQUENCES = {
+ {FINDER_PAT_A, FINDER_PAT_A},
+ {FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B},
+ {FINDER_PAT_A, FINDER_PAT_C, FINDER_PAT_B, FINDER_PAT_D},
+ {FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_C},
+ {FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_D, FINDER_PAT_F},
+ {FINDER_PAT_A, FINDER_PAT_E, FINDER_PAT_B, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F},
+ {FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_D},
+ {FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_E},
+ {FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F},
+ {FINDER_PAT_A, FINDER_PAT_A, FINDER_PAT_B, FINDER_PAT_B, FINDER_PAT_C, FINDER_PAT_D, FINDER_PAT_D, FINDER_PAT_E, FINDER_PAT_E, FINDER_PAT_F, FINDER_PAT_F},
+ };
+
+ //private static final int LONGEST_SEQUENCE_SIZE = FINDER_PATTERN_SEQUENCES[FINDER_PATTERN_SEQUENCES.length - 1].length;
+
+ private static final int MAX_PAIRS = 11;
+
+ private final List pairs = new ArrayList<>(MAX_PAIRS);
+ private final List rows = new ArrayList<>();
+ private final int[] startEnd = new int[2];
+ //private final int [] currentSequence = new int[LONGEST_SEQUENCE_SIZE];
+ private boolean startFromEven = false;
+
+ // Whether the pairs form a valid find pattern seqience,
+ // either complete or a prefix
+ private static boolean isValidSequence(List pairs) {
+ for (int[] sequence : FINDER_PATTERN_SEQUENCES) {
+ if (pairs.size() > sequence.length) {
+ continue;
+ }
+
+ boolean stop = true;
+ for (int j = 0; j < pairs.size(); j++) {
+ if (pairs.get(j).getFinderPattern().getValue() != sequence[j]) {
+ stop = false;
+ break;
+ }
+ }
+
+ if (stop) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ // Remove all the rows that contains only specified pairs
+ private static void removePartialRows(List pairs, List rows) {
+ for (Iterator iterator = rows.iterator(); iterator.hasNext(); ) {
+ ExpandedRow r = iterator.next();
+ if (r.getPairs().size() == pairs.size()) {
+ continue;
+ }
+ boolean allFound = true;
+ for (ExpandedPair p : r.getPairs()) {
+ boolean found = false;
+ for (ExpandedPair pp : pairs) {
+ if (p.equals(pp)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ allFound = false;
+ break;
+ }
+ }
+ if (allFound) {
+ // 'pairs' contains all the pairs from the row 'r'
+ iterator.remove();
+ }
+ }
+ }
+
+ // Returns true when one of the rows already contains all the pairs
+ private static boolean isPartialRow(Iterable pairs, Iterable rows) {
+ for (ExpandedRow r : rows) {
+ boolean allFound = true;
+ for (ExpandedPair p : pairs) {
+ boolean found = false;
+ for (ExpandedPair pp : r.getPairs()) {
+ if (p.equals(pp)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ allFound = false;
+ break;
+ }
+ }
+ if (allFound) {
+ // the row 'r' contain all the pairs from 'pairs'
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Not private for unit testing
+ static Result constructResult(List pairs) throws NotFoundException, FormatException {
+ BitArray binary = BitArrayBuilder.buildBitArray(pairs);
+
+ AbstractExpandedDecoder decoder = AbstractExpandedDecoder.createDecoder(binary);
+ String resultingString = decoder.parseInformation();
+
+ ResultPoint[] firstPoints = pairs.get(0).getFinderPattern().getResultPoints();
+ ResultPoint[] lastPoints = pairs.get(pairs.size() - 1).getFinderPattern().getResultPoints();
+
+ return new Result(
+ resultingString,
+ null,
+ new ResultPoint[]{firstPoints[0], firstPoints[1], lastPoints[0], lastPoints[1]},
+ BarcodeFormat.RSS_EXPANDED
+ );
+ }
+
+ private static int getNextSecondBar(BitArray row, int initialPos) {
+ int currentPos;
+ if (row.get(initialPos)) {
+ currentPos = row.getNextUnset(initialPos);
+ currentPos = row.getNextSet(currentPos);
+ } else {
+ currentPos = row.getNextSet(initialPos);
+ currentPos = row.getNextUnset(currentPos);
+ }
+ return currentPos;
+ }
+
+ 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 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);
+ }
+
+ @Override
+ public Result decodeRow(int rowNumber,
+ BitArray row,
+ Map hints) throws NotFoundException, FormatException {
+ // Rows can start with even pattern in case in prev rows there where odd number of patters.
+ // So lets try twice
+ this.pairs.clear();
+ this.startFromEven = false;
+ try {
+ List pairs = decodeRow2pairs(rowNumber, row);
+ return constructResult(pairs);
+ } catch (NotFoundException e) {
+ // OK
+ }
+
+ this.pairs.clear();
+ this.startFromEven = true;
+ List pairs = decodeRow2pairs(rowNumber, row);
+ return constructResult(pairs);
+ }
+
+ @Override
+ public void reset() {
+ this.pairs.clear();
+ this.rows.clear();
+ }
+
+ // Not private for testing
+ List decodeRow2pairs(int rowNumber, BitArray row) throws NotFoundException {
+ try {
+ while (true) {
+ ExpandedPair nextPair = retrieveNextPair(row, this.pairs, rowNumber);
+ this.pairs.add(nextPair);
+ //System.out.println(this.pairs.size()+" pairs found so far on row "+rowNumber+": "+this.pairs);
+ // exit this loop when retrieveNextPair() fails and throws
+ }
+ } catch (NotFoundException nfe) {
+ if (this.pairs.isEmpty()) {
+ throw nfe;
+ }
+ }
+
+ // TODO: verify sequence of finder patterns as in checkPairSequence()
+ if (checkChecksum()) {
+ return this.pairs;
+ }
+
+ boolean tryStackedDecode = !this.rows.isEmpty();
+ boolean wasReversed = false; // TODO: deal with reversed rows
+ storeRow(rowNumber, wasReversed);
+ if (tryStackedDecode) {
+ // When the image is 180-rotated, then rows are sorted in wrong dirrection.
+ // Try twice with both the directions.
+ List ps = checkRows(false);
+ if (ps != null) {
+ return ps;
+ }
+ ps = checkRows(true);
+ if (ps != null) {
+ return ps;
+ }
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private List checkRows(boolean reverse) {
+ // Limit number of rows we are checking
+ // We use recursive algorithm with pure complexity and don't want it to take forever
+ // Stacked barcode can have up to 11 rows, so 25 seems resonable enough
+ if (this.rows.size() > 25) {
+ this.rows.clear(); // We will never have a chance to get result, so clear it
+ return null;
+ }
+
+ this.pairs.clear();
+ if (reverse) {
+ Collections.reverse(this.rows);
+ }
+
+ List ps = null;
+ try {
+ ps = checkRows(new ArrayList(), 0);
+ } catch (NotFoundException e) {
+ // OK
+ }
+
+ if (reverse) {
+ Collections.reverse(this.rows);
+ }
+
+ return ps;
+ }
+
+ // Try to construct a valid rows sequence
+ // Recursion is used to implement backtracking
+ private List checkRows(List collectedRows, int currentRow) throws NotFoundException {
+ for (int i = currentRow; i < rows.size(); i++) {
+ ExpandedRow row = rows.get(i);
+ this.pairs.clear();
+ int size = collectedRows.size();
+ for (int j = 0; j < size; j++) {
+ this.pairs.addAll(collectedRows.get(j).getPairs());
+ }
+ this.pairs.addAll(row.getPairs());
+
+ if (!isValidSequence(this.pairs)) {
+ continue;
+ }
+
+ if (checkChecksum()) {
+ return this.pairs;
+ }
+
+ List rs = new ArrayList<>();
+ rs.addAll(collectedRows);
+ rs.add(row);
+ try {
+ // Recursion: try to add more rows
+ return checkRows(rs, i + 1);
+ } catch (NotFoundException e) {
+ // We failed, try the next candidate
+ }
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private void storeRow(int rowNumber, boolean wasReversed) {
+ // Discard if duplicate above or below; otherwise insert in order by row number.
+ int insertPos = 0;
+ boolean prevIsSame = false;
+ boolean nextIsSame = false;
+ while (insertPos < this.rows.size()) {
+ ExpandedRow erow = this.rows.get(insertPos);
+ if (erow.getRowNumber() > rowNumber) {
+ nextIsSame = erow.isEquivalent(this.pairs);
+ break;
+ }
+ prevIsSame = erow.isEquivalent(this.pairs);
+ insertPos++;
+ }
+ if (nextIsSame || prevIsSame) {
+ return;
+ }
+
+ // When the row was partially decoded (e.g. 2 pairs found instead of 3),
+ // it will prevent us from detecting the barcode.
+ // Try to merge partial rows
+
+ // Check whether the row is part of an allready detected row
+ if (isPartialRow(this.pairs, this.rows)) {
+ return;
+ }
+
+ this.rows.add(insertPos, new ExpandedRow(this.pairs, rowNumber, wasReversed));
+
+ removePartialRows(this.pairs, this.rows);
+ }
+
+ // Only used for unit testing
+ List getRows() {
+ return this.rows;
+ }
+
+ private boolean checkChecksum() {
+ ExpandedPair firstPair = this.pairs.get(0);
+ DataCharacter checkCharacter = firstPair.getLeftChar();
+ DataCharacter firstCharacter = firstPair.getRightChar();
+
+ if (firstCharacter == null) {
+ return false;
+ }
+
+ int checksum = firstCharacter.getChecksumPortion();
+ int s = 2;
+
+ for (int i = 1; i < this.pairs.size(); ++i) {
+ ExpandedPair currentPair = this.pairs.get(i);
+ checksum += currentPair.getLeftChar().getChecksumPortion();
+ s++;
+ DataCharacter currentRightChar = currentPair.getRightChar();
+ if (currentRightChar != null) {
+ checksum += currentRightChar.getChecksumPortion();
+ s++;
+ }
+ }
+
+ checksum %= 211;
+
+ int checkCharacterValue = 211 * (s - 4) + checksum;
+
+ return checkCharacterValue == checkCharacter.getValue();
+ }
+
+ // not private for testing
+ ExpandedPair retrieveNextPair(BitArray row, List previousPairs, int rowNumber)
+ throws NotFoundException {
+ boolean isOddPattern = previousPairs.size() % 2 == 0;
+ if (startFromEven) {
+ isOddPattern = !isOddPattern;
+ }
+
+ FinderPattern pattern;
+
+ boolean keepFinding = true;
+ int forcedOffset = -1;
+ do {
+ this.findNextPair(row, previousPairs, forcedOffset);
+ pattern = parseFoundFinderPattern(row, rowNumber, isOddPattern);
+ if (pattern == null) {
+ forcedOffset = getNextSecondBar(row, this.startEnd[0]);
+ } else {
+ keepFinding = false;
+ }
+ } while (keepFinding);
+
+ // When stacked symbol is split over multiple rows, there's no way to guess if this pair can be last or not.
+ // boolean mayBeLast = checkPairSequence(previousPairs, pattern);
+
+ DataCharacter leftChar = this.decodeDataCharacter(row, pattern, isOddPattern, true);
+
+ if (!previousPairs.isEmpty() && previousPairs.get(previousPairs.size() - 1).mustBeLast()) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ DataCharacter rightChar;
+ try {
+ rightChar = this.decodeDataCharacter(row, pattern, isOddPattern, false);
+ } catch (NotFoundException ignored) {
+ rightChar = null;
+ }
+ boolean mayBeLast = true;
+ return new ExpandedPair(leftChar, rightChar, pattern, mayBeLast);
+ }
+
+ private void findNextPair(BitArray row, List previousPairs, int forcedOffset)
+ throws NotFoundException {
+ int[] counters = this.getDecodeFinderCounters();
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+
+ int width = row.getSize();
+
+ int rowOffset;
+ if (forcedOffset >= 0) {
+ rowOffset = forcedOffset;
+ } else if (previousPairs.isEmpty()) {
+ rowOffset = 0;
+ } else {
+ ExpandedPair lastPair = previousPairs.get(previousPairs.size() - 1);
+ rowOffset = lastPair.getFinderPattern().getStartEnd()[1];
+ }
+ boolean searchingEvenPair = previousPairs.size() % 2 != 0;
+ if (startFromEven) {
+ searchingEvenPair = !searchingEvenPair;
+ }
+
+ boolean isWhite = false;
+ while (rowOffset < width) {
+ isWhite = !row.get(rowOffset);
+ if (!isWhite) {
+ break;
+ }
+ rowOffset++;
+ }
+
+ int counterPosition = 0;
+ int patternStart = rowOffset;
+ for (int x = rowOffset; x < width; x++) {
+ if (row.get(x) ^ isWhite) {
+ counters[counterPosition]++;
+ } else {
+ if (counterPosition == 3) {
+ if (searchingEvenPair) {
+ reverseCounters(counters);
+ }
+
+ if (isFinderPattern(counters)) {
+ this.startEnd[0] = patternStart;
+ this.startEnd[1] = x;
+ return;
+ }
+
+ if (searchingEvenPair) {
+ reverseCounters(counters);
+ }
+
+ patternStart += counters[0] + counters[1];
+ counters[0] = counters[2];
+ counters[1] = counters[3];
+ counters[2] = 0;
+ counters[3] = 0;
+ counterPosition--;
+ } else {
+ counterPosition++;
+ }
+ counters[counterPosition] = 1;
+ isWhite = !isWhite;
+ }
+ }
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private FinderPattern parseFoundFinderPattern(BitArray row, int rowNumber, boolean oddPattern) {
+ // Actually we found elements 2-5.
+ int firstCounter;
+ int start;
+ int end;
+
+ if (oddPattern) {
+ // If pattern number is odd, we need to locate element 1 *before* the current block.
+
+ int firstElementStart = this.startEnd[0] - 1;
+ // Locate element 1
+ while (firstElementStart >= 0 && !row.get(firstElementStart)) {
+ firstElementStart--;
+ }
+
+ firstElementStart++;
+ firstCounter = this.startEnd[0] - firstElementStart;
+ start = firstElementStart;
+ end = this.startEnd[1];
+
+ } else {
+ // If pattern number is even, the pattern is reversed, so we need to locate element 1 *after* the current block.
+
+ start = this.startEnd[0];
+
+ end = row.getNextUnset(this.startEnd[1] + 1);
+ firstCounter = end - this.startEnd[1];
+ }
+
+ // Make 'counters' hold 1-4
+ int[] counters = this.getDecodeFinderCounters();
+ System.arraycopy(counters, 0, counters, 1, counters.length - 1);
+
+ counters[0] = firstCounter;
+ int value;
+ try {
+ value = parseFinderValue(counters, FINDER_PATTERNS);
+ } catch (NotFoundException ignored) {
+ return null;
+ }
+ return new FinderPattern(value, new int[]{start, end}, start, end, rowNumber);
+ }
+
+ DataCharacter decodeDataCharacter(BitArray row,
+ FinderPattern pattern,
+ boolean isOddPattern,
+ boolean leftChar) throws NotFoundException {
+ int[] counters = this.getDataCharacterCounters();
+ counters[0] = 0;
+ counters[1] = 0;
+ counters[2] = 0;
+ counters[3] = 0;
+ counters[4] = 0;
+ counters[5] = 0;
+ counters[6] = 0;
+ counters[7] = 0;
+
+ if (leftChar) {
+ recordPatternInReverse(row, pattern.getStartEnd()[0], counters);
+ } else {
+ recordPattern(row, pattern.getStartEnd()[1], counters);
+ // reverse it
+ for (int i = 0, j = counters.length - 1; i < j; i++, j--) {
+ int temp = counters[i];
+ counters[i] = counters[j];
+ counters[j] = temp;
+ }
+ }//counters[] has the pixels of the module
+
+ int numModules = 17; //left and right data characters have all the same length
+ float elementWidth = (float) count(counters) / (float) numModules;
+
+ // Sanity check: element width for pattern and the character should match
+ float expectedElementWidth = (pattern.getStartEnd()[1] - pattern.getStartEnd()[0]) / 15.0f;
+ if (Math.abs(elementWidth - expectedElementWidth) / expectedElementWidth > 0.3f) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ int[] oddCounts = this.getOddCounts();
+ int[] evenCounts = this.getEvenCounts();
+ float[] oddRoundingErrors = this.getOddRoundingErrors();
+ float[] evenRoundingErrors = this.getEvenRoundingErrors();
+
+ for (int i = 0; i < counters.length; i++) {
+ float value = 1.0f * counters[i] / elementWidth;
+ int count = (int) (value + 0.5f); // Round
+ if (count < 1) {
+ if (value < 0.3f) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ count = 1;
+ } else if (count > 8) {
+ if (value > 8.7f) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ count = 8;
+ }
+ int offset = i / 2;
+ 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 void adjustOddEvenCounts(int numModules) throws NotFoundException {
+
+ int oddSum = count(this.getOddCounts());
+ int evenSum = count(this.getEvenCounts());
+ int mismatch = oddSum + evenSum - numModules;
+ boolean oddParityBad = (oddSum & 0x01) == 1;
+ boolean evenParityBad = (evenSum & 0x01) == 0;
+
+ boolean incrementOdd = false;
+ boolean decrementOdd = false;
+
+ if (oddSum > 13) {
+ decrementOdd = true;
+ } else if (oddSum < 4) {
+ incrementOdd = true;
+ }
+ boolean incrementEven = false;
+ boolean decrementEven = false;
+ if (evenSum > 13) {
+ decrementEven = true;
+ } else if (evenSum < 4) {
+ incrementEven = true;
+ }
+
+ if (mismatch == 1) {
+ if (oddParityBad) {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decrementOdd = true;
+ } else {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ decrementEven = true;
+ }
+ } else if (mismatch == -1) {
+ if (oddParityBad) {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ incrementOdd = true;
+ } else {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ incrementEven = true;
+ }
+ } else if (mismatch == 0) {
+ if (oddParityBad) {
+ if (!evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ // Both bad
+ if (oddSum < evenSum) {
+ incrementOdd = true;
+ decrementEven = true;
+ } else {
+ decrementOdd = true;
+ incrementEven = true;
+ }
+ } else {
+ if (evenParityBad) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ // Nothing to do!
+ }
+ } else {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ if (incrementOdd) {
+ if (decrementOdd) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ increment(this.getOddCounts(), this.getOddRoundingErrors());
+ }
+ if (decrementOdd) {
+ decrement(this.getOddCounts(), this.getOddRoundingErrors());
+ }
+ if (incrementEven) {
+ if (decrementEven) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ increment(this.getEvenCounts(), this.getOddRoundingErrors());
+ }
+ if (decrementEven) {
+ decrement(this.getEvenCounts(), this.getEvenRoundingErrors());
+ }
+ }
+}
diff --git a/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..b39c7eb
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI013103decoder.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+final class AI013103decoder extends AI013x0xDecoder {
+
+ AI013103decoder(BitArray information) {
+ super(information);
+ }
+
+ @Override
+ protected void addWeightCode(StringBuilder buf, int weight) {
+ buf.append("(3103)");
+ }
+
+ @Override
+ protected int checkWeight(int weight) {
+ return weight;
+ }
+}
diff --git a/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..e0dd806
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01320xDecoder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+final class AI01320xDecoder extends AI013x0xDecoder {
+
+ AI01320xDecoder(BitArray information) {
+ super(information);
+ }
+
+ @Override
+ protected void addWeightCode(StringBuilder buf, int weight) {
+ if (weight < 10000) {
+ buf.append("(3202)");
+ } else {
+ buf.append("(3203)");
+ }
+ }
+
+ @Override
+ protected int checkWeight(int weight) {
+ if (weight < 10000) {
+ return weight;
+ }
+ return weight - 10000;
+ }
+
+}
diff --git a/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..7af9692
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01392xDecoder.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+final class AI01392xDecoder extends AI01decoder {
+
+ private static final int HEADER_SIZE = 5 + 1 + 2;
+ private static final int LAST_DIGIT_SIZE = 2;
+
+ AI01392xDecoder(BitArray information) {
+ super(information);
+ }
+
+ @Override
+ public String parseInformation() throws NotFoundException, FormatException {
+ if (this.getInformation().getSize() < HEADER_SIZE + GTIN_SIZE) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ encodeCompressedGtin(buf, HEADER_SIZE);
+
+ int lastAIdigit =
+ this.getGeneralDecoder().extractNumericValueFromBitArray(HEADER_SIZE + GTIN_SIZE, LAST_DIGIT_SIZE);
+ buf.append("(392");
+ buf.append(lastAIdigit);
+ buf.append(')');
+
+ DecodedInformation decodedInformation =
+ this.getGeneralDecoder().decodeGeneralPurposeField(HEADER_SIZE + GTIN_SIZE + LAST_DIGIT_SIZE, null);
+ buf.append(decodedInformation.getNewString());
+
+ return buf.toString();
+ }
+
+}
diff --git a/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..17c5f69
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01393xDecoder.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+final class AI01393xDecoder extends AI01decoder {
+
+ private static final int HEADER_SIZE = 5 + 1 + 2;
+ private static final int LAST_DIGIT_SIZE = 2;
+ private static final int FIRST_THREE_DIGITS_SIZE = 10;
+
+ AI01393xDecoder(BitArray information) {
+ super(information);
+ }
+
+ @Override
+ public String parseInformation() throws NotFoundException, FormatException {
+ if (this.getInformation().getSize() < HEADER_SIZE + GTIN_SIZE) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ encodeCompressedGtin(buf, HEADER_SIZE);
+
+ int lastAIdigit =
+ this.getGeneralDecoder().extractNumericValueFromBitArray(HEADER_SIZE + GTIN_SIZE, LAST_DIGIT_SIZE);
+
+ buf.append("(393");
+ buf.append(lastAIdigit);
+ buf.append(')');
+
+ int firstThreeDigits =
+ this.getGeneralDecoder().extractNumericValueFromBitArray(HEADER_SIZE + GTIN_SIZE + LAST_DIGIT_SIZE, FIRST_THREE_DIGITS_SIZE);
+ if (firstThreeDigits / 100 == 0) {
+ buf.append('0');
+ }
+ if (firstThreeDigits / 10 == 0) {
+ buf.append('0');
+ }
+ buf.append(firstThreeDigits);
+
+ DecodedInformation generalInformation =
+ this.getGeneralDecoder().decodeGeneralPurposeField(HEADER_SIZE + GTIN_SIZE + LAST_DIGIT_SIZE + FIRST_THREE_DIGITS_SIZE, null);
+ buf.append(generalInformation.getNewString());
+
+ return buf.toString();
+ }
+}
diff --git a/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..7d7bf30
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI013x0x1xDecoder.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class AI013x0x1xDecoder extends AI01weightDecoder {
+
+ private static final int HEADER_SIZE = 7 + 1;
+ private static final int WEIGHT_SIZE = 20;
+ private static final int DATE_SIZE = 16;
+
+ private final String dateCode;
+ private final String firstAIdigits;
+
+ AI013x0x1xDecoder(BitArray information, String firstAIdigits, String dateCode) {
+ super(information);
+ this.dateCode = dateCode;
+ this.firstAIdigits = firstAIdigits;
+ }
+
+ @Override
+ public String parseInformation() throws NotFoundException {
+ if (this.getInformation().getSize() != HEADER_SIZE + GTIN_SIZE + WEIGHT_SIZE + DATE_SIZE) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ encodeCompressedGtin(buf, HEADER_SIZE);
+ encodeCompressedWeight(buf, HEADER_SIZE + GTIN_SIZE, WEIGHT_SIZE);
+ encodeCompressedDate(buf, HEADER_SIZE + GTIN_SIZE + WEIGHT_SIZE);
+
+ return buf.toString();
+ }
+
+ private void encodeCompressedDate(StringBuilder buf, int currentPos) {
+ int numericDate = this.getGeneralDecoder().extractNumericValueFromBitArray(currentPos, DATE_SIZE);
+ if (numericDate == 38400) {
+ return;
+ }
+
+ buf.append('(');
+ buf.append(this.dateCode);
+ buf.append(')');
+
+ int day = numericDate % 32;
+ numericDate /= 32;
+ int month = numericDate % 12 + 1;
+ numericDate /= 12;
+ int year = numericDate;
+
+ if (year / 10 == 0) {
+ buf.append('0');
+ }
+ buf.append(year);
+ if (month / 10 == 0) {
+ buf.append('0');
+ }
+ buf.append(month);
+ if (day / 10 == 0) {
+ buf.append('0');
+ }
+ buf.append(day);
+ }
+
+ @Override
+ protected void addWeightCode(StringBuilder buf, int weight) {
+ int lastAI = weight / 100000;
+ buf.append('(');
+ buf.append(this.firstAIdigits);
+ buf.append(lastAI);
+ buf.append(')');
+ }
+
+ @Override
+ protected int checkWeight(int weight) {
+ return weight % 100000;
+ }
+}
diff --git a/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..33d9ad5
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI013x0xDecoder.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+abstract class AI013x0xDecoder extends AI01weightDecoder {
+
+ private static final int HEADER_SIZE = 4 + 1;
+ private static final int WEIGHT_SIZE = 15;
+
+ AI013x0xDecoder(BitArray information) {
+ super(information);
+ }
+
+ @Override
+ public String parseInformation() throws NotFoundException {
+ if (this.getInformation().getSize() != HEADER_SIZE + GTIN_SIZE + WEIGHT_SIZE) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ StringBuilder buf = new StringBuilder();
+
+ encodeCompressedGtin(buf, HEADER_SIZE);
+ encodeCompressedWeight(buf, HEADER_SIZE + GTIN_SIZE, WEIGHT_SIZE);
+
+ return buf.toString();
+ }
+}
diff --git a/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..4dee137
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01AndOtherAIs.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class AI01AndOtherAIs extends AI01decoder {
+
+ private static final int HEADER_SIZE = 1 + 1 + 2; //first bit encodes the linkage flag,
+
+ //the second one is the encodation method, and the other two are for the variable length
+ AI01AndOtherAIs(BitArray information) {
+ super(information);
+ }
+
+ @Override
+ public String parseInformation() throws NotFoundException, FormatException {
+ StringBuilder buff = new StringBuilder();
+
+ buff.append("(01)");
+ int initialGtinPosition = buff.length();
+ int firstGtinDigit = this.getGeneralDecoder().extractNumericValueFromBitArray(HEADER_SIZE, 4);
+ buff.append(firstGtinDigit);
+
+ this.encodeCompressedGtinWithoutAI(buff, HEADER_SIZE + 4, initialGtinPosition);
+
+ return this.getGeneralDecoder().decodeAllCodes(buff, HEADER_SIZE + 44);
+ }
+}
diff --git a/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..b59965e
--- /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 GTIN_SIZE = 40;
+
+ AI01decoder(BitArray information) {
+ super(information);
+ }
+
+ private static void appendCheckDigit(StringBuilder buf, int currentPos) {
+ int checkDigit = 0;
+ for (int i = 0; i < 13; i++) {
+ int digit = buf.charAt(i + currentPos) - '0';
+ checkDigit += (i & 0x01) == 0 ? 3 * digit : digit;
+ }
+
+ checkDigit = 10 - (checkDigit % 10);
+ if (checkDigit == 10) {
+ checkDigit = 0;
+ }
+
+ buf.append(checkDigit);
+ }
+
+ protected final void encodeCompressedGtin(StringBuilder buf, int currentPos) {
+ buf.append("(01)");
+ int initialPosition = buf.length();
+ buf.append('9');
+
+ encodeCompressedGtinWithoutAI(buf, currentPos, initialPosition);
+ }
+
+ protected final void encodeCompressedGtinWithoutAI(StringBuilder buf, int currentPos, int initialBufferPosition) {
+ for (int i = 0; i < 4; ++i) {
+ int currentBlock = this.getGeneralDecoder().extractNumericValueFromBitArray(currentPos + 10 * i, 10);
+ if (currentBlock / 100 == 0) {
+ buf.append('0');
+ }
+ if (currentBlock / 10 == 0) {
+ buf.append('0');
+ }
+ buf.append(currentBlock);
+ }
+
+ appendCheckDigit(buf, initialBufferPosition);
+ }
+
+}
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..8afad3d
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AI01weightDecoder.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+abstract class AI01weightDecoder extends AI01decoder {
+
+ AI01weightDecoder(BitArray information) {
+ super(information);
+ }
+
+ protected final void encodeCompressedWeight(StringBuilder buf, int currentPos, int weightSize) {
+ int originalWeightNumeric = this.getGeneralDecoder().extractNumericValueFromBitArray(currentPos, weightSize);
+ addWeightCode(buf, originalWeightNumeric);
+
+ int weightNumeric = checkWeight(originalWeightNumeric);
+
+ int currentDivisor = 100000;
+ for (int i = 0; i < 5; ++i) {
+ if (weightNumeric / currentDivisor == 0) {
+ buf.append('0');
+ }
+ currentDivisor /= 10;
+ }
+ buf.append(weightNumeric);
+ }
+
+ protected abstract void addWeightCode(StringBuilder buf, int weight);
+
+ protected abstract int checkWeight(int weight);
+
+}
diff --git a/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..8ff3350
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AbstractExpandedDecoder.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+public abstract class AbstractExpandedDecoder {
+
+ private final BitArray information;
+ private final GeneralAppIdDecoder generalDecoder;
+
+ AbstractExpandedDecoder(BitArray information) {
+ this.information = information;
+ this.generalDecoder = new GeneralAppIdDecoder(information);
+ }
+
+ public static AbstractExpandedDecoder createDecoder(BitArray information) {
+ if (information.get(1)) {
+ return new AI01AndOtherAIs(information);
+ }
+ if (!information.get(2)) {
+ return new AnyAIDecoder(information);
+ }
+
+ int fourBitEncodationMethod = GeneralAppIdDecoder.extractNumericValueFromBitArray(information, 1, 4);
+
+ switch (fourBitEncodationMethod) {
+ case 4:
+ return new AI013103decoder(information);
+ case 5:
+ return new AI01320xDecoder(information);
+ }
+
+ int fiveBitEncodationMethod = GeneralAppIdDecoder.extractNumericValueFromBitArray(information, 1, 5);
+ switch (fiveBitEncodationMethod) {
+ case 12:
+ return new AI01392xDecoder(information);
+ case 13:
+ return new AI01393xDecoder(information);
+ }
+
+ int sevenBitEncodationMethod = GeneralAppIdDecoder.extractNumericValueFromBitArray(information, 1, 7);
+ switch (sevenBitEncodationMethod) {
+ case 56:
+ return new AI013x0x1xDecoder(information, "310", "11");
+ case 57:
+ return new AI013x0x1xDecoder(information, "320", "11");
+ case 58:
+ return new AI013x0x1xDecoder(information, "310", "13");
+ case 59:
+ return new AI013x0x1xDecoder(information, "320", "13");
+ case 60:
+ return new AI013x0x1xDecoder(information, "310", "15");
+ case 61:
+ return new AI013x0x1xDecoder(information, "320", "15");
+ case 62:
+ return new AI013x0x1xDecoder(information, "310", "17");
+ case 63:
+ return new AI013x0x1xDecoder(information, "320", "17");
+ }
+
+ throw new IllegalStateException("unknown decoder: " + information);
+ }
+
+ protected final BitArray getInformation() {
+ return information;
+ }
+
+ protected final GeneralAppIdDecoder getGeneralDecoder() {
+ return generalDecoder;
+ }
+
+ public abstract String parseInformation() throws NotFoundException, FormatException;
+
+}
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..2c7b136
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/AnyAIDecoder.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class AnyAIDecoder extends AbstractExpandedDecoder {
+
+ private static final int HEADER_SIZE = 2 + 1 + 2;
+
+ AnyAIDecoder(BitArray information) {
+ super(information);
+ }
+
+ @Override
+ public String parseInformation() throws NotFoundException, FormatException {
+ StringBuilder buf = new StringBuilder();
+ return this.getGeneralDecoder().decodeAllCodes(buf, HEADER_SIZE);
+ }
+}
diff --git a/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..bba0ebd
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/BlockParsedResult.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class BlockParsedResult {
+
+ private final DecodedInformation decodedInformation;
+ private final boolean finished;
+
+ BlockParsedResult(boolean finished) {
+ this(null, finished);
+ }
+
+ BlockParsedResult(DecodedInformation information, boolean finished) {
+ this.finished = finished;
+ this.decodedInformation = information;
+ }
+
+ DecodedInformation getDecodedInformation() {
+ return this.decodedInformation;
+ }
+
+ boolean isFinished() {
+ return this.finished;
+ }
+}
diff --git a/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..a92b011
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/CurrentParsingState.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ */
+final class CurrentParsingState {
+
+ private int position;
+ private State encoding;
+
+ CurrentParsingState() {
+ this.position = 0;
+ this.encoding = State.NUMERIC;
+ }
+
+ int getPosition() {
+ return position;
+ }
+
+ void setPosition(int position) {
+ this.position = position;
+ }
+
+ void incrementPosition(int delta) {
+ position += delta;
+ }
+
+ boolean isAlpha() {
+ return this.encoding == State.ALPHA;
+ }
+
+ boolean isNumeric() {
+ return this.encoding == State.NUMERIC;
+ }
+
+ boolean isIsoIec646() {
+ return this.encoding == State.ISO_IEC_646;
+ }
+
+ void setNumeric() {
+ this.encoding = State.NUMERIC;
+ }
+
+ void setAlpha() {
+ this.encoding = State.ALPHA;
+ }
+
+ void setIsoIec646() {
+ this.encoding = State.ISO_IEC_646;
+ }
+
+ private enum State {
+ NUMERIC,
+ ALPHA,
+ 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..4e617d9
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/DecodedChar.java
@@ -0,0 +1,51 @@
+/*
+ * 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 {
+
+ static final char FNC1 = '$'; // It's not in Alphanumeric neither in ISO/IEC 646 charset
+ private final char value;
+
+ 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..c0179a3
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/DecodedInformation.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class DecodedInformation extends DecodedObject {
+
+ private final String newString;
+ private final int remainingValue;
+ private final boolean remaining;
+
+ DecodedInformation(int newPosition, String newString) {
+ super(newPosition);
+ this.newString = newString;
+ this.remaining = false;
+ this.remainingValue = 0;
+ }
+
+ DecodedInformation(int newPosition, String newString, int remainingValue) {
+ super(newPosition);
+ this.remaining = true;
+ this.remainingValue = remainingValue;
+ this.newString = newString;
+ }
+
+ String getNewString() {
+ return this.newString;
+ }
+
+ boolean isRemaining() {
+ return this.remaining;
+ }
+
+ int getRemainingValue() {
+ return this.remainingValue;
+ }
+}
diff --git a/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..756d2ee
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/DecodedNumeric.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.FormatException;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class DecodedNumeric extends DecodedObject {
+
+ static final int FNC1 = 10;
+ private final int firstDigit;
+ private final int secondDigit;
+
+ DecodedNumeric(int newPosition, int firstDigit, int secondDigit) throws FormatException {
+ super(newPosition);
+
+ if (firstDigit < 0 || firstDigit > 10 || secondDigit < 0 || secondDigit > 10) {
+ throw FormatException.getFormatInstance();
+ }
+
+ this.firstDigit = firstDigit;
+ this.secondDigit = secondDigit;
+ }
+
+ int getFirstDigit() {
+ return this.firstDigit;
+ }
+
+ int getSecondDigit() {
+ return this.secondDigit;
+ }
+
+ int getValue() {
+ return this.firstDigit * 10 + this.secondDigit;
+ }
+
+ boolean isFirstDigitFNC1() {
+ return this.firstDigit == FNC1;
+ }
+
+ boolean isSecondDigitFNC1() {
+ return this.secondDigit == FNC1;
+ }
+
+ boolean isAnyFNC1() {
+ return this.firstDigit == FNC1 || this.secondDigit == FNC1;
+ }
+
+}
diff --git a/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..59cf0e8
--- /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 {
+
+ private final int newPosition;
+
+ DecodedObject(int newPosition) {
+ this.newPosition = newPosition;
+ }
+
+ final 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..d7aa96d
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/FieldParser.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.NotFoundException;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class FieldParser {
+
+ private static final Object VARIABLE_LENGTH = new Object();
+
+ private static final Object[][] TWO_DIGIT_DATA_LENGTH = {
+ // "DIGITS", new Integer(LENGTH)
+ // or
+ // "DIGITS", VARIABLE_LENGTH, new Integer(MAX_SIZE)
+
+ {"00", 18},
+ {"01", 14},
+ {"02", 14},
+
+ {"10", VARIABLE_LENGTH, 20},
+ {"11", 6},
+ {"12", 6},
+ {"13", 6},
+ {"15", 6},
+ {"17", 6},
+
+ {"20", 2},
+ {"21", VARIABLE_LENGTH, 20},
+ {"22", VARIABLE_LENGTH, 29},
+
+ {"30", VARIABLE_LENGTH, 8},
+ {"37", VARIABLE_LENGTH, 8},
+
+ //internal company codes
+ {"90", VARIABLE_LENGTH, 30},
+ {"91", VARIABLE_LENGTH, 30},
+ {"92", VARIABLE_LENGTH, 30},
+ {"93", VARIABLE_LENGTH, 30},
+ {"94", VARIABLE_LENGTH, 30},
+ {"95", VARIABLE_LENGTH, 30},
+ {"96", VARIABLE_LENGTH, 30},
+ {"97", VARIABLE_LENGTH, 30},
+ {"98", VARIABLE_LENGTH, 30},
+ {"99", VARIABLE_LENGTH, 30},
+ };
+
+ private static final Object[][] THREE_DIGIT_DATA_LENGTH = {
+ // Same format as above
+
+ {"240", VARIABLE_LENGTH, 30},
+ {"241", VARIABLE_LENGTH, 30},
+ {"242", VARIABLE_LENGTH, 6},
+ {"250", VARIABLE_LENGTH, 30},
+ {"251", VARIABLE_LENGTH, 30},
+ {"253", VARIABLE_LENGTH, 17},
+ {"254", VARIABLE_LENGTH, 20},
+
+ {"400", VARIABLE_LENGTH, 30},
+ {"401", VARIABLE_LENGTH, 30},
+ {"402", 17},
+ {"403", VARIABLE_LENGTH, 30},
+ {"410", 13},
+ {"411", 13},
+ {"412", 13},
+ {"413", 13},
+ {"414", 13},
+ {"420", VARIABLE_LENGTH, 20},
+ {"421", VARIABLE_LENGTH, 15},
+ {"422", 3},
+ {"423", VARIABLE_LENGTH, 15},
+ {"424", 3},
+ {"425", 3},
+ {"426", 3},
+ };
+
+ private static final Object[][] THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH = {
+ // Same format as above
+
+ {"310", 6},
+ {"311", 6},
+ {"312", 6},
+ {"313", 6},
+ {"314", 6},
+ {"315", 6},
+ {"316", 6},
+ {"320", 6},
+ {"321", 6},
+ {"322", 6},
+ {"323", 6},
+ {"324", 6},
+ {"325", 6},
+ {"326", 6},
+ {"327", 6},
+ {"328", 6},
+ {"329", 6},
+ {"330", 6},
+ {"331", 6},
+ {"332", 6},
+ {"333", 6},
+ {"334", 6},
+ {"335", 6},
+ {"336", 6},
+ {"340", 6},
+ {"341", 6},
+ {"342", 6},
+ {"343", 6},
+ {"344", 6},
+ {"345", 6},
+ {"346", 6},
+ {"347", 6},
+ {"348", 6},
+ {"349", 6},
+ {"350", 6},
+ {"351", 6},
+ {"352", 6},
+ {"353", 6},
+ {"354", 6},
+ {"355", 6},
+ {"356", 6},
+ {"357", 6},
+ {"360", 6},
+ {"361", 6},
+ {"362", 6},
+ {"363", 6},
+ {"364", 6},
+ {"365", 6},
+ {"366", 6},
+ {"367", 6},
+ {"368", 6},
+ {"369", 6},
+ {"390", VARIABLE_LENGTH, 15},
+ {"391", VARIABLE_LENGTH, 18},
+ {"392", VARIABLE_LENGTH, 15},
+ {"393", VARIABLE_LENGTH, 18},
+ {"703", VARIABLE_LENGTH, 30}
+ };
+
+ private static final Object[][] FOUR_DIGIT_DATA_LENGTH = {
+ // Same format as above
+
+ {"7001", 13},
+ {"7002", VARIABLE_LENGTH, 30},
+ {"7003", 10},
+
+ {"8001", 14},
+ {"8002", VARIABLE_LENGTH, 20},
+ {"8003", VARIABLE_LENGTH, 30},
+ {"8004", VARIABLE_LENGTH, 30},
+ {"8005", 6},
+ {"8006", 18},
+ {"8007", VARIABLE_LENGTH, 30},
+ {"8008", VARIABLE_LENGTH, 12},
+ {"8018", 18},
+ {"8020", VARIABLE_LENGTH, 25},
+ {"8100", 6},
+ {"8101", 10},
+ {"8102", 2},
+ {"8110", VARIABLE_LENGTH, 70},
+ {"8200", VARIABLE_LENGTH, 70},
+ };
+
+ private FieldParser() {
+ }
+
+ static String parseFieldsInGeneralPurpose(String rawInformation) throws NotFoundException {
+ if (rawInformation.isEmpty()) {
+ return null;
+ }
+
+ // Processing 2-digit AIs
+
+ if (rawInformation.length() < 2) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String firstTwoDigits = rawInformation.substring(0, 2);
+
+ for (Object[] dataLength : TWO_DIGIT_DATA_LENGTH) {
+ if (dataLength[0].equals(firstTwoDigits)) {
+ if (dataLength[1] == VARIABLE_LENGTH) {
+ return processVariableAI(2, (Integer) dataLength[2], rawInformation);
+ }
+ return processFixedAI(2, (Integer) dataLength[1], rawInformation);
+ }
+ }
+
+ if (rawInformation.length() < 3) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String firstThreeDigits = rawInformation.substring(0, 3);
+
+ for (Object[] dataLength : THREE_DIGIT_DATA_LENGTH) {
+ if (dataLength[0].equals(firstThreeDigits)) {
+ if (dataLength[1] == VARIABLE_LENGTH) {
+ return processVariableAI(3, (Integer) dataLength[2], rawInformation);
+ }
+ return processFixedAI(3, (Integer) dataLength[1], rawInformation);
+ }
+ }
+
+
+ for (Object[] dataLength : THREE_DIGIT_PLUS_DIGIT_DATA_LENGTH) {
+ if (dataLength[0].equals(firstThreeDigits)) {
+ if (dataLength[1] == VARIABLE_LENGTH) {
+ return processVariableAI(4, (Integer) dataLength[2], rawInformation);
+ }
+ return processFixedAI(4, (Integer) dataLength[1], rawInformation);
+ }
+ }
+
+ if (rawInformation.length() < 4) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String firstFourDigits = rawInformation.substring(0, 4);
+
+ for (Object[] dataLength : FOUR_DIGIT_DATA_LENGTH) {
+ if (dataLength[0].equals(firstFourDigits)) {
+ if (dataLength[1] == VARIABLE_LENGTH) {
+ return processVariableAI(4, (Integer) dataLength[2], rawInformation);
+ }
+ return processFixedAI(4, (Integer) dataLength[1], rawInformation);
+ }
+ }
+
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ private static String processFixedAI(int aiSize, int fieldSize, String rawInformation) throws NotFoundException {
+ if (rawInformation.length() < aiSize) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String ai = rawInformation.substring(0, aiSize);
+
+ if (rawInformation.length() < aiSize + fieldSize) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+
+ String field = rawInformation.substring(aiSize, aiSize + fieldSize);
+ String remaining = rawInformation.substring(aiSize + fieldSize);
+ String result = '(' + ai + ')' + field;
+ String parsedAI = parseFieldsInGeneralPurpose(remaining);
+ return parsedAI == null ? result : result + parsedAI;
+ }
+
+ private static String processVariableAI(int aiSize, int variableFieldSize, String rawInformation)
+ throws NotFoundException {
+ String ai = rawInformation.substring(0, aiSize);
+ int maxSize;
+ if (rawInformation.length() < aiSize + variableFieldSize) {
+ maxSize = rawInformation.length();
+ } else {
+ maxSize = aiSize + variableFieldSize;
+ }
+ String field = rawInformation.substring(aiSize, maxSize);
+ String remaining = rawInformation.substring(maxSize);
+ String result = '(' + ai + ')' + field;
+ String parsedAI = parseFieldsInGeneralPurpose(remaining);
+ return parsedAI == null ? result : result + parsedAI;
+ }
+}
diff --git a/src/com/google/zxing/oned/rss/expanded/decoders/GeneralAppIdDecoder.java b/src/com/google/zxing/oned/rss/expanded/decoders/GeneralAppIdDecoder.java
new file mode 100644
index 0000000..9193607
--- /dev/null
+++ b/src/com/google/zxing/oned/rss/expanded/decoders/GeneralAppIdDecoder.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2010 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * These authors would like to acknowledge the Spanish Ministry of Industry,
+ * Tourism and Trade, for the support in the project TSI020301-2008-2
+ * "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
+ * Mobile Dynamic Environments", led by Treelogic
+ * ( http://www.treelogic.com/ ):
+ *
+ * http://www.piramidepse.com/
+ */
+
+package com.google.zxing.oned.rss.expanded.decoders;
+
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
+import com.google.zxing.common.BitArray;
+
+/**
+ * @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
+ * @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
+ */
+final class GeneralAppIdDecoder {
+
+ private final BitArray information;
+ private final CurrentParsingState current = new CurrentParsingState();
+ private final StringBuilder buffer = new StringBuilder();
+
+ GeneralAppIdDecoder(BitArray information) {
+ this.information = information;
+ }
+
+ static int extractNumericValueFromBitArray(BitArray information, int pos, int bits) {
+ int value = 0;
+ for (int i = 0; i < bits; ++i) {
+ if (information.get(pos + i)) {
+ value |= 1 << (bits - i - 1);
+ }
+ }
+
+ return value;
+ }
+
+ String decodeAllCodes(StringBuilder buff, int initialPosition) throws NotFoundException, FormatException {
+ int currentPosition = initialPosition;
+ String remaining = null;
+ do {
+ DecodedInformation info = this.decodeGeneralPurposeField(currentPosition, remaining);
+ String parsedFields = FieldParser.parseFieldsInGeneralPurpose(info.getNewString());
+ if (parsedFields != null) {
+ buff.append(parsedFields);
+ }
+ if (info.isRemaining()) {
+ remaining = String.valueOf(info.getRemainingValue());
+ } else {
+ remaining = null;
+ }
+
+ if (currentPosition == info.getNewPosition()) {// No step forward!
+ break;
+ }
+ currentPosition = info.getNewPosition();
+ } while (true);
+
+ return buff.toString();
+ }
+
+ private boolean isStillNumeric(int pos) {
+ // It's numeric if it still has 7 positions
+ // and one of the first 4 bits is "1".
+ if (pos + 7 > this.information.getSize()) {
+ return pos + 4 <= this.information.getSize();
+ }
+
+ for (int i = pos; i < pos + 3; ++i) {
+ if (this.information.get(i)) {
+ return true;
+ }
+ }
+
+ return this.information.get(pos + 3);
+ }
+
+ private DecodedNumeric decodeNumeric(int pos) throws FormatException {
+ if (pos + 7 > this.information.getSize()) {
+ int numeric = extractNumericValueFromBitArray(pos, 4);
+ if (numeric == 0) {
+ return new DecodedNumeric(this.information.getSize(), DecodedNumeric.FNC1, DecodedNumeric.FNC1);
+ }
+ return new DecodedNumeric(this.information.getSize(), numeric - 1, DecodedNumeric.FNC1);
+ }
+ int numeric = extractNumericValueFromBitArray(pos, 7);
+
+ int digit1 = (numeric - 8) / 11;
+ int digit2 = (numeric - 8) % 11;
+
+ return new DecodedNumeric(pos + 7, digit1, digit2);
+ }
+
+ int extractNumericValueFromBitArray(int pos, int bits) {
+ return extractNumericValueFromBitArray(this.information, pos, bits);
+ }
+
+ DecodedInformation decodeGeneralPurposeField(int pos, String remaining) throws FormatException {
+ this.buffer.setLength(0);
+
+ if (remaining != null) {
+ this.buffer.append(remaining);
+ }
+
+ this.current.setPosition(pos);
+
+ DecodedInformation lastDecoded = parseBlocks();
+ if (lastDecoded != null && lastDecoded.isRemaining()) {
+ return new DecodedInformation(this.current.getPosition(), this.buffer.toString(), lastDecoded.getRemainingValue());
+ }
+ return new DecodedInformation(this.current.getPosition(), this.buffer.toString());
+ }
+
+ private DecodedInformation parseBlocks() throws FormatException {
+ boolean isFinished;
+ BlockParsedResult result;
+ do {
+ int initialPosition = current.getPosition();
+
+ if (current.isAlpha()) {
+ result = parseAlphaBlock();
+ isFinished = result.isFinished();
+ } else if (current.isIsoIec646()) {
+ result = parseIsoIec646Block();
+ isFinished = result.isFinished();
+ } else { // it must be numeric
+ result = parseNumericBlock();
+ isFinished = result.isFinished();
+ }
+
+ boolean positionChanged = initialPosition != current.getPosition();
+ if (!positionChanged && !isFinished) {
+ break;
+ }
+ } while (!isFinished);
+
+ return result.getDecodedInformation();
+ }
+
+ private BlockParsedResult parseNumericBlock() throws FormatException {
+ while (isStillNumeric(current.getPosition())) {
+ DecodedNumeric numeric = decodeNumeric(current.getPosition());
+ current.setPosition(numeric.getNewPosition());
+
+ if (numeric.isFirstDigitFNC1()) {
+ DecodedInformation information;
+ if (numeric.isSecondDigitFNC1()) {
+ information = new DecodedInformation(current.getPosition(), buffer.toString());
+ } else {
+ information = new DecodedInformation(current.getPosition(), buffer.toString(), numeric.getSecondDigit());
+ }
+ return new BlockParsedResult(information, true);
+ }
+ buffer.append(numeric.getFirstDigit());
+
+ if (numeric.isSecondDigitFNC1()) {
+ DecodedInformation information = new DecodedInformation(current.getPosition(), buffer.toString());
+ return new BlockParsedResult(information, true);
+ }
+ buffer.append(numeric.getSecondDigit());
+ }
+
+ if (isNumericToAlphaNumericLatch(current.getPosition())) {
+ current.setAlpha();
+ current.incrementPosition(4);
+ }
+ return new BlockParsedResult(false);
+ }
+
+ private BlockParsedResult parseIsoIec646Block() throws FormatException {
+ while (isStillIsoIec646(current.getPosition())) {
+ DecodedChar iso = decodeIsoIec646(current.getPosition());
+ current.setPosition(iso.getNewPosition());
+
+ if (iso.isFNC1()) {
+ DecodedInformation information = new DecodedInformation(current.getPosition(), buffer.toString());
+ return new BlockParsedResult(information, true);
+ }
+ buffer.append(iso.getValue());
+ }
+
+ if (isAlphaOr646ToNumericLatch(current.getPosition())) {
+ current.incrementPosition(3);
+ current.setNumeric();
+ } else if (isAlphaTo646ToAlphaLatch(current.getPosition())) {
+ if (current.getPosition() + 5 < this.information.getSize()) {
+ current.incrementPosition(5);
+ } else {
+ current.setPosition(this.information.getSize());
+ }
+
+ current.setAlpha();
+ }
+ return new BlockParsedResult(false);
+ }
+
+ private BlockParsedResult parseAlphaBlock() {
+ while (isStillAlpha(current.getPosition())) {
+ DecodedChar alpha = decodeAlphanumeric(current.getPosition());
+ current.setPosition(alpha.getNewPosition());
+
+ if (alpha.isFNC1()) {
+ DecodedInformation information = new DecodedInformation(current.getPosition(), buffer.toString());
+ return new BlockParsedResult(information, true); //end of the char block
+ }
+
+ buffer.append(alpha.getValue());
+ }
+
+ if (isAlphaOr646ToNumericLatch(current.getPosition())) {
+ current.incrementPosition(3);
+ current.setNumeric();
+ } else if (isAlphaTo646ToAlphaLatch(current.getPosition())) {
+ if (current.getPosition() + 5 < this.information.getSize()) {
+ current.incrementPosition(5);
+ } else {
+ current.setPosition(this.information.getSize());
+ }
+
+ current.setIsoIec646();
+ }
+ return new BlockParsedResult(false);
+ }
+
+ private boolean isStillIsoIec646(int pos) {
+ if (pos + 5 > this.information.getSize()) {
+ return false;
+ }
+
+ int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
+ if (fiveBitValue >= 5 && fiveBitValue < 16) {
+ return true;
+ }
+
+ if (pos + 7 > this.information.getSize()) {
+ return false;
+ }
+
+ int sevenBitValue = extractNumericValueFromBitArray(pos, 7);
+ if (sevenBitValue >= 64 && sevenBitValue < 116) {
+ return true;
+ }
+
+ if (pos + 8 > this.information.getSize()) {
+ return false;
+ }
+
+ int eightBitValue = extractNumericValueFromBitArray(pos, 8);
+ return eightBitValue >= 232 && eightBitValue < 253;
+
+ }
+
+ private DecodedChar decodeIsoIec646(int pos) throws FormatException {
+ int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
+ if (fiveBitValue == 15) {
+ return new DecodedChar(pos + 5, DecodedChar.FNC1);
+ }
+
+ if (fiveBitValue >= 5 && fiveBitValue < 15) {
+ return new DecodedChar(pos + 5, (char) ('0' + fiveBitValue - 5));
+ }
+
+ int sevenBitValue = extractNumericValueFromBitArray(pos, 7);
+
+ if (sevenBitValue >= 64 && sevenBitValue < 90) {
+ return new DecodedChar(pos + 7, (char) (sevenBitValue + 1));
+ }
+
+ if (sevenBitValue >= 90 && sevenBitValue < 116) {
+ return new DecodedChar(pos + 7, (char) (sevenBitValue + 7));
+ }
+
+ int eightBitValue = extractNumericValueFromBitArray(pos, 8);
+ char c;
+ switch (eightBitValue) {
+ case 232:
+ c = '!';
+ break;
+ case 233:
+ c = '"';
+ break;
+ case 234:
+ c = '%';
+ break;
+ case 235:
+ c = '&';
+ break;
+ case 236:
+ c = '\'';
+ break;
+ case 237:
+ c = '(';
+ break;
+ case 238:
+ c = ')';
+ break;
+ case 239:
+ c = '*';
+ break;
+ case 240:
+ c = '+';
+ break;
+ case 241:
+ c = ',';
+ break;
+ case 242:
+ c = '-';
+ break;
+ case 243:
+ c = '.';
+ break;
+ case 244:
+ c = '/';
+ break;
+ case 245:
+ c = ':';
+ break;
+ case 246:
+ c = ';';
+ break;
+ case 247:
+ c = '<';
+ break;
+ case 248:
+ c = '=';
+ break;
+ case 249:
+ c = '>';
+ break;
+ case 250:
+ c = '?';
+ break;
+ case 251:
+ c = '_';
+ break;
+ case 252:
+ c = ' ';
+ break;
+ default:
+ throw FormatException.getFormatInstance();
+ }
+ return new DecodedChar(pos + 8, c);
+ }
+
+ private boolean isStillAlpha(int pos) {
+ if (pos + 5 > this.information.getSize()) {
+ return false;
+ }
+
+ // We now check if it's a valid 5-bit value (0..9 and FNC1)
+ int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
+ if (fiveBitValue >= 5 && fiveBitValue < 16) {
+ return true;
+ }
+
+ if (pos + 6 > this.information.getSize()) {
+ return false;
+ }
+
+ int sixBitValue = extractNumericValueFromBitArray(pos, 6);
+ return sixBitValue >= 16 && sixBitValue < 63; // 63 not included
+ }
+
+ private DecodedChar decodeAlphanumeric(int pos) {
+ int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
+ if (fiveBitValue == 15) {
+ return new DecodedChar(pos + 5, DecodedChar.FNC1);
+ }
+
+ if (fiveBitValue >= 5 && fiveBitValue < 15) {
+ return new DecodedChar(pos + 5, (char) ('0' + fiveBitValue - 5));
+ }
+
+ int sixBitValue = extractNumericValueFromBitArray(pos, 6);
+
+ if (sixBitValue >= 32 && sixBitValue < 58) {
+ return new DecodedChar(pos + 6, (char) (sixBitValue + 33));
+ }
+
+ char c;
+ switch (sixBitValue) {
+ case 58:
+ c = '*';
+ break;
+ case 59:
+ c = ',';
+ break;
+ case 60:
+ c = '-';
+ break;
+ case 61:
+ c = '.';
+ break;
+ case 62:
+ c = '/';
+ break;
+ default:
+ throw new IllegalStateException("Decoding invalid alphanumeric value: " + sixBitValue);
+ }
+ return new DecodedChar(pos + 6, c);
+ }
+
+ private boolean isAlphaTo646ToAlphaLatch(int pos) {
+ if (pos + 1 > this.information.getSize()) {
+ return false;
+ }
+
+ for (int i = 0; i < 5 && i + pos < this.information.getSize(); ++i) {
+ if (i == 2) {
+ if (!this.information.get(pos + 2)) {
+ return false;
+ }
+ } else if (this.information.get(pos + i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private boolean isAlphaOr646ToNumericLatch(int pos) {
+ // Next is alphanumeric if there are 3 positions and they are all zeros
+ if (pos + 3 > this.information.getSize()) {
+ return false;
+ }
+
+ for (int i = pos; i < pos + 3; ++i) {
+ if (this.information.get(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isNumericToAlphaNumericLatch(int pos) {
+ // Next is alphanumeric if there are 4 positions and they are all zeros, or
+ // if there is a subset of this just before the end of the symbol
+ if (pos + 1 > this.information.getSize()) {
+ return false;
+ }
+
+ for (int i = 0; i < 4 && i + pos < this.information.getSize(); ++i) {
+ if (this.information.get(pos + i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/src/com/google/zxing/pdf417/PDF417Common.java b/src/com/google/zxing/pdf417/PDF417Common.java
new file mode 100644
index 0000000..f79f037
--- /dev/null
+++ b/src/com/google/zxing/pdf417/PDF417Common.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2009 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.google.zxing.pdf417;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * @author SITA Lab (kevin.osullivan@sita.aero)
+ * @author Guenther Grau
+ */
+public final class PDF417Common {
+
+ public static final int NUMBER_OF_CODEWORDS = 929;
+ // Maximum Codewords (Data + Error).
+ public static final int MAX_CODEWORDS_IN_BARCODE = NUMBER_OF_CODEWORDS - 1;
+ public static final int MIN_ROWS_IN_BARCODE = 3;
+ public static final int MAX_ROWS_IN_BARCODE = 90;
+ // One left row indication column + max 30 data columns + one right row indicator column
+ //public static final int MAX_CODEWORDS_IN_ROW = 32;
+ public static final int MODULES_IN_CODEWORD = 17;
+ public static final int MODULES_IN_STOP_PATTERN = 18;
+ public static final int BARS_IN_MODULE = 8;
+ /**
+ * The sorted table of all possible symbols. Extracted from the PDF417
+ * specification. The index of a symbol in this table corresponds to the
+ * index into the codeword table.
+ */
+ public static final int[] SYMBOL_TABLE = {
+ 0x1025e, 0x1027a, 0x1029e, 0x102bc, 0x102f2, 0x102f4, 0x1032e, 0x1034e, 0x1035c, 0x10396, 0x103a6, 0x103ac,
+ 0x10422, 0x10428, 0x10436, 0x10442, 0x10444, 0x10448, 0x10450, 0x1045e, 0x10466, 0x1046c, 0x1047a, 0x10482,
+ 0x1049e, 0x104a0, 0x104bc, 0x104c6, 0x104d8, 0x104ee, 0x104f2, 0x104f4, 0x10504, 0x10508, 0x10510, 0x1051e,
+ 0x10520, 0x1053c, 0x10540, 0x10578, 0x10586, 0x1058c, 0x10598, 0x105b0, 0x105be, 0x105ce, 0x105dc, 0x105e2,
+ 0x105e4, 0x105e8, 0x105f6, 0x1062e, 0x1064e, 0x1065c, 0x1068e, 0x1069c, 0x106b8, 0x106de, 0x106fa, 0x10716,
+ 0x10726, 0x1072c, 0x10746, 0x1074c, 0x10758, 0x1076e, 0x10792, 0x10794, 0x107a2, 0x107a4, 0x107a8, 0x107b6,
+ 0x10822, 0x10828, 0x10842, 0x10848, 0x10850, 0x1085e, 0x10866, 0x1086c, 0x1087a, 0x10882, 0x10884, 0x10890,
+ 0x1089e, 0x108a0, 0x108bc, 0x108c6, 0x108cc, 0x108d8, 0x108ee, 0x108f2, 0x108f4, 0x10902, 0x10908, 0x1091e,
+ 0x10920, 0x1093c, 0x10940, 0x10978, 0x10986, 0x10998, 0x109b0, 0x109be, 0x109ce, 0x109dc, 0x109e2, 0x109e4,
+ 0x109e8, 0x109f6, 0x10a08, 0x10a10, 0x10a1e, 0x10a20, 0x10a3c, 0x10a40, 0x10a78, 0x10af0, 0x10b06, 0x10b0c,
+ 0x10b18, 0x10b30, 0x10b3e, 0x10b60, 0x10b7c, 0x10b8e, 0x10b9c, 0x10bb8, 0x10bc2, 0x10bc4, 0x10bc8, 0x10bd0,
+ 0x10bde, 0x10be6, 0x10bec, 0x10c2e, 0x10c4e, 0x10c5c, 0x10c62, 0x10c64, 0x10c68, 0x10c76, 0x10c8e, 0x10c9c,
+ 0x10cb8, 0x10cc2, 0x10cc4, 0x10cc8, 0x10cd0, 0x10cde, 0x10ce6, 0x10cec, 0x10cfa, 0x10d0e, 0x10d1c, 0x10d38,
+ 0x10d70, 0x10d7e, 0x10d82, 0x10d84, 0x10d88, 0x10d90, 0x10d9e, 0x10da0, 0x10dbc, 0x10dc6, 0x10dcc, 0x10dd8,
+ 0x10dee, 0x10df2, 0x10df4, 0x10e16, 0x10e26, 0x10e2c, 0x10e46, 0x10e58, 0x10e6e, 0x10e86, 0x10e8c, 0x10e98,
+ 0x10eb0, 0x10ebe, 0x10ece, 0x10edc, 0x10f0a, 0x10f12, 0x10f14, 0x10f22, 0x10f28, 0x10f36, 0x10f42, 0x10f44,
+ 0x10f48, 0x10f50, 0x10f5e, 0x10f66, 0x10f6c, 0x10fb2, 0x10fb4, 0x11022, 0x11028, 0x11042, 0x11048, 0x11050,
+ 0x1105e, 0x1107a, 0x11082, 0x11084, 0x11090, 0x1109e, 0x110a0, 0x110bc, 0x110c6, 0x110cc, 0x110d8, 0x110ee,
+ 0x110f2, 0x110f4, 0x11102, 0x1111e, 0x11120, 0x1113c, 0x11140, 0x11178, 0x11186, 0x11198, 0x111b0, 0x111be,
+ 0x111ce, 0x111dc, 0x111e2, 0x111e4, 0x111e8, 0x111f6, 0x11208, 0x1121e, 0x11220, 0x11278, 0x112f0, 0x1130c,
+ 0x11330, 0x1133e, 0x11360, 0x1137c, 0x1138e, 0x1139c, 0x113b8, 0x113c2, 0x113c8, 0x113d0, 0x113de, 0x113e6,
+ 0x113ec, 0x11408, 0x11410, 0x1141e, 0x11420, 0x1143c, 0x11440, 0x11478, 0x114f0, 0x115e0, 0x1160c, 0x11618,
+ 0x11630, 0x1163e, 0x11660, 0x1167c, 0x116c0, 0x116f8, 0x1171c, 0x11738, 0x11770, 0x1177e, 0x11782, 0x11784,
+ 0x11788, 0x11790, 0x1179e, 0x117a0, 0x117bc, 0x117c6, 0x117cc, 0x117d8, 0x117ee, 0x1182e, 0x11834, 0x1184e,
+ 0x1185c, 0x11862, 0x11864, 0x11868, 0x11876, 0x1188e, 0x1189c, 0x118b8, 0x118c2, 0x118c8, 0x118d0, 0x118de,
+ 0x118e6, 0x118ec, 0x118fa, 0x1190e, 0x1191c, 0x11938, 0x11970, 0x1197e, 0x11982, 0x11984, 0x11990, 0x1199e,
+ 0x119a0, 0x119bc, 0x119c6, 0x119cc, 0x119d8, 0x119ee, 0x119f2, 0x119f4, 0x11a0e, 0x11a1c, 0x11a38, 0x11a70,
+ 0x11a7e, 0x11ae0, 0x11afc, 0x11b08, 0x11b10, 0x11b1e, 0x11b20, 0x11b3c, 0x11b40, 0x11b78, 0x11b8c, 0x11b98,
+ 0x11bb0, 0x11bbe, 0x11bce, 0x11bdc, 0x11be2, 0x11be4, 0x11be8, 0x11bf6, 0x11c16, 0x11c26, 0x11c2c, 0x11c46,
+ 0x11c4c, 0x11c58, 0x11c6e, 0x11c86, 0x11c98, 0x11cb0, 0x11cbe, 0x11cce, 0x11cdc, 0x11ce2, 0x11ce4, 0x11ce8,
+ 0x11cf6, 0x11d06, 0x11d0c, 0x11d18, 0x11d30, 0x11d3e, 0x11d60, 0x11d7c, 0x11d8e, 0x11d9c, 0x11db8, 0x11dc4,
+ 0x11dc8, 0x11dd0, 0x11dde, 0x11de6, 0x11dec, 0x11dfa, 0x11e0a, 0x11e12, 0x11e14, 0x11e22, 0x11e24, 0x11e28,
+ 0x11e36, 0x11e42, 0x11e44, 0x11e50, 0x11e5e, 0x11e66, 0x11e6c, 0x11e82, 0x11e84, 0x11e88, 0x11e90, 0x11e9e,
+ 0x11ea0, 0x11ebc, 0x11ec6, 0x11ecc, 0x11ed8, 0x11eee, 0x11f1a, 0x11f2e, 0x11f32, 0x11f34, 0x11f4e, 0x11f5c,
+ 0x11f62, 0x11f64, 0x11f68, 0x11f76, 0x12048, 0x1205e, 0x12082, 0x12084, 0x12090, 0x1209e, 0x120a0, 0x120bc,
+ 0x120d8, 0x120f2, 0x120f4, 0x12108, 0x1211e, 0x12120, 0x1213c, 0x12140, 0x12178, 0x12186, 0x12198, 0x121b0,
+ 0x121be, 0x121e2, 0x121e4, 0x121e8, 0x121f6, 0x12204, 0x12210, 0x1221e, 0x12220, 0x12278, 0x122f0, 0x12306,
+ 0x1230c, 0x12330, 0x1233e, 0x12360, 0x1237c, 0x1238e, 0x1239c, 0x123b8, 0x123c2, 0x123c8, 0x123d0, 0x123e6,
+ 0x123ec, 0x1241e, 0x12420, 0x1243c, 0x124f0, 0x125e0, 0x12618, 0x1263e, 0x12660, 0x1267c, 0x126c0, 0x126f8,
+ 0x12738, 0x12770, 0x1277e, 0x12782, 0x12784, 0x12790, 0x1279e, 0x127a0, 0x127bc, 0x127c6, 0x127cc, 0x127d8,
+ 0x127ee, 0x12820, 0x1283c, 0x12840, 0x12878, 0x128f0, 0x129e0, 0x12bc0, 0x12c18, 0x12c30, 0x12c3e, 0x12c60,
+ 0x12c7c, 0x12cc0, 0x12cf8, 0x12df0, 0x12e1c, 0x12e38, 0x12e70, 0x12e7e, 0x12ee0, 0x12efc, 0x12f04, 0x12f08,
+ 0x12f10, 0x12f20, 0x12f3c, 0x12f40, 0x12f78, 0x12f86, 0x12f8c, 0x12f98, 0x12fb0, 0x12fbe, 0x12fce, 0x12fdc,
+ 0x1302e, 0x1304e, 0x1305c, 0x13062, 0x13068, 0x1308e, 0x1309c, 0x130b8, 0x130c2, 0x130c8, 0x130d0, 0x130de,
+ 0x130ec, 0x130fa, 0x1310e, 0x13138, 0x13170, 0x1317e, 0x13182, 0x13184, 0x13190, 0x1319e, 0x131a0, 0x131bc,
+ 0x131c6, 0x131cc, 0x131d8, 0x131f2, 0x131f4, 0x1320e, 0x1321c, 0x13270, 0x1327e, 0x132e0, 0x132fc, 0x13308,
+ 0x1331e, 0x13320, 0x1333c, 0x13340, 0x13378, 0x13386, 0x13398, 0x133b0, 0x133be, 0x133ce, 0x133dc, 0x133e2,
+ 0x133e4, 0x133e8, 0x133f6, 0x1340e, 0x1341c, 0x13438, 0x13470, 0x1347e, 0x134e0, 0x134fc, 0x135c0, 0x135f8,
+ 0x13608, 0x13610, 0x1361e, 0x13620, 0x1363c, 0x13640, 0x13678, 0x136f0, 0x1370c, 0x13718, 0x13730, 0x1373e,
+ 0x13760, 0x1377c, 0x1379c, 0x137b8, 0x137c2, 0x137c4, 0x137c8, 0x137d0, 0x137de, 0x137e6, 0x137ec, 0x13816,
+ 0x13826, 0x1382c, 0x13846, 0x1384c, 0x13858, 0x1386e, 0x13874, 0x13886, 0x13898, 0x138b0, 0x138be, 0x138ce,
+ 0x138dc, 0x138e2, 0x138e4, 0x138e8, 0x13906, 0x1390c, 0x13930, 0x1393e, 0x13960, 0x1397c, 0x1398e, 0x1399c,
+ 0x139b8, 0x139c8, 0x139d0, 0x139de, 0x139e6, 0x139ec, 0x139fa, 0x13a06, 0x13a0c, 0x13a18, 0x13a30, 0x13a3e,
+ 0x13a60, 0x13a7c, 0x13ac0, 0x13af8, 0x13b0e, 0x13b1c, 0x13b38, 0x13b70, 0x13b7e, 0x13b88, 0x13b90, 0x13b9e,
+ 0x13ba0, 0x13bbc, 0x13bcc, 0x13bd8, 0x13bee, 0x13bf2, 0x13bf4, 0x13c12, 0x13c14, 0x13c22, 0x13c24, 0x13c28,
+ 0x13c36, 0x13c42, 0x13c48, 0x13c50, 0x13c5e, 0x13c66, 0x13c6c, 0x13c82, 0x13c84, 0x13c90, 0x13c9e, 0x13ca0,
+ 0x13cbc, 0x13cc6, 0x13ccc, 0x13cd8, 0x13cee, 0x13d02, 0x13d04, 0x13d08, 0x13d10, 0x13d1e, 0x13d20, 0x13d3c,
+ 0x13d40, 0x13d78, 0x13d86, 0x13d8c, 0x13d98, 0x13db0, 0x13dbe, 0x13dce, 0x13ddc, 0x13de4, 0x13de8, 0x13df6,
+ 0x13e1a, 0x13e2e, 0x13e32, 0x13e34, 0x13e4e, 0x13e5c, 0x13e62, 0x13e64, 0x13e68, 0x13e76, 0x13e8e, 0x13e9c,
+ 0x13eb8, 0x13ec2, 0x13ec4, 0x13ec8, 0x13ed0, 0x13ede, 0x13ee6, 0x13eec, 0x13f26, 0x13f2c, 0x13f3a, 0x13f46,
+ 0x13f4c, 0x13f58, 0x13f6e, 0x13f72, 0x13f74, 0x14082, 0x1409e, 0x140a0, 0x140bc, 0x14104, 0x14108, 0x14110,
+ 0x1411e, 0x14120, 0x1413c, 0x14140, 0x14178, 0x1418c, 0x14198, 0x141b0, 0x141be, 0x141e2, 0x141e4, 0x141e8,
+ 0x14208, 0x14210, 0x1421e, 0x14220, 0x1423c, 0x14240, 0x14278, 0x142f0, 0x14306, 0x1430c, 0x14318, 0x14330,
+ 0x1433e, 0x14360, 0x1437c, 0x1438e, 0x143c2, 0x143c4, 0x143c8, 0x143d0, 0x143e6, 0x143ec, 0x14408, 0x14410,
+ 0x1441e, 0x14420, 0x1443c, 0x14440, 0x14478, 0x144f0, 0x145e0, 0x1460c, 0x14618, 0x14630, 0x1463e, 0x14660,
+ 0x1467c, 0x146c0, 0x146f8, 0x1471c, 0x14738, 0x14770, 0x1477e, 0x14782, 0x14784, 0x14788, 0x14790, 0x147a0,
+ 0x147bc, 0x147c6, 0x147cc, 0x147d8, 0x147ee, 0x14810, 0x14820, 0x1483c, 0x14840, 0x14878, 0x148f0, 0x149e0,
+ 0x14bc0, 0x14c30, 0x14c3e, 0x14c60, 0x14c7c, 0x14cc0, 0x14cf8, 0x14df0, 0x14e38, 0x14e70, 0x14e7e, 0x14ee0,
+ 0x14efc, 0x14f04, 0x14f08, 0x14f10, 0x14f1e, 0x14f20, 0x14f3c, 0x14f40, 0x14f78, 0x14f86, 0x14f8c, 0x14f98,
+ 0x14fb0, 0x14fce, 0x14fdc, 0x15020, 0x15040, 0x15078, 0x150f0, 0x151e0, 0x153c0, 0x15860, 0x1587c, 0x158c0,
+ 0x158f8, 0x159f0, 0x15be0, 0x15c70, 0x15c7e, 0x15ce0, 0x15cfc, 0x15dc0, 0x15df8, 0x15e08, 0x15e10, 0x15e20,
+ 0x15e40, 0x15e78, 0x15ef0, 0x15f0c, 0x15f18, 0x15f30, 0x15f60, 0x15f7c, 0x15f8e, 0x15f9c, 0x15fb8, 0x1604e,
+ 0x1605c, 0x1608e, 0x1609c, 0x160b8, 0x160c2, 0x160c4, 0x160c8, 0x160de, 0x1610e, 0x1611c, 0x16138, 0x16170,
+ 0x1617e, 0x16184, 0x16188, 0x16190, 0x1619e, 0x161a0, 0x161bc, 0x161c6, 0x161cc, 0x161d8, 0x161f2, 0x161f4,
+ 0x1620e, 0x1621c, 0x16238, 0x16270, 0x1627e, 0x162e0, 0x162fc, 0x16304, 0x16308, 0x16310, 0x1631e, 0x16320,
+ 0x1633c, 0x16340, 0x16378, 0x16386, 0x1638c, 0x16398, 0x163b0, 0x163be, 0x163ce, 0x163dc, 0x163e2, 0x163e4,
+ 0x163e8, 0x163f6, 0x1640e, 0x1641c, 0x16438, 0x16470, 0x1647e, 0x164e0, 0x164fc, 0x165c0, 0x165f8, 0x16610,
+ 0x1661e, 0x16620, 0x1663c, 0x16640, 0x16678, 0x166f0, 0x16718, 0x16730, 0x1673e, 0x16760, 0x1677c, 0x1678e,
+ 0x1679c, 0x167b8, 0x167c2, 0x167c4, 0x167c8, 0x167d0, 0x167de, 0x167e6, 0x167ec, 0x1681c, 0x16838, 0x16870,
+ 0x168e0, 0x168fc, 0x169c0, 0x169f8, 0x16bf0, 0x16c10, 0x16c1e, 0x16c20, 0x16c3c, 0x16c40, 0x16c78, 0x16cf0,
+ 0x16de0, 0x16e18, 0x16e30, 0x16e3e, 0x16e60, 0x16e7c, 0x16ec0, 0x16ef8, 0x16f1c, 0x16f38, 0x16f70, 0x16f7e,
+ 0x16f84, 0x16f88, 0x16f90, 0x16f9e, 0x16fa0, 0x16fbc, 0x16fc6, 0x16fcc, 0x16fd8, 0x17026, 0x1702c, 0x17046,
+ 0x1704c, 0x17058, 0x1706e, 0x17086, 0x1708c, 0x17098, 0x170b0, 0x170be, 0x170ce, 0x170dc, 0x170e8, 0x17106,
+ 0x1710c, 0x17118, 0x17130, 0x1713e, 0x17160, 0x1717c, 0x1718e, 0x1719c, 0x171b8, 0x171c2, 0x171c4, 0x171c8,
+ 0x171d0, 0x171de, 0x171e6, 0x171ec, 0x171fa, 0x17206, 0x1720c, 0x17218, 0x17230, 0x1723e, 0x17260, 0x1727c,
+ 0x172c0, 0x172f8, 0x1730e, 0x1731c, 0x17338, 0x17370, 0x1737e, 0x17388, 0x17390, 0x1739e, 0x173a0, 0x173bc,
+ 0x173cc, 0x173d8, 0x173ee, 0x173f2, 0x173f4, 0x1740c, 0x17418, 0x17430, 0x1743e, 0x17460, 0x1747c, 0x174c0,
+ 0x174f8, 0x175f0, 0x1760e, 0x1761c, 0x17638, 0x17670, 0x1767e, 0x176e0, 0x176fc, 0x17708, 0x17710, 0x1771e,
+ 0x17720, 0x1773c, 0x17740, 0x17778, 0x17798, 0x177b0, 0x177be, 0x177dc, 0x177e2, 0x177e4, 0x177e8, 0x17822,
+ 0x17824, 0x17828, 0x17836, 0x17842, 0x17844, 0x17848, 0x17850, 0x1785e, 0x17866, 0x1786c, 0x17882, 0x17884,
+ 0x17888, 0x17890, 0x1789e, 0x178a0, 0x178bc, 0x178c6, 0x178cc, 0x178d8, 0x178ee, 0x178f2, 0x178f4, 0x17902,
+ 0x17904, 0x17908, 0x17910, 0x1791e, 0x17920, 0x1793c, 0x17940, 0x17978, 0x17986, 0x1798c, 0x17998, 0x179b0,
+ 0x179be, 0x179ce, 0x179dc, 0x179e2, 0x179e4, 0x179e8, 0x179f6, 0x17a04, 0x17a08, 0x17a10, 0x17a1e, 0x17a20,
+ 0x17a3c, 0x17a40, 0x17a78, 0x17af0, 0x17b06, 0x17b0c, 0x17b18, 0x17b30, 0x17b3e, 0x17b60, 0x17b7c, 0x17b8e,
+ 0x17b9c, 0x17bb8, 0x17bc4, 0x17bc8, 0x17bd0, 0x17bde, 0x17be6, 0x17bec, 0x17c2e, 0x17c32, 0x17c34, 0x17c4e,
+ 0x17c5c, 0x17c62, 0x17c64, 0x17c68, 0x17c76, 0x17c8e, 0x17c9c, 0x17cb8, 0x17cc2, 0x17cc4, 0x17cc8, 0x17cd0,
+ 0x17cde, 0x17ce6, 0x17cec, 0x17d0e, 0x17d1c, 0x17d38, 0x17d70, 0x17d82, 0x17d84, 0x17d88, 0x17d90, 0x17d9e,
+ 0x17da0, 0x17dbc, 0x17dc6, 0x17dcc, 0x17dd8, 0x17dee, 0x17e26, 0x17e2c, 0x17e3a, 0x17e46, 0x17e4c, 0x17e58,
+ 0x17e6e, 0x17e72, 0x17e74, 0x17e86, 0x17e8c, 0x17e98, 0x17eb0, 0x17ece, 0x17edc, 0x17ee2, 0x17ee4, 0x17ee8,
+ 0x17ef6, 0x1813a, 0x18172, 0x18174, 0x18216, 0x18226, 0x1823a, 0x1824c, 0x18258, 0x1826e, 0x18272, 0x18274,
+ 0x18298, 0x182be, 0x182e2, 0x182e4, 0x182e8, 0x182f6, 0x1835e, 0x1837a, 0x183ae, 0x183d6, 0x18416, 0x18426,
+ 0x1842c, 0x1843a, 0x18446, 0x18458, 0x1846e, 0x18472, 0x18474, 0x18486, 0x184b0, 0x184be, 0x184ce, 0x184dc,
+ 0x184e2, 0x184e4, 0x184e8, 0x184f6, 0x18506, 0x1850c, 0x18518, 0x18530, 0x1853e, 0x18560, 0x1857c, 0x1858e,
+ 0x1859c, 0x185b8, 0x185c2, 0x185c4, 0x185c8, 0x185d0, 0x185de, 0x185e6, 0x185ec, 0x185fa, 0x18612, 0x18614,
+ 0x18622, 0x18628, 0x18636, 0x18642, 0x18650, 0x1865e, 0x1867a, 0x18682, 0x18684, 0x18688, 0x18690, 0x1869e,
+ 0x186a0, 0x186bc, 0x186c6, 0x186cc, 0x186d8, 0x186ee, 0x186f2, 0x186f4, 0x1872e, 0x1874e, 0x1875c, 0x18796,
+ 0x187a6, 0x187ac, 0x187d2, 0x187d4, 0x18826, 0x1882c, 0x1883a, 0x18846, 0x1884c, 0x18858, 0x1886e, 0x18872,
+ 0x18874, 0x18886, 0x18898, 0x188b0, 0x188be, 0x188ce, 0x188dc, 0x188e2, 0x188e4, 0x188e8, 0x188f6, 0x1890c,
+ 0x18930, 0x1893e, 0x18960, 0x1897c, 0x1898e, 0x189b8, 0x189c2, 0x189c8, 0x189d0, 0x189de, 0x189e6, 0x189ec,
+ 0x189fa, 0x18a18, 0x18a30, 0x18a3e, 0x18a60, 0x18a7c, 0x18ac0, 0x18af8, 0x18b1c, 0x18b38, 0x18b70, 0x18b7e,
+ 0x18b82, 0x18b84, 0x18b88, 0x18b90, 0x18b9e, 0x18ba0, 0x18bbc, 0x18bc6, 0x18bcc, 0x18bd8, 0x18bee, 0x18bf2,
+ 0x18bf4, 0x18c22, 0x18c24, 0x18c28, 0x18c36, 0x18c42, 0x18c48, 0x18c50, 0x18c5e, 0x18c66, 0x18c7a, 0x18c82,
+ 0x18c84, 0x18c90, 0x18c9e, 0x18ca0, 0x18cbc, 0x18ccc, 0x18cf2, 0x18cf4, 0x18d04, 0x18d08, 0x18d10, 0x18d1e,
+ 0x18d20, 0x18d3c, 0x18d40, 0x18d78, 0x18d86, 0x18d98, 0x18dce, 0x18de2, 0x18de4, 0x18de8, 0x18e2e, 0x18e32,
+ 0x18e34, 0x18e4e, 0x18e5c, 0x18e62, 0x18e64, 0x18e68, 0x18e8e, 0x18e9c, 0x18eb8, 0x18ec2, 0x18ec4, 0x18ec8,
+ 0x18ed0, 0x18efa, 0x18f16, 0x18f26, 0x18f2c, 0x18f46, 0x18f4c, 0x18f58, 0x18f6e, 0x18f8a, 0x18f92, 0x18f94,
+ 0x18fa2, 0x18fa4, 0x18fa8, 0x18fb6, 0x1902c, 0x1903a, 0x19046, 0x1904c, 0x19058, 0x19072, 0x19074, 0x19086,
+ 0x19098, 0x190b0, 0x190be, 0x190ce, 0x190dc, 0x190e2, 0x190e8, 0x190f6, 0x19106, 0x1910c, 0x19130, 0x1913e,
+ 0x19160, 0x1917c, 0x1918e, 0x1919c, 0x191b8, 0x191c2, 0x191c8, 0x191d0, 0x191de, 0x191e6, 0x191ec, 0x191fa,
+ 0x19218, 0x1923e, 0x19260, 0x1927c, 0x192c0, 0x192f8, 0x19338, 0x19370, 0x1937e, 0x19382, 0x19384, 0x19390,
+ 0x1939e, 0x193a0, 0x193bc, 0x193c6, 0x193cc, 0x193d8, 0x193ee, 0x193f2, 0x193f4, 0x19430, 0x1943e, 0x19460,
+ 0x1947c, 0x194c0, 0x194f8, 0x195f0, 0x19638, 0x19670, 0x1967e, 0x196e0, 0x196fc, 0x19702, 0x19704, 0x19708,
+ 0x19710, 0x19720, 0x1973c, 0x19740, 0x19778, 0x19786, 0x1978c, 0x19798, 0x197b0, 0x197be, 0x197ce, 0x197dc,
+ 0x197e2, 0x197e4, 0x197e8, 0x19822, 0x19824, 0x19842, 0x19848, 0x19850, 0x1985e, 0x19866, 0x1987a, 0x19882,
+ 0x19884, 0x19890, 0x1989e, 0x198a0, 0x198bc, 0x198cc, 0x198f2, 0x198f4, 0x19902, 0x19908, 0x1991e, 0x19920,
+ 0x1993c, 0x19940, 0x19978, 0x19986, 0x19998, 0x199ce, 0x199e2, 0x199e4, 0x199e8, 0x19a08, 0x19a10, 0x19a1e,
+ 0x19a20, 0x19a3c, 0x19a40, 0x19a78, 0x19af0, 0x19b18, 0x19b3e, 0x19b60, 0x19b9c, 0x19bc2, 0x19bc4, 0x19bc8,
+ 0x19bd0, 0x19be6, 0x19c2e, 0x19c34, 0x19c4e, 0x19c5c, 0x19c62, 0x19c64, 0x19c68, 0x19c8e, 0x19c9c, 0x19cb8,
+ 0x19cc2, 0x19cc8, 0x19cd0, 0x19ce6, 0x19cfa, 0x19d0e, 0x19d1c, 0x19d38, 0x19d70, 0x19d7e, 0x19d82, 0x19d84,
+ 0x19d88, 0x19d90, 0x19da0, 0x19dcc, 0x19df2, 0x19df4, 0x19e16, 0x19e26, 0x19e2c, 0x19e46, 0x19e4c, 0x19e58,
+ 0x19e74, 0x19e86, 0x19e8c, 0x19e98, 0x19eb0, 0x19ebe, 0x19ece, 0x19ee2, 0x19ee4, 0x19ee8, 0x19f0a, 0x19f12,
+ 0x19f14, 0x19f22, 0x19f24, 0x19f28, 0x19f42, 0x19f44, 0x19f48, 0x19f50, 0x19f5e, 0x19f6c, 0x19f9a, 0x19fae,
+ 0x19fb2, 0x19fb4, 0x1a046, 0x1a04c, 0x1a072, 0x1a074, 0x1a086, 0x1a08c, 0x1a098, 0x1a0b0, 0x1a0be, 0x1a0e2,
+ 0x1a0e4, 0x1a0e8, 0x1a0f6, 0x1a106, 0x1a10c, 0x1a118, 0x1a130, 0x1a13e, 0x1a160, 0x1a17c, 0x1a18e, 0x1a19c,
+ 0x1a1b8, 0x1a1c2, 0x1a1c4, 0x1a1c8, 0x1a1d0, 0x1a1de, 0x1a1e6, 0x1a1ec, 0x1a218, 0x1a230, 0x1a23e, 0x1a260,
+ 0x1a27c, 0x1a2c0, 0x1a2f8, 0x1a31c, 0x1a338, 0x1a370, 0x1a37e, 0x1a382, 0x1a384, 0x1a388, 0x1a390, 0x1a39e,
+ 0x1a3a0, 0x1a3bc, 0x1a3c6, 0x1a3cc, 0x1a3d8, 0x1a3ee, 0x1a3f2, 0x1a3f4, 0x1a418, 0x1a430, 0x1a43e, 0x1a460,
+ 0x1a47c, 0x1a4c0, 0x1a4f8, 0x1a5f0, 0x1a61c, 0x1a638, 0x1a670, 0x1a67e, 0x1a6e0, 0x1a6fc, 0x1a702, 0x1a704,
+ 0x1a708, 0x1a710, 0x1a71e, 0x1a720, 0x1a73c, 0x1a740, 0x1a778, 0x1a786, 0x1a78c, 0x1a798, 0x1a7b0, 0x1a7be,
+ 0x1a7ce, 0x1a7dc, 0x1a7e2, 0x1a7e4, 0x1a7e8, 0x1a830, 0x1a860, 0x1a87c, 0x1a8c0, 0x1a8f8, 0x1a9f0, 0x1abe0,
+ 0x1ac70, 0x1ac7e, 0x1ace0, 0x1acfc, 0x1adc0, 0x1adf8, 0x1ae04, 0x1ae08, 0x1ae10, 0x1ae20, 0x1ae3c, 0x1ae40,
+ 0x1ae78, 0x1aef0, 0x1af06, 0x1af0c, 0x1af18, 0x1af30, 0x1af3e, 0x1af60, 0x1af7c, 0x1af8e, 0x1af9c, 0x1afb8,
+ 0x1afc4, 0x1afc8, 0x1afd0, 0x1afde, 0x1b042, 0x1b05e, 0x1b07a, 0x1b082, 0x1b084, 0x1b088, 0x1b090, 0x1b09e,
+ 0x1b0a0, 0x1b0bc, 0x1b0cc, 0x1b0f2, 0x1b0f4, 0x1b102, 0x1b104, 0x1b108, 0x1b110, 0x1b11e, 0x1b120, 0x1b13c,
+ 0x1b140, 0x1b178, 0x1b186, 0x1b198, 0x1b1ce, 0x1b1e2, 0x1b1e4, 0x1b1e8, 0x1b204, 0x1b208, 0x1b210, 0x1b21e,
+ 0x1b220, 0x1b23c, 0x1b240, 0x1b278, 0x1b2f0, 0x1b30c, 0x1b33e, 0x1b360, 0x1b39c, 0x1b3c2, 0x1b3c4, 0x1b3c8,
+ 0x1b3d0, 0x1b3e6, 0x1b410, 0x1b41e, 0x1b420, 0x1b43c, 0x1b440, 0x1b478, 0x1b4f0, 0x1b5e0, 0x1b618, 0x1b660,
+ 0x1b67c, 0x1b6c0, 0x1b738, 0x1b782, 0x1b784, 0x1b788, 0x1b790, 0x1b79e, 0x1b7a0, 0x1b7cc, 0x1b82e, 0x1b84e,
+ 0x1b85c, 0x1b88e, 0x1b89c, 0x1b8b8, 0x1b8c2, 0x1b8c4, 0x1b8c8, 0x1b8d0, 0x1b8e6, 0x1b8fa, 0x1b90e, 0x1b91c,
+ 0x1b938, 0x1b970, 0x1b97e, 0x1b982, 0x1b984, 0x1b988, 0x1b990, 0x1b99e, 0x1b9a0, 0x1b9cc, 0x1b9f2, 0x1b9f4,
+ 0x1ba0e, 0x1ba1c, 0x1ba38, 0x1ba70, 0x1ba7e, 0x1bae0, 0x1bafc, 0x1bb08, 0x1bb10, 0x1bb20, 0x1bb3c, 0x1bb40,
+ 0x1bb98, 0x1bbce, 0x1bbe2, 0x1bbe4, 0x1bbe8, 0x1bc16, 0x1bc26, 0x1bc2c, 0x1bc46, 0x1bc4c, 0x1bc58, 0x1bc72,
+ 0x1bc74, 0x1bc86, 0x1bc8c, 0x1bc98, 0x1bcb0, 0x1bcbe, 0x1bcce, 0x1bce2, 0x1bce4, 0x1bce8, 0x1bd06, 0x1bd0c,
+ 0x1bd18, 0x1bd30, 0x1bd3e, 0x1bd60, 0x1bd7c, 0x1bd9c, 0x1bdc2, 0x1bdc4, 0x1bdc8, 0x1bdd0, 0x1bde6, 0x1bdfa,
+ 0x1be12, 0x1be14, 0x1be22, 0x1be24, 0x1be28, 0x1be42, 0x1be44, 0x1be48, 0x1be50, 0x1be5e, 0x1be66, 0x1be82,
+ 0x1be84, 0x1be88, 0x1be90, 0x1be9e, 0x1bea0, 0x1bebc, 0x1becc, 0x1bef4, 0x1bf1a, 0x1bf2e, 0x1bf32, 0x1bf34,
+ 0x1bf4e, 0x1bf5c, 0x1bf62, 0x1bf64, 0x1bf68, 0x1c09a, 0x1c0b2, 0x1c0b4, 0x1c11a, 0x1c132, 0x1c134, 0x1c162,
+ 0x1c164, 0x1c168, 0x1c176, 0x1c1ba, 0x1c21a, 0x1c232, 0x1c234, 0x1c24e, 0x1c25c, 0x1c262, 0x1c264, 0x1c268,
+ 0x1c276, 0x1c28e, 0x1c2c2, 0x1c2c4, 0x1c2c8, 0x1c2d0, 0x1c2de, 0x1c2e6, 0x1c2ec, 0x1c2fa, 0x1c316, 0x1c326,
+ 0x1c33a, 0x1c346, 0x1c34c, 0x1c372, 0x1c374, 0x1c41a, 0x1c42e, 0x1c432, 0x1c434, 0x1c44e, 0x1c45c, 0x1c462,
+ 0x1c464, 0x1c468, 0x1c476, 0x1c48e, 0x1c49c, 0x1c4b8, 0x1c4c2, 0x1c4c8, 0x1c4d0, 0x1c4de, 0x1c4e6, 0x1c4ec,
+ 0x1c4fa, 0x1c51c, 0x1c538, 0x1c570, 0x1c57e, 0x1c582, 0x1c584, 0x1c588, 0x1c590, 0x1c59e, 0x1c5a0, 0x1c5bc,
+ 0x1c5c6, 0x1c5cc, 0x1c5d8, 0x1c5ee, 0x1c5f2, 0x1c5f4, 0x1c616, 0x1c626, 0x1c62c, 0x1c63a, 0x1c646, 0x1c64c,
+ 0x1c658, 0x1c66e, 0x1c672, 0x1c674, 0x1c686, 0x1c68c, 0x1c698, 0x1c6b0, 0x1c6be, 0x1c6ce, 0x1c6dc, 0x1c6e2,
+ 0x1c6e4, 0x1c6e8, 0x1c712, 0x1c714, 0x1c722, 0x1c728, 0x1c736, 0x1c742, 0x1c744, 0x1c748, 0x1c750, 0x1c75e,
+ 0x1c766, 0x1c76c, 0x1c77a, 0x1c7ae, 0x1c7d6, 0x1c7ea, 0x1c81a, 0x1c82e, 0x1c832, 0x1c834, 0x1c84e, 0x1c85c,
+ 0x1c862, 0x1c864, 0x1c868, 0x1c876, 0x1c88e, 0x1c89c, 0x1c8b8, 0x1c8c2, 0x1c8c8, 0x1c8d0, 0x1c8de, 0x1c8e6,
+ 0x1c8ec, 0x1c8fa, 0x1c90e, 0x1c938, 0x1c970, 0x1c97e, 0x1c982, 0x1c984, 0x1c990, 0x1c99e, 0x1c9a0, 0x1c9bc,
+ 0x1c9c6, 0x1c9cc, 0x1c9d8, 0x1c9ee, 0x1c9f2, 0x1c9f4, 0x1ca38, 0x1ca70, 0x1ca7e, 0x1cae0, 0x1cafc, 0x1cb02,
+ 0x1cb04, 0x1cb08, 0x1cb10, 0x1cb20, 0x1cb3c, 0x1cb40, 0x1cb78, 0x1cb86, 0x1cb8c, 0x1cb98, 0x1cbb0, 0x1cbbe,
+ 0x1cbce, 0x1cbdc, 0x1cbe2, 0x1cbe4, 0x1cbe8, 0x1cbf6, 0x1cc16, 0x1cc26, 0x1cc2c, 0x1cc3a, 0x1cc46, 0x1cc58,
+ 0x1cc72, 0x1cc74, 0x1cc86, 0x1ccb0, 0x1ccbe, 0x1ccce, 0x1cce2, 0x1cce4, 0x1cce8, 0x1cd06, 0x1cd0c, 0x1cd18,
+ 0x1cd30, 0x1cd3e, 0x1cd60, 0x1cd7c, 0x1cd9c, 0x1cdc2, 0x1cdc4, 0x1cdc8, 0x1cdd0, 0x1cdde, 0x1cde6, 0x1cdfa,
+ 0x1ce22, 0x1ce28, 0x1ce42, 0x1ce50, 0x1ce5e, 0x1ce66, 0x1ce7a, 0x1ce82, 0x1ce84, 0x1ce88, 0x1ce90, 0x1ce9e,
+ 0x1cea0, 0x1cebc, 0x1cecc, 0x1cef2, 0x1cef4, 0x1cf2e, 0x1cf32, 0x1cf34, 0x1cf4e, 0x1cf5c, 0x1cf62, 0x1cf64,
+ 0x1cf68, 0x1cf96, 0x1cfa6, 0x1cfac, 0x1cfca, 0x1cfd2, 0x1cfd4, 0x1d02e, 0x1d032, 0x1d034, 0x1d04e, 0x1d05c,
+ 0x1d062, 0x1d064, 0x1d068, 0x1d076, 0x1d08e, 0x1d09c, 0x1d0b8, 0x1d0c2, 0x1d0c4, 0x1d0c8, 0x1d0d0, 0x1d0de,
+ 0x1d0e6, 0x1d0ec, 0x1d0fa, 0x1d11c, 0x1d138, 0x1d170, 0x1d17e, 0x1d182, 0x1d184, 0x1d188, 0x1d190, 0x1d19e,
+ 0x1d1a0, 0x1d1bc, 0x1d1c6, 0x1d1cc, 0x1d1d8, 0x1d1ee, 0x1d1f2, 0x1d1f4, 0x1d21c, 0x1d238, 0x1d270, 0x1d27e,
+ 0x1d2e0, 0x1d2fc, 0x1d302, 0x1d304, 0x1d308, 0x1d310, 0x1d31e, 0x1d320, 0x1d33c, 0x1d340, 0x1d378, 0x1d386,
+ 0x1d38c, 0x1d398, 0x1d3b0, 0x1d3be, 0x1d3ce, 0x1d3dc, 0x1d3e2, 0x1d3e4, 0x1d3e8, 0x1d3f6, 0x1d470, 0x1d47e,
+ 0x1d4e0, 0x1d4fc, 0x1d5c0, 0x1d5f8, 0x1d604, 0x1d608, 0x1d610, 0x1d620, 0x1d640, 0x1d678, 0x1d6f0, 0x1d706,
+ 0x1d70c, 0x1d718, 0x1d730, 0x1d73e, 0x1d760, 0x1d77c, 0x1d78e, 0x1d79c, 0x1d7b8, 0x1d7c2, 0x1d7c4, 0x1d7c8,
+ 0x1d7d0, 0x1d7de, 0x1d7e6, 0x1d7ec, 0x1d826, 0x1d82c, 0x1d83a, 0x1d846, 0x1d84c, 0x1d858, 0x1d872, 0x1d874,
+ 0x1d886, 0x1d88c, 0x1d898, 0x1d8b0, 0x1d8be, 0x1d8ce, 0x1d8e2, 0x1d8e4, 0x1d8e8, 0x1d8f6, 0x1d90c, 0x1d918,
+ 0x1d930, 0x1d93e, 0x1d960, 0x1d97c, 0x1d99c, 0x1d9c2, 0x1d9c4, 0x1d9c8, 0x1d9d0, 0x1d9e6, 0x1d9fa, 0x1da0c,
+ 0x1da18, 0x1da30, 0x1da3e, 0x1da60, 0x1da7c, 0x1dac0, 0x1daf8, 0x1db38, 0x1db82, 0x1db84, 0x1db88, 0x1db90,
+ 0x1db9e, 0x1dba0, 0x1dbcc, 0x1dbf2, 0x1dbf4, 0x1dc22, 0x1dc42, 0x1dc44, 0x1dc48, 0x1dc50, 0x1dc5e, 0x1dc66,
+ 0x1dc7a, 0x1dc82, 0x1dc84, 0x1dc88, 0x1dc90, 0x1dc9e, 0x1dca0, 0x1dcbc, 0x1dccc, 0x1dcf2, 0x1dcf4, 0x1dd04,
+ 0x1dd08, 0x1dd10, 0x1dd1e, 0x1dd20, 0x1dd3c, 0x1dd40, 0x1dd78, 0x1dd86, 0x1dd98, 0x1ddce, 0x1dde2, 0x1dde4,
+ 0x1dde8, 0x1de2e, 0x1de32, 0x1de34, 0x1de4e, 0x1de5c, 0x1de62, 0x1de64, 0x1de68, 0x1de8e, 0x1de9c, 0x1deb8,
+ 0x1dec2, 0x1dec4, 0x1dec8, 0x1ded0, 0x1dee6, 0x1defa, 0x1df16, 0x1df26, 0x1df2c, 0x1df46, 0x1df4c, 0x1df58,
+ 0x1df72, 0x1df74, 0x1df8a, 0x1df92, 0x1df94, 0x1dfa2, 0x1dfa4, 0x1dfa8, 0x1e08a, 0x1e092, 0x1e094, 0x1e0a2,
+ 0x1e0a4, 0x1e0a8, 0x1e0b6, 0x1e0da, 0x1e10a, 0x1e112, 0x1e114, 0x1e122, 0x1e124, 0x1e128, 0x1e136, 0x1e142,
+ 0x1e144, 0x1e148, 0x1e150, 0x1e166, 0x1e16c, 0x1e17a, 0x1e19a, 0x1e1b2, 0x1e1b4, 0x1e20a, 0x1e212, 0x1e214,
+ 0x1e222, 0x1e224, 0x1e228, 0x1e236, 0x1e242, 0x1e248, 0x1e250, 0x1e25e, 0x1e266, 0x1e26c, 0x1e27a, 0x1e282,
+ 0x1e284, 0x1e288, 0x1e290, 0x1e2a0, 0x1e2bc, 0x1e2c6, 0x1e2cc, 0x1e2d8, 0x1e2ee, 0x1e2f2, 0x1e2f4, 0x1e31a,
+ 0x1e332, 0x1e334, 0x1e35c, 0x1e362, 0x1e364, 0x1e368, 0x1e3ba, 0x1e40a, 0x1e412, 0x1e414, 0x1e422, 0x1e428,
+ 0x1e436, 0x1e442, 0x1e448, 0x1e450, 0x1e45e, 0x1e466, 0x1e46c, 0x1e47a, 0x1e482, 0x1e484, 0x1e490, 0x1e49e,
+ 0x1e4a0, 0x1e4bc, 0x1e4c6, 0x1e4cc, 0x1e4d8, 0x1e4ee, 0x1e4f2, 0x1e4f4, 0x1e502, 0x1e504, 0x1e508, 0x1e510,
+ 0x1e51e, 0x1e520, 0x1e53c, 0x1e540, 0x1e578, 0x1e586, 0x1e58c, 0x1e598, 0x1e5b0, 0x1e5be, 0x1e5ce, 0x1e5dc,
+ 0x1e5e2, 0x1e5e4, 0x1e5e8, 0x1e5f6, 0x1e61a, 0x1e62e, 0x1e632, 0x1e634, 0x1e64e, 0x1e65c, 0x1e662, 0x1e668,
+ 0x1e68e, 0x1e69c, 0x1e6b8, 0x1e6c2, 0x1e6c4, 0x1e6c8, 0x1e6d0, 0x1e6e6, 0x1e6fa, 0x1e716, 0x1e726, 0x1e72c,
+ 0x1e73a, 0x1e746, 0x1e74c, 0x1e758, 0x1e772, 0x1e774, 0x1e792, 0x1e794, 0x1e7a2, 0x1e7a4, 0x1e7a8, 0x1e7b6,
+ 0x1e812, 0x1e814, 0x1e822, 0x1e824, 0x1e828, 0x1e836, 0x1e842, 0x1e844, 0x1e848, 0x1e850, 0x1e85e, 0x1e866,
+ 0x1e86c, 0x1e87a, 0x1e882, 0x1e884, 0x1e888, 0x1e890, 0x1e89e, 0x1e8a0, 0x1e8bc, 0x1e8c6, 0x1e8cc, 0x1e8d8,
+ 0x1e8ee, 0x1e8f2, 0x1e8f4, 0x1e902, 0x1e904, 0x1e908, 0x1e910, 0x1e920, 0x1e93c, 0x1e940, 0x1e978, 0x1e986,
+ 0x1e98c, 0x1e998, 0x1e9b0, 0x1e9be, 0x1e9ce, 0x1e9dc, 0x1e9e2, 0x1e9e4, 0x1e9e8, 0x1e9f6, 0x1ea04, 0x1ea08,
+ 0x1ea10, 0x1ea20, 0x1ea40, 0x1ea78, 0x1eaf0, 0x1eb06, 0x1eb0c, 0x1eb18, 0x1eb30, 0x1eb3e, 0x1eb60, 0x1eb7c,
+ 0x1eb8e, 0x1eb9c, 0x1ebb8, 0x1ebc2, 0x1ebc4, 0x1ebc8, 0x1ebd0, 0x1ebde, 0x1ebe6, 0x1ebec, 0x1ec1a, 0x1ec2e,
+ 0x1ec32, 0x1ec34, 0x1ec4e, 0x1ec5c, 0x1ec62, 0x1ec64, 0x1ec68, 0x1ec8e, 0x1ec9c, 0x1ecb8, 0x1ecc2, 0x1ecc4,
+ 0x1ecc8, 0x1ecd0, 0x1ece6, 0x1ecfa, 0x1ed0e, 0x1ed1c, 0x1ed38, 0x1ed70, 0x1ed7e, 0x1ed82, 0x1ed84, 0x1ed88,
+ 0x1ed90, 0x1ed9e, 0x1eda0, 0x1edcc, 0x1edf2, 0x1edf4, 0x1ee16, 0x1ee26, 0x1ee2c, 0x1ee3a, 0x1ee46, 0x1ee4c,
+ 0x1ee58, 0x1ee6e, 0x1ee72, 0x1ee74, 0x1ee86, 0x1ee8c, 0x1ee98, 0x1eeb0, 0x1eebe, 0x1eece, 0x1eedc, 0x1eee2,
+ 0x1eee4, 0x1eee8, 0x1ef12, 0x1ef22, 0x1ef24, 0x1ef28, 0x1ef36, 0x1ef42, 0x1ef44, 0x1ef48, 0x1ef50, 0x1ef5e,
+ 0x1ef66, 0x1ef6c, 0x1ef7a, 0x1efae, 0x1efb2, 0x1efb4, 0x1efd6, 0x1f096, 0x1f0a6, 0x1f0ac, 0x1f0ba, 0x1f0ca,
+ 0x1f0d2, 0x1f0d4, 0x1f116, 0x1f126, 0x1f12c, 0x1f13a, 0x1f146, 0x1f14c, 0x1f158, 0x1f16e, 0x1f172, 0x1f174,
+ 0x1f18a, 0x1f192, 0x1f194, 0x1f1a2, 0x1f1a4, 0x1f1a8, 0x1f1da, 0x1f216, 0x1f226, 0x1f22c, 0x1f23a, 0x1f246,
+ 0x1f258, 0x1f26e, 0x1f272, 0x1f274, 0x1f286, 0x1f28c, 0x1f298, 0x1f2b0, 0x1f2be, 0x1f2ce, 0x1f2dc, 0x1f2e2,
+ 0x1f2e4, 0x1f2e8, 0x1f2f6, 0x1f30a, 0x1f312, 0x1f314, 0x1f322, 0x1f328, 0x1f342, 0x1f344, 0x1f348, 0x1f350,
+ 0x1f35e, 0x1f366, 0x1f37a, 0x1f39a, 0x1f3ae, 0x1f3b2, 0x1f3b4, 0x1f416, 0x1f426, 0x1f42c, 0x1f43a, 0x1f446,
+ 0x1f44c, 0x1f458, 0x1f46e, 0x1f472, 0x1f474, 0x1f486, 0x1f48c, 0x1f498, 0x1f4b0, 0x1f4be, 0x1f4ce, 0x1f4dc,
+ 0x1f4e2, 0x1f4e4, 0x1f4e8, 0x1f4f6, 0x1f506, 0x1f50c, 0x1f518, 0x1f530, 0x1f53e, 0x1f560, 0x1f57c, 0x1f58e,
+ 0x1f59c, 0x1f5b8, 0x1f5c2, 0x1f5c4, 0x1f5c8, 0x1f5d0, 0x1f5de, 0x1f5e6, 0x1f5ec, 0x1f5fa, 0x1f60a, 0x1f612,
+ 0x1f614, 0x1f622, 0x1f624, 0x1f628, 0x1f636, 0x1f642, 0x1f644, 0x1f648, 0x1f650, 0x1f65e, 0x1f666, 0x1f67a,
+ 0x1f682, 0x1f684, 0x1f688, 0x1f690, 0x1f69e, 0x1f6a0, 0x1f6bc, 0x1f6cc, 0x1f6f2, 0x1f6f4, 0x1f71a, 0x1f72e,
+ 0x1f732, 0x1f734, 0x1f74e, 0x1f75c, 0x1f762, 0x1f764, 0x1f768, 0x1f776, 0x1f796, 0x1f7a6, 0x1f7ac, 0x1f7ba,
+ 0x1f7d2, 0x1f7d4, 0x1f89a, 0x1f8ae, 0x1f8b2, 0x1f8b4, 0x1f8d6, 0x1f8ea, 0x1f91a, 0x1f92e, 0x1f932, 0x1f934,
+ 0x1f94e, 0x1f95c, 0x1f962, 0x1f964, 0x1f968, 0x1f976, 0x1f996, 0x1f9a6, 0x1f9ac, 0x1f9ba, 0x1f9ca, 0x1f9d2,
+ 0x1f9d4, 0x1fa1a, 0x1fa2e, 0x1fa32, 0x1fa34, 0x1fa4e, 0x1fa5c, 0x1fa62, 0x1fa64, 0x1fa68, 0x1fa76, 0x1fa8e,
+ 0x1fa9c, 0x1fab8, 0x1fac2, 0x1fac4, 0x1fac8, 0x1fad0, 0x1fade, 0x1fae6, 0x1faec, 0x1fb16, 0x1fb26, 0x1fb2c,
+ 0x1fb3a, 0x1fb46, 0x1fb4c, 0x1fb58, 0x1fb6e, 0x1fb72, 0x1fb74, 0x1fb8a, 0x1fb92, 0x1fb94, 0x1fba2, 0x1fba4,
+ 0x1fba8, 0x1fbb6, 0x1fbda};
+ private static final int[] EMPTY_INT_ARRAY = {};
+ /**
+ * 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};
+
+ private PDF417Common() {
+ }
+
+ public static int getBitCountSum(int[] moduleBitCount) {
+ int bitCountSum = 0;
+ for (int count : moduleBitCount) {
+ bitCountSum += count;
+ }
+ return bitCountSum;
+ }
+
+ public static int[] toIntArray(Collection list) {
+ if (list == null || list.isEmpty()) {
+ return EMPTY_INT_ARRAY;
+ }
+ int[] result = new int[list.size()];
+ int i = 0;
+ for (Integer integer : list) {
+ result[i++] = integer;
+ }
+ return result;
+ }
+
+ /**
+ * @param symbol encoded symbol to translate to a codeword
+ * @return the codeword corresponding to the symbol.
+ */
+ public static int getCodeword(int symbol) {
+ int i = Arrays.binarySearch(SYMBOL_TABLE, symbol & 0x3FFFF);
+ if (i < 0) {
+ return -1;
+ }
+ return (CODEWORD_TABLE[i] - 1) % NUMBER_OF_CODEWORDS;
+ }
+}
diff --git a/src/com/google/zxing/pdf417/PDF417Reader.java b/src/com/google/zxing/pdf417/PDF417Reader.java
new file mode 100644
index 0000000..713237d
--- /dev/null
+++ b/src/com/google/zxing/pdf417/PDF417Reader.java
@@ -0,0 +1,126 @@
+/*
+ * 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.*;
+import com.google.zxing.common.DecoderResult;
+import com.google.zxing.multi.MultipleBarcodeReader;
+import com.google.zxing.pdf417.decoder.PDF417ScanningDecoder;
+import com.google.zxing.pdf417.detector.Detector;
+import com.google.zxing.pdf417.detector.PDF417DetectorResult;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This implementation can detect and decode PDF417 codes in an image.
+ *
+ * @author Guenther Grau
+ */
+public final class PDF417Reader implements Reader, MultipleBarcodeReader {
+
+ private static Result[] decode(BinaryBitmap image, Map hints, boolean multiple)
+ throws NotFoundException, FormatException, ChecksumException {
+ List results = new ArrayList<>();
+ PDF417DetectorResult detectorResult = Detector.detect(image, hints, multiple);
+ for (ResultPoint[] points : detectorResult.getPoints()) {
+ DecoderResult decoderResult = PDF417ScanningDecoder.decode(detectorResult.getBits(), points[4], points[5],
+ points[6], points[7], getMinCodewordWidth(points), getMaxCodewordWidth(points));
+ Result result = new Result(decoderResult.getText(), decoderResult.getRawBytes(), points, BarcodeFormat.PDF_417);
+ result.putMetadata(ResultMetadataType.ERROR_CORRECTION_LEVEL, decoderResult.getECLevel());
+ PDF417ResultMetadata pdf417ResultMetadata = (PDF417ResultMetadata) decoderResult.getOther();
+ if (pdf417ResultMetadata != null) {
+ result.putMetadata(ResultMetadataType.PDF417_EXTRA_METADATA, pdf417ResultMetadata);
+ }
+ results.add(result);
+ }
+ return results.toArray(new Result[results.size()]);
+ }
+
+ private static int getMaxWidth(ResultPoint p1, ResultPoint p2) {
+ if (p1 == null || p2 == null) {
+ return 0;
+ }
+ return (int) Math.abs(p1.getX() - p2.getX());
+ }
+
+ private static int getMinWidth(ResultPoint p1, ResultPoint p2) {
+ if (p1 == null || p2 == null) {
+ return Integer.MAX_VALUE;
+ }
+ return (int) Math.abs(p1.getX() - p2.getX());
+ }
+
+ private static int getMaxCodewordWidth(ResultPoint[] p) {
+ return Math.max(
+ Math.max(getMaxWidth(p[0], p[4]), getMaxWidth(p[6], p[2]) * PDF417Common.MODULES_IN_CODEWORD /
+ PDF417Common.MODULES_IN_STOP_PATTERN),
+ Math.max(getMaxWidth(p[1], p[5]), getMaxWidth(p[7], p[3]) * PDF417Common.MODULES_IN_CODEWORD /
+ PDF417Common.MODULES_IN_STOP_PATTERN));
+ }
+
+ private static int getMinCodewordWidth(ResultPoint[] p) {
+ return Math.min(
+ Math.min(getMinWidth(p[0], p[4]), getMinWidth(p[6], p[2]) * PDF417Common.MODULES_IN_CODEWORD /
+ PDF417Common.MODULES_IN_STOP_PATTERN),
+ Math.min(getMinWidth(p[1], p[5]), getMinWidth(p[7], p[3]) * PDF417Common.MODULES_IN_CODEWORD /
+ PDF417Common.MODULES_IN_STOP_PATTERN));
+ }
+
+ /**
+ * Locates and decodes a PDF417 code in an image.
+ *
+ * @return a String representing the content encoded by the PDF417 code
+ * @throws NotFoundException if a PDF417 code cannot be found,
+ * @throws FormatException if a PDF417 cannot be decoded
+ */
+ @Override
+ public Result decode(BinaryBitmap image) throws NotFoundException, FormatException, ChecksumException {
+ return decode(image, null);
+ }
+
+ @Override
+ public Result decode(BinaryBitmap image, Map hints) throws NotFoundException, FormatException,
+ ChecksumException {
+ Result[] result = decode(image, hints, false);
+ if (result == null || result.length == 0 || result[0] == null) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ return result[0];
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image) throws NotFoundException {
+ return decodeMultiple(image, null);
+ }
+
+ @Override
+ public Result[] decodeMultiple(BinaryBitmap image, Map hints) throws NotFoundException {
+ try {
+ return decode(image, hints, true);
+ } catch (FormatException | ChecksumException ignored) {
+ throw NotFoundException.getNotFoundInstance();
+ }
+ }
+
+ @Override
+ public void reset() {
+ // nothing needs to be reset
+ }
+
+}
diff --git a/src/com/google/zxing/pdf417/PDF417ResultMetadata.java b/src/com/google/zxing/pdf417/PDF417ResultMetadata.java
new file mode 100644
index 0000000..174d3d8
--- /dev/null
+++ b/src/com/google/zxing/pdf417/PDF417ResultMetadata.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2013 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.pdf417;
+
+/**
+ * @author Guenther Grau
+ */
+public final class PDF417ResultMetadata {
+
+ private int segmentIndex;
+ private String fileId;
+ private int[] optionalData;
+ private boolean lastSegment;
+
+ public int getSegmentIndex() {
+ return segmentIndex;
+ }
+
+ public void setSegmentIndex(int segmentIndex) {
+ this.segmentIndex = segmentIndex;
+ }
+
+ public String getFileId() {
+ return fileId;
+ }
+
+ public void setFileId(String fileId) {
+ this.fileId = fileId;
+ }
+
+ public int[] getOptionalData() {
+ return optionalData;
+ }
+
+ public void setOptionalData(int[] optionalData) {
+ this.optionalData = optionalData;
+ }
+
+ public boolean isLastSegment() {
+ return lastSegment;
+ }
+
+ public void setLastSegment(boolean lastSegment) {
+ this.lastSegment = lastSegment;
+ }
+
+}
diff --git a/src/com/google/zxing/pdf417/PDF417Writer.java b/src/com/google/zxing/pdf417/PDF417Writer.java
new file mode 100644
index 0000000..ba196ec
--- /dev/null
+++ b/src/com/google/zxing/pdf417/PDF417Writer.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2012 ZXing authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.zxing.pdf417;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.Writer;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.pdf417.encoder.Compaction;
+import com.google.zxing.pdf417.encoder.Dimensions;
+import com.google.zxing.pdf417.encoder.PDF417;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+
+/**
+ * @author Jacob Haynes
+ * @author qwandor@google.com (Andrew Walbran)
+ */
+public final class PDF417Writer implements Writer {
+
+ /**
+ * default white space (margin) around the code
+ */
+ static final int WHITE_SPACE = 30;
+ private static int errorCorrectionLevel = 2;
+
+ /**
+ * Takes encoder, accounts for width/height, and retrieves bit matrix
+ */
+ private static BitMatrix bitMatrixFromEncoder(PDF417 encoder,
+ String contents,
+ int width,
+ int height,
+ int margin) throws WriterException {
+ encoder.generateBarcodeLogic(contents, errorCorrectionLevel);
+
+ int lineThickness = 2;
+ int aspectRatio = 4;
+ byte[][] originalScale = encoder.getBarcodeMatrix().getScaledMatrix(lineThickness, aspectRatio * lineThickness);
+ boolean rotated = false;
+ if ((height > width) ^ (originalScale[0].length < originalScale.length)) {
+ originalScale = rotateArray(originalScale);
+ rotated = true;
+ }
+
+ int scaleX = width / originalScale[0].length;
+ int scaleY = height / originalScale.length;
+
+ int scale;
+ if (scaleX < scaleY) {
+ scale = scaleX;
+ } else {
+ scale = scaleY;
+ }
+
+ if (scale > 1) {
+ byte[][] scaledMatrix =
+ encoder.getBarcodeMatrix().getScaledMatrix(scale * lineThickness, scale * aspectRatio * lineThickness);
+ if (rotated) {
+ scaledMatrix = rotateArray(scaledMatrix);
+ }
+ return bitMatrixFrombitArray(scaledMatrix, margin);
+ }
+ return bitMatrixFrombitArray(originalScale, margin);
+ }
+
+ /**
+ * This takes an array holding the values of the PDF 417
+ *
+ * @param input a byte array of information with 0 is black, and 1 is white
+ * @param margin border around the barcode
+ * @return BitMatrix of the input
+ */
+ private static BitMatrix bitMatrixFrombitArray(byte[][] input, int margin) {
+ // Creates the bitmatrix with extra space for whitespace
+ BitMatrix output = new BitMatrix(input[0].length + 2 * margin, input.length + 2 * margin);
+ output.clear();
+ for (int y = 0, yOutput = output.getHeight() - margin - 1; y < input.length; y++, yOutput--) {
+ for (int x = 0; x < input[0].length; x++) {
+ // Zero is white in the bytematrix
+ if (input[y][x] == 1) {
+ output.set(x + margin, yOutput);
+ }
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Takes and rotates the it 90 degrees
+ */
+ private static byte[][] rotateArray(byte[][] bitarray) {
+ byte[][] temp = new byte[bitarray[0].length][bitarray.length];
+ for (int ii = 0; ii < bitarray.length; ii++) {
+ // This makes the direction consistent on screen when rotating the
+ // screen;
+ int inverseii = bitarray.length - ii - 1;
+ for (int jj = 0; jj < bitarray[0].length; jj++) {
+ temp[jj][inverseii] = bitarray[ii][jj];
+ }
+ }
+ return temp;
+ }
+
+ @Override
+ public BitMatrix encode(String contents,
+ BarcodeFormat format,
+ int width,
+ int height,
+ Map