Skip to content

zly2006/Markdown

Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

68 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ–ŠοΈ KMP Markdown

A Blazing-Fast, Cross-Platform Markdown Engine for Compose Multiplatform

Kotlin Compose Multiplatform Maven Central CommonMark Android API License

One library. One codebase. Pixel-perfect Markdown on Android, iOS, Desktop & Web.

English Β· δΈ­ζ–‡


✨ Why KMP Markdown?

Feature Description
πŸš€ Blazing Fast AST-based recursive descent parser with incremental parsing β€” only re-parses what changed
🌍 True Cross-Platform One codebase renders identically on Android, iOS, Desktop (JVM), Web (Wasm/JS)
πŸ“ 100% Coverage 372 Markdown features, 652/652 CommonMark Spec tests passing, plus GFM & 20+ extensions
πŸ€– LLM-Ready Streaming First-class token-by-token rendering with 5fps throttling β€” zero flicker during AI generation
🎨 Fully Themeable 30+ configurable properties, built-in GitHub light/dark themes, auto system detection
πŸ“Š LaTeX Math Inline $...$ and block $$...$$ formulas via integrated LaTeX rendering engine
πŸ” Built-in Linting 13+ diagnostic rules including WCAG accessibility checks β€” catch issues at parse time
πŸ–ΌοΈ Image Loading Coil3 + Ktor3 out-of-the-box, with size specification and custom renderer support
πŸ“„ Pagination Progressive rendering for ultra-long documents (500+ blocks) with auto load-more

🎬 See It in Action

πŸ€– LLM Streaming Rendering

Real-time token-by-token output with incremental parsing β€” no flicker, no re-render.

LLM Streaming Rendering Demo

πŸ” Syntax Diagnostics & Linting

Built-in linting with WCAG accessibility checks β€” heading jumps, broken footnotes, empty links, and more.

Markdown Diagnostics & Linting

🌐 Rich HTML & Extension Support

Full HTML block/inline support, GFM tables, admonitions, math, code highlighting, and 20+ extensions.

HTML & Extension Support


πŸš€ Quick Start

Installation

Add to your gradle/libs.versions.toml:

[versions]
markdown = "0.0.1-alpha.4"

[libraries]
markdown-parser = { module = "io.github.zly2006:markdown-parser", version.ref = "markdown" }
markdown-runtime = { module = "io.github.zly2006:markdown-runtime", version.ref = "markdown" }
markdown-renderer = { module = "io.github.zly2006:markdown-renderer", version.ref = "markdown" }

Then in your module's build.gradle.kts:

dependencies {
    implementation(libs.markdown.parser)
    implementation(libs.markdown.runtime)
    implementation(libs.markdown.renderer)
}

πŸ’‘ markdown-renderer bundles Coil3 + Ktor3 for image loading as transitive dependencies.

Basic Usage

import com.hrm.markdown.renderer.Markdown
import com.hrm.markdown.renderer.MarkdownTheme
import com.hrm.codehigh.theme.OneDarkProTheme

@Composable
fun MyScreen() {
    Markdown(
        markdown = """
            # Hello World
            
            This is a paragraph with **bold** and *italic* text.
            
            - Item 1
            - Item 2
            
            ```kotlin
            fun hello() = println("Hello")
            ```
        """.trimIndent(),
        modifier = Modifier.fillMaxSize(),
        theme = MarkdownTheme.auto(), // Follows system light/dark mode
        codeTheme = OneDarkProTheme,  // Optional: pass a codehigh theme directly
    )
}

That's it β€” 3 lines to render beautiful Markdown across all platforms.


πŸ”Œ Plugin Extensions

This project ships a plugin-friendly architecture based on:

  • An official directive gateway ({% tag ... %}) parsed by markdown-parser
  • A runtime input transform pipeline to normalize custom syntax into directives
  • Renderer-side dispatch that maps directives (e.g. video) to native Compose blocks

Basic usage:

Markdown(
    markdown = """
Custom syntax:

!VIDEO[Demo](https://cdn.example.com/a.mp4){poster=https://cdn.example.com/a.jpg}
    """.trimIndent(),
    directivePlugins = listOf(VideoDirectivePlugin),
)

Plugin skeleton:

object VideoDirectivePlugin : MarkdownDirectivePlugin {
    override val id: String = "video"

    override val inputTransformers = listOf(VideoSyntaxTransformer())

    override val blockDirectiveRenderers = mapOf(
        "video" to { scope ->
            VideoPlayer(
                url = scope.args.getValue("url"),
                poster = scope.args["poster"],
                title = scope.args["title"],
            )
        }
    )
}

class VideoSyntaxTransformer : MarkdownInputTransformer {
    override val id: String = "video-syntax"

    override fun transform(input: String): MarkdownTransformResult {
        val normalized = input.replace(
            Regex("""!VIDEO\[(.*?)\]\((.*?)\)\{poster=(.*?)\}""")
        ) { match ->
            val title = match.groupValues[1]
            val url = match.groupValues[2]
            val poster = match.groupValues[3]
            """{% video title="$title" url="$url" poster="$poster" %}"""
        }
        return MarkdownTransformResult(markdown = normalized)
    }
}

HTML export uses the same directive pipeline:

val html = MarkdownHtml.render(
    markdown = markdown,
    directivePlugins = listOf(VideoDirectivePlugin),
)

πŸ€– LLM Streaming Integration

Purpose-built for AI/LLM scenarios. Enable isStreaming for flicker-free incremental rendering:

var text by remember { mutableStateOf("") }
var isStreaming by remember { mutableStateOf(true) }

LaunchedEffect(Unit) {
    llmTokenFlow.collect { token ->
        text += token
    }
    isStreaming = false
}

Markdown(
    markdown = text,
    isStreaming = isStreaming, // Enables incremental parsing + 5fps throttled rendering
)

What happens under the hood:

  • βœ… Only re-parses the "dirty" tail region β€” stable blocks are reused
  • βœ… Auto-closes unclosed fences, math blocks, emphasis during streaming
  • βœ… Lazy inline parsing β€” block structure first, inline elements on demand
  • βœ… FNV-1a content hashing for O(1) block stability detection

🎨 Theming

// Built-in themes
Markdown(markdown = text, theme = MarkdownTheme.light())  // GitHub Light
Markdown(markdown = text, theme = MarkdownTheme.dark())   // GitHub Dark
Markdown(markdown = text, theme = MarkdownTheme.auto())   // Auto-detect

// Full customization (30+ properties)
Markdown(
    markdown = text,
    theme = MarkdownTheme(
        headingStyles = listOf(
            TextStyle(fontSize = 32.sp, fontWeight = FontWeight.Bold),
            // h2 ~ h6 ...
        ),
        bodyStyle = TextStyle(fontSize = 16.sp),
        codeBlockBackground = Color(0xFFF5F5F5),
        // ...and much more
    ),
    onLinkClick = { url -> /* handle click */ },
)

Code highlighting and code block/inline code theming are provided by the codehigh library. markdown-renderer no longer bundles its own regex-based highlighter.

import com.hrm.codehigh.theme.DraculaProTheme
import com.hrm.codehigh.theme.LocalCodeTheme
import com.hrm.codehigh.theme.OneDarkProTheme

// Option 1: Pass codehigh theme per Markdown call
Markdown(
    markdown = text,
    theme = MarkdownTheme.auto(),
    codeTheme = OneDarkProTheme,
)

// Option 2: Provide a default codehigh theme globally via CompositionLocal
CompositionLocalProvider(
    LocalCodeTheme provides DraculaProTheme
) {
    Markdown(
        markdown = text,
        theme = MarkdownTheme.auto(),
    )
}
  • theme controls general Markdown UI (headings, body, tables, quotes, math, etc.)
  • codeTheme controls code highlighting and styling for code blocks and inline code
  • If codeTheme is not provided, the default from codehigh is used

πŸ“ Comprehensive Syntax Support (372 Features)

πŸ“¦ Block Elements β€” Everything you need for structured content
Feature Details
Headings ATX (# ~ ######), Setext (===/---), custom IDs ({#id}), auto-generated anchors
Paragraphs Multi-line merging, blank line separation, lazy continuation
Code Blocks Fenced (```/~~~) with language highlight (20+ languages), indented, line numbers, line highlighting
Block Quotes Nested, lazy continuation, inner block elements
Lists Unordered/ordered/task lists, nested, tight/loose distinction
Tables (GFM) Column alignment, inline formatting in cells, escaped pipes
Thematic Breaks ---, ***, ___
HTML Blocks All 7 CommonMark types
Link Reference Definitions Full support with title variants
✏️ Inline Elements β€” Rich text formatting at your fingertips
Feature Details
Emphasis Bold, italic, bold-italic, nested, CJK-aware delimiter rules
Strikethrough ~~text~~, ~text~
Inline Code Single/multi backtick, space stripping
Links Inline, reference (full/collapsed/shortcut), autolinks, GFM bare URLs, attribute blocks
Images Inline, reference, =WxH size specification, attribute blocks, auto Figure conversion
Inline HTML Tags, comments, CDATA, processing instructions
Escapes & Entities 32 escapable characters, named/numeric HTML entities
Line Breaks Hard (spaces/backslash), soft
πŸ”Œ Extensions β€” Power features beyond standard Markdown
Feature Syntax
Math (LaTeX) $...$ inline, $$...$$ block, \tag{}, \ref{}
Footnotes [^label] references, multi-line definitions, block content
Admonitions > [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], > [!CAUTION]
Highlight ==text==
Super/Subscript ^text^, ~text~, <sup>, <sub>
Insert Text ++text++
Emoji :smile: shortcodes (200+), ASCII emoticons (40+), custom mappings
Definition Lists Term + : definition format
Front Matter YAML (---) and TOML (+++)
TOC [TOC] with depth, exclude, ordering options
Custom Containers :::type with nesting, CSS classes, IDs
Diagram Blocks Mermaid, PlantUML, Graphviz, and more
Multi-Column Layout :::columns with percentage/pixel widths
Tab Blocks === "Tab Title" MkDocs Material style
Directives {% tag args %}...{% endtag %} with positional/keyword args
Spoiler Text >!hidden text!< Discord/Reddit style
Wiki Links [[page]], [[page|display text]]
Ruby Text {ζΌ’ε­—|γ‹γ‚“γ˜} pronunciation annotations
Bibliography [@key] citations with [^bibliography] definitions
Block Attributes {.class #id key=value} kramdown/Pandoc style
Page Breaks ***pagebreak*** for print/PDF export
Styled Text [text]{.red style="color:red"} inline CSS

πŸ” Built-in Linting & Diagnostics

Enable once, catch problems everywhere:

val parser = MarkdownParser(enableLinting = true)
val document = parser.parse(markdown)

document.diagnostics.forEach { diagnostic ->
    println("Line ${diagnostic.line}: [${diagnostic.severity}] ${diagnostic.message}")
}

13+ diagnostic rules:

Rule Severity Description
Heading level skip ⚠️ WARNING h1 β†’ h3 without h2
Duplicate heading ID ⚠️ WARNING Multiple headings generate the same anchor
Invalid footnote ref ❌ ERROR Reference to undefined footnote
Unused footnote ⚠️ WARNING Footnote defined but never referenced
Empty link target ⚠️ WARNING [text]() with no URL
Missing alt text ⚠️ WARNING Images without description
Empty link text ⚠️ WARNING Links invisible to screen readers
Non-descriptive link ⚠️ WARNING "click here", "read more" links
Missing code language ℹ️ INFO Fenced code without language tag
Table missing header ⚠️ WARNING Screen readers need <th>
Long alt text ⚠️ WARNING Alt text > 125 characters

Follows WCAG 2.1 AA standards for accessibility compliance.


πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Your Compose App                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  markdown-renderer        β”‚  markdown-preview               β”‚
β”‚  AST β†’ Compose UI         β”‚  Interactive demo & showcase    β”‚
β”‚  Block/Inline renderers   β”‚  Categorized feature browser    β”‚
β”‚  Theme system             β”‚                                 β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€                                 β”‚
β”‚  markdown-parser                                            β”‚
β”‚  Markdown β†’ AST                                             β”‚
β”‚  Streaming / Incremental / Flavour system                   β”‚
β”‚  Linting / Diagnostics / Post-processors                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Module Description
:markdown-parser Core parsing engine β€” Markdown string β†’ AST. Streaming, incremental, multi-flavour.
:markdown-renderer Rendering engine β€” AST β†’ Compose UI. Theming, image loading, code highlighting.
:markdown-preview Interactive showcase β€” categorized demo of all supported features.
:composeApp Cross-platform demo app (Android/iOS/Desktop/Web).
:androidApp Android-specific demo app.

πŸ§ͺ Spec Compliance

Spec Status
CommonMark 0.31.2 652/652 (100%) βœ…
GFM 0.29 Tables, task lists, strikethrough, autolinks βœ…
Markdown Extra Footnotes, definition lists, abbreviations, fenced code βœ…

Flavour System

Configure which syntax features to enable:

// Strict CommonMark β€” no extensions
val doc = MarkdownParser(CommonMarkFlavour).parse(input)

// GFM β€” CommonMark + tables, strikethrough, autolinks
val doc = MarkdownParser(GFMFlavour).parse(input)

// Extended (default) β€” everything enabled
val doc = MarkdownParser().parse(input)

// One-shot HTML rendering
val html = HtmlRenderer.renderMarkdown(input, flavour = CommonMarkFlavour)

πŸ–ΌοΈ Custom Image Rendering

Built-in Coil3 handles images automatically. Need custom rendering? Easy:

Markdown(
    markdown = markdownText,
    imageContent = { data, modifier ->
        // data.url, data.altText, data.width, data.height available
        AsyncImage(
            model = data.url,
            contentDescription = data.altText,
            modifier = modifier,
        )
    },
)

Supports size specification in Markdown:

![Photo](https://example.com/photo.png =400x300)
![Auto width](https://example.com/photo.png =x200)

▢️ Running the Demo

# Android
./gradlew :composeApp:assembleDebug

# Desktop (JVM)
./gradlew :composeApp:run

# Web (Wasm)
./gradlew :composeApp:wasmJsBrowserDevelopmentRun

# Web (JS)
./gradlew :composeApp:jsBrowserDevelopmentRun

# iOS β€” open iosApp/ in Xcode

Running Tests

./gradlew :markdown-parser:jvmTest      # Parser tests
./gradlew :markdown-renderer:jvmTest     # Renderer tests
./gradlew jvmTest                        # All tests

πŸ“Š Coverage Summary

# Category Coverage
1 Headings 17/17 (100%)
2 Paragraphs 5/5 (100%)
3 Code Blocks 17/17 (100%)
4 Block Quotes 8/8 (100%)
5 Lists 20/20 (100%)
6 Thematic Breaks 6/6 (100%)
7 Tables (GFM) 11/11 (100%)
8 HTML Blocks 10/10 (100%)
9 Link References 12/12 (100%)
10 Block Extensions 85/85 (100%)
11 Emphasis 13/13 (100%)
12 Strikethrough 4/4 (100%)
13 Inline Code 8/8 (100%)
14 Links 27/27 (100%)
15 Images 17/17 (100%)
16 Inline HTML 8/8 (100%)
17 Escapes & Entities 10/10 (100%)
18 Line Breaks 5/5 (100%)
19 Inline Extensions 50/50 (100%)
20 Streaming Engine 27/27 (100%)
21 Character & Encoding 10/10 (100%)
22 HTML Generator 12/12 (100%)
23 Linting / WCAG 19/19 (100%)
24 Directives 8/8 (100%)
Total 372/372 (100%)

πŸ“– Full details: PARSER_COVERAGE_ANALYSIS.md


πŸ“„ License

MIT License Β· Copyright (c) 2026 huarangmeng

This project is licensed under the MIT License β€” see the LICENSE file for details.

About

Compose Markdown

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Kotlin 99.9%
  • Other 0.1%