/* * 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. */ using System; using ReaderException = com.google.zxing.ReaderException; using DecoderResult = com.google.zxing.common.DecoderResult; namespace com.google.zxing.pdf417.decoder { ///

This class contains the methods for decoding the PDF417 codewords.

/// ///
/// SITA Lab (kevin.osullivan@sita.aero) /// /// www.Redivivus.in (suraj.supekar@redivivus.in) - Ported from ZXING Java Source /// sealed class DecodedBitStreamParser { private const int TEXT_COMPACTION_MODE_LATCH = 900; private const int BYTE_COMPACTION_MODE_LATCH = 901; private const int NUMERIC_COMPACTION_MODE_LATCH = 902; private const int BYTE_COMPACTION_MODE_LATCH_6 = 924; private const int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928; private const int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923; private const int MACRO_PDF417_TERMINATOR = 922; private const int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; private const int MAX_NUMERIC_CODEWORDS = 15; private const int ALPHA = 0; private const int LOWER = 1; private const int MIXED = 2; private const int PUNCT = 3; private const int PUNCT_SHIFT = 4; private const int PL = 25; private const int LL = 27; private const int AS = 27; private const int ML = 28; private const int AL = 28; private const int PS = 29; private const int PAL = 29; //UPGRADE_NOTE: Final was removed from the declaration of 'PUNCT_CHARS'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" private static readonly char[] PUNCT_CHARS = new char[]{';', '<', '>', '@', '[', (char) (92), '}', '_', (char) (96), '~', '!', (char) (13), (char) (9), ',', ':', (char) (10), '-', '.', '$', '/', (char) (34), '|', '*', '(', ')', '?', '{', '}', (char) (39)}; //UPGRADE_NOTE: Final was removed from the declaration of 'MIXED_CHARS'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" private static readonly char[] MIXED_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '&', (char) (13), (char) (9), ',', ':', '#', '-', '.', '$', '/', '+', '%', '*', '=', '^'}; // Table containing values for the exponent of 900. // This is used in the numeric compaction decode algorithm. //UPGRADE_NOTE: Final was removed from the declaration of 'EXP900'. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'" private static readonly System.String[] EXP900 = new System.String[]{"000000000000000000000000000000000000000000001", "000000000000000000000000000000000000000000900", "000000000000000000000000000000000000000810000", "000000000000000000000000000000000000729000000", "000000000000000000000000000000000656100000000", "000000000000000000000000000000590490000000000", "000000000000000000000000000531441000000000000", "000000000000000000000000478296900000000000000", "000000000000000000000430467210000000000000000", "000000000000000000387420489000000000000000000", "000000000000000348678440100000000000000000000", "000000000000313810596090000000000000000000000", "000000000282429536481000000000000000000000000", "000000254186582832900000000000000000000000000", "000228767924549610000000000000000000000000000", "205891132094649000000000000000000000000000000"}; private DecodedBitStreamParser() { } internal static DecoderResult decode(int[] codewords) { System.Text.StringBuilder result = new System.Text.StringBuilder(100); // Get compaction mode int codeIndex = 1; int code = codewords[codeIndex++]; while (codeIndex < codewords[0]) { switch (code) { case TEXT_COMPACTION_MODE_LATCH: { codeIndex = textCompaction(codewords, codeIndex, result); break; } case BYTE_COMPACTION_MODE_LATCH: { codeIndex = byteCompaction(code, codewords, codeIndex, result); break; } case NUMERIC_COMPACTION_MODE_LATCH: { codeIndex = numericCompaction(codewords, codeIndex, result); break; } case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: { codeIndex = byteCompaction(code, codewords, codeIndex, result); break; } case BYTE_COMPACTION_MODE_LATCH_6: { codeIndex = byteCompaction(code, codewords, codeIndex, result); break; } default: { // Default to text compaction. During testing numerous barcodes // appeared to be missing the starting mode. In these cases defaulting // to text compaction seems to work. codeIndex--; codeIndex = textCompaction(codewords, codeIndex, result); break; } } if (codeIndex < codewords.Length) { code = codewords[codeIndex++]; } else { throw ReaderException.Instance; } } return new DecoderResult(null, result.ToString(), null, null); } /// Text Compaction mode (see 5.4.1.5) permits all printable ASCII characters to be /// encoded, i.e. values 32 - 126 inclusive in accordance with ISO/IEC 646 (IRV), as /// well as selected control characters. /// /// /// The array of codewords (data + error) /// /// The current index into the codeword array. /// /// The decoded data is appended to the result. /// /// The next index into the codeword array. /// private static int textCompaction(int[] codewords, int codeIndex, System.Text.StringBuilder result) { // 2 character per codeword int[] textCompactionData = new int[codewords[0] << 1]; // Used to hold the byte compaction value if there is a mode shift int[] byteCompactionData = new int[codewords[0] << 1]; int index = 0; bool end = false; while ((codeIndex < codewords[0]) && !end) { int code = codewords[codeIndex++]; if (code < TEXT_COMPACTION_MODE_LATCH) { textCompactionData[index] = code / 30; textCompactionData[index + 1] = code % 30; index += 2; } else { switch (code) { case TEXT_COMPACTION_MODE_LATCH: { codeIndex--; end = true; break; } case BYTE_COMPACTION_MODE_LATCH: { codeIndex--; end = true; break; } case NUMERIC_COMPACTION_MODE_LATCH: { codeIndex--; end = true; break; } case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: { // The Mode Shift codeword 913 shall cause a temporary // switch from Text Compaction mode to Byte Compaction mode. // This switch shall be in effect for only the next codeword, // after which the mode shall revert to the prevailing sub-mode // of the Text Compaction mode. Codeword 913 is only available // in Text Compaction mode; its use is described in 5.4.2.4. textCompactionData[index] = MODE_SHIFT_TO_BYTE_COMPACTION_MODE; byteCompactionData[index] = code; //Integer.toHexString(code); index++; break; } case BYTE_COMPACTION_MODE_LATCH_6: { codeIndex--; end = true; break; } } } } decodeTextCompaction(textCompactionData, byteCompactionData, index, result); return codeIndex; } /// The Text Compaction mode includes all the printable ASCII characters /// (i.e. values from 32 to 126) and three ASCII control characters: HT or tab /// (ASCII value 9), LF or line feed (ASCII value 10), and CR or carriage /// return (ASCII value 13). The Text Compaction mode also includes various latch /// and shift characters which are used exclusively within the mode. The Text /// Compaction mode encodes up to 2 characters per codeword. The compaction rules /// for converting data into PDF417 codewords are defined in 5.4.2.2. The sub-mode /// switches are defined in 5.4.2.3. /// /// /// The text compaction data. /// /// The byte compaction data if there /// was a mode shift. /// /// The size of the text compaction and byte compaction data. /// /// The decoded data is appended to the result. /// private static void decodeTextCompaction(int[] textCompactionData, int[] byteCompactionData, int length, System.Text.StringBuilder result) { // Beginning from an initial state of the Alpha sub-mode // The default compaction mode for PDF417 in effect at the start of each symbol shall always be Text // Compaction mode Alpha sub-mode (uppercase alphabetic). A latch codeword from another mode to the Text // Compaction mode shall always switch to the Text Compaction Alpha sub-mode. int subMode = ALPHA; int priorToShiftMode = ALPHA; int i = 0; while (i < length) { int subModeCh = textCompactionData[i]; char ch = (char) (0); switch (subMode) { case ALPHA: // Alpha (uppercase alphabetic) if (subModeCh < 26) { // Upper case Alpha Character ch = (char) ('A' + subModeCh); } else { if (subModeCh == 26) { ch = ' '; } else if (subModeCh == LL) { subMode = LOWER; } else if (subModeCh == ML) { subMode = MIXED; } else if (subModeCh == PS) { // Shift to punctuation priorToShiftMode = subMode; subMode = PUNCT_SHIFT; } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) { result.Append((char) byteCompactionData[i]); } } break; case LOWER: // Lower (lowercase alphabetic) if (subModeCh < 26) { ch = (char) ('a' + subModeCh); } else { if (subModeCh == 26) { ch = ' '; } else if (subModeCh == AL) { subMode = ALPHA; } else if (subModeCh == ML) { subMode = MIXED; } else if (subModeCh == PS) { // Shift to punctuation priorToShiftMode = subMode; subMode = PUNCT_SHIFT; } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) { result.Append((char) byteCompactionData[i]); } } break; case MIXED: // Mixed (numeric and some punctuation) if (subModeCh < PL) { ch = MIXED_CHARS[subModeCh]; } else { if (subModeCh == PL) { subMode = PUNCT; } else if (subModeCh == 26) { ch = ' '; } else if (subModeCh == AS) { //mode_change = true; } else if (subModeCh == AL) { subMode = ALPHA; } else if (subModeCh == PS) { // Shift to punctuation priorToShiftMode = subMode; subMode = PUNCT_SHIFT; } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) { result.Append((char) byteCompactionData[i]); } } break; case PUNCT: // Punctuation if (subModeCh < PS) { ch = PUNCT_CHARS[subModeCh]; } else { if (subModeCh == PAL) { subMode = ALPHA; } else if (subModeCh == MODE_SHIFT_TO_BYTE_COMPACTION_MODE) { result.Append((char) byteCompactionData[i]); } } break; case PUNCT_SHIFT: // Restore sub-mode subMode = priorToShiftMode; if (subModeCh < PS) { ch = PUNCT_CHARS[subModeCh]; } else { if (subModeCh == PAL) { subMode = ALPHA; } } break; } if (ch != 0) { // Append decoded character to result result.Append(ch); } i++; } } /// Byte Compaction mode (see 5.4.3) permits all 256 possible 8-bit byte values to be encoded. /// This includes all ASCII characters value 0 to 127 inclusive and provides for international /// character set support. /// /// /// The byte compaction mode i.e. 901 or 924 /// /// The array of codewords (data + error) /// /// The current index into the codeword array. /// /// The decoded data is appended to the result. /// /// The next index into the codeword array. /// private static int byteCompaction(int mode, int[] codewords, int codeIndex, System.Text.StringBuilder result) { if (mode == BYTE_COMPACTION_MODE_LATCH) { // Total number of Byte Compaction characters to be encoded // is not a multiple of 6 int count = 0; long value_Renamed = 0; char[] decodedData = new char[6]; int[] byteCompactedCodewords = new int[6]; bool end = false; while ((codeIndex < codewords[0]) && !end) { int code = codewords[codeIndex++]; if (code < TEXT_COMPACTION_MODE_LATCH) { byteCompactedCodewords[count] = code; count++; // Base 900 value_Renamed *= 900; value_Renamed += code; } else { if ((code == TEXT_COMPACTION_MODE_LATCH) || (code == BYTE_COMPACTION_MODE_LATCH) || (code == NUMERIC_COMPACTION_MODE_LATCH) || (code == BYTE_COMPACTION_MODE_LATCH_6) || (code == BEGIN_MACRO_PDF417_CONTROL_BLOCK) || (code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) || (code == MACRO_PDF417_TERMINATOR)) { } codeIndex--; end = true; } if ((count % 5 == 0) && (count > 0)) { // Decode every 5 codewords // Convert to Base 256 for (int j = 0; j < 6; ++j) { decodedData[5 - j] = (char) (value_Renamed % 256); value_Renamed >>= 8; } result.Append(decodedData); count = 0; } } // If Byte Compaction mode is invoked with codeword 901, // the final group of codewords is interpreted directly // as one byte per codeword, without compaction. for (int i = ((count / 5) * 5); i < count; i++) { result.Append((char) byteCompactedCodewords[i]); } } else if (mode == BYTE_COMPACTION_MODE_LATCH_6) { // Total number of Byte Compaction characters to be encoded // is an integer multiple of 6 int count = 0; long value_Renamed = 0; bool end = false; while ((codeIndex < codewords[0]) && !end) { int code = codewords[codeIndex++]; if (code < TEXT_COMPACTION_MODE_LATCH) { count += 1; // Base 900 value_Renamed *= 900; value_Renamed += code; } else { if ((code == TEXT_COMPACTION_MODE_LATCH) || (code == BYTE_COMPACTION_MODE_LATCH) || (code == NUMERIC_COMPACTION_MODE_LATCH) || (code == BYTE_COMPACTION_MODE_LATCH_6) || (code == BEGIN_MACRO_PDF417_CONTROL_BLOCK) || (code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) || (code == MACRO_PDF417_TERMINATOR)) { } codeIndex--; end = true; } if ((count % 5 == 0) && (count > 0)) { // Decode every 5 codewords // Convert to Base 256 char[] decodedData = new char[6]; for (int j = 0; j < 6; ++j) { decodedData[5 - j] = (char) (value_Renamed % 256); value_Renamed >>= 8; } result.Append(decodedData); } } } return codeIndex; } /// Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings. /// /// /// The array of codewords (data + error) /// /// The current index into the codeword array. /// /// The decoded data is appended to the result. /// /// The next index into the codeword array. /// private static int numericCompaction(int[] codewords, int codeIndex, System.Text.StringBuilder result) { int count = 0; bool end = false; int[] numericCodewords = new int[MAX_NUMERIC_CODEWORDS]; while ((codeIndex < codewords.Length) && !end) { int code = codewords[codeIndex++]; if (code < TEXT_COMPACTION_MODE_LATCH) { numericCodewords[count] = code; count++; } else { if ((code == TEXT_COMPACTION_MODE_LATCH) || (code == BYTE_COMPACTION_MODE_LATCH) || (code == BYTE_COMPACTION_MODE_LATCH_6) || (code == BEGIN_MACRO_PDF417_CONTROL_BLOCK) || (code == BEGIN_MACRO_PDF417_OPTIONAL_FIELD) || (code == MACRO_PDF417_TERMINATOR)) { } codeIndex--; end = true; } if ((count % MAX_NUMERIC_CODEWORDS) == 0 || code == NUMERIC_COMPACTION_MODE_LATCH) { // Re-invoking Numeric Compaction mode (by using codeword 902 // while in Numeric Compaction mode) serves to terminate the // current Numeric Compaction mode grouping as described in 5.4.4.2, // and then to start a new one grouping. System.String s = decodeBase900toBase10(numericCodewords, count); result.Append(s); count = 0; } } return codeIndex; } /// Convert a list of Numeric Compacted codewords from Base 900 to Base 10. /// /// /// The array of codewords /// /// The number of codewords /// /// The decoded string representing the Numeric data. /// /* EXAMPLE Encode the fifteen digit numeric string 000213298174000 Prefix the numeric string with a 1 and set the initial value of t = 1 000 213 298 174 000 Calculate codeword 0 d0 = 1 000 213 298 174 000 mod 900 = 200 t = 1 000 213 298 174 000 div 900 = 1 111 348 109 082 Calculate codeword 1 d1 = 1 111 348 109 082 mod 900 = 282 t = 1 111 348 109 082 div 900 = 1 234 831 232 Calculate codeword 2 d2 = 1 234 831 232 mod 900 = 632 t = 1 234 831 232 div 900 = 1 372 034 Calculate codeword 3 d3 = 1 372 034 mod 900 = 434 t = 1 372 034 div 900 = 1 524 Calculate codeword 4 d4 = 1 524 mod 900 = 624 t = 1 524 div 900 = 1 Calculate codeword 5 d5 = 1 mod 900 = 1 t = 1 div 900 = 0 Codeword sequence is: 1, 624, 434, 632, 282, 200 Decode the above codewords involves 1 x 900 power of 5 + 624 x 900 power of 4 + 434 x 900 power of 3 + 632 x 900 power of 2 + 282 x 900 power of 1 + 200 x 900 power of 0 = 1000213298174000 Remove leading 1 => Result is 000213298174000 As there are huge numbers involved here we must use fake out the maths using string tokens for the numbers. BigDecimal is not supported by J2ME. */ private static System.String decodeBase900toBase10(int[] codewords, int count) { System.Text.StringBuilder accum = null; for (int i = 0; i < count; i++) { System.Text.StringBuilder value_Renamed = multiply(EXP900[count - i - 1], codewords[i]); if (accum == null) { // First time in accum=0 accum = value_Renamed; } else { accum = add(accum.ToString(), value_Renamed.ToString()); } } System.String result = null; // Remove leading '1' which was inserted to preserve // leading zeros for (int i = 0; i < accum.Length; i++) { if (accum[i] == '1') { //result = accum.substring(i + 1); result = accum.ToString().Substring(i + 1); break; } } if (result == null) { // No leading 1 => just write the converted number. result = accum.ToString(); } return result; } /// Multiplies two String numbers /// /// /// Any number represented as a string. /// /// A number <= 999. /// /// the result of value1 * value2. /// private static System.Text.StringBuilder multiply(System.String value1, int value2) { System.Text.StringBuilder result = new System.Text.StringBuilder(value1.Length); for (int i = 0; i < value1.Length; i++) { // Put zeros into the result. result.Append('0'); } int hundreds = value2 / 100; int tens = (value2 / 10) % 10; int ones = value2 % 10; // Multiply by ones for (int j = 0; j < ones; j++) { result = add(result.ToString(), value1); } // Multiply by tens for (int j = 0; j < tens; j++) { result = add(result.ToString(), (value1 + '0').Substring(1)); } // Multiply by hundreds for (int j = 0; j < hundreds; j++) { result = add(result.ToString(), (value1 + "00").Substring(2)); } return result; } /// Add two numbers which are represented as strings. /// /// /// /// /// /// /// the result of value1 + value2 /// private static System.Text.StringBuilder add(System.String value1, System.String value2) { System.Text.StringBuilder temp1 = new System.Text.StringBuilder(5); System.Text.StringBuilder temp2 = new System.Text.StringBuilder(5); System.Text.StringBuilder result = new System.Text.StringBuilder(value1.Length); for (int i = 0; i < value1.Length; i++) { // Put zeros into the result. result.Append('0'); } int carry = 0; for (int i = value1.Length - 3; i > - 1; i -= 3) { temp1.Length = 0; temp1.Append(value1[i]); temp1.Append(value1[i + 1]); temp1.Append(value1[i + 2]); temp2.Length = 0; temp2.Append(value2[i]); temp2.Append(value2[i + 1]); temp2.Append(value2[i + 2]); int intValue1 = System.Int32.Parse(temp1.ToString()); int intValue2 = System.Int32.Parse(temp2.ToString()); int sumval = (intValue1 + intValue2 + carry) % 1000; carry = (intValue1 + intValue2 + carry) / 1000; result[i + 2] = (char) ((sumval % 10) + '0'); result[i + 1] = (char) (((sumval / 10) % 10) + '0'); result[i] = (char) ((sumval / 100) + '0'); } return result; } /* private static String decodeBase900toBase10(int codewords[], int count) { BigInteger accum = BigInteger.valueOf(0); BigInteger value = null; for (int i = 0; i < count; i++) { value = BigInteger.valueOf(900).pow(count - i - 1); value = value.multiply(BigInteger.valueOf(codewords[i])); accum = accum.add(value); } if (debug) System.out.println("Big Integer " + accum); String result = accum.toString().substring(1); return result; } */ } }