'rtf' in the 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 theInkKitRTF Swift package and gives Inkkit a RichTextDocument, a JSON form of the document’s blocks and runs:
- Your app reads the file with
RTFConverter.document(fromRTF:)and passes the document’s JSON toloadDocumentas{ format: 'rtf', document }. - The person edits it in formatted mode.
- Your app takes
snapshot, whosedocumentis the current document, and writes it withRTFConverter.rtfData(from:).
What an RTF document keeps
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: 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 throwsRTFConversionError.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.
commandStatereports the commands for headings, code blocks, quotes, callouts, tasks, highlight, images, footnotes and reference definitions as unavailable, andformatreturnsfalsefor 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.
setEditingMode('source') returns false, and replaceSource throws invalid-argument.
Alignment
The format commandsalignLeft, 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. 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.markdownpastes as its text, unparsed.
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 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 prints the document with its marks and alignment.
Convert between Markdown and RTF
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 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 reportsmark-spaceswhere 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.
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.
