QR code improvements

This commit is contained in:
Zachary Levy
2026-04-21 15:35:55 -07:00
parent a4623a13b5
commit ba522fa051
3 changed files with 247 additions and 109 deletions

View File

@@ -117,7 +117,7 @@ NUM_ERROR_CORRECTION_BLOCKS := [4][41]i8{
// - The text cannot fit in any version within [min_version, max_version] at the given ECL.
// - The encoded segment data exceeds the buffer capacity.
@(require_results)
encode_text_explicit_temp :: proc(
encode_text_manual :: proc(
text: string,
temp_buffer, qrcode: []u8,
ecl: Ecc,
@@ -130,7 +130,7 @@ encode_text_explicit_temp :: proc(
) {
text_len := len(text)
if text_len == 0 {
return encode_segments_advanced_explicit_temp(
return encode_segments_advanced_manual(
nil,
ecl,
min_version,
@@ -162,7 +162,7 @@ encode_text_explicit_temp :: proc(
seg.data = temp_buffer[:text_len]
}
segs := [1]Segment{seg}
return encode_segments_advanced_explicit_temp(
return encode_segments_advanced_manual(
segs[:],
ecl,
min_version,
@@ -211,13 +211,9 @@ encode_text_auto :: proc(
return false
}
defer delete(temp_buffer, temp_allocator)
return encode_text_explicit_temp(text, temp_buffer, qrcode, ecl, min_version, max_version, mask, boost_ecl)
return encode_text_manual(text, temp_buffer, qrcode, ecl, min_version, max_version, mask, boost_ecl)
}
encode_text :: proc {
encode_text_explicit_temp,
encode_text_auto,
}
// Encodes arbitrary binary data to a QR Code using byte mode.
//
@@ -234,7 +230,7 @@ encode_text :: proc {
// Returns ok=false when:
// - The payload cannot fit in any version within [min_version, max_version] at the given ECL.
@(require_results)
encode_binary :: proc(
encode_binary_manual :: proc(
data_and_temp: []u8,
data_len: int,
qrcode: []u8,
@@ -256,7 +252,7 @@ encode_binary :: proc(
seg.num_chars = data_len
seg.data = data_and_temp[:data_len]
segs := [1]Segment{seg}
return encode_segments_advanced(
return encode_segments_advanced_manual(
segs[:],
ecl,
min_version,
@@ -268,6 +264,55 @@ encode_binary :: proc(
)
}
// Encodes arbitrary binary data to a QR Code using byte mode,
// automatically allocating and freeing the temp buffer.
//
// Parameters:
// bin_data - [in] Payload bytes (aliased by the internal segment; not modified).
// qrcode - [out] On success, contains the encoded QR Code. On failure, qrcode[0] is
// set to 0.
// temp_allocator - Allocator used for the internal scratch buffer. Freed before return.
//
// qrcode must have length >= buffer_len_for_version(max_version).
//
// Returns ok=false when:
// - The payload cannot fit in any version within [min_version, max_version] at the given ECL.
// - The temp_allocator fails to allocate.
@(require_results)
encode_binary_auto :: proc(
bin_data: []u8,
qrcode: []u8,
ecl: Ecc,
min_version: int = VERSION_MIN,
max_version: int = VERSION_MAX,
mask: Maybe(Mask) = nil,
boost_ecl: bool = true,
temp_allocator := context.temp_allocator,
) -> (
ok: bool,
) {
seg: Segment
seg.mode = .Byte
seg.bit_length = calc_segment_bit_length(.Byte, len(bin_data))
if seg.bit_length == LENGTH_OVERFLOW {
qrcode[0] = 0
return false
}
seg.num_chars = len(bin_data)
seg.data = bin_data
segs := [1]Segment{seg}
return encode_segments_advanced_auto(
segs[:],
ecl,
min_version,
max_version,
mask,
boost_ecl,
qrcode,
temp_allocator,
)
}
// Encodes the given segments to a QR Code using default parameters
// (VERSION_MIN..VERSION_MAX, auto mask, boost ECL).
//
@@ -282,17 +327,8 @@ encode_binary :: proc(
// Returns ok=false when:
// - The total segment data exceeds the capacity of version 40 at the given ECL.
@(require_results)
encode_segments_explicit_temp :: proc(segs: []Segment, ecl: Ecc, temp_buffer, qrcode: []u8) -> (ok: bool) {
return encode_segments_advanced_explicit_temp(
segs,
ecl,
VERSION_MIN,
VERSION_MAX,
nil,
true,
temp_buffer,
qrcode,
)
encode_segments_manual :: proc(segs: []Segment, ecl: Ecc, temp_buffer, qrcode: []u8) -> (ok: bool) {
return encode_segments_advanced_manual(segs, ecl, VERSION_MIN, VERSION_MAX, nil, true, temp_buffer, qrcode)
}
// Encodes segments to a QR Code using default parameters, automatically allocating the temp buffer.
@@ -328,13 +364,9 @@ encode_segments_auto :: proc(
return false
}
defer delete(temp_buffer, temp_allocator)
return encode_segments_explicit_temp(segs, ecl, temp_buffer, qrcode)
return encode_segments_manual(segs, ecl, temp_buffer, qrcode)
}
encode_segments :: proc {
encode_segments_explicit_temp,
encode_segments_auto,
}
// Encodes the given segments to a QR Code with full control over version range, mask, and ECL boosting.
//
@@ -353,7 +385,7 @@ encode_segments :: proc {
// - The total segment data exceeds the capacity of every version in [min_version, max_version]
// at the given ECL.
@(require_results)
encode_segments_advanced_explicit_temp :: proc(
encode_segments_advanced_manual :: proc(
segs: []Segment,
ecl: Ecc,
min_version, max_version: int,
@@ -490,7 +522,7 @@ encode_segments_advanced_auto :: proc(
return false
}
defer delete(temp_buffer, temp_allocator)
return encode_segments_advanced_explicit_temp(
return encode_segments_advanced_manual(
segs,
ecl,
min_version,
@@ -502,18 +534,17 @@ encode_segments_advanced_auto :: proc(
)
}
encode_segments_advanced :: proc {
encode_segments_advanced_explicit_temp,
encode_segments_advanced_auto,
encode_manual :: proc {
encode_text_manual,
encode_binary_manual,
encode_segments_manual,
encode_segments_advanced_manual,
}
encode :: proc {
encode_text_explicit_temp,
encode_auto :: proc {
encode_text_auto,
encode_binary,
encode_segments_explicit_temp,
encode_binary_auto,
encode_segments_auto,
encode_segments_advanced_explicit_temp,
encode_segments_advanced_auto,
}
@@ -981,7 +1012,7 @@ min_buffer_size :: proc {
min_buffer_size_segments,
}
// Text path: auto-selects numeric/alphanumeric/byte mode the same way encode_text does.
// Text path: auto-selects numeric/alphanumeric/byte mode the same way encode_text_manual does.
//
// Returns ok=false when:
// - The text exceeds QR Code capacity for every version in the range at the given ECL.
@@ -1162,7 +1193,6 @@ calc_segment_buffer_size :: proc(mode: Mode, num_chars: int) -> int {
return (temp + 7) / 8
}
@(private)
calc_segment_bit_length :: proc(mode: Mode, num_chars: int) -> int {
if num_chars < 0 || num_chars > 32767 {
return LENGTH_OVERFLOW
@@ -2487,7 +2517,7 @@ test_min_buffer_size_text :: proc(t: ^testing.T) {
testing.expect(t, planned > 0)
qrcode: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok := encode_text(text, temp[:], qrcode[:], Ecc.Low)
ok := encode_text_manual(text, temp[:], qrcode[:], Ecc.Low)
testing.expect(t, ok)
actual_version_size := get_size(qrcode[:])
actual_buf_len := buffer_len_for_version((actual_version_size - 17) / 4)
@@ -2538,7 +2568,7 @@ test_min_buffer_size_binary :: proc(t: ^testing.T) {
testing.expect(t, size > 0)
testing.expect(t, size <= buffer_len_for_version(2))
// Verify agreement with encode_binary
// Verify agreement with encode_binary_manual
{
data_len :: 100
planned, planned_ok := min_buffer_size(data_len, .Medium)
@@ -2549,7 +2579,7 @@ test_min_buffer_size_binary :: proc(t: ^testing.T) {
for i in 0 ..< data_len {
dat[i] = u8(i)
}
ok := encode_binary(dat[:], data_len, qrcode[:], .Medium)
ok := encode_binary_manual(dat[:], data_len, qrcode[:], .Medium)
testing.expect(t, ok)
actual_version_size := get_size(qrcode[:])
actual_buf_len := buffer_len_for_version((actual_version_size - 17) / 4)
@@ -2609,7 +2639,7 @@ test_min_buffer_size_segments :: proc(t: ^testing.T) {
// Verify against actual encode
qrcode: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok := encode_segments(segs[:], Ecc.Low, temp[:], qrcode[:])
ok := encode_segments_manual(segs[:], Ecc.Low, temp[:], qrcode[:])
testing.expect(t, ok)
actual_version_size := get_size(qrcode[:])
actual_buf_len := buffer_len_for_version((actual_version_size - 17) / 4)
@@ -2631,7 +2661,7 @@ test_encode_text_auto :: proc(t: ^testing.T) {
text :: "Hello, world!"
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_text_explicit_temp(text, temp[:], qr_explicit[:], .Low)
ok_explicit := encode_text_manual(text, temp[:], qr_explicit[:], .Low)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
@@ -2650,7 +2680,7 @@ test_encode_text_auto :: proc(t: ^testing.T) {
text :: "314159265358979323846264338327950288419716939937510"
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_text_explicit_temp(text, temp[:], qr_explicit[:], .Medium)
ok_explicit := encode_text_manual(text, temp[:], qr_explicit[:], .Medium)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
@@ -2669,7 +2699,7 @@ test_encode_text_auto :: proc(t: ^testing.T) {
text :: "HELLO WORLD"
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_text_explicit_temp(text, temp[:], qr_explicit[:], .Quartile)
ok_explicit := encode_text_manual(text, temp[:], qr_explicit[:], .Quartile)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
@@ -2695,7 +2725,7 @@ test_encode_text_auto :: proc(t: ^testing.T) {
text :: "https://www.nayuki.io/"
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_text_explicit_temp(text, temp[:], qr_explicit[:], .High, mask = .M3)
ok_explicit := encode_text_manual(text, temp[:], qr_explicit[:], .High, mask = .M3)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
@@ -2732,7 +2762,7 @@ test_encode_segments_auto :: proc(t: ^testing.T) {
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_segments_explicit_temp(segs[:], .Low, temp[:], qr_explicit[:])
ok_explicit := encode_segments_manual(segs[:], .Low, temp[:], qr_explicit[:])
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
@@ -2764,7 +2794,7 @@ test_encode_segments_advanced_auto :: proc(t: ^testing.T) {
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_segments_advanced_explicit_temp(
ok_explicit := encode_segments_advanced_manual(
segs[:],
.Medium,
VERSION_MIN,
@@ -2795,7 +2825,7 @@ test_encode_segments_advanced_auto :: proc(t: ^testing.T) {
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_segments_advanced_explicit_temp(
ok_explicit := encode_segments_advanced_manual(
segs[:],
.High,
1,