RaTeX-CMP
July 28, 2026 · View on GitHub
✨ RaTeX-CMP is a math formula rendering project for multi-platform UI scenarios, built with Kotlin Multiplatform and Compose Multiplatform. Its core rendering capability is powered by RaTeX.
It allows the same formula rendering engine to be reused across Android, iOS, JVM Desktop, JS, and Wasm, making it easier to integrate consistent mathematical typesetting and display into Compose Multiplatform applications.
This repository is maintained as an independent project. It can continue evolving as a library while also serving as a sample project and integration reference.
🌍 Supported Platforms
| Platform | Architectures / Targets | Notes |
|---|---|---|
| Android | arm64-v8a, armeabi-v7a, x86_64, x86 | x86 is currently untested |
| iOS | iPhone / Simulator | Integrated via Kotlin Multiplatform Framework |
| JVM Desktop | Windows x86_64, macOS x86_64 / arm64, Linux x86_64 / arm64 | Desktop native libraries are built and published based on what the current machine supports |
| Web | js(IR), wasmJs | Both targets share webMain and load WASM through the ratex-wasm npm package |
📷 Screenshots
| Android | iOS | JVM Desktop |
|---|---|---|
![]() |
![]() |
![]() |
🚀 Usage
1. Add repositories
If you are using Maven Central, make sure your project repositories include:
repositories {
mavenCentral()
}
If you want to validate locally first, you can also use:
repositories {
mavenLocal()
mavenCentral()
}
2. Add the dependency
The current KMP main library coordinates are:
implementation("io.github.darriousliu:ratex:0.1.14")
In a Kotlin Multiplatform project, you would typically add it to commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.darriousliu:ratex:0.1.14")
}
}
}
If you want to run on JVM Desktop, you also need to add the native runtime dependency for the current platform:
kotlin {
sourceSets {
jvmMain.dependencies {
implementation("io.github.darriousliu:ratex:0.1.14")
runtimeOnly("io.github.darriousliu:ratex-native-darwin-aarch64:0.1.14")
}
}
}
Available Desktop native coordinates:
io.github.darriousliu:ratex-native-darwin-aarch64io.github.darriousliu:ratex-native-darwin-x86-64io.github.darriousliu:ratex-native-linux-aarch64io.github.darriousliu:ratex-native-linux-x86-64io.github.darriousliu:ratex-native-windows-x86-64
In this repository, Desktop native libraries are published as separate submodules; the sample app automatically selects the matching runtime dependency for the current host platform.
3. Use the Compose component
3.1 Basic usage
The simplest usage is to pass a LaTeX string directly:
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import io.ratex.compose.RaTeX
@Composable
fun FormulaSample(modifier: Modifier = Modifier) {
RaTeX(
latex = """\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}""",
modifier = modifier,
fontSize = 28.sp,
displayMode = true,
color = Color(0xFF1565C0),
)
}
If you want inline math, set displayMode = false:
RaTeX(
latex = """e^{i\pi}+1=0""",
fontSize = 20.sp,
displayMode = false,
)
If you want the formula color to follow the current Material text color, you can omit color and the composable will use LocalContentColor.current.
3.2 Reuse parsed results
If you want to parse first and reuse the resulting DisplayList in multiple places, you can use rememberRaTeXDisplayList:
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.unit.sp
import io.ratex.compose.RaTeX
import io.ratex.compose.rememberRaTeXDisplayList
@Composable
fun ParsedFormulaSample(latex: String) {
val parseResult by rememberRaTeXDisplayList(
latex = latex,
displayMode = true,
color = MaterialTheme.colorScheme.primary,
)
RaTeX(
displayList = parseResult?.getOrNull(),
fontSize = 28.sp,
)
}
3.3 Use Text with inlineContent
To mix an inline formula into Text, parse it synchronously with rememberBlockingRaTeXDisplayList, then create InlineTextContent from the measured result:
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.unit.sp
import io.ratex.compose.RaTeX
import io.ratex.compose.rememberBlockingRaTeXDisplayList
import io.ratex.measure
@Composable
fun InlineFormulaText() {
val formula = """E = mc^2"""
val formulaId = "energy"
val formulaFontSize = 18.sp
val density = LocalDensity.current
val parseResult = rememberBlockingRaTeXDisplayList(
latex = formula,
displayMode = false,
)
val displayList = parseResult.getOrNull()
val fontSizePx = with(density) { formulaFontSize.toPx() }
val measured = remember(displayList, fontSizePx) {
displayList?.measure(fontSizePx)
}
val placeholderWidth = with(density) {
(measured?.widthPx ?: fontSizePx).toSp()
}
val placeholderHeight = with(density) {
(measured?.totalHeightPx ?: fontSizePx).toSp()
}
val inlineContent = mapOf(
formulaId to InlineTextContent(
placeholder = Placeholder(
width = placeholderWidth,
height = placeholderHeight,
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter,
),
) {
RaTeX(
displayList = displayList,
fontSize = formulaFontSize,
)
},
)
Text(
text = buildAnnotatedString {
append("The mass–energy equation ")
appendInlineContent(formulaId, formula)
append(" describes the relationship between mass and energy.")
},
inlineContent = inlineContent,
)
}
Note: JS/Wasm browser targets must explicitly call RaTeXEngine.initialize() from a coroutine
before any parsing. After initialization, rememberRaTeXDisplayList / RaTeX(latex = ...) load
fonts and parse asynchronously. Before using rememberBlockingRaTeXDisplayList, also preload fonts
asynchronously with RaTeXFontLoader.ensureLoaded(); the synchronous helper is therefore intended
mainly for Android, iOS, and JVM Desktop.
🧭 Repository Overview
library: core library moduledesktop-native/*: publishing modules for JVM Desktop native librariesexample: shared sample module, including the Desktop, JS/Wasm entry points and the newRaTeXShowcasePageandroidApp: Android sample appiosApp: iOS sample projectbuild-logic: shared Gradle convention plugins for Desktop native publishingexternal/RaTeX: upstream RaTeX submodule
🛠️ Local Development
1. Clone the repository and initialize submodules
git clone https://github.com/darriousliu/RaTeX-CMP.git
cd RaTeX-CMP
git submodule update --init --recursive
If you have already cloned the repository but have not initialized submodules, you can simply run the last command.
2. Prepare the base environment
Recommended tools:
- JDK 17
- Android Studio or IntelliJ IDEA
- Rust toolchain
- Bash environment
- Android SDK; Android NDK is also required if you want to build Android native libraries
- Xcode; required only when developing for iOS on macOS
Depending on the platform, you may also need:
- Android:
cargo-ndk - Desktop full-platform native packaging:
cargo-zigbuildandzig
3. Install the required Rust targets
Before running or building for a platform for the first time, it is recommended to install the required Rust targets for that platform.
Android:
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android
iOS:
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
JVM Desktop:
- When building only for the current host platform, you usually do not need to run
rustup target addmanually - When running
bash prepare-jvm-rust.sh --all, the script automatically selects all targets supported by the current machine and runsrustup target addas needed - For example, on
arm64 macOS, it buildsdarwin-aarch64,darwin-x86-64,linux-aarch64, andlinux-x86-64, but does not attemptwindows-x86-64
4. Prepare local artifacts
If this is your first time running the project, or if you changed the underlying Rust code, you will usually need to prepare the corresponding local artifacts first.
Android:
bash prepare-android-rust.sh
iOS:
bash prepare-ios-rust.sh
JVM Desktop:
bash prepare-jvm-rust.sh
Prepare all Desktop Rust artifacts supported by the current machine:
bash prepare-jvm-rust.sh --all
If you are only working on the Kotlin / Compose layer and usable artifacts already exist in the repository, run these commands only when needed instead of every time.
5. Run and verify
JVM Desktop sample:
./gradlew :example:run
On Windows:
.\gradlew.bat :example:run
Android debug package:
./gradlew :androidApp:assembleDebug
On Windows:
.\gradlew.bat :androidApp:assembleDebug
For iOS development, it is recommended to open iosApp/iosApp.xcodeproj on macOS for debugging.
Web sample:
./gradlew :example:jsBrowserDevelopmentRun
./gradlew :example:wasmJsBrowserDevelopmentRun
6. Development suggestions
- Prefer completing Compose UI, Kotlin API, and sample project work within this repository first
- Prefer keeping Desktop native publishing changes inside
build-logic - Enter
external/RaTeXonly when you need to coordinate with the lower-level engine - After updating the submodule, remember to regenerate the corresponding platform artifacts
- When committing changes, distinguish carefully between changes in this project and changes in the submodule
🚀 Common Commands
Initialize submodules:
git submodule update --init --recursive
Run the Desktop sample:
./gradlew :example:run
Build the Android sample:
./gradlew :androidApp:assembleDebug
Run the Web sample:
./gradlew :example:jsBrowserDevelopmentRun
./gradlew :example:wasmJsBrowserDevelopmentRun
Prepare Android Rust artifacts:
bash prepare-android-rust.sh
Prepare iOS Rust artifacts:
bash prepare-ios-rust.sh
Prepare Desktop Rust artifacts:
bash prepare-jvm-rust.sh
Prepare all Desktop Rust artifacts supported by the current machine:
bash prepare-jvm-rust.sh --all
Publish the library to Maven Central:
Make sure your publishing credentials and signing configuration are ready before publishing.
Publish all artifacts supported by the current machine to Maven Local:
./gradlew publishToMavenLocal
This publishes:
- the KMP main library from
:library - Desktop native submodules supported by the current machine
This is also the recommended local verification path. If it succeeds, both the main library and the Desktop native libraries supported by the current machine will be published to your local Maven repository.
For example:
- On
arm64 macOS, it also publishesratex-native-darwin-aarch64,ratex-native-darwin-x86-64,ratex-native-linux-aarch64, andratex-native-linux-x86-64 - On
Linux, it also publishesratex-native-linux-aarch64andratex-native-linux-x86-64 - On
Windows, it also publishesratex-native-windows-x86-64
Publish all artifacts supported by the current machine to Maven Central:
./gradlew publishAndReleaseToMavenCentral
Publish only the KMP main library:
./gradlew :library:publishKotlinMultiplatformPublicationToMavenCentralRepository
Publish all JVM Desktop native libraries supported by the current machine:
./gradlew publishSupportedDesktopNativePublicationsToMavenCentralRepository
Publish all JVM Desktop native libraries supported by the current machine to Maven Local:
./gradlew publishSupportedDesktopNativePublicationsToMavenLocal
This task automatically:
- runs the publish tasks for native submodules supported by the current machine
- automatically calls the matching
prepare-jvm-rust.sh <target>inside each submodule - verifies that Desktop native artifacts supported by the current machine were generated successfully
These Desktop native submodules share the same precompiled script plugin from build-logic; each module only declares its target platform, file name, artifactId, and supported host OS set.
For example:
- On
arm64 macOS, it publishesdarwin-aarch64,darwin-x86-64,linux-aarch64, andlinux-x86-64 - On
Linux, it publisheslinux-aarch64andlinux-x86-64 - On
Windows, it publisheswindows-x86-64
🙏 Acknowledgements
Thanks to the RaTeX project for providing the core capabilities and open-source foundation.
Thanks also to Kotlin Multiplatform, Compose Multiplatform, Rust, and the related open-source communities for making this cross-platform project possible and helping it continue to evolve in a more unified way across platforms.


