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_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 } // ------------------------------------------------------------------------------------------------- // 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_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 } // ------------------------------------------------------------------------------------------------- // 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_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)) } }