QRKude Specification v0.1
Who this is for. This document is for people who write or maintain a QRKude package. It is precise rather than friendly. To use QRKude, read the guide and the style reference instead; the words used below (module, finder pattern, quiet zone…) are explained in the guide’s glossary.
This document defines what every QRKude implementation must do. The C library
in core/ is the reference implementation; the conformance vectors
in spec/vectors/ are generated from it. A port is
conformant when it reproduces every vector byte for byte (SVG output,
module matrices and payload strings), the version/ecc/mask values, the
lint flags, and logoDamage within 1e-6.
Keywords MUST/SHOULD follow RFC 2119. All coordinates are in modules (canvas units) with the y axis pointing down.
1. Encoding
QR Code Model 2, versions 1–40, as in ISO/IEC 18004.
1.1 Mode selection
The whole payload is a single segment (no mixed-mode optimization):
numericif every byte is0–9;- else
alphanumericif every byte is in0-9 A-Z space $ % * + - . / :; - else
byte(the UTF-8 bytes of the text).
An empty payload produces no segment (only terminator and padding).
1.2 Options (encode object)
| key | type | default | meaning |
|---|---|---|---|
ecc |
string | see 1.3 | "L", "M", "Q", "H" — minimum error correction |
minVersion |
int | 1 | smallest version to try |
maxVersion |
int | 40 | largest version to try |
mask |
int | -1 | -1 = automatic, 0–7 = forced |
boostEcc |
bool | true | raise ECC while the chosen version still fits |
eci |
bool | false | prepend ECI designator 26 (UTF-8): 0111 + 8-bit 26 |
1.3 Default ECC
When encode.ecc is absent the ECC is H if the style has a logo (see 4.4),
otherwise M.
1.4 Version and ECC choice
For v from minVersion to maxVersion, the first version where the bit
length fits the data capacity of (v, ecc) is chosen. The bit length is
12 (if eci) + 4 + charCountBits(mode, v) + payloadBits (if the payload
is non-empty). The character count must also be < 2^charCountBits.
Character count bits: numeric 10/12/14, alphanumeric 9/11/13, byte 8/16/16 for
versions 1–9/10–26/27–40. If no version fits the result is the error
data too long.
With boostEcc, for e in ecc+1 .. H (in order L<M<Q<H): if the bit length
fits (v, e), ecc = e.
1.5 Codewords, matrix and masking
Standard ISO procedure: terminator (up to 4 zero bits), zero-pad to a byte,
alternate pad bytes 0xEC, 0x11; Reed–Solomon over GF(256) with polynomial
0x11D, generator roots 2^0 .. 2^(n-1); block interleaving with short
blocks first; zig-zag placement starting at the bottom-right, skipping
column 6; remainder bits light.
The automatic mask is the one with the lowest penalty (N1=3, N2=3, N3=40,
N4=10). Ties go to the lowest mask number. The penalty computation MUST
match the reference exactly, including the finder-like pattern rule that
treats the area outside the symbol as light (see penalty_score in
core/src/qrk_encode.c). The format information is drawn before the penalty
is evaluated for each candidate mask.
2. Style object
Unknown keys MUST be ignored. null is accepted where noted.
{
"margin": 4, // quiet zone, modules (>= 0)
"modules": {
"shape": "square", // see 5.2
"size": 1, // clamp to [0.1, 1]
"roundness": 1, // clamp to [0, 1]
"color": "#000000" // paint (2.1)
},
"eyes": {
"frame": "square", // 7x7 ring, see 6
"ball": "square", // 3x3 center
"frameColor": null, // paint or null = modules.color
"ballColor": null
},
"alignment": { // null or absent frame = draw as data modules
"frame": null,
"ball": null // null = "square" when frame is set
},
"background": { "color": "#ffffff", "radius": 0 }, // color: paint or null (none)
"border": { "width": 0, "color": "#000000" }, // color: solid only
"logo": { // null / absent = no logo
"href": null, // SVG <image> URL or data: URI
"size": 0.2, // fraction of the symbol width, clamp [0, 1]
"margin": 1, // clear space in modules (>= 0)
"shape": "square", // "square" | "rounded" | "circle"
"excavate": true, // remove modules under the logo area
"background": null // solid color or null
},
"label": { // null / absent / empty text = no label
"text": "SCAN ME",
"font": "sans-serif",
"size": 4, // font size in modules (min 0.1)
"color": "#000000",
"position": "bottom", // "bottom" | "top"
"bold": true
}
}
A document may wrap it: {"encode": {...}, "style": {...}, "svg": {...}}.
Top-level style keys outside "style" are also accepted.
2.0 Reading the JSON
Readers MUST behave as follows, because styles often come from untrusted sources (web forms, databases, devices on a network):
- Unknown keys of any length are ignored, whatever their value.
- Document order: members apply in order. A repeated key whose value is
an object (e.g. two
modulesobjects) merges field by field into what the earlier members set; scalars, arrays and paints replace the earlier value.logotakes its default size 0.2 only when no logo is present yet.logo: null,label: nullandalignment: nullreset that feature to its defaults (absent), forgetting every member set before. - Numbers parse to the nearest IEEE double. A number token longer than
400 characters, or one that is not finite (
1e999), is a parse error wherever it appears, unknown keys included. Integer fields (minVersion,maxVersion,mask) clamp to [-1000, 1000] and then truncate toward zero. - Nesting deeper than 64 objects/arrays (the top-level object is level 1) is a parse error.
- Strings:
\uXXXXescapes decode to UTF-8; a surrogate that is not part of a valid high/low pair becomes U+FFFD. A string ends at its first U+0000 (C strings), so"\u0000x"reads as""; this applies to every string value and key ("#f00\u0000x"is the color#f00). - Errors have three kinds:
parse error(reading the JSON),invalid argument(raised when encoding, for options out of range such asminVersion: 0) anddata too long.
The behaviour for JSON that is not well-formed is otherwise unspecified (the reference reader is lenient). Conformance vectors only use numbers with at most 15 significant digits and decimal exponents within ±22.
2.1 Colors and paints
Color strings: #rgb, #rrggbb, #rrggbbaa (case-insensitive) or
"transparent" (= #00000000). Internally 0xRRGGBBAA.
A paint is a color string, null (none), or a gradient:
{ "type": "linear", "angle": 45, // degrees, 0 = left→right, 90 = top→bottom
"stops": [ {"offset": 0, "color": "#6a11cb"}, {"offset": 1, "color": "#2575fc"} ] }
{ "type": "radial", "cx": 0.5, "cy": 0.5, "r": 0.75, // relative to the painted area
"stops": [ ... ] }
type defaults to "linear", angle to 0, cx/cy to 0.5, r to 0.75.
At most 8 stops are used. A gradient without stops paints nothing (the SVG
still writes the empty gradient element) and counts as a missing paint for
lint.
2.2 Clamping summary
clamp maps NaN to the lower bound (only reachable through styles built in
code; JSON numbers are finite).
| value | rule |
|---|---|
margin, border.width, logo.margin |
clamp(v, 0, 1000) |
modules.size |
clamp(v, 0.1, 1) |
modules.roundness |
clamp(v, 0, 1) |
background.radius |
clamp(v, 0, min(W, H) / 2) |
logo.size |
clamp(v, 0, 1); logo present iff size > 0 |
label.size |
clamp(v, 0.1, 1000) |
3. Layout
Let n be the modules per side, m the margin, b the border width.
W = n + 2(m + b) canvas width
H = W (+ bandH if label) canvas height
ox = m + b x of module column 0
oy = m + b (+ bandH if label at top)
Label band (only when label.text is non-empty): f = max(label.size, 0.1),
bandH = 1.5 f. Bottom: bandY = W - b. Top: bandY = b. Text anchor
x = W / 2, baseline y = bandY + 1.1 f.
Logo (when present): cx = ox + n/2, cy = oy + n/2,
half = n * size / 2 (image box), areaHalf = half + logo.margin.
4. Module classification
For module (x, y) with 0 <= x, y < n:
- finder:
(x<7 && y<7) || (x>=n-7 && y<7) || (x<7 && y>=n-7). - styled alignment: alignment patterns are styled when
alignment.frameis a valid shape. Then every module within ±2 of an alignment center that does not overlap a finder (the usual exclusion of the three corner positions) belongs to it. - excavated: logo present,
excavatetrue and the module is in the logo area:dx = |x + 0.5 - n/2|,dy = |y + 0.5 - n/2|,h = areaHalfcircle:max(dx-0.5,0)^2 + max(dy-0.5,0)^2 < h^2- otherwise:
dx < h + 0.5 && dy < h + 0.5
- data cell: in range and not finder, not styled alignment, not excavated.
- data dark
D(x,y): data cell and dark. Out of range is never dark.
Everything that is data dark is drawn with the module shape, including timing, format, version and (unstyled) alignment modules.
5. Module geometry
5.1 Corner rectangles
Every shape is built from a corner rectangle (x0, y0, x1, y1) with a
treatment per corner in the order TL, TR, BR, BL: none, round(a) or
chamfer(a). A corner with a <= 0 behaves as none.
Clockwise emission (a_k = amount, 0 for none):
move (x0 + a_TL, y0)
line (x1 - a_TR, y0); corner TR → (x1, y0 + a_TR) center (x1 - a_TR, y0 + a_TR)
line (x1, y1 - a_BR); corner BR → (x1 - a_BR, y1) center (x1 - a_BR, y1 - a_BR)
line (x0 + a_BL, y1); corner BL → (x0, y1 - a_BL) center (x0 + a_BL, y1 - a_BL)
line (x0, y0 + a_TL); corner TL → (x0 + a_TL, y0) center (x0 + a_TL, y0 + a_TL)
close
A round corner is a clockwise arc of radius a; a chamfer corner is a
line; none emits nothing. The counter-clockwise variant (used for holes)
is:
move (x0, y0 + a_TL)
line (x0, y1 - a_BL); corner BL → (x0 + a_BL, y1)
line (x1 - a_BR, y1); corner BR → (x1, y1 - a_BR)
line (x1, y0 + a_TR); corner TR → (x1 - a_TR, y0)
line (x0 + a_TL, y0); corner TL → (x0, y0 + a_TL)
close
with counter-clockwise arcs and the same centers.
5.2 Shapes
s = size, ρ = roundness, d = (1 - s) / 2. Cell-local coordinates
(cell = [0,1]², then translated by (ox + x, oy + y)).
| shape | geometry of a dark data module |
|---|---|
square |
rect [d, 1-d]², no corners |
dot |
rect [d, 1-d]², all round(s/2) |
rounded |
rect [d, 1-d]², all round(ρ s / 2) |
diamond |
rect [d, 1-d]², all chamfer(s/2) |
star |
see 5.3 with h = s/2 |
fluid |
rect [0,1]²; corner k is round(ρ/2) when both orthogonal neighbours touching that corner are not D (TL: up & left, TR: up & right, BR: down & right, BL: down & left) |
liquid |
as fluid, plus fillets (5.4) |
bevel |
as fluid with chamfer(ρ/2) |
classy |
as fluid but only TL and BR may be rounded |
hbars |
x0 = D(x-1,y) ? 0 : d, x1 = D(x+1,y) ? 1 : 1-d, y0 = d, y1 = 1-d; TL/BL round(ρ s/2) if left is not D, TR/BR if right is not D |
vbars |
transposed hbars |
fluid, liquid, bevel and classy ignore size.
5.3 Star
Center (cx, cy), tips at distance h, r = sqrt(2.5) * h, c = 1.5 * h:
move (cx, cy - h)
arc ccw r → (cx + h, cy) center (cx + c, cy - c)
arc ccw r → (cx, cy + h) center (cx + c, cy + c)
arc ccw r → (cx - h, cy) center (cx - c, cy + c)
arc ccw r → (cx, cy - h) center (cx - c, cy - c)
close
5.4 Liquid fillets
For a data cell that is not dark at (x, y) (canvas X = ox+x, Y = oy+y)
and ρ > 0, a fillet of radius r = ρ/2 is added at corner:
- TL if
D(x,y-1) && D(x-1,y) && D(x-1,y-1):move (X,Y) line (X+r,Y) arc ccw → (X, Y+r) close - TR if
D(x,y-1) && D(x+1,y) && D(x+1,y-1):move (X+1,Y) line (X+1,Y+r) arc ccw → (X+1-r, Y) close - BR if
D(x,y+1) && D(x+1,y) && D(x+1,y+1):move (X+1,Y+1) line (X+1-r,Y+1) arc ccw → (X+1, Y+1-r) close - BL if
D(x,y+1) && D(x-1,y) && D(x-1,y+1):move (X,Y+1) line (X,Y+1-r) arc ccw → (X+r, Y+1) close
Arc centers are the cell corner moved by r towards the cell center.
5.5 Emission order (modules layer)
squarewithsize == 1, andhbars: row by row (yascending), each maximal horizontal run[x0, x1)ofDis one corner rectangle[ox+x0+i, oy+y+i, ox+x1-i, oy+y+1-i]wherei = dforhbarsand0for squares;hbarscorners are allround(ρ s/2), squares none.vbars: column by column (xascending), vertical runs[y0, y1)→[ox+x+d, oy+y0+d, ox+x+1-d, oy+y1-d], allround(ρ s/2).- every other shape: row-major over all cells; a
Dcell emits its shape, otherwise (onlyliquid) a data cell emits its fillets in order TL, TR, BR, BL.
6. Eyes and alignment patterns
Shapes: square, rounded, circle, leaf, drop, bevel, diamond, dots.
For a box of size s whose corner cc faces the symbol center:
| shape | corners |
|---|---|
square |
none |
rounded |
all round(0.25 s) |
circle |
all round(s/2) |
leaf |
cc and the opposite corner round(0.36 s), others none |
drop |
all round(0.36 s) except cc |
bevel |
all chamfer(0.25 s) |
diamond |
all chamfer(s/2) |
dots |
frames only: every ring module is a circle ([cell], round(0.5)) |
A ball with shape dots is drawn as circle.
Finders (top-left corners in module space, cc): (0,0) BR, (n-7,0) BL,
(0,n-7) TR. Alignment patterns use cc = BR.
Frame (size 7, or 5 for alignment) at (X, Y): outer corner rectangle
[X, Y, X+s, Y+s] clockwise, then the inner one [X+1, Y+1, X+s-1, Y+s-1]
counter-clockwise with inner corners derived from the outer ones:
round: a-1, chamfer: a-(2-sqrt(2)), clamped to [0, (s-2)/2]; a clamped
value of 0 means none. dots frames emit circles for ring cells in
row-major order instead.
Ball (size 3 at (X+2, Y+2), or 1 at the alignment center): one clockwise
corner rectangle.
Layer eyeFrames emits the three finder frames (TL, TR, BL), then the
styled alignment frames in row-major order of their centers (y outer,
x inner, from the alignment position list). Layer eyeBalls likewise.
7. Path normalization
All layers go through the same emitter, which MUST:
- drop a
linewhose end equals the current point (exact float equality); - delay each
line; whenclosefollows and the pending line ends at the sub-path start, omit it; - update the current point after arcs to the arc end; after
closeto the sub-path start.
Fill rule: non-zero. Shapes are clockwise; holes counter-clockwise.
8. SVG serialization
8.1 Numbers
fmt(v): n = floor(v * 1000 + 0.5) (as a 64-bit integer)
sign "-" if n < 0, then |n| / 1000, then if |n| % 1000 != 0:
"." followed by the 3-digit fraction with trailing zeros removed
Examples: 0.0625 → "0.063", -0.0625 → "-0.062", -0.0001 → "0".
NaN and values with |v| >= 1e15 format as "0" (only reachable through
programmatic styles: JSON input is finite and lengths are clamped).
8.2 Path data
M{x} {y}; a line is H{x} if y equals the previous point’s y, else
V{y} if x equals the previous x, else L{x} {y};
A{r} {r} 0 0 {1|0} {x} {y} (1 = clockwise); Z. No separators between
commands.
8.3 Document
Elements, each followed by \n, in this order (bracketed parts optional):
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 {W} {H}"[ width="{w}" height="{w*H/W}"]>
[<defs>{gradients}</defs>]
[<rect width="{W}" height="{H}"[ rx="{radius}"]{fill(background,"bg")}/>]
[<path d="{border}"{fill(border)}/>]
[<path d="{modules}"{fill(modules,"fg")}/>]
<path d="{eyeFrames}"{fill(frameColor ?? modules, "ef" or "fg")}/>
<path d="{eyeBalls}"{fill(ballColor ?? modules, "eb" or "fg")}/>
[<path d="{logoBackground}"{fill(logo.background)}/>]
[<image x="{cx-half}" y="{cy-half}" width="{2half}" height="{2half}" preserveAspectRatio="xMidYMid meet" xlink:href="{href}"/>]
[<text x="{W/2}" y="{baseline}" text-anchor="middle" font-family="{font}" font-size="{f}"[ font-weight="bold"] fill="{#rrggbb}"[ fill-opacity="{a}"]>{text}</text>]
</svg>
width/heightonly whensvg.width > 0.- A
<path>element is omitted when its layer emits no sub-path. - The background
<rect>is written when the paint is a gradient or a solid color with alpha > 0.rxonly when the clamped radius is > 0. - Border: outer canvas rectangle (all corners
round(radius)if radius > 0) clockwise + inner rectangle inset byb(radiusradius - bif > 0) counter-clockwise. Only whenb > 0. - Logo background: only when logo present and its alpha > 0. Rectangle
[cx-h, cy-h, cx+h, cy+h]withh = areaHalf;square: none,rounded: allround(0.3 h),circle: allround(h). <image>only when the logo is present andhrefis non-empty.<text>only when the label text is non-empty.fontempty/null →sans-serif.- Text escaping (href, font, label text):
&→&,<→<,>→>,"→"; characters U+0001–U+001F other than tab, LF and CR are dropped (XML 1.0 does not allow them).
Id prefix. prefix is svg.idPrefix sanitized: every byte of its UTF-8
encoding outside [A-Za-z0-9_-] becomes _; when it starts with a digit or
-, q is prepended; an empty or absent prefix is qrk. It is written
inside id="..." and url(#...), so it can never carry markup (a style
from an untrusted source must not be able to inject attributes or elements).
Gradient ids are document-global in HTML: when several SVGs are inlined in
one page, give each a distinct svg.idPrefix.
fill(paint, id): solid → fill="#rrggbb" (lower-case) plus
fill-opacity="{fmt(a/255)}" when alpha ≠ 255; gradient →
fill="url(#{prefix}-{id})"; none (e.g. "modules": {"color": null}) →
fill="none".
8.4 Gradients
<defs> is written when any of background, modules, frameColor, ballColor is
a gradient, containing in that order the gradients with ids bg, fg, ef,
eb:
<linearGradient id="{prefix}-{id}" gradientUnits="userSpaceOnUse" x1="" y1="" x2="" y2="">{stops}</linearGradient>
<radialGradient id="{prefix}-{id}" gradientUnits="userSpaceOnUse" cx="" cy="" r="">{stops}</radialGradient>
<stop offset="{o}" stop-color="#rrggbb"[ stop-opacity="{a}"]/>
Painted area: background → (0, 0, W, H), everything else →
(ox, oy, n, n). With area (ax, ay, aw, ah) and center (cx, cy):
- linear:
a = angle mod 360(into[0, 360)); direction(dx, dy)is exactly(1,0),(0,1),(-1,0),(0,-1)for 0/90/180/270, otherwise(cos, sin)ofa·π/180.half = (|dx| aw + |dy| ah) / 2;x1 = cx - dx·half,y1 = cy - dy·half,x2 = cx + dx·half,y2 = cy + dy·half. - radial:
cx' = ax + aw·cx,cy' = ay + ah·cy,r' = r·max(aw, ah).
8.5 Layer visibility
Ports that expose layerVisible(layer) (C: qrk_layer_visible) return true
when the layer’s resolved paint has at least one color with alpha > 0 (a
solid color, or any gradient stop), and additionally: border → width > 0;
logo background → a logo is present; modules → at least one sub-path. It is
a hint for native renderers that skip invisible layers; the SVG writer keeps
its own rules above.
9. Raster output (informative)
Raster renderers SHOULD draw exactly the SVG geometry (the reference rasterizer point-samples the same shape model with supersampling and matches cairo/librsvg output). Layers composite in SVG order with source-over. Gradient colors interpolate per channel in sRGB, unpremultiplied, clamped outside the first/last stop. Labels need a font engine and MAY be omitted by raster backends without one (the band stays background-colored). Backends that draw labels SHOULD follow SVG 1.1 whitespace handling (remove newlines, turn tabs into spaces, collapse runs of spaces). Raster outputs SHOULD refuse images larger than 16384 px on either side, since an untrusted style can request very tall canvases (label size up to 1000 modules).
10. Logo damage
Estimates whether a logo still allows decoding. Walk the codeword placement
order (zig-zag, skipping function modules, ignoring remainder bits). A
codeword is hidden when any of its 8 modules is in the logo area (section
4, regardless of excavate). Map the interleaved index k to its block:
data part (k < dataLen): k % numBlocks while k < shortLen·numBlocks,
else numShort + (k - shortLen·numBlocks); ECC part: (k - dataLen) % numBlocks.
logoDamage = max over blocks of hidden / floor(eccPerBlock / 2);
0 without logo. Above 1 the symbol cannot be decoded.
11. Lint
lint(code, style) returns the set of:
| flag | condition |
|---|---|
logo-risky |
logo damage in (0.7, 1] |
logo-unreadable |
logo damage > 1 |
low-contrast |
contrast < 3 (see below) |
inverted |
mean foreground luminance > mean background luminance |
transparent-background |
background is none or solid with alpha 0 (treated as white below) |
quiet-zone |
clamped margin < 2 |
small-modules |
size < 0.6 for square, dot, rounded, diamond, star, hbars, vbars |
eye-shapes |
eye frame is diamond, or the eye ball (after the dots→circle rule) is diamond and the frame is square or leaf |
invisible |
the resolved modules, eye frame or eye ball paint is none, or all of its colors have alpha 0 |
Colors of a paint: a solid paint has its color, a gradient its stop
colors; a missing paint is one fully transparent color (#00000000).
Background: its colors, each composited over white
(channel = floor(c·a + 255·(1 − a) + 0.5) per 8-bit channel with a = alpha / 255, result alpha 255). It is
transparent-background when every original color has alpha 0.
Foreground: for every color f of the resolved modules, eye frame and eye
ball paints and every background color b, the luminance of f composited
over b (in 0..1 floating point, f·a + b·(1 − a) per channel, not rounded).
Luminance is the WCAG relative luminance. [bgLo, bgHi] and [fgLo, fgHi]
are the ranges of those values; means are (lo + hi) / 2. Contrast =
(bgLo + 0.05) / (fgHi + 0.05), or (fgLo + 0.05) / (bgHi + 0.05) when
inverted. Vectors list flags in the order of the table.
12. Payloads
| builder | output |
|---|---|
wifi(ssid, password, auth, hidden) |
WIFI: + (T:{auth}; if auth) + S:{esc ssid}; + (P:{esc password}; if password and auth ≠ nopass) + (H:true; if hidden) + ; — esc escapes \ ; , : " with \ |
email(to, subject, body) |
mailto:{to} + ?subject= / &body= (only non-empty, in that order, percent-encoded: keep A-Z a-z 0-9 - _ . ~, others %XX upper-case per UTF-8 byte) |
sms(number, message) |
SMSTO:{number} + (:{message} if message) |
tel(number) |
tel:{number} |
geo(lat, lon) |
geo:{f6(lat)},{f6(lon)} — latitude clamped to [-90, 90], longitude to [-180, 180]; f6 is fmt with 6 decimals |
vcard(...) |
BEGIN:VCARD\nVERSION:3.0\nN:{last};{first};;;\nFN:{first}[ ]{last}\n + optional ORG:, TITLE:, TEL:, EMAIL:, URL:, ADR:;;{address};;;;, NOTE: lines + END:VCARD. Values escape \ ; , with \ and newline as \n |
event(...) |
BEGIN:VEVENT\nSUMMARY:{summary}\n + optional DTSTART:, DTEND:, LOCATION:, DESCRIPTION: lines + END:VEVENT (same escaping) |
“Optional” means omitted when null or empty. No trailing newline.
13. Conformance vectors
spec/vectors/encode.json:{text, encode, version, ecc, mask, mode, matrix[]}(rows of0/1), or{text, encode, error: "data too long"}.spec/vectors/render.json:{name, text, style, encode?, svg?, version, ecc, mask, lint[], logoDamage?, output}—outputis the exact SVG. Cases with ajsonstring instead ofstyle/encode/svgmust be read from that raw text (they test duplicate keys, limits and errors); cases witherror(parse error,invalid argument) must fail with that kind of error.spec/vectors/payloads.json:{type, args, output}.
Regenerate with make -C core && python3 spec/tools/gen_vectors.py.
This page is docs/SPEC.md in the QRKude repository.