diff --git a/c/qrcodegen.c b/c/qrcodegen.c index d0ac38b..987db5e 100644 --- a/c/qrcodegen.c +++ b/c/qrcodegen.c @@ -89,6 +89,8 @@ 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); +static int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version); +static int numCharCountBits(const struct qrcodegen_Segment *seg, int version); @@ -1037,3 +1039,133 @@ struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { result.data = buf; return result; } + + +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]) { + return qrcodegen_encodeSegmentsAdvanced(segs, len, ecl, + qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, -1, true, tempBuffer, qrcode); +} + + +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]) { + assert(segs != NULL || len == 0); + assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX); + assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = getTotalBits(segs, len, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + } + assert(dataUsedBits != -1); + + // Increase the error correction level while the data still fits in the current version number + for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) { + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8) + ecl = (enum qrcodegen_Ecc)i; + } + + // Create the data bit string by concatenating all segments + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; + memset(qrcode, 0, qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0])); + int bitLen = 0; + for (size_t i = 0; i < len; i++) { + const struct qrcodegen_Segment *seg = &segs[i]; + unsigned int modeBits; + switch (seg->mode) { + case qrcodegen_Mode_NUMERIC : modeBits = 0x1; break; + case qrcodegen_Mode_ALPHANUMERIC: modeBits = 0x2; break; + case qrcodegen_Mode_BYTE : modeBits = 0x4; break; + case qrcodegen_Mode_KANJI : modeBits = 0x8; break; + case qrcodegen_Mode_ECI : modeBits = 0x7; break; + default: assert(false); + } + appendBitsToBuffer(modeBits, 4, qrcode, &bitLen); + appendBitsToBuffer(seg->numChars, numCharCountBits(seg, version), qrcode, &bitLen); + for (int j = 0; j < seg->bitLength; j++) + appendBitsToBuffer((seg->data[j >> 3] >> (7 - (j & 7))) & 1, 1, qrcode, &bitLen); + } + + // Add terminator and pad up to a byte if applicable + int terminatorBits = dataCapacityBits - bitLen; + if (terminatorBits > 4) + terminatorBits = 4; + appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen); + appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen); + + // Pad with alternate bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + appendBitsToBuffer(padByte, 8, qrcode, &bitLen); + assert(bitLen % 8 == 0); + + // Draw function and data codeword modules + appendErrorCorrection(qrcode, version, ecl, tempBuffer); + initializeFunctionModules(version, qrcode); + drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode); + drawWhiteFunctionModules(qrcode, version); + initializeFunctionModules(version, tempBuffer); + + // Handle masking + if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + drawFormatBits(ecl, (enum qrcodegen_Mask)i, qrcode); + applyMask(tempBuffer, qrcode, (enum qrcodegen_Mask)i); + long penalty = getPenaltyScore(qrcode); + if (penalty < minPenalty) { + mask = (enum qrcodegen_Mask)i; + minPenalty = penalty; + } + applyMask(tempBuffer, qrcode, (enum qrcodegen_Mask)i); // Undoes the mask due to XOR + } + } + assert(0 <= (int)mask && (int)mask <= 7); + drawFormatBits(ecl, mask, qrcode); + applyMask(tempBuffer, qrcode, mask); + return true; +} + + +static int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version) { + assert(segs != NULL || len == 0); + assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int result = 0; + for (size_t i = 0; i < len; i++) { + int ccbits = numCharCountBits(&segs[i], version); + // Fail if segment length value doesn't fit in the length field's bit-width + if (segs[i].numChars >= (1L << ccbits)) + return -1; + long temp = 4L + ccbits + segs[i].bitLength; + if (temp > INT16_MAX - result) + return -1; + result += temp; + } + return result; +} + + +static int numCharCountBits(const struct qrcodegen_Segment *seg, int version) { + int i; + if ( 1 <= version && version <= 9) i = 0; + else if (10 <= version && version <= 26) i = 1; + else if (27 <= version && version <= 40) i = 2; + else assert(false); + + switch (seg->mode) { + case qrcodegen_Mode_NUMERIC : { const int temp[] = {10, 12, 14}; return temp[i]; } + case qrcodegen_Mode_ALPHANUMERIC: { const int temp[] = { 9, 11, 13}; return temp[i]; } + case qrcodegen_Mode_BYTE : { const int temp[] = { 8, 16, 16}; return temp[i]; } + case qrcodegen_Mode_KANJI : { const int temp[] = { 8, 10, 12}; return temp[i]; } + case qrcodegen_Mode_ECI : return 0; + default: assert(false); + } +} diff --git a/c/qrcodegen.h b/c/qrcodegen.h index f2f7977..1e9d6cd 100644 --- a/c/qrcodegen.h +++ b/c/qrcodegen.h @@ -206,6 +206,13 @@ struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t bu struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]); +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]); + /*---- Functions to extract raw data from QR Codes ----*/