/* * Copyright 2014 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 "ZXAztecHighLevelEncoder.h" #import "ZXAztecState.h" #import "ZXByteArray.h" NSArray *ZX_AZTEC_MODE_NAMES = nil; const int ZX_AZTEC_MODE_UPPER = 0; // 5 bits const int ZX_AZTEC_MODE_LOWER = 1; // 5 bits const int ZX_AZTEC_MODE_DIGIT = 2; // 4 bits const int ZX_AZTEC_MODE_MIXED = 3; // 5 bits const int ZX_AZTEC_MODE_PUNCT = 4; // 5 bits // The Latch Table shows, for each pair of Modes, the optimal method for // getting from one mode to another. In the worst possible case, this can // be up to 14 bits. In the best possible case, we are already there! // The high half-word of each entry gives the number of bits. // The low half-word of each entry are the actual bits necessary to change const int ZX_AZTEC_LATCH_TABLE[][5] = { { 0, (5 << 16) + 28, // UPPER -> LOWER (5 << 16) + 30, // UPPER -> DIGIT (5 << 16) + 29, // UPPER -> MIXED (10 << 16) + (29 << 5) + 30, // UPPER -> MIXED -> PUNCT }, { (9 << 16) + (30 << 4) + 14, // LOWER -> DIGIT -> UPPER 0, (5 << 16) + 30, // LOWER -> DIGIT (5 << 16) + 29, // LOWER -> MIXED (10 << 16) + (29 << 5) + 30, // LOWER -> MIXED -> PUNCT }, { (4 << 16) + 14, // DIGIT -> UPPER (9 << 16) + (14 << 5) + 28, // DIGIT -> UPPER -> LOWER 0, (9 << 16) + (14 << 5) + 29, // DIGIT -> UPPER -> MIXED (14 << 16) + (14 << 10) + (29 << 5) + 30, // DIGIT -> UPPER -> MIXED -> PUNCT }, { (5 << 16) + 29, // MIXED -> UPPER (5 << 16) + 28, // MIXED -> LOWER (10 << 16) + (29 << 5) + 30, // MIXED -> UPPER -> DIGIT 0, (5 << 16) + 30, // MIXED -> PUNCT }, { (5 << 16) + 31, // PUNCT -> UPPER (10 << 16) + (31 << 5) + 28, // PUNCT -> UPPER -> LOWER (10 << 16) + (31 << 5) + 30, // PUNCT -> UPPER -> DIGIT (10 << 16) + (31 << 5) + 29, // PUNCT -> UPPER -> MIXED 0, }, }; // A reverse mapping from [mode][char] to the encoding for that character // in that mode. An entry of 0 indicates no mapping exists. const int ZX_AZTEC_CHAR_MAP_HEIGHT = 5; const int ZX_AZTEC_CHAR_MAP_WIDTH = 256; static int ZX_AZTEC_CHAR_MAP[ZX_AZTEC_CHAR_MAP_HEIGHT][ZX_AZTEC_CHAR_MAP_WIDTH]; // A map showing the available shift codes. (The shifts to BINARY are not // shown int ZX_AZTEC_SHIFT_TABLE[ZX_AZTEC_SHIFT_TABLE_SIZE][ZX_AZTEC_SHIFT_TABLE_SIZE]; @interface ZXAztecHighLevelEncoder () @property (nonatomic, assign, readonly) ZXByteArray *text; @end @implementation ZXAztecHighLevelEncoder + (void)load { ZX_AZTEC_MODE_NAMES = @[@"UPPER", @"LOWER", @"DIGIT", @"MIXED", @"PUNCT"]; memset(ZX_AZTEC_CHAR_MAP, 0, ZX_AZTEC_CHAR_MAP_HEIGHT * ZX_AZTEC_CHAR_MAP_WIDTH * sizeof(int)); ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_UPPER][' '] = 1; for (int c = 'A'; c <= 'Z'; c++) { ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_UPPER][c] = c - 'A' + 2; } ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_LOWER][' '] = 1; for (int c = 'a'; c <= 'z'; c++) { ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_LOWER][c] = c - 'a' + 2; } ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_DIGIT][' '] = 1; for (int c = '0'; c <= '9'; c++) { ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_DIGIT][c] = c - '0' + 2; } ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_DIGIT][','] = 12; ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_DIGIT]['.'] = 13; const int mixedTable[] = { '\0', ' ', '\1', '\2', '\3', '\4', '\5', '\6', '\7', '\b', '\t', '\n', '\13', '\f', '\r', '\33', '\34', '\35', '\36', '\37', '@', '\\', '^', '_', '`', '|', '~', '\177' }; for (int i = 0; i < sizeof(mixedTable) / sizeof(int); i++) { ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_MIXED][mixedTable[i]] = i; } const int punctTable[] = { '\0', '\r', '\0', '\0', '\0', '\0', '!', '\'', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '[', ']', '{', '}' }; for (int i = 0; i < sizeof(punctTable) / sizeof(int); i++) { if (punctTable[i] > 0) { ZX_AZTEC_CHAR_MAP[ZX_AZTEC_MODE_PUNCT][punctTable[i]] = i; } } memset(ZX_AZTEC_SHIFT_TABLE, -1, ZX_AZTEC_SHIFT_TABLE_SIZE * ZX_AZTEC_SHIFT_TABLE_SIZE * sizeof(int)); ZX_AZTEC_SHIFT_TABLE[ZX_AZTEC_MODE_UPPER][ZX_AZTEC_MODE_PUNCT] = 0; ZX_AZTEC_SHIFT_TABLE[ZX_AZTEC_MODE_LOWER][ZX_AZTEC_MODE_PUNCT] = 0; ZX_AZTEC_SHIFT_TABLE[ZX_AZTEC_MODE_LOWER][ZX_AZTEC_MODE_UPPER] = 28; ZX_AZTEC_SHIFT_TABLE[ZX_AZTEC_MODE_MIXED][ZX_AZTEC_MODE_PUNCT] = 0; ZX_AZTEC_SHIFT_TABLE[ZX_AZTEC_MODE_DIGIT][ZX_AZTEC_MODE_PUNCT] = 0; ZX_AZTEC_SHIFT_TABLE[ZX_AZTEC_MODE_DIGIT][ZX_AZTEC_MODE_UPPER] = 15; } - (id)initWithText:(ZXByteArray *)text { if (self = [super init]) { _text = text; } return self; } - (ZXBitArray *)encode { NSArray *states = @[[ZXAztecState initialState]]; for (int index = 0; index < self.text.length; index++) { int pairCode; int nextChar = index + 1 < self.text.length ? self.text.array[index + 1] : 0; switch (self.text.array[index]) { case '\r': pairCode = nextChar == '\n' ? 2 : 0; break; case '.' : pairCode = nextChar == ' ' ? 3 : 0; break; case ',' : pairCode = nextChar == ' ' ? 4 : 0; break; case ':' : pairCode = nextChar == ' ' ? 5 : 0; break; default: pairCode = 0; } if (pairCode > 0) { // We have one of the four special PUNCT pairs. Treat them specially. // Get a new set of states for the two new characters. states = [self updateStateListForPair:states index:index pairCode:pairCode]; index++; } else { // Get a new set of states for the new character. states = [self updateStateListForChar:states index:index]; } } // We are left with a set of states. Find the shortest one. ZXAztecState *minState = [[states sortedArrayUsingComparator:^NSComparisonResult(ZXAztecState *a, ZXAztecState *b) { return a.bitCount - b.bitCount; }] firstObject]; // Convert it to a bit array, and return. return [minState toBitArray:self.text]; } // We update a set of states for a new character by updating each state // for the new character, merging the results, and then removing the // non-optimal states. - (NSArray *)updateStateListForChar:(NSArray *)states index:(int)index { NSMutableArray *result = [NSMutableArray array]; for (ZXAztecState *state in states) { [self updateStateForChar:state index:index result:result]; } return [self simplifyStates:result]; } // Return a set of states that represent the possible ways of updating this // state for the next character. The resulting set of states are added to // the "result" list. - (void)updateStateForChar:(ZXAztecState *)state index:(int)index result:(NSMutableArray *)result { unichar ch = (unichar) (self.text.array[index] & 0xFF); BOOL charInCurrentTable = ZX_AZTEC_CHAR_MAP[state.mode][ch] > 0; ZXAztecState *stateNoBinary = nil; for (int mode = 0; mode <= ZX_AZTEC_MODE_PUNCT; mode++) { int charInMode = ZX_AZTEC_CHAR_MAP[mode][ch]; if (charInMode > 0) { if (!stateNoBinary) { // Only create stateNoBinary the first time it's required. stateNoBinary = [state endBinaryShift:index]; } // Try generating the character by latching to its mode if (!charInCurrentTable || mode == state.mode || mode == ZX_AZTEC_MODE_DIGIT) { // If the character is in the current table, we don't want to latch to // any other mode except possibly digit (which uses only 4 bits). Any // other latch would be equally successful *after* this character, and // so wouldn't save any bits. ZXAztecState *latch_state = [stateNoBinary latchAndAppend:mode value:charInMode]; [result addObject:latch_state]; } // Try generating the character by switching to its mode. if (!charInCurrentTable && ZX_AZTEC_SHIFT_TABLE[state.mode][mode] >= 0) { // It never makes sense to temporarily shift to another mode if the // character exists in the current mode. That can never save bits. ZXAztecState *shift_state = [stateNoBinary shiftAndAppend:mode value:charInMode]; [result addObject:shift_state]; } } } if (state.binaryShiftByteCount > 0 || ZX_AZTEC_CHAR_MAP[state.mode][ch] == 0) { // It's never worthwhile to go into binary shift mode if you're not already // in binary shift mode, and the character exists in your current mode. // That can never save bits over just outputting the char in the current mode. ZXAztecState *binaryState = [state addBinaryShiftChar:index]; [result addObject:binaryState]; } } - (NSArray *)updateStateListForPair:(NSArray *)states index:(int)index pairCode:(int)pairCode { NSMutableArray *result = [NSMutableArray array]; for (ZXAztecState *state in states) { [self updateStateForPair:state index:index pairCode:pairCode result:result]; } return [self simplifyStates:result]; } - (void)updateStateForPair:(ZXAztecState *)state index:(int)index pairCode:(int)pairCode result:(NSMutableArray *)result { ZXAztecState *stateNoBinary = [state endBinaryShift:index]; // Possibility 1. Latch to ZX_AZTEC_MODE_PUNCT, and then append this code [result addObject:[stateNoBinary latchAndAppend:ZX_AZTEC_MODE_PUNCT value:pairCode]]; if (state.mode != ZX_AZTEC_MODE_PUNCT) { // Possibility 2. Shift to ZX_AZTEC_MODE_PUNCT, and then append this code. // Every state except ZX_AZTEC_MODE_PUNCT (handled above) can shift [result addObject:[stateNoBinary shiftAndAppend:ZX_AZTEC_MODE_PUNCT value:pairCode]]; } if (pairCode == 3 || pairCode == 4) { // both characters are in DIGITS. Sometimes better to just add two digits ZXAztecState *digit_state = [[stateNoBinary latchAndAppend:ZX_AZTEC_MODE_DIGIT value:16 - pairCode] // period or comma in DIGIT latchAndAppend:ZX_AZTEC_MODE_DIGIT value:1]; // space in DIGIT [result addObject:digit_state]; } if (state.binaryShiftByteCount > 0) { // It only makes sense to do the characters as binary if we're already // in binary mode. ZXAztecState *binaryState = [[state addBinaryShiftChar:index] addBinaryShiftChar:index + 1]; [result addObject:binaryState]; } } - (NSArray *)simplifyStates:(NSArray *)states { NSMutableArray *result = [NSMutableArray array]; for (ZXAztecState *newState in states) { BOOL add = YES; NSArray *resultCopy = [NSArray arrayWithArray:result]; for (ZXAztecState *oldState in resultCopy) { if ([oldState isBetterThanOrEqualTo:newState]) { add = NO; break; } if ([newState isBetterThanOrEqualTo:oldState]) { [result removeObject:oldState]; } } if (add) { [result addObject:newState]; } } return result; } @end