README.md

<p align="center">
  <img src="assets/logo_styled.svg" alt="Qiroex" width="180" />
</p>

<h1 align="center">Qiroex</h1>

<p align="center">
  <strong>A pure-Elixir QR code generator — zero dependencies, full spec, beautiful output.</strong>
</p>

<p align="center">
  <a href="https://hex.pm/packages/qiroex"><img src="https://img.shields.io/hexpm/v/qiroex.svg" alt="Hex.pm Version" /></a>
  <a href="https://hexdocs.pm/qiroex"><img src="https://img.shields.io/badge/docs-hexdocs-blue.svg" alt="Documentation" /></a>
  <a href="https://github.com/ricn/qiroex/actions/workflows/ci.yml"><img src="https://github.com/ricn/qiroex/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
  <a href="https://github.com/ricn/qiroex/blob/main/LICENSE"><img src="https://img.shields.io/hexpm/l/qiroex.svg" alt="License" /></a>
</p>

<p align="center">
  <a href="#installation">Installation</a> · <a href="#quick-start">Quick Start</a> · <a href="#styling">Styling</a> · <a href="#logo-embedding">Logos</a> · <a href="#payload-builders">Payloads</a> · <a href="#api-reference">API</a>
</p>

---

Qiroex generates **valid, scannable QR codes** entirely in Elixir with no external dependencies — no C NIFs, no system libraries, no ImageMagick. It implements the full **ISO 18004** specification and outputs to **SVG**, **PNG**, and **terminal**.

## Features

- **Zero dependencies** — pure Elixir, runs anywhere the BEAM runs
- **Full QR spec** — versions 1–40, error correction L/M/Q/H, all 4 encoding modes (numeric, alphanumeric, byte, kanji), 8 mask patterns
- **Three output formats** — SVG (vector), PNG (raster), terminal (Unicode art)
- **Visual styling** — module shapes (circle, rounded, diamond), custom colors, gradients, finder pattern colors
- **Logo embedding** — embed SVG or raster image logos (PNG, JPEG, WEBP, GIF, BMP) with automatic coverage validation
- **11 payload builders** — WiFi, URL, Email, SMS, Phone, Geo, vCard, vEvent, MeCard, Bitcoin, WhatsApp
- **Input validation** — descriptive error messages for every misconfiguration
- **Thoroughly tested** — 500+ unit and integration tests

## Installation

Add `qiroex` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:qiroex, "~> 0.1.0"}
  ]
end
```

Then run `mix deps.get`.

## Quick Start

### Generate and save an SVG

```elixir
Qiroex.save_svg("https://example.com", "qr.svg")
```

<img src="assets/basic.svg" alt="Basic QR code" width="200" />

### Generate and save a PNG

```elixir
Qiroex.save_png("https://example.com", "qr.png")
```

### Print to terminal

```elixir
Qiroex.print("Hello from Qiroex!")
```

### Work with raw data

```elixir
# Get an SVG string
{:ok, svg} = Qiroex.to_svg("Hello")

# Get a PNG binary
{:ok, png} = Qiroex.to_png("Hello")

# Get a QR struct for inspection
{:ok, qr} = Qiroex.encode("Hello")
Qiroex.info(qr)
# => %{version: 1, ec_level: :m, mode: :byte, mask: 4, modules: 21, data_bytes: 5}

# Get the raw 0/1 matrix
{:ok, matrix} = Qiroex.to_matrix("Hello")
```

## Encoding Options

Control the encoding process with these options (available on all functions):

```elixir
# Error correction level (:l, :m, :q, :h)
Qiroex.save_svg("Hello", "qr.svg", level: :h)

# Force a specific version (1–40)
Qiroex.save_svg("Hello", "qr.svg", version: 5)

# Force encoding mode
Qiroex.save_svg("12345", "qr.svg", mode: :numeric)

# Force mask pattern (0–7)
Qiroex.save_svg("Hello", "qr.svg", mask: 2)

# Combine freely
Qiroex.save_svg("Hello", "qr.svg", level: :q, version: 3, mask: 0)
```

## Render Options

### SVG Options

```elixir
Qiroex.save_svg("Hello", "qr.svg",
  module_size: 12,             # pixel size of each module (default: 10)
  quiet_zone: 2,               # modules of white border (default: 4)
  dark_color: "#4B275F",       # any CSS color
  light_color: "#F4F1F6"       # background color
)
```

<img src="assets/colors.svg" alt="Custom colors" width="200" />

### PNG Options

```elixir
Qiroex.save_png("Hello", "qr.png",
  module_size: 20,                   # pixel size per module (default: 10)
  quiet_zone: 3,                     # quiet zone modules (default: 4)
  dark_color: {75, 39, 95},          # {r, g, b} tuple, 0–255
  light_color: {244, 241, 246}       # background color
)
```

## Styling

Qiroex supports rich visual customization through the `Qiroex.Style` struct. All style options apply to **SVG output**; PNG supports finder pattern colors.

### Module Shapes

Choose how individual data modules are rendered:

```elixir
# Circular dots
style = Qiroex.Style.new(module_shape: :circle)
Qiroex.save_svg("Hello", "circles.svg", style: style)

# Rounded squares
style = Qiroex.Style.new(module_shape: :rounded, module_radius: 0.4)
Qiroex.save_svg("Hello", "rounded.svg", style: style)

# Diamond (rotated squares)
style = Qiroex.Style.new(module_shape: :diamond)
Qiroex.save_svg("Hello", "diamond.svg", style: style)
```

<table>
  <tr>
    <td align="center"><img src="assets/circles.svg" alt="Circle modules" width="180" /><br /><code>:circle</code></td>
    <td align="center"><img src="assets/rounded.svg" alt="Rounded modules" width="180" /><br /><code>:rounded</code></td>
    <td align="center"><img src="assets/diamond.svg" alt="Diamond modules" width="180" /><br /><code>:diamond</code></td>
  </tr>
</table>

### Finder Pattern Colors

Customize the three concentric layers of each finder pattern independently:

```elixir
style = Qiroex.Style.new(
  module_shape: :rounded,
  module_radius: 0.3,
  finder: %{
    outer: "#E63946",    # 7×7 dark border ring
    inner: "#F1FAEE",    # 5×5 light ring
    eye:   "#1D3557"     # 3×3 dark center
  }
)

Qiroex.save_svg("Hello", "finder.svg", style: style)
```

<img src="assets/finder_colors.svg" alt="Finder pattern colors" width="200" />

### Gradient Fills

Apply linear or radial gradients to dark modules:

```elixir
# Linear gradient at 135°
style = Qiroex.Style.new(
  module_shape: :circle,
  gradient: %{
    type: :linear,
    start_color: "#667EEA",
    end_color: "#764BA2",
    angle: 135
  }
)

Qiroex.save_svg("Hello", "gradient.svg", style: style)
```

<table>
  <tr>
    <td align="center"><img src="assets/gradient.svg" alt="Linear gradient" width="180" /><br />Linear</td>
    <td align="center"><img src="assets/radial.svg" alt="Radial gradient" width="180" /><br />Radial</td>
    <td align="center"><img src="assets/styled.svg" alt="Full styled" width="180" /><br />Combined</td>
  </tr>
</table>

### Kitchen Sink

Combine everything for maximum visual impact:

```elixir
style = Qiroex.Style.new(
  module_shape: :circle,
  finder: %{outer: "#2D3436", inner: "#FFFFFF", eye: "#E17055"},
  gradient: %{type: :linear, start_color: "#2D3436", end_color: "#636E72", angle: 45}
)

Qiroex.save_svg("https://elixir-lang.org", "styled.svg", style: style)
```

## Logo Embedding

Embed a logo in the center of your QR code. Qiroex supports both **SVG markup** and **raster images** (PNG, JPEG, WEBP, GIF, BMP) — all with zero dependencies. It automatically clears the modules behind the logo area and validates that the logo doesn't exceed the error correction capacity.

### SVG Logo

```elixir
logo = Qiroex.Logo.new(
  svg: ~s(<svg viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="40" fill="#9B59B6"/>
    <text x="50" y="62" text-anchor="middle" font-size="36"
          font-weight="bold" fill="white" font-family="sans-serif">Ex</text>
  </svg>),
  size: 0.22,          # 22% of QR code size
  shape: :circle,      # background shape (:square, :rounded, :circle)
  padding: 1           # padding in modules around the logo
)

# Use high EC level (:h) for best scan reliability with logos
Qiroex.save_svg("https://elixir-lang.org", "logo.svg", level: :h, logo: logo)
```

### Raster Image Logo (PNG, JPEG, WEBP, ...)

Load any image file and embed it directly — the format is auto-detected from the binary:

```elixir
logo = Qiroex.Logo.new(
  image: File.read!("company_logo.png"),
  size: 0.22,
  shape: :circle,
  padding: 1
)

Qiroex.save_svg("https://example.com", "branded.svg", level: :h, logo: logo)
```

Raster images are embedded as base64 data URIs inside the SVG — no external files or dependencies needed. You can also specify the format explicitly:

```elixir
Qiroex.Logo.new(image: jpeg_bytes, image_type: :jpeg, size: 0.2)
```

<table>
  <tr>
    <td align="center"><img src="assets/logo.svg" alt="Logo embedding" width="200" /><br />SVG Logo</td>
    <td align="center"><img src="assets/logo_styled.svg" alt="Styled + Logo" width="200" /><br />Styled + Logo</td>
    <td align="center"><img src="assets/logo_png.svg" alt="PNG Logo" width="200" /><br />PNG Logo</td>
  </tr>
</table>

### Logo + Style

Logos work seamlessly with all styling options:

```elixir
style = Qiroex.Style.new(
  module_shape: :rounded,
  module_radius: 0.3,
  finder: %{outer: "#4B275F", inner: "#FFFFFF", eye: "#9B59B6"}
)

Qiroex.save_svg("https://elixir-lang.org", "branded.svg",
  level: :h, style: style, logo: logo)
```

### Logo Options

| Option | Default | Description |
|--------|---------|-------------|
| `:svg` | — | SVG markup string (provide `:svg` **or** `:image`) |
| `:image` | — | Binary image data: PNG, JPEG, WEBP, GIF, BMP (provide `:image` **or** `:svg`) |
| `:image_type` | *auto-detected* | Image format: `:png`, `:jpeg`, `:webp`, `:gif`, `:bmp` |
| `:size` | `0.2` | Logo size as fraction of QR code (0.0–0.4) |
| `:padding` | `1` | Padding around logo in modules |
| `:background` | `"#ffffff"` | Background color behind the logo |
| `:shape` | `:square` | Background shape: `:square`, `:rounded`, `:circle` |
| `:border_radius` | `4` | Corner radius for `:rounded` shape |

### Coverage Validation

Qiroex automatically validates that the logo doesn't cover too many modules. If the logo is too large for the chosen error correction level, you'll get a clear error message:

```elixir
large_logo = Qiroex.Logo.new(svg: "<svg/>", size: 0.4)

{:error, message} = Qiroex.to_svg("Hello", level: :l, logo: large_logo)
# => "Logo covers 28.3% of modules, but EC level :l safely supports only 5.6%.
#     Use a higher EC level or a smaller logo size."
```

> **Tip:** Always use error correction level `:h` when embedding logos for maximum scan reliability.

## Payload Builders

Generate structured data payloads for common QR code use cases with a single function call:

```elixir
# WiFi network — scan to connect
{:ok, svg} = Qiroex.payload(:wifi,
  [ssid: "CoffeeShop", password: "latte2024"],
  :svg, dark_color: "#2C3E50")
```

Qiroex ships with **11 payload builders** covering the most common QR code use cases:

### WiFi

Scan to auto-connect to a network.

```elixir
{:ok, svg} = Qiroex.payload(:wifi,
  [ssid: "MyNetwork", password: "secret123", auth: :wpa],
  :svg)
```

<img src="assets/wifi.svg" alt="WiFi QR" width="180" />

### URL

Open a website in the browser.

```elixir
{:ok, svg} = Qiroex.payload(:url,
  [url: "https://elixir-lang.org"],
  :svg)
```

<img src="assets/url.svg" alt="URL QR" width="180" />

### Email

Compose an email with pre-filled fields.

```elixir
{:ok, svg} = Qiroex.payload(:email,
  [to: "hello@example.com", subject: "Hi!", body: "Nice to meet you."],
  :svg)
```

<img src="assets/email.svg" alt="Email QR" width="180" />

### SMS

Open the messaging app with a pre-filled text.

```elixir
{:ok, svg} = Qiroex.payload(:sms,
  [number: "+1-555-0123", message: "Hello!"],
  :svg)
```

<img src="assets/sms.svg" alt="SMS QR" width="180" />

### Phone

Initiate a phone call.

```elixir
{:ok, svg} = Qiroex.payload(:phone,
  [number: "+1-555-0199"],
  :svg)
```

<img src="assets/phone.svg" alt="Phone QR" width="180" />

### Geo Location

Open a map to a specific location.

```elixir
{:ok, svg} = Qiroex.payload(:geo,
  [latitude: 48.8566, longitude: 2.3522, query: "Eiffel Tower"],
  :svg)
```

<img src="assets/geo.svg" alt="Geo QR" width="180" />

### vCard

Share a full contact card.

```elixir
{:ok, svg} = Qiroex.payload(:vcard,
  [first_name: "Jane", last_name: "Doe",
   phone: "+1-555-0199", email: "jane@example.com",
   org: "Acme Corp", title: "Engineer"],
  :svg)
```

<img src="assets/vcard.svg" alt="vCard QR" width="180" />

### vEvent

Add a calendar event.

```elixir
{:ok, svg} = Qiroex.payload(:vevent,
  [summary: "Team Standup",
   start: ~U[2026-03-01 09:00:00Z],
   end: ~U[2026-03-01 09:30:00Z],
   location: "Conference Room A"],
  :svg)
```

<img src="assets/vevent.svg" alt="vEvent QR" width="180" />

### MeCard

Share a contact (simpler alternative to vCard, popular on mobile).

```elixir
{:ok, svg} = Qiroex.payload(:mecard,
  [name: "Doe,Jane", phone: "+1-555-0199", email: "jane@example.com"],
  :svg)
```

<img src="assets/mecard.svg" alt="MeCard QR" width="180" />

### Bitcoin

Request a Bitcoin payment (BIP-21).

```elixir
{:ok, svg} = Qiroex.payload(:bitcoin,
  [address: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
   amount: 0.001, label: "Donation"],
  :svg)
```

<img src="assets/bitcoin.svg" alt="Bitcoin QR" width="180" />

### WhatsApp

Open a WhatsApp chat with a pre-filled message.

```elixir
{:ok, svg} = Qiroex.payload(:whatsapp,
  [number: "+1234567890", message: "Hello from Qiroex!"],
  :svg)
```

<img src="assets/whatsapp.svg" alt="WhatsApp QR" width="180" />

The third argument is the output format: `:svg`, `:png`, `:terminal`, `:matrix`, or `:encode`.

## Error Handling

All functions return `{:ok, result}` / `{:error, reason}` tuples. Bang variants raise `ArgumentError`:

```elixir
# Safe — returns error tuple
{:error, message} = Qiroex.encode("")
# => "Data cannot be empty"

{:error, message} = Qiroex.to_svg("test", level: :x)
# => "invalid error correction level: :x. Must be one of [:l, :m, :q, :h]"

{:error, message} = Qiroex.to_png("test", dark_color: "#000")
# => "invalid dark_color: \"#000\". Must be an {r, g, b} tuple with values 0–255"

# Bang — raises on error
svg = Qiroex.to_svg!("Hello")        # returns SVG string directly
png = Qiroex.to_png!("Hello")        # returns PNG binary directly
qr  = Qiroex.encode!("Hello")        # returns QR struct directly
```

## API Reference

### Core Functions

| Function | Description |
|----------|-------------|
| `Qiroex.encode(data, opts)` | Encode data into a `%Qiroex.QR{}` struct |
| `Qiroex.to_svg(data, opts)` | Generate SVG string |
| `Qiroex.to_png(data, opts)` | Generate PNG binary |
| `Qiroex.to_terminal(data, opts)` | Generate terminal-printable string |
| `Qiroex.to_matrix(data, opts)` | Generate 2D list of `0`/`1` |
| `Qiroex.save_svg(data, path, opts)` | Write SVG to file |
| `Qiroex.save_png(data, path, opts)` | Write PNG to file |
| `Qiroex.print(data, opts)` | Print QR code to terminal |
| `Qiroex.payload(type, opts, format)` | Generate payload QR code |
| `Qiroex.info(qr)` | Get metadata about an encoded QR |

All functions have bang (`!`) variants that raise instead of returning error tuples.

### Encoding Options

| Option | Values | Default | Description |
|--------|--------|---------|-------------|
| `:level` | `:l`, `:m`, `:q`, `:h` | `:m` | Error correction level |
| `:version` | `1`–`40`, `:auto` | `:auto` | QR version (size) |
| `:mode` | `:numeric`, `:alphanumeric`, `:byte`, `:kanji`, `:auto` | `:auto` | Encoding mode |
| `:mask` | `0`–`7`, `:auto` | `:auto` | Mask pattern |

### SVG Render Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `:module_size` | integer | `10` | Pixel size of each module |
| `:quiet_zone` | integer | `4` | Quiet zone border in modules |
| `:dark_color` | string | `"#000000"` | CSS color for dark modules |
| `:light_color` | string | `"#ffffff"` | CSS color for background |
| `:style` | `%Style{}` | `nil` | Visual styling configuration |
| `:logo` | `%Logo{}` | `nil` | Center logo configuration |

### PNG Render Options

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `:module_size` | integer | `10` | Pixel size of each module |
| `:quiet_zone` | integer | `4` | Quiet zone border in modules |
| `:dark_color` | `{r,g,b}` | `{0,0,0}` | RGB tuple for dark modules |
| `:light_color` | `{r,g,b}` | `{255,255,255}` | RGB tuple for background |
| `:style` | `%Style{}` | `nil` | Finder pattern colors |

## Architecture

Qiroex implements the full QR code pipeline from scratch:

```
Data → Mode Detection → Version Selection → Bit Encoding
    → Reed-Solomon EC → Interleaving → Matrix Placement
    → Masking (8 patterns × 4 penalty rules) → Format Info
    → Render (SVG / PNG / Terminal)
```

Key implementation details:

- **Galois Field GF(2⁸)** with primitive polynomial 285 and compile-time lookup tables
- **Reed-Solomon** error correction with polynomial division
- **BCH** encoding for format and version information
- **Matrix** stored as a `Map` of `{row, col} => :dark | :light` with `MapSet` for reserved positions
- **SVG** built with IO lists for zero-copy string assembly
- **PNG** encoded with pure-Erlang `:zlib` and `:erlang.crc32` — no image libraries needed

## License

MIT License. See [LICENSE](LICENSE) for details.