Added, at least, parsing of ECI mode in QR Code
[zxing.git] / core / src / com / google / zxing / qrcode / decoder / DecodedBitStreamParser.java
index 8db7ca3..b6dda58 100644 (file)
@@ -17,6 +17,7 @@
 package com.google.zxing.qrcode.decoder;
 
 import com.google.zxing.ReaderException;
+import com.google.zxing.common.BitSource;
 
 import java.io.UnsupportedEncodingException;
 
@@ -33,21 +34,21 @@ final class DecodedBitStreamParser {
   /**
    * See ISO 18004:2006, 6.4.4 Table 5
    */
-  private static final char[] ALPHANUMERIC_CHARS = new char[]{
+  private static final char[] ALPHANUMERIC_CHARS = {
       '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
       'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
       'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
       ' ', '$', '%', '*', '+', '-', '.', '/', ':'
   };
   private static final String SHIFT_JIS = "Shift_JIS";
+  private static final String EUC_JP = "EUC-JP";
   private static final boolean ASSUME_SHIFT_JIS;
   private static final String UTF8 = "UTF-8";
   private static final String ISO88591 = "ISO-8859-1";
 
   static {
     String platformDefault = System.getProperty("file.encoding");
-    ASSUME_SHIFT_JIS = SHIFT_JIS.equalsIgnoreCase(platformDefault) ||
-        "EUC-JP".equalsIgnoreCase(platformDefault);
+    ASSUME_SHIFT_JIS = SHIFT_JIS.equalsIgnoreCase(platformDefault) || EUC_JP.equalsIgnoreCase(platformDefault);
   }
 
   private DecodedBitStreamParser() {
@@ -59,20 +60,34 @@ final class DecodedBitStreamParser {
     Mode mode;
     do {
       // While still another segment to read...
-      mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
+      if (bits.available() == 0) {
+        // OK, assume we're done. Really, a TERMINATOR mode should have been recorded here
+        mode = Mode.TERMINATOR;
+      } else {
+        mode = Mode.forBits(bits.readBits(4)); // mode is encoded by 4 bits
+      }
       if (!mode.equals(Mode.TERMINATOR)) {
-        // How many characters will follow, encoded in this mode?
-        int count = bits.readBits(mode.getCharacterCountBits(version));
-        if (mode.equals(Mode.NUMERIC)) {
-          decodeNumericSegment(bits, result, count);
-        } else if (mode.equals(Mode.ALPHANUMERIC)) {
-          decodeAlphanumericSegment(bits, result, count);
-        } else if (mode.equals(Mode.BYTE)) {
-          decodeByteSegment(bits, result, count);
-        } else if (mode.equals(Mode.KANJI)) {
-          decodeKanjiSegment(bits, result, count);
+        if (mode.equals(Mode.ECI)) {
+          // Count doesn't apply to ECI
+          parseECI(bits);
+          // We don't currently do anything with ECI, since there seems to be no reference
+          // defining what each value means. AIM's "Extended Channel Interpretations" does
+          // not define it. I have never observed a QR Code using it. So for now, we at least
+          // parse it but don't know how to take action on it.
         } else {
-          throw new ReaderException("Unsupported mode indicator");
+          // How many characters will follow, encoded in this mode?
+         int count = bits.readBits(mode.getCharacterCountBits(version));
+          if (mode.equals(Mode.NUMERIC)) {
+            decodeNumericSegment(bits, result, count);
+          } else if (mode.equals(Mode.ALPHANUMERIC)) {
+            decodeAlphanumericSegment(bits, result, count);
+          } else if (mode.equals(Mode.BYTE)) {
+            decodeByteSegment(bits, result, count);
+          } else if (mode.equals(Mode.KANJI)) {
+            decodeKanjiSegment(bits, result, count);
+          } else {
+            throw new ReaderException("Unsupported mode indicator");
+          }
         }
       }
     } while (!mode.equals(Mode.TERMINATOR));
@@ -89,6 +104,23 @@ final class DecodedBitStreamParser {
     return result.toString();
   }
 
+  private static int parseECI(BitSource bits) {
+    int firstByte = bits.readBits(8);
+    if (firstByte & 0x80 == 0) {
+      // just one byte
+      return firstByte & 0x7F;
+    } else if (firstByte & 0xC0 == 0x80) {
+      // two bytes
+      int secondByte = bits.readBits(8);
+      return ((firstByte & 0x3F) << 8) | secondByte;
+    } else if (firstByte & 0xE0 == 0xC0) {
+      // three bytes
+      int secondByte = bits.readBits(8);
+      int thirdByte = bits.readBits(8);
+      return ((firstByte & 0x1F) << 16) | (secondByte << 8) | thirdByte;
+    }
+  }
+
   private static void decodeKanjiSegment(BitSource bits,
                                          StringBuffer result,
                                          int count) throws ReaderException {
@@ -208,28 +240,43 @@ final class DecodedBitStreamParser {
     // If we see something else in that second byte, we'll make the risky guess
     // that it's UTF-8.
     int length = bytes.length;
+    boolean canBeISO88591 = true;
+    boolean lastWasPossibleDoubleByteStart = false;
     for (int i = 0; i < length; i++) {
       int value = bytes[i] & 0xFF;
       if (value >= 0x80 && value <= 0x9F && i < length - 1) {
+        canBeISO88591 = false;
         // ISO-8859-1 shouldn't use this, but before we decide it is Shift_JIS,
         // just double check that it is followed by a byte that's valid in
         // the Shift_JIS encoding
-        int nextValue = bytes[i + 1] & 0xFF;
-        if ((value & 0x1) == 0) {
-          // if even,
-          if (nextValue >= 0x40 && nextValue <= 0x9E) {
-            return SHIFT_JIS;
-          }
+        if (lastWasPossibleDoubleByteStart) {
+          // If we just checked this and the last byte for being a valid double-byte
+          // char, don't check starting on this byte. If the this and the last byte
+          // formed a valid pair, then this shouldn't be checked to see if it starts
+          // a double byte pair of course.
+          lastWasPossibleDoubleByteStart = false;
         } else {
-          if (nextValue >= 0x9F && nextValue <= 0x7C) {
-            return SHIFT_JIS;
+          // ... otherwise do check to see if this plus the next byte form a valid
+          // double byte pair encoding a character.
+          lastWasPossibleDoubleByteStart = true;
+          int nextValue = bytes[i + 1] & 0xFF;
+          if ((value & 0x1) == 0) {
+            // if even, next value should be in [0x9F,0xFC]
+            // if not, we'll guess UTF-8
+            if (nextValue < 0x9F || nextValue > 0xFC) {
+              return UTF8;
+            }
+          } else {
+            // if odd, next value should be in [0x40,0x9E]
+            // if not, we'll guess UTF-8
+            if (nextValue < 0x40 || nextValue > 0x9E) {
+              return UTF8;
+            }
           }
         }
-        // otherwise we're going to take a guess that it's UTF-8
-        return UTF8;
       }
     }
-    return ISO88591;
+    return canBeISO88591 ? ISO88591 : SHIFT_JIS;
   }
 
 }