/*
|
* 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 "ZXAztecDecoder.h"
|
#import "ZXAztecDetectorResult.h"
|
#import "ZXBitMatrix.h"
|
#import "ZXBoolArray.h"
|
#import "ZXDecoderResult.h"
|
#import "ZXErrors.h"
|
#import "ZXGenericGF.h"
|
#import "ZXIntArray.h"
|
#import "ZXReedSolomonDecoder.h"
|
#import "ZXByteArray.h"
|
|
typedef enum {
|
ZXAztecTableUpper = 0,
|
ZXAztecTableLower,
|
ZXAztecTableMixed,
|
ZXAztecTableDigit,
|
ZXAztecTablePunct,
|
ZXAztecTableBinary
|
} ZXAztecTable;
|
|
static NSString *ZX_AZTEC_UPPER_TABLE[] = {
|
@"CTRL_PS", @" ", @"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", @"CTRL_LL", @"CTRL_ML", @"CTRL_DL", @"CTRL_BS"
|
};
|
|
static NSString *ZX_AZTEC_LOWER_TABLE[] = {
|
@"CTRL_PS", @" ", @"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", @"CTRL_US", @"CTRL_ML", @"CTRL_DL", @"CTRL_BS"
|
};
|
|
static NSString *ZX_AZTEC_MIXED_TABLE[] = {
|
@"CTRL_PS", @" ", @"\1", @"\2", @"\3", @"\4", @"\5", @"\6", @"\7", @"\b", @"\t", @"\n",
|
@"\13", @"\f", @"\r", @"\33", @"\34", @"\35", @"\36", @"\37", @"@", @"\\", @"^", @"_",
|
@"`", @"|", @"~", @"\177", @"CTRL_LL", @"CTRL_UL", @"CTRL_PL", @"CTRL_BS"
|
};
|
|
static NSString *ZX_AZTEC_PUNCT_TABLE[] = {
|
@"", @"\r", @"\r\n", @". ", @", ", @": ", @"!", @"\"", @"#", @"$", @"%", @"&", @"'", @"(", @")",
|
@"*", @"+", @",", @"-", @".", @"/", @":", @";", @"<", @"=", @">", @"?", @"[", @"]", @"{", @"}", @"CTRL_UL"
|
};
|
|
static NSString *ZX_AZTEC_DIGIT_TABLE[] = {
|
@"CTRL_PS", @" ", @"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @",", @".", @"CTRL_UL", @"CTRL_US"
|
};
|
|
@interface ZXAztecDecoder ()
|
|
@property (nonatomic, strong) ZXAztecDetectorResult *ddata;
|
|
@end
|
|
@implementation ZXAztecDecoder
|
|
- (ZXDecoderResult *)decode:(ZXAztecDetectorResult *)detectorResult error:(NSError **)error {
|
self.ddata = detectorResult;
|
ZXBitMatrix *matrix = [detectorResult bits];
|
ZXBoolArray *rawbits = [self extractBits:matrix];
|
if (!rawbits) {
|
if (error) *error = ZXFormatErrorInstance();
|
return nil;
|
}
|
|
ZXBoolArray *correctedBits = [self correctBits:rawbits error:error];
|
if (!correctedBits) {
|
return nil;
|
}
|
NSMutableArray *rawBytes = [ZXAztecDecoder convertBoolArrayToByteArray: correctedBits];
|
NSString *result = [[self class] encodedData:correctedBits];
|
|
NSUInteger rawBytesSize = [rawBytes count];
|
ZXByteArray *rawBytesReturned = [[ZXByteArray alloc] initWithLength:(unsigned int)rawBytesSize];
|
for (int i = 0; i < rawBytesSize; i++) {
|
rawBytesReturned.array[i] = (int8_t)[rawBytes[i] intValue];
|
}
|
|
return [[ZXDecoderResult alloc] initWithRawBytes:rawBytesReturned text:result byteSegments:nil ecLevel:nil];
|
}
|
|
+ (NSString *)highLevelDecode:(ZXBoolArray *)correctedBits {
|
return [self encodedData:correctedBits];
|
}
|
|
/**
|
* Gets the string encoded in the aztec code bits
|
*
|
* @return the decoded string
|
*/
|
+ (NSString *)encodedData:(ZXBoolArray *)correctedBits {
|
int endIndex = (int)correctedBits.length;
|
ZXAztecTable latchTable = ZXAztecTableUpper; // table most recently latched to
|
ZXAztecTable shiftTable = ZXAztecTableUpper; // table to use for the next read
|
NSMutableString *result = [NSMutableString stringWithCapacity:20];
|
int index = 0;
|
while (index < endIndex) {
|
if (shiftTable == ZXAztecTableBinary) {
|
if (endIndex - index < 5) {
|
break;
|
}
|
int length = [self readCode:correctedBits startIndex:index length:5];
|
index += 5;
|
if (length == 0) {
|
if (endIndex - index < 11) {
|
break;
|
}
|
|
length = [self readCode:correctedBits startIndex:index length:11] + 31;
|
index += 11;
|
}
|
for (int charCount = 0; charCount < length; charCount++) {
|
if (endIndex - index < 8) {
|
index = endIndex; // Force outer loop to exit
|
break;
|
}
|
|
int code = [self readCode:correctedBits startIndex:index length:8];
|
[result appendFormat:@"%C", (unichar)code];
|
index += 8;
|
}
|
// Go back to whatever mode we had been in
|
shiftTable = latchTable;
|
} else {
|
int size = shiftTable == ZXAztecTableDigit ? 4 : 5;
|
if (endIndex - index < size) {
|
break;
|
}
|
int code = [self readCode:correctedBits startIndex:index length:size];
|
index += size;
|
NSString *str = [self character:shiftTable code:code];
|
if ([str hasPrefix:@"CTRL_"]) {
|
// Table changes
|
shiftTable = [self table:[str characterAtIndex:5]];
|
if ([str characterAtIndex:6] == 'L') {
|
latchTable = shiftTable;
|
}
|
} else {
|
[result appendString:str];
|
// Go back to whatever mode we had been in
|
shiftTable = latchTable;
|
}
|
}
|
}
|
return [NSString stringWithString:result];
|
}
|
|
/**
|
* gets the table corresponding to the char passed
|
*/
|
+ (ZXAztecTable)table:(unichar)t {
|
switch (t) {
|
case 'L':
|
return ZXAztecTableLower;
|
case 'P':
|
return ZXAztecTablePunct;
|
case 'M':
|
return ZXAztecTableMixed;
|
case 'D':
|
return ZXAztecTableDigit;
|
case 'B':
|
return ZXAztecTableBinary;
|
case 'U':
|
default:
|
return ZXAztecTableUpper;
|
}
|
}
|
|
/**
|
* Gets the character (or string) corresponding to the passed code in the given table
|
*
|
* @param table the table used
|
* @param code the code of the character
|
*/
|
+ (NSString *)character:(ZXAztecTable)table code:(int)code {
|
switch (table) {
|
case ZXAztecTableUpper:
|
return ZX_AZTEC_UPPER_TABLE[code];
|
case ZXAztecTableLower:
|
return ZX_AZTEC_LOWER_TABLE[code];
|
case ZXAztecTableMixed:
|
return ZX_AZTEC_MIXED_TABLE[code];
|
case ZXAztecTablePunct:
|
return ZX_AZTEC_PUNCT_TABLE[code];
|
case ZXAztecTableDigit:
|
return ZX_AZTEC_DIGIT_TABLE[code];
|
default:
|
// Should not reach here.
|
@throw [NSException exceptionWithName:@"IllegalStateException" reason:@"Bad table" userInfo:nil];
|
}
|
}
|
|
/**
|
* <p>Performs RS error correction on an array of bits.</p>
|
*
|
* @return the number of corrected bits, or 0 if the input contains too many errors
|
*/
|
- (ZXBoolArray *)correctBits:(ZXBoolArray *)rawbits error:(NSError **)error {
|
ZXGenericGF *gf;
|
int codewordSize;
|
|
if ([self.ddata nbLayers] <= 2) {
|
codewordSize = 6;
|
gf = [ZXGenericGF AztecData6];
|
} else if ([self.ddata nbLayers] <= 8) {
|
codewordSize = 8;
|
gf = [ZXGenericGF AztecData8];
|
} else if ([self.ddata nbLayers] <= 22) {
|
codewordSize = 10;
|
gf = [ZXGenericGF AztecData10];
|
} else {
|
codewordSize = 12;
|
gf = [ZXGenericGF AztecData12];
|
}
|
|
int numDataCodewords = [self.ddata nbDatablocks];
|
int numCodewords = rawbits.length / codewordSize;
|
if (numCodewords < numDataCodewords) {
|
if (error) *error = ZXFormatErrorInstance();
|
return 0;
|
}
|
int offset = rawbits.length % codewordSize;
|
int numECCodewords = numCodewords - numDataCodewords;
|
|
ZXIntArray *dataWords = [[ZXIntArray alloc] initWithLength:numCodewords];
|
for (int i = 0; i < numCodewords; i++, offset += codewordSize) {
|
dataWords.array[i] = [[self class] readCode:rawbits startIndex:offset length:codewordSize];
|
}
|
|
ZXReedSolomonDecoder *rsDecoder = [[ZXReedSolomonDecoder alloc] initWithField:gf];
|
NSError *decodeError = nil;
|
if (![rsDecoder decode:dataWords twoS:numECCodewords error:&decodeError]) {
|
if (decodeError.code == ZXReedSolomonError) {
|
if (error) *error = ZXFormatErrorInstance();
|
} else {
|
if (error) *error = decodeError;
|
}
|
return 0;
|
}
|
|
// Now perform the unstuffing operation.
|
// First, count how many bits are going to be thrown out as stuffing
|
int mask = (1 << codewordSize) - 1;
|
int stuffedBits = 0;
|
for (int i = 0; i < numDataCodewords; i++) {
|
int32_t dataWord = dataWords.array[i];
|
if (dataWord == 0 || dataWord == mask) {
|
if (error) *error = ZXFormatErrorInstance();
|
return 0;
|
} else if (dataWord == 1 || dataWord == mask - 1) {
|
stuffedBits++;
|
}
|
}
|
|
// Now, actually unpack the bits and remove the stuffing
|
ZXBoolArray *correctedBits = [[ZXBoolArray alloc] initWithLength:numDataCodewords * codewordSize - stuffedBits];
|
int index = 0;
|
for (int i = 0; i < numDataCodewords; i++) {
|
int dataWord = dataWords.array[i];
|
if (dataWord == 1 || dataWord == mask - 1) {
|
// next codewordSize-1 bits are all zeros or all ones
|
memset(correctedBits.array + index * sizeof(BOOL), dataWord > 1, codewordSize - 1);
|
index += codewordSize - 1;
|
} else {
|
for (int bit = codewordSize - 1; bit >= 0; --bit) {
|
correctedBits.array[index++] = (dataWord & (1 << bit)) != 0;
|
}
|
}
|
}
|
return correctedBits;
|
}
|
|
/**
|
* Gets the array of bits from an Aztec Code matrix
|
*
|
* @return the size of the array of bits
|
*/
|
- (ZXBoolArray *)extractBits:(ZXBitMatrix *)matrix {
|
BOOL compact = self.ddata.isCompact;
|
int layers = self.ddata.nbLayers;
|
int baseMatrixSize = compact ? 11 + layers * 4 : 14 + layers * 4; // not including alignment lines
|
ZXIntArray *alignmentMap = [[ZXIntArray alloc] initWithLength:baseMatrixSize];
|
ZXBoolArray *rawbits = [[ZXBoolArray alloc] initWithLength:[self totalBitsInLayer:layers compact:compact]];
|
|
if (compact) {
|
for (int i = 0; i < alignmentMap.length; i++) {
|
alignmentMap.array[i] = i;
|
}
|
} else {
|
int matrixSize = baseMatrixSize + 1 + 2 * ((baseMatrixSize / 2 - 1) / 15);
|
int origCenter = baseMatrixSize / 2;
|
int center = matrixSize / 2;
|
for (int i = 0; i < origCenter; i++) {
|
int newOffset = i + i / 15;
|
alignmentMap.array[origCenter - i - 1] = (int32_t)(center - newOffset - 1);
|
alignmentMap.array[origCenter + i] = (int32_t)(center + newOffset + 1);
|
}
|
}
|
for (int i = 0, rowOffset = 0; i < layers; i++) {
|
int rowSize = compact ? (layers - i) * 4 + 9 : (layers - i) * 4 + 12;
|
// The top-left most point of this layer is <low, low> (not including alignment lines)
|
int low = i * 2;
|
// The bottom-right most point of this layer is <high, high> (not including alignment lines)
|
int high = baseMatrixSize - 1 - low;
|
// We pull bits from the two 2 x rowSize columns and two rowSize x 2 rows
|
for (int j = 0; j < rowSize; j++) {
|
int columnOffset = j * 2;
|
for (int k = 0; k < 2; k++) {
|
// left column
|
rawbits.array[rowOffset + columnOffset + k] =
|
[matrix getX:alignmentMap.array[low + k] y:alignmentMap.array[low + j]];
|
// bottom row
|
rawbits.array[rowOffset + 2 * rowSize + columnOffset + k] =
|
[matrix getX:alignmentMap.array[low + j] y:alignmentMap.array[high - k]];
|
// right column
|
rawbits.array[rowOffset + 4 * rowSize + columnOffset + k] =
|
[matrix getX:alignmentMap.array[high - k] y:alignmentMap.array[high - j]];
|
// top row
|
rawbits.array[rowOffset + 6 * rowSize + columnOffset + k] =
|
[matrix getX:alignmentMap.array[high - j] y:alignmentMap.array[low + k]];
|
}
|
}
|
rowOffset += rowSize * 8;
|
}
|
return rawbits;
|
}
|
|
/**
|
* Reads a code of given length and at given index in an array of bits
|
*/
|
+ (int)readCode:(ZXBoolArray *)rawbits startIndex:(int)startIndex length:(int)length {
|
int res = 0;
|
for (int i = startIndex; i < startIndex + length; i++) {
|
res <<= 1;
|
if (rawbits.array[i]) {
|
res |= 0x01;
|
}
|
}
|
return res;
|
}
|
|
/**
|
* Reads a code of length 8 in an array of bits, padding with zeros
|
*/
|
+ (int) readByte:(ZXBoolArray *) rawbits startIndex:(int) startIndex {
|
int n = rawbits.length - startIndex;
|
if (n >= 8) {
|
return (int) [self readCode:rawbits startIndex:startIndex length:8];
|
}
|
return (int) ([self readCode:rawbits startIndex:startIndex length:n] << (8 - n));
|
}
|
|
/**
|
* Packs a bit array into bytes, most significant bit first
|
*/
|
+ (NSMutableArray *) convertBoolArrayToByteArray:(ZXBoolArray *) boolArr {
|
NSMutableArray *byteArr = [[NSMutableArray alloc] init];
|
int byteArrLength = (boolArr.length + 7) / 8;
|
for (int i = 0; i < byteArrLength; i++) {
|
int code = [self readByte:boolArr startIndex:8 * i];
|
[byteArr addObject:@(code)];
|
}
|
return byteArr;
|
}
|
|
- (int)totalBitsInLayer:(int)layers compact:(BOOL)compact {
|
return ((compact ? 88 : 112) + 16 * layers) * layers;
|
}
|
|
@end
|