Files
ANSLibs/QRCode/aztec/AZDecoder.cpp

376 lines
12 KiB
C++
Raw Normal View History

/*
* Copyright 2016 Nu-book Inc.
* Copyright 2016 ZXing authors
* Copyright 2022 Axel Waggershauser
*/
// SPDX-License-Identifier: Apache-2.0
#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;
}
/**
* Gets the array of bits from an Aztec Code matrix
*
* @return the array of bits
*/
static BitArray ExtractBits(const DetectorResult& ddata)
{
bool compact = ddata.isCompact();
int layers = ddata.nbLayers();
int baseMatrixSize = (compact ? 11 : 14) + layers * 4; // not including alignment lines
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);
// 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 colOffset = j * 2;
for (int k = 0; k < 2; k++) {
// left column
rawbits.set(rowOffset + 0 * rowSize + colOffset + k, matrix.get(map[low + k], map[low + j]));
// bottom row
rawbits.set(rowOffset + 2 * rowSize + colOffset + k, matrix.get(map[low + j], map[high - k]));
// right column
rawbits.set(rowOffset + 4 * rowSize + colOffset + k, matrix.get(map[high - k], map[high - j]));
// top row
rawbits.set(rowOffset + 6 * rowSize + colOffset + k, matrix.get(map[high - j], map[low + k]));
}
}
rowOffset += rowSize * 8;
}
return rawbits;
}
/**
* @brief Performs RS error correction on an array of bits.
*/
static BitArray 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();
// drop the ECCodewords from the dataWords array
dataWords.resize(numDataCodewords);
// Now perform the unstuffing operation.
BitArray correctedBits;
// correctedBits.reserve(numDataCodewords * codewordSize - stuffedBits);
for (int dataWord : dataWords) {
if (dataWord == 0 || dataWord == (1 << codewordSize) - 1)
return {};
else if (dataWord == 1) // next codewordSize-1 bits are all zeros or all ones
correctedBits.appendBits(0, codewordSize - 1);
else if (dataWord == (1 << codewordSize) - 2)
correctedBits.appendBits(0xffffffff, codewordSize - 1);
else
correctedBits.appendBits(dataWord, codewordSize);
}
return correctedBits;
}
/**
* gets the table corresponding to the char passed
*/
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;
}
}
/**
* 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
*/
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; // should not happen
}
// silence gcc warning/error (this code can not be reached)
return nullptr;
}
/**
* See ISO/IEC 24778:2008 Section 10.1
*/
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);
}
/**
* See ISO/IEC 24778:2008 Section 8
*/
static StructuredAppendInfo ParseStructuredAppend(ByteArray& bytes)
{
std::string text(bytes.begin(), bytes.end());
StructuredAppendInfo sai;
std::string::size_type i = 0;
if (text[0] == ' ') { // Space-delimited id
std::string::size_type sp = text.find(' ', 1);
if (sp == std::string::npos)
return {};
sai.id = text.substr(1, sp - 1); // Strip space delimiters
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) // If info doesn't make sense
sai.count = 0; // Choose to mark count as unknown
text.erase(0, i + 2); // Remove
bytes = ByteArray(text);
return sai;
}
static void DecodeContent(const BitArray& bits, Content& res)
{
Table latchTable = Table::UPPER; // table most recently latched to
Table shiftTable = Table::UPPER; // table to use for the next read
auto remBits = BitArrayView(bits);
while (remBits.size() >= (shiftTable == Table::DIGIT ? 4 : 5)) { // see ISO/IEC 24778:2008 7.3.1.2 regarding padding bits
if (shiftTable == Table::BINARY) {
if (remBits.size() <= 6) // padding bits
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));
// Go back to whatever mode we had been in
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) {
// Table changes
// ISO/IEC 24778:2008 prescibes ending a shift sequence in the mode from which it was invoked.
// That's including when that mode is a shift.
// Our test case dlusbs.png for issue #642 exercises that.
latchTable = shiftTable; // Latch the current mode, so as to return to Upper after U/S B/S
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) { // FNC1
res.push_back(29); // May be removed at end if first/second FNC1
} else if (flg <= 6) {
// FLG(1) to FLG(6) ECI
res.switchEncoding(ParseECIValue(remBits, flg));
} else {
// FLG(7) is invalid
}
shiftTable = latchTable;
} else {
res.append(str);
// Go back to whatever mode we had been in
shiftTable = latchTable;
}
}
}
}
ZXING_EXPORT_TEST_ONLY
DecoderResult Decode(const BitArray& bits)
{
Content res;
res.symbology = {'z', '0', 3};
try {
DecodeContent(bits, res);
} catch (const std::exception&) { // see BitArrayView::readBits
return FormatError();
}
if (res.bytes.empty())
return FormatError("Empty symbol content");
// Check for Structured Append - need 4 5-bit words, beginning with ML UL, ending with index and count
bool haveStructuredAppend = Size(bits) > 20 && ToInt(bits, 0, 5) == 29 // latch to MIXED (from UPPER)
&& ToInt(bits, 5, 5) == 29; // latch back to UPPER (from MIXED)
StructuredAppendInfo sai = haveStructuredAppend ? ParseStructuredAppend(res.bytes) : StructuredAppendInfo();
// As converting character set ECIs ourselves and ignoring/skipping non-character ECIs, not using
// modifiers that indicate ECI protocol (ISO/IEC 24778:2008 Annex F Table F.1)
if (res.bytes.size() > 1 && res.bytes[0] == 29) {
res.symbology.modifier = '1'; // GS1
res.symbology.aiFlag = AIFlag::GS1;
res.erase(0, 1); // Remove FNC1
} else if (res.bytes.size() > 2 && std::isupper(res.bytes[0]) && res.bytes[1] == 29) {
// FNC1 following single uppercase letter (the AIM Application Indicator)
res.symbology.modifier = '2'; // AIM
res.symbology.aiFlag = AIFlag::AIM;
res.erase(1, 1); // Remove FNC1,
// The AIM Application Indicator character "A"-"Z" is left in the stream (ISO/IEC 24778:2008 16.2)
} else if (res.bytes.size() > 3 && std::isdigit(res.bytes[0]) && std::isdigit(res.bytes[1]) && res.bytes[2] == 29) {
// FNC1 following 2 digits (the AIM Application Indicator)
res.symbology.modifier = '2'; // AIM
res.symbology.aiFlag = AIFlag::AIM;
res.erase(2, 1); // Remove FNC1
// The AIM Application Indicator characters "00"-"99" are left in the stream (ISO/IEC 24778:2008 16.2)
}
if (sai.index != -1)
res.symbology.modifier += 6; // TODO: this is wrong as long as we remove the sai info from the content in ParseStructuredAppend
return DecoderResult(std::move(res)).setStructuredAppend(sai);
}
DecoderResult DecodeRune(const DetectorResult& detectorResult) {
Content res;
res.symbology = {'z', 'C', 0}; // Runes cannot have ECI
// Bizarrely, this is what it says to do in the spec
auto runeString = ToString(detectorResult.runeValue(), 3);
res.append(runeString);
return DecoderResult(std::move(res));
}
DecoderResult Decode(const DetectorResult& detectorResult)
{
try {
if (detectorResult.nbLayers() == 0) {
// This is a rune - just return the rune value
return DecodeRune(detectorResult);
}
auto bits = CorrectBits(detectorResult, ExtractBits(detectorResult));
return Decode(bits);
} catch (Error e) {
return e;
}
}
} // namespace ZXing::Aztec