Skip to content

Add LaTeX module for math rendering#535

Draft
keta1 wants to merge 6 commits intomikepenz:developfrom
keta1:feat/latex
Draft

Add LaTeX module for math rendering#535
keta1 wants to merge 6 commits intomikepenz:developfrom
keta1:feat/latex

Conversation

@keta1
Copy link
Copy Markdown
Contributor

@keta1 keta1 commented Apr 7, 2026

Summary

  • Add new multiplatform-markdown-renderer-latex module that integrates RaTeX engine for LaTeX math formula rendering
  • Support inline math ($...$), block math ($$...$$), and math code fences (```math)
  • Extend core renderer with blockMath component and composable MarkdownInlineContent interface to enable math rendering as an opt-in extension
  • Bundle KaTeX fonts as Compose resources for cross-platform glyph rendering

Platform support

Platform Status Implementation
Android Supported Native RaTeX AAR via io.ratex:ratex-android
iOS Supported RaTeX static library via cinterop FFI
WasmJS Supported RaTeX WASM via ratex-wasm npm package
JVM Desktop Fallback only Renders raw LaTeX as plain text (JNI bridge not yet available)
JS Fallback only Renders raw LaTeX as plain text

Note: Full rendering is available on Android, iOS, and WasmJS. Other platforms display the raw LaTeX string as a fallback.

Screenshot

Platform Adnroid iOS WasmJS
- image image image

Breaking changes

  • MarkdownInlineContent interface changed: The inlineContent property (val inlineContent: Map<String, InlineTextContent>) has been replaced with a @Composable function (fun inlineContent(content: AnnotatedString): Map<String, InlineTextContent>). This allows inline content to be resolved dynamically based on the annotated string (required for math formula measurement). Consumers that implement this interface directly will need to update their implementation.
  • DefaultMarkdownInlineContent removed: Replaced by markdownInlineContent() factory function. Migrate from DefaultMarkdownInlineContent(map) to markdownInlineContent(map).

Usage

Markdown(
    content = markdownText,
    components = markdownComponents(
        blockMath = ratexBlockMath,
    ),
    inlineContent = mathInlineContent(),
)

Test plan

  • Verify inline math renders correctly on Android
  • Verify block math and math code fences render correctly on Android
  • Verify inline/block math renders correctly on iOS
  • Verify LaTeX renders correctly on WasmJS via ratex-wasm
  • Verify fallback text is shown on JVM Desktop / JS
  • Verify math sizing scales properly with system font scale changes

@keta1 keta1 force-pushed the feat/latex branch 2 times, most recently from ca3915c to 24b69f3 Compare April 7, 2026 15:22
@keta1 keta1 marked this pull request as ready for review April 7, 2026 15:22
Copilot AI review requested due to automatic review settings April 7, 2026 15:22
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds opt-in LaTeX math rendering to the markdown renderer by introducing a new multiplatform-markdown-renderer-latex module (RaTeX-backed on Android/iOS, fallback elsewhere) and extending the core renderer to recognize and render inline/block math nodes.

Changes:

  • Add multiplatform-markdown-renderer-latex module with RaTeX-backed parsing/rendering, KaTeX font resources, and platform stubs/fallback behavior.
  • Extend core renderer/components to support blockMath, math code fences (```math), and dynamic MarkdownInlineContent resolution for inline math placeholders.
  • Update the sample to demonstrate inline math, block math, and math fences.

Reviewed changes

Copilot reviewed 33 out of 54 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
settings.gradle.kts Includes the new multiplatform-markdown-renderer-latex module in the build.
sample/shared/src/commonMain/kotlin/com/mikepenz/markdown/sample/MarkDownPage.kt Wires latexBlockMath + mathInlineContent() into the sample renderer configuration.
sample/shared/src/commonMain/composeResources/files/sample.md Adds sample markdown content demonstrating inline math, block math, and math fences.
sample/shared/build.gradle.kts Adds a dependency on the new LaTeX renderer module.
multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/utils/Extensions.kt Adds math-related constants and an AST helper to extract math content.
multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/model/MarkdownInlineContent.kt Introduces dynamic inline-content resolution API and replaces the previous static implementation.
multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/MarkdownExtension.kt Adds math fence routing + block-math handling in the element renderer.
multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/elements/MarkdownText.kt Uses the new dynamic inline-content API to resolve inline placeholders based on the AnnotatedString.
multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/ComposeLocal.kt Updates the default inline-content provider to the new factory.
multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/compose/components/MarkdownComponents.kt Adds blockMath to components (defaulting to code-fence rendering).
multiplatform-markdown-renderer/src/commonMain/kotlin/com/mikepenz/markdown/annotator/AnnotatedStringKtx.kt Annotates inline math as InlineTextContent placeholders keyed by offset.
multiplatform-markdown-renderer-latex/src/commonMain/kotlin/com/mikepenz/markdown/latex/MathEngine.kt Defines the math engine API and a MathCanvas for rendering display lists.
multiplatform-markdown-renderer-latex/src/commonMain/kotlin/com/mikepenz/markdown/latex/MathInlineContentProvider.kt Provides mathInlineContent() to render inline math via InlineTextContent.
multiplatform-markdown-renderer-latex/src/commonMain/kotlin/com/mikepenz/markdown/latex/MarkdownBlockMath.kt Adds a latexBlockMath component for block math + math fences.
multiplatform-markdown-renderer-latex/src/commonMain/kotlin/com/mikepenz/markdown/latex/MathSize.kt Adds a small sizing model for display-list measurements.
multiplatform-markdown-renderer-latex/src/commonMain/kotlin/com/mikepenz/markdown/latex/model/DisplayList.kt Adds the shared serialized display-list model used across platforms.
multiplatform-markdown-renderer-latex/src/commonMain/kotlin/com/mikepenz/markdown/latex/renderer/GlyphDrawer.kt Declares the expected glyph drawing abstraction + a codepoint helper.
multiplatform-markdown-renderer-latex/src/commonMain/kotlin/com/mikepenz/markdown/latex/renderer/ComposeGlyphDrawer.kt Implements a cross-platform glyph drawer using TextMeasurer/drawText.
multiplatform-markdown-renderer-latex/src/commonMain/kotlin/com/mikepenz/markdown/latex/renderer/DisplayListRenderer.kt Renders display-list primitives onto a Compose DrawScope.
multiplatform-markdown-renderer-latex/src/commonMain/kotlin/com/mikepenz/markdown/latex/renderer/KaTeXFonts.kt Loads bundled KaTeX fonts as Compose resources and exposes a font map.
multiplatform-markdown-renderer-latex/src/androidMain/kotlin/com/mikepenz/markdown/latex/MathEngine.android.kt Android RaTeX integration and mapping from native display list to common model.
multiplatform-markdown-renderer-latex/src/androidMain/kotlin/com/mikepenz/markdown/latex/renderer/GlyphDrawer.android.kt Android native glyph drawing via RaTeXFontLoader typefaces.
multiplatform-markdown-renderer-latex/src/androidMain/kotlin/com/mikepenz/markdown/latex/RaTeXInitializer.kt AndroidX Startup initializer for loading RaTeX fonts.
multiplatform-markdown-renderer-latex/src/androidMain/AndroidManifest.xml Registers the initializer metadata/provider merge for Android apps consuming the library.
multiplatform-markdown-renderer-latex/src/iosMain/kotlin/com/mikepenz/markdown/latex/MathEngine.ios.kt iOS RaTeX FFI parsing + JSON decoding.
multiplatform-markdown-renderer-latex/src/jvmMain/kotlin/com/mikepenz/markdown/latex/MathEngine.jvm.kt JVM stub engine (unsupported).
multiplatform-markdown-renderer-latex/src/jsMain/kotlin/com/mikepenz/markdown/latex/MathEngine.js.kt JS stub engine (unsupported).
multiplatform-markdown-renderer-latex/src/wasmJsMain/kotlin/com/mikepenz/markdown/latex/MathEngine.wasmJs.kt WasmJS stub engine (unsupported).
multiplatform-markdown-renderer-latex/src/nonAndroidMain/kotlin/com/mikepenz/markdown/latex/renderer/GlyphDrawer.nonAndroid.kt Non-Android glyph drawer wiring using the Compose implementation + bundled fonts.
multiplatform-markdown-renderer-latex/src/nativeInterop/cinterop/ratex.def CInterop definition for the iOS RaTeX static library.
multiplatform-markdown-renderer-latex/build.gradle.kts New module build config, including iOS XCFramework download + cinterop setup.
multiplatform-markdown-renderer-latex/src/commonMain/composeResources/font/KaTeX_Size1-Regular.ttf Bundled KaTeX font resource.
multiplatform-markdown-renderer-latex/src/commonMain/composeResources/font/KaTeX_Size2-Regular.ttf Bundled KaTeX font resource.
multiplatform-markdown-renderer-latex/src/commonMain/composeResources/font/KaTeX_Size3-Regular.ttf Bundled KaTeX font resource.
multiplatform-markdown-renderer-latex/src/commonMain/composeResources/font/KaTeX_Size4-Regular.ttf Bundled KaTeX font resource.
multiplatform-markdown-renderer-latex/src/commonMain/composeResources/font/KaTeX_Script-Regular.ttf Bundled KaTeX font resource.
multiplatform-markdown-renderer-latex/src/commonMain/composeResources/font/KaTeX_Caligraphic-Regular.ttf Bundled KaTeX font resource.
multiplatform-markdown-renderer-latex/src/commonMain/composeResources/font/KaTeX_Caligraphic-Bold.ttf Bundled KaTeX font resource.
gradle/libs.versions.toml Adds versions/deps for serialization JSON, AndroidX Startup, and RaTeX.
gradle.properties Enables cinterop commonization and normalizes formatting.
build.gradle.kts Adds the serialization plugin alias to the root build.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread multiplatform-markdown-renderer-latex/build.gradle.kts
Comment thread multiplatform-markdown-renderer-latex/build.gradle.kts
@keta1 keta1 changed the title Add RaTeX module for LaTeX math rendering Add LaTeX module for math rendering Apr 7, 2026
Copilot AI review requested due to automatic review settings April 8, 2026 03:26
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 49 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread gradle/libs.versions.toml Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Copy link
Copy Markdown
Owner

@mikepenz mikepenz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for the PR, please see my initial feedback

Comment on lines +22 to +26
val url = "https://github.com/erweixin/RaTeX/releases/download/v$ratexVersion/RaTeX.xcframework.zip"
logger.lifecycle("Downloading RaTeX XCFramework from $url")
uri(url).toURL().openStream().use { input ->
zipFile.outputStream().use { output -> input.copyTo(output) }
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not something we can have in the build script, as that's high risk and could lead to external code to be injected in the project.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, downloading binaries directly in the build script is a security concern.

SPM(Swift Package Manager) itself also downloads the XCFramework from GitHub Releases, but enforces checksum verification.

I see two viable paths forward:

  1. Add SHA-256 checksum verification to the download task (matching what SPM does)
  2. Add RaTeX as a git submodule and build the XCFramework from source during the build process, so the code is fully auditable

Would either of these work for you?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, it's probably best to wait for 2.4.0: https://kotlinlang.org/docs/whatsnew-eap.html#swift-package-import for official SPM support 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great point — Kotlin 2.4.0's SPM import will definitely simplify the iOS story. However, my concern extends beyond just iOS.

If we want to bring full LaTeX rendering to the JVM Desktop target as well (which is currently fallback-only), we'd still need a native binary integrated via JNI. In that scenario, using a git submodule or prebuilt binaries with cinterop/JNI bridging seems like the most practical path — it gives us a single, auditable integration strategy that works across both iOS (via cinterop) and JVM (via JNI), rather than relying on platform-specific package managers for each target.

That said, I'm happy to wait for 2.4.0 if you'd prefer to defer the iOS integration to SPM and tackle JVM support separately. What do you think?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might even be better to build a KMP library around RaTeX, and have this be maintained in isolation. which then publishes a KMP library with all targets properly handled.

And then an individual extension library can be built on top of that to add support for this project.

An interesting side note. In theory plugins/extensions don't have to be in this project directly, but could be maintained outside - as long as the core APIs support adding the functionality (and in this context having a conversation on API stability and extensibility makes sense).

I'd envision this project to be the core, and an ecosystem of extensions being built all around.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — building a standalone KMP library around RaTeX is the right approach. I'm planning to contribute a KMP implementation directly to the RaTeX project, so it can publish proper multiplatform artifacts. Once that's in place, multiplatform-markdown-renderer-latex would just depend on it as a regular Gradle dependency, with no download tasks or cinterop setup needed in this repo.

Hopefully the RaTeX maintainers will be open to the contribution — I'd really prefer not to maintain a separate repo and deal with Maven Central publishing on my own (Sonatype's portal is... not a pleasant experience 😅).

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HAha fair on the maven central part. Not sure if you have seen the new central portal: https://central.sonatype.com/ which is definitely better than what it was in the past.

Comment thread multiplatform-markdown-renderer-latex/build.gradle.kts Outdated
Copilot AI review requested due to automatic review settings April 10, 2026 01:58
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 30 out of 51 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread multiplatform-markdown-renderer-latex/build.gradle.kts
Copilot AI review requested due to automatic review settings April 10, 2026 03:24
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 52 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread multiplatform-markdown-renderer-latex/build.gradle.kts
keta1 and others added 5 commits April 10, 2026 14:35
Add multiplatform-markdown-renderer-latex module using RaTeX engine for
LaTeX formula rendering. Supports inline math ($...$), block math
($$...$$), and math code fences. Includes platform implementations for
Android (native AAR), iOS (cinterop FFI), and fallback for JVM/JS/WasmJS.

Also fixes unit handling: use sp-based measurement and proper
sp-to-px conversion (including fontScale) for rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Implement WasmJsMathEngine using @JSFun interop with ratex-wasm npm
  package, which provides the same RaTeX rendering engine compiled to
  WebAssembly
- Add ratex-wasm npm dependency for wasmJs source sets
- Integrate gradle-buildconfig-plugin to generate per-platform
  BuildConfig.PLATFORM constant
- Skip font map `remember` caching on WASM platforms where Compose
  resource fonts load asynchronously, ensuring Canvas redraws with
  correct KaTeX fonts after load

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread build.gradle.kts Outdated
Copilot AI review requested due to automatic review settings April 10, 2026 10:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 52 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

gradle/libs.versions.toml:35

  • kotlinx-serialization is declared in the version catalog without a version. Gradle plugin aliases generally require a version (or version.ref), and this will also break alias(libs.plugins.kotlinx.serialization) usage in modules. Define a plugin version (or reference the Kotlin version if appropriate), or remove this alias and apply the serialization plugin via the existing baseLibs.plugins.kotlinSerialization alias.
[plugins]
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization" }


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread multiplatform-markdown-renderer-latex/build.gradle.kts Outdated
Migrate kotlinSerialization plugin from project libs to baseLibs
(now provided by the version catalog). Adapt KMP Android DSL to
stable APIs (androidLibrary -> android, androidResources.enable).

com.mikepenz:version-catalog 0.14.2 -> 0.14.3:
- conventionPlugin 0.10.1 -> 0.10.2
- com.mikepenz:aboutlibraries-* 14.0.0-b03 -> 14.0.1
- com.mikepenz.aboutlibraries.plugin 13.2.1 -> 14.0.1 (via version ref)
- added org.jetbrains.kotlin.plugin.serialization plugin
@keta1 keta1 marked this pull request as draft April 11, 2026 05:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants