barcode

tape
@profile(t1);
import io from "io";
import fs from "fs";
import gfx from "gfx";
import image from "image";
import barcode from "barcode";

fn main() -> i32 {
    let code: barcode.Symbol = barcode.qr("https://tapelang.org") or {
        io.println("no QR code: {err}");
        return 1;
    };
    var picture: gfx.Surface = barcode.render(&code, &barcode.Style {}) or { return 2; };
    let png = image.encode(&picture, image.Format.Png) or { return 3; };
    fs.write_file("tapelang.png", png.slice()) or { return 4; };
    picture.free();
    return 0;
}

A barcode is made in two steps. encode turns your data into a Symbol, the grid of light and dark modules the standard asks for. Then you draw the symbol: render gives a gfx.Surface (what image.decode gives for a PNG, so you can save it, draw it in a window or print it), svg gives an SVG document. barcode is a pure module: it reads and writes no files.

FunctionDoes
barcode.qr(data) -> Symbol or BarcodeErrorA QR code at level M, the smallest that holds the data
barcode.encode(kind, data, options) -> Symbol or BarcodeErrorAny kind, with options
barcode.render(&symbol, &style) -> gfx.Surface or BarcodeErrorThe picture, style.scale pixels a module; free() it
barcode.svg(&symbol, &style) -> string or BarcodeErrorThe same picture as an SVG document, a module one unit
barcode.size_px(&symbol, &style) -> gfx.PointThe picture’s size before you draw it
barcode.contrast(a, b) -> f64How far apart two colours are (1 to 21)

Kinds

KindFor
QrQR Code: links, Wi-Fi details, payments, tickets. Versions 1-40
DataMatrixSmall parts and labels, GS1 health care codes
AztecBoarding passes and train tickets: no quiet zone needed
Pdf417ID cards, shipping labels: a stack of rows
Code128Shipping and logistics; GS1-128 with gs1
Ean13, Ean8, UpcA, UpcERetail products
Code39, Code93Industry, inventory
ItfCartons (pairs of digits)
CodabarLibraries, blood banks

Options:

FieldIs
ec: Ec = Ec.MediumError correction: Low, Medium, Quartile, High. QR’s L, M, Q, H; Aztec’s 10, 23, 36, 50 %. More of it is a bigger symbol that survives more damage
size: i64 = 00: the smallest symbol that fits. Else QR’s version (1-40), Aztec’s layers (negative: compact), Data Matrix’s size, PDF417’s columns
mask: i64 = -1QR: the mask, else the best by the standard’s scoring
utf8: bool = falseMark the data as UTF-8 (ECI 26) for readers that would guess otherwise
gs1: bool = falseA GS1 code: put an ASCII 29 ("\x1d") between variable-length fields
check: bool = falseCode 39: add the optional check character

A 2D symbol grows with its data; QR then uses the error correction left over in that size, so a short link at level M may come out at level Q. Each symbology is also a module of its own with every option it has (barcode_qr, barcode_datamatrix, barcode_aztec, barcode_pdf417, barcode_linear); its encode gives the same Symbol.

BarcodeError:

VariantWhen
TooLongThe data does not fit the largest symbol (or the size you asked for)
InvalidCharA character the kind has no code for: a letter in an EAN, lowercase in Code 39
InvalidLengthEAN-13 with 11 digits, ITF with an odd count
BadCheckDigitThe check digit you gave is wrong
InvalidOptionAn option out of range
LogoTooLargeThe logo hides more than the error correction can restore
NoLogoA logo on a symbol without error correction (a 1D barcode)
InvalidSizescale below 1, or a picture too large
OutOfMemoryOut of memory

Style

tape
let style: barcode.Style = barcode.Style {
    shape: barcode.Shape.Rounded;           // modules joined into round blobs
    eye: barcode.EyeShape.Rounded;          // the three corner squares
    eye_core: barcode.EyeShape.Circle;
    fill: barcode.Fill.Linear;              // a gradient
    dark: #6A00F4;
    dark2: #00B4D8;
    angle: 45.0;
};
FieldIs
scale: i64 = 8Pixels a module (render)
quiet: i64 = -1The light margin in modules; -1: what the standard asks (QR 4)
dark: color = #000000, light: color = #FFFFFFThe colours; a light with alpha 0 leaves the background transparent
fill: Fill = Fill.SolidLinear or Radial: a gradient from dark to dark2 over the symbol
dark2: color, angle: f64The gradient’s other colour; Linear’s direction in degrees (0: left to right, 90: top to bottom)
shape: Shape = Shape.SquareSquare, Dot, Rounded (neighbours joined, lone modules round), Diamond
dot: f64 = 1.0How much of its cell a module fills, 0.5 to 1: below 1 leaves gaps
eye, eye_core: EyeShapeThe finder’s frame and centre: Square, Rounded, Circle
eye_color, core_color: colorTheir colours; alpha 0 (the default): as the modules
logo: *gfx.Surface = nullA picture in the middle (below)
logo_href: []const u8 = ""The logo in svg’s output: a URL or a data: URI
logo_size: f64 = 0.2The logo’s width, a share of the symbol’s
logo_pad: f64 = 1.0Modules kept clear round it
logo_round: f64 = 0.0The cleared area’s corner radius, in modules
bar_height: i64 = 501D: the bars’ height, in modules
show_text: bool = true1D: the digits under the bars

The finders are always drawn whole in their own shape, whatever the modules’ shape, so a reader still finds them.

Keep the colours far apart: dark modules on a light background, with a barcode.contrast of 3 or more for the modules, the eyes and the gradient’s ends. Gold eyes on white (2.2) already fail; most phone cameras do not read light modules on a dark background either.

tape
var logo: gfx.Surface = image.decode(logo_png) or { return 1; };
let code: barcode.Symbol = barcode.encode(barcode.Kind.Qr, url, barcode.Options {
    ec: barcode.Ec.High;
}) or { return 2; };
var picture: gfx.Surface = barcode.render(&code, &barcode.Style {
    logo: &logo;
    logo_size: 0.22;
    logo_round: 1.5;
}) or {
    io.println("{err}");        // LogoTooLarge: a smaller logo, or more error correction
    return 3;
};

The modules under the logo are cleared, so the data they held is lost and the error correction has to restore it. render and svg count what the logo hides and refuse it (LogoTooLarge) when it hides more than three quarters of what the code can restore, leaving the rest for a crease or a glare. Ask for Ec.High when you put a logo on a QR code: it takes a logo of about a fifth of its width; at the default level M only a small one fits. The finders, timing and format modules are never cleared.

Logos are for QR codes. An Aztec code has its bullseye in the middle, so it takes none; a Data Matrix code takes a small one in the sizes up to 26 x 26 (one data region, no alignment lines through the middle) when it has room to spare; PDF417’s error correction is spread too thin for one. Each refuses with LogoTooLarge rather than giving a code that does not scan.

For svg, give the logo as logo_href: a URL the SVG will be shown next to, or a data:image/png;base64,... URI you make with image.encode and encoding.base64.

1D barcodes

tape
let ean: barcode.Symbol = barcode.encode(barcode.Kind.Ean13, "400638133393", barcode.Options {}) or { return 1; };
io.println(ean.text);         // "4006381333931": the check digit added

Give EAN and UPC codes without their check digit and it is added, or with it and it is checked (BadCheckDigit). A 1D symbol is one module high: render draws its bars bar_height modules tall with the text under them.

Your own symbology

A Symbol is a grid you can fill yourself, so a barcode the stdlib lacks is drawn like the others:

tape
import barcode_core from "barcode_core";

var s: barcode_core.Symbol = barcode_core.symbol(9, 9);
s.set(4, 4, true, barcode_core.ROLE_DATA);
s.quiet = 2;
var picture: gfx.Surface = barcode.render(&s, &barcode.Style { shape: barcode.Shape.Dot; }) or { return 1; };

Last modified: