+
+---
+
+`MarkupText` pairs a plain string with immutable, layered markup runs. The same value can render
+as ANSI, HTML, Pueblo, MXP, BBCode, or plain text—and round-trip through JSON without discarding
+markup a reader does not understand.
+
+```text
+ ┌─ ANSI
+ ├─ HTML
+plain text + runs ┼─ MXP / Pueblo
+ ├─ BBCode
+ └─ plain text
+```
-[](https://github.com/SharpMUSH/MarkupString/actions/workflows/ci.yml)
-[](https://www.nuget.org/packages/MarkupString)
-[](https://www.nuget.org/packages/MarkupString.Ansi)
-[](https://www.nuget.org/packages/MarkupString.Html)
-[](https://www.nuget.org/packages/MarkupString.Mxp)
-[](https://www.nuget.org/packages/MarkupString.Pueblo)
-[](LICENSE)
+String operations preserve those runs. Layout uses terminal display cells, understands wide CJK,
+combining marks, and emoji sequences, and never cuts a grapheme cluster in half.
-**Immutable styled text for terminals and the web.** One value — a plain string plus layered
-markup runs over it — renders to ANSI, HTML, Pueblo, MXP, BBCode or plain text, and round-trips
-through JSON without losing a layer the reader does not understand.
+## One value, native output
-```csharp
-var text = MarkupText.Concat(
- MarkupText.Plain("Hello, "),
- MarkupText.Wrap(AnsiCodeParser.Parse("hr"), "world"));
+Choose the renderer at the boundary. The underlying `MarkupText` stays the same while each target
+gets the representation it understands best.
-text.Render(MarkupFormat.Ansi); // Hello, \e[1;31mworld\e[0m
-text.Render(MarkupFormat.Html); // Hello, world
-text.Render(MarkupFormat.Plain); // Hello, world
-```
+### Box drawing becomes browser structure
+
+The ANSI renderer emits exact terminal cells and SGR colour. The HTML renderer turns that same
+block tree into responsive fieldsets, definitions, flex rows, rules, and meters.
+
+
+
+### Images become useful terminal art
+
+A `Figure` sends text art and flowing copy to a terminal, then becomes a real image with alt text
+and naturally flowing content in HTML.
-Slicing, padding, wrapping, trimming and the rest of the string operations carry the markup with
-them, and measure in **display cells** — wide CJK, combining marks and emoji sequences count
-correctly, and no operation ever cuts a grapheme cluster in half. On top of them sits a column
-layout engine: wrap, justify, fill, and assemble columns into aligned rows.
+
-## Install
+## Why MarkupString?
+
+- **One source of truth** — keep content and semantics together, then choose the output format at
+ the boundary.
+- **Immutable values** — slicing, editing, wrapping, padding, and composition return new values
+ while preserving markup.
+- **Unicode-correct layout** — distinguish UTF-16 length, grapheme count, and terminal display
+ width.
+- **Extensible formats** — add an `IMarkup`, emitter, and codec through an explicit registry; no
+ runtime discovery or reflection.
+- **Streaming output** — render directly to an `IBufferWriter` when allocating a string is
+ unnecessary.
+- **Native AOT ready** — every package is trimming-safe and free of reflection and dynamic code.
+
+## Quick start
+
+Install the core model and the markup kinds you need:
```sh
dotnet add package MarkupString
dotnet add package MarkupString.Ansi
+```
+
+Register those kinds once at startup, build a value, and render it explicitly:
+
+```csharp
+using MarkupString;
+using MarkupString.Ansi;
+
+MarkupRegistry.Default = MarkupRegistry.Empty.WithAnsi();
+
+var greeting = MarkupText.Concat(
+[
+ MarkupText.Plain("Hello, "),
+ MarkupText.Wrap(AnsiCodeParser.Parse("hr"), "world"),
+ MarkupText.Plain("!")
+]);
+
+greeting.Render(MarkupFormat.Ansi); // Hello, \e[1;31mworld\e[0m!
+greeting.Render(MarkupFormat.Html); // Hello, world!
+greeting.Render(MarkupFormat.Plain); // Hello, world!
+greeting.ToString(); // Hello, world! — always plain text
+```
+
+Need a buffer instead of a new string?
+
+```csharp
+greeting.RenderTo(MarkupFormat.Ansi, bufferWriter);
+```
+
+See [Getting started](docs/getting-started.md) for registry lifetime, direct style construction,
+parsing existing ANSI, and serialization.
+
+## Packages
+
+Choose only the markup kinds your application uses. The core package deliberately has no
+rendering opinions.
+
+| Package | Purpose |
+|---|---|
+| [`MarkupString`](https://www.nuget.org/packages/MarkupString) | `MarkupText`, runs, formats, registry and emitter contracts, JSON serialization, Unicode helpers, layout, and the shared sound/image/pane/gauge vocabulary. |
+| [`MarkupString.Ansi`](https://www.nuget.org/packages/MarkupString.Ansi) | ANSI colours and attributes, links, code and escape-sequence parsers, multi-format emitters, and the matching HTML stylesheet. |
+| [`MarkupString.Html`](https://www.nuget.org/packages/MarkupString.Html) | Checked raw HTML markup with configurable tag policies for trusted and untrusted input. |
+| [`MarkupString.Mxp`](https://www.nuget.org/packages/MarkupString.Mxp) | The shared vocabulary expressed as client-supported MXP elements. |
+| [`MarkupString.Pueblo`](https://www.nuget.org/packages/MarkupString.Pueblo) | The shared vocabulary expressed through Pueblo client extensions. |
+
+```sh
dotnet add package MarkupString.Html
dotnet add package MarkupString.Mxp
dotnet add package MarkupString.Pueblo
```
-| Package | What it gives you |
-|---|---|
-| [`MarkupString`](https://www.nuget.org/packages/MarkupString) | The `MarkupText` type, runs, formats, the registry, the emitter/codec contracts, the JSON serializer, grapheme and display-width helpers, and the shared vocabulary — sounds, pictures, panes, gauges — that the format packages write. No rendering opinions. |
-| [`MarkupString.Ansi`](https://www.nuget.org/packages/MarkupString.Ansi) | Terminal styling: colours (16 / xterm-256 / truecolor), attributes, links; an `ansi()` code parser and an escape-sequence parser; emitters for ANSI, HTML, Pueblo, MXP and BBCode; and `AnsiCss`, the stylesheet for the `ms-*` classes the HTML emitters write. |
-| [`MarkupString.Html`](https://www.nuget.org/packages/MarkupString.Html) | Raw HTML tag markup — an anchor, a `
`, a `` — with checked construction and tag policies for untrusted input; the shared vocabulary for a browser. |
-| [`MarkupString.Mxp`](https://www.nuget.org/packages/MarkupString.Mxp) | The shared vocabulary as MXP's elements, held to what the client said it supports. |
-| [`MarkupString.Pueblo`](https://www.nuget.org/packages/MarkupString.Pueblo) | The shared vocabulary in the Pueblo client's own extensions. |
+All packages share one version and are released together.
-The core package renders nothing on its own: emitters live in the kind packages, so a consumer
-that only needs one of them pays for one of them, and a kind of your own is a first-class peer
-rather than a fork.
+## Unicode-aware layout
-## Getting started
+MarkupString keeps three measurements separate because they answer different questions:
-```csharp
-using MarkupString;
-using MarkupString.Ansi;
-using MarkupString.Html;
-using MarkupString.Mxp;
-using MarkupString.Pueblo;
-
-// Once, at startup. Set-once: a second, different registry throws.
-MarkupRegistry.Default = MarkupRegistry.Empty.WithAnsi().WithHtml().WithMxp().WithPueblo();
-
-// A command link: each format writes it in its own dialect — for Pueblo,
-// for MXP, a clickable anchor for HTML — and a terminal shows the text.
-var prompt = MarkupText.Wrap(
- AnsiMarkup.Create(linkUrl: "north", linkKind: LinkKind.Command),
- MarkupText.Wrap(AnsiCodeParser.Parse("hc"), "Go north"));
-
-Console.WriteLine(prompt.Render(MarkupFormat.Ansi));
-
-// A sound and a picture, said once. MXP gets and , Pueblo its xch_ tags, a browser
-//