TL;DR
I built and published BarBridge, a language-independent Rust core that generates GS1-compliant 1D+2D barcode pairs for the upcoming GS1 Sunrise 2027 transition. The core exposes a minimal C ABI, and today I finished publishing ready-to-run samples in 7 languages across 6 platform targets (Windows/macOS/Linux × x64/ARM64), plus a dual MIT/Apache-2.0 license.
Repo: https://github.com/acrossreport/barbridge
The problem: GS1 Sunrise 2027
In 2027, GS1 is moving retail POS barcode practice away from 1D-only symbols (EAN/UPC, etc.) toward pairing them with 2D symbols (GS1 QR Code / GS1 DataMatrix). The 2D symbol can carry attribute data — lot number, expiration date — alongside the GTIN, which is great for traceability. But during the transition, retailers and manufacturers need to print both symbol types together, correctly sized and laid out.
That means, for every label:
- Keep the barcode's X-dimension (module width) inside GS1's allowed range
- Compute the largest GS1-compliant size that still fits a target footprint (e.g. 50mm × 50mm)
- Arrange the 1D and 2D symbols together (side-by-side or stacked) for the same GTIN
Reimplementing that math per printer vendor or per language is exactly the kind of thing that quietly drifts out of spec over time. So I pulled it into a single core.
From an ACR feature to a standalone library
BarBridge didn't start as its own project. The first version of this GS1 pairing logic was built as an optional feature inside ACR, our receipt/label rendering engine, specifically to get ahead of GS1 Sunrise 2027 support there. But the moment I got it working, it was obvious the same logic — GS1 size optimization, 1D+2D layout, pairing — had nothing ACR-specific about it. Any POS, ERP, or label-printing tool facing the same 2027 transition would need the exact same math. So instead of keeping it locked inside ACR, I pulled it out and rebuilt it as BarBridge, a standalone Rust core with its own C ABI, so it could be adopted independently of whatever rendering stack a given tool already uses.
Architecture: one Rust core, one C ABI boundary
request_json (string)
│
▼
barbridge_render_png() ← Rust core: GS1 size optimization + layout + rendering
│
▼
PNG bytes (caller-owned buffer)
The exposed surface is intentionally small:
| Function | Description |
|---|---|
barbridge_render_png(request_json, &out_data, &out_len) |
Renders a GS1 1D+2D pair (or a single symbol) to PNG bytes in memory |
barbridge_bytes_free(data, len) |
Frees the returned buffer (always call it) |
barbridge_last_error() |
Human-readable reason for the last failure |
Request example:
{
"data": "(01)04901234567894(10)LOT42(17)271231",
"mode": "pair",
"matrix": "data_matrix",
"arrangement": "auto",
"max_width_mm": 50,
"max_height_mm": 50,
"dpi": 600
}
data accepts either a GS1 AI element string ((01)GTIN(10)LOT…) or a bare GTIN-8/12/13/14. mode is pair (default), linear (1D only), or matrix (2D only).
Everything happens in memory — no temp files, no subprocess, no external CLI tool. The caller's process owns the output buffer, so there's no process-spawn overhead when embedding this in a server or batch pipeline. A WebAssembly build for the browser is planned too.
Today's work: samples for 7 languages × 6 platforms
The bulk of today's push was finishing a consistent sample set so anyone can see exactly how to call the library from their stack of choice, without reading the core source:
barbridge/
├── barbridge.h # C ABI header (C/C++, cgo, ...)
├── windows-x64/
├── windows-arm64/
├── linux-x64/
├── linux-arm64/
├── macos-arm64/
└── macos-x64/
├── native/ barbridge.dll / libbarbridge.so / libbarbridge.dylib
├── common/request.json # shared sample GS1 data
├── c/
├── python/ ctypes, standard library only
├── csharp/ P/Invoke, `dotnet run`
├── vb/ P/Invoke, `dotnet run`
├── java/ JNA, `mvn package`
├── php/ built-in FFI extension (PHP 7.4+)
└── nodejs/ koffi (prebuilt binary, no build toolchain)
| Language | Binding | Run |
|---|---|---|
| Python |
ctypes (stdlib only) |
python render_sample.py |
| C / C++ | link against barbridge.dll.lib / .so / .dylib
|
see header comment in render_sample.c
|
| C# | P/Invoke | dotnet run |
| VB.NET | P/Invoke | dotnet run |
| Java | JNA | mvn package && java -jar target/barbridge-sample-jar-with-dependencies.jar |
| PHP | built-in FFI extension |
php render_sample.php |
| Node.js | koffi |
npm install && node render_sample.js |
Every sample follows the same four steps: read ../common/request.json, call barbridge_render_png, write output.png, free the buffer. Seeing the FFI boilerplate side-by-side across ctypes/P-Invoke/JNA/koffi/PHP-FFI was honestly useful just for my own sanity-checking of the C ABI design — if one binding felt awkward, that was a signal the header needed adjusting, not the sample.
If you keep the library elsewhere, BARBRIDGE_NATIVE_LIB lets you point at it instead of relying on the relative path.
I also added a dual MIT OR Apache-2.0 license and cleaned up a stale "macOS build planned" note that had been sitting in both README files since the initial concept push.
What's next
- GS1-128 and GS1 DataBar support for logistics/case labels
- A WebAssembly sample for the browser
- Deciding how much of the core implementation itself to open up
Repo: https://github.com/acrossreport/barbridge — issues and feedback welcome, especially from anyone already dealing with Sunrise 2027 compliance on the printing side.
Top comments (0)