Conversation
ca3915c to
24b69f3
Compare
There was a problem hiding this comment.
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-latexmodule 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 dynamicMarkdownInlineContentresolution 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.
There was a problem hiding this comment.
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.
mikepenz
left a comment
There was a problem hiding this comment.
Thank you very much for the PR, please see my initial feedback
| 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) } | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
- Add SHA-256 checksum verification to the download task (matching what SPM does)
- 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?
There was a problem hiding this comment.
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 🤔
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 😅).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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>
…e text measurement
…enceContent utility
There was a problem hiding this comment.
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-serializationis declared in the version catalog without a version. Gradle plugin aliases generally require a version (orversion.ref), and this will also breakalias(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 existingbaseLibs.plugins.kotlinSerializationalias.
[plugins]
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization" }
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
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
Summary
multiplatform-markdown-renderer-latexmodule that integrates RaTeX engine for LaTeX math formula rendering$...$), block math ($$...$$), and math code fences (```math)blockMathcomponent and composableMarkdownInlineContentinterface to enable math rendering as an opt-in extensionPlatform support
io.ratex:ratex-androidratex-wasmnpm packageScreenshot
Breaking changes
MarkdownInlineContentinterface changed: TheinlineContentproperty (val inlineContent: Map<String, InlineTextContent>) has been replaced with a@Composablefunction (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.DefaultMarkdownInlineContentremoved: Replaced bymarkdownInlineContent()factory function. Migrate fromDefaultMarkdownInlineContent(map)tomarkdownInlineContent(map).Usage
Test plan