A powerful and highly customizable Jetpack Compose Markdown rendering library that supports rich Markdown syntax and custom styling.
Need cross-platform support? Check out ComposeMarkdownMultiplatform โ it supports Android, iOS, Desktop, and WebAssembly with a single codebase.
- Features
- Tech Stack
- Installation
- Quick Start
- Core Components
- Style Customization
- MarkdownText (Text-based Rendering)
- Advanced Features
- Plugins
- API Overview
- FAQ
- Contributing
- License
| Custom Styles | Tables and Code Blocks | Custom Plugins (Alerts) |
|---|---|---|
| Custom typography styles | Complex tables and code highlighting | Custom block |
![]() |
![]() |
![]() |
- โ Standard Markdown Support - Full support for CommonMark specification
- โ Extended Syntax - Support for GFM (GitHub Flavored Markdown) tables
- โ Code Syntax Highlighting - Multi-language code block syntax highlighting
- โ Multimedia Support - Rendering of images, links and other multimedia content
- โ Responsive Design - Perfect adaptation to different screen sizes
- โก Async Parsing - Background thread parsing ensures UI fluidity
- ๐จ Fully Customizable - Support for custom styles, renderers and parsers
- ๐ Plugin System - Flexible plugin architecture for feature extensions
- ๐ก๏ธ Error Handling - Graceful error state handling mechanism
A utility component for custom Renderers to visit and render children nodes with proper spacing.
@Composable
fun MarkdownChildren(
parent: Node,
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
spacerHeight: Dp = currentTheme().spacerTheme.spacerHeight,
showSpacer: Boolean = currentTheme().spacerTheme.showSpacer,
childModifierFactory: (child: Node) -> Modifier = { ... },
onBeforeChild: (@Composable (child: Node, parent: Node) -> Unit)? = null,
onAfterChild: (@Composable (child: Node, parent: Node) -> Unit)? = null,
onBeforeAll: (@Composable (parent: Node) -> Unit)? = null,
onAfterAll: (@Composable (parent: Node) -> Unit)? = null,
)| Technology | Version | Purpose |
|---|---|---|
| Jetpack Compose | 2025.12.00+ | Modern UI framework |
| Flexmark | 0.64.8 | Markdown parsing engine |
| Kotlin Coroutines | 1.7+ | Asynchronous processing |
| Material Design 3 | Latest | Design language specification |
- Android API: 24+ (Android 7.0)
- Kotlin: 2.0.21+
- Compose BOM: 2025.12.00+
- Java: 8+
Add the dependency to your project's build.gradle.kts :
define library module in your ./gradle/libs.versions.toml file:
[versions]
compose-markdown = "0.0.1"
composeBom = "2025.12.00"
coil = "2.5.0"
[libraries]
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
compose-markdown = { group = "io.github.feiyin0719", name = "ComposeMarkdown", version.ref = "compose-markdown" }add the dependency in your module build.gradle.kts:
dependencies {
implementation(libs.androidx.core.ktx)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.coil.compose)
implementation(libs.compose.markdown)
}The simplest way to use:
import com.iffly.compose.markdown.MarkdownView
import com.iffly.compose.markdown.config.MarkdownRenderConfig
@Composable
fun SimpleMarkdownExample() {
val markdownContent = """
# Welcome to Compose Markdown
This is a powerful Markdown rendering library.
## Supported Features
- **Bold text**
- *Italic text*
- `Inline code`
- [Link](https://github.com)
### Code Block Example
```kotlin
@Composable
fun HelloWorld() {
Text("Hello, Compose Markdown!")
}
```
""".trimIndent()
val config = remember { MarkdownRenderConfig.Builder().build() }
MarkdownView(
content = markdownContent,
markdownRenderConfig = config,
modifier = Modifier.fillMaxSize(),
)
}import com.iffly.compose.markdown.style.MarkdownTheme
@Composable
fun ConfiguredMarkdownExample() {
val markdownTheme =
MarkdownTheme(
textStyle =
TextStyle(
fontSize = 16.sp,
lineHeight = 24.sp,
),
headStyle =
mapOf(
1 to TextStyle(fontSize = 28.sp, fontWeight = FontWeight.Bold),
2 to TextStyle(fontSize = 24.sp, fontWeight = FontWeight.Bold),
),
)
val config =
remember {
MarkdownRenderConfig.Builder()
.markdownTheme(markdownTheme)
.build()
}
MarkdownView(
content = "# Custom Style Title\n\nThis is Markdown content with custom styling.",
markdownRenderConfig = config,
modifier = Modifier.fillMaxSize(),
showNotSupportedText = true,
actionHandler = ActionHandler { action ->
// Handle actions (links, images, etc.)
},
onError = { error ->
Text(
text = "Content parsing failed: ${error.message}",
color = MaterialTheme.colorScheme.error,
)
},
)
}High-level configuration for markdown rendering, created via:
val config = MarkdownRenderConfig.Builder()
.markdownTheme(MarkdownTheme())
// .addPlugin(...)
// .addBlockRenderer(...)
// .addInlineNodeStringBuilder(...)
.build()For full configuration API, see docs/API.md.
MarkdownTheme describes typography, colors and component styles for markdown content.
val markdownTheme =
MarkdownTheme(
textStyle =
TextStyle(
fontSize = 16.sp,
lineHeight = 24.sp,
fontFamily = FontFamily.Default,
),
strongEmphasis =
SpanStyle(
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
),
emphasis =
SpanStyle(
fontStyle = FontStyle.Italic,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f),
),
code =
TextStyle(
fontFamily = FontFamily.Monospace,
fontSize = 14.sp,
color = MaterialTheme.colorScheme.secondary,
background = MaterialTheme.colorScheme.surfaceVariant,
),
link =
TextLinkStyles(
style =
SpanStyle(
color = MaterialTheme.colorScheme.primary,
textDecoration = TextDecoration.Underline,
),
hoveredStyle =
SpanStyle(
color =
MaterialTheme.colorScheme.primary.copy(
alpha = 0.8f,
),
textDecoration = TextDecoration.Underline,
),
pressedStyle =
SpanStyle(
color =
MaterialTheme.colorScheme.primary.copy(
alpha = 0.6f,
),
textDecoration = TextDecoration.Underline,
),
),
)val markdownTheme =
MarkdownTheme(
headStyle =
mapOf(
MarkdownTheme.HEAD1 to
TextStyle(
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.primary,
),
MarkdownTheme.HEAD2 to
TextStyle(
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface,
),
MarkdownTheme.HEAD3 to
TextStyle(
fontSize = 24.sp,
fontWeight = FontWeight.SemiBold,
color = MaterialTheme.colorScheme.onSurface,
),
),
)You can pass this theme into MarkdownRenderConfig.Builder().markdownTheme(markdownTheme).
MarkdownText is an alternative rendering approach that renders the entire Markdown document through a single RichText composable, unlike MarkdownView which renders each block as a separate composable in a Column.
Key advantage: Works just like Compose's built-in Text โ supports maxLines, overflow, and other standard text parameters. You can limit the number of displayed lines (e.g., maxLines = 3 with overflow = TextOverflow.Ellipsis) to create collapsible previews, just as you would with a regular Text composable. It also enables cross-paragraph text selection when wrapped in SelectionContainer.
Non-text blocks (code blocks, block quotes, lists, tables, etc.) are embedded as inline content that adjusts its size based on actual content.
// Line-limited preview (like Text)
MarkdownText(
content = markdownContent,
modifier = Modifier.padding(16.dp),
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
// Full rendering with text selection
SelectionContainer {
MarkdownText(
content = markdownContent,
markdownRenderConfig = config,
modifier = Modifier.padding(16.dp),
actionHandler = object : ActionHandler {
override fun handleUrlClick(url: String, node: Node) {
// Handle link clicks
}
},
)
}| Overload | Key Parameter | Use Case |
|---|---|---|
| Sync | content: String |
Small/medium documents, instant parsing |
| Async | content: String + parseDispatcher |
Large documents, background parsing with onLoading |
| Pre-parsed | node: Node |
When you manage parsing yourself |
MarkdownText supports standard Compose text parameters:
overflowโ How to handle visual overflow (TextOverflow.Clip,Ellipsis, etc.)softWrapโ Whether to break text at soft line breakstextAlignโ Text alignmentmaxLines/minLinesโ Line count constraintsletterSpacingโ Spacing between characterstextDecorationโ Text decorations (underline, strikethrough)onTextLayoutโ Callback for text layout results
When to use which?
- Use
MarkdownViewwhen you need independent block layout and don't need cross-paragraph selection.- Use
MarkdownTextwhen you need line count limits (maxLines), text overflow control, or cross-paragraph text selection โ it behaves like a standard ComposeText.
MarkdownView provides multiple usage modes to adapt to different use cases.
Suitable for small content that can be parsed instantly without blocking the UI.
@Composable
fun MarkdownView(
content: String,
markdownRenderConfig: MarkdownRenderConfig,
modifier: Modifier = Modifier,
showNotSupportedText: Boolean = false,
actionHandler: ActionHandler? = null,
onError: (@Composable (Throwable) -> Unit)? = null,
)Usage Example:
@Composable
fun SyncMarkdownExample() {
val shortContent = """
# Quick Notes
This is short markdown content that can be parsed instantly.
- Item 1
- Item 2
**Bold text** and *italic text*
""".trimIndent()
val config = remember { MarkdownRenderConfig.Builder().build() }
MarkdownView(
content = shortContent,
markdownRenderConfig = config,
modifier = Modifier.padding(16.dp),
showNotSupportedText = true,
actionHandler = ActionHandler { action ->
// Handle actions
},
onError = { error ->
Text(
text = "Parsing failed: ${error.message}",
color = MaterialTheme.colorScheme.error,
)
},
)
}Recommended for large content or scenarios requiring loading/error state display.
@Composable
fun MarkdownView(
content: String,
markdownRenderConfig: MarkdownRenderConfig,
modifier: Modifier = Modifier,
showNotSupportedText: Boolean = false,
actionHandler: ActionHandler? = null,
parseDispatcher: CoroutineDispatcher? = null,
onLoading: (@Composable () -> Unit)? = null,
onError: (@Composable (Throwable) -> Unit)? = null,
)Usage Example:
@Composable
fun AsyncMarkdownExample() {
val largeContent = """
# Large Document
This is a large markdown document that may take time to parse.
## Features
${generateLargeMarkdownContent()}
""".trimIndent()
val config = remember { MarkdownRenderConfig.Builder().build() }
MarkdownView(
content = largeContent,
markdownRenderConfig = config,
modifier = Modifier.fillMaxSize(),
showNotSupportedText = true,
actionHandler = ActionHandler { action ->
// Handle actions such as links, images, etc.
},
parseDispatcher = Dispatchers.IO,
onLoading = {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
) {
CircularProgressIndicator()
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Parsing markdown content...",
style = MaterialTheme.typography.bodyMedium,
)
}
}
},
onError = { error ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
colors =
CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.errorContainer,
),
) {
Column(
modifier = Modifier.padding(16.dp),
) {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = Icons.Default.Error,
contentDescription = "Error",
tint = MaterialTheme.colorScheme.onErrorContainer,
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "Parse Error",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onErrorContainer,
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = error.message ?: "Unknown error occurred",
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onErrorContainer,
)
}
}
},
)
}Suitable for cases where you already have parsed Nodes.
@Composable
fun MarkdownView(
node: Node,
markdownRenderConfig: MarkdownRenderConfig,
modifier: Modifier = Modifier,
showNotSupportedText: Boolean = false,
actionHandler: ActionHandler? = null,
)Usage Example:
@Composable
fun PreParsedMarkdownExample() {
val config = remember { MarkdownRenderConfig.Builder().build() }
val parser = remember(config) { config.parser }
val preParseNode = remember {
parser.parse("# Pre-parsed Content\n\nThis content was parsed outside the composable.")
}
MarkdownView(
node = preParseNode,
markdownRenderConfig = config,
modifier = Modifier.padding(16.dp),
showNotSupportedText = true,
actionHandler = ActionHandler { action ->
// Handle actions
},
)
}When dealing with very large Markdown sources (multiโMB, >10k lines, lots of images / long code blocks), use LazyMarkdownView.
@Composable
fun LazyMarkdownView(
file: File,
markdownRenderConfig: MarkdownRenderConfig,
modifier: Modifier = Modifier,
showNotSupportedText: Boolean = false,
actionHandler: ActionHandler? = null,
chunkLoaderConfig: ChunkLoaderConfig = ChunkLoaderConfig(parserDispatcher = MarkdownThreadPool.dispatcher),
nestedPrefetchItemCount: Int = 3,
)Basic example:
@Composable
fun LargeMarkdownDocument() {
val markdownFile = File("/path/to/large-document.md")
val config = remember { MarkdownRenderConfig.Builder().build() }
LazyMarkdownView(
file = markdownFile,
markdownRenderConfig = config,
modifier = Modifier.fillMaxSize(),
chunkLoaderConfig =
ChunkLoaderConfig(
/* initialLines = 1000,
incrementalLines = 500,
chunkSize = 5, */
parserDispatcher = MarkdownThreadPool.dispatcher,
),
)
}Minimal usage (use defaults):
LazyMarkdownView(
file = File(path),
markdownRenderConfig = MarkdownRenderConfig.Builder().build(),
)For detailed configuration of
ChunkLoaderConfig, see the source and docs/API.md.
When you want to display markdown content in a LazyColumn for efficient rendering (only visible items are composed), use LazyMarkdownColumn. Unlike LazyMarkdownView, this parses the entire content upfront and only uses LazyColumn for display.
@Composable
fun LazyMarkdownColumn(
content: String,
markdownRenderConfig: MarkdownRenderConfig,
modifier: Modifier = Modifier,
showNotSupportedText: Boolean = false,
actionHandler: ActionHandler? = null,
lazyListState: LazyListState = rememberLazyListState(),
)Basic example:
@Composable
fun LazyMarkdownExample() {
val config = remember { MarkdownRenderConfig.Builder().build() }
LazyMarkdownColumn(
content = longMarkdownContent,
markdownRenderConfig = config,
modifier = Modifier.fillMaxSize(),
)
}You can provide a custom renderer for any Flexmark Block type.
class AlertBlockRenderer : IBlockRenderer<AlertBlock> {
@Composable
override fun Invoke(
node: AlertBlock,
modifier: Modifier,
) {
Card(modifier = modifier) {
Text(text = node.title)
}
}
}
val config =
MarkdownRenderConfig
.Builder()
.addBlockRenderer(AlertBlock::class.java, AlertBlockRenderer())
.build()Then pass config into MarkdownView.
Use IInlineNodeStringBuilder to turn custom inline nodes into styled text.
class MentionInlineBuilder : IInlineNodeStringBuilder<MentionNode> {
override fun AnnotatedString.Builder.buildInlineNodeString(
node: MentionNode,
inlineContentMap: MutableMap<String, MarkdownInlineView>,
markdownTheme: MarkdownTheme,
actionHandler: ActionHandler?,
indentLevel: Int,
isShowNotSupported: Boolean,
renderRegistry: RenderRegistry,
nodeStringBuilderContext: NodeStringBuilderContext,
) {
pushStyle(markdownTheme.linkTextStyle)
append("@" + node.username)
pop()
}
}
val config =
MarkdownRenderConfig
.Builder()
.addInlineNodeStringBuilder(MentionNode::class.java, MentionInlineBuilder())
.build()Currently supported official plugin modules:
| Plugin | Module (artifact) | Description |
|---|---|---|
| HTML Inline | markdown-html | Supports HTML inline tags (<b>, <i>, <u>, <a>, <span>, etc.) and HTML block rendering, with extensible custom tag handlers |
| Table | markdown-table | Supports GFM tables |
| Image | markdown-image | Supports Markdown images with Coil integration |
| Task List | markdown-task | Supports GitHub-style task list checkboxes: - [ ] / - [x] |
| LaTeX / Math | markdown-latex | Supports inline and block formulas: $...$, $$...$$ |
| AutoLink | markdown-autolink | Supports automatic linking for URLs and emails |
dependencies {
implementation("io.github.feiyin0719:markdown-html:<version>")
implementation("io.github.feiyin0719:markdown-table:<version>")
implementation("io.github.feiyin0719:markdown-image:<version>")
implementation("io.github.feiyin0719:markdown-task:<version>")
implementation("io.github.feiyin0719:markdown-latex:<version>")
implementation("io.github.feiyin0719:markdown-autolink:<version>")
}If only the root library (e.g. ComposeMarkdown) is published, these modules may already be bundled and you can just import their classes directly.
// Option 1: Default (supports <b>, <i>, <u>, <del>, <a>, <span>, and HTML blocks)
val config = MarkdownRenderConfig.Builder()
.addPlugin(HtmlMarkdownPlugin())
.build()
// Option 2: Add custom tag handlers (e.g. <mark>)
class MarkTagHandler : HtmlInlineTagHandler {
override val tagNames = setOf("mark")
override fun onOpenTag(
tagName: String,
rawTag: String,
builder: AnnotatedString.Builder,
context: HtmlInlineTagContext,
) {
builder.pushStyle(SpanStyle(background = Color.Yellow))
}
}
val config = MarkdownRenderConfig.Builder()
.addPlugin(HtmlMarkdownPlugin(customTagHandlers = listOf(MarkTagHandler())))
.build()
// Option 3: Override a default tag handler
class MyBoldTagHandler : HtmlInlineTagHandler {
override val tagNames = setOf("b", "strong")
override fun onOpenTag(
tagName: String,
rawTag: String,
builder: AnnotatedString.Builder,
context: HtmlInlineTagContext,
) {
builder.pushStyle(SpanStyle(fontWeight = FontWeight.Black, color = Color.Red))
}
}
val config = MarkdownRenderConfig.Builder()
.addPlugin(HtmlMarkdownPlugin(customTagHandlers = listOf(MyBoldTagHandler())))
.build()// Option 1: Default theme
val config = MarkdownRenderConfig.Builder()
.addPlugin(TableMarkdownPlugin())
.build()
// Option 2: Custom table theme
val tableTheme = TableTheme(
borderColor = Color.Blue,
borderThickness = 2.dp,
// ... other properties
)
val config = MarkdownRenderConfig.Builder()
.addPlugin(TableMarkdownPlugin(tableThemeuilder()
.addPlugin(TableMarkdownPlugin())
.build()
// Option 2: Custom table theme
val tableTheme = TableTheme(
borderColor = Color.Blue,
borderThickness = 2.dp,
// ... other properties
)
val config = MarkdownRenderConfig.Builder()
.addPlugin(TableMarkdownPlugin(tableTheme))
.build()Markdown sample:
| Feature | Supported |
| :---: | :---: |
| Tables | โ
|
// Option 1: Default theme
val config = MarkdownRenderConfig.Builder()
.addPlugin(ImageMarkdownPlugin())
.build()
// Option 2: Custom image theme
val imageTheme = ImageTheme(
alignment = Alignment.Center,
contentScale = ContentScale.Crop,
modifier = Modifier.clip(RoundedCornerShape(8.dp))
)
val config = MarkdownRenderConfig.Builder()
.addPlugin(ImageMarkdownPlugin(imageTheme))
.build()val config = MarkdownRenderConfig.Builder()
.addPlugin(
TaskMarkdownRenderPlugin(
taskStyle = SpanStyle(/* customize color / weight etc. */)
)
)
.build()Markdown sample:
- [ ] Unfinished item
- [x] Completed item
val mathConfig = MarkdownRenderConfig.Builder()
.addPlugin(
MarkdownMathPlugin(
mathStyle = SpanStyle(fontStyle = FontStyle.Italic),
width = 200.sp,
height = 80.sp,
align = TextAlign.Center,
enableGitLabExtension = false
)
)
.build()Supported:
-
Inline:
$E = mc^2$ -
Multi-line block:
$$ E = mc^2 $$ -
Single-line block:
$$ E = mc^2 $$
// Option 1: Default configuration
val config = MarkdownRenderConfig.Builder()
.addPlugin(AutolinkMarkdownRenderPlugin())
.build()
// Option 2: Custom link styles
val config = MarkdownRenderConfig.Builder()
.addPlugin(AutolinkMarkdownRenderPlugin(
linkStyles = TextLinkStyles(
style = SpanStyle(
color = Color.Blue,
textDecoration = TextDecoration.Underline
)
)
))
.build()val fullConfig = MarkdownRenderConfig.Builder()
.addPlugin(TaskMarkdownRenderPlugin())
.addPlugin(
MarkdownMathPlugin(
mathStyle = SpanStyle(fontStyle = FontStyle.Italic),
width = 180.sp,
height = 72.sp,
align = TextAlign.Center
)
)
.build()Implement IMarkdownRenderPlugin (or extend AbstractMarkdownRenderPlugin) and register via addPlugin(). A typical plugin can:
- Add Flexmark extensions (override
extensions()) - Provide custom block/inline parsers
- Register custom block renderers / inline node string builders
See the earlier "Creating Custom Plugins" section for a complete example.
This section gives a high-level overview of the main APIs. For full signatures and detailed parameter explanations, see the dedicated API document:
- Full API Reference: docs/API.md
- ๐ Support load large markdown file and render progressively
Load and render visible blocks to improve performance and memory usage -- Completed in v0.0.4
-
Support markdown inline editing mode(inline edit is edit markdown and render at the same time) -- Planned for v0.1.0
-
Supports jump-to-section functionality via clickable TOC.
-
๐ Add more built-in plugins for common use cases
A: The library uses an asynchronous parsing mechanism that processes Markdown parsing in background threads without blocking the UI thread. For very large documents, pagination or lazy loading is recommended.
A: Currently supports CommonMark standard syntax and GFM tables. More extension syntax support will be added in the future.
A: The library includes BasicSyntaxHighlighter โ a built-in CodeAnnotator supporting 20+
languages. Pass it as codeAnnotator when constructing FencedCodeBlockRenderer:
val config = MarkdownRenderConfig.Builder()
.addBlockRenderer(
FencedCodeBlock::class.java,
FencedCodeBlockRenderer(codeAnnotator = BasicSyntaxHighlighter()),
)
.build()You can also implement the CodeAnnotator fun interface yourself to integrate any
third-party syntax highlighting library.
We welcome contributions! To get started:
- Fork the repository
- Create a feature branch:
git checkout -b feat/my-feature - Make your changes (keep scope focused; add tests when possible)
- Run checks locally (examples):
./gradlew ktlintFormatโ format code with ktlint./gradlew ktlintCheckโ verify code style./gradlew assembleโ compile & run tests
- Commit using conventional prefixes:
feat:new featurefix:bug fixdocs:documentation changesrefactor:code restructure without behavior changeperf:performance improvementtest:adding / improving testsbuild:build system / dependency changeschore:maintenance tasks
- Open a Pull Request describing:
- What & why
- Screenshots (UI changes) / benchmarks (perf changes)
- Related issue IDs (e.g.
Closes #12)
Code Style & Guidelines:
- Prefer small, composable functions
- Avoid premature optimizationโmeasure first
- Keep public APIs documented with KDoc
- Use meaningful, concise commit messages
Issue Reports:
- Provide reproduction steps
- Attach minimal markdown sample content triggering the issue
- Include device / emulator API level & library version
Released under the MIT License.
MIT License
Copyright (c) 2025 Compose Markdown Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Made with โค๏ธ by the Compose Markdown team


