/* * 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 "ZXBitArray.h" #import "ZXBarcodeFormat.h" #import "ZXDecodeHints.h" #import "ZXErrors.h" #import "ZXIntArray.h" #import "ZXResult.h" #import "ZXResultPointCallback.h" #import "ZXRSS14Reader.h" #import "ZXRSSFinderPattern.h" #import "ZXRSSPair.h" #import "ZXRSSUtils.h" const int ZX_RSS14_OUTSIDE_EVEN_TOTAL_SUBSET[5] = {1,10,34,70,126}; const int ZX_RSS14_INSIDE_ODD_TOTAL_SUBSET[4] = {4,20,48,81}; const int ZX_RSS14_OUTSIDE_GSUM[5] = {0,161,961,2015,2715}; const int ZX_RSS14_INSIDE_GSUM[4] = {0,336,1036,1516}; const int ZX_RSS14_OUTSIDE_ODD_WIDEST[5] = {8,6,4,3,1}; const int ZX_RSS14_INSIDE_ODD_WIDEST[4] = {2,4,6,8}; @interface ZXRSS14Reader () @property (nonatomic, strong, readonly) NSMutableArray *possibleLeftPairs; @property (nonatomic, strong, readonly) NSMutableArray *possibleRightPairs; @end @implementation ZXRSS14Reader - (id)init { if (self = [super init]) { _possibleLeftPairs = [NSMutableArray array]; _possibleRightPairs = [NSMutableArray array]; } return self; } - (ZXResult *)decodeRow:(int)rowNumber row:(ZXBitArray *)row hints:(ZXDecodeHints *)hints error:(NSError **)error { ZXRSSPair *leftPair = [self decodePair:row right:NO rowNumber:rowNumber hints:hints]; [self addOrTally:self.possibleLeftPairs pair:leftPair]; [row reverse]; ZXRSSPair *rightPair = [self decodePair:row right:YES rowNumber:rowNumber hints:hints]; [self addOrTally:self.possibleRightPairs pair:rightPair]; [row reverse]; for (ZXRSSPair *left in self.possibleLeftPairs) { if ([left count] > 1) { for (ZXRSSPair *right in self.possibleRightPairs) { if ([right count] > 1) { if ([self checkChecksum:left rightPair:right]) { return [self constructResult:left rightPair:right]; } } } } } if (error) *error = ZXNotFoundErrorInstance(); return nil; } - (void)addOrTally:(NSMutableArray *)possiblePairs pair:(ZXRSSPair *)pair { if (pair == nil) { return; } BOOL found = NO; for (ZXRSSPair *other in possiblePairs) { if (other.value == pair.value) { [other incrementCount]; found = YES; break; } } if (!found) { [possiblePairs addObject:pair]; } } - (void)reset { [self.possibleLeftPairs removeAllObjects]; [self.possibleRightPairs removeAllObjects]; } - (ZXResult *)constructResult:(ZXRSSPair *)leftPair rightPair:(ZXRSSPair *)rightPair { long long symbolValue = 4537077LL * leftPair.value + rightPair.value; NSString *text = [@(symbolValue) stringValue]; NSMutableString *buffer = [NSMutableString stringWithCapacity:14]; for (int i = 13 - (int)[text length]; i > 0; i--) { [buffer appendString:@"0"]; } [buffer appendString:text]; int checkDigit = 0; for (int i = 0; i < 13; i++) { int digit = [buffer characterAtIndex:i] - '0'; checkDigit += (i & 0x01) == 0 ? 3 * digit : digit; } checkDigit = 10 - (checkDigit % 10); if (checkDigit == 10) { checkDigit = 0; } [buffer appendFormat:@"%d", checkDigit]; NSArray *leftPoints = [[leftPair finderPattern] resultPoints]; NSArray *rightPoints = [[rightPair finderPattern] resultPoints]; return [ZXResult resultWithText:buffer rawBytes:nil resultPoints:@[leftPoints[0], leftPoints[1], rightPoints[0], rightPoints[1]] format:kBarcodeFormatRSS14]; } - (BOOL)checkChecksum:(ZXRSSPair *)leftPair rightPair:(ZXRSSPair *)rightPair { // int leftFPValue = leftPair.finderPattern.value; // int rightFPValue = rightPair.finderPattern.value; // if ((leftFPValue == 0 && rightFPValue == 8) || (leftFPValue == 8 && rightFPValue == 0)) { // } int checkValue = (leftPair.checksumPortion + 16 * rightPair.checksumPortion) % 79; int targetCheckValue = 9 * leftPair.finderPattern.value + rightPair.finderPattern.value; if (targetCheckValue > 72) { targetCheckValue--; } if (targetCheckValue > 8) { targetCheckValue--; } return checkValue == targetCheckValue; } - (ZXRSSPair *)decodePair:(ZXBitArray *)row right:(BOOL)right rowNumber:(int)rowNumber hints:(ZXDecodeHints *)hints { ZXIntArray *startEnd = [self findFinderPattern:row rowOffset:0 rightFinderPattern:right]; if (!startEnd) { return nil; } ZXRSSFinderPattern *pattern = [self parseFoundFinderPattern:row rowNumber:rowNumber right:right startEnd:startEnd]; if (!pattern) { return nil; } id resultPointCallback = hints == nil ? nil : hints.resultPointCallback; if (resultPointCallback != nil) { float center = (startEnd.array[0] + startEnd.array[1]) / 2.0f; if (right) { center = [row size] - 1 - center; } [resultPointCallback foundPossibleResultPoint:[[ZXResultPoint alloc] initWithX:center y:rowNumber]]; } ZXRSSDataCharacter *outside = [self decodeDataCharacter:row pattern:pattern outsideChar:YES]; ZXRSSDataCharacter *inside = [self decodeDataCharacter:row pattern:pattern outsideChar:NO]; if (!outside || !inside) { return nil; } return [[ZXRSSPair alloc] initWithValue:1597 * outside.value + inside.value checksumPortion:outside.checksumPortion + 4 * inside.checksumPortion finderPattern:pattern]; } - (ZXRSSDataCharacter *)decodeDataCharacter:(ZXBitArray *)row pattern:(ZXRSSFinderPattern *)pattern outsideChar:(BOOL)outsideChar { ZXIntArray *counters = self.dataCharacterCounters; [counters clear]; int32_t *array = counters.array; if (outsideChar) { if (![ZXOneDReader recordPatternInReverse:row start:[pattern startEnd].array[0] counters:counters]) { return nil; } } else { if (![ZXOneDReader recordPattern:row start:[pattern startEnd].array[1] counters:counters]) { return nil; } for (int i = 0, j = counters.length - 1; i < j; i++, j--) { int temp = array[i]; array[i] = array[j]; array[j] = temp; } } int numModules = outsideChar ? 16 : 15; float elementWidth = (float)[ZXAbstractRSSReader count:counters] / (float)numModules; for (int i = 0; i < counters.length; i++) { float value = (float) array[i] / elementWidth; int count = (int)(value + 0.5f); if (count < 1) { count = 1; } else if (count > 8) { count = 8; } int offset = i / 2; if ((i & 0x01) == 0) { self.oddCounts.array[offset] = count; self.oddRoundingErrors[offset] = value - count; } else { self.evenCounts.array[offset] = count; self.evenRoundingErrors[offset] = value - count; } } if (![self adjustOddEvenCounts:outsideChar numModules:numModules]) { return nil; } int oddSum = 0; int oddChecksumPortion = 0; for (int i = self.oddCounts.length - 1; i >= 0; i--) { oddChecksumPortion *= 9; oddChecksumPortion += self.oddCounts.array[i]; oddSum += self.oddCounts.array[i]; } int evenChecksumPortion = 0; int evenSum = 0; for (int i = self.evenCounts.length - 1; i >= 0; i--) { evenChecksumPortion *= 9; evenChecksumPortion += self.evenCounts.array[i]; evenSum += self.evenCounts.array[i]; } int checksumPortion = oddChecksumPortion + 3 * evenChecksumPortion; if (outsideChar) { if ((oddSum & 0x01) != 0 || oddSum > 12 || oddSum < 4) { return nil; } int group = (12 - oddSum) / 2; int oddWidest = ZX_RSS14_OUTSIDE_ODD_WIDEST[group]; int evenWidest = 9 - oddWidest; int vOdd = [ZXRSSUtils rssValue:self.oddCounts maxWidth:oddWidest noNarrow:NO]; int vEven = [ZXRSSUtils rssValue:self.evenCounts maxWidth:evenWidest noNarrow:YES]; int tEven = ZX_RSS14_OUTSIDE_EVEN_TOTAL_SUBSET[group]; int gSum = ZX_RSS14_OUTSIDE_GSUM[group]; return [[ZXRSSDataCharacter alloc] initWithValue:vOdd * tEven + vEven + gSum checksumPortion:checksumPortion]; } else { if ((evenSum & 0x01) != 0 || evenSum > 10 || evenSum < 4) { return nil; } int group = (10 - evenSum) / 2; int oddWidest = ZX_RSS14_INSIDE_ODD_WIDEST[group]; int evenWidest = 9 - oddWidest; int vOdd = [ZXRSSUtils rssValue:self.oddCounts maxWidth:oddWidest noNarrow:YES]; int vEven = [ZXRSSUtils rssValue:self.evenCounts maxWidth:evenWidest noNarrow:NO]; int tOdd = ZX_RSS14_INSIDE_ODD_TOTAL_SUBSET[group]; int gSum = ZX_RSS14_INSIDE_GSUM[group]; return [[ZXRSSDataCharacter alloc] initWithValue:vEven * tOdd + vOdd + gSum checksumPortion:checksumPortion]; } } - (ZXIntArray *)findFinderPattern:(ZXBitArray *)row rowOffset:(int)rowOffset rightFinderPattern:(BOOL)rightFinderPattern { ZXIntArray *counters = self.decodeFinderCounters; [counters clear]; int32_t *array = counters.array; int width = row.size; BOOL isWhite = NO; while (rowOffset < width) { isWhite = ![row get:rowOffset]; if (rightFinderPattern == isWhite) { break; } rowOffset++; } int counterPosition = 0; int patternStart = rowOffset; for (int x = rowOffset; x < width; x++) { if ([row get:x] ^ isWhite) { array[counterPosition]++; } else { if (counterPosition == 3) { if ([ZXAbstractRSSReader isFinderPattern:counters]) { return [[ZXIntArray alloc] initWithInts:patternStart, x, -1]; } patternStart += array[0] + array[1]; array[0] = array[2]; array[1] = array[3]; array[2] = 0; array[3] = 0; counterPosition--; } else { counterPosition++; } array[counterPosition] = 1; isWhite = !isWhite; } } return nil; } - (ZXRSSFinderPattern *)parseFoundFinderPattern:(ZXBitArray *)row rowNumber:(int)rowNumber right:(BOOL)right startEnd:(ZXIntArray *)startEnd { BOOL firstIsBlack = [row get:startEnd.array[0]]; int firstElementStart = startEnd.array[0] - 1; while (firstElementStart >= 0 && firstIsBlack ^ [row get:firstElementStart]) { firstElementStart--; } firstElementStart++; int firstCounter = startEnd.array[0] - firstElementStart; ZXIntArray *counters = self.decodeFinderCounters; int32_t *array = counters.array; for (int i = counters.length - 1; i > 0; i--) { array[i] = array[i-1]; } array[0] = firstCounter; int value = [ZXAbstractRSSReader parseFinderValue:counters finderPatternType:ZX_RSS_PATTERNS_RSS14_PATTERNS]; if (value == -1) { return nil; } int start = firstElementStart; int end = startEnd.array[1]; if (right) { start = [row size] - 1 - start; end = [row size] - 1 - end; } return [[ZXRSSFinderPattern alloc] initWithValue:value startEnd:[[ZXIntArray alloc] initWithInts:firstElementStart, startEnd.array[1], -1] start:start end:end rowNumber:rowNumber]; } - (BOOL)adjustOddEvenCounts:(BOOL)outsideChar numModules:(int)numModules { int oddSum = [ZXAbstractRSSReader count:self.oddCounts]; int evenSum = [ZXAbstractRSSReader count:self.evenCounts]; int mismatch = oddSum + evenSum - numModules; BOOL oddParityBad = (oddSum & 0x01) == (outsideChar ? 1 : 0); BOOL evenParityBad = (evenSum & 0x01) == 1; BOOL incrementOdd = NO; BOOL decrementOdd = NO; BOOL incrementEven = NO; BOOL decrementEven = NO; if (outsideChar) { if (oddSum > 12) { decrementOdd = YES; } else if (oddSum < 4) { incrementOdd = YES; } if (evenSum > 12) { decrementEven = YES; } else if (evenSum < 4) { incrementEven = YES; } } else { if (oddSum > 11) { decrementOdd = YES; } else if (oddSum < 5) { incrementOdd = YES; } if (evenSum > 10) { decrementEven = YES; } else if (evenSum < 4) { incrementEven = YES; } } if (mismatch == 1) { if (oddParityBad) { if (evenParityBad) { return NO; } decrementOdd = YES; } else { if (!evenParityBad) { return NO; } decrementEven = YES; } } else if (mismatch == -1) { if (oddParityBad) { if (evenParityBad) { return NO; } incrementOdd = YES; } else { if (!evenParityBad) { return NO; } incrementEven = YES; } } else if (mismatch == 0) { if (oddParityBad) { if (!evenParityBad) { return NO; } if (oddSum < evenSum) { incrementOdd = YES; decrementEven = YES; } else { decrementOdd = YES; incrementEven = YES; } } else { if (evenParityBad) { return NO; } } } else { return NO; } if (incrementOdd) { if (decrementOdd) { return NO; } [ZXAbstractRSSReader increment:self.oddCounts errors:self.oddRoundingErrors]; } if (decrementOdd) { [ZXAbstractRSSReader decrement:self.oddCounts errors:self.oddRoundingErrors]; } if (incrementEven) { if (decrementEven) { return NO; } [ZXAbstractRSSReader increment:self.evenCounts errors:self.oddRoundingErrors]; } if (decrementEven) { [ZXAbstractRSSReader decrement:self.evenCounts errors:self.evenRoundingErrors]; } return YES; } @end