---
title: creaturesurvive/htmltext
framework: Swift Package Catalog
role: article
path: packages/creaturesurvive/htmltext
---

# creaturesurvive/htmltext

Fast, thread-safe HTML → `AttributedString` conversion for SwiftUI, UIKit and AppKit. It uses no

## Why

Media servers, forums, feeds and CMS APIs return HTML fragments: Jellyfin and Plex overviews, RSS descriptions, comments. Apple's only built-in converter is `NSAttributedString(data:options:[.documentType: .html])`. It:

- runs a hidden WebKit instance, and is slow (milliseconds per call) and memory-hungry - **must run on the main thread** and can deadlock or crash when it doesn't - spins the run loop re-entrantly, which breaks SwiftUI updates and scrolling - produces Times New Roman text with web styling you then have to strip out - returns `NSAttributedString`, not the `AttributedString` SwiftUI wants

HTMLText parses HTML itself. It runs on any thread or actor and returns a semantic `AttributedString` that SwiftUI's `Text` renders in your app's fonts.

## Features

- **Lenient parser** that repairs malformed markup the way browsers do: implied closes (`<p>`,   `<li>`, table cells), mis-nested tags, stray end tags, unquoted attributes, bare `<` and `&`.   Scripts, styles, comments and `<head>` are dropped. - **Complete entity decoding**: the full WHATWG table (2,125 named references), numeric references   with Windows-1252 remapping, and legacy semicolon-less forms, all per spec. - **Correct text layout**: HTML whitespace collapsing, paragraph and line breaks, `<pre>`, nested   ordered/unordered lists with aligned markers (`start`, `reversed`, `value`), blockquotes,   definition lists, tables, `<hr>`, `<q>`, and image alt text. - **Semantic output**: bold, italic, code and strikethrough map to `inlinePresentationIntent`,   which SwiftUI `Text` renders natively. Links become tappable `link` attributes, resolved against   a base URL, with schemes filtered by an allow list so `javascript:` never becomes a link. - **Styling hooks**: headings, underline, superscript/subscript, `<mark>` and blockquotes get SwiftUI   attributes. Provide your own `HTMLStyleSheet` to change any of it. - **UIKit/AppKit**: `NSAttributedString(html:baseFont:)` derives bold, italic, monospaced and heading   fonts from your base font. - **Plain text**: `HTML.plainText(from:)` uses the same layout rules, for previews, search indexes   and accessibility labels. - **Safe on hostile input**: rendering is iterative with a nesting cap, so deeply nested or garbage   markup can't overflow the stack.

## Installation

Add HTMLText to your `Package.swift`:

```swift dependencies: [     .package(url: "https://github.com/CreatureSurvive/HTMLText.git", from: "1.0.0"), ], targets: [     .target(name: "MyApp", dependencies: ["HTMLText"]), ] ```

Or in Xcode, choose **File › Add Package Dependencies…** and enter `https://github.com/CreatureSurvive/HTMLText`.

### Requirements

| Platform | Minimum | | --- | --- | | iOS | 16.0 | | macOS | 13.0 | | tvOS | 16.0 | | watchOS | 9.0 | | visionOS | 1.0 |

Swift 6.1 (Xcode 16.4) or later, in Swift 6 language mode. No third-party dependencies.

## Usage

### SwiftUI

```swift Text(AttributedString(html: "<p>A <b>bold</b> and <i>italic</i> <a href=\"/about\">link</a>.</p>",                       options: HTMLRenderOptions(baseURL: serverURL))) ```

Convert off the main thread for large content:

```swift .task(id: html) {     attributed = await Task.detached { AttributedString(html: html) }.value } ```

### Plain text

```swift let preview = HTML.plainText(from: overview)       // "Line one.\n\nLine two." if HTML.containsMarkup(text) { ... } ```

### UIKit / AppKit

```swift label.attributedText = NSAttributedString(html: html, baseFont: .preferredFont(forTextStyle: .body), textColor: .label) ```

### Options

```swift var options = HTMLRenderOptions(baseURL: URL(string: "https://example.com")) options.paragraphSpacing = 1                 // no blank line between paragraphs options.bulletMarkers = ["–"] options.images = .omit options.allowedLinkSchemes.insert("myapp") ```

### Custom styling

```swift let styleSheet = HTMLStyleSheet { style in     var container = HTMLStyleSheet.semantic.attributes(style)     if style.headingLevel != nil { container.swiftUI.foregroundColor = .accentColor }     if style.code { container.swiftUI.font = .system(.body, design: .monospaced) }     return container } Text(AttributedString(html: html, styleSheet: styleSheet)) ```

### Lower-level access

```swift let tree = HTML.parse(html)                       // HTMLElement tree let runs = HTMLLayout().runs(for: tree)           // [HTMLRun] — text + semantic style HTMLEntityDecoder.decode("Tom &amp; Jerry")        // "Tom & Jerry" htmlEscape("<b>")                                  // "&lt;b&gt;" ```

## Performance

A typical 1–5 KB overview converts in well under a millisecond. A 1.2 MB document with 60,000 styled runs converts in about 0.9 s in a release build. Conversion is thread-safe, so batches can be parallelized.

## Scope

HTMLText targets content HTML, not web pages. CSS is ignored apart from semantic tags, and there is no layout engine or JavaScript. Use `WKWebView` for full pages.

## Changelog

See [CHANGELOG.md](CHANGELOG.md). Releases follow [Semantic Versioning](https://semver.org).

## Contributing

Issues and pull requests are welcome. Please run `swift test` before opening a pull request, and add tests for new behavior.

## License

Available under the MIT license. See [LICENSE](LICENSE) for details.

The entity table is generated from the WHATWG HTML Standard (CC BY 4.0).

## Package Metadata

Repository: creaturesurvive/htmltext

Default branch: main

README: README.md
