This commit is contained in:
Zachary Levy
2026-04-21 16:16:51 -07:00
parent 7650b90d91
commit ea19b83ba4
5 changed files with 211 additions and 271 deletions

View File

@@ -73,57 +73,32 @@ main :: proc() {
}
}
// -------------------------------------------------------------------------------------------------
// Utilities
// -------------------------------------------------------------------------------------------------
// Prints the given QR Code to the console.
print_qr :: proc(qrcode: []u8) {
size := qr.get_size(qrcode)
border :: 4
for y in -border ..< size + border {
for x in -border ..< size + border {
fmt.print("##" if qr.get_module(qrcode, x, y) else " ")
}
fmt.println()
}
fmt.println()
}
// -------------------------------------------------------------------------------------------------
// Demo: Basic
// -------------------------------------------------------------------------------------------------
// Creates a single QR Code, then prints it to the console.
basic :: proc() {
text :: "Hello, world!"
ecl :: qr.Ecc.Low
qrcode: [qr.BUFFER_LEN_MAX]u8
ok := qr.encode(text, qrcode[:], ecl)
ok := qr.encode_auto(text, qrcode[:], ecl)
if ok do print_qr(qrcode[:])
}
// -------------------------------------------------------------------------------------------------
// Demo: Variety
// -------------------------------------------------------------------------------------------------
// Creates a variety of QR Codes that exercise different features of the library.
variety :: proc() {
qrcode: [qr.BUFFER_LEN_MAX]u8
{ // Numeric mode encoding (3.33 bits per digit)
ok := qr.encode("314159265358979323846264338327950288419716939937510", qrcode[:], qr.Ecc.Medium)
ok := qr.encode_auto("314159265358979323846264338327950288419716939937510", qrcode[:], qr.Ecc.Medium)
if ok do print_qr(qrcode[:])
}
{ // Alphanumeric mode encoding (5.5 bits per character)
ok := qr.encode("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", qrcode[:], qr.Ecc.High)
ok := qr.encode_auto("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", qrcode[:], qr.Ecc.High)
if ok do print_qr(qrcode[:])
}
{ // Unicode text as UTF-8
ok := qr.encode(
ok := qr.encode_auto(
"\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1wa\xE3\x80\x81" +
"\xE4\xB8\x96\xE7\x95\x8C\xEF\xBC\x81\x20\xCE\xB1\xCE\xB2\xCE\xB3\xCE\xB4",
qrcode[:],
@@ -133,7 +108,7 @@ variety :: proc() {
}
{ // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland)
ok := qr.encode(
ok := qr.encode_auto(
"Alice was beginning to get very tired of sitting by her sister on the bank, " +
"and of having nothing to do: once or twice she had peeped into the book her sister was reading, " +
"but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice " +
@@ -148,10 +123,6 @@ variety :: proc() {
}
}
// -------------------------------------------------------------------------------------------------
// Demo: Segment
// -------------------------------------------------------------------------------------------------
// Creates QR Codes with manually specified segments for better compactness.
segment :: proc() {
qrcode: [qr.BUFFER_LEN_MAX]u8
@@ -163,7 +134,7 @@ segment :: proc() {
// Encode as single text (auto mode selection)
{
concat :: silver0 + silver1
ok := qr.encode(concat, qrcode[:], qr.Ecc.Low)
ok := qr.encode_auto(concat, qrcode[:], qr.Ecc.Low)
if ok do print_qr(qrcode[:])
}
@@ -172,7 +143,7 @@ segment :: proc() {
seg_buf0: [qr.BUFFER_LEN_MAX]u8
seg_buf1: [qr.BUFFER_LEN_MAX]u8
segs := [2]qr.Segment{qr.make_alphanumeric(silver0, seg_buf0[:]), qr.make_numeric(silver1, seg_buf1[:])}
ok := qr.encode(segs[:], qr.Ecc.Low, qrcode[:])
ok := qr.encode_auto(segs[:], qr.Ecc.Low, qrcode[:])
if ok do print_qr(qrcode[:])
}
}
@@ -185,7 +156,7 @@ segment :: proc() {
// Encode as single text (auto mode selection)
{
concat :: golden0 + golden1 + golden2
ok := qr.encode(concat, qrcode[:], qr.Ecc.Low)
ok := qr.encode_auto(concat, qrcode[:], qr.Ecc.Low)
if ok do print_qr(qrcode[:])
}
@@ -201,7 +172,7 @@ segment :: proc() {
qr.make_numeric(golden1, seg_buf1[:]),
qr.make_alphanumeric(golden2, seg_buf2[:]),
}
ok := qr.encode(segs[:], qr.Ecc.Low, qrcode[:])
ok := qr.encode_auto(segs[:], qr.Ecc.Low, qrcode[:])
if ok do print_qr(qrcode[:])
}
}
@@ -219,7 +190,7 @@ segment :: proc() {
"\xEF\xBD\x84\xEF\xBD\x85\xEF\xBD\x93\xEF" +
"\xBD\x95\xE3\x80\x80\xCE\xBA\xCE\xB1\xEF" +
"\xBC\x9F"
ok := qr.encode(madoka, qrcode[:], qr.Ecc.Low)
ok := qr.encode_auto(madoka, qrcode[:], qr.Ecc.Low)
if ok do print_qr(qrcode[:])
}
@@ -254,16 +225,12 @@ segment :: proc() {
seg.data = seg_buf[:(seg.bit_length + 7) / 8]
segs := [1]qr.Segment{seg}
ok := qr.encode(segs[:], qr.Ecc.Low, qrcode[:])
ok := qr.encode_auto(segs[:], qr.Ecc.Low, qrcode[:])
if ok do print_qr(qrcode[:])
}
}
}
// -------------------------------------------------------------------------------------------------
// Demo: Mask
// -------------------------------------------------------------------------------------------------
// Creates QR Codes with the same size and contents but different mask patterns.
mask :: proc() {
qrcode: [qr.BUFFER_LEN_MAX]u8
@@ -271,10 +238,10 @@ mask :: proc() {
{ // Project Nayuki URL
ok: bool
ok = qr.encode("https://www.nayuki.io/", qrcode[:], qr.Ecc.High)
ok = qr.encode_auto("https://www.nayuki.io/", qrcode[:], qr.Ecc.High)
if ok do print_qr(qrcode[:])
ok = qr.encode("https://www.nayuki.io/", qrcode[:], qr.Ecc.High, mask = qr.Mask.M3)
ok = qr.encode_auto("https://www.nayuki.io/", qrcode[:], qr.Ecc.High, mask = qr.Mask.M3)
if ok do print_qr(qrcode[:])
}
@@ -290,16 +257,29 @@ mask :: proc() {
ok: bool
ok = qr.encode(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M0)
ok = qr.encode_auto(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M0)
if ok do print_qr(qrcode[:])
ok = qr.encode(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M1)
ok = qr.encode_auto(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M1)
if ok do print_qr(qrcode[:])
ok = qr.encode(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M5)
ok = qr.encode_auto(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M5)
if ok do print_qr(qrcode[:])
ok = qr.encode(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M7)
ok = qr.encode_auto(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M7)
if ok do print_qr(qrcode[:])
}
}
// Prints the given QR Code to the console.
print_qr :: proc(qrcode: []u8) {
size := qr.get_size(qrcode)
border :: 4
for y in -border ..< size + border {
for x in -border ..< size + border {
fmt.print("##" if qr.get_module(qrcode, x, y) else " ")
}
fmt.println()
}
fmt.println()
}

View File

@@ -2,10 +2,30 @@ package qrcode
import "core:slice"
VERSION_MIN :: 1
VERSION_MAX :: 40
// -------------------------------------------------------------------------------------------------
// Types
// -------------------------------------------------------------------------------------------------
// 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 {
@@ -44,39 +64,6 @@ Segment :: struct {
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)
@@ -96,10 +83,9 @@ NUM_ERROR_CORRECTION_BLOCKS := [4][41]i8{
}
//odinfmt: enable
// -------------------------------------------------------------------------------------------------
// Encode procedures
// -------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
// ----- Encode Procedures ------------------------
// ---------------------------------------------------------------------------------------------------------------------
// Encodes the given text string to a QR Code, automatically selecting
// numeric, alphanumeric, or byte mode based on content.
@@ -548,9 +534,10 @@ encode_auto :: proc {
encode_segments_advanced_auto,
}
// -------------------------------------------------------------------------------------------------
// Error correction code generation
// -------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
// ----- Error Correction Code Generation ------------------------
// ---------------------------------------------------------------------------------------------------------------------
// Appends error correction bytes to each block of data, then interleaves bytes from all blocks.
@(private)
@@ -618,10 +605,6 @@ get_num_raw_data_modules :: proc(ver: int) -> int {
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")
@@ -668,9 +651,9 @@ reed_solomon_multiply :: proc(x, y: u8) -> u8 {
return z
}
// -------------------------------------------------------------------------------------------------
// Drawing function modules
// -------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
// ----- Drawing Function Modules ------------------------
// ---------------------------------------------------------------------------------------------------------------------
// Clears the QR Code grid and marks every function module as dark.
@(private)
@@ -816,9 +799,9 @@ fill_rectangle :: proc(left, top, width, height: int, qrcode: []u8) {
}
}
// -------------------------------------------------------------------------------------------------
// Drawing data modules and masking
// -------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
// ----- Drawing data modules and masking ------------------------
// ---------------------------------------------------------------------------------------------------------------------
@(private)
draw_codewords :: proc(data: []u8, data_len: int, qrcode: []u8) {
@@ -996,9 +979,9 @@ finder_penalty_add_history :: proc(current_run_length: int, run_history: ^[7]int
run_history[0] = current_run_length
}
// -------------------------------------------------------------------------------------------------
// Basic QR Code information
// -------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
// ----- 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.
@@ -1158,9 +1141,9 @@ get_bit :: #force_inline proc(x: int, i: uint) -> bool {
return ((x >> i) & 1) != 0
}
// -------------------------------------------------------------------------------------------------
// Segment handling
// -------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
// ----- Segment Handling ------------------------
// ---------------------------------------------------------------------------------------------------------------------
// Tests whether the given string can be encoded in numeric mode.
is_numeric :: proc(text: string) -> bool {
@@ -1349,11 +1332,11 @@ make_eci :: proc(assign_val: int, buf: []u8) -> Segment {
return result
}
// -------------------------------------------------------------------------------------------------
// Private helpers
// -------------------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------------------------------------
// ----- Helpers ------------------------
// ---------------------------------------------------------------------------------------------------------------------
@(private)
// 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 {
@@ -1362,7 +1345,7 @@ append_bits_to_buffer :: proc(val: uint, num_bits: int, buffer: []u8, bit_len: ^
}
}
@(private)
// Internal
get_total_bits :: proc(segs: []Segment, version: int) -> int {
result := 0
for &seg in segs {
@@ -1384,7 +1367,7 @@ get_total_bits :: proc(segs: []Segment, version: int) -> int {
return result
}
@(private)
// 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
@@ -1406,8 +1389,8 @@ num_char_count_bits :: proc(mode: Mode, version: int) -> int {
unreachable()
}
// Internal
// 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')