More minor code improvements
[zxing.git] / core / src / com / google / zxing / qrcode / decoder / DecodedBitStreamParser.java
index 8fa91f4..059007d 100644 (file)
@@ -21,7 +21,10 @@ import com.google.zxing.ReaderException;
 import java.io.UnsupportedEncodingException;
 
 /**
- * See ISO 18004:2006, 6.4.3 - 6.4.7
+ * <p>QR Codes can encode text as bits in one of several modes, and can use multiple modes
+ * in one QR Code. This class decodes the bits back into text.</p>
+ *
+ * <p>See ISO 18004:2006, 6.4.3 - 6.4.7</p>
  *
  * @author srowen@google.com (Sean Owen)
  */
@@ -37,12 +40,14 @@ final class DecodedBitStreamParser {
       ' ', '$', '%', '*', '+', '-', '.', '/', ':'
   };
   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() {
@@ -54,8 +59,9 @@ final class DecodedBitStreamParser {
     Mode mode;
     do {
       // While still another segment to read...
-      mode = Mode.forBits(bits.readBits(4));
+      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);
@@ -66,11 +72,12 @@ final class DecodedBitStreamParser {
         } else if (mode.equals(Mode.KANJI)) {
           decodeKanjiSegment(bits, result, count);
         } else {
-          throw new ReaderException("Unsupported mode indicator: " + mode);
+          throw new ReaderException("Unsupported mode indicator");
         }
       }
     } while (!mode.equals(Mode.TERMINATOR));
 
+    // I thought it wasn't allowed to leave extra bytes after the terminator but it happens
     /*
     int bitsLeft = bits.available();
     if (bitsLeft > 0) {
@@ -85,9 +92,12 @@ final class DecodedBitStreamParser {
   private static void decodeKanjiSegment(BitSource bits,
                                          StringBuffer result,
                                          int count) throws ReaderException {
+    // Each character will require 2 bytes. Read the characters as 2-byte pairs
+    // and decode as Shift_JIS afterwards
     byte[] buffer = new byte[2 * count];
     int offset = 0;
     while (count > 0) {
+      // Each 13 bits encodes a 2-byte character
       int twoBytes = bits.readBits(13);
       int assembledTwoBytes = ((twoBytes / 0x0C0) << 8) | (twoBytes % 0x0C0);
       if (assembledTwoBytes < 0x01F00) {
@@ -104,9 +114,9 @@ final class DecodedBitStreamParser {
     }
     // Shift_JIS may not be supported in some environments:
     try {
-      result.append(new String(buffer, "Shift_JIS"));
+      result.append(new String(buffer, SHIFT_JIS));
     } catch (UnsupportedEncodingException uee) {
-      throw new ReaderException("Can't decode SHIFT_JIS string: " + uee);
+      throw new ReaderException(SHIFT_JIS + " encoding is not supported on this device");
     }
   }
 
@@ -144,7 +154,7 @@ final class DecodedBitStreamParser {
       count -= 2;
     }
     if (count == 1) {
-      // special case on char left
+      // special case: one character left
       result.append(ALPHANUMERIC_CHARS[bits.readBits(6)]);
     }
   }
@@ -152,7 +162,9 @@ final class DecodedBitStreamParser {
   private static void decodeNumericSegment(BitSource bits,
                                            StringBuffer result,
                                            int count) throws ReaderException {
+    // Read three digits at a time
     while (count >= 3) {
+      // Each 10 bits encodes three digits
       int threeDigitsBits = bits.readBits(10);
       if (threeDigitsBits >= 1000) {
         throw new ReaderException("Illegal value for 3-digit unit: " + threeDigitsBits);
@@ -163,6 +175,7 @@ final class DecodedBitStreamParser {
       count -= 3;
     }
     if (count == 2) {
+      // Two digits left over to read, encoded in 7 bits
       int twoDigitsBits = bits.readBits(7);
       if (twoDigitsBits >= 100) {
         throw new ReaderException("Illegal value for 2-digit unit: " + twoDigitsBits);
@@ -170,6 +183,7 @@ final class DecodedBitStreamParser {
       result.append(ALPHANUMERIC_CHARS[twoDigitsBits / 10]);
       result.append(ALPHANUMERIC_CHARS[twoDigitsBits % 10]);
     } else if (count == 1) {
+      // One digit left over to read
       int digitBits = bits.readBits(4);
       if (digitBits >= 10) {
         throw new ReaderException("Illegal value for digit unit: " + digitBits);
@@ -182,32 +196,55 @@ final class DecodedBitStreamParser {
     if (ASSUME_SHIFT_JIS) {
       return SHIFT_JIS;
     }
-    // For now, merely tries to distinguish ISO-8859-1 and Shift_JIS,
+    // Does it start with the UTF-8 byte order mark? then guess it's UTF-8
+    if (bytes.length > 3 && bytes[0] == (byte) 0xEF && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF) {
+      return UTF8;
+    }
+    // For now, merely tries to distinguish ISO-8859-1, UTF-8 and Shift_JIS,
     // which should be by far the most common encodings. ISO-8859-1
     // should not have bytes in the 0x80 - 0x9F range, while Shift_JIS
     // uses this as a first byte of a two-byte character. If we see this
     // followed by a valid second byte in Shift_JIS, assume it is Shift_JIS.
+    // If we see something else in that second byte, we'll make the risky guess
+    // that it's UTF-8.
     int length = bytes.length;
+    boolean canBeISO88591 = true;
+    boolean 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;
+            }
           }
         }
       }
     }
-    return "ISO-8859-1";
+    return canBeISO88591 ? ISO88591 : SHIFT_JIS;
   }
 
 }