# `Color.ANSI`

ANSI SGR (Select Graphic Rendition) colour parsing and encoding.

Supports the three common forms of ANSI colour used by modern
terminals:

* **16-colour palette** — `\e[30m`..`\e[37m` (foreground),
  `\e[40m`..`\e[47m` (background), plus the bright versions
  `\e[90m`..`\e[97m` and `\e[100m`..`\e[107m`.

* **256-colour indexed palette** — `\e[38;5;Nm` (foreground),
  `\e[48;5;Nm` (background). Indices 0–15 are the base
  16-colour palette; 16–231 are a 6×6×6 colour cube with levels
  `[0, 95, 135, 175, 215, 255]`; 232–255 are a 24-step grayscale
  ramp starting at 8 with a step of 10.

* **Truecolor (24-bit)** — `\e[38;2;R;G;Bm` (foreground),
  `\e[48;2;R;G;Bm` (background), where `R`, `G` and `B` are
  0..255 bytes.

The `:mode` option on `to_string/2` selects which form to emit.
When the target is the 16- or 256-colour palette and the input
colour isn't an exact palette entry, the encoder picks the
perceptually nearest palette entry using CIEDE2000 in CIELAB.

## Examples

    iex> Color.ANSI.to_string("red") == "\e[38;2;255;0;0m"
    true

    iex> Color.ANSI.to_string("red", mode: :ansi256) == "\e[38;5;196m"
    true

    iex> Color.ANSI.to_string("red", mode: :ansi16, layer: :background) == "\e[101m"
    true

    iex> {:ok, c, :foreground} = Color.ANSI.parse("\e[38;2;255;0;0m")
    iex> Color.to_hex(c)
    "#ff0000"

    iex> {:ok, c, :background} = Color.ANSI.parse("\e[41m")
    iex> Color.to_hex(c)
    "#aa0000"

    iex> Color.ANSI.nearest_256("#ff0000")
    196

    iex> Color.ANSI.wrap("hello", "red") == "\e[38;2;255;0;0mhello\e[0m"
    true

# `layer`

```elixir
@type layer() :: :foreground | :background
```

Which ANSI layer a colour targets.

# `mode`

```elixir
@type mode() :: :truecolor | :ansi256 | :ansi16
```

How `to_string/2` should encode the colour.

# `nearest_16`

```elixir
@spec nearest_16(Color.input()) :: 0..15
```

Returns the 16-colour palette index of the entry perceptually
nearest to `color`, using CIEDE2000 in CIELAB.

### Arguments

* `color` is any input accepted by `Color.new/1`.

### Returns

* An integer in `0..15`.

### Examples

    iex> Color.ANSI.nearest_16("#ff0000")
    9

    iex> Color.ANSI.nearest_16("#000000")
    0

# `nearest_256`

```elixir
@spec nearest_256(Color.input()) :: 0..255
```

Returns the 256-colour palette index of the entry perceptually
nearest to `color`, using CIEDE2000 in CIELAB.

### Arguments

* `color` is any input accepted by `Color.new/1`.

### Returns

* An integer in `0..255`.

### Examples

    iex> Color.ANSI.nearest_256("#ff0000")
    196

    iex> Color.ANSI.nearest_256("#000000")
    0

# `palette_16`

```elixir
@spec palette_16() :: [{0..15, {0..255, 0..255, 0..255}}]
```

Returns the 16-colour palette as a list of `{index, {r, g, b}}`
tuples.

### Examples

    iex> length(Color.ANSI.palette_16())
    16

# `palette_256`

```elixir
@spec palette_256() :: [{0..255, {0..255, 0..255, 0..255}}]
```

Returns the full 256-colour palette as a list of
`{index, {r, g, b}}` tuples, with each channel in `0..255`.

### Examples

    iex> length(Color.ANSI.palette_256())
    256

    iex> Enum.find(Color.ANSI.palette_256(), &match?({196, _}, &1))
    {196, {255, 0, 0}}

# `parse`

```elixir
@spec parse(binary()) :: {:ok, Color.SRGB.t(), layer()} | {:error, Exception.t()}
```

Parses an ANSI SGR escape sequence and returns the encoded colour.

The function accepts any of the three canonical ANSI colour forms.
Style parameters (bold, italic, reverse, …) that precede or
follow the colour are ignored; only the colour is extracted.
Sequences that contain no colour descriptor (for example `\e[0m`
reset, `\e[39m` default-foreground, or `\e[1m` bold-only)
return an error.

### Arguments

* `sequence` is a binary string starting with `ESC [` and ending
  with `m`. The leading ESC character may be written using the
  Elixir `"\e"` escape or the raw byte `0x1b`.

### Returns

* `{:ok, %Color.SRGB{}, :foreground | :background}` on success.

* `{:error, %Color.ANSI.ParseError{}}` if the sequence cannot be
  interpreted.

# `reset`

```elixir
@spec reset() :: String.t()
```

Returns the ANSI reset escape sequence (`ESC[0m`).

### Examples

    iex> Color.ANSI.reset() == "\e[0m"
    true

# `to_string`

```elixir
@spec to_string(
  Color.input(),
  keyword()
) :: String.t()
```

Serialises a colour to an ANSI SGR escape sequence.

### Arguments

* `color` is any input accepted by `Color.new/1` — a colour
  struct, a bare list, a hex string, a CSS named colour, or an
  atom.

* `options` is a keyword list.

### Options

* `:mode` — one of:

  * `:truecolor` (default) — emits `\e[38;2;R;G;Bm`
    (24-bit, modern terminals only).

  * `:ansi256` — emits `\e[38;5;Nm` using the perceptually
    nearest 256-colour palette index.

  * `:ansi16` — emits `\e[30m`..`\e[37m` or `\e[90m`..`\e[97m`
    using the perceptually nearest 16-colour palette index.

* `:layer` — `:foreground` (default) or `:background`.

### Returns

* A binary string containing the escape sequence.

### Examples

    iex> Color.ANSI.to_string("red") == "\e[38;2;255;0;0m"
    true

    iex> Color.ANSI.to_string(%Color.SRGB{r: 0.0, g: 1.0, b: 0.0}) == "\e[38;2;0;255;0m"
    true

    iex> Color.ANSI.to_string("red", layer: :background) == "\e[48;2;255;0;0m"
    true

# `wrap`

```elixir
@spec wrap(String.t(), Color.input(), keyword()) :: String.t()
```

Wraps `string` in the ANSI escape that sets the given colour,
followed by a reset.

### Arguments

* `string` is any `String.t()`.

* `color` is any input accepted by `Color.new/1`.

* `options` is the same as `to_string/2`.

### Returns

* A binary string with the wrapped text.

### Examples

    iex> Color.ANSI.wrap("hi", "red") == "\e[38;2;255;0;0mhi\e[0m"
    true

    iex> Color.ANSI.wrap("hi", "red", mode: :ansi256) == "\e[38;5;196mhi\e[0m"
    true

---

*Consult [api-reference.md](api-reference.md) for complete listing*
