> ## Documentation Index
> Fetch the complete documentation index at: https://inkkit.cyzr.me/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Import only `@aicayzer/inkkit`, `@aicayzer/inkkit/core` and `@aicayzer/inkkit/style.css`. Never import Milkdown or ProseMirror, and never rely on Inkkit's internal classes or markup.
> Pass the current generation to every call that takes one. Handle failures by `InkKitError.code`, not by message.
> Save only the text of a `snapshot()` call that succeeded. A failed snapshot is not an unchanged document.

# Rich text

> How Inkkit edits RTF documents: what an RTF file keeps and drops, what the editor refuses, how paste and conversion work, and what saving promises.

Inkkit edits RTF (Rich Text Format) documents in formatted mode, beside Markdown and TXT. Your app converts the file, and Inkkit edits the converted document. Offer RTF by listing `'rtf'` in the [`formats`](https://inkkit.cyzr.me/reference/options#formats) option; it is never offered by default.

## How a host opens and saves RTF

Inkkit never reads or writes RTF itself. Apple's text system reads RTF reliably and a browser can't, so your app converts the file with the `InkKitRTF` Swift package and gives Inkkit a [`RichTextDocument`](https://inkkit.cyzr.me/reference/editor#rich-text-documents), a JSON form of the document's blocks and runs:

1. Your app reads the file with `RTFConverter.document(fromRTF:)` and passes the document's JSON to [`loadDocument`](https://inkkit.cyzr.me/reference/editor#loaddocument) as `{ format: 'rtf', document }`.
2. The person edits it in formatted mode.
3. Your app takes [`snapshot`](https://inkkit.cyzr.me/reference/editor#snapshot), whose `document` is the current document, and writes it with `RTFConverter.rtfData(from:)`.

[Host RTF documents](https://inkkit.cyzr.me/guides/web-view.md#host-rtf-documents) shows the Swift side. Snapshots stay synchronous: no call to the native converter happens during a save.

## What an RTF document keeps

| Kept | Dropped by the converter |
| - | - |
| Paragraphs, with left, centered, right and justified alignment | Fonts and font sizes |
| Bulleted and numbered lists, nested up to 9 levels, with a starting number from 1 to 2,147,483,647 | Text and background colors, highlighting |
| Simple tables, with the same number of cells in every row | Indents, spacing, tab stops and kerning |
| Line breaks inside a paragraph or cell | |
| Bold, italic, underline, strikethrough, superscript, subscript, monospace (code) and links | |

Inkkit's own style replaces what the converter drops. That is the `structure` promise: saving keeps the structure and marks, and **the first save after an edit rewrites the whole file in Inkkit's style**, the style of TextEdit's default document. Inkkit doesn't write files: your app decides when to save, so write only after the person changes something, and an unedited file keeps its own bytes.

A link's underline is how the link is drawn, so a run with a link never also has underline: the converter and Inkkit both remove it, and the underline command is unavailable on link text.

**One link rule.** A link keeps any address that isn't empty and follows the rule for text with no control character, such as `http://[`, whether it was loaded, added or pasted; an address you add loses the spaces at its ends. The system's RTF writer percent-encodes an address, so the converter also records each link's exact address and restores it when it reads the file back. When the record no longer matches the links, for example after the file was edited in TextEdit, or when two links side by side would read as one, the converter keeps the system's addresses, such as `http://%5B`. A link that a person adds or pastes must also pass Inkkit's [link safety](https://inkkit.cyzr.me/reference/options.md#editor-options): a relative address, or an `http`, `https`, `mailto` or `tel` address or a scheme in `linkSchemes`. A link loaded from a file keeps its address whatever its scheme, and Inkkit opens it only when link safety allows.

**Text.** A run never holds a line or paragraph separator, U+FFFC, U+0000 or a lone surrogate. Loading refuses such a document, and an edit that would leave one in the text is refused and changes nothing: `insertText` returns `false`, and `replaceAll` counts no replacement.

### Refused, never dropped

The converter refuses a file that holds content the document can't represent, rather than losing it: pictures and attachments (RTFD), merged table cells, tables inside tables, lists inside tables, headers and footers, footnotes, comments and equations. It also refuses a table of more than 1,000 columns or 100,000 cells, since every row is as wide as the widest and a short file could otherwise declare millions of empty cells. It throws `RTFConversionError.unsupportedContent` with the kinds it found, and your app tells the person the file can't be opened for editing. Old-style Word `\pntext` list markers read as plain paragraphs, keeping their text.

## Why there are no headings

RTF has no heading that its readers report: a heading written to RTF comes back as a bold paragraph, whichever app wrote it. An RTF document in Inkkit therefore has no headings. `headings()` is empty, and the heading commands are unavailable. A heading converted or pasted from elsewhere becomes a bold paragraph.

## What the editor refuses

An RTF document holds paragraphs, lists, tables, line breaks and the eight marks, and nothing else. Inkkit keeps everything else out in three ways:

* **Commands.** [`commandState`](https://inkkit.cyzr.me/reference/options#command-state) reports the commands for headings, code blocks, quotes, callouts, tasks, highlight, images, footnotes and reference definitions as unavailable, and [`format`](https://inkkit.cyzr.me/reference/editor#format) returns `false` for them. Table column alignment is unavailable, because an RTF cell has none.
* **Typing.** Markdown that a person types stays text: `# `, `> `, `- `, three backticks, `---`, `==`, `[ ]`, `**bold**` and the rest make no headings, quotes, lists, code, rules, highlights or tasks. A bare web address stays text too. Smart punctuation, when on, still applies.
* **Every edit.** Any edit that would bring in something RTF can't hold is refused, and the document, revision and history stay as they were.

There is no source mode: `setEditingMode('source')` returns `false`, and `replaceSource` throws `invalid-argument`.

## Alignment

The format commands `alignLeft`, `alignCenter`, `alignRight` and `alignJustify` align every paragraph the selection touches, including paragraphs in list items. They are available only in RTF; a table cell has no alignment. Their shortcut names have no default keys, so bind them through [`keymap`](https://inkkit.cyzr.me/reference/appearance#shortcuts). `commandState().caret.align` reports the alignment at the selection, for a toolbar's alignment buttons.

## Paste

A paste into an RTF document keeps what RTF holds:

* **HTML** keeps paragraphs and their alignment, lists, tables, line breaks and the eight marks, including from styled spans such as a word processor writes. Headings become bold paragraphs, code blocks become paragraphs in code, and quotes and other containers give up their paragraphs. Images, rules and anything that shows no text are left out. Whitespace between tags, such as indentation, makes no paragraph or list item, while an empty `<p></p>` is an empty paragraph; a break that ends a block is dropped, as a browser draws it. A table whose cells span rows becomes one paragraph for each row, with its cells separated by tabs, because spanned cells would shift; cells that span columns keep the table, padded with empty cells. Pasted HTML is read without running it, and a link whose address Inkkit wouldn't open keeps its text without the link.
* **Plain text** pastes a paragraph for each line. Every paragraph separator, including a final one, starts a new paragraph, so typing or pasting `'\n'` at the end of a paragraph leaves the caret in a new one; a line separator (U+2028) is a line break. The text takes the marks the next typed text would: those you set at the caret, or else those of the text around it.
* **Explicit Markdown** in `ClipboardInput.markdown` pastes as its text, unparsed.

A paste into a table cell keeps to the cell, as lines of it. In a list item, which holds one paragraph, each pasted line or paragraph becomes an item beside it at its level: the first joins the item at the caret, and the text after the caret moves to the last. A pasted list nests under the item before it, a level deeper than nine joins the ninth, and a table becomes lines of tab-separated cells.

A paste that the document still can't hold is refused: `paste` rejects with `preservation`, a person's paste reports it to `events.error`, and nothing changes.

## Copy and print

Copying gives readable text and semantic HTML, with `<u>`, `<sup>` and `<sub>` for underline, superscript and subscript and `text-align` for alignment. [`clipboardSnapshot`](https://inkkit.cyzr.me/reference/editor#clipboardsnapshot) also returns `markdown`, the copied content written as Markdown, so text such as `**literal**` stays text when it is pasted into a Markdown document; alignment is left out. [`printableSnapshot`](https://inkkit.cyzr.me/reference/editor#printablesnapshot) prints the document with its marks and alignment.

## Convert between Markdown and RTF

[`convertDocument`](https://inkkit.cyzr.me/reference/editor#convertdocument) converts the loaded Markdown document to RTF, or the loaded RTF document to Markdown, for a Save As to the other format. It changes nothing in the editor and returns the result with a list of what was lost, as stable [`FormatLossCode`](https://inkkit.cyzr.me/reference/editor#convertdocument) values, so your app can tell the person before saving. Load the result as a new generation.

* **Markdown to RTF** turns headings into bold paragraphs and code blocks into paragraphs in code, and reports those and everything else RTF can't hold, such as quotes, tasks, images and footnotes.
* **RTF to Markdown** writes underline, superscript and subscript as `<u>`, `<sup>` and `<sub>`. Markdown can't start or end a mark with a space, so spaces at a run's edges move outside its marks; it reports `mark-spaces` where that changes what shows, such as an underline under a space. It also reports alignment, a paragraph of only a line break and empty paragraphs at the end, which Markdown doesn't keep.

Inkkit doesn't convert TXT to or from RTF. The Swift package's `RichTextDocument(plainText:)` and `plainText` do that.

## What stays as in Markdown

Find and replace, undo and redo, revisions, generation and stale-document checks, `reloadDocument` keeping the selection, `textSnapshot` and the readable-text ranges, read-only mode, composition guards and `changed` events work as they do in formatted Markdown. A snapshot's `dirty` compares canonical documents, so undoing every edit makes it clean again.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.