# `Text.WordCloud.SVG`
[🔗](https://github.com/kipcole9/text/blob/v0.5.0/lib/word_cloud/svg.ex#L1)

Renders a laid-out word cloud as an SVG string.

Takes the output of `Text.WordCloud.Layout.layout/2` and produces a
self-contained SVG document. The result is a string suitable for
writing to a `.svg` file, embedding inline in HTML, or piping to a
rasteriser for PNG/PDF export. No DOM, no NIFs, no runtime
dependencies — just text concatenation.

### Colour palettes

Per-word colour comes from a pluggable `:palette` option:

* **A list of colour inputs** — hex strings, CSS named colours, or
  anything `Color.new/1` accepts (when the optional `:color`
  dependency is present). Without `:color`, only hex strings (`"#rrggbb"`
  or `"#rgb"`) are accepted directly.

* **A `Color.Palette.Tonal` struct** — uses its stops as the
  palette. Higher-weight terms map to darker (or lighter, configurable)
  stops.

* **A `Color.Palette.Theme` struct** — uses the theme's `:primary`
  scale; same mapping rules as `:tonal`.

* **`nil`** *(default)* — every term renders in a single fill colour
  (`:fill` option, default `"#1f2937"`).

### Mapping strategy

The `:color_strategy` option controls how each term picks a colour
from the palette:

* `:by_weight` *(default)* — terms are sorted by weight descending
  and mapped onto the palette stops in order. The top-weighted term
  gets the first palette colour. With a tonal scale you typically
  want `:by_weight, palette_direction: :descending` so the highest-
  weighted term gets the *darkest* stop.

* `:by_index` — round-robin through the palette in placement order.

* `:by_hash` — `:erlang.phash2(term, palette_size)` so identical
  terms always pick the same colour across runs.

### Background

By default the SVG has no background. Pass `:background` (any colour
input) to render a `<rect>` filling the canvas before the words.

### Fonts

SVG itself doesn't embed font files; the rendered file references
`:font_family` (default `"sans-serif"`). For a self-contained PDF or
PNG you'll typically want to pass a specific webfont stack and pair
it with a `:font_metrics` callback in the layout step that consults
metrics for that exact font.

# `color_input`

```elixir
@type color_input() :: String.t() | struct() | atom() | tuple()
```

# `palette_direction`

```elixir
@type palette_direction() :: :ascending | :descending
```

Direction of the palette traversal under `:by_weight`. `:ascending`
walks the palette in its natural order (typically light → dark);
`:descending` reverses it (dark → light), the more common choice for
reading clouds where higher-weighted = "darker".

# `placement`

```elixir
@type placement() :: Text.WordCloud.Layout.placement()
```

# `strategy`

```elixir
@type strategy() :: :by_weight | :by_index | :by_hash
```

How term weights map onto palette colours.

# `render`

```elixir
@spec render(
  [placement()],
  keyword()
) :: String.t()
```

Renders `placements` as an SVG string.

### Arguments

* `placements` is a list of placement maps as returned by
  `Text.WordCloud.Layout.layout/2`.

### Options

* `:width` — canvas width in pixels. Default `800`.

* `:height` — canvas height in pixels. Default `600`.

* `:palette` — colour source: a list of colour inputs, a
  `Color.Palette.Tonal` struct, a `Color.Palette.Theme` struct, or
  `nil` (single-colour rendering). Default `nil`.

* `:color_strategy` — mapping strategy. One of `:by_weight`,
  `:by_index`, `:by_hash`. Default `:by_weight`.

* `:palette_direction` — only used with `:by_weight` and a
  `Color.Palette.Tonal`/`Theme`. `:descending` (default) maps the
  top weight to the darkest stop; `:ascending` maps it to the
  lightest.

* `:fill` — fallback fill colour for terms when no palette is given,
  or for terms that don't get a palette colour. Default `"#1f2937"`.

* `:background` — colour input for a full-canvas `<rect>` background,
  or `nil` for transparent. Default `nil`.

* `:font_family` — CSS font-family stack. Default `"sans-serif"`.

* `:font_weight` — CSS font-weight value. Default `"600"`.

### Returns

* An SVG document as a binary string. The result starts with
  `<?xml version="1.0" encoding="UTF-8"?>` and is safe to write
  directly to a `.svg` file.

### Examples

    iex> placements = [
    ...>   %{term: "hello", weight: 1.0, count: 5, kind: :word,
    ...>     x: 200.0, y: 100.0, width: 60.0, height: 24.0,
    ...>     font_size: 24.0, rotation: 0}
    ...> ]
    iex> svg = Text.WordCloud.SVG.render(placements, width: 400, height: 200)
    iex> svg =~ "<svg"
    true
    iex> svg =~ "hello"
    true

---

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