2026-02-25 09:12:31 +00:00
|
|
|
#include "QrUtils.h"
|
|
|
|
|
|
2026-03-12 07:05:46 +11:00
|
|
|
#include <Utf8.h>
|
2026-02-25 09:12:31 +00:00
|
|
|
#include <qrcode.h>
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <memory>
|
|
|
|
|
|
|
|
|
|
#include "Logging.h"
|
|
|
|
|
|
|
|
|
|
void QrUtils::drawQrCode(const GfxRenderer& renderer, const Rect& bounds, const std::string& textPayload) {
|
|
|
|
|
// Dynamically calculate the QR code version based on text length
|
|
|
|
|
// Version 4 holds ~114 bytes, Version 10 ~395, Version 20 ~1066, up to 40
|
|
|
|
|
// qrcode.h max version is 40.
|
|
|
|
|
// Formula: approx version = size / 26 + 1 (very rough estimate, better to find best fit)
|
2026-03-12 07:05:46 +11:00
|
|
|
size_t len = textPayload.length();
|
|
|
|
|
|
|
|
|
|
// Truncate to max QR capacity at a UTF-8 safe boundary to avoid splitting multi-byte sequences
|
|
|
|
|
static constexpr size_t MAX_QR_CAPACITY = 2953; // Version 40, ECC_LOW, byte mode
|
|
|
|
|
std::string truncated;
|
|
|
|
|
const char* payload = textPayload.c_str();
|
|
|
|
|
if (len > MAX_QR_CAPACITY) {
|
|
|
|
|
len = utf8SafeTruncateBuffer(textPayload.c_str(), static_cast<int>(MAX_QR_CAPACITY));
|
|
|
|
|
truncated = textPayload.substr(0, len);
|
|
|
|
|
payload = truncated.c_str();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 09:12:31 +00:00
|
|
|
int version = 4;
|
|
|
|
|
if (len > 114) version = 10;
|
|
|
|
|
if (len > 395) version = 20;
|
|
|
|
|
if (len > 1066) version = 30;
|
|
|
|
|
if (len > 2110) version = 40;
|
|
|
|
|
|
|
|
|
|
// Make sure we have a large enough buffer on the heap to avoid blowing the stack
|
|
|
|
|
uint32_t bufferSize = qrcode_getBufferSize(version);
|
|
|
|
|
auto qrcodeBytes = std::make_unique<uint8_t[]>(bufferSize);
|
|
|
|
|
|
|
|
|
|
QRCode qrcode;
|
|
|
|
|
// Initialize the QR code. We use ECC_LOW for max capacity.
|
2026-03-12 07:05:46 +11:00
|
|
|
int8_t res = qrcode_initText(&qrcode, qrcodeBytes.get(), version, ECC_LOW, payload);
|
2026-02-25 09:12:31 +00:00
|
|
|
|
|
|
|
|
if (res == 0) {
|
|
|
|
|
// Determine the optimal pixel size.
|
|
|
|
|
const int maxDim = std::min(bounds.width, bounds.height);
|
|
|
|
|
|
|
|
|
|
int px = maxDim / qrcode.size;
|
|
|
|
|
if (px < 1) px = 1;
|
|
|
|
|
|
|
|
|
|
// Calculate centering X and Y
|
|
|
|
|
const int qrDisplaySize = qrcode.size * px;
|
|
|
|
|
const int xOff = bounds.x + (bounds.width - qrDisplaySize) / 2;
|
|
|
|
|
const int yOff = bounds.y + (bounds.height - qrDisplaySize) / 2;
|
|
|
|
|
|
|
|
|
|
// Draw the QR Code
|
|
|
|
|
for (uint8_t cy = 0; cy < qrcode.size; cy++) {
|
|
|
|
|
for (uint8_t cx = 0; cx < qrcode.size; cx++) {
|
|
|
|
|
if (qrcode_getModule(&qrcode, cx, cy)) {
|
|
|
|
|
renderer.fillRect(xOff + px * cx, yOff + px * cy, px, px, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// If it fails (e.g. text too large), log an error
|
|
|
|
|
LOG_ERR("QR", "Text too large for QR Code version %d", version);
|
|
|
|
|
}
|
|
|
|
|
}
|