Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,17 @@ jobs:
- name: Format
run: cargo fmt --all --check

- name: Clippy
- name: Clippy (all features)
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Test
- name: Clippy (no_std, no alloc)
run: cargo clippy --all-targets --no-default-features -- -D warnings

- name: Build (no_std, no alloc — proves zero heap)
run: cargo build --no-default-features

- name: Test (all features)
run: cargo test --all-features

- name: Test (no_std, no alloc)
run: cargo test --no-default-features
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ target
# These are backup files generated by rustfmt
**/*.rs.bk

# Locally generated test barcodes (see examples/gen_all.rs)
/generated_barcodes/

# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb

Expand Down
120 changes: 120 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Changelog

All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
[Semantic Versioning](https://semver.org/) (with `0.x` minor bumps signalling
breaking changes).

## [0.2.0] — Unreleased

### Breaking

- **Zero-allocation core.** The primary API is now
[`BarcodeEncoder::encode_into(input, &mut [bool])`](https://docs.rs/barcodes/latest/barcodes/common/traits/trait.BarcodeEncoder.html),
which writes a symbol's modules into a caller-provided buffer and returns an
`Encoded { Linear | Matrix }` describing the written region. The crate is now
pure `no_std` with **no heap allocation** by default.
- The owned-output convenience methods `encode()` (returning `BarcodeOutput`)
and `to_svg_string()` moved behind the new **`alloc`** feature. Code that
called `Encoder::encode(...)` on 0.1.x must either enable `features = ["alloc"]`
or migrate to `encode_into`. See [Migration](#migration-from-01x) below.
- `EncodeError` messages are now `&'static str` (no allocated `String`).

### Added

- Feature flags: `alloc` (owned output + SVG string), `std` (implies `alloc`),
`image` (implies `std`, raster PNG/GIF/WebP output).
- Full-spec, scanner-verified rewrites of the larger symbologies:
- **PDF417** (ISO/IEC 15438) — byte compaction + Reed–Solomon EC.
- **GS1 DataBar Omnidirectional / RSS-14** (ISO/IEC 24724).
- **Aztec Code** (ISO/IEC 24778) — Binary Shift, Reed–Solomon over
GF(16/64/256/1024).
- **USPS Intelligent Mail (IMb)** — verified bit-for-bit against the canonical
USPS-B-3200 DAFT reference vector.
- **Royal Mail RM4SCC** — 4-state 3-row output.
- Streaming SVG rendering into any `core::fmt::Write` sink via `common::svg`.

### Fixed

- **Data Matrix** ECC 200 now produces scannable symbols, with 32×32–48×48
multi-region support for larger data.
- **EAN-13/EAN-8 L-code**, **UPC-E parity**, and the **Code 39** pattern table
corrected (symbols now scan).
- **GS1-128** now decodes correctly (Code B path).
- **UPC-A / UPC-E check digit** for odd-length data (also released as 0.1.3).

### Packaging

- `examples/` and locally generated barcodes are excluded from the published
crate.

## [0.1.3] — 2026-07-07

### Fixed

- **UPC-A / UPC-E check digit.** The shared check-digit routine weighted digits
from the left, which is only correct for even-length data (EAN-13's 12
digits). For UPC-A and UPC-E (11 data digits) the rightmost digit received the
wrong weight, producing an invalid check digit — UPC-A symbols failed to scan.
It now weights from the right (the length-independent GS1 rule); EAN-13/EAN-8
output is unchanged.

## [0.1.2] — 2026

### Fixed

- Critical **EAN-13 / EAN-8 / UPC-E** encoding fixes (L-code digits and UPC-E
parity) so retail symbols scan.
- **GS1-128** decoding correctness.

## [0.1.1] — 2026

### Fixed

- Data Matrix capacity/length handling and scannability improvements.

## [0.1.0] — 2026

- Initial release: QR, EAN-13/8, UPC-A/E, Code 128/39/93, Codabar, ITF, GS1-128,
GS1 DataBar, PDF417, Data Matrix, Aztec, USPS IMb, Royal Mail RM4SCC.

## Migration from 0.1.x

**0.1.x (owned output):**

```rust
use barcodes::common::traits::BarcodeEncoder;
use barcodes::ean_upc::ean13::Ean13;

let svg = Ean13::encode("5901234123457").unwrap().to_svg_string();
```

**0.2.0, option A — keep the convenience API** (enable `alloc`):

```toml
barcodes = { version = "0.2", features = ["alloc"] }
```

```rust
// identical code — encode() and to_svg_string() require the `alloc` feature
let svg = Ean13::encode("5901234123457").unwrap().to_svg_string();
```

**0.2.0, option B — zero allocation** (default, no features):

```rust
use barcodes::common::traits::BarcodeEncoder;
use barcodes::common::types::Encoded;
use barcodes::ean_upc::ean13::Ean13;

let mut buf = [false; 128]; // one bool per module
let Encoded::Linear { len, .. } = Ean13::encode_into("5901234123457", &mut buf).unwrap()
else { unreachable!() };
let bars = &buf[..len]; // true = dark module
```

[0.2.0]: https://github.com/ashaffah/barcodes/compare/v0.1.3...HEAD
[0.1.3]: https://github.com/ashaffah/barcodes/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/ashaffah/barcodes/compare/v0.1.1...v0.1.2
[0.1.1]: https://github.com/ashaffah/barcodes/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/ashaffah/barcodes/releases/tag/v0.1.0
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
[package]
name = "barcodes"
version = "0.1.0"
version = "0.2.0"
edition = "2024"
description = "Universal Bar/QR codes library"
keywords = ["barcode", "qrcode", "ean", "code128", "pdf417"]
categories = ["encoding", "no-std"]
license = "MIT"
repository = "https://github.com/ashaffah/barcodes"
readme = "README.md"
exclude = ["/examples", "/generated_barcodes"]

[features]
default = []
std = []
alloc = []
std = ["alloc"]
image = ["std", "dep:image"]

[dependencies]
Expand All @@ -20,5 +24,9 @@ optional = true
default-features = false
features = ["png", "gif", "webp"]

[[example]]
name = "gen_all"
required-features = ["image"]

[package.metadata.docs.rs]
all-features = true
78 changes: 65 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,79 @@
[![Rust](https://img.shields.io/badge/rust-edition_2024-orange.svg)]()

A **universal bar/QR code generation library** for Rust, supporting many symbologies.
Zero external dependencies, `no_std` compatible (requires `alloc`).
Zero external dependencies, pure `no_std`, and **zero heap allocation** by default.

## Features

- **Zero heap allocation** by default — encoders write into a caller-provided
`&mut [bool]` buffer via [`encode_into`](#zero-allocation-core); pure `no_std`,
no `alloc` required
- Zero external dependencies (default)
- `no_std` compatible (requires `alloc`)
- SVG output built-in (`to_svg_string()`)
- Optional `alloc` feature for owned-output convenience (`encode()` +
`to_svg_string()`)
- Optional image output (PNG, GIF, WebP) via `image` feature
- Supports 16+ barcode symbologies: linear, 2D, and postal

## Installation

Add `barcodes` to your `Cargo.toml`.

**Default (no_std, SVG only):**
**Default (pure `no_std`, zero allocation):**

```toml
[dependencies]
barcodes = "0.1"
barcodes = "0.2"
```

**With image output (PNG/GIF/WebP):**
**With owned output + SVG string convenience (`alloc`):**

```toml
[dependencies]
barcodes = { version = "0.1", features = ["image"] }
barcodes = { version = "0.2", features = ["alloc"] }
```

**With image output (PNG/GIF/WebP — implies `std`):**

```toml
[dependencies]
barcodes = { version = "0.2", features = ["image"] }
```

## Zero-allocation core

Every encoder implements
[`BarcodeEncoder::encode_into`](https://docs.rs/barcodes/latest/barcodes/common/traits/trait.BarcodeEncoder.html),
which writes the symbol's modules into a caller-provided buffer and returns an
`Encoded` describing the written region — no heap, no `alloc`:

```rust
use barcodes::common::traits::BarcodeEncoder;
use barcodes::common::types::Encoded;
use barcodes::ean_upc::ean13::Ean13;

let mut buf = [false; 128]; // stack buffer, one bool per module
let Encoded::Linear { len, height } = Ean13::encode_into("5901234123457", &mut buf).unwrap()
else { unreachable!() };

let bars = &buf[..len]; // true = dark module, false = light
assert_eq!(bars.len(), 95);
let _ = height;
```

2D symbologies return `Encoded::Matrix { width, height }`; their modules fill
`buf[..width * height]` in row-major order.

Render to SVG without allocating via [`common::svg`](https://docs.rs/barcodes/latest/barcodes/common/svg/index.html),
which streams into any `core::fmt::Write` sink.

> The `alloc` feature adds the convenience `Encoder::encode()` (returning an
> owned `BarcodeOutput`) and `.to_svg_string()`. The examples below use it.

> **Upgrading from 0.1.x?** `encode()` / `to_svg_string()` now live behind the
> `alloc` feature. Enable `features = ["alloc"]` to keep the old code unchanged,
> or switch to the zero-allocation `encode_into` shown above. See
> [CHANGELOG.md](CHANGELOG.md#migration-from-01x).

## Supported symbologies

| Symbology | Module | Status |
Expand Down Expand Up @@ -198,9 +243,9 @@ println!("{svg}");

```rust
use barcodes::common::traits::BarcodeEncoder;
use barcodes::gs1::databar::GS1DataBar;
use barcodes::gs1::databar::DataBar;

let output = GS1DataBar::encode("0950110153001").unwrap();
let output = DataBar::encode("0950110153001").unwrap();
let svg = output.to_svg_string();
println!("{svg}");
```
Expand Down Expand Up @@ -299,11 +344,18 @@ let img = qr.to_image(4); // module_size = 4px
img.save("qrcode.png").unwrap();
```

## `no_std` Support
## `no_std` and features

This library is pure `no_std` by default and performs **no heap allocation** —
the default build does not even link `alloc`, so any accidental allocation is a
compile error.

This library is `no_std` compatible by default and only requires the `alloc` crate.
Enable the `std` feature if you need standard library support.
Image output (`to_image()`) requires the `image` feature, which implies `std`.
| Feature | Adds | Implies |
| ----------- | ----------------------------------------------------- | ------- |
| _(default)_ | zero-alloc `encode_into` + `core::fmt::Write` SVG | — |
| `alloc` | owned `encode()` → `BarcodeOutput`, `to_svg_string()` | — |
| `std` | `std::error::Error` for `EncodeError` | `alloc` |
| `image` | raster output `to_image()` (PNG/GIF/WebP) | `std` |

## Modules Overview

Expand Down
Loading