#include "AZDecoder.h"
#include "AZDetectorResult.h"
#include "BitArray.h"
#include "BitMatrix.h"
#include "DecoderResult.h"
#include "GenericGF.h"
#include "ReedSolomonDecoder.h"
#include "ZXTestSupport.h"
#include "ZXAlgorithms.h"
#include <cctype>
#include <cstring>
#include <numeric>
#include <string>
#include <utility>
#include <vector>
namespace ZXing::Aztec {
enum class Table
{
UPPER,
LOWER,
MIXED,
DIGIT,
PUNCT,
BINARY
};
static const char* 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 const char* 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 const char* 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 const char* PUNCT_TABLE[] = {
"FLGN", "\r", "\r\n", ". ", ", ", ": ", "!", "\"", "#", "$", "%", "&", "'", "(", ")",
"*", "+", ",", "-", ".", "/", ":", ";", "<", "=", ">", "?", "[", "]", "{", "}", "CTRL_UL"
};
static const char* DIGIT_TABLE[] = {
"CTRL_PS", " ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", ".", "CTRL_UL", "CTRL_US"
};
static int TotalBitsInLayer(int layers, bool compact)
{
return ((compact ? 88 : 112) + 16 * layers) * layers;
}
static BitArray ExtractBits(const DetectorResult& ddata)
{
bool compact = ddata.isCompact();
int layers = ddata.nbLayers();
int baseMatrixSize = (compact ? 11 : 14) + layers * 4; std::vector<int> map(baseMatrixSize, 0);
if (compact) {
std::iota(map.begin(), map.end(), 0);
} 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;
map[origCenter - i - 1] = center - newOffset - 1;
map[origCenter + i] = center + newOffset + 1;
}
}
auto& matrix = ddata.bits();
BitArray rawbits(TotalBitsInLayer(layers, compact));
for (int i = 0, rowOffset = 0; i < layers; i++) {
int rowSize = (layers - i) * 4 + (compact ? 9 : 12);
int low = i * 2;
int high = baseMatrixSize - 1 - low;
for (int j = 0; j < rowSize; j++) {
int colOffset = j * 2;
for (int k = 0; k < 2; k++) {
rawbits.set(rowOffset + 0 * rowSize + colOffset + k, matrix.get(map[low + k], map[low + j]));
rawbits.set(rowOffset + 2 * rowSize + colOffset + k, matrix.get(map[low + j], map[high - k]));
rawbits.set(rowOffset + 4 * rowSize + colOffset + k, matrix.get(map[high - k], map[high - j]));
rawbits.set(rowOffset + 6 * rowSize + colOffset + k, matrix.get(map[high - j], map[low + k]));
}
}
rowOffset += rowSize * 8;
}
return rawbits;
}
static std::pair<BitArray, int> CorrectBits(const DetectorResult& ddata, const BitArray& rawbits)
{
const GenericGF* gf = nullptr;
int codewordSize;
if (ddata.nbLayers() <= 2) {
codewordSize = 6;
gf = &GenericGF::AztecData6();
} else if (ddata.nbLayers() <= 8) {
codewordSize = 8;
gf = &GenericGF::AztecData8();
} else if (ddata.nbLayers() <= 22) {
codewordSize = 10;
gf = &GenericGF::AztecData10();
} else {
codewordSize = 12;
gf = &GenericGF::AztecData12();
}
int numCodewords = Size(rawbits) / codewordSize;
int numDataCodewords = ddata.nbDatablocks();
int numECCodewords = numCodewords - numDataCodewords;
if (numCodewords < numDataCodewords)
throw FormatError("Invalid number of code words");
auto dataWords = ToInts<int>(rawbits, codewordSize, numCodewords, Size(rawbits) % codewordSize);
if (!ReedSolomonDecode(*gf, dataWords, numECCodewords))
throw ChecksumError();
dataWords.resize(numDataCodewords);
BitArray correctedBits;
for (int dataWord : dataWords) {
if (dataWord == 0 || dataWord == (1 << codewordSize) - 1)
return {};
else if (dataWord == 1) correctedBits.appendBits(0, codewordSize - 1);
else if (dataWord == (1 << codewordSize) - 2)
correctedBits.appendBits(0xffffffff, codewordSize - 1);
else
correctedBits.appendBits(dataWord, codewordSize);
}
return {std::move(correctedBits), numECCodewords * 100 / numCodewords};
}
static Table GetTable(char t)
{
switch (t) {
case 'L': return Table::LOWER;
case 'P': return Table::PUNCT;
case 'M': return Table::MIXED;
case 'D': return Table::DIGIT;
case 'B': return Table::BINARY;
case 'U':
default: return Table::UPPER;
}
}
static const char* GetCharacter(Table table, int code)
{
switch (table) {
case Table::UPPER: return UPPER_TABLE[code];
case Table::LOWER: return LOWER_TABLE[code];
case Table::MIXED: return MIXED_TABLE[code];
case Table::PUNCT: return PUNCT_TABLE[code];
case Table::DIGIT: return DIGIT_TABLE[code];
case Table::BINARY: return nullptr; }
return nullptr;
}
static ECI ParseECIValue(BitArrayView& bits, const int flg)
{
int eci = 0;
for (int i = 0; i < flg; i++)
eci = 10 * eci + bits.readBits(4) - 2;
return ECI(eci);
}
static StructuredAppendInfo ParseStructuredAppend(Content& res)
{
std::string text(res.bytes.begin(), res.bytes.end());
StructuredAppendInfo sai;
std::string::size_type i = 0;
if (text[0] == ' ') { std::string::size_type sp = text.find(' ', 1);
if (sp == std::string::npos)
return {};
sai.id = text.substr(1, sp - 1); i = sp + 1;
}
if (i + 1 >= text.size() || !std::isupper(text[i]) || !std::isupper(text[i + 1]))
return {};
sai.index = text[i] - 'A';
sai.count = text[i + 1] - 'A' + 1;
if (sai.count == 1 || sai.count <= sai.index) sai.count = 0;
res.erase(0, narrow_cast<int>(i + 2));
return sai;
}
static void DecodeContent(const BitArray& bits, Content& res, bool &haveFNC1)
{
Table latchTable = Table::UPPER; Table shiftTable = Table::UPPER;
auto remBits = BitArrayView(bits);
haveFNC1 = false;
while (remBits.size() >= (shiftTable == Table::DIGIT ? 4 : 5)) { if (shiftTable == Table::BINARY) {
if (remBits.size() <= 6) break;
int length = remBits.readBits(5);
if (length == 0)
length = remBits.readBits(11) + 31;
for (int i = 0; i < length; i++)
res.push_back(remBits.readBits(8));
shiftTable = latchTable;
} else {
int size = shiftTable == Table::DIGIT ? 4 : 5;
int code = remBits.readBits(size);
const char* str = GetCharacter(shiftTable, code);
if (std::strncmp(str, "CTRL_", 5) == 0) {
latchTable = shiftTable; shiftTable = GetTable(str[5]);
if (str[6] == 'L')
latchTable = shiftTable;
} else if (std::strcmp(str, "FLGN") == 0) {
int flg = remBits.readBits(3);
if (flg == 0) { haveFNC1 = true;
res.push_back(29); } else if (flg <= 6) {
res.switchEncoding(ParseECIValue(remBits, flg));
} else {
}
shiftTable = latchTable;
} else {
res.append(str);
shiftTable = latchTable;
}
}
}
}
ZXING_EXPORT_TEST_ONLY
DecoderResult Decode(const BitArray& bits)
{
Content res;
res.symbology = {'z', '0', 3};
bool haveFNC1;
try {
DecodeContent(bits, res, haveFNC1);
} catch (const std::exception&) { return FormatError();
}
if (res.bytes.empty())
return FormatError("Empty symbol content");
bool haveStructuredAppend = Size(bits) > 20 && ToInt(bits, 0, 5) == 29 && ToInt(bits, 5, 5) == 29;
StructuredAppendInfo sai = haveStructuredAppend ? ParseStructuredAppend(res) : StructuredAppendInfo();
if (haveFNC1) {
if (res.bytes[0] == 29) {
res.symbology.modifier = '1'; res.symbology.aiFlag = AIFlag::GS1;
res.erase(0, 1); } else if (res.bytes.size() > 2 && std::isupper(res.bytes[0]) && res.bytes[1] == 29) {
res.symbology.modifier = '2'; res.symbology.aiFlag = AIFlag::AIM;
res.erase(1, 1); } else if (res.bytes.size() > 3 && std::isdigit(res.bytes[0]) && std::isdigit(res.bytes[1]) && res.bytes[2] == 29) {
res.symbology.modifier = '2'; res.symbology.aiFlag = AIFlag::AIM;
res.erase(2, 1); }
}
if (sai.index != -1)
res.symbology.modifier += 6;
return DecoderResult(std::move(res)).setStructuredAppend(sai);
}
DecoderResult DecodeRune(const DetectorResult& detectorResult) {
Content res;
res.symbology = {'z', 'C', 0};
auto runeString = ToString(detectorResult.runeValue(), 3);
res.append(runeString);
return DecoderResult(std::move(res));
}
DecoderResult Decode(const DetectorResult& detectorResult)
{
try {
if (detectorResult.nbLayers() == 0) {
return DecodeRune(detectorResult);
}
auto [bits, ecLevel] = CorrectBits(detectorResult, ExtractBits(detectorResult));
return Decode(bits).setEcLevel(std::to_string(ecLevel) + "%");
} catch (Error e) {
return e;
}
}
}