Files
levlib/qrcode/generate.odin
2026-04-20 19:00:34 -07:00

2833 lines
82 KiB
Odin

package qrcode
import "core:slice"
// -------------------------------------------------------------------------------------------------
// Types
// -------------------------------------------------------------------------------------------------
// The error correction level in a QR Code symbol.
Ecc :: enum {
Low, // The QR Code can tolerate about 7% erroneous codewords
Medium, // The QR Code can tolerate about 15% erroneous codewords
Quartile, // The QR Code can tolerate about 25% erroneous codewords
High, // The QR Code can tolerate about 30% erroneous codewords
}
// The mask pattern used in a QR Code symbol.
Mask :: enum u8 {
M0,
M1,
M2,
M3,
M4,
M5,
M6,
M7,
}
// Describes how a segment's data bits are interpreted.
Mode :: enum {
Numeric = 0x1,
Alphanumeric = 0x2,
Byte = 0x4,
Kanji = 0x8,
Eci = 0x7,
}
// A segment of character/binary/control data in a QR Code symbol.
Segment :: struct {
mode: Mode,
num_chars: int,
data: []u8, // Slice into caller-provided buffer. Not owned.
bit_length: int,
}
// -------------------------------------------------------------------------------------------------
// Constants
// -------------------------------------------------------------------------------------------------
VERSION_MIN :: 1
VERSION_MAX :: 40
// The worst-case number of bytes needed to store one QR Code, up to and including version 40.
BUFFER_LEN_MAX :: 3918 // buffer_len_for_version(VERSION_MAX)
// Returns the number of bytes needed to store any QR Code up to and including the given version.
buffer_len_for_version :: #force_inline proc(n: int) -> int {
size := n * 4 + 17
return (size * size + 7) / 8 + 1
}
// -------------------------------------------------------------------------------------------------
// Private constants
// -------------------------------------------------------------------------------------------------
@(private)
LENGTH_OVERFLOW :: -1
@(private)
REED_SOLOMON_DEGREE_MAX :: 30
@(private)
PENALTY_N1 :: 3
@(private)
PENALTY_N2 :: 3
@(private)
PENALTY_N3 :: 40
@(private)
PENALTY_N4 :: 10
//odinfmt: disable
// For generating error correction codes. Index 0 is padding (set to illegal value).
@(private)
ECC_CODEWORDS_PER_BLOCK := [4][41]i8{
{-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low
{-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium
{-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile
{-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High
}
@(private)
NUM_ERROR_CORRECTION_BLOCKS := [4][41]i8{
{-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low
{-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium
{-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile
{-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High
}
//odinfmt: enable
// -------------------------------------------------------------------------------------------------
// Encode procedures
// -------------------------------------------------------------------------------------------------
// Encodes the given text string to a QR Code, automatically selecting
// numeric, alphanumeric, or byte mode based on content.
//
// Parameters:
// text - [in] UTF-8 encoded input string.
// temp_buffer - [scratch] Temporary workspace, clobbered during encoding. Contents are
// undefined after return. Must not alias qrcode.
// qrcode - [out] On success, contains the encoded QR Code (byte 0 = side length,
// remaining bytes = module bits). On failure, qrcode[0] is set to 0.
//
// Both temp_buffer and qrcode must have length >= buffer_len_for_version(max_version).
//
// Returns ok=false when:
// - 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(
text: string,
temp_buffer, qrcode: []u8,
ecl: Ecc,
min_version: int = VERSION_MIN,
max_version: int = VERSION_MAX,
mask: Maybe(Mask) = nil,
boost_ecl: bool = true,
) -> (
ok: bool,
) {
text_len := len(text)
if text_len == 0 {
return encode_segments_advanced_explicit_temp(
nil,
ecl,
min_version,
max_version,
mask,
boost_ecl,
temp_buffer,
qrcode,
)
}
buf_len := buffer_len_for_version(max_version)
seg: Segment
encode: {
if is_numeric(text) {
if calc_segment_buffer_size(.Numeric, text_len) > buf_len do break encode
seg = make_numeric(text, temp_buffer)
} else if is_alphanumeric(text) {
if calc_segment_buffer_size(.Alphanumeric, text_len) > buf_len do break encode
seg = make_alphanumeric(text, temp_buffer)
} else {
seg.bit_length = calc_segment_bit_length(.Byte, text_len)
if seg.bit_length == LENGTH_OVERFLOW || text_len > buf_len do break encode
for i in 0 ..< text_len {
temp_buffer[i] = text[i]
}
seg.mode = .Byte
seg.num_chars = text_len
seg.data = temp_buffer[:text_len]
}
segs := [1]Segment{seg}
return encode_segments_advanced_explicit_temp(
segs[:],
ecl,
min_version,
max_version,
mask,
boost_ecl,
temp_buffer,
qrcode,
)
}
qrcode[0] = 0
return false
}
// Encodes text to a QR Code, automatically allocating and freeing the temp buffer.
//
// Parameters:
// 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.
//
// Returns ok=false when:
// - The text cannot fit in any version within [min_version, max_version] at the given ECL.
// - The temp_allocator fails to allocate.
@(require_results)
encode_text_auto :: proc(
text: string,
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,
) {
size, size_ok := min_buffer_size(text, ecl, min_version, max_version)
if !size_ok {
qrcode[0] = 0
return false
}
temp_buffer, alloc_err := make([]u8, size, temp_allocator)
if alloc_err != nil {
qrcode[0] = 0
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)
}
encode_text :: proc {
encode_text_explicit_temp,
encode_text_auto,
}
// Encodes arbitrary binary data to a QR Code using byte mode.
//
// Parameters:
// data_and_temp - [in,scratch] The first data_len bytes are the input payload. The entire
// slice is used as temporary workspace during encoding and its
// contents are undefined after return. Must not alias qrcode.
// data_len - Number of input bytes in data_and_temp.
// qrcode - [out] On success, contains the encoded QR Code. On failure,
// qrcode[0] is set to 0.
//
// Both data_and_temp and 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.
@(require_results)
encode_binary :: proc(
data_and_temp: []u8,
data_len: int,
qrcode: []u8,
ecl: Ecc,
min_version: int = VERSION_MIN,
max_version: int = VERSION_MAX,
mask: Maybe(Mask) = nil,
boost_ecl: bool = true,
) -> (
ok: bool,
) {
seg: Segment
seg.mode = .Byte
seg.bit_length = calc_segment_bit_length(.Byte, data_len)
if seg.bit_length == LENGTH_OVERFLOW {
qrcode[0] = 0
return false
}
seg.num_chars = data_len
seg.data = data_and_temp[:data_len]
segs := [1]Segment{seg}
return encode_segments_advanced(
segs[:],
ecl,
min_version,
max_version,
mask,
boost_ecl,
data_and_temp,
qrcode,
)
}
// Encodes the given segments to a QR Code using default parameters
// (VERSION_MIN..VERSION_MAX, auto mask, boost ECL).
//
// Parameters:
// segs - [in] Slice of segments to encode. Segment data buffers may alias temp_buffer.
// temp_buffer - [scratch] Temporary workspace, clobbered during encoding. Must not alias qrcode.
// qrcode - [out] On success, contains the encoded QR Code. On failure, qrcode[0] is
// set to 0.
//
// Both temp_buffer and qrcode must have length >= BUFFER_LEN_MAX.
//
// 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,
)
}
// Encodes segments to a QR Code using default parameters, automatically allocating the temp buffer.
//
// Parameters:
// segs - [in] Slice of segments to encode.
// 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_MAX.
//
// Returns ok=false when:
// - The total segment data exceeds the capacity of version 40 at the given ECL.
// - The temp_allocator fails to allocate.
@(require_results)
encode_segments_auto :: proc(
segs: []Segment,
ecl: Ecc,
qrcode: []u8,
temp_allocator := context.temp_allocator,
) -> (
ok: bool,
) {
size, size_ok := min_buffer_size(segs, ecl)
if !size_ok {
qrcode[0] = 0
return false
}
temp_buffer, alloc_err := make([]u8, size, temp_allocator)
if alloc_err != nil {
qrcode[0] = 0
return false
}
defer delete(temp_buffer, temp_allocator)
return encode_segments_explicit_temp(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.
//
// Parameters:
// segs - [in] Slice of segments to encode. Segment data buffers may alias temp_buffer.
// temp_buffer - [scratch] Temporary workspace, clobbered during encoding. Must not alias qrcode.
// qrcode - [out] On success, contains the encoded QR Code. On failure, qrcode[0] is
// set to 0.
// mask - nil for automatic mask selection, or a specific Mask value.
// boost_ecl - If true, the ECL may be raised above ecl when the data fits without
// increasing the version.
//
// Both temp_buffer and qrcode must have length >= buffer_len_for_version(max_version).
//
// Returns ok=false when:
// - 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(
segs: []Segment,
ecl: Ecc,
min_version, max_version: int,
mask: Maybe(Mask),
boost_ecl: bool,
temp_buffer, qrcode: []u8,
) -> (
ok: bool,
) {
assert(
VERSION_MIN <= min_version && min_version <= max_version && max_version <= VERSION_MAX,
"version range out of bounds",
)
ecl := ecl
// Find the minimal version number to use
version, data_used_bits: int
for version = min_version;; version += 1 {
data_capacity_bits := get_num_data_codewords(version, ecl) * 8
data_used_bits = get_total_bits(segs, version)
if data_used_bits != LENGTH_OVERFLOW && data_used_bits <= data_capacity_bits {
break
}
if version >= max_version {
qrcode[0] = 0
return false
}
}
assert(data_used_bits != LENGTH_OVERFLOW, "data bits overflowed")
// Increase the error correction level while the data still fits
if boost_ecl {
for e in Ecc.Medium ..= Ecc.High {
if data_used_bits <= get_num_data_codewords(version, e) * 8 {
ecl = e
}
}
}
// Concatenate all segments to create the data bit string
buf_len := buffer_len_for_version(version)
slice.zero(qrcode[:buf_len])
bit_len := 0
for &seg in segs {
append_bits_to_buffer(uint(seg.mode), 4, qrcode, &bit_len)
append_bits_to_buffer(uint(seg.num_chars), num_char_count_bits(seg.mode, version), qrcode, &bit_len)
for j in 0 ..< seg.bit_length {
bit := uint((seg.data[j >> 3] >> uint(7 - (j & 7))) & 1)
append_bits_to_buffer(bit, 1, qrcode, &bit_len)
}
}
assert(bit_len == data_used_bits, "bit length mismatch after segment concatenation")
// Add terminator and pad up to a byte if applicable
data_capacity_bits := get_num_data_codewords(version, ecl) * 8
assert(bit_len <= data_capacity_bits, "data exceeds capacity")
terminator_bits := min(data_capacity_bits - bit_len, 4)
append_bits_to_buffer(0, terminator_bits, qrcode, &bit_len)
append_bits_to_buffer(0, (8 - bit_len % 8) % 8, qrcode, &bit_len)
assert(bit_len % 8 == 0, "bit length not byte-aligned after padding")
// Pad with alternating bytes until data capacity is reached
pad_byte: u8 = 0xEC
for bit_len < data_capacity_bits {
append_bits_to_buffer(uint(pad_byte), 8, qrcode, &bit_len)
pad_byte ~= 0xEC ~ 0x11
}
// Compute ECC, draw modules
add_ecc_and_interleave(qrcode, version, ecl, temp_buffer)
initialize_function_modules(version, qrcode)
draw_codewords(temp_buffer, get_num_raw_data_modules(version) / 8, qrcode)
draw_light_function_modules(qrcode, version)
initialize_function_modules(version, temp_buffer)
// Do masking
chosen_mask: Mask
if m, m_ok := mask.?; m_ok {
chosen_mask = m
} else {
// Automatically choose best mask
min_penalty := max(int)
for msk in Mask {
apply_mask(temp_buffer, qrcode, msk)
draw_format_bits(ecl, msk, qrcode)
penalty := get_penalty_score(qrcode)
if penalty < min_penalty {
chosen_mask = msk
min_penalty = penalty
}
apply_mask(temp_buffer, qrcode, msk) // Undo mask via XOR
}
}
apply_mask(temp_buffer, qrcode, chosen_mask)
draw_format_bits(ecl, chosen_mask, qrcode)
return true
}
// Encodes segments with full control over parameters, automatically allocating the temp buffer.
//
// Parameters:
// segs - [in] Slice of segments to encode.
// 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 total segment data exceeds the capacity of every version in [min_version, max_version]
// at the given ECL.
// - The temp_allocator fails to allocate.
@(require_results)
encode_segments_advanced_auto :: proc(
segs: []Segment,
ecl: Ecc,
min_version, max_version: int,
mask: Maybe(Mask),
boost_ecl: bool,
qrcode: []u8,
temp_allocator := context.temp_allocator,
) -> (
ok: bool,
) {
size, size_ok := min_buffer_size(segs, ecl, min_version, max_version)
if !size_ok {
qrcode[0] = 0
return false
}
temp_buffer, alloc_err := make([]u8, size, temp_allocator)
if alloc_err != nil {
qrcode[0] = 0
return false
}
defer delete(temp_buffer, temp_allocator)
return encode_segments_advanced_explicit_temp(
segs,
ecl,
min_version,
max_version,
mask,
boost_ecl,
temp_buffer,
qrcode,
)
}
encode_segments_advanced :: proc {
encode_segments_advanced_explicit_temp,
encode_segments_advanced_auto,
}
encode :: proc {
encode_text_explicit_temp,
encode_text_auto,
encode_binary,
encode_segments_explicit_temp,
encode_segments_auto,
encode_segments_advanced_explicit_temp,
encode_segments_advanced_auto,
}
// -------------------------------------------------------------------------------------------------
// Error correction code generation
// -------------------------------------------------------------------------------------------------
// Appends error correction bytes to each block of data, then interleaves bytes from all blocks.
@(private)
add_ecc_and_interleave :: proc(data: []u8, version: int, ecl: Ecc, result: []u8) {
assert(VERSION_MIN <= version && version <= VERSION_MAX, "version out of bounds")
num_blocks := int(NUM_ERROR_CORRECTION_BLOCKS[int(ecl)][version])
block_ecc_len := int(ECC_CODEWORDS_PER_BLOCK[int(ecl)][version])
raw_codewords := get_num_raw_data_modules(version) / 8
data_len := get_num_data_codewords(version, ecl)
num_short_blocks := num_blocks - raw_codewords % num_blocks
short_block_data_len := raw_codewords / num_blocks - block_ecc_len
rsdiv: [REED_SOLOMON_DEGREE_MAX]u8
reed_solomon_compute_divisor(block_ecc_len, rsdiv[:block_ecc_len])
dat_offset := 0
for i in 0 ..< num_blocks {
dat_len := short_block_data_len + (0 if i < num_short_blocks else 1)
ecc := data[data_len:] // Temporary storage
reed_solomon_compute_remainder(
data[dat_offset:dat_offset + dat_len],
rsdiv[:block_ecc_len],
ecc[:block_ecc_len],
)
k := i
for j in 0 ..< dat_len {
if j == short_block_data_len {
k -= num_short_blocks
}
result[k] = data[dat_offset + j]
k += num_blocks
}
k = data_len + i
for j in 0 ..< block_ecc_len {
result[k] = ecc[j]
k += num_blocks
}
dat_offset += dat_len
}
}
// Returns the number of 8-bit data codewords (not ECC) for the given version and ECC level.
@(private)
get_num_data_codewords :: proc(version: int, ecl: Ecc) -> int {
e := int(ecl)
return(
get_num_raw_data_modules(version) / 8 -
int(ECC_CODEWORDS_PER_BLOCK[e][version]) * int(NUM_ERROR_CORRECTION_BLOCKS[e][version]) \
)
}
// Returns the number of data bits that can be stored in a QR Code of the given version number.
@(private)
get_num_raw_data_modules :: proc(ver: int) -> int {
assert(VERSION_MIN <= ver && ver <= VERSION_MAX, "version out of bounds")
result := (16 * ver + 128) * ver + 64
if ver >= 2 {
num_align := ver / 7 + 2
result -= (25 * num_align - 10) * num_align - 55
if ver >= 7 {
result -= 36
}
}
assert(208 <= result && result <= 29648, "raw data modules out of expected range")
return result
}
// -------------------------------------------------------------------------------------------------
// Reed-Solomon ECC generator
// -------------------------------------------------------------------------------------------------
@(private)
reed_solomon_compute_divisor :: proc(degree: int, result: []u8) {
assert(1 <= degree && degree <= REED_SOLOMON_DEGREE_MAX, "reed-solomon degree out of range")
slice.zero(result[:degree])
result[degree - 1] = 1
root: u8 = 1
for _ in 0 ..< degree {
for j in 0 ..< degree {
result[j] = reed_solomon_multiply(result[j], root)
if j + 1 < degree {
result[j] ~= result[j + 1]
}
}
root = reed_solomon_multiply(root, 0x02)
}
}
@(private)
reed_solomon_compute_remainder :: proc(data: []u8, generator: []u8, result: []u8) {
degree := len(generator)
assert(1 <= degree && degree <= REED_SOLOMON_DEGREE_MAX, "reed-solomon degree out of range")
slice.zero(result[:degree])
for i in 0 ..< len(data) {
factor := data[i] ~ result[0]
for j in 0 ..< degree - 1 {
result[j] = result[j + 1]
}
result[degree - 1] = 0
for j in 0 ..< degree {
result[j] ~= reed_solomon_multiply(generator[j], factor)
}
}
}
// Returns the product of two field elements modulo GF(2^8/0x11D).
@(private)
reed_solomon_multiply :: proc(x, y: u8) -> u8 {
z: u8 = 0
for i := int(7); i >= 0; i -= 1 {
z = u8((uint(z) << 1) ~ ((uint(z) >> 7) * 0x11D))
z ~= ((y >> uint(i)) & 1) * x
}
return z
}
// -------------------------------------------------------------------------------------------------
// Drawing function modules
// -------------------------------------------------------------------------------------------------
// Clears the QR Code grid and marks every function module as dark.
@(private)
initialize_function_modules :: proc(version: int, qrcode: []u8) {
qrsize := version * 4 + 17
total_bytes := (qrsize * qrsize + 7) / 8 + 1
slice.zero(qrcode[:total_bytes])
qrcode[0] = u8(qrsize)
fill_rectangle(6, 0, 1, qrsize, qrcode)
fill_rectangle(0, 6, qrsize, 1, qrcode)
fill_rectangle(0, 0, 9, 9, qrcode)
fill_rectangle(qrsize - 8, 0, 8, 9, qrcode)
fill_rectangle(0, qrsize - 8, 9, 8, qrcode)
align_pat_pos: [7]u8
num_align := get_alignment_pattern_positions(version, &align_pat_pos)
for i in 0 ..< num_align {
for j in 0 ..< num_align {
if !((i == 0 && j == 0) || (i == 0 && j == num_align - 1) || (i == num_align - 1 && j == 0)) {
fill_rectangle(int(align_pat_pos[i]) - 2, int(align_pat_pos[j]) - 2, 5, 5, qrcode)
}
}
}
if version >= 7 {
fill_rectangle(qrsize - 11, 0, 3, 6, qrcode)
fill_rectangle(0, qrsize - 11, 6, 3, qrcode)
}
}
@(private)
draw_light_function_modules :: proc(qrcode: []u8, version: int) {
qrsize := get_size(qrcode)
for i := 7; i < qrsize - 7; i += 2 {
set_module_bounded(qrcode, 6, i, false)
set_module_bounded(qrcode, i, 6, false)
}
for dy in -4 ..= 4 {
for dx in -4 ..= 4 {
dist := abs(dx)
if abs(dy) > dist {
dist = abs(dy)
}
if dist == 2 || dist == 4 {
set_module_unbounded(qrcode, 3 + dx, 3 + dy, false)
set_module_unbounded(qrcode, qrsize - 4 + dx, 3 + dy, false)
set_module_unbounded(qrcode, 3 + dx, qrsize - 4 + dy, false)
}
}
}
align_pat_pos: [7]u8
num_align := get_alignment_pattern_positions(version, &align_pat_pos)
for i in 0 ..< num_align {
for j in 0 ..< num_align {
if (i == 0 && j == 0) || (i == 0 && j == num_align - 1) || (i == num_align - 1 && j == 0) {
continue
}
for dy in -1 ..= 1 {
for dx in -1 ..= 1 {
set_module_bounded(qrcode, int(align_pat_pos[i]) + dx, int(align_pat_pos[j]) + dy, dx == 0 && dy == 0)
}
}
}
}
if version >= 7 {
rem := version
for _ in 0 ..< 12 {
rem = (rem << 1) ~ ((rem >> 11) * 0x1F25)
}
bits := version << 12 | rem
assert(bits >> 18 == 0, "version bits overflow")
for i in 0 ..< 6 {
for j in 0 ..< 3 {
k := qrsize - 11 + j
set_module_bounded(qrcode, k, i, (bits & 1) != 0)
set_module_bounded(qrcode, i, k, (bits & 1) != 0)
bits >>= 1
}
}
}
}
@(private)
draw_format_bits :: proc(ecl: Ecc, mask: Mask, qrcode: []u8) {
format_table := [4]int{1, 0, 3, 2}
data := format_table[int(ecl)] << 3 | int(mask)
rem := data
for _ in 0 ..< 10 {
rem = (rem << 1) ~ ((rem >> 9) * 0x537)
}
bits := (data << 10 | rem) ~ 0x5412
assert(bits >> 15 == 0, "format bits overflow")
for i in 0 ..= 5 {
set_module_bounded(qrcode, 8, i, get_bit(bits, uint(i)))
}
set_module_bounded(qrcode, 8, 7, get_bit(bits, 6))
set_module_bounded(qrcode, 8, 8, get_bit(bits, 7))
set_module_bounded(qrcode, 7, 8, get_bit(bits, 8))
for i in 9 ..< 15 {
set_module_bounded(qrcode, 14 - i, 8, get_bit(bits, uint(i)))
}
qrsize := get_size(qrcode)
for i in 0 ..< 8 {
set_module_bounded(qrcode, qrsize - 1 - i, 8, get_bit(bits, uint(i)))
}
for i in 8 ..< 15 {
set_module_bounded(qrcode, 8, qrsize - 15 + i, get_bit(bits, uint(i)))
}
set_module_bounded(qrcode, 8, qrsize - 8, true)
}
@(private)
get_alignment_pattern_positions :: proc(version: int, result: ^[7]u8) -> int {
if version == 1 {
return 0
}
num_align := version / 7 + 2
step := (version * 8 + num_align * 3 + 5) / (num_align * 4 - 4) * 2
pos := version * 4 + 10
for i := num_align - 1; i >= 1; i -= 1 {
result[i] = u8(pos)
pos -= step
}
result[0] = 6
return num_align
}
@(private)
fill_rectangle :: proc(left, top, width, height: int, qrcode: []u8) {
for dy in 0 ..< height {
for dx in 0 ..< width {
set_module_bounded(qrcode, left + dx, top + dy, true)
}
}
}
// -------------------------------------------------------------------------------------------------
// Drawing data modules and masking
// -------------------------------------------------------------------------------------------------
@(private)
draw_codewords :: proc(data: []u8, data_len: int, qrcode: []u8) {
qrsize := get_size(qrcode)
i := 0
right := qrsize - 1
for right >= 1 {
if right == 6 {
right = 5
}
for vert in 0 ..< qrsize {
for j in 0 ..< 2 {
x := right - j
upward := ((right + 1) & 2) == 0
y := (qrsize - 1 - vert) if upward else vert
if !get_module_bounded(qrcode, x, y) && i < data_len * 8 {
dark := get_bit(int(data[i >> 3]), uint(7 - (i & 7)))
set_module_bounded(qrcode, x, y, dark)
i += 1
}
}
}
right -= 2
}
assert(i == data_len * 8, "not all codeword bits were consumed")
}
@(private)
apply_mask :: proc(function_modules: []u8, qrcode: []u8, mask: Mask) {
qrsize := get_size(qrcode)
for y in 0 ..< qrsize {
for x in 0 ..< qrsize {
if get_module_bounded(function_modules, x, y) {
continue
}
invert: bool
switch mask {
case .M0: invert = (x + y) % 2 == 0
case .M1: invert = y % 2 == 0
case .M2: invert = x % 3 == 0
case .M3: invert = (x + y) % 3 == 0
case .M4: invert = (x / 3 + y / 2) % 2 == 0
case .M5: invert = x * y % 2 + x * y % 3 == 0
case .M6: invert = (x * y % 2 + x * y % 3) % 2 == 0
case .M7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0
}
val := get_module_bounded(qrcode, x, y)
set_module_bounded(qrcode, x, y, val ~ invert) // XOR booleans
}
}
}
@(private)
get_penalty_score :: proc(qrcode: []u8) -> int {
qrsize := get_size(qrcode)
result := 0
// Adjacent modules in row having same color, and finder-like patterns
for y in 0 ..< qrsize {
run_color := false
run_x := 0
run_history: [7]int
for x in 0 ..< qrsize {
if get_module_bounded(qrcode, x, y) == run_color {
run_x += 1
if run_x == 5 {
result += PENALTY_N1
} else if run_x > 5 {
result += 1
}
} else {
finder_penalty_add_history(run_x, &run_history, qrsize)
if !run_color {
result += finder_penalty_count_patterns(&run_history, qrsize) * PENALTY_N3
}
run_color = get_module_bounded(qrcode, x, y)
run_x = 1
}
}
result += finder_penalty_terminate_and_count(run_color, run_x, &run_history, qrsize) * PENALTY_N3
}
// Adjacent modules in column having same color, and finder-like patterns
for x in 0 ..< qrsize {
run_color := false
run_y := 0
run_history: [7]int
for y in 0 ..< qrsize {
if get_module_bounded(qrcode, x, y) == run_color {
run_y += 1
if run_y == 5 {
result += PENALTY_N1
} else if run_y > 5 {
result += 1
}
} else {
finder_penalty_add_history(run_y, &run_history, qrsize)
if !run_color {
result += finder_penalty_count_patterns(&run_history, qrsize) * PENALTY_N3
}
run_color = get_module_bounded(qrcode, x, y)
run_y = 1
}
}
result += finder_penalty_terminate_and_count(run_color, run_y, &run_history, qrsize) * PENALTY_N3
}
// 2*2 blocks of modules having same color
for y in 0 ..< qrsize - 1 {
for x in 0 ..< qrsize - 1 {
color := get_module_bounded(qrcode, x, y)
if color == get_module_bounded(qrcode, x + 1, y) &&
color == get_module_bounded(qrcode, x, y + 1) &&
color == get_module_bounded(qrcode, x + 1, y + 1) {
result += PENALTY_N2
}
}
}
// Balance of dark and light modules
dark := 0
for y in 0 ..< qrsize {
for x in 0 ..< qrsize {
if get_module_bounded(qrcode, x, y) {
dark += 1
}
}
}
total := qrsize * qrsize
k := (abs(dark * 20 - total * 10) + total - 1) / total - 1
assert(0 <= k && k <= 9, "penalty balance factor out of range")
result += k * PENALTY_N4
return result
}
@(private)
finder_penalty_count_patterns :: proc(run_history: ^[7]int, qrsize: int) -> int {
n := run_history[1]
assert(n <= qrsize * 3, "finder penalty run exceeds limit")
core :=
n > 0 && run_history[2] == n && run_history[3] == n * 3 && run_history[4] == n && run_history[5] == n
return(
(1 if core && run_history[0] >= n * 4 && run_history[6] >= n else 0) +
(1 if core && run_history[6] >= n * 4 && run_history[0] >= n else 0) \
)
}
@(private)
finder_penalty_terminate_and_count :: proc(
current_run_color: bool,
current_run_length: int,
run_history: ^[7]int,
qrsize: int,
) -> int {
current_run_length := current_run_length
if current_run_color {
finder_penalty_add_history(current_run_length, run_history, qrsize)
current_run_length = 0
}
current_run_length += qrsize
finder_penalty_add_history(current_run_length, run_history, qrsize)
return finder_penalty_count_patterns(run_history, qrsize)
}
@(private)
finder_penalty_add_history :: proc(current_run_length: int, run_history: ^[7]int, qrsize: int) {
current_run_length := current_run_length
if run_history[0] == 0 {
current_run_length += qrsize
}
// Shift history right by 1
for i := 5; i >= 0; i -= 1 {
run_history[i + 1] = run_history[i]
}
run_history[0] = current_run_length
}
// -------------------------------------------------------------------------------------------------
// Basic QR Code information
// -------------------------------------------------------------------------------------------------
// Returns the minimum buffer size (in bytes) needed for both temp_buffer and qrcode
// to encode the given content at the given ECC level within the given version range.
//
// Returns ok=false when:
// - The content exceeds QR Code capacity for every version in [min_version, max_version]
// at the given ECL.
min_buffer_size :: proc {
min_buffer_size_text,
min_buffer_size_binary,
min_buffer_size_segments,
}
// Text path: auto-selects numeric/alphanumeric/byte mode the same way encode_text does.
//
// Returns ok=false when:
// - The text exceeds QR Code capacity for every version in the range at the given ECL.
// - The encoded segment data would exceed the QR grid buffer size (theoretical edge case).
@(require_results)
min_buffer_size_text :: proc(
text: string,
ecl: Ecc,
min_version: int = VERSION_MIN,
max_version: int = VERSION_MAX,
) -> (
size: int,
ok: bool,
) {
text_len := len(text)
if text_len == 0 {
return buffer_len_for_version(min_version), true
}
mode: Mode = .Byte
if is_numeric(text) {
mode = .Numeric
} else if is_alphanumeric(text) {
mode = .Alphanumeric
}
bit_len := calc_segment_bit_length(mode, text_len)
if bit_len == LENGTH_OVERFLOW do return 0, false
for v in min_version ..= max_version {
total := 4 + num_char_count_bits(mode, v) + bit_len
if total <= get_num_data_codewords(v, ecl) * 8 {
buf_size := buffer_len_for_version(v)
// Segment data is written into temp_buffer, so it must also fit the segment buffer.
seg_buf := calc_segment_buffer_size(mode, text_len)
if seg_buf < 0 || seg_buf > buf_size do return 0, false
return buf_size, true
}
}
return 0, false
}
// Binary path: always byte mode, only needs the payload length.
//
// Returns ok=false when:
// - The payload cannot fit in any version within [min_version, max_version] at the given ECL.
@(require_results)
min_buffer_size_binary :: proc(
data_len: int,
ecl: Ecc,
min_version: int = VERSION_MIN,
max_version: int = VERSION_MAX,
) -> (
size: int,
ok: bool,
) {
if data_len == 0 {
return buffer_len_for_version(min_version), true
}
bit_len := calc_segment_bit_length(.Byte, data_len)
if bit_len == LENGTH_OVERFLOW do return 0, false
for v in min_version ..= max_version {
total := 4 + num_char_count_bits(.Byte, v) + bit_len
if total <= get_num_data_codewords(v, ecl) * 8 {
return buffer_len_for_version(v), true
}
}
return 0, false
}
// Segments path: sums per-segment bit costs across candidate versions.
//
// Returns ok=false when:
// - The combined segment data exceeds QR Code capacity for every version in the range
// at the given ECL.
@(require_results)
min_buffer_size_segments :: proc(
segs: []Segment,
ecl: Ecc,
min_version: int = VERSION_MIN,
max_version: int = VERSION_MAX,
) -> (
size: int,
ok: bool,
) {
if len(segs) == 0 {
return buffer_len_for_version(min_version), true
}
for v in min_version ..= max_version {
data_used_bits := get_total_bits(segs, v)
if data_used_bits != LENGTH_OVERFLOW && data_used_bits <= get_num_data_codewords(v, ecl) * 8 {
return buffer_len_for_version(v), true
}
}
return 0, false
}
// Returns the side length of the given QR Code (range [21, 177]).
get_size :: #force_inline proc(qrcode: []u8) -> int {
return int(qrcode[0])
}
// Returns the color of the module at the given coordinates (false=light, true=dark).
// Out-of-bounds coordinates return false.
get_module :: proc(qrcode: []u8, x, y: int) -> bool {
qrsize := int(qrcode[0])
return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && get_module_bounded(qrcode, x, y)
}
@(private)
get_module_bounded :: #force_inline proc(qrcode: []u8, x, y: int) -> bool {
qrsize := int(qrcode[0])
index := y * qrsize + x
return get_bit(int(qrcode[(index >> 3) + 1]), uint(index & 7))
}
@(private)
set_module_bounded :: #force_inline proc(qrcode: []u8, x, y: int, is_dark: bool) {
qrsize := int(qrcode[0])
index := y * qrsize + x
bit_index := uint(index & 7)
byte_index := (index >> 3) + 1
if is_dark {
qrcode[byte_index] |= u8(1) << bit_index
} else {
qrcode[byte_index] &= ~(u8(1) << bit_index)
}
}
@(private)
set_module_unbounded :: #force_inline proc(qrcode: []u8, x, y: int, is_dark: bool) {
qrsize := int(qrcode[0])
if 0 <= x && x < qrsize && 0 <= y && y < qrsize {
set_module_bounded(qrcode, x, y, is_dark)
}
}
@(private)
get_bit :: #force_inline proc(x: int, i: uint) -> bool {
return ((x >> i) & 1) != 0
}
// -------------------------------------------------------------------------------------------------
// Segment handling
// -------------------------------------------------------------------------------------------------
// Tests whether the given string can be encoded in numeric mode.
is_numeric :: proc(text: string) -> bool {
for i in 0 ..< len(text) {
if text[i] < '0' || text[i] > '9' {
return false
}
}
return true
}
// Tests whether the given string can be encoded in alphanumeric mode.
// Valid characters: 0-9, A-Z (uppercase), space, $ % * + - . / :
is_alphanumeric :: proc(text: string) -> bool {
for i in 0 ..< len(text) {
if alphanumeric_index(text[i]) < 0 do return false
}
return true
}
// Returns the number of bytes needed for the data buffer of a segment.
// Returns -1 when num_chars exceeds the representable range for the given mode
// (i.e. the resulting bit length would exceed 32767).
calc_segment_buffer_size :: proc(mode: Mode, num_chars: int) -> int {
temp := calc_segment_bit_length(mode, num_chars)
if temp == LENGTH_OVERFLOW {
return -1
}
assert(0 <= temp && temp <= 32767, "segment bit length out of range")
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
}
result := num_chars
switch mode {
case .Numeric: result = (result * 10 + 2) / 3
case .Alphanumeric: result = (result * 11 + 1) / 2
case .Byte: result *= 8
case .Kanji: result *= 13
case .Eci:
assert(num_chars == 0, "ECI mode requires num_chars == 0")
result = 3 * 8
}
assert(result >= 0, "negative bit length")
if result > 32767 {
return LENGTH_OVERFLOW
}
return result
}
// Returns a segment representing the given binary data encoded in byte mode.
//
// Parameters:
// data - [in] Source bytes to encode. May be nil/empty.
// buf - [out] Buffer to receive the copied data. Must have length >= len(data).
// The segment's data pointer will reference this buffer.
make_bytes :: proc(data: []u8, buf: []u8) -> Segment {
result: Segment
result.mode = .Byte
result.bit_length = calc_segment_bit_length(.Byte, len(data))
assert(result.bit_length != LENGTH_OVERFLOW, "segment data too long")
result.num_chars = len(data)
if len(data) > 0 {
copy(buf, data)
}
result.data = buf[:len(data)]
return result
}
// Returns a segment representing the given decimal digits encoded in numeric mode.
// Every character in digits must be '0'-'9'.
//
// Parameters:
// digits - [in] String of decimal digit characters.
// buf - [out] Buffer to receive the packed bit data. Must have length >=
// calc_segment_buffer_size(.Numeric, len(digits)). The segment's data
// pointer will reference this buffer.
make_numeric :: proc(digits: string, buf: []u8) -> Segment {
result: Segment
digit_len := len(digits)
result.mode = .Numeric
bit_len := calc_segment_bit_length(.Numeric, digit_len)
assert(bit_len != LENGTH_OVERFLOW, "segment data too long")
result.num_chars = digit_len
bytes_needed := (bit_len + 7) / 8
if bit_len > 0 {
slice.zero(buf[:bytes_needed])
}
result.bit_length = 0
accum_data: uint = 0
accum_count := 0
for i in 0 ..< digit_len {
c := digits[i]
assert('0' <= c && c <= '9', "non-digit character in numeric segment")
accum_data = accum_data * 10 + uint(c - '0')
accum_count += 1
if accum_count == 3 {
append_bits_to_buffer(accum_data, 10, buf, &result.bit_length)
accum_data = 0
accum_count = 0
}
}
if accum_count > 0 {
append_bits_to_buffer(accum_data, accum_count * 3 + 1, buf, &result.bit_length)
}
assert(result.bit_length == bit_len, "encoded bit length mismatch")
result.data = buf[:bytes_needed]
return result
}
// Returns a segment representing the given text encoded in alphanumeric mode.
// Every character must be in: 0-9 A-Z (uppercase) space $ % * + - . / :
//
// Parameters:
// text - [in] String of alphanumeric characters.
// buf - [out] Buffer to receive the packed bit data. Must have length >=
// calc_segment_buffer_size(.Alphanumeric, len(text)). The segment's data
// pointer will reference this buffer.
make_alphanumeric :: proc(text: string, buf: []u8) -> Segment {
result: Segment
text_len := len(text)
result.mode = .Alphanumeric
bit_len := calc_segment_bit_length(.Alphanumeric, text_len)
assert(bit_len != LENGTH_OVERFLOW, "segment data too long")
result.num_chars = text_len
bytes_needed := (bit_len + 7) / 8
if bit_len > 0 {
slice.zero(buf[:bytes_needed])
}
result.bit_length = 0
accum_data: uint = 0
accum_count := 0
for i in 0 ..< text_len {
idx := alphanumeric_index(text[i])
assert(idx >= 0, "character not in alphanumeric charset")
accum_data = accum_data * 45 + uint(idx)
accum_count += 1
if accum_count == 2 {
append_bits_to_buffer(accum_data, 11, buf, &result.bit_length)
accum_data = 0
accum_count = 0
}
}
if accum_count > 0 {
append_bits_to_buffer(accum_data, 6, buf, &result.bit_length)
}
assert(result.bit_length == bit_len, "encoded bit length mismatch")
result.data = buf[:bytes_needed]
return result
}
// Returns a segment representing an Extended Channel Interpretation (ECI) designator
// with the given assignment value (must be in [0, 999999]).
//
// Parameters:
// buf - [out] Buffer to receive the packed ECI data. Must have length >= 3.
// The segment's data pointer will reference this buffer.
make_eci :: proc(assign_val: int, buf: []u8) -> Segment {
result: Segment
result.mode = .Eci
result.num_chars = 0
result.bit_length = 0
if assign_val < 0 {
assert(false, "ECI assignment value must be non-negative")
} else if assign_val < (1 << 7) {
slice.zero(buf[:1])
append_bits_to_buffer(uint(assign_val), 8, buf, &result.bit_length)
} else if assign_val < (1 << 14) {
slice.zero(buf[:2])
append_bits_to_buffer(2, 2, buf, &result.bit_length)
append_bits_to_buffer(uint(assign_val), 14, buf, &result.bit_length)
} else if assign_val < 1000000 {
slice.zero(buf[:3])
append_bits_to_buffer(6, 3, buf, &result.bit_length)
append_bits_to_buffer(uint(assign_val >> 10), 11, buf, &result.bit_length)
append_bits_to_buffer(uint(assign_val & 0x3FF), 10, buf, &result.bit_length)
} else {
assert(false, "ECI assignment value must be < 1000000")
}
result.data = buf[:(result.bit_length + 7) / 8]
return result
}
// -------------------------------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------------------------------
@(private)
append_bits_to_buffer :: proc(val: uint, num_bits: int, buffer: []u8, bit_len: ^int) {
assert(0 <= num_bits && num_bits <= 16 && val >> uint(num_bits) == 0, "invalid bit count or value overflow")
for i := num_bits - 1; i >= 0; i -= 1 {
buffer[bit_len^ >> 3] |= u8(((val >> uint(i)) & 1)) << uint(7 - (bit_len^ & 7))
bit_len^ += 1
}
}
@(private)
get_total_bits :: proc(segs: []Segment, version: int) -> int {
result := 0
for &seg in segs {
num_chars := seg.num_chars
bit_length := seg.bit_length
assert(0 <= num_chars && num_chars <= 32767, "segment num_chars out of range")
assert(0 <= bit_length && bit_length <= 32767, "segment bit_length out of range")
ccbits := num_char_count_bits(seg.mode, version)
assert(0 <= ccbits && ccbits <= 16, "char count bit width out of range")
if num_chars >= (1 << uint(ccbits)) {
return LENGTH_OVERFLOW
}
result += 4 + ccbits + bit_length
if result > 32767 {
return LENGTH_OVERFLOW
}
}
assert(0 <= result && result <= 32767, "total bits out of range")
return result
}
@(private)
num_char_count_bits :: proc(mode: Mode, version: int) -> int {
assert(VERSION_MIN <= version && version <= VERSION_MAX, "version out of bounds")
i := (version + 7) / 17
switch mode {
case .Numeric:
t := [3]int{10, 12, 14}
return t[i]
case .Alphanumeric:
t := [3]int{9, 11, 13}
return t[i]
case .Byte:
t := [3]int{8, 16, 16}
return t[i]
case .Kanji:
t := [3]int{8, 10, 12}
return t[i]
case .Eci: return 0
}
unreachable()
}
// Returns the index of c in the alphanumeric charset (0-44), or -1 if not found.
@(private)
alphanumeric_index :: proc(c: u8) -> int {
switch c {
case '0' ..= '9': return int(c - '0')
case 'A' ..= 'Z': return int(c - 'A') + 10
case ' ': return 36
case '$': return 37
case '%': return 38
case '*': return 39
case '+': return 40
case '-': return 41
case '.': return 42
case '/': return 43
case ':': return 44
}
return -1
}
// ---------------------------------------------------------------------------------------------------------------------
// ----- Tests ------------------------
// ---------------------------------------------------------------------------------------------------------------------
import "core:math/rand"
import "core:testing"
// Helper: reference implementation of add_ecc_and_interleave for testing.
// This uses a straightforward block-by-block approach with dynamic allocation,
// then interleaves column-by-column. The result is written to the provided buffer.
add_ecc_and_interleave_reference :: proc(data: []u8, version: int, ecl: Ecc, result: []u8) {
num_blocks := int(NUM_ERROR_CORRECTION_BLOCKS[int(ecl)][version])
block_ecc_len := int(ECC_CODEWORDS_PER_BLOCK[int(ecl)][version])
raw_codewords := get_num_raw_data_modules(version) / 8
num_short_blocks := num_blocks - raw_codewords % num_blocks
short_block_data_len := raw_codewords / num_blocks - block_ecc_len
gen: [REED_SOLOMON_DEGREE_MAX]u8
reed_solomon_compute_divisor(block_ecc_len, gen[:block_ecc_len])
blocks := make([][]u8, num_blocks)
defer {
for &b in blocks {
delete(b)
}
delete(blocks)
}
k := 0
for i in 0 ..< num_blocks {
dat_len := short_block_data_len + (0 if i < num_short_blocks else 1)
block_len := dat_len + block_ecc_len
blocks[i] = make([]u8, block_len)
copy(blocks[i][:dat_len], data[k:k + dat_len])
reed_solomon_compute_remainder(
data[k:k + dat_len],
gen[:block_ecc_len],
blocks[i][dat_len:dat_len + block_ecc_len],
)
k += dat_len
}
k = 0
for i in 0 ..= short_block_data_len {
for j in 0 ..< num_blocks {
if i == short_block_data_len && j < num_short_blocks {
continue
}
result[k] = blocks[j][i]
k += 1
}
}
for i in 0 ..< block_ecc_len {
for j in 0 ..< num_blocks {
dat_len := short_block_data_len + (0 if j < num_short_blocks else 1)
result[k] = blocks[j][dat_len + i]
k += 1
}
}
}
@(test)
test_append_bits_to_buffer :: proc(t: ^testing.T) {
// Sub-test 1: single byte buffer
{
buf: [1]u8
bit_len := 0
append_bits_to_buffer(0, 0, buf[:], &bit_len)
testing.expect_value(t, bit_len, 0)
testing.expect_value(t, buf[0], u8(0x00))
append_bits_to_buffer(1, 1, buf[:], &bit_len)
testing.expect_value(t, bit_len, 1)
testing.expect_value(t, buf[0], u8(0x80))
append_bits_to_buffer(0, 1, buf[:], &bit_len)
testing.expect_value(t, bit_len, 2)
testing.expect_value(t, buf[0], u8(0x80))
append_bits_to_buffer(5, 3, buf[:], &bit_len)
testing.expect_value(t, bit_len, 5)
testing.expect_value(t, buf[0], u8(0xA8))
append_bits_to_buffer(6, 3, buf[:], &bit_len)
testing.expect_value(t, bit_len, 8)
testing.expect_value(t, buf[0], u8(0xAE))
}
// Sub-test 2: multi-byte buffer
{
buf: [6]u8
bit_len := 0
append_bits_to_buffer(16942, 16, buf[:], &bit_len)
testing.expect_value(t, bit_len, 16)
testing.expect_value(t, buf[0], u8(0x42))
testing.expect_value(t, buf[1], u8(0x2E))
testing.expect_value(t, buf[2], u8(0x00))
testing.expect_value(t, buf[3], u8(0x00))
testing.expect_value(t, buf[4], u8(0x00))
testing.expect_value(t, buf[5], u8(0x00))
append_bits_to_buffer(10, 7, buf[:], &bit_len)
testing.expect_value(t, bit_len, 23)
testing.expect_value(t, buf[0], u8(0x42))
testing.expect_value(t, buf[1], u8(0x2E))
testing.expect_value(t, buf[2], u8(0x14))
testing.expect_value(t, buf[3], u8(0x00))
testing.expect_value(t, buf[4], u8(0x00))
testing.expect_value(t, buf[5], u8(0x00))
append_bits_to_buffer(15, 4, buf[:], &bit_len)
testing.expect_value(t, bit_len, 27)
testing.expect_value(t, buf[0], u8(0x42))
testing.expect_value(t, buf[1], u8(0x2E))
testing.expect_value(t, buf[2], u8(0x15))
testing.expect_value(t, buf[3], u8(0xE0))
testing.expect_value(t, buf[4], u8(0x00))
testing.expect_value(t, buf[5], u8(0x00))
append_bits_to_buffer(26664, 15, buf[:], &bit_len)
testing.expect_value(t, bit_len, 42)
testing.expect_value(t, buf[0], u8(0x42))
testing.expect_value(t, buf[1], u8(0x2E))
testing.expect_value(t, buf[2], u8(0x15))
testing.expect_value(t, buf[3], u8(0xFA))
testing.expect_value(t, buf[4], u8(0x0A))
testing.expect_value(t, buf[5], u8(0x00))
}
}
@(test)
test_add_ecc_and_interleave :: proc(t: ^testing.T) {
for ver in VERSION_MIN ..= VERSION_MAX {
for ecl_int in 0 ..< 4 {
ecl := Ecc(ecl_int)
data_len := get_num_data_codewords(ver, ecl)
raw_codewords := get_num_raw_data_modules(ver) / 8
data: [BUFFER_LEN_MAX]u8
data_copy: [BUFFER_LEN_MAX]u8
result: [BUFFER_LEN_MAX]u8
expected: [BUFFER_LEN_MAX]u8
for i in 0 ..< data_len {
data[i] = u8(rand.uint32())
}
copy(data_copy[:data_len], data[:data_len])
add_ecc_and_interleave_reference(data_copy[:data_len], ver, ecl, expected[:])
add_ecc_and_interleave(data[:], ver, ecl, result[:])
for i in 0 ..< raw_codewords {
testing.expect_value(t, result[i], expected[i])
}
}
}
}
@(test)
test_get_num_data_codewords :: proc(t: ^testing.T) {
//odinfmt: disable
vers := [?]int{3, 3, 3, 6, 7, 9, 9, 12, 15, 16, 19, 21, 22, 22, 22, 24, 24, 28, 30, 32, 33, 33, 35, 35, 35, 36, 37, 39, 40}
ecls := [?]int{1, 2, 3, 0, 0, 0, 1, 3, 0, 2, 3, 0, 0, 1, 3, 0, 3, 0, 3, 3, 0, 3, 0, 1, 2, 3, 3, 1, 1}
exps := [?]int{44, 34, 26, 136, 156, 232, 182, 158, 523, 325, 341, 932, 1006, 782, 442, 1174, 514, 1531, 745, 845, 2071, 901, 2306, 1812, 1286, 1054, 1096, 2216, 2334}
//odinfmt: enable
for i in 0 ..< len(vers) {
testing.expect_value(t, get_num_data_codewords(vers[i], Ecc(ecls[i])), exps[i])
}
}
@(test)
test_get_num_raw_data_modules :: proc(t: ^testing.T) {
//odinfmt: disable
vers := [?]int{1, 2, 3, 6, 7, 12, 15, 18, 22, 26, 32, 37, 40}
exps := [?]int{208, 359, 567, 1383, 1568, 3728, 5243, 7211, 10068, 13652, 19723, 25568, 29648}
//odinfmt: enable
for i in 0 ..< len(vers) {
testing.expect_value(t, get_num_raw_data_modules(vers[i]), exps[i])
}
}
@(test)
test_reed_solomon_compute_divisor :: proc(t: ^testing.T) {
// Degree 1
{
gen: [1]u8
reed_solomon_compute_divisor(1, gen[:])
testing.expect_value(t, gen[0], u8(0x01))
}
// Degree 2
{
gen: [2]u8
reed_solomon_compute_divisor(2, gen[:])
testing.expect_value(t, gen[0], u8(0x03))
testing.expect_value(t, gen[1], u8(0x02))
}
// Degree 5
{
gen: [5]u8
reed_solomon_compute_divisor(5, gen[:])
testing.expect_value(t, gen[0], u8(0x1F))
testing.expect_value(t, gen[1], u8(0xC6))
testing.expect_value(t, gen[2], u8(0x3F))
testing.expect_value(t, gen[3], u8(0x93))
testing.expect_value(t, gen[4], u8(0x74))
}
// Degree 30
{
gen: [REED_SOLOMON_DEGREE_MAX]u8
reed_solomon_compute_divisor(30, gen[:30])
testing.expect_value(t, gen[0], u8(0xD4))
testing.expect_value(t, gen[1], u8(0xF6))
testing.expect_value(t, gen[5], u8(0xC0))
testing.expect_value(t, gen[12], u8(0x16))
testing.expect_value(t, gen[13], u8(0xD9))
testing.expect_value(t, gen[20], u8(0x12))
testing.expect_value(t, gen[27], u8(0x6A))
testing.expect_value(t, gen[29], u8(0x96))
}
}
@(test)
test_reed_solomon_compute_remainder :: proc(t: ^testing.T) {
// Sub-test 1: empty data, degree 3 → all zeros
{
gen: [3]u8
reed_solomon_compute_divisor(3, gen[:])
rem: [3]u8
empty: [0]u8
reed_solomon_compute_remainder(empty[:], gen[:], rem[:])
testing.expect_value(t, rem[0], u8(0x00))
testing.expect_value(t, rem[1], u8(0x00))
testing.expect_value(t, rem[2], u8(0x00))
}
// Sub-test 2: data={0,1}, degree 4 → remainder == generator
{
gen: [4]u8
reed_solomon_compute_divisor(4, gen[:])
data := [?]u8{0x00, 0x01}
rem: [4]u8
reed_solomon_compute_remainder(data[:], gen[:], rem[:])
testing.expect_value(t, rem[0], gen[0])
testing.expect_value(t, rem[1], gen[1])
testing.expect_value(t, rem[2], gen[2])
testing.expect_value(t, rem[3], gen[3])
}
// Sub-test 3: 5-byte data, degree 5
{
gen: [5]u8
reed_solomon_compute_divisor(5, gen[:])
data := [?]u8{0x03, 0x3A, 0x60, 0x12, 0xC7}
rem: [5]u8
reed_solomon_compute_remainder(data[:], gen[:], rem[:])
testing.expect_value(t, rem[0], u8(0xCB))
testing.expect_value(t, rem[1], u8(0x36))
testing.expect_value(t, rem[2], u8(0x16))
testing.expect_value(t, rem[3], u8(0xFA))
testing.expect_value(t, rem[4], u8(0x9D))
}
// Sub-test 4: 43-byte data, degree 30
{
gen: [REED_SOLOMON_DEGREE_MAX]u8
reed_solomon_compute_divisor(30, gen[:30])
//odinfmt: disable
data := [?]u8{
0x03, 0x3A, 0x60, 0x12, 0xC7, 0x40, 0x04, 0x00,
0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11, 0xEC, 0x11,
0xEC, 0x11, 0xEC,
}
//odinfmt: enable
rem: [REED_SOLOMON_DEGREE_MAX]u8
reed_solomon_compute_remainder(data[:], gen[:30], rem[:30])
testing.expect_value(t, rem[0], u8(0xFE))
testing.expect_value(t, rem[1], u8(0x71))
testing.expect_value(t, rem[2], u8(0xFA))
testing.expect_value(t, rem[3], u8(0xFE))
testing.expect_value(t, rem[9], u8(0xB9))
testing.expect_value(t, rem[10], u8(0xC9))
testing.expect_value(t, rem[15], u8(0xAC))
testing.expect_value(t, rem[16], u8(0xFA))
testing.expect_value(t, rem[19], u8(0x07))
testing.expect_value(t, rem[20], u8(0x63))
testing.expect_value(t, rem[25], u8(0xB5))
testing.expect_value(t, rem[29], u8(0xCB))
}
}
@(test)
test_reed_solomon_multiply :: proc(t: ^testing.T) {
//odinfmt: disable
cases := [?][3]u8{
{0x00, 0x00, 0x00},
{0x01, 0x01, 0x01},
{0x02, 0x02, 0x04},
{0x00, 0x6E, 0x00},
{0xB2, 0xDD, 0xE6},
{0x41, 0x11, 0x25},
{0xB0, 0x1F, 0x11},
{0x05, 0x75, 0xBC},
{0x52, 0xB5, 0xAE},
{0xA8, 0x20, 0xA4},
{0x0E, 0x44, 0x9F},
{0xD4, 0x13, 0xA0},
{0x31, 0x10, 0x37},
{0x6C, 0x58, 0xCB},
{0xB6, 0x75, 0x3E},
{0xFF, 0xFF, 0xE2},
}
//odinfmt: enable
for tc in cases {
testing.expect_value(t, reed_solomon_multiply(tc[0], tc[1]), tc[2])
}
}
@(test)
test_initialize_function_modules :: proc(t: ^testing.T) {
for ver in VERSION_MIN ..= VERSION_MAX {
qrcode: [BUFFER_LEN_MAX]u8
initialize_function_modules(ver, qrcode[:])
size := ver * 4 + 17
testing.expect_value(t, get_size(qrcode[:]), size)
has_light := false
has_dark := false
for y in 0 ..< size {
for x in 0 ..< size {
if get_module(qrcode[:], x, y) {
has_dark = true
} else {
has_light = true
}
}
}
testing.expect(t, has_light)
testing.expect(t, has_dark)
}
}
@(test)
test_get_alignment_pattern_positions :: proc(t: ^testing.T) {
// {version, num, positions...}
{
result: [7]u8
num := get_alignment_pattern_positions(1, &result)
testing.expect_value(t, num, 0)
}
{
result: [7]u8
num := get_alignment_pattern_positions(2, &result)
testing.expect_value(t, num, 2)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(18))
}
{
result: [7]u8
num := get_alignment_pattern_positions(3, &result)
testing.expect_value(t, num, 2)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(22))
}
{
result: [7]u8
num := get_alignment_pattern_positions(6, &result)
testing.expect_value(t, num, 2)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(34))
}
{
result: [7]u8
num := get_alignment_pattern_positions(7, &result)
testing.expect_value(t, num, 3)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(22))
testing.expect_value(t, result[2], u8(38))
}
{
result: [7]u8
num := get_alignment_pattern_positions(8, &result)
testing.expect_value(t, num, 3)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(24))
testing.expect_value(t, result[2], u8(42))
}
{
result: [7]u8
num := get_alignment_pattern_positions(16, &result)
testing.expect_value(t, num, 4)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(26))
testing.expect_value(t, result[2], u8(50))
testing.expect_value(t, result[3], u8(74))
}
{
result: [7]u8
num := get_alignment_pattern_positions(25, &result)
testing.expect_value(t, num, 5)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(32))
testing.expect_value(t, result[2], u8(58))
testing.expect_value(t, result[3], u8(84))
testing.expect_value(t, result[4], u8(110))
}
{
result: [7]u8
num := get_alignment_pattern_positions(32, &result)
testing.expect_value(t, num, 6)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(34))
testing.expect_value(t, result[2], u8(60))
testing.expect_value(t, result[3], u8(86))
testing.expect_value(t, result[4], u8(112))
testing.expect_value(t, result[5], u8(138))
}
{
result: [7]u8
num := get_alignment_pattern_positions(33, &result)
testing.expect_value(t, num, 6)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(30))
testing.expect_value(t, result[2], u8(58))
testing.expect_value(t, result[3], u8(86))
testing.expect_value(t, result[4], u8(114))
testing.expect_value(t, result[5], u8(142))
}
{
result: [7]u8
num := get_alignment_pattern_positions(39, &result)
testing.expect_value(t, num, 7)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(26))
testing.expect_value(t, result[2], u8(54))
testing.expect_value(t, result[3], u8(82))
testing.expect_value(t, result[4], u8(110))
testing.expect_value(t, result[5], u8(138))
testing.expect_value(t, result[6], u8(166))
}
{
result: [7]u8
num := get_alignment_pattern_positions(40, &result)
testing.expect_value(t, num, 7)
testing.expect_value(t, result[0], u8(6))
testing.expect_value(t, result[1], u8(30))
testing.expect_value(t, result[2], u8(58))
testing.expect_value(t, result[3], u8(86))
testing.expect_value(t, result[4], u8(114))
testing.expect_value(t, result[5], u8(142))
testing.expect_value(t, result[6], u8(170))
}
}
@(test)
test_get_set_module :: proc(t: ^testing.T) {
qrcode: [BUFFER_LEN_MAX]u8
initialize_function_modules(23, qrcode[:])
size := 23 * 4 + 17 // 109
// Clear all modules to light
for y in 0 ..< size {
for x in 0 ..< size {
set_module_bounded(qrcode[:], x, y, false)
}
}
// Check all are light
for y in 0 ..< size {
for x in 0 ..< size {
testing.expect(t, !get_module(qrcode[:], x, y))
}
}
// Set all modules to dark
for y in 0 ..< size {
for x in 0 ..< size {
set_module_bounded(qrcode[:], x, y, true)
}
}
// Check all are dark
for y in 0 ..< size {
for x in 0 ..< size {
testing.expect(t, get_module(qrcode[:], x, y))
}
}
// Set out-of-bounds to light (should not change anything)
set_module_unbounded(qrcode[:], -1, -1, false)
set_module_unbounded(qrcode[:], -1, 0, false)
set_module_unbounded(qrcode[:], 0, -1, false)
set_module_unbounded(qrcode[:], size, 5, false)
set_module_unbounded(qrcode[:], 72, size, false)
// Check all still dark
for y in 0 ..< size {
for x in 0 ..< size {
testing.expect(t, get_module(qrcode[:], x, y))
}
}
// Set specific modules to light, verify pattern
set_module_bounded(qrcode[:], 3, 8, false)
set_module_bounded(qrcode[:], 61, 49, false)
testing.expect(t, !get_module(qrcode[:], 3, 8))
testing.expect(t, get_module(qrcode[:], 2, 8))
testing.expect(t, get_module(qrcode[:], 4, 8))
testing.expect(t, !get_module(qrcode[:], 61, 49))
testing.expect(t, get_module(qrcode[:], 60, 49))
testing.expect(t, get_module(qrcode[:], 62, 49))
}
@(test)
test_get_set_module_randomly :: proc(t: ^testing.T) {
qrcode: [BUFFER_LEN_MAX]u8
initialize_function_modules(1, qrcode[:])
size :: 21 // version 1: 1*4+17
modules: [size][size]bool
for y in 0 ..< size {
for x in 0 ..< size {
modules[y][x] = get_module(qrcode[:], x, y)
}
}
for _ in 0 ..< 100000 {
x := int(rand.uint32() % u32(size * 2)) - size / 2
y := int(rand.uint32() % u32(size * 2)) - size / 2
is_in_bounds := 0 <= x && x < size && 0 <= y && y < size
expected: bool
if is_in_bounds {
expected = modules[y][x]
}
testing.expect(t, get_module(qrcode[:], x, y) == expected)
if is_in_bounds {
new_val := rand.uint32() % 2 == 0
set_module_bounded(qrcode[:], x, y, new_val)
modules[y][x] = new_val
}
}
}
@(test)
test_is_alphanumeric :: proc(t: ^testing.T) {
testing.expect(t, is_alphanumeric(""))
testing.expect(t, is_alphanumeric("0"))
testing.expect(t, is_alphanumeric("A"))
testing.expect(t, !is_alphanumeric("a"))
testing.expect(t, is_alphanumeric(" "))
testing.expect(t, is_alphanumeric("."))
testing.expect(t, is_alphanumeric("*"))
testing.expect(t, !is_alphanumeric(","))
testing.expect(t, !is_alphanumeric("|"))
testing.expect(t, !is_alphanumeric("@"))
testing.expect(t, is_alphanumeric("XYZ"))
testing.expect(t, !is_alphanumeric("XYZ!"))
testing.expect(t, is_alphanumeric("79068"))
testing.expect(t, is_alphanumeric("+123 ABC$"))
{
b := [1]u8{0x01}
testing.expect(t, !is_alphanumeric(string(b[:])))
}
{
b := [1]u8{0x7F}
testing.expect(t, !is_alphanumeric(string(b[:])))
}
{
b := [1]u8{0x80}
testing.expect(t, !is_alphanumeric(string(b[:])))
}
{
b := [1]u8{0xC0}
testing.expect(t, !is_alphanumeric(string(b[:])))
}
{
b := [1]u8{0xFF}
testing.expect(t, !is_alphanumeric(string(b[:])))
}
}
@(test)
test_is_numeric :: proc(t: ^testing.T) {
testing.expect(t, is_numeric(""))
testing.expect(t, is_numeric("0"))
testing.expect(t, !is_numeric("A"))
testing.expect(t, !is_numeric("a"))
testing.expect(t, !is_numeric(" "))
testing.expect(t, !is_numeric("."))
testing.expect(t, !is_numeric("*"))
testing.expect(t, !is_numeric(","))
testing.expect(t, !is_numeric("|"))
testing.expect(t, !is_numeric("@"))
testing.expect(t, !is_numeric("XYZ"))
testing.expect(t, !is_numeric("XYZ!"))
testing.expect(t, is_numeric("79068"))
testing.expect(t, !is_numeric("+123 ABC$"))
{
b := [1]u8{0x01}
testing.expect(t, !is_numeric(string(b[:])))
}
{
b := [1]u8{0x7F}
testing.expect(t, !is_numeric(string(b[:])))
}
{
b := [1]u8{0x80}
testing.expect(t, !is_numeric(string(b[:])))
}
{
b := [1]u8{0xC0}
testing.expect(t, !is_numeric(string(b[:])))
}
{
b := [1]u8{0xFF}
testing.expect(t, !is_numeric(string(b[:])))
}
}
@(test)
test_calc_segment_buffer_size :: proc(t: ^testing.T) {
// NUMERIC: {num_chars, expected}
{
cases := [?][2]int {
{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, -1},
{9832, -1},
{12000, -1},
{28453, -1},
{55555, -1},
}
for tc in cases {
testing.expect_value(t, calc_segment_buffer_size(.Numeric, tc[0]), tc[1])
}
}
// ALPHANUMERIC
{
cases := [?][2]int {
{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, -1},
{5959, -1},
{12000, -1},
{28453, -1},
{55555, -1},
}
for tc in cases {
testing.expect_value(t, calc_segment_buffer_size(.Alphanumeric, tc[0]), tc[1])
}
}
// BYTE
{
cases := [?][2]int {
{0, 0},
{1, 1},
{2, 2},
{3, 3},
{1472, 1472},
{2097, 2097},
{4094, 4094},
{4095, 4095},
{4096, -1},
{4097, -1},
{5957, -1},
{12000, -1},
{28453, -1},
{55555, -1},
}
for tc in cases {
testing.expect_value(t, calc_segment_buffer_size(.Byte, tc[0]), tc[1])
}
}
// KANJI
{
cases := [?][2]int {
{0, 0},
{1, 2},
{2, 4},
{3, 5},
{1472, 2392},
{2097, 3408},
{2519, 4094},
{2520, 4095},
{2521, -1},
{5957, -1},
{2522, -1},
{12000, -1},
{28453, -1},
{55555, -1},
}
for tc in cases {
testing.expect_value(t, calc_segment_buffer_size(.Kanji, tc[0]), tc[1])
}
}
// ECI
{
testing.expect_value(t, calc_segment_buffer_size(.Eci, 0), 3)
}
}
@(test)
test_calc_segment_bit_length :: proc(t: ^testing.T) {
// NUMERIC: {num_chars, expected}
{
cases := [?][2]int {
{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},
}
for tc in cases {
testing.expect_value(t, calc_segment_bit_length(.Numeric, tc[0]), tc[1])
}
}
// ALPHANUMERIC
{
cases := [?][2]int {
{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},
}
for tc in cases {
testing.expect_value(t, calc_segment_bit_length(.Alphanumeric, tc[0]), tc[1])
}
}
// BYTE
{
cases := [?][2]int {
{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},
}
for tc in cases {
testing.expect_value(t, calc_segment_bit_length(.Byte, tc[0]), tc[1])
}
}
// KANJI
{
cases := [?][2]int {
{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},
}
for tc in cases {
testing.expect_value(t, calc_segment_bit_length(.Kanji, tc[0]), tc[1])
}
}
// ECI
testing.expect_value(t, calc_segment_bit_length(.Eci, 0), 24)
}
@(test)
test_make_bytes :: proc(t: ^testing.T) {
// Empty
{
seg := make_bytes(nil, nil)
testing.expect_value(t, seg.mode, Mode.Byte)
testing.expect_value(t, seg.num_chars, 0)
testing.expect_value(t, seg.bit_length, 0)
testing.expect(t, seg.data == nil)
}
// Single byte {0x00}
{
data := [1]u8{0x00}
buf: [1]u8
seg := make_bytes(data[:], buf[:])
testing.expect_value(t, seg.mode, Mode.Byte)
testing.expect_value(t, seg.num_chars, 1)
testing.expect_value(t, seg.bit_length, 8)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0x00))
}
// Three bytes {0xEF, 0xBB, 0xBF}
{
data := [3]u8{0xEF, 0xBB, 0xBF}
buf: [3]u8
seg := make_bytes(data[:], buf[:])
testing.expect_value(t, seg.mode, Mode.Byte)
testing.expect_value(t, seg.num_chars, 3)
testing.expect_value(t, seg.bit_length, 24)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0xEF))
testing.expect_value(t, seg.data[1], u8(0xBB))
testing.expect_value(t, seg.data[2], u8(0xBF))
}
}
@(test)
test_make_numeric :: proc(t: ^testing.T) {
// Empty
{
seg := make_numeric("", nil)
testing.expect_value(t, seg.mode, Mode.Numeric)
testing.expect_value(t, seg.num_chars, 0)
testing.expect_value(t, seg.bit_length, 0)
}
// "9"
{
buf: [1]u8
seg := make_numeric("9", buf[:])
testing.expect_value(t, seg.mode, Mode.Numeric)
testing.expect_value(t, seg.num_chars, 1)
testing.expect_value(t, seg.bit_length, 4)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0x90))
}
// "81"
{
buf: [1]u8
seg := make_numeric("81", buf[:])
testing.expect_value(t, seg.mode, Mode.Numeric)
testing.expect_value(t, seg.num_chars, 2)
testing.expect_value(t, seg.bit_length, 7)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0xA2))
}
// "673"
{
buf: [2]u8
seg := make_numeric("673", buf[:])
testing.expect_value(t, seg.mode, Mode.Numeric)
testing.expect_value(t, seg.num_chars, 3)
testing.expect_value(t, seg.bit_length, 10)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0xA8))
testing.expect_value(t, seg.data[1], u8(0x40))
}
// "3141592653"
{
buf: [5]u8
seg := make_numeric("3141592653", buf[:])
testing.expect_value(t, seg.mode, Mode.Numeric)
testing.expect_value(t, seg.num_chars, 10)
testing.expect_value(t, seg.bit_length, 34)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0x4E))
testing.expect_value(t, seg.data[1], u8(0x89))
testing.expect_value(t, seg.data[2], u8(0xF4))
testing.expect_value(t, seg.data[3], u8(0x24))
testing.expect_value(t, seg.data[4], u8(0xC0))
}
}
@(test)
test_make_alphanumeric :: proc(t: ^testing.T) {
// Empty
{
seg := make_alphanumeric("", nil)
testing.expect_value(t, seg.mode, Mode.Alphanumeric)
testing.expect_value(t, seg.num_chars, 0)
testing.expect_value(t, seg.bit_length, 0)
}
// "A"
{
buf: [1]u8
seg := make_alphanumeric("A", buf[:])
testing.expect_value(t, seg.mode, Mode.Alphanumeric)
testing.expect_value(t, seg.num_chars, 1)
testing.expect_value(t, seg.bit_length, 6)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0x28))
}
// "%:"
{
buf: [2]u8
seg := make_alphanumeric("%:", buf[:])
testing.expect_value(t, seg.mode, Mode.Alphanumeric)
testing.expect_value(t, seg.num_chars, 2)
testing.expect_value(t, seg.bit_length, 11)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0xDB))
testing.expect_value(t, seg.data[1], u8(0x40))
}
// "Q R"
{
buf: [3]u8
seg := make_alphanumeric("Q R", buf[:])
testing.expect_value(t, seg.mode, Mode.Alphanumeric)
testing.expect_value(t, seg.num_chars, 3)
testing.expect_value(t, seg.bit_length, 17)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0x96))
testing.expect_value(t, seg.data[1], u8(0xCD))
testing.expect_value(t, seg.data[2], u8(0x80))
}
}
@(test)
test_make_eci :: proc(t: ^testing.T) {
// 127
{
buf: [3]u8
seg := make_eci(127, buf[:])
testing.expect_value(t, seg.mode, Mode.Eci)
testing.expect_value(t, seg.num_chars, 0)
testing.expect_value(t, seg.bit_length, 8)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0x7F))
}
// 10345
{
buf: [3]u8
seg := make_eci(10345, buf[:])
testing.expect_value(t, seg.mode, Mode.Eci)
testing.expect_value(t, seg.num_chars, 0)
testing.expect_value(t, seg.bit_length, 16)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0xA8))
testing.expect_value(t, seg.data[1], u8(0x69))
}
// 999999
{
buf: [3]u8
seg := make_eci(999999, buf[:])
testing.expect_value(t, seg.mode, Mode.Eci)
testing.expect_value(t, seg.num_chars, 0)
testing.expect_value(t, seg.bit_length, 24)
testing.expect(t, seg.data != nil)
testing.expect_value(t, seg.data[0], u8(0xCF))
testing.expect_value(t, seg.data[1], u8(0x42))
testing.expect_value(t, seg.data[2], u8(0x3F))
}
}
@(test)
test_get_total_bits :: proc(t: ^testing.T) {
// Empty segments
{
empty: []Segment
testing.expect_value(t, get_total_bits(empty, 1), 0)
testing.expect_value(t, get_total_bits(empty, 40), 0)
}
// Single BYTE segment: {.BYTE, 3, nil, 24}
{
segs := [?]Segment{{mode = .Byte, num_chars = 3, data = nil, bit_length = 24}}
testing.expect_value(t, get_total_bits(segs[:], 2), 36)
testing.expect_value(t, get_total_bits(segs[:], 10), 44)
testing.expect_value(t, get_total_bits(segs[:], 39), 44)
}
// Mixed segments: ECI + NUMERIC + ALPHANUMERIC + KANJI
{
segs := [?]Segment {
{mode = .Eci, num_chars = 0, data = nil, bit_length = 8},
{mode = .Numeric, num_chars = 7, data = nil, bit_length = 24},
{mode = .Alphanumeric, num_chars = 1, data = nil, bit_length = 6},
{mode = .Kanji, num_chars = 4, data = nil, bit_length = 52},
}
testing.expect_value(t, get_total_bits(segs[:], 9), 133)
testing.expect_value(t, get_total_bits(segs[:], 21), 139)
testing.expect_value(t, get_total_bits(segs[:], 27), 145)
}
// Large BYTE segment: {.BYTE, 4093, nil, 32744}
{
segs := [?]Segment{{mode = .Byte, num_chars = 4093, data = nil, bit_length = 32744}}
testing.expect_value(t, get_total_bits(segs[:], 1), -1)
testing.expect_value(t, get_total_bits(segs[:], 10), 32764)
testing.expect_value(t, get_total_bits(segs[:], 27), 32764)
}
// 5 segments: 4x {.NUMERIC,2047,nil,6824} + 1x {.NUMERIC,1617,nil,5390}
{
segs := [?]Segment {
{mode = .Numeric, num_chars = 2047, data = nil, bit_length = 6824},
{mode = .Numeric, num_chars = 2047, data = nil, bit_length = 6824},
{mode = .Numeric, num_chars = 2047, data = nil, bit_length = 6824},
{mode = .Numeric, num_chars = 2047, data = nil, bit_length = 6824},
{mode = .Numeric, num_chars = 1617, data = nil, bit_length = 5390},
}
testing.expect_value(t, get_total_bits(segs[:], 1), -1)
testing.expect_value(t, get_total_bits(segs[:], 10), 32766)
testing.expect_value(t, get_total_bits(segs[:], 27), -1)
}
// 10 segments: 9x {.KANJI,255,nil,3315} + 1x {.ALPHANUMERIC,511,nil,2811}
{
segs := [?]Segment {
{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
{mode = .Kanji, num_chars = 255, data = nil, bit_length = 3315},
{mode = .Alphanumeric, num_chars = 511, data = nil, bit_length = 2811},
}
testing.expect_value(t, get_total_bits(segs[:], 9), 32767)
testing.expect_value(t, get_total_bits(segs[:], 26), -1)
testing.expect_value(t, get_total_bits(segs[:], 40), -1)
}
}
@(test)
test_min_buffer_size_text :: proc(t: ^testing.T) {
// Empty string fits in version 1 at any ECL
{
size, ok := min_buffer_size("", .Low)
testing.expect(t, ok)
testing.expect_value(t, size, buffer_len_for_version(VERSION_MIN))
}
{
size, ok := min_buffer_size("", .High)
testing.expect(t, ok)
testing.expect_value(t, size, buffer_len_for_version(VERSION_MIN))
}
// "Hello, world!" is short byte-mode text, should fit in a small version
size, size_ok := min_buffer_size("Hello, world!", .Low)
testing.expect(t, size_ok)
testing.expect(t, size > 0)
testing.expect(t, size <= buffer_len_for_version(3))
// Pure numeric string gets numeric mode — more compact than byte
num_size, num_ok := min_buffer_size("314159265358979323846264338327950288419716939937510", .Medium)
byte_size, byte_ok := min_buffer_size_binary(51, .Medium) // same length forced to byte mode
testing.expect(t, num_ok)
testing.expect(t, byte_ok)
testing.expect(t, num_size > 0)
testing.expect(t, num_size <= byte_size) // numeric mode should be at least as compact
// Alphanumeric string
alnum_size, alnum_ok := min_buffer_size("HELLO WORLD", .Low)
testing.expect(t, alnum_ok)
testing.expect(t, alnum_size > 0)
// Verify it agrees with what encode_text actually picks
{
text :: "Hello, world!"
planned, planned_ok := min_buffer_size(text, .Low)
testing.expect(t, planned_ok)
testing.expect(t, planned > 0)
qrcode: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok := encode_text(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)
testing.expect_value(t, planned, actual_buf_len)
}
// Constrained max_version: short text should fit in v1
{
hi_size, ok := min_buffer_size("Hi", .Low, max_version = 1)
testing.expect(t, ok)
testing.expect(t, hi_size > 0)
}
// Constrained max_version: long text should fail in v1
long_text := "This string is definitely too long to fit in a version 1 QR code at high ECC"
{
_, ok := min_buffer_size(long_text, .High, max_version = 1)
testing.expect(t, !ok)
}
// min_version raises the floor
{
hi_size, ok := min_buffer_size("Hi", .Low, min_version = 5)
testing.expect(t, ok)
testing.expect_value(t, hi_size, buffer_len_for_version(5))
}
// Higher ECL requires more capacity → may need a larger version
low_size, low_ok := min_buffer_size("Hello, world!", .Low)
high_size, high_ok := min_buffer_size("Hello, world!", .High)
testing.expect(t, low_ok)
testing.expect(t, high_ok)
testing.expect(t, high_size >= low_size)
}
@(test)
test_min_buffer_size_binary :: proc(t: ^testing.T) {
// Empty payload
{
size, ok := min_buffer_size(0, .Low)
testing.expect(t, ok)
testing.expect_value(t, size, buffer_len_for_version(VERSION_MIN))
}
// Small payload
size, size_ok := min_buffer_size(10, .Low)
testing.expect(t, size_ok)
testing.expect(t, size > 0)
testing.expect(t, size <= buffer_len_for_version(2))
// Verify agreement with encode_binary
{
data_len :: 100
planned, planned_ok := min_buffer_size(data_len, .Medium)
testing.expect(t, planned_ok)
testing.expect(t, planned > 0)
dat: [BUFFER_LEN_MAX]u8
qrcode: [BUFFER_LEN_MAX]u8
for i in 0 ..< data_len {
dat[i] = u8(i)
}
ok := encode_binary(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)
testing.expect_value(t, planned, actual_buf_len)
}
// Max capacity at LOW: 2953 bytes should fit, something huge should not
{
size, ok := min_buffer_size(2953, .Low)
testing.expect(t, ok)
testing.expect(t, size > 0)
}
{
_, ok := min_buffer_size(3000, .Low)
testing.expect(t, !ok)
}
// Constrained version
{
_, ok := min_buffer_size(500, .Low, max_version = 5)
testing.expect(t, !ok)
}
}
@(test)
test_min_buffer_size_segments :: proc(t: ^testing.T) {
// Empty segments
{
size, ok := min_buffer_size([]Segment{}, .Low)
testing.expect(t, ok)
testing.expect_value(t, size, buffer_len_for_version(VERSION_MIN))
}
// Single numeric segment
{
buf: [32]u8
seg := make_numeric("12345", buf[:])
segs := [1]Segment{seg}
size, size_ok := min_buffer_size(segs[:], .Low)
testing.expect(t, size_ok)
testing.expect(t, size > 0)
testing.expect(t, size <= buffer_len_for_version(1))
}
// Mixed segments (alphanumeric + numeric) — the silver demo
{
buf0: [BUFFER_LEN_MAX]u8
buf1: [BUFFER_LEN_MAX]u8
segs := [2]Segment {
make_alphanumeric("THE SQUARE ROOT OF 2 IS 1.", buf0[:]),
make_numeric("41421356237309504880168872420969807856967187537694807317667973799", buf1[:]),
}
planned, planned_ok := min_buffer_size(segs[:], .Low)
testing.expect(t, planned_ok)
testing.expect(t, planned > 0)
// Verify against actual encode
qrcode: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok := encode_segments(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)
testing.expect_value(t, planned, actual_buf_len)
}
// Overflow case: segments that exceed version 40
{
segs := [1]Segment{{mode = .Byte, num_chars = 3000, data = nil, bit_length = 24000}}
_, ok := min_buffer_size(segs[:], .Low)
testing.expect(t, !ok)
}
}
@(test)
test_encode_text_auto :: proc(t: ^testing.T) {
// Basic: auto variant produces identical output to explicit_temp
{
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)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
ok_auto := encode_text_auto(text, qr_auto[:], .Low)
testing.expect(t, ok_auto)
size := get_size(qr_explicit[:])
buf_len := buffer_len_for_version((size - 17) / 4)
for i in 0 ..< buf_len {
testing.expect_value(t, qr_auto[i], qr_explicit[i])
}
}
// Numeric mode
{
text :: "314159265358979323846264338327950288419716939937510"
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_text_explicit_temp(text, temp[:], qr_explicit[:], .Medium)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
ok_auto := encode_text_auto(text, qr_auto[:], .Medium)
testing.expect(t, ok_auto)
size := get_size(qr_explicit[:])
buf_len := buffer_len_for_version((size - 17) / 4)
for i in 0 ..< buf_len {
testing.expect_value(t, qr_auto[i], qr_explicit[i])
}
}
// Alphanumeric mode
{
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)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
ok_auto := encode_text_auto(text, qr_auto[:], .Quartile)
testing.expect(t, ok_auto)
size := get_size(qr_explicit[:])
buf_len := buffer_len_for_version((size - 17) / 4)
for i in 0 ..< buf_len {
testing.expect_value(t, qr_auto[i], qr_explicit[i])
}
}
// Empty string
{
qr_auto: [BUFFER_LEN_MAX]u8
ok := encode_text_auto("", qr_auto[:], .Low)
testing.expect(t, ok)
}
// With specific mask
{
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)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
ok_auto := encode_text_auto(text, qr_auto[:], .High, mask = .M3)
testing.expect(t, ok_auto)
size := get_size(qr_explicit[:])
buf_len := buffer_len_for_version((size - 17) / 4)
for i in 0 ..< buf_len {
testing.expect_value(t, qr_auto[i], qr_explicit[i])
}
}
// Oversized content returns false
{
long_text := "This string is definitely too long to fit in a version 1 QR code at high ECC"
qr_auto: [BUFFER_LEN_MAX]u8
ok := encode_text_auto(long_text, qr_auto[:], .High, max_version = 1)
testing.expect(t, !ok)
testing.expect_value(t, qr_auto[0], u8(0))
}
}
@(test)
test_encode_segments_auto :: proc(t: ^testing.T) {
// Mixed segments (silver demo): auto matches explicit_temp
{
buf0: [BUFFER_LEN_MAX]u8
buf1: [BUFFER_LEN_MAX]u8
segs := [2]Segment {
make_alphanumeric("THE SQUARE ROOT OF 2 IS 1.", buf0[:]),
make_numeric("41421356237309504880168872420969807856967187537694807317667973799", buf1[:]),
}
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_segments_explicit_temp(segs[:], .Low, temp[:], qr_explicit[:])
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
ok_auto := encode_segments_auto(segs[:], .Low, qr_auto[:])
testing.expect(t, ok_auto)
size := get_size(qr_explicit[:])
buf_len := buffer_len_for_version((size - 17) / 4)
for i in 0 ..< buf_len {
testing.expect_value(t, qr_auto[i], qr_explicit[i])
}
}
// Empty segments
{
qr_auto: [BUFFER_LEN_MAX]u8
ok := encode_segments_auto(nil, .Low, qr_auto[:])
testing.expect(t, ok)
}
}
@(test)
test_encode_segments_advanced_auto :: proc(t: ^testing.T) {
// Single numeric segment with constrained version range
{
buf: [BUFFER_LEN_MAX]u8
seg := make_numeric("12345678901234567890", buf[:])
segs := [1]Segment{seg}
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_segments_advanced_explicit_temp(
segs[:],
.Medium,
VERSION_MIN,
10,
nil,
true,
temp[:],
qr_explicit[:],
)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
ok_auto := encode_segments_advanced_auto(segs[:], .Medium, VERSION_MIN, 10, nil, true, qr_auto[:])
testing.expect(t, ok_auto)
size := get_size(qr_explicit[:])
buf_len := buffer_len_for_version((size - 17) / 4)
for i in 0 ..< buf_len {
testing.expect_value(t, qr_auto[i], qr_explicit[i])
}
}
// With specific mask and boost_ecl = false
{
buf: [BUFFER_LEN_MAX]u8
seg := make_alphanumeric("TESTING 123", buf[:])
segs := [1]Segment{seg}
qr_explicit: [BUFFER_LEN_MAX]u8
temp: [BUFFER_LEN_MAX]u8
ok_explicit := encode_segments_advanced_explicit_temp(
segs[:],
.High,
1,
40,
Mask.M5,
false,
temp[:],
qr_explicit[:],
)
testing.expect(t, ok_explicit)
qr_auto: [BUFFER_LEN_MAX]u8
ok_auto := encode_segments_advanced_auto(segs[:], .High, 1, 40, Mask.M5, false, qr_auto[:])
testing.expect(t, ok_auto)
size := get_size(qr_explicit[:])
buf_len := buffer_len_for_version((size - 17) / 4)
for i in 0 ..< buf_len {
testing.expect_value(t, qr_auto[i], qr_explicit[i])
}
}
// Oversized: too much data for max_version = 1
{
buf: [BUFFER_LEN_MAX]u8
seg := make_alphanumeric("THIS IS A SOMEWHAT LONG STRING THAT WILL NOT FIT", buf[:])
segs := [1]Segment{seg}
qr_auto: [BUFFER_LEN_MAX]u8
ok := encode_segments_advanced_auto(segs[:], .High, 1, 1, nil, true, qr_auto[:])
testing.expect(t, !ok)
testing.expect_value(t, qr_auto[0], u8(0))
}
}