# Machine-Readable Code Reader 1.0 — C++ and C ABI

> One call reads DataMatrix, GS1-128 (SSCC), Code 128, EAN-13, EAN-8, UPC-A and UPC-E in a frame — Chestny ZNAK marking, logistics labels and retail barcodes.

Source page: https://libscanner.com/en/docs/universal-reader/1.0

## Overview

The library reads codes of several symbologies in a frame with a single call: **DataMatrix (ECC200)** — Chestny ZNAK marking codes (the Russian national product marking system), **GS1-128** — SSCC on logistics labels, **Code 128**, and retail barcodes **EAN-13, EAN-8, UPC-A and UPC-E**. Accuracy and read speed are shown at the top of the page; below is the interface only.

The library is static; the public interface is C++20, eight headers in `include/unir/`, plus a flat C interface for other languages (`unir_c.h`, see C ABI). A frame is passed as a view over a foreign buffer (`unir::ImageView`) — no copying, straight from the camera buffer or image you already have.

## Installation

The library ships prebuilt — you do not build it, you only build your own program. There are two ways to use it: **statically** — the C++ API from `libunir.a`, together with both engines, is linked into your program in full, or **dynamically** — through the `unir_c` library with a flat C interface (C ABI). Packages are available for Windows x64 and Linux (x86-64, ARM64 and ARMv7), with the same contents:

**Windows:**

```
include\unir\{Unir,Geometry,Image,License,Scanner,Settings,Symbology,Version}.h   C++ API
include\unir\unir_c.h                                                               C ABI
lib\libunir.a                C++ static library (with the dmr and ssccr engines)
lib\pkgconfig\unir.pc
bin\unir_c.dll               C ABI library
lib\unir_c.dll.a             its import library (MinGW)
lib\pkgconfig\unir_c.pc
bin\unir.exe                 command-line utility
share\doc\unir\licenses\      third-party licenses
```

**Linux:**

```
include/unir/{Unir,Geometry,Image,License,Scanner,Settings,Symbology,Version}.h   C++ API
include/unir/unir_c.h                                                             C ABI
lib/libunir.a                 C++ static library (with the dmr and ssccr engines)
lib/pkgconfig/unir.pc
lib/libunir_c.so              C ABI library
lib/pkgconfig/unir_c.pc
bin/unir                      command-line utility
share/doc/unir/licenses/      third-party licenses
```

In “Downloads” in your account there are two archives per platform: `unir-1.0.1-<platform>.zip` (Windows) or `.tar.gz` (Linux) — the full package with libraries and headers, and `unir-1.0.1-<platform>-cli` with the same extension — the `unir` utility only, for checks without programming. Platforms: `windows-x64`, `linux-x64`, `linux-arm64` and `linux-armhf` — the latter is ARMv7, a 32-bit OS (for example, a Raspberry Pi running a 32-bit system). An archive unpacks into a directory of the same name, for example `unir-1.0.1-linux-x64/`.

| Your program | What to use |
| --- | --- |
| C++, built with MinGW-w64 GCC (Windows) or GCC (Linux) | Static linking — C++ API, `libunir.a` |
| MSVC or another compiler; another language (C, C#, Delphi, Go…); updating the library without rebuilding the program | Dynamic linking — C ABI, `unir_c` |
| Python | `pip install libscanner-unir` or the wheel from “Downloads” — [Python documentation](https://libscanner.com/en/docs/universal-reader/1.0/python.md) |

### Static linking — C++ API

`libunir.a` is linked into your program in full: nothing needs to sit next to it except the key file `unir.key`, which goes next to the program (see “License”). A **C++20** compiler is required. The API is C++ and returns `std::string` and `std::vector`, so the compiler and its standard library must be the same as the package's (the same or a newer version).

**Windows:**

> **MinGW-w64 GCC required, not MSVC.** The Windows package is built with MinGW-w64 GCC — `libunir.a` is in its format and with its ABI. MSVC cannot link this static archive; your program needs the same toolchain (for example, MSYS2/MinGW or CLion with a MinGW profile). From MSVC, link dynamically, via unir_c.
>
> The toolchain is free: [MSYS2](https://www.msys2.org/) — run the installer from the website, then in its **MINGW64** shell (the package is built in exactly this environment, not UCRT64):
>
> ```bash
> pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-pkgconf
> ```

Include paths and the link line come from `unir.pc`. Point an environment variable (PowerShell) at `lib\pkgconfig` of the unpacked package; paths in the `.pc` are relative to the file itself, so the unpack directory can be anywhere:

```bash
$env:PKG_CONFIG_PATH = "C:\path\to\unpacked\lib\pkgconfig"
pkg-config --static --cflags --libs unir
```

The system libraries `winhttp`, `crypt32`, `shell32` and `advapi32` (used by the license check) are already listed in the package's `Libs.private` — no need to add them separately.

**Linux:**

The package is built with GCC 12 (Debian 12, glibc 2.36): GCC 12 or newer is required. Include paths and the link line come from `unir.pc`; point an environment variable at `lib/pkgconfig` of the unpacked package — the unpack directory can be anywhere:

```bash
PKG_CONFIG_PATH=/path/to/unpacked/lib/pkgconfig pkg-config --static --cflags --libs unir
```

`-lpthread -ldl` are required — they are already in the package. The `.pc` does not add `-std=c++20`: pass it yourself (GCC 12 defaults to C++17). Without pkg-config the same line by hand:

```bash
g++ -std=c++20 main.cpp -o myprogram -I/path/to/unpacked/include /path/to/unpacked/lib/libunir.a -pthread -ldl
```

OpenSSL is not a link dependency: for the license server's `https://` the library loads `libssl.so.3` (or `libssl.so.1.1`) at run time.

> **The pkg-config query must be static.** The package is a single static library, so the caller also needs the libraries it depends on. Without `--static` they will not be on the command line, and linking will fail with unresolved symbols.

In meson — a dependency by package name:

```cpp
unir_dep = dependency('unir', required: true, static: true)
executable('myprogram', 'main.cpp', dependencies: [unir_dep],
  link_args: ['-static'])   # Windows: MinGW runtime linked into the program
```

On Windows, `-static` also links the compiler runtime into the program: without it, the program will need `libstdc++-6.dll`, `libgcc_s_seh-1.dll` and `libwinpthread-1.dll` from MSYS2 next to it. On Linux it is not needed. A complete `meson.build` is in “Quick start”.

### Dynamic linking — C ABI

`unir_c` is the same library behind a flat C interface (header `unir/unir_c.h`): `unir_*` functions and opaque pointers, no C++ types. The compiler runtime is linked into it statically, so it depends neither on the caller's compiler nor on its C++ standard library — it works with MSVC, Clang and any language that can call C functions. Everything the library returns (results, images, strings) is freed by its own functions: `unir_scan_result_free`, `unir_image_free`, `unir_free_string`.

The rules of the interface and every function — in C ABI.

**Windows:**

**MinGW-w64 GCC** — via `unir_c.pc` and the import library `lib\unir_c.dll.a`, without `--static`:

```bash
$env:PKG_CONFIG_PATH = "C:\path\to\unpacked\lib\pkgconfig"
pkg-config --cflags --libs unir_c
```

This yields `-I…\include -L…\lib -lunir_c`.

**MSVC** — the package has no `.lib` file; you generate it from the DLL itself. In the MSYS2 shell, extract the export definitions:

```bash
gendef unir_c.dll         # package mingw-w64-x86_64-tools; writes unir_c.def
```

then, in the Developer Command Prompt for VS (x64), create the import library and build:

```bash
lib /def:unir_c.def /machine:x64 /out:unir_c.lib
cl /I C:\path\to\unpacked\include main.c unir_c.lib
```

At run time `unir_c.dll` must be next to your `.exe` (or in a directory on `PATH`), and the key file `unir.key` goes next to `unir_c.dll` or next to the `.exe` (see “License”).

**Linux:**

```bash
PKG_CONFIG_PATH=/path/to/unpacked/lib/pkgconfig pkg-config --cflags --libs unir_c
```

This yields `-I…/include -L…/lib -lunir_c`. At run time the loader must find `libunir_c.so`: either via `LD_LIBRARY_PATH`, or put it next to the program and set the search path at link time:

```bash
LD_LIBRARY_PATH=/path/to/unpacked/lib ./myprogram

# or: libunir_c.so next to the program
cc main.c -o myprogram $(pkg-config --cflags --libs unir_c) -Wl,-rpath,'$ORIGIN'
cp /path/to/unpacked/lib/libunir_c.so .
```

The key file `unir.key` goes next to `libunir_c.so` or next to the program (see “License”).

In meson — likewise by package name, without `static`:

```cpp
unir_c_dep = dependency('unir_c', required: true)
executable('myprogram', 'main.c', dependencies: [unir_c_dep])
```

## Quick start

A minimal program: load an image from disk, read it, print the symbology and the text. All you need is one header:

```cpp
#include <unir/Unir.h>
#include <iostream>

int main() {
    unir::Scanner scanner;                 // frame stream, all symbologies of the build, one code

    unir::LoadStatus load = unir::LoadStatus::Ok;
    const unir::ScanResult r = scanner.scanFile("frame.jpg", &load);   // loading and reading under one time limit
    if (load != unir::LoadStatus::Ok) {
        std::cerr << "could not read the file\n";
        return 1;
    }

    if (r.ok()) {
        for (const unir::Code& c : r.codes)
            std::cout << unir::nameOf(c.symbology) << ": " << c.text << "\n";
    } else if (r.status == unir::Status::NotFound || r.status == unir::Status::NotDecoded) {
        std::cout << "no code read\n";   // NotDecoded also means "no code"
    } else {
        std::cout << "frame not read, status " << static_cast<int>(r.status) << "\n";
    }
}
```

The example can be built with Meson. Next to `main.cpp` put the unpacked package (`unir-1.0.1-windows-x64` or `unir-1.0.1-linux-x64`) and a `meson.build` file:

**Windows:**

```cpp
project('test_unir', 'cpp',
  version : '0.1.0',
  default_options : ['cpp_std=c++20', 'warning_level=3', 'buildtype=release'])

cxx = meson.get_compiler('cpp')

# The prebuilt unir sits next to the project; the dependency is described by
# hand, so pkg-config is not needed at all.
unir_root = meson.current_source_dir() / 'unir-1.0.1-windows-x64'

unir_lib = cxx.find_library('unir', dirs : unir_root / 'lib', static : true)

# Libs.private from unir.pc — required for static linking
unir_private = []
foreach l : ['winhttp', 'crypt32', 'shell32', 'advapi32']
  unir_private += cxx.find_library(l)
endforeach

unir_dep = declare_dependency(
  include_directories : include_directories('unir-1.0.1-windows-x64/include'),
  dependencies : [unir_lib] + unir_private)

executable('test_unir', 'main.cpp',
  dependencies : unir_dep,
  cpp_args : ['-finput-charset=UTF-8', '-fexec-charset=UTF-8'],
  link_args : ['-static'])
```

Build in the MSYS2 MINGW64 shell:

```bash
meson setup build
meson compile -C build
```

The same with pkg-config: the package's `.pc` files are relocatable, so instead of the manual description one line in `meson.build` is enough — `unir_dep = dependency('unir', static : true)` — plus the path to the unpacked `lib/pkgconfig` at configure time:

```bash
meson setup build --pkg-config-path="$PWD/unir-1.0.1-windows-x64/lib/pkgconfig"
meson compile -C build
```

**Linux:**

```cpp
project('test_unir', 'cpp',
  version : '0.1.0',
  default_options : ['cpp_std=c++20', 'warning_level=3', 'buildtype=release'])

cxx = meson.get_compiler('cpp')

unir_root = meson.current_source_dir() / 'unir-1.0.1-linux-x64'   # or linux-arm64, linux-armhf

unir_lib = cxx.find_library('unir', dirs : unir_root / 'lib', static : true)

# Libs.private from unir.pc: -lpthread -ldl
unir_dep = declare_dependency(
  include_directories : include_directories('unir-1.0.1-linux-x64/include'),
  dependencies : [unir_lib, dependency('threads'), cxx.find_library('dl')])

executable('test_unir', 'main.cpp', dependencies : unir_dep)
```

GCC 12 or newer and Meson are required; `-static` is not needed on Linux.

```bash
meson setup build
meson compile -C build
```

Or with pkg-config — `unir_dep = dependency('unir', static : true)` and:

```bash
meson setup build --pkg-config-path="$PWD/unir-1.0.1-linux-x64/lib/pkgconfig"
meson compile -C build
```

For a frame from your own capture pipeline (camera SDK, OpenCV buffer, Windows bitmap) the disk is not involved — you build a view over the buffer you already have, without copying:

```cpp
const unir::ImageView view{ m.data, m.cols, m.rows, (int)m.step, unir::PixelFormat::Bgr8 };
const unir::ScanResult r = scanner.scan(view);
```

## Headers

There are seven public C++ headers, all under `include/unir/`; the eighth, `unir/Unir.h`, includes them all at once. A ninth file, `unir/unir_c.h`, is a separate C interface (C ABI). The headers of the dmr and ssccr engines are not shipped: their types never appear in the interface.

| Header | Contents |
| --- | --- |
| `unir/Unir.h` | Umbrella header — includes everything below |
| `unir/Image.h` | `ImageView`, `Image`, `PixelFormat`, `ImageLimits`, `loadImage` |
| `unir/Scanner.h` | `Scanner`, `ScanResult`, `Code`, `Status` |
| `unir/Settings.h` | Scanner settings and presets |
| `unir/Symbology.h` | `Symbology`, the `Symbologies` mask, names and AIM prefixes |
| `unir/Geometry.h` | `Point`, `Box`, `Quad` — where the code is located in the frame |
| `unir/License.h` | License check and activation, the `unir::license` namespace |
| `unir/Version.h` | Version of the header (macros), of the built library and of the engines inside it |

The headers pull in nothing extra: code that only builds a frame view needs just `Image.h`, and `Symbology.h` needs only `<cstdint>` and `<string>`.

## Scanner

`Scanner` is an object, not a function, and there is no need to create one per frame. The constructor assembles the chain of engines for the requested symbologies and also performs the first license check — if a not-yet-activated `unir.key` lies nearby, the constructor waits for the server once, so the first frame does not pay that delay. In stream mode (`Settings::stream()`) the DataMatrix engine accumulates a symbol size hint across frames, and the hint has to outlive each frame.

```cpp
class Scanner {
public:
    explicit Scanner(Settings s = Settings::stream());
    ~Scanner();

    Scanner(Scanner&&) noexcept;
    Scanner& operator=(Scanner&&) noexcept;
    Scanner(const Scanner&)            = delete;
    Scanner& operator=(const Scanner&) = delete;

    // Read a frame. Not thread-safe on a single Scanner in Stream mode
    // (the dmr hint changes from frame to frame); for several threads,
    // use one Scanner per thread.
    ScanResult scan(ImageView frame) const;

    // An image from disk and its reading under ONE frame time limit
    // (loading is included). load — what happened when reading the file
    // (may be nullptr).
    ScanResult scanFile(const std::string& path, LoadStatus* load = nullptr) const;

    const Settings& settings() const;
};

// One-off read without an object: a probe, a script, a single file.
ScanResult scan(ImageView frame, const Settings& s = Settings::single());
```

> **One Scanner per thread.** `scan()` is declared `const`, but in `Stream` mode it updates the DataMatrix size hint from frame to frame. If frames come from several threads (several cameras, a worker pool), create a separate `Scanner` for each thread rather than sharing one.

`scanFile()` reads an image from disk and the codes in it under a common time limit: loading counts against the same `budgetMs` / `timeoutMs` as reading, so a heavy file does not get a second budget. The image is loaded as grayscale (`LoadAs::Gray`) with the limits from `Settings::imageLimits`. If the file fails to load, `*load` receives the reason (see “Frame”), and the result status is `ImageTooLarge` for an oversized image and `InvalidImage` for every other failure; if the time limit cut decoding short, `timedOut` is set as well. Without a license the file is not even opened: `*load` stays `Ok`, the status is `Unlicensed`.

`LoadStatus` in C++ has four values, and there is no separate “ran out of time” among them: decoding cut short by the time limit is `Unreadable` together with `timedOut = true`. In the C ABI and in Python the same case comes as a separate value, `TIMED_OUT`; one more value there, `INVALID_ARGUMENT` (settings rejected by the ABI boundary), is not needed in C++ — the `Settings` type cannot be malformed here.

| What happened to the file | `*load` | `status`, `timedOut` | C ABI / Python |
| --- | --- | --- | --- |
| Read | `Ok` | outcome of the scan | `UNIR_LOAD_OK` / `OK` |
| No such file | `NotFound` | `InvalidImage`, `false` | `UNIR_LOAD_NOT_FOUND` / `NOT_FOUND` |
| Not JPEG/PNG, or damaged | `Unreadable` | `InvalidImage`, `false` | `UNIR_LOAD_UNREADABLE` / `UNREADABLE` |
| Decoding did not fit into the time limit | `Unreadable` | `InvalidImage`, `true` | `UNIR_LOAD_TIMED_OUT` / `TIMED_OUT` |
| Exceeds the limits according to the header | `TooLarge` | `ImageTooLarge`, `false` | `UNIR_LOAD_TOO_LARGE` / `TOO_LARGE` |
| No license — the file was not opened | `Ok` | `Unlicensed`, `false` | `UNIR_LOAD_OK` / `OK` |

> **Errors are returned, not thrown.** `scan()` and `scanFile()` catch exceptions internally: an internal failure (for example, out of memory) becomes `Status::Failed` if no code has been read by then, and the process keeps running. `loadImage()` reports problems through `LoadStatus`.

> **Move-only.** `Scanner` can be moved but not copied — the copy constructor and copy assignment are explicitly deleted.

## Scan result

### Status

```cpp
// The numbers are the same as in dmr and ssccr. The exit codes of the unir utility are the
// same numbers, except for file load failures (see "Command line").
enum class Status {
    Ok            = 0,  // at least one code read
    NotFound      = 1,  // no symbol found
    NotDecoded    = 2,  // symbol found but not read
    Unlicensed    = 3,  // no unir license, trial period over; the frame was not processed
    ImageTooLarge = 4,  // frame larger than Settings::imageLimits, not processed
    InvalidImage  = 5,  // inconsistent frame description (row length, format)
    Failed        = 6,  // processing aborted by an internal error; the process lives on
};
```

`Ok` comes back if and only if `codes` holds at least one code — from any engine. If none of them read anything, the status is the weightiest of their answers: `Failed`, then `InvalidImage`, `ImageTooLarge`, `NotDecoded` and finally `NotFound`. This way a DataMatrix that was found but not read is not hidden behind a “no barcode” from the linear engine. With `Status::Unlicensed` the frame was not processed at all — the reason is given by `unir::license::status()` (see “License”).

### What NotDecoded really means

`NotDecoded` does not mean “there definitely is a code”. It means that at least one engine in the mask found a *candidate* in the frame that looks like a symbol of its symbology, and could not read it. A candidate is not yet a symbol. The DataMatrix engine takes as a candidate anything that resembles a solid L finder pattern or a texture of modules — the bars of a linear code, printed text or a packaging pattern can all qualify. The linear engine flags a run of bars that partly matched the Code 128 or EAN/UPC pattern but failed acceptance. That is why status 2 is common on frames that contain no code of that symbology at all: a DataMatrix-only mask on a frame with a single GS1-128, an EAN/UPC-only mask on a frame with a single DataMatrix.

The status is computed only from the engines in the mask: a code of a symbology outside the mask is not searched for, and its bars or modules can become a candidate for another engine — hence `NotDecoded` rather than `NotFound`. A linear code that the engine did read but the mask does not admit (an SSCC when the mask holds only `Code128`) is discarded and does not produce `Ok` either.

Treat `NotDecoded` as “no code was read”: on a conveyor, take the next frame; for a one-off image, ask for a new shot or retry with a longer time limit. Do not use it as a sign that “the code is there but bad” (for rejecting products). If `timedOut` is set together with `NotFound` or `NotDecoded`, the answer is not final: the time limit cut the search short, and with more time the code might have been read.

For your own program the statuses boil down to three outcomes:

| Status | Outcome | What to do |
| --- | --- | --- |
| `Ok` | Code present | Use `codes`. With `timedOut`, some symbologies or codes may have gone unchecked |
| `NotFound` | No code | Next frame. With `timedOut` — not final |
| `NotDecoded` | No code (not read) | Next frame or a new shot; do not assume a code is present |
| `Unlicensed` | Error | No license: show `license::status().message`; retrying the frame is pointless until activation |
| `ImageTooLarge` | Input error | Downscale the frame or raise `imageLimits` |
| `InvalidImage` | Input error | From `scan()` — a mistake in the frame description (your code); from `scanFile()` — the file was not read, the reason is in `*load` |
| `Failed` | Error | Internal failure (for example, out of memory); the frame can be retried |

### ScanResult

| Field | Type | Meaning |
| --- | --- | --- |
| `status` | `Status` | How processing of the frame ended |
| `codes` | `std::vector<Code>` | The codes read, in reading order (see “Symbologies”). Empty if `status` is not `Ok`. No longer than `Settings::maxCodes` when that is greater than zero |
| `timedOut` | `bool` | At least one engine was cut short by the frame time limit or never got to start. Success with the flag set is possible: a code was read, but some symbologies or hypotheses were not checked |
| `elapsedMs` | `double` | How long the call took, ms. For `scanFile()` this includes loading the image |

Methods: `bool ok() const` — shorthand for `status == Status::Ok`; `const std::string& text() const` — the text of the first code or an empty string.

### Code

| Field | Type | Meaning |
| --- | --- | --- |
| `symbology` | `Symbology` | The code's symbology — exactly one bit of the mask (see “Symbologies”) |
| `text` | `std::string` | The symbol data, bytes as they are. A leading FNC1 (the GS1 flag) is not passed on; an FNC1 inside is the GS separator, byte `0x1D`, not its printable form |
| `box` | `Box` | Where the symbol was found in the original frame — a rectangle aligned with the frame axes |
| `corners` | `Quad` | The symbol's four corners in the same frame, more precise than the box; the meaning of the indices depends on the symbology (see “Geometry”) |
| `moduleCount` | `int` | DataMatrix: modules per symbol side; 0 — unknown or not a DataMatrix |
| `modulePx` | `float` | Linear symbologies: module width in pixels; 0 — unknown or not a linear symbology |

### Text encoding

`Code::text` holds the bytes the symbol carries; the library neither re-encodes them nor guesses their encoding.

- **DataMatrix.** Bytes 0x00–0x7F are ASCII. Bytes 0x80–0xFF (Upper Shift, Base 256 mode) are returned as they are. By the DataMatrix standard such bytes default to ISO 8859-1 (Latin-1), but many generators put UTF-8 text into Base 256, so the decision is yours. ECI is not supported: an ECI switch is not interpreted, and the text of a symbol with ECI may contain service bytes; macros 05/06 are not expanded into the `[)>` header, and Structured Append is not assembled — each symbol is read on its own. Chestny ZNAK codes and GS1 DataMatrix use none of this: they contain only ASCII and the `0x1D` separator.
- **Code 128 and GS1-128.** Code sets A, B and C plus SHIFT; the text is always 7-bit, 0x00–0x7F (code set A also yields control characters 0x00–0x1F). FNC2, FNC3 and FNC4 are not supported: a symbol containing them is not read — extended Latin-1 characters via FNC4 never appear in the text.
- **EAN/UPC** — digits only.

For display where UTF-8 is required: the text of linear symbologies is already ASCII; check DataMatrix text for valid UTF-8 and, if it is not, convert it from Latin-1 (each byte 0x80–0xFF is one character U+0080–U+00FF).

### Multiple codes per frame

```cpp
const unir::Scanner scanner(unir::Settings::multi(0));   // 0 — as many as found
for (const unir::Code& c : scanner.scan(frame).codes)
    use(c.symbology, c.text, c.box);
```

With one code (`maxCodes = 1`, the default) processing stops at the first code read — of any symbology in the mask. If a frame has a DataMatrix and an SSCC side by side and you need both, use `multi()`; if you need only one of them, narrow the mask (see “Symbologies”).

## Symbologies

Each symbology is a separate bit: `Settings::symbologies` is a mask of them — what to look for — and `Code::symbology` is exactly one bit — what was read.

```cpp
enum class Symbology : uint32_t {
    None       = 0,
    DataMatrix = 1u << 0,  // ECC200, ISO/IEC 16022 (dmr engine)
    Code128    = 1u << 1,  // Code 128 without the GS1 flag (ssccr engine)
    Gs1_128    = 1u << 2,  // GS1-128: Code 128 with a leading FNC1 (ssccr engine)
    Ean13      = 1u << 3,  // EAN/UPC — ssccr engine
    Ean8       = 1u << 4,
    UpcA       = 1u << 5,
    UpcE       = 1u << 6,
};

using Symbologies = uint32_t;   // symbology mask

constexpr Symbologies operator|(Symbology a, Symbology b);
constexpr Symbologies operator|(Symbologies a, Symbology b);
constexpr bool has(Symbologies mask, Symbology s);

constexpr Symbologies kEanUpc         = Ean13 | Ean8 | UpcA | UpcE;
constexpr Symbologies kAllSymbologies = DataMatrix | Code128 | Gs1_128 | kEanUpc;   // every symbology of this build
```

```cpp
auto s = unir::Settings::stream();
s.symbologies = unir::Symbology::DataMatrix | unir::Symbology::Gs1_128;   // marking codes and SSCC
const unir::Scanner scanner(s);
```

| Symbology | What is read |
| --- | --- |
| `DataMatrix` | DataMatrix ECC200 of any structure — by the same engine and with the same answers as DataMatrix Reader |
| `Gs1_128` | Code 128 with a leading FNC1. Without `Code128` in the mask only SSCC (AI 00) is read; together with `Code128`, other GS1-128 as well |
| `Code128` | Code 128 without the GS1 flag. Acceptance is stricter than for SSCC: at least two scan lines must agree. Without `Gs1_128` in the mask, SSCC and other GS1-128 are discarded |
| `Ean13`, `Ean8`, `UpcA`, `UpcE` | Acceptance requires two agreeing scan lines and quiet zones. UPC-A is 12 digits (an EAN-13 whose first digit is 0), UPC-E is 8 digits (number system, 6 digits, check digit), not expanded to UPC-A. The engine reads all four together, but only those in the mask are returned |

> **The default is every symbology of the build.** By default `Settings::symbologies` is `kAllSymbologies`, i.e. everything this build can read. Future versions will add QR and other symbologies, and a program relying on the default will start looking for them after an update — and spend frame time on them. If you need a fixed set, set the mask explicitly.

### Names and AIM prefixes

```cpp
// Short name: "datamatrix", "code128", "gs1-128", "ean-13", "ean-8",
// "upc-a", "upc-e"; None — "none".
const char* nameOf(Symbology s);

// AIM symbology identifier (ISO/IEC 15424), without the data modifier:
// "]d", "]C", "]E".
const char* aimPrefixOf(Symbology s);

// Mask from a comma-separated list of names: "dm,128,gs1-128", "all".
// An unknown name returns false and leaves mask unchanged.
bool parseSymbologies(const std::string& list, Symbologies& mask);
```

`parseSymbologies` accepts, case-insensitively: `datamatrix` | `dm`, `code128` | `128`, `gs1-128` | `gs1` | `sscc`, `ean-13` | `ean13`, `ean-8` | `ean8`, `upc-a` | `upca`, `upc-e` | `upce`, `ean` — all four EAN/UPC, and `all`. An empty string counts as an unknown name too. This is handy when the set of symbologies comes from a configuration file or the command line — the utility's `--sym` option uses the same parser.

`aimPrefixOf` returns the symbology identifier without a modifier: `]d` for DataMatrix, `]C` for Code 128 and GS1-128, `]E` for EAN/UPC. In this version the GS1 flag (FNC1) of a DataMatrix is not reported, so there is no modifier such as `]d2`: tell GS1-structured DataMatrix apart by its content. The modifier will be added once the engines start reporting the FNC1 flag.

### Processing order and code order

The engines are called one after another, and the order depends on how many codes you ask for:

- **One code** (`maxCodes = 1`): first a fast pass of the linear engine — SSCC and EAN/UPC, then DataMatrix, then other Code 128 (and other GS1-128), which is the most expensive on a frame without a barcode and therefore goes last. The first code read is the answer: the remaining engines are not called.
- **Several codes** (`maxCodes` 0 or greater than 1): first all linear symbologies, then DataMatrix. `codes` lists the linear codes first, then DataMatrix; processing ends as soon as `maxCodes` have been collected.

Engines not covered by the mask are left out of the chain and cost no time. Before handing the frame to the DataMatrix engine, unir paints over the linear codes already read, together with their quiet zones, in a flat tone, so the bars do not send it into a search over false candidates. The mask is built from the corners of the codes read, does not touch other symbols and does not affect the coordinates in the result.

## Frame

### PixelFormat

```cpp
enum class PixelFormat {
    Gray8,  // one channel: luminance; cheapest for the scanner
    Bgr8,   // three channels, OpenCV order
    Rgb8,
    Bgra8,  // four channels, OpenCV and Windows order
    Rgba8,
};

constexpr int channelsOf(PixelFormat f);   // 1, 3 or 4
```

For a monochrome camera, pass the frame as `Gray8` rather than replicated into three channels.

### ImageView

A view over a foreign buffer: the library does not copy it, free it or outlive it — the buffer must stay alive for the whole call. The fields mirror the frame view of dmr and ssccr, so the frame reaches the engines without copying.

```cpp
struct ImageView {
    const uint8_t* data   = nullptr;
    int            width  = 0;
    int            height = 0;
    int            stride = 0;              // bytes per row; 0 — rows packed tightly
    PixelFormat    format = PixelFormat::Gray8;

    bool empty()    const;
    int  channels() const;
    int  rowBytes() const;                  // stride with the zero already resolved
    const uint8_t* row(int y) const;
};
```

`stride = 0` means “rows packed tightly” (`width * channels`), not “zero bytes per row”: a camera frame is almost never tightly packed — rows are aligned, and a crop inherits the row length of the original. An empty view or a `stride` shorter than the row gives `Status::InvalidImage`; such a frame never reaches the engines.

### Image

A frame owned by the library itself, rows packed tightly. It converts implicitly to `ImageView`, so it can be passed to `scan()` directly:

```cpp
class Image {
public:
    Image() = default;
    Image(int width, int height, PixelFormat format);

    void reset(int width, int height, PixelFormat format);  // buffer of that size, filled with zeros
    void clear();

    int         width()  const;
    int         height() const;
    int         stride() const;
    PixelFormat format() const;
    bool        empty()  const;

    uint8_t*       data();
    const uint8_t* data() const;
    uint8_t*       row(int y);
    const uint8_t* row(int y) const;

    ImageView view() const;
    operator ImageView() const;   // implicit — for calling scan(image)
};
```

### Reading from disk

```cpp
struct ImageLimits {
    int       maxSide   = 16384;        // 0 — kHardMaxSide
    long long maxPixels = 100'000'000;  // 0 — kHardMaxPixels

    static constexpr int       kHardMaxSide   = 65535;
    static constexpr long long kHardMaxPixels = 1LL << 29;

    bool allows(long long w, long long h) const;
};

enum class LoadStatus {
    Ok,          // image read
    NotFound,    // no such file
    Unreadable,  // the file exists but is not a readable image (or did not make it in time)
    TooLarge,    // size from the header exceeds ImageLimits; pixels were not read
};

enum class LoadAs {
    Color,  // PixelFormat::Bgr8
    Gray,   // PixelFormat::Gray8 — enough for the scanner
};

LoadStatus loadImage(const std::string& path, Image& out, LoadAs as = LoadAs::Gray,
                     const ImageLimits& limits = ImageLimits{});
```

The image is decoded by the same decoders as in dmr — with size limits and protection against decompression bombs: **JPEG and PNG**, detected by the first bytes of the file, not by the extension. The path is UTF-8 (on Windows the system encoding works too, as with `argv`: a string that is not valid UTF-8 is read in it). By default the image is loaded as grayscale: the scanner does not need colour. The limits are checked against the file header before any memory is allocated; pass the same ones as in `Settings::imageLimits`. On failure `out` is left unchanged.

```cpp
unir::Image frame;
switch (unir::loadImage("frame.jpg", frame)) {
    case unir::LoadStatus::Ok:         break;
    case unir::LoadStatus::NotFound:   /* no such file */ break;
    case unir::LoadStatus::Unreadable: /* the file exists but is not an image */ break;
    case unir::LoadStatus::TooLarge:   /* exceeds the limits */ break;
}
```

A standalone `loadImage()` runs without the frame time limit. When the image comes from an untrusted source, `Scanner::scanFile()` is the better choice: loading and reading run under one time limit there.

## Geometry

```cpp
struct Point {
    float x = 0.0f;
    float y = 0.0f;
};

struct Box {                 // rectangle aligned with the frame axes
    int x = 0, y = 0, width = 0, height = 0;
    bool empty() const;
};

struct Quad {                // the symbol's four corners, in circular order
    Point corner[4];
};
```

All coordinates are in pixels of the original frame. `Point` is fractional: corners are located with sub-pixel precision. `Box` is a rectangle aligned with the frame axes; `Quad` gives the symbol's four corners more precisely than a rectangle can: on a conveyor the symbol is captured at an angle.

```cpp
for (const unir::Point& p : c.corners.corner)
    drawTo(p.x, p.y);
```

> **Index meaning depends on the symbology.** The corners of a `Quad` always go in circular order, but which corner sits at which index depends on the symbology:
>
> - **DataMatrix** — index 3 holds the corner the detector took for the corner of the solid L finder pattern. `corner[3]` is fine for drawing an overlay on the frame, but not for drawing conclusions about how the symbol's content is rotated.
> - **Code 128 and GS1-128** — `corner[0]` is on the start side, `corner[1]` on the stop side along the same edge of the bars, `corner[2]` and `corner[3]` on the opposite edge. They show the reading direction.
>
> Check `Code::symbology` before interpreting the corners.

## Settings

Start from a preset rather than individual fields — the preset's name already tells you which scenario it is tuned for. The preset sets the mode of the DataMatrix engine; the linear engine does not depend on the mode.

| Preset | Use case | What differs |
| --- | --- | --- |
| `Settings::stream()` | A frame stream from one camera — a conveyor. The default | `mode = Stream`: the DataMatrix engine accumulates a symbol size hint across frames; one code |
| `Settings::single()` | One-off, unrelated images — a folder, manual checks | `mode = Single`: no hint; the DataMatrix engine tries every standard size if it could not measure the size; one code |
| `Settings::multi(n)` | Several codes in one frame | `mode = Multi`, `maxCodes = n` (default 0 — as many as found) |

```cpp
auto s = unir::Settings::stream();
s.symbologies = unir::Symbology::DataMatrix | unir::kEanUpc;
s.budgetMs    = 120.0;     // one time limit for all symbologies; 0 — no limit
const unir::Scanner scanner(s);
```

### All fields

```cpp
// unir/Settings.h
struct Settings {
    static Settings stream();                  // frame stream from one camera (the default)
    static Settings single();                  // unrelated frames
    static Settings multi(int maxCodes = 0);   // up to maxCodes codes per frame (0 — all)

    Symbologies symbologies = kAllSymbologies; // which symbologies to look for
    int         maxCodes    = 1;               // 1 — up to the first code, N — up to N, 0 — all
    double      budgetMs    = 0.0;             // frame time limit, ms, one for all symbologies; 0 — none
    double      timeoutMs   = 10000.0;         // safety net, ms; 0 — the 60 s ceiling
    ImageLimits imageLimits{};                 // 16384 per side, 100,000,000 pixels

    enum class Mode { Stream, Single, Multi };
    Mode mode = Mode::Stream;                  // mode of the DataMatrix engine
};
```

The presets change only `mode`, and `multi()` also `maxCodes`; all other fields keep the defaults from the declaration.

| Field | Default | Meaning |
| --- | --- | --- |
| `symbologies` | `kAllSymbologies` | Which symbologies to look for — a mask (see “Symbologies”). The default is every symbology of the build; future versions will have more |
| `maxCodes` | 1 | How many codes to look for: 1 — up to the first code of any symbology, N — up to N, 0 — all |
| `budgetMs` | 0.0 | Time limit per frame, ms — **one for all symbologies**: the engines share it in turn, and an engine with no time left does not start at all (`timedOut` is set). 0 — no limit, only `timeoutMs` applies. The limit is soft: frame time may exceed it by the duration of the stage in progress. The DataMatrix engine receives the budget as is, and, as in dmr itself, a budget changes how it searches, not only when it stops |
| `timeoutMs` | 10000.0 | A safety net, ms: the same cut-off as `budgetMs` (the smaller of the two applies), but it does not change how the engines work. 0 and anything above 60000 mean the 60 s ceiling: a frame is never processed for longer. Protection against hostile frames, not a latency setting |
| `imageLimits` | 16384 per side, 100,000,000 pixels | The largest frame the scanner agrees to process (`ImageLimits` from `unir/Image.h`; 0 — the hard ceiling of 65535 and 2^29). A larger frame immediately gets `Status::ImageTooLarge`. `scanFile()` checks the same limits against the file header before decoding. A safeguard, not a quality setting |
| `mode` | `Mode::Stream` | Mode of the DataMatrix engine: `Stream` — size hint across frames, `Single` — unrelated frames with a size search, `Multi` — several codes. Usually set by the preset |

unir does not expose the fine-grained dmr settings (rotation, GS1 check, candidate sources and the like): the DataMatrix engine runs with the dmr preset chosen by `mode`, plus your `maxCodes`, time limits and frame limits.

### Speed: mode, mask, time limit

Frame time is determined by three settings, and all three are worth setting explicitly rather than left at their defaults.

- **Mode.** With one code (`stream()`, `single()`) processing ends at the first code read, and the cheapest pass — SSCC and EAN/UPC — goes first: a frame with a single barcode is read quickly and DataMatrix is never reached. `multi(0)` cannot stop early: every engine in the mask goes through the whole frame, the linear one looks for every Code 128 and EAN/UPC, the DataMatrix engine tries all of its candidates. On a frame with one code this takes several times longer than one-code mode, and on a slow machine an order of magnitude longer. If you know how many codes to expect, use `multi(N)`: processing ends as soon as N have been collected.
- **Mask.** Every symbology left out of the mask is an engine or a pass that does not run. The most expensive outcome is “nothing found” on a frame without a code: other `Code128` is tried on anything that looks like bars, and the DataMatrix engine goes into a deep search. SSCC is read without `Code128` — with `Gs1_128` alone. A mask of only the symbologies you need is noticeably faster than “all”, and a DataMatrix-only mask even more so.
- **Time limit.** `budgetMs` is one limit per frame for all symbologies. The engines spend it in turn; once it runs out, the running engine stops at its next stage, the following ones do not start, and `timedOut` is set in the result. Codes read before the limit stay in `codes`. Without `budgetMs` a frame is bounded only by the `timeoutMs` safety net (10 s), which is noticeable for `multi(0)` on a hard frame.

In practice: on a conveyor, set the limit to the interval between frames minus your own processing; for a one-off image that a person is waiting for, in `multi()`, set it to as long as they are willing to wait, for example `budgetMs = 3000`. For `scanFile()` loading the file counts against the same limit.

## Library version

```cpp
#define UNIR_VERSION_MAJOR 1
#define UNIR_VERSION_MINOR 0
#define UNIR_VERSION_PATCH 1
#define UNIR_VERSION_STRING "1.0.1"

#define UNIR_VERSION_AT(ma, mi, pa) ((ma) * 10000 + (mi) * 100 + (pa))
#define UNIR_VERSION UNIR_VERSION_AT(UNIR_VERSION_MAJOR, UNIR_VERSION_MINOR, UNIR_VERSION_PATCH)

namespace unir {
    std::string version();          // "1.0.1"
    std::string engineVersions();   // engine versions inside the build: "dmr 1.0.12, ssccr 0.1.0"
}
```

The macros describe the header you compiled against; `unir::version()` describes the library you linked against. They can diverge: the library is installed into a prefix and stays there longer than whoever built against it remembers. `engineVersions()` is for support requests and for comparing with DataMatrix Reader: it shows which revisions of dmr and ssccr the package was built with.

```cpp
#if UNIR_VERSION >= UNIR_VERSION_AT(1, 1, 0)
    // code for header 1.1.0 and newer
#endif
```

## License

The library reads codes while the trial period (30 days from the first run on the machine, no network or registration needed) or a license is in effect. Otherwise `Scanner::scan()` and `scanFile()` immediately return `Status::Unlicensed`: no engine is called and the image file is not opened. The license is bound to the hardware (motherboard, CPU, system disk), not to the OS installation: reinstalling Windows or replacing the disk or CPU keeps it, replacing the motherboard does not.

The unir trial period and license are its own, independent of DataMatrix Reader and DataMatrix Generator on the same machine: the license server is the same, but the product is different, and a license for one product does not unlock another.

```cpp
#include <unir/License.h>

const unir::LicenseStatus s = unir::license::status();
if (!s.canScan()) show(s.message);   // "The trial period has ended. A license is required"

unir::LicenseResult r = unir::license::activate(keyFromUser);   // "UNIR-XXXXX-XXXXX-XXXXX-XXXXX"
if (!r.ok) show(r.message);
```

### Key file unir.key

The main way to activate is to put the key into a text file `unir.key` instead of calling `activate()` from your program. The name is its own, not `license.key` as with DataMatrix Reader: libraries of different products may sit in the same directory, and their keys differ. The library looks for the file, first match wins:

1. next to the module that contains the library: `unir_c.dll` / `libunir_c.so` for the C ABI, the program itself for static `libunir.a` and the `unir` utility;
2. next to the program's executable.

The current directory and environment variables are not searched. Format: the first line that is not empty and not a `#` comment is the key, as issued — `UNIR-XXXXX-XXXXX-XXXXX-XXXXX` (case, spaces and dashes do not matter; a UTF-8 BOM is fine; files over 4 KB are ignored). The key is activated over the network automatically, with nothing to call: when the first `Scanner` is created or on the first `license::` call — that call waits for the server once. If it fails, the reason is in `status().message`; without a network connection the attempt is repeated every hour, after a rejection by the server once a day, including across restarts. A file added while the program runs is picked up within an hour. A working license is not replaced by a file with a different key; a key released with `deactivate()` is not activated again from the file. `license::keyFile()` tells you which file the library found.

### Where the trial and license state is kept

Windows: `%ProgramData%\unir\license.dat`, `%LOCALAPPDATA%\unir\license.dat` and the `state` value under the registry key `HKCU\Software\unir`. Linux: `/var/lib/unir/license.dat` and `~/.config/unir/license.dat` of the user the process runs as (taken from the user database, not from `$HOME`). The library keeps several copies and takes the earliest trial start. A container has neither DMI data nor its own disk, so a license cannot be bound to it — run the program on the host or in a VM.

On Linux only root can create the shared copy in `/var/lib/unir`: under an ordinary user the directory is not created and that copy is silently skipped — the state lives in `~/.config/unir`. This is not an error. The trial period starts on the first call into the library on the machine — the first `Scanner` or the first `license::status()` — and is written to disk right away, not when the program exits.

### LicenseState

| Value | Meaning |
| --- | --- |
| `Trial` | Trial period in progress |
| `Active` | License is in effect |
| `RefreshDue` | In effect, but there has been no contact with the server for a long time; auto-check retries hourly |
| `TrialExpired` | Trial period over, no license |
| `Expired` | The paid license term has ended; a renewal in your account is picked up by the auto-check |
| `NetworkRequired` | Cannot continue without contacting the server; as soon as the server responds, the auto-check restores the license |
| `WrongMachine` | The license was issued to a different machine |
| `Invalid` | The license record fails signature verification |

### LicenseStatus

```cpp
// unir/License.h
enum class LicenseState {
    Trial = 0, Active = 1, RefreshDue = 2, TrialExpired = 3,
    Expired = 4, NetworkRequired = 5, WrongMachine = 6, Invalid = 7,
};

struct LicenseStatus {
    LicenseState state = LicenseState::TrialExpired;
    bool canScan() const;            // Trial, Active or RefreshDue

    int64_t validUntil   = 0;        // Unix seconds UTC
    int64_t refreshAfter = 0;        // 0 — never goes stale
    int64_t offlineUntil = 0;        // 0 — no limit
    int     daysLeft     = 0;

    std::string licenseId;
    std::string activationId;
    std::string offlineMode;         // limited | extended | full; empty without a license
    bool        clockRollback = false;
    std::string message;             // the state in words, in Russian
};

struct LicenseResult {
    bool          ok = false;
    std::string   error;             // failure code: LICENSE_NOT_FOUND, KEY_INVALID, NETWORK, ...
    std::string   message;
    LicenseStatus status;            // the state after the operation
};
```

The C++ API has no “`LicenseState` → string” function: for the user there is `message`, and the machine-readable state code (`trial`, `active`, …) is given only by the utility's JSON, the C ABI and Python. If you need it in C++, a short `switch` with the same names will do:

```cpp
const char* stateName(unir::LicenseState s) {
    switch (s) {
        case unir::LicenseState::Trial:           return "trial";
        case unir::LicenseState::Active:          return "active";
        case unir::LicenseState::RefreshDue:      return "refresh_due";
        case unir::LicenseState::TrialExpired:    return "trial_expired";
        case unir::LicenseState::Expired:         return "expired";
        case unir::LicenseState::NetworkRequired: return "network_required";
        case unir::LicenseState::WrongMachine:    return "wrong_machine";
        case unir::LicenseState::Invalid:         return "invalid";
    }
    return "invalid";
}
```

| Field | Meaning |
| --- | --- |
| `state` | `LicenseState` |
| `canScan()` | Whether the library reads right now — true for `Trial`, `Active` or `RefreshDue` |
| `validUntil` | End of the trial period or of the paid term, Unix seconds UTC |
| `refreshAfter` | When the token is considered stale and the license moves to `RefreshDue`; 0 — never goes stale (trial period, `full` mode) |
| `offlineUntil` | Beyond this time the license does not work without contacting the server; 0 — no limit (`full` mode) |
| `daysLeft` | Whole days until the nearest of the limits above; 0 — limit passed |
| `licenseId` / `activationId` | IDs of the license and of the specific activation |
| `offlineMode` | `limited \| extended \| full`; empty without a license |
| `clockRollback` | The system clock is behind a time the machine has already seen — rolling back the clock does not extend the term |
| `message` | The state in words, for display to the user |

Functions that act on the license return a `LicenseResult`: `ok`, the failure code `error` (the server's answer as is — `LICENSE_NOT_FOUND`, `ACTIVATION_LIMIT_REACHED`, `LICENSE_REVOKED` and others — or the library's own: `KEY_INVALID`, `NETWORK`, `WRONG_PRODUCT` and others), `message` in words and `status` — the state after the operation.

### Activation: online and offline

Online — a key from your account; the machine contacts the license server itself:

```cpp
unir::LicenseResult r = unir::license::activate("UNIR-XXXXX-XXXXX-XXXXX-XXXXX");
```

Offline — for a machine without internet access: the request file is carried to a machine with network access and uploaded to your account, which returns `license.lic`:

```cpp
unir::license::writeActivationRequest("request.json");   // → upload to your account, get license.lic
unir::license::importLicenseFile("license.lic");          // the same call renews: the new file replaces the old one
```

A license activated online checks in with the server once a day in a background thread — refreshing the token (the `limited` and `extended` modes need this to keep working) and picking up revocation and term renewal. You don't need to call anything for this, and reading a frame never waits for the network; if the server is unreachable, it retries in an hour, and the license keeps working until the offline period runs out.

### Moving to another machine and other functions

```cpp
namespace unir::license {
    void configure(const LicenseOptions& options);   // before the first Scanner; optional
    LicenseStatus status();
    LicenseResult activate(const std::string& licenseKey);
    LicenseResult refresh();                                    // check now, without waiting a day
    LicenseResult deactivate(const std::string& reason = {});    // the receipt is sent to the server
    LicenseResult deactivateToFile(const std::filesystem::path& receiptPath, const std::string& reason = {});
    LicenseResult writeActivationRequest(const std::filesystem::path& path);
    LicenseResult importLicenseFile(const std::filesystem::path& path);
    std::string machineFingerprint();                           // for support requests
    std::filesystem::path keyFile();                            // the unir.key found; empty — none
}

struct LicenseOptions {
    std::string serverUrl;    // empty — the one built in
    std::string appVersion;   // your program's version — shown in your account next to the machine
    int  timeoutMs = 15000;   // how long to wait for one server request
    bool autoCheck = true;    // daily auto-check
};
```

Call `configure()` before creating the first `Scanner`: the key from `unir.key` is activated on the first call into the library and takes the server and `appVersion` from here. Overriding the server is safe: responses are verified against signing keys built into the library. Turn off `autoCheck` only if your program must not go online on its own by its own rules; then `limited` and `extended` licenses are renewed by the program's own `refresh()` call. A license imported from a file has no key, so `refresh()` will not help it — it is renewed with a new `license.lic`.

To move to another machine, call `deactivate()` on the old one: the seat is released on the server with a receipt signed by that machine's secret; without a connection the license stays where it is. Without network access, use `deactivateToFile()`: the license is removed from the machine at once, and the receipt is written to a file that can be sent from any machine. All functions are thread-safe; functions that use the network block the caller until a response arrives or `LicenseOptions::timeoutMs` expires.

Every license server response is bound to its request: the library sends a random nonce, the server returns it in the signed response, and a previously recorded response will not be accepted again. The seat release receipt is single-use as well, so the server will not accept a receipt from a restored copy of the machine's old state. For licenses with full autonomy (`full`) releasing a seat consumes the transfer limit; once it is exhausted, transfers are handled by support. You can release a seat of a license yourself at most twice in 30 days in `limited` mode and at most once in `extended` mode; further transfers are handled by support.

> **Activation key.** The activation key is issued in your account after you purchase a plan in the [catalog](https://libscanner.com/en).

## C ABI

The same library behind a flat C99 interface — header `unir/unir_c.h`, library `unir_c.dll` / `libunir_c.so` (how to link it — see “Installation”). For C, MSVC and any language that can call C functions (C#, Go, Delphi, Rust…). Rules of the boundary:

- Only plain C types cross it: integers, `double`, pointers to buffers of known size, enums as `int` and opaque handles. A C++ exception never crosses it — an internal failure becomes `NULL` or an error code.
- Ownership: everything a function *returns* (a handle, a string) belongs to the caller and is freed by its own function (`unir_image_free`, `unir_scan_result_free`, `unir_free_string`; the scanner — `unir_scanner_destroy`). Pointers returned by accessors (`unir_scan_result_code_text` and the like) live no longer than the object they came from and are not freed separately. The string from `unir_symbology_name` is static and is not freed.
- `unir_settings_t` starts with `struct_size` and the `abi_tag` marker: the caller sets both **before** the preset — in C the easiest way is `unir_settings_t s = UNIR_SETTINGS_INIT;`, in C++ — `unir_settings_prepare(&s)`. A preset writes no further than `struct_size`; fields the caller does not know about (a newer library) keep their defaults. A wrong tag or an out-of-range size is rejected: the preset returns 0, `unir_scanner_create` returns `NULL`.
- Symbologies are bits of the `UNIR_SYM_*` mask, with the same values as `unir::Symbology` (see “Symbologies”); a decoded code carries exactly one bit (`unir_scan_result_code_symbology`), its name comes from `unir_symbology_name`. `UNIR_SYM_ALL` means all symbologies of *this* build: later versions will add new ones (QR and others) to it, so set the mask explicitly if you need a fixed set.
- Decoded text keeps the GS separator as the byte `0x1D` and may contain a zero byte — take the length from `unir_scan_result_code_text_len`, not `strlen()`.
- Paths are UTF-8. On Windows the system encoding works too, as with `argv`: a string that is not valid UTF-8 is read in it. Pass a name that is not in the Windows code page as UTF-8.
- Statuses are the same as `unir::Status` (see “Scan result”), plus `UNIR_STATUS_INTERNAL_ERROR = -1` for a failure of the C layer itself. `unir_scanner_scan` and `unir_scanner_scan_file` return `NULL` only on an internal error; “no code” is a result with the status `UNIR_STATUS_NOT_FOUND`. An image that failed to load gives `UNIR_STATUS_IMAGE_TOO_LARGE` or `UNIR_STATUS_INVALID`, with details in `*load_status`.
- Do not call one scanner from several threads at once — use one scanner per thread.
- The license functions return the same JSON as `unir --license … --json` (see “Command line”): one contract for the tool, the C ABI and Python.

### Complete example: all codes in an image

```cpp
#include <unir/unir_c.h>
#include <stdio.h>

/* All codes in an image: 0 — read, 1 — no code, 2 — error. */
int main(int argc, char** argv) {
    if (argc < 2) return 2;

    unir_settings_t settings = UNIR_SETTINGS_INIT;   /* size and tag — before the preset */
    unir_settings_multi(&settings, 0);               /* all codes in the image */
    settings.symbologies = UNIR_SYM_ALL;             /* or, e.g., UNIR_SYM_DATAMATRIX | UNIR_SYM_EAN_UPC */
    settings.budget_ms   = 3000;                     /* one deadline for loading and all symbologies */

    unir_scanner_t* scanner = unir_scanner_create(&settings);
    if (!scanner) return 2;

    unir_load_status_t load = UNIR_LOAD_UNREADABLE;
    unir_scan_result_t* r = unir_scanner_scan_file(scanner, argv[1], &load);
    int rc = 2;
    if (r) {                                         /* NULL — internal error */
        unir_status_t st = unir_scan_result_status(r);
        if (st == UNIR_STATUS_OK) {
            for (int i = 0; i < unir_scan_result_code_count(r); ++i) {
                printf("%s\t", unir_symbology_name(unir_scan_result_code_symbology(r, i)));
                /* text may contain 0x1D (GS) and even 0x00 — use the length, not strlen() */
                fwrite(unir_scan_result_code_text(r, i), 1, unir_scan_result_code_text_len(r, i), stdout);
                putchar('\n');
            }
            rc = 0;
        } else if (st == UNIR_STATUS_NOT_FOUND || st == UNIR_STATUS_NOT_DECODED) {
            rc = 1;
        } else if (st == UNIR_STATUS_UNLICENSED) {
            char* why = unir_license_status_json();   /* {"state":"trial_expired",...} */
            fprintf(stderr, "%s\n", why ? why : "unlicensed");
            unir_free_string(why);
        } else {
            fprintf(stderr, "status %d, load %d\n", (int)st, (int)load);   /* no file, not an image, too large */
        }
        unir_scan_result_free(r);
    }
    unir_scanner_destroy(scanner);
    return rc;
}
```

On Linux: `cc main.c -o read $(pkg-config --cflags --libs unir_c)`, run with `LD_LIBRARY_PATH=/unpacked/lib ./read label.png`. It prints one line per code: the symbology, a tab and the text — for a Chestny ZNAK DataMatrix, with real `0x1D` bytes between the elements.

### Reference

All declarations of `unir_c.h` (ABI 1.0, library 1.0.1); comments are shortened — the full ones are in the header itself.

```cpp
/* ── versions ── */
#define UNIR_C_ABI_VERSION_MAJOR 1
#define UNIR_C_ABI_VERSION_MINOR 0
size_t unir_c_abi_version(char* buf, size_t buf_size);    /* "1.0" */
size_t unir_version(char* buf, size_t buf_size);          /* library version, "1.0.1" */
size_t unir_engine_versions(char* buf, size_t buf_size);  /* "dmr 1.0.12, ssccr 0.1.0" */

/* ── symbologies: mask bits ── */
#define UNIR_SYM_NONE        0x00u
#define UNIR_SYM_DATAMATRIX  0x01u
#define UNIR_SYM_CODE128     0x02u   /* Code 128 without the GS1 flag */
#define UNIR_SYM_GS1_128     0x04u   /* without UNIR_SYM_CODE128 in the mask — SSCC (AI 00) only */
#define UNIR_SYM_EAN13       0x08u
#define UNIR_SYM_EAN8        0x10u
#define UNIR_SYM_UPCA        0x20u   /* 12 digits */
#define UNIR_SYM_UPCE        0x40u   /* 8 digits, not expanded to UPC-A */
#define UNIR_SYM_EAN_UPC     (UNIR_SYM_EAN13 | UNIR_SYM_EAN8 | UNIR_SYM_UPCA | UNIR_SYM_UPCE)
#define UNIR_SYM_ALL         (UNIR_SYM_DATAMATRIX | UNIR_SYM_CODE128 | UNIR_SYM_GS1_128 | UNIR_SYM_EAN_UPC)
const char* unir_symbology_name(uint32_t symbology);      /* "datamatrix" … "upc-e"; 0 — "none";
                                                            * a mask or an unknown bit — "unknown" */
int unir_parse_symbologies(const char* list, uint32_t* mask);   /* "dm,128,ean", "all"; 1 — done,
                                                                  * 0 — unknown name */

/* ── frame ── */
typedef enum { UNIR_PIXEL_GRAY8 = 0, UNIR_PIXEL_BGR8 = 1, UNIR_PIXEL_RGB8 = 2,
               UNIR_PIXEL_BGRA8 = 3, UNIR_PIXEL_RGBA8 = 4 } unir_pixel_format_t;
typedef struct {
    const uint8_t*      data;     /* caller's buffer, alive for the whole scan call */
    int                 width;
    int                 height;
    int                 stride;   /* bytes per row; 0 — tightly packed */
    unir_pixel_format_t format;
} unir_image_view_t;

typedef struct unir_image unir_image_t;                 /* opaque, buffer owned by the library */
typedef struct unir_settings unir_settings_t;           /* defined below */
typedef enum { UNIR_LOAD_COLOR = 0, UNIR_LOAD_GRAY = 1 } unir_load_as_t;
typedef enum { UNIR_LOAD_OK = 0, UNIR_LOAD_NOT_FOUND = 1, UNIR_LOAD_UNREADABLE = 2,
               UNIR_LOAD_TOO_LARGE = 3, UNIR_LOAD_TIMED_OUT = 4,
               UNIR_LOAD_INVALID_ARGUMENT = 5 } unir_load_status_t;
/* JPEG and PNG; GRAY — GRAY8, COLOR — BGR8; *out is NULL on failure */
unir_load_status_t unir_load_image(const char* path, unir_load_as_t as, unir_image_t** out);
unir_load_status_t unir_load_image_ex(const char* path, unir_load_as_t as,
                                      const unir_settings_t* settings,   /* NULL — defaults */
                                      unir_image_t** out);
unir_image_view_t  unir_image_view(const unir_image_t* image);
void               unir_image_free(unir_image_t* image);

/* ── settings: same fields and defaults as unir::Settings ── */
typedef enum { UNIR_MODE_STREAM = 0, UNIR_MODE_SINGLE = 1, UNIR_MODE_MULTI = 2 } unir_mode_t;
struct unir_settings {
    uint32_t struct_size;        /* sizeof(unir_settings_t), set by the caller FIRST */
    uint32_t abi_tag;            /* UNIR_SETTINGS_TAG, also before the preset */
    uint32_t symbologies;        /* UNIR_SYM_* mask; presets use UNIR_SYM_ALL */
    int      max_codes;          /* 1 — up to the first code of any symbology, N — up to N, 0 — all */
    int      mode;               /* unir_mode_t */
    double   budget_ms;          /* ONE deadline for all symbologies; 0 — no limit */
    double   timeout_ms;         /* safety timeout, 10000 in presets; 0 — the 60 s ceiling */
    int      max_image_side;     /* 0 — the 65535 ceiling; 16384 in presets */
    int64_t  max_image_pixels;   /* 0 — the 2^29 ceiling; 100,000,000 in presets */
};
#define UNIR_SETTINGS_TAG 0x31524E55u                   /* 'U' 'N' 'R' '1' */
#define UNIR_SETTINGS_INIT { .struct_size = (uint32_t)sizeof(unir_settings_t), .abi_tag = UNIR_SETTINGS_TAG }
#define UNIR_SETTINGS_MAX_SIZE 4096u
static inline void unir_settings_prepare(unir_settings_t* s);   /* zeroes, sets size and tag */
int unir_settings_stream(unir_settings_t* out);         /* 1 — filled in; 0 — NULL, wrong */
int unir_settings_single(unir_settings_t* out);         /*   tag or size              */
int unir_settings_multi(unir_settings_t* out, int max_codes);   /* 0 — all codes */

/* ── scanner ── */
typedef struct unir_scanner unir_scanner_t;             /* opaque; one scanner per thread */
unir_scanner_t* unir_scanner_create(const unir_settings_t* settings);   /* NULL — stream() */
void            unir_scanner_destroy(unir_scanner_t* scanner);

/* ── result ── */
typedef enum { UNIR_STATUS_OK = 0, UNIR_STATUS_NOT_FOUND = 1, UNIR_STATUS_NOT_DECODED = 2,
               UNIR_STATUS_UNLICENSED = 3, UNIR_STATUS_IMAGE_TOO_LARGE = 4,
               UNIR_STATUS_INVALID = 5, UNIR_STATUS_FAILED = 6,
               UNIR_STATUS_INTERNAL_ERROR = -1 } unir_status_t;
typedef struct unir_scan_result unir_scan_result_t;     /* opaque */
unir_scan_result_t* unir_scanner_scan(unir_scanner_t* scanner,
                                      const unir_image_view_t* frame);   /* NULL — internal error */
/* an image from disk and the codes in it under one scanner deadline; the load
 * outcome goes to *load_status (NULL — not needed) */
unir_scan_result_t* unir_scanner_scan_file(unir_scanner_t* scanner, const char* path,
                                           unir_load_status_t* load_status);
void          unir_scan_result_free(unir_scan_result_t* result);
unir_status_t unir_scan_result_status(const unir_scan_result_t* result);
int           unir_scan_result_timed_out(const unir_scan_result_t* result);
double        unir_scan_result_elapsed_ms(const unir_scan_result_t* result);
int           unir_scan_result_code_count(const unir_scan_result_t* result);
uint32_t      unir_scan_result_code_symbology(const unir_scan_result_t* result, int index);   /* a single bit */
const char*   unir_scan_result_code_text(const unir_scan_result_t* result, int index);
size_t        unir_scan_result_code_text_len(const unir_scan_result_t* result, int index);
void          unir_scan_result_code_box(const unir_scan_result_t* result, int index,
                                        int* x, int* y, int* width, int* height);
void          unir_scan_result_code_corners(const unir_scan_result_t* result, int index,
                                            float corners_xy[8]);   /* x0,y0 … x3,y3 */
int           unir_scan_result_code_module_count(const unir_scan_result_t* result, int index);  /* DataMatrix */
float         unir_scan_result_code_module_px(const unir_scan_result_t* result, int index);     /* linear codes */

/* ── license: JSON as from "unir --license … --json"; free with unir_free_string, NULL — internal error ── */
void  unir_license_configure(const char* server_url, const char* app_version,
                             int timeout_ms, int auto_check);   /* timeout_ms <= 0 — 15000 */
char* unir_license_status_json(void);
char* unir_license_activate_json(const char* key);
char* unir_license_refresh_json(void);
char* unir_license_deactivate_json(const char* reason);           /* reason may be NULL */
char* unir_license_deactivate_to_file_json(const char* path, const char* reason);
char* unir_license_write_activation_request_json(const char* path);
char* unir_license_import_license_file_json(const char* path);
char* unir_license_fingerprint_json(void);                        /* {"fingerprint":"hex"} */
void  unir_free_string(char* s);
```

## Command line

The package includes the `unir` utility alongside the library — for quick checks without writing code: a single image, a list of paths, a folder or a stream for an external service. Modes and formats are the same as those of the `dmr` utility, plus a symbology column.

```
unir <image>            [options]
unir --batch  <list>    [options]   list of paths, one per line
unir --folder <dir>     [options]   every image in the directory (not recursive)
unir --serve            [options]   one path per line from stdin
unir --license <subcommand>         license
unir --version                      versions of unir and its engines
```

| Option | Meaning |
| --- | --- |
| `--sym LIST` | Comma-separated symbologies, names as in `parseSymbologies`: `dm`, `128`, `gs1` (`sscc`), `ean` (`ean-13`, `ean-8`, `upc-a`, `upc-e`), `all` — the default |
| `--codes N` | How many codes to look for: 1 — the default, 0 — all; any value other than 1 selects the `multi(N)` preset |
| `--single` | Images are unrelated — the `single()` preset; the default is a stream, `stream()` |
| `--budget-ms N` | Time limit per image, ms, one for all symbologies; 0 — no limit |
| `--timeout-ms N` | Safety net, ms (10000); 0 — the 60 s ceiling |
| `--max-side N` / `--max-pixels N` | Largest image side (16384) and area (100000000) |
| `--color` | Load the image in colour; grayscale by default |
| `--corners` | In batch modes — the symbol's corners as a sixth column |
| `--root D` | In `--serve`: read only images inside directory D |

### Single image

```
$ unir label.jpg
processing: 24.9 ms
code: datamatrix 0104627191145677215n5Gr,pHTGIU&<GS>93CuXU
```

The utility prints in the system language (English on any non-Russian system; `--lang ru|en` or the `LIBSCANNER_LANG` variable sets it explicitly; the same variable sets the language of `status().message` and library error messages): `processing` is the processing time, and there is one `code: <symbology> <text>` line per code, with the symbology as given by `nameOf()`. In the text GS is shown as `<GS>`, TAB as `<TAB>`, line breaks as spaces. If the time limit cut the search short, a line `budget: exhausted, search cut off` follows. Errors go to stderr. The outcome is the exit code. The numbers are the same as in `unir::Status`, with one difference: an image that failed to load (no such file, not JPEG/PNG, decoding did not fit into the time limit) gives 1, not 5 (`InvalidImage`) as `scanFile()` would return; an oversized image gives 4, the same as `ImageTooLarge`. A time limit that cuts short the scan of an image already loaded does not change the code: it is 1 or 2 (or 0 if something was read in time) plus the budget line. Invalid command-line options also give 1.

| Code | Meaning |
| --- | --- |
| 0 | At least one code read (also when the time limit cut the search short but codes were found) |
| 1 | No code found; also no such file, not JPEG/PNG, decoding ran out of time, invalid options; in `--serve` — a path outside the `--root` directory |
| 2 | A candidate was found but not read — this does not mean the image holds a code (see “What NotDecoded really means”) |
| 3 | No trial period and no license |
| 4 | Image exceeds the limit |
| 5 | Inconsistent frame; the utility does not return it for images from disk — a file that failed to load gives 1 |
| 6 | Internal error; in `--serve` also an invalid symbology list in the request |

### Batch mode and streaming

`--batch` and `--folder` run one process for the whole set of images known in advance and print results line by line to stdout, one line per code:

```
path<TAB>exit code<TAB>ms<TAB>text<TAB>symbology[<TAB>corners]
```

The first four columns are the same as those of the `dmr` utility, so a parser for its output works here too. An image without a code gives one line with empty text and no symbology column; the second column holds the codes from the table above. `--corners` adds a sixth column `x0,y0 x1,y1 x2,y2 x3,y3`. The summary goes to stderr. Without a license the run stops at the first image with code 3: the rest would answer the same.

`--serve` uses the same line format, but the images are not known in advance: paths arrive one at a time, a line from stdin each, and are processed immediately — the process keeps the scanner alive between requests of an external service instead of paying for initialization on each one. After the lines of each image comes a `#END#` line, and the output is flushed at once — it marks the end of the answer even when there are several codes. A request line is either a path or `path<TAB>sym=LIST`: the symbologies for this image, in the same form as `--sym`; each set gets its own scanner with its own DataMatrix hint. Without a license the service answers with the image's line carrying code 3, then `#END#`, and exits with code 3. The process ends at the end of input.

```
$ mkfifo queue
$ unir --serve --codes 0 < queue &
$ printf '/path/to/image.jpg\tsym=dm,gs1\n' > queue
/path/to/image.jpg	0	91.24	0104627191145677215n5Gr,pHTGIU&<GS>93CuXU	datamatrix
#END#
```

### License from the command line

```
unir --license status                     [--json]
unir --license activate <key>             [--server URL] [--json]
unir --license refresh                    [--server URL] [--json]
unir --license request  <request.json>
unir --license import   <license.lic>
unir --license deactivate [<file>]        [--server URL] [--json]
unir --license fingerprint                [--json]
```

`request` writes an offline activation request file, `import` accepts a `license.lic` (renewals too), and `deactivate` releases the machine over the network when given no file, or writes a receipt to be sent from another machine when given one. Exit codes: `status` returns 0 if unir reads and 3 if it does not; the other subcommands return 0 (done) or 1 (failed).

The `--json` flag gives the same result as a single JSON line on stdout — for scripts and admin panels. The fields, their order and the exit codes are exactly those of `dmr --license … --json`, so a service that already parses dmr's output can call `unir` instead:

```
$ unir --license status --json
{"state":"trial","can_scan":true,"valid_until":1792831857,"refresh_after":0,
 "offline_until":0,"days_left":28,"license_id":"","activation_id":"",
 "offline_mode":"","clock_rollback":false,"message":"Trial period: 28 days left"}
```

Operations (`activate`, `refresh`, `deactivate`) answer with an object `{"ok":…,"error":…,"message":…,"status":{…}}`, where `status` is the state in the same form as above; `fingerprint` gives `{"fingerprint":"…"}`. The `state` field is a stable machine-readable code (`trial`, `active`, `refresh_due`, `trial_expired`, `expired`, `network_required`, `wrong_machine`, `invalid`) — part of the output format; it is never translated or reworded. The `message` field is in Russian.
