2846 lines
82 KiB
Odin
2846 lines
82 KiB
Odin
package qrcode
|
|
|
|
import "core:slice"
|
|
|
|
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)
|
|
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
|
|
|
|
// 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,
|
|
}
|
|
|
|
//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_manual :: 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_manual(
|
|
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_manual(
|
|
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_manual(text, temp_buffer, qrcode, ecl, min_version, max_version, mask, boost_ecl)
|
|
}
|
|
|
|
|
|
// 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_manual :: 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_manual(
|
|
segs[:],
|
|
ecl,
|
|
min_version,
|
|
max_version,
|
|
mask,
|
|
boost_ecl,
|
|
data_and_temp,
|
|
qrcode,
|
|
)
|
|
}
|
|
|
|
// 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).
|
|
//
|
|
// 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_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.
|
|
//
|
|
// 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_manual(segs, ecl, temp_buffer, qrcode)
|
|
}
|
|
|
|
|
|
// 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_manual :: 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_manual(
|
|
segs,
|
|
ecl,
|
|
min_version,
|
|
max_version,
|
|
mask,
|
|
boost_ecl,
|
|
temp_buffer,
|
|
qrcode,
|
|
)
|
|
}
|
|
|
|
encode_manual :: proc {
|
|
encode_text_manual,
|
|
encode_binary_manual,
|
|
encode_segments_manual,
|
|
encode_segments_advanced_manual,
|
|
}
|
|
|
|
encode_auto :: proc {
|
|
encode_text_auto,
|
|
encode_binary_auto,
|
|
encode_segments_auto,
|
|
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
|
|
}
|
|
|
|
@(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_manual 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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
// ----- Helpers ------------------------
|
|
// ---------------------------------------------------------------------------------------------------------------------
|
|
|
|
// Internal
|
|
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
|
|
}
|
|
}
|
|
|
|
// Internal
|
|
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
|
|
}
|
|
|
|
// Internal
|
|
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()
|
|
}
|
|
|
|
// Internal
|
|
// Returns the index of c in the alphanumeric charset (0-44), or -1 if not found.
|
|
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_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)
|
|
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_manual
|
|
{
|
|
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_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)
|
|
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_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)
|
|
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_manual(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_manual(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_manual(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_manual(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_manual(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_manual(
|
|
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_manual(
|
|
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))
|
|
}
|
|
}
|