Skip to content

SergeiKriukov/MarkdownToAttributedString

Repository files navigation

MarkdownToAttributedString

Swift Platform License

MarkdownToAttributedString converts a pragmatic subset of Markdown into NSAttributedString for iOS, macOS, tvOS, and watchOS.

It is designed for apps that need predictable rich text rendering without pulling in a heavy Markdown stack. The current implementation focuses on the elements that are most useful in app UI and knowledge-style content.

Features

  • Headers # to ######
  • Paragraphs with inline formatting
  • Bold, italic, bold+italic, and strikethrough
  • Inline code and multiline fenced code blocks
  • Ordered and unordered lists
  • Indentation-based nested list rendering
  • Blockquotes with multiline content
  • Links rendered with the .link attribute
  • Image placeholders rendered as text
  • Custom fonts, weights, italic style, and foreground colors
  • Public parser and converter APIs

Installation

Add the package in Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/SergeiKriukov/MarkdownToAttributedString.git", from: "1.0.0")
]

Quick Start

import MarkdownToAttributedString

let markdown = """
# Welcome

This is **bold**, *italic*, and `inline code`.

> A blockquote with [a link](https://example.com)

- First item
  - Nested item
"""

let attributedString = markdown.toAttributedString()

Styling

Use MarkdownConfiguration to override the styles used for each Markdown element.

import MarkdownToAttributedString

let configuration = MarkdownConfiguration(
    text: .init(fontSize: 14),
    h1: .init(fontSize: 28, fontWeight: .bold),
    h2: .init(fontSize: 22, fontWeight: .semibold),
    bold: .init(fontWeight: .bold),
    italic: .init(isItalic: true),
    code: .init(fontSize: 13, fontWeight: .medium),
    listPrefix: .init(fontWeight: .semibold)
)

let attributedString = "# Styled title".toAttributedString(configuration: configuration)

MarkdownStyle currently supports:

  • fontSize
  • fontWeight
  • isItalic
  • foregroundColor

The available font weights are:

  • .regular
  • .medium
  • .semibold
  • .bold
  • .light

Color Example

import MarkdownToAttributedString
#if canImport(UIKit)
import UIKit

let configuration = MarkdownConfiguration(
    h1: .init(fontSize: 28, fontWeight: .bold, foregroundColor: .systemBlue),
    link: .init(foregroundColor: .systemRed)
)
#elseif canImport(AppKit)
import AppKit

let configuration = MarkdownConfiguration(
    h1: .init(fontSize: 28, fontWeight: .bold, foregroundColor: .systemBlue),
    link: .init(foregroundColor: .systemRed)
)
#endif

Parser And Converter APIs

You can use the high-level facade or the low-level parser/converter types directly.

import MarkdownToAttributedString

let parser = MarkdownParser()
let elements = parser.parse("# Title\nParagraph with **bold** text.")

let converter = AttributedStringConverter(configuration: .default)
let attributedString = converter.convert(elements)

Or with the facade:

import MarkdownToAttributedString

let engine = MarkdownToAttributedString(configuration: .default)
let attributedString = engine.convert("Visit [Apple](https://apple.com)")

Supported Markdown

Element Example Status
Headers # Title Supported
Paragraphs plain text blocks Supported
Bold **bold** Supported
Italic *italic* Supported
Bold + italic ***value*** Supported
Strikethrough ~~text~~ Supported
Inline code `code` Supported
Fenced code blocks ```swift ... ``` Supported
Ordered lists 1. item Supported
Unordered lists - item Supported
Nested list indentation two-space indentation Supported
Blockquotes > quote Supported
Links [title](url) Supported
Images ![alt](url) Text placeholder
Tables ` a

Current Behavior Notes

  • Wrapped paragraph lines are joined with spaces.
  • Consecutive blockquote lines are grouped into one rendered block.
  • Images are rendered as a placeholder like 🖼️ Alt text.
  • Tables are currently treated as plain text because there is no table parser yet.
  • MarkdownParser.parse(_:) now returns block-level elements with richer metadata like children, info, and nestingLevel.

Export

The result is a normal NSAttributedString, so you can export it to RTF or plain text using standard Foundation APIs.

let attributedString = markdown.toAttributedString()

let rtfData = try attributedString.data(
    from: NSRange(location: 0, length: attributedString.length),
    documentAttributes: [.documentType: NSAttributedString.DocumentType.rtf]
)

let plainText = attributedString.string

Testing

Run the test suite with:

swift test

The package includes:

  • smoke tests for basic conversion;
  • parser contract tests for block-level structure;
  • rendering regression tests for links, fenced code blocks, nested formatting, and block separation.

Roadmap

The next practical improvements are:

  • better blockquote prefix rendering for each quoted line;
  • richer image handling via a resolver or attachment builder;
  • table parsing and rendering;
  • more advanced Markdown compatibility where it adds real UI value.

Contributing

Contributions are welcome. Please keep changes small, add focused tests, and make sure examples and API docs stay in sync with the implementation.

License

MIT. See LICENSE.

About

A powerful Swift library for converting Markdown text to NSAttributedString with full formatting support. Cross-platform compatible for iOS, macOS, tvOS, and watchOS.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages