Added C functions to calculate segment buffer size and bit length, added test cases.
This commit is contained in:
parent
4f823c3039
commit
08108ee6d8
|
@ -64,6 +64,7 @@ int getAlignmentPatternPositions(int version, uint8_t result[7]);
|
|||
bool getModule(const uint8_t qrcode[], int x, int y);
|
||||
void setModule(uint8_t qrcode[], int x, int y, bool isBlack);
|
||||
void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack);
|
||||
int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars);
|
||||
|
||||
|
||||
/*---- Test cases ----*/
|
||||
|
@ -562,6 +563,257 @@ static void testGetSetModuleRandomly(void) {
|
|||
}
|
||||
|
||||
|
||||
static void testCalcSegmentBufferSize(void) {
|
||||
{
|
||||
const size_t cases[][2] = {
|
||||
{0, 0},
|
||||
{1, 1},
|
||||
{2, 1},
|
||||
{3, 2},
|
||||
{4, 2},
|
||||
{5, 3},
|
||||
{6, 3},
|
||||
{1472, 614},
|
||||
{2097, 874},
|
||||
{5326, 2220},
|
||||
{9828, 4095},
|
||||
{9829, 4096},
|
||||
{9830, 4096},
|
||||
{9831, SIZE_MAX},
|
||||
{9832, SIZE_MAX},
|
||||
{12000, SIZE_MAX},
|
||||
{28453, SIZE_MAX},
|
||||
{55555, SIZE_MAX},
|
||||
{SIZE_MAX / 6, SIZE_MAX},
|
||||
{SIZE_MAX / 4, SIZE_MAX},
|
||||
{SIZE_MAX / 2, SIZE_MAX},
|
||||
{SIZE_MAX / 1, SIZE_MAX},
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) {
|
||||
assert(qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, cases[i][0]) == cases[i][1]);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
{
|
||||
const size_t cases[][2] = {
|
||||
{0, 0},
|
||||
{1, 1},
|
||||
{2, 2},
|
||||
{3, 3},
|
||||
{4, 3},
|
||||
{5, 4},
|
||||
{6, 5},
|
||||
{1472, 1012},
|
||||
{2097, 1442},
|
||||
{5326, 3662},
|
||||
{5955, 4095},
|
||||
{5956, 4095},
|
||||
{5957, 4096},
|
||||
{5958, SIZE_MAX},
|
||||
{5959, SIZE_MAX},
|
||||
{12000, SIZE_MAX},
|
||||
{28453, SIZE_MAX},
|
||||
{55555, SIZE_MAX},
|
||||
{SIZE_MAX / 10, SIZE_MAX},
|
||||
{SIZE_MAX / 8, SIZE_MAX},
|
||||
{SIZE_MAX / 5, SIZE_MAX},
|
||||
{SIZE_MAX / 2, SIZE_MAX},
|
||||
{SIZE_MAX / 1, SIZE_MAX},
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) {
|
||||
assert(qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ALPHANUMERIC, cases[i][0]) == cases[i][1]);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
{
|
||||
const size_t cases[][2] = {
|
||||
{0, 0},
|
||||
{1, 1},
|
||||
{2, 2},
|
||||
{3, 3},
|
||||
{1472, 1472},
|
||||
{2097, 2097},
|
||||
{4094, 4094},
|
||||
{4095, 4095},
|
||||
{4096, SIZE_MAX},
|
||||
{4097, SIZE_MAX},
|
||||
{5957, SIZE_MAX},
|
||||
{12000, SIZE_MAX},
|
||||
{28453, SIZE_MAX},
|
||||
{55555, SIZE_MAX},
|
||||
{SIZE_MAX / 16 + 1, SIZE_MAX},
|
||||
{SIZE_MAX / 14, SIZE_MAX},
|
||||
{SIZE_MAX / 9, SIZE_MAX},
|
||||
{SIZE_MAX / 7, SIZE_MAX},
|
||||
{SIZE_MAX / 4, SIZE_MAX},
|
||||
{SIZE_MAX / 3, SIZE_MAX},
|
||||
{SIZE_MAX / 2, SIZE_MAX},
|
||||
{SIZE_MAX / 1, SIZE_MAX},
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) {
|
||||
assert(qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_BYTE, cases[i][0]) == cases[i][1]);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
{
|
||||
const size_t cases[][2] = {
|
||||
{0, 0},
|
||||
{1, 2},
|
||||
{2, 4},
|
||||
{3, 5},
|
||||
{1472, 2392},
|
||||
{2097, 3408},
|
||||
{2519, 4094},
|
||||
{2520, 4095},
|
||||
{2521, SIZE_MAX},
|
||||
{5957, SIZE_MAX},
|
||||
{2522, SIZE_MAX},
|
||||
{12000, SIZE_MAX},
|
||||
{28453, SIZE_MAX},
|
||||
{55555, SIZE_MAX},
|
||||
{SIZE_MAX / 13 + 1, SIZE_MAX},
|
||||
{SIZE_MAX / 12, SIZE_MAX},
|
||||
{SIZE_MAX / 9, SIZE_MAX},
|
||||
{SIZE_MAX / 4, SIZE_MAX},
|
||||
{SIZE_MAX / 3, SIZE_MAX},
|
||||
{SIZE_MAX / 2, SIZE_MAX},
|
||||
{SIZE_MAX / 1, SIZE_MAX},
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) {
|
||||
assert(qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_KANJI, cases[i][0]) == cases[i][1]);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
{
|
||||
assert(qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ECI, 0) == 3);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void testCalcSegmentBitLength(void) {
|
||||
{
|
||||
const int cases[][2] = {
|
||||
{0, 0},
|
||||
{1, 4},
|
||||
{2, 7},
|
||||
{3, 10},
|
||||
{4, 14},
|
||||
{5, 17},
|
||||
{6, 20},
|
||||
{1472, 4907},
|
||||
{2097, 6990},
|
||||
{5326, 17754},
|
||||
{9828, 32760},
|
||||
{9829, 32764},
|
||||
{9830, 32767},
|
||||
{9831, -1},
|
||||
{9832, -1},
|
||||
{12000, -1},
|
||||
{28453, -1},
|
||||
{INT_MAX / 3, -1},
|
||||
{INT_MAX / 2, -1},
|
||||
{INT_MAX / 1, -1},
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) {
|
||||
assert(calcSegmentBitLength(qrcodegen_Mode_NUMERIC, cases[i][0]) == cases[i][1]);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
{
|
||||
const int cases[][2] = {
|
||||
{0, 0},
|
||||
{1, 6},
|
||||
{2, 11},
|
||||
{3, 17},
|
||||
{4, 22},
|
||||
{5, 28},
|
||||
{6, 33},
|
||||
{1472, 8096},
|
||||
{2097, 11534},
|
||||
{5326, 29293},
|
||||
{5955, 32753},
|
||||
{5956, 32758},
|
||||
{5957, 32764},
|
||||
{5958, -1},
|
||||
{5959, -1},
|
||||
{12000, -1},
|
||||
{28453, -1},
|
||||
{INT_MAX / 5, -1},
|
||||
{INT_MAX / 4, -1},
|
||||
{INT_MAX / 3, -1},
|
||||
{INT_MAX / 2, -1},
|
||||
{INT_MAX / 1, -1},
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) {
|
||||
assert(calcSegmentBitLength(qrcodegen_Mode_ALPHANUMERIC, cases[i][0]) == cases[i][1]);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
{
|
||||
const int cases[][2] = {
|
||||
{0, 0},
|
||||
{1, 8},
|
||||
{2, 16},
|
||||
{3, 24},
|
||||
{1472, 11776},
|
||||
{2097, 16776},
|
||||
{4094, 32752},
|
||||
{4095, 32760},
|
||||
{4096, -1},
|
||||
{4097, -1},
|
||||
{5957, -1},
|
||||
{12000, -1},
|
||||
{28453, -1},
|
||||
{INT_MAX / 8 + 1, -1},
|
||||
{INT_MAX / 7, -1},
|
||||
{INT_MAX / 6, -1},
|
||||
{INT_MAX / 5, -1},
|
||||
{INT_MAX / 4, -1},
|
||||
{INT_MAX / 3, -1},
|
||||
{INT_MAX / 2, -1},
|
||||
{INT_MAX / 1, -1},
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) {
|
||||
assert(calcSegmentBitLength(qrcodegen_Mode_BYTE, cases[i][0]) == cases[i][1]);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
{
|
||||
const int cases[][2] = {
|
||||
{0, 0},
|
||||
{1, 13},
|
||||
{2, 26},
|
||||
{3, 39},
|
||||
{1472, 19136},
|
||||
{2097, 27261},
|
||||
{2519, 32747},
|
||||
{2520, 32760},
|
||||
{2521, -1},
|
||||
{5957, -1},
|
||||
{2522, -1},
|
||||
{12000, -1},
|
||||
{28453, -1},
|
||||
{INT_MAX / 13 + 1, -1},
|
||||
{INT_MAX / 12, -1},
|
||||
{INT_MAX / 9, -1},
|
||||
{INT_MAX / 4, -1},
|
||||
{INT_MAX / 3, -1},
|
||||
{INT_MAX / 2, -1},
|
||||
{INT_MAX / 1, -1},
|
||||
};
|
||||
for (size_t i = 0; i < ARRAY_LENGTH(cases); i++) {
|
||||
assert(calcSegmentBitLength(qrcodegen_Mode_KANJI, cases[i][0]) == cases[i][1]);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
{
|
||||
assert(calcSegmentBitLength(qrcodegen_Mode_ECI, 0) == 24);
|
||||
numTestCases++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*---- Main runner ----*/
|
||||
|
||||
int main(void) {
|
||||
|
@ -578,6 +830,8 @@ int main(void) {
|
|||
testGetAlignmentPatternPositions();
|
||||
testGetSetModule();
|
||||
testGetSetModuleRandomly();
|
||||
testCalcSegmentBufferSize();
|
||||
testCalcSegmentBitLength();
|
||||
printf("All %d test cases passed\n", numTestCases);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
|
|
@ -88,6 +88,8 @@ testable bool getModule(const uint8_t qrcode[], int x, int y);
|
|||
testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack);
|
||||
testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack);
|
||||
|
||||
testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars);
|
||||
|
||||
|
||||
|
||||
/*---- Private tables of constants ----*/
|
||||
|
@ -849,3 +851,65 @@ testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack) {
|
|||
if (0 <= x && x < qrsize && 0 <= y && y < qrsize)
|
||||
setModule(qrcode, x, y, isBlack);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*---- Segment handling ----*/
|
||||
|
||||
size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars) {
|
||||
int temp = calcSegmentBitLength(mode, numChars);
|
||||
if (temp == -1)
|
||||
return SIZE_MAX;
|
||||
assert(0 <= temp && temp <= INT16_MAX);
|
||||
return ((size_t)temp + 7) / 8;
|
||||
}
|
||||
|
||||
|
||||
// Returns the number of data bits needed to represent a segment
|
||||
// containing the given number of characters using the given mode. Notes:
|
||||
// - Returns -1 on failure, i.e. numChars > INT16_MAX or
|
||||
// the number of needed bits exceeds INT16_MAX (i.e. 32767).
|
||||
// - Otherwise, all valid results are in the range [0, INT16_MAX].
|
||||
// - For byte mode, numChars measures the number of bytes, not Unicode code points.
|
||||
// - For ECI mode, numChars must be 0, and the worst-case number of bits is returned.
|
||||
// An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
|
||||
testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) {
|
||||
const int LIMIT = INT16_MAX; // Can be configured as high as INT_MAX
|
||||
if (numChars > (unsigned int)LIMIT)
|
||||
return -1;
|
||||
int n = (int)numChars;
|
||||
|
||||
int result = -2;
|
||||
if (mode == qrcodegen_Mode_NUMERIC) {
|
||||
// n * 3 + ceil(n / 3)
|
||||
if (n > LIMIT / 3)
|
||||
goto overflow;
|
||||
result = n * 3;
|
||||
int temp = n / 3 + (n % 3 == 0 ? 0 : 1);
|
||||
if (temp > LIMIT - result)
|
||||
goto overflow;
|
||||
result += temp;
|
||||
} else if (mode == qrcodegen_Mode_ALPHANUMERIC) {
|
||||
// n * 5 + ceil(n / 2)
|
||||
if (n > LIMIT / 5)
|
||||
goto overflow;
|
||||
result = n * 5;
|
||||
int temp = n / 2 + n % 2;
|
||||
if (temp > LIMIT - result)
|
||||
goto overflow;
|
||||
result += temp;
|
||||
} else if (mode == qrcodegen_Mode_BYTE) {
|
||||
if (n > LIMIT / 8)
|
||||
goto overflow;
|
||||
result = n * 8;
|
||||
} else if (mode == qrcodegen_Mode_KANJI) {
|
||||
if (n > LIMIT / 13)
|
||||
goto overflow;
|
||||
result = n * 13;
|
||||
} else if (mode == qrcodegen_Mode_ECI && numChars == 0)
|
||||
result = 3 * 8;
|
||||
assert(0 <= result && result <= LIMIT);
|
||||
return result;
|
||||
overflow:
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -153,6 +153,20 @@ bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcod
|
|||
enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl);
|
||||
|
||||
|
||||
/*
|
||||
* Returns the number of bytes (uint8_t) needed for the data buffer of a segment
|
||||
* containing the given number of characters using the given mode. Notes:
|
||||
* - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or
|
||||
* the number of needed bits exceeds INT16_MAX (i.e. 32767).
|
||||
* - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096.
|
||||
* - It is okay for the user to allocate more bytes for the buffer than needed.
|
||||
* - For byte mode, numChars measures the number of bytes, not Unicode code points.
|
||||
* - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned.
|
||||
* An actual ECI segment can have shorter data. For non-ECI modes, the result is exact.
|
||||
*/
|
||||
size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars);
|
||||
|
||||
|
||||
|
||||
/*---- Functions to extract raw data from QR Codes ----*/
|
||||
|
||||
|
|
Loading…
Reference in New Issue