116 / Input, output and data

Barcodes and QR codes

std/barcode is Nyx code, no C: the same program that runs on the server compiles to wasm32-wasi and runs offline on a point-of-sale box. It returns DATA, never a drawing: qr_matrix gives the module grid, code128_widths and ean13_widths give the alternating bar/space widths in modules. Each caller paints that as it needs — terminal text, an SVG for a ticket, or a bar count for a thermal printer — this recipe shows all three.

116-barcode-qr.nxSource →
// Nyx by Example: el QR y el código de barras del número de una venta.
//
// El caso real: un punto de venta imprime en el ticket el número de la venta
// para leerlo con un lector al devolver o reimprimir. El mismo programa corre en
// el servidor y compilado a wasm32-wasi en la caja, sin conexión — por eso
// std/barcode es Nyx puro, sin C.
//
// std/barcode devuelve DATOS, no dibujos:
//   - qr_matrix     → la matriz de módulos (Array de filas; 1 oscuro, 0 claro);
//   - code128_widths → anchos alternados barra/espacio en módulos;
//   - ean13_widths   → lo mismo para el código de un producto.
// Cada programa lo pinta como necesite. Esta receta muestra tres formas: texto
// en la terminal, un SVG para un ticket HTML, y la cuenta de una barra en puntos
// de impresora.

import "std/barcode"
import "std/error"

// El QR como texto: dos caracteres por módulo para que salga cuadrado, y la zona
// de silencio de 4 módulos claros alrededor, que el lector NECESITA (la matriz
// no la trae: el margen es cosa del que dibuja).
fn qr_as_text(m: Array) -> String {
    let quiet: int = 4
    let side: int = m.length() + 2 * quiet
    var sb: StringBuilder = StringBuilder.new()
    var r: int = 0 - quiet
    while r < m.length() + quiet {
        var c: int = 0 - quiet
        while c < m.length() + quiet {
            var dark: bool = false
            if r >= 0 and r < m.length() and c >= 0 and c < m.length() {
                let row: Array = m[r]
                let v: int = row[c]
                dark = v == 1
            }
            if dark { sb.append("██") } else { sb.append("  ") }
            c = c + 1
        }
        sb.append("\n")
        r = r + 1
    }
    return sb.to_string()
}

// El Code 128 como SVG: un <rect> por barra, en unidades de módulo; el ancho real
// lo pone la hoja de estilos. Los anchos empiezan siempre en barra, así que las
// posiciones pares son barras y las impares espacios.
fn code128_svg(widths: Array, alto: int) -> String {
    let quiet: int = 10
    var sb: StringBuilder = StringBuilder.new()
    var x: int = quiet
    var i: int = 0
    while i < widths.length() {
        let w: int = widths[i]
        if i % 2 == 0 {
            sb.append("<rect x=\"" + int_to_string(x) + "\" width=\"" + int_to_string(w) + "\" height=\"" + int_to_string(alto) + "\"/>")
        }
        x = x + w
        i = i + 1
    }
    let total: int = x + quiet
    return "<svg viewBox=\"0 0 " + int_to_string(total) + " " + int_to_string(alto) + "\">" + sb.to_string() + "</svg>"
}

fn join(widths: Array) -> String {
    var sb: StringBuilder = StringBuilder.new()
    var i: int = 0
    while i < widths.length() {
        let w: int = widths[i]
        if i > 0 { sb.append(" ") }
        sb.append(int_to_string(w))
        i = i + 1
    }
    return sb.to_string()
}

fn main() -> int {
    let venta: String = "C1-000123"

    // ── 1. QR ────────────────────────────────────────────────────────────
    // QrEcc.M recupera ~15% de módulos dañados; H (~30%) para papel térmico que
    // se borra. Versión y máscara las elige el módulo.
    match qr_matrix(venta, QrEcc.M) {
        Result.Ok(m) => {
            print("QR de " + venta + ": " + int_to_string(m.length()) + "x" + int_to_string(m.length()) + " módulos (versión " + int_to_string((m.length() - 17) / 4) + ")")
            print_no_newline(qr_as_text(m))
        }
        Result.Err(e) => {
            print("no se pudo: " + e.msg)
            return 1
        }
    }

    // Lo que no entra en la versión 10 es un Err, no un QR ilegible.
    match qr_matrix("x".repeat(400), QrEcc.M) {
        Result.Ok(m) => { print("inesperado: entró") }
        Result.Err(e) => { print("400 bytes: " + e.kind + " — " + e.msg) }
    }

    // ── 2. Code 128 ──────────────────────────────────────────────────────
    // El juego (B para letras, C para pares de dígitos) se elige solo: «C1-» va
    // en B y «000123» en C, que ocupa la mitad.
    match code128_widths(venta) {
        Result.Ok(w) => {
            print("Code 128 de " + venta + ": " + int_to_string(w.length()) + " anchos")
            print(join(w))
            // En una impresora térmica de 203 ppp, un módulo de 2 puntos da una
            // barra de ~0,25 mm: el ancho de cada barra es módulos * 2 puntos.
            var modulos: int = 0
            var i: int = 0
            while i < w.length() {
                let x: int = w[i]
                modulos = modulos + x
                i = i + 1
            }
            print("ancho total: " + int_to_string(modulos) + " módulos = " + int_to_string(modulos * 2) + " puntos a 2 puntos por módulo")
            let svg: String = code128_svg(w, 40)
            print("SVG: " + svg.substring(0, 60) + "...")
        }
        Result.Err(e) => {
            print("no se pudo: " + e.msg)
            return 1
        }
    }

    // ── 3. EAN-13 ────────────────────────────────────────────────────────
    // Con 12 dígitos calcula el de control; con 13 lo valida.
    match ean13_widths("590123412345") {
        Result.Ok(w) => { print("EAN-13 5901234123457: " + int_to_string(w.length()) + " anchos, 95 módulos") }
        Result.Err(e) => { print("no se pudo: " + e.msg) }
    }
    match ean13_widths("5901234123458") {
        Result.Ok(w) => { print("inesperado: aceptó un control equivocado") }
        Result.Err(e) => { print("control equivocado: " + e.msg) }
    }
    return 0
}
Outputstdout
QR de C1-000123: 21x21 módulos (versión 1)




        ██████████████  ████  ██    ██████████████
        ██          ██      ██      ██          ██
        ██  ██████  ██    ████  ██  ██  ██████  ██
        ██  ██████  ██  ████  ████  ██  ██████  ██
        ██  ██████  ██  ████    ██  ██  ██████  ██
        ██          ██  ████████    ██          ██
        ██████████████  ██  ██  ██  ██████████████
                        ██████████
        ██      ██  ████████████  ██████████    ██
          ██████  ██  ██  ██  ████        ██    ██
        ██    ██    ██      ████    ██████████████
                        ████      ████████    ██
        ██  ██  ████████    ██  ██████        ████
                        ██████  ██████  ██    ██
        ██████████████  ████    ████    ██████  ██
        ██          ██      ██████    ██████
        ██  ██████  ██  ██    ██    ██  ██  ██  ██
        ██  ██████  ██    ████████    ████  ██████
        ██  ██████  ██    ██████    ██  ████
        ██          ██    ██      ████  ████    ██
        ██████████████  ██      ██████      ██████




400 bytes: invalid — qr: 400 bytes no entran en un QR de versión 10 con ese nivel de corrección (fase 1: versiones 1-10)
Code 128 de C1-000123: 61 anchos
2 1 1 2 1 4 1 3 1 3 2 1 1 2 3 2 2 1 1 2 2 1 3 2 1 1 3 1 4 1 2 1 2 2 2 2 2 2 2 1 2 2 3 1 2 1 3 1 3 1 1 1 2 3 2 3 3 1 1 1 2
ancho total: 112 módulos = 224 puntos a 2 puntos por módulo
SVG: <svg viewBox="0 0 132 40"><rect x="10" width="2" height="40"...
EAN-13 5901234123457: 59 anchos, 95 módulos
control equivocado: ean13: el dígito de control es 8 y debería ser 7

How it works

qr_matrix(text, ecc) picks the version (size) and mask on its own and only fails when the payload does not fit — a Result.Err, never a QR that a reader cannot decode. QrEcc.M recovers about 15% of damaged modules; QrEcc.H (about 30%) is for thermal paper that fades. The matrix carries no quiet zone: the 4-module margin a scanner needs around it is the caller's job, drawn here in qr_as_text.

code128_widths chooses the character set by itself: "C1-" goes out in set B (letters) and "000123" switches to set C, which packs a digit pair per symbol — half the width of the same run in B. The widths are barred and spaced alternately starting with a bar, which is what lets code128_svg emit one <rect> per even-indexed width and skip the spaces.

ean13_widths takes either 12 digits, and computes the check digit, or 13, and validates it — the second call here deliberately gets the wrong one back as an Err naming both the digit it found and the one it expected, instead of silently accepting a mistyped barcode.