blob: 821fbc4808306f6b0fad1f85fd3570dfd0f78a4e [file] [log] [blame]
/*
* Copyright (C) 2010 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.
*/
/*
* These authors would like to acknowledge the Spanish Ministry of Industry,
* Tourism and Trade, for the support in the project TSI020301-2008-2
* "PIRAmIDE: Personalizable Interactions with Resources on AmI-enabled
* Mobile Dynamic Environments", led by Treelogic
* ( http://www.treelogic.com/ ):
*
* http://www.piramidepse.com/
*/
package com.google.zxing.oned.rss.expanded.decoders;
import com.google.zxing.NotFoundException;
import com.google.zxing.common.BitArray;
/**
* @author Pablo Orduña, University of Deusto (pablo.orduna@deusto.es)
* @author Eduardo Castillejo, University of Deusto (eduardo.castillejo@deusto.es)
*/
final class GeneralAppIdDecoder {
private final BitArray information;
private final CurrentParsingState current = new CurrentParsingState();
private final StringBuffer buffer = new StringBuffer();
GeneralAppIdDecoder(BitArray information){
this.information = information;
}
String decodeAllCodes(StringBuffer buff, int initialPosition) throws NotFoundException {
int currentPosition = initialPosition;
String remaining = null;
do{
DecodedInformation info = this.decodeGeneralPurposeField(currentPosition, remaining);
String parsedFields = FieldParser.parseFieldsInGeneralPurpose(info.getNewString());
buff.append(parsedFields);
if(info.isRemaining()) {
remaining = String.valueOf(info.getRemainingValue());
} else {
remaining = null;
}
if(currentPosition == info.getNewPosition()) {// No step forward!
break;
}
currentPosition = info.getNewPosition();
}while(true);
return buff.toString();
}
private boolean isStillNumeric(int pos) {
// It's numeric if it still has 7 positions
// and one of the first 4 bits is "1".
if(pos + 7 > this.information.size){
return pos + 4 <= this.information.size;
}
for(int i = pos; i < pos + 3; ++i) {
if (this.information.get(i)) {
return true;
}
}
return this.information.get(pos + 3);
}
private DecodedNumeric decodeNumeric(int pos) {
if(pos + 7 > this.information.size){
int numeric = extractNumericValueFromBitArray(pos, 4);
if(numeric == 0) {
return new DecodedNumeric(this.information.size, DecodedNumeric.FNC1, DecodedNumeric.FNC1);
}
return new DecodedNumeric(this.information.size, numeric - 1, DecodedNumeric.FNC1);
}
int numeric = extractNumericValueFromBitArray(pos, 7);
int digit1 = (numeric - 8) / 11;
int digit2 = (numeric - 8) % 11;
return new DecodedNumeric(pos + 7, digit1, digit2);
}
int extractNumericValueFromBitArray(int pos, int bits){
return extractNumericValueFromBitArray(this.information, pos, bits);
}
static int extractNumericValueFromBitArray(BitArray information, int pos, int bits) {
if(bits > 32) {
throw new IllegalArgumentException("extractNumberValueFromBitArray can't handle more than 32 bits");
}
int value = 0;
for(int i = 0; i < bits; ++i) {
if (information.get(pos + i)) {
value |= (1 << (bits - i - 1));
}
}
return value;
}
DecodedInformation decodeGeneralPurposeField(int pos, String remaining) {
this.buffer.setLength(0);
if(remaining != null) {
this.buffer.append(remaining);
}
this.current.position = pos;
DecodedInformation lastDecoded = parseBlocks();
if(lastDecoded != null && lastDecoded.isRemaining()) {
return new DecodedInformation(this.current.position, this.buffer.toString(), lastDecoded.getRemainingValue());
}
return new DecodedInformation(this.current.position, this.buffer.toString());
}
private DecodedInformation parseBlocks() {
boolean isFinished;
BlockParsedResult result;
do{
int initialPosition = current.position;
if (current.isAlpha()){
result = parseAlphaBlock();
isFinished = result.isFinished();
}else if (current.isIsoIec646()){
result = parseIsoIec646Block();
isFinished = result.isFinished();
}else{ // it must be numeric
result = parseNumericBlock();
isFinished = result.isFinished();
}
boolean positionChanged = initialPosition != current.position;
if(!positionChanged && !isFinished) {
break;
}
} while (!isFinished);
return result.getDecodedInformation();
}
private BlockParsedResult parseNumericBlock() {
while(isStillNumeric(current.position)){
DecodedNumeric numeric = decodeNumeric(current.position);
current.position = numeric.getNewPosition();
if(numeric.isFirstDigitFNC1()){
DecodedInformation information;
if (numeric.isSecondDigitFNC1()) {
information = new DecodedInformation(current.position, buffer.toString());
} else {
information = new DecodedInformation(current.position, buffer.toString(), numeric.getSecondDigit());
}
return new BlockParsedResult(information, true);
}
buffer.append(numeric.getFirstDigit());
if(numeric.isSecondDigitFNC1()){
DecodedInformation information = new DecodedInformation(current.position, buffer.toString());
return new BlockParsedResult(information, true);
}
buffer.append(numeric.getSecondDigit());
}
if(isNumericToAlphaNumericLatch(current.position)){
current.setAlpha();
current.position += 4;
}
return new BlockParsedResult(false);
}
private BlockParsedResult parseIsoIec646Block() {
while (isStillIsoIec646(current.position)) {
DecodedChar iso = decodeIsoIec646(current.position);
current.position = iso.getNewPosition();
if (iso.isFNC1()) {
DecodedInformation information = new DecodedInformation(current.position, buffer.toString());
return new BlockParsedResult(information, true);
}
buffer.append(iso.getValue());
}
if (isAlphaOr646ToNumericLatch(current.position)) {
current.position += 3;
current.setNumeric();
} else if (isAlphaTo646ToAlphaLatch(current.position)) {
if (current.position + 5 < this.information.size) {
current.position += 5;
} else {
current.position = this.information.size;
}
current.setAlpha();
}
return new BlockParsedResult(false);
}
private BlockParsedResult parseAlphaBlock() {
while (isStillAlpha(current.position)) {
DecodedChar alpha = decodeAlphanumeric(current.position);
current.position = alpha.getNewPosition();
if(alpha.isFNC1()) {
DecodedInformation information = new DecodedInformation(current.position, buffer.toString());
return new BlockParsedResult(information, true); //end of the char block
}
buffer.append(alpha.getValue());
}
if (isAlphaOr646ToNumericLatch(current.position)) {
current.position += 3;
current.setNumeric();
} else if (isAlphaTo646ToAlphaLatch(current.position)) {
if (current.position + 5 < this.information.size) {
current.position += 5;
} else {
current.position = this.information.size;
}
current.setIsoIec646();
}
return new BlockParsedResult(false);
}
private boolean isStillIsoIec646(int pos) {
if(pos + 5 > this.information.size) {
return false;
}
int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
if(fiveBitValue >= 5 && fiveBitValue < 16) {
return true;
}
if(pos + 7 > this.information.size) {
return false;
}
int sevenBitValue = extractNumericValueFromBitArray(pos, 7);
if(sevenBitValue >= 64 && sevenBitValue < 116) {
return true;
}
if(pos + 8 > this.information.size) {
return false;
}
int eightBitValue = extractNumericValueFromBitArray(pos, 8);
return eightBitValue >= 232 && eightBitValue < 253;
}
private DecodedChar decodeIsoIec646(int pos) {
int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
if(fiveBitValue == 15) {
return new DecodedChar(pos + 5, DecodedChar.FNC1);
}
if(fiveBitValue >= 5 && fiveBitValue < 15) {
return new DecodedChar(pos + 5, (char) ('0' + fiveBitValue - 5));
}
int sevenBitValue = extractNumericValueFromBitArray(pos, 7);
if(sevenBitValue >= 64 && sevenBitValue < 90) {
return new DecodedChar(pos + 7, (char) (sevenBitValue + 1));
}
if(sevenBitValue >= 90 && sevenBitValue < 116) {
return new DecodedChar(pos + 7, (char) (sevenBitValue + 7));
}
int eightBitValue = extractNumericValueFromBitArray(pos, 8);
switch (eightBitValue){
case 232: return new DecodedChar(pos + 8, '!');
case 233: return new DecodedChar(pos + 8, '"');
case 234: return new DecodedChar(pos + 8, '%');
case 235: return new DecodedChar(pos + 8, '&');
case 236: return new DecodedChar(pos + 8, '\'');
case 237: return new DecodedChar(pos + 8, '(');
case 238: return new DecodedChar(pos + 8, ')');
case 239: return new DecodedChar(pos + 8, '*');
case 240: return new DecodedChar(pos + 8, '+');
case 241: return new DecodedChar(pos + 8, ',');
case 242: return new DecodedChar(pos + 8, '-');
case 243: return new DecodedChar(pos + 8, '.');
case 244: return new DecodedChar(pos + 8, '/');
case 245: return new DecodedChar(pos + 8, ':');
case 246: return new DecodedChar(pos + 8, ';');
case 247: return new DecodedChar(pos + 8, '<');
case 248: return new DecodedChar(pos + 8, '=');
case 249: return new DecodedChar(pos + 8, '>');
case 250: return new DecodedChar(pos + 8, '?');
case 251: return new DecodedChar(pos + 8, '_');
case 252: return new DecodedChar(pos + 8, ' ');
}
throw new RuntimeException("Decoding invalid ISO/IEC 646 value: " + eightBitValue);
}
private boolean isStillAlpha(int pos) {
if(pos + 5 > this.information.size) {
return false;
}
// We now check if it's a valid 5-bit value (0..9 and FNC1)
int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
if(fiveBitValue >= 5 && fiveBitValue < 16) {
return true;
}
if(pos + 6 > this.information.size) {
return false;
}
int sixBitValue = extractNumericValueFromBitArray(pos, 6);
return sixBitValue >= 16 && sixBitValue < 63; // 63 not included
}
private DecodedChar decodeAlphanumeric(int pos) {
int fiveBitValue = extractNumericValueFromBitArray(pos, 5);
if(fiveBitValue == 15) {
return new DecodedChar(pos + 5, DecodedChar.FNC1);
}
if(fiveBitValue >= 5 && fiveBitValue < 15) {
return new DecodedChar(pos + 5, (char) ('0' + fiveBitValue - 5));
}
int sixBitValue = extractNumericValueFromBitArray(pos, 6);
if(sixBitValue >= 32 && sixBitValue < 58) {
return new DecodedChar(pos + 6, (char) (sixBitValue + 33));
}
switch(sixBitValue){
case 58: return new DecodedChar(pos + 6, '*');
case 59: return new DecodedChar(pos + 6, ',');
case 60: return new DecodedChar(pos + 6, '-');
case 61: return new DecodedChar(pos + 6, '.');
case 62: return new DecodedChar(pos + 6, '/');
}
throw new RuntimeException("Decoding invalid alphanumeric value: " + sixBitValue);
}
private boolean isAlphaTo646ToAlphaLatch(int pos) {
if(pos + 1 > this.information.size) {
return false;
}
for(int i = 0; i < 5 && i + pos < this.information.size; ++i){
if(i == 2){
if(!this.information.get(pos + 2)) {
return false;
}
} else if(this.information.get(pos + i)) {
return false;
}
}
return true;
}
private boolean isAlphaOr646ToNumericLatch(int pos) {
// Next is alphanumeric if there are 3 positions and they are all zeros
if (pos + 3 > this.information.size) {
return false;
}
for (int i = pos; i < pos + 3; ++i) {
if (this.information.get(i)) {
return false;
}
}
return true;
}
private boolean isNumericToAlphaNumericLatch(int pos) {
// Next is alphanumeric if there are 4 positions and they are all zeros, or
// if there is a subset of this just before the end of the symbol
if (pos + 1 > this.information.size) {
return false;
}
for (int i = 0; i < 4 && i + pos < this.information.size; ++i) {
if (this.information.get(pos + i)) {
return false;
}
}
return true;
}
}