/* * 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. */ #import "ZXCharacterSetECI.h" #import "ZXDecoderResult.h" #import "ZXErrors.h" #import "ZXIntArray.h" #import "ZXPDF417DecodedBitStreamParser.h" #import "ZXPDF417ResultMetadata.h" typedef enum { ZXPDF417ModeAlpha = 0, ZXPDF417ModeLower, ZXPDF417ModeMixed, ZXPDF417ModePunct, ZXPDF417ModeAlphaShift, ZXPDF417ModePunctShift } ZXPDF417Mode; const int ZX_PDF417_TEXT_COMPACTION_MODE_LATCH = 900; const int ZX_PDF417_BYTE_COMPACTION_MODE_LATCH = 901; const int ZX_PDF417_NUMERIC_COMPACTION_MODE_LATCH = 902; const int ZX_PDF417_BYTE_COMPACTION_MODE_LATCH_6 = 924; const int ZX_PDF417_ECI_USER_DEFINED = 925; const int ZX_PDF417_ECI_GENERAL_PURPOSE = 926; const int ZX_PDF417_ECI_CHARSET = 927; const int ZX_PDF417_BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928; const int ZX_PDF417_BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923; const int ZX_PDF417_MACRO_PDF417_TERMINATOR = 922; const int ZX_PDF417_MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; const int ZX_PDF417_MAX_NUMERIC_CODEWORDS = 15; const int ZX_PDF417_PL = 25; const int ZX_PDF417_LL = 27; const int ZX_PDF417_AS = 27; const int ZX_PDF417_ML = 28; const int ZX_PDF417_AL = 28; const int ZX_PDF417_PS = 29; const int ZX_PDF417_PAL = 29; const unichar ZX_PDF417_PUNCT_CHARS[] = { ';', '<', '>', '@', '[', '\\', ']', '_', '`', '~', '!', '\r', '\t', ',', ':', '\n', '-', '.', '$', '/', '"', '|', '*', '(', ')', '?', '{', '}', '\''}; const unichar ZX_PDF417_MIXED_CHARS[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '&', '\r', '\t', ',', ':', '#', '-', '.', '$', '/', '+', '%', '*', '=', '^'}; const int ZX_PDF417_NUMBER_OF_SEQUENCE_CODEWORDS = 2; const NSStringEncoding ZX_PDF417_DECODING_DEFAULT_ENCODING = NSISOLatin1StringEncoding; /** * Table containing values for the exponent of 900. * This is used in the numeric compaction decode algorithm. */ static NSArray *ZX_PDF417_EXP900 = nil; @implementation ZXPDF417DecodedBitStreamParser + (void)initialize { if ([self class] != [ZXPDF417DecodedBitStreamParser class]) return; NSMutableArray *exponents = [NSMutableArray arrayWithCapacity:16]; [exponents addObject:[NSDecimalNumber one]]; NSDecimalNumber *nineHundred = [NSDecimalNumber decimalNumberWithString:@"900"]; [exponents addObject:nineHundred]; for (int i = 2; i < 16; i++) { [exponents addObject:[exponents[i - 1] decimalNumberByMultiplyingBy:nineHundred]]; } ZX_PDF417_EXP900 = [[NSArray alloc] initWithArray:exponents]; } + (ZXDecoderResult *)decode:(ZXIntArray *)codewords ecLevel:(NSString *)ecLevel error:(NSError **)error { NSMutableString *result = [NSMutableString stringWithCapacity:codewords.length * 2]; NSStringEncoding encoding = ZX_PDF417_DECODING_DEFAULT_ENCODING; // Get compaction mode int codeIndex = 1; int code = codewords.array[codeIndex++]; ZXPDF417ResultMetadata *resultMetadata = [[ZXPDF417ResultMetadata alloc] init]; while (codeIndex < codewords.array[0]) { switch (code) { case ZX_PDF417_TEXT_COMPACTION_MODE_LATCH: codeIndex = [self textCompaction:codewords codeIndex:codeIndex result:result]; break; case ZX_PDF417_BYTE_COMPACTION_MODE_LATCH: case ZX_PDF417_BYTE_COMPACTION_MODE_LATCH_6: codeIndex = [self byteCompaction:code codewords:codewords encoding:encoding codeIndex:codeIndex result:result]; break; case ZX_PDF417_MODE_SHIFT_TO_BYTE_COMPACTION_MODE: [result appendFormat:@"%C", (unichar)codewords.array[codeIndex++]]; break; case ZX_PDF417_NUMERIC_COMPACTION_MODE_LATCH: codeIndex = [self numericCompaction:codewords codeIndex:codeIndex result:result]; if (codeIndex < 0) { if (error) *error = ZXFormatErrorInstance(); return nil; } break; case ZX_PDF417_ECI_CHARSET: { ZXCharacterSetECI *charsetECI = [ZXCharacterSetECI characterSetECIByValue:codewords.array[codeIndex++]]; encoding = charsetECI.encoding; break; } case ZX_PDF417_ECI_GENERAL_PURPOSE: // Can't do anything with generic ECI; skip its 2 characters codeIndex += 2; break; case ZX_PDF417_ECI_USER_DEFINED: // Can't do anything with user ECI; skip its 1 character codeIndex ++; break; case ZX_PDF417_BEGIN_MACRO_PDF417_CONTROL_BLOCK: codeIndex = [self decodeMacroBlock:codewords codeIndex:codeIndex resultMetadata:resultMetadata]; if (codeIndex < 0) { if (error) *error = ZXFormatErrorInstance(); return nil; } break; case ZX_PDF417_BEGIN_MACRO_PDF417_OPTIONAL_FIELD: case ZX_PDF417_MACRO_PDF417_TERMINATOR: // Should not see these outside a macro block if (error) *error = ZXFormatErrorInstance(); return nil; 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 = [self textCompaction:codewords codeIndex:codeIndex result:result]; break; } if (codeIndex < codewords.length) { code = codewords.array[codeIndex++]; } else { if (error) *error = ZXFormatErrorInstance(); return nil; } } if ([result length] == 0) { if (error) *error = ZXFormatErrorInstance(); return nil; } ZXDecoderResult *decoderResult = [[ZXDecoderResult alloc] initWithRawBytes:nil text:result byteSegments:nil ecLevel:ecLevel]; decoderResult.other = resultMetadata; return decoderResult; } + (int)decodeMacroBlock:(ZXIntArray *)codewords codeIndex:(int)codeIndex resultMetadata:(ZXPDF417ResultMetadata *)resultMetadata { if (codeIndex + ZX_PDF417_NUMBER_OF_SEQUENCE_CODEWORDS > codewords.array[0]) { // we must have at least two bytes left for the segment index return -1; } ZXIntArray *segmentIndexArray = [[ZXIntArray alloc] initWithLength:ZX_PDF417_NUMBER_OF_SEQUENCE_CODEWORDS]; for (int i = 0; i < ZX_PDF417_NUMBER_OF_SEQUENCE_CODEWORDS; i++, codeIndex++) { segmentIndexArray.array[i] = codewords.array[codeIndex]; } resultMetadata.segmentIndex = [[self decodeBase900toBase10:segmentIndexArray count:ZX_PDF417_NUMBER_OF_SEQUENCE_CODEWORDS] intValue]; NSMutableString *fileId = [NSMutableString string]; codeIndex = [self textCompaction:codewords codeIndex:codeIndex result:fileId]; resultMetadata.fileId = [NSString stringWithString:fileId]; if (codewords.array[codeIndex] == ZX_PDF417_BEGIN_MACRO_PDF417_OPTIONAL_FIELD) { codeIndex++; NSMutableArray *additionalOptionCodeWords = [NSMutableArray array]; BOOL end = NO; while ((codeIndex < codewords.array[0]) && !end) { int code = codewords.array[codeIndex++]; if (code < ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { [additionalOptionCodeWords addObject:@(code)]; } else { switch (code) { case ZX_PDF417_MACRO_PDF417_TERMINATOR: resultMetadata.lastSegment = YES; codeIndex++; end = YES; break; default: return -1; } } } resultMetadata.optionalData = additionalOptionCodeWords; } else if (codewords.array[codeIndex] == ZX_PDF417_MACRO_PDF417_TERMINATOR) { resultMetadata.lastSegment = YES; codeIndex++; } return codeIndex; } /** * 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. * * @param codewords The array of codewords (data + error) * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. * @return The next index into the codeword array. */ + (int)textCompaction:(ZXIntArray *)codewords codeIndex:(int)codeIndex result:(NSMutableString *)result { // 2 character per codeword ZXIntArray *textCompactionData = [[ZXIntArray alloc] initWithLength:(codewords.array[0] - codeIndex) * 2]; // Used to hold the byte compaction value if there is a mode shift ZXIntArray *byteCompactionData = [[ZXIntArray alloc] initWithLength:(codewords.array[0] - codeIndex) * 2]; int index = 0; BOOL end = NO; while ((codeIndex < codewords.array[0]) && !end) { int code = codewords.array[codeIndex++]; if (code < ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { textCompactionData.array[index] = code / 30; textCompactionData.array[index + 1] = code % 30; index += 2; } else { switch (code) { case ZX_PDF417_TEXT_COMPACTION_MODE_LATCH: // reinitialize text compaction mode to alpha sub mode textCompactionData.array[index++] = ZX_PDF417_TEXT_COMPACTION_MODE_LATCH; break; case ZX_PDF417_BYTE_COMPACTION_MODE_LATCH: case ZX_PDF417_BYTE_COMPACTION_MODE_LATCH_6: case ZX_PDF417_NUMERIC_COMPACTION_MODE_LATCH: case ZX_PDF417_BEGIN_MACRO_PDF417_CONTROL_BLOCK: case ZX_PDF417_BEGIN_MACRO_PDF417_OPTIONAL_FIELD: case ZX_PDF417_MACRO_PDF417_TERMINATOR: codeIndex--; end = YES; break; case ZX_PDF417_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.array[index] = ZX_PDF417_MODE_SHIFT_TO_BYTE_COMPACTION_MODE; code = codewords.array[codeIndex++]; byteCompactionData.array[index] = code; index++; break; } } } [self decodeTextCompaction:textCompactionData byteCompactionData:byteCompactionData length:index result: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. * * @param textCompactionData The text compaction data. * @param byteCompactionData The byte compaction data if there * was a mode shift. * @param length The size of the text compaction and byte compaction data. * @param result The decoded data is appended to the result. */ + (void)decodeTextCompaction:(ZXIntArray *)textCompactionData byteCompactionData:(ZXIntArray *)byteCompactionData length:(unsigned int)length result:(NSMutableString *)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. ZXPDF417Mode subMode = ZXPDF417ModeAlpha; ZXPDF417Mode priorToShiftMode = ZXPDF417ModeAlpha; int i = 0; while (i < length) { int subModeCh = textCompactionData.array[i]; unichar ch = 0; switch (subMode) { case ZXPDF417ModeAlpha: // Alpha (uppercase alphabetic) if (subModeCh < 26) { // Upper case Alpha Character ch = (unichar)('A' + subModeCh); } else { if (subModeCh == 26) { ch = ' '; } else if (subModeCh == ZX_PDF417_LL) { subMode = ZXPDF417ModeLower; } else if (subModeCh == ZX_PDF417_ML) { subMode = ZXPDF417ModeMixed; } else if (subModeCh == ZX_PDF417_PS) { // Shift to punctuation priorToShiftMode = subMode; subMode = ZXPDF417ModePunctShift; } else if (subModeCh == ZX_PDF417_MODE_SHIFT_TO_BYTE_COMPACTION_MODE) { // TODO Does this need to use the current character encoding? See other occurrences below [result appendFormat:@"%C", (unichar)byteCompactionData.array[i]]; } else if (subModeCh == ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { subMode = ZXPDF417ModeAlpha; } } break; case ZXPDF417ModeLower: // Lower (lowercase alphabetic) if (subModeCh < 26) { ch = (unichar)('a' + subModeCh); } else { if (subModeCh == 26) { ch = ' '; } else if (subModeCh == ZX_PDF417_AS) { // Shift to alpha priorToShiftMode = subMode; subMode = ZXPDF417ModeAlphaShift; } else if (subModeCh == ZX_PDF417_ML) { subMode = ZXPDF417ModeMixed; } else if (subModeCh == ZX_PDF417_PS) { // Shift to punctuation priorToShiftMode = subMode; subMode = ZXPDF417ModePunctShift; } else if (subModeCh == ZX_PDF417_MODE_SHIFT_TO_BYTE_COMPACTION_MODE) { [result appendFormat:@"%C", (unichar)byteCompactionData.array[i]]; } else if (subModeCh == ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { subMode = ZXPDF417ModeAlpha; } } break; case ZXPDF417ModeMixed: // Mixed (numeric and some punctuation) if (subModeCh < ZX_PDF417_PL) { ch = ZX_PDF417_MIXED_CHARS[subModeCh]; } else { if (subModeCh == ZX_PDF417_PL) { subMode = ZXPDF417ModePunct; } else if (subModeCh == 26) { ch = ' '; } else if (subModeCh == ZX_PDF417_LL) { subMode = ZXPDF417ModeLower; } else if (subModeCh == ZX_PDF417_AL) { subMode = ZXPDF417ModeAlpha; } else if (subModeCh == ZX_PDF417_PS) { // Shift to punctuation priorToShiftMode = subMode; subMode = ZXPDF417ModePunctShift; } else if (subModeCh == ZX_PDF417_MODE_SHIFT_TO_BYTE_COMPACTION_MODE) { [result appendFormat:@"%C", (unichar)byteCompactionData.array[i]]; } else if (subModeCh == ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { subMode = ZXPDF417ModeAlpha; } } break; case ZXPDF417ModePunct: // Punctuation if (subModeCh < ZX_PDF417_PAL) { ch = ZX_PDF417_PUNCT_CHARS[subModeCh]; } else { if (subModeCh == ZX_PDF417_PAL) { subMode = ZXPDF417ModeAlpha; } else if (subModeCh == ZX_PDF417_MODE_SHIFT_TO_BYTE_COMPACTION_MODE) { [result appendFormat:@"%C", (unichar)byteCompactionData.array[i]]; } else if (ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { subMode = ZXPDF417ModeAlpha; } } break; case ZXPDF417ModeAlphaShift: // Restore sub-mode subMode = priorToShiftMode; if (subModeCh < 26) { ch = (unichar)('A' + subModeCh); } else { if (subModeCh == 26) { ch = ' '; } else if (subModeCh == ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { subMode = ZXPDF417ModeAlpha; } } break; case ZXPDF417ModePunctShift: // Restore sub-mode subMode = priorToShiftMode; if (subModeCh < ZX_PDF417_PAL) { ch = ZX_PDF417_PUNCT_CHARS[subModeCh]; } else { if (subModeCh == ZX_PDF417_PAL) { subMode = ZXPDF417ModeAlpha; } else if (subModeCh == ZX_PDF417_MODE_SHIFT_TO_BYTE_COMPACTION_MODE) { // PS before Shift-to-Byte is used as a padding character, // see 5.4.2.4 of the specification [result appendFormat:@"%C", (unichar)byteCompactionData.array[i]]; } else if (subModeCh == ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { subMode = ZXPDF417ModeAlpha; } } break; } if (ch != 0) { // Append decoded character to result [result appendFormat:@"%C", 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. * * @param mode The byte compaction mode i.e. 901 or 924 * @param codewords The array of codewords (data + error) * @param encoding Currently active character encoding * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. * @return The next index into the codeword array. */ + (int)byteCompaction:(int)mode codewords:(ZXIntArray *)codewords encoding:(NSStringEncoding)encoding codeIndex:(int)codeIndex result:(NSMutableString *)result { NSMutableData *decodedBytes = [NSMutableData data]; if (mode == ZX_PDF417_BYTE_COMPACTION_MODE_LATCH) { // Total number of Byte Compaction characters to be encoded // is not a multiple of 6 int count = 0; long long value = 0; ZXIntArray *byteCompactedCodewords = [[ZXIntArray alloc] initWithLength:6]; BOOL end = NO; int nextCode = codewords.array[codeIndex++]; while ((codeIndex < codewords.array[0]) && !end) { byteCompactedCodewords.array[count++] = nextCode; // Base 900 value = 900 * value + nextCode; nextCode = codewords.array[codeIndex++]; // perhaps it should be ok to check only nextCode >= TEXT_COMPACTION_MODE_LATCH if (nextCode == ZX_PDF417_TEXT_COMPACTION_MODE_LATCH || nextCode == ZX_PDF417_BYTE_COMPACTION_MODE_LATCH || nextCode == ZX_PDF417_NUMERIC_COMPACTION_MODE_LATCH || nextCode == ZX_PDF417_BYTE_COMPACTION_MODE_LATCH_6 || nextCode == ZX_PDF417_BEGIN_MACRO_PDF417_CONTROL_BLOCK || nextCode == ZX_PDF417_BEGIN_MACRO_PDF417_OPTIONAL_FIELD || nextCode == ZX_PDF417_MACRO_PDF417_TERMINATOR) { codeIndex--; end = YES; } else { if ((count % 5 == 0) && (count > 0)) { // Decode every 5 codewords // Convert to Base 256 for (int j = 0; j < 6; ++j) { int8_t byte = (int8_t) (value >> (8 * (5 - j))); [decodedBytes appendBytes:&byte length:1]; } value = 0; count = 0; } } } // if the end of all codewords is reached the last codeword needs to be added if (codeIndex == codewords.array[0] && nextCode < ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { byteCompactedCodewords.array[count++] = nextCode; } // If Byte Compaction mode is invoked with codeword 901, // the last group of codewords is interpreted directly // as one byte per codeword, without compaction. for (int i = 0; i < count; i++) { int8_t byte = (int8_t)byteCompactedCodewords.array[i]; [decodedBytes appendBytes:&byte length:1]; } } else if (mode == ZX_PDF417_BYTE_COMPACTION_MODE_LATCH_6) { // Total number of Byte Compaction characters to be encoded // is an integer multiple of 6 int count = 0; long long value = 0; BOOL end = NO; while (codeIndex < codewords.array[0] && !end) { int code = codewords.array[codeIndex++]; if (code < ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { count++; // Base 900 value = 900 * value + code; } else { if (code == ZX_PDF417_TEXT_COMPACTION_MODE_LATCH || code == ZX_PDF417_BYTE_COMPACTION_MODE_LATCH || code == ZX_PDF417_NUMERIC_COMPACTION_MODE_LATCH || code == ZX_PDF417_BYTE_COMPACTION_MODE_LATCH_6 || code == ZX_PDF417_BEGIN_MACRO_PDF417_CONTROL_BLOCK || code == ZX_PDF417_BEGIN_MACRO_PDF417_OPTIONAL_FIELD || code == ZX_PDF417_MACRO_PDF417_TERMINATOR) { codeIndex--; end = YES; } } if ((count % 5 == 0) && (count > 0)) { // Decode every 5 codewords // Convert to Base 256 for (int j = 0; j < 6; ++j) { int8_t byte = (int8_t) (value >> (8 * (5 - j))); [decodedBytes appendBytes:&byte length:1]; } value = 0; count = 0; } } } [result appendString:[[NSString alloc] initWithData:decodedBytes encoding:encoding]]; return codeIndex; } /** * Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings. * * @param codewords The array of codewords (data + error) * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. * @return The next index into the codeword array. */ + (int)numericCompaction:(ZXIntArray *)codewords codeIndex:(int)codeIndex result:(NSMutableString *)result { int count = 0; BOOL end = NO; ZXIntArray *numericCodewords = [[ZXIntArray alloc] initWithLength:ZX_PDF417_MAX_NUMERIC_CODEWORDS]; while (codeIndex < codewords.array[0] && !end) { int code = codewords.array[codeIndex++]; if (codeIndex == codewords.array[0]) { end = YES; } if (code < ZX_PDF417_TEXT_COMPACTION_MODE_LATCH) { numericCodewords.array[count] = code; count++; } else { if (code == ZX_PDF417_TEXT_COMPACTION_MODE_LATCH || code == ZX_PDF417_BYTE_COMPACTION_MODE_LATCH || code == ZX_PDF417_BYTE_COMPACTION_MODE_LATCH_6 || code == ZX_PDF417_BEGIN_MACRO_PDF417_CONTROL_BLOCK || code == ZX_PDF417_BEGIN_MACRO_PDF417_OPTIONAL_FIELD || code == ZX_PDF417_MACRO_PDF417_TERMINATOR) { codeIndex--; end = YES; } } if (count % ZX_PDF417_MAX_NUMERIC_CODEWORDS == 0 || code == ZX_PDF417_NUMERIC_COMPACTION_MODE_LATCH || end) { // 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. if (count > 0) { NSString *s = [self decodeBase900toBase10:numericCodewords count:count]; if (s == nil) { return -1; } [result appendString:s]; count = 0; } } } return codeIndex; } /** * Convert a list of Numeric Compacted codewords from Base 900 to Base 10. * * @param codewords The array of codewords * @param count The number of codewords * @return 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 4u 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 */ + (NSString *)decodeBase900toBase10:(ZXIntArray *)codewords count:(int)count { NSDecimalNumber *result = [NSDecimalNumber zero]; for (int i = 0; i < count; i++) { result = [result decimalNumberByAdding:[ZX_PDF417_EXP900[count - i - 1] decimalNumberByMultiplyingBy:[NSDecimalNumber decimalNumberWithDecimal:[@(codewords.array[i]) decimalValue]]]]; } NSString *resultString = [result stringValue]; if (![resultString hasPrefix:@"1"]) { return nil; } return [resultString substringFromIndex:1]; } @end