Add codeset A support to Code 128 (#877)

* add support of Codeset A for Code 128

* simplification, everything from 0 to 127 is supported by Codeset A and B
diff --git a/core/src/main/java/com/google/zxing/oned/Code128Writer.java b/core/src/main/java/com/google/zxing/oned/Code128Writer.java
index 7ce9808..2c850d1 100644
--- a/core/src/main/java/com/google/zxing/oned/Code128Writer.java
+++ b/core/src/main/java/com/google/zxing/oned/Code128Writer.java
@@ -32,8 +32,10 @@
  */
 public final class Code128Writer extends OneDimensionalCodeWriter {
 
+  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_CODE_A = 101;
   private static final int CODE_CODE_B = 100;
   private static final int CODE_CODE_C = 99;
   private static final int CODE_STOP = 106;
@@ -47,6 +49,7 @@
   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_A = 101; // Code A
   private static final int CODE_FNC_4_B = 100; // Code B
 
   // Results of minimal lookahead for code C
@@ -80,16 +83,17 @@
     // 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:
+      switch (c) {
+        case ESCAPE_FNC_1:
+        case ESCAPE_FNC_2:
+        case ESCAPE_FNC_3:
+        case ESCAPE_FNC_4:
+          break;
+        default:
+          if (c > 127) {
+            // support for FNC4 isn't implemented, no full Latin-1 character set available at the moment
             throw new IllegalArgumentException("Bad character in input: " + c);
-        }
+          }
       }
     }
 
@@ -119,15 +123,30 @@
             patternIndex = CODE_FNC_3;
             break;
           case ESCAPE_FNC_4:
-            patternIndex = CODE_FNC_4_B; // FIXME if this ever outputs Code A
+            if (codeSet == CODE_CODE_A) {
+              patternIndex = CODE_FNC_4_A;
+            } else {
+              patternIndex = CODE_FNC_4_B;
+            }
             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
+            switch (codeSet) {
+              case CODE_CODE_A:
+                patternIndex = contents.charAt(position) - ' ';
+                if (patternIndex < 0) {
+                  // everything below a space character comes behind the underscore in the code patterns table
+                  patternIndex += '`';
+                }
+                break;
+              case CODE_CODE_B:
+                patternIndex = contents.charAt(position) - ' ';
+                break;
+              default:
+                // CODE_CODE_C
+                patternIndex = Integer.parseInt(contents.substring(position, position + 2));
+                position++; // Also incremented below
+                break;
             }
         }
         position++;
@@ -136,11 +155,16 @@
         // 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;
+          switch (newCodeSet) {
+            case CODE_CODE_A:
+              patternIndex = CODE_START_A;
+              break;
+            case CODE_CODE_B:
+              patternIndex = CODE_START_B;
+              break;
+            default:
+              patternIndex = CODE_START_C;
+              break;
           }
         } else {
           // Yes, we have a code set
@@ -208,7 +232,17 @@
 
   private static int chooseCode(CharSequence value, int start, int oldCode) {
     CType lookahead = findCType(value, start);
-    if (lookahead == CType.UNCODABLE || lookahead == CType.ONE_DIGIT) {
+    if (lookahead == CType.ONE_DIGIT) {
+       return CODE_CODE_B;
+    }
+    if (lookahead == CType.UNCODABLE) {
+      if (start < value.length()) {
+        char c = value.charAt(start);
+        if (c < ' ' || (oldCode == CODE_CODE_A && c < '`')) {
+          // can continue in code A, encodes ASCII 0 to 95
+          return CODE_CODE_A;
+        }
+      }
       return CODE_CODE_B; // no choice
     }
     if (oldCode == CODE_CODE_C) { // can continue in code C
diff --git a/core/src/test/java/com/google/zxing/oned/Code128WriterTestCase.java b/core/src/test/java/com/google/zxing/oned/Code128WriterTestCase.java
index 312128f..7a667ae 100644
--- a/core/src/test/java/com/google/zxing/oned/Code128WriterTestCase.java
+++ b/core/src/test/java/com/google/zxing/oned/Code128WriterTestCase.java
@@ -34,8 +34,10 @@
   private static final String FNC2 = "11110101000";
   private static final String FNC3 = "10111100010";
   private static final String FNC4 = "10111101110";
+  private static final String START_CODE_A = "11010000100";
   private static final String START_CODE_B = "11010010000";
   private static final String START_CODE_C = "11010011100";
+  private static final String SWITCH_CODE_A = "11101011110";
   private static final String SWITCH_CODE_B = "10111101110";
   private static final String QUIET_SPACE = "00000";
   private static final String STOP = "1100011101011";
@@ -108,6 +110,27 @@
     String actual = BitMatrixTestCase.matrixToString(result);
     assertEquals(expected, actual);
   }
+  
+  @Test
+  public void testEncodeSwitchBetweenCodesetsAAndB() throws Exception {
+    // start with A switch to B and back to A
+    //                                                      "\0"            "A"             "B"             Switch to B     "a"             "b"             Switch to A     "\u0010"        check digit
+    testEncode("\0ABab\u0010", QUIET_SPACE + START_CODE_A + "10100001100" + "10100011000" + "10001011000" + SWITCH_CODE_B + "10010110000" + "10010000110" + SWITCH_CODE_A + "10100111100" + "11001110100" + STOP + QUIET_SPACE);
 
+    // start with B switch to A and back to B
+    //                                                "a"             "b"             Switch to A     "\0             "Switch to B"   "a"             "b"             check digit
+    testEncode("ab\0ab", QUIET_SPACE + START_CODE_B + "10010110000" + "10010000110" + SWITCH_CODE_A + "10100001100" + SWITCH_CODE_B + "10010110000" + "10010000110" + "11010001110" + STOP + QUIET_SPACE);
+  }
+  
+  private void testEncode(String toEncode, String expected) throws Exception {
+    BitMatrix result = writer.encode(toEncode, BarcodeFormat.CODE_128, 0, 0);
 
+    String actual = BitMatrixTestCase.matrixToString(result);
+    assertEquals(toEncode, expected, actual);
+    
+    BitArray row = result.getRow(0, null);
+    Result rtResult = reader.decodeRow(0, row, null);
+    String actualRoundtripResultText = rtResult.getText();
+    assertEquals(toEncode, actualRoundtripResultText);
+  }
 }