
Cross-end UI math typesetting and LaTeX rendering with Compose components, reusable parsed DisplayList caching, synchronous parsing helper for inline layout, and prebuilt native runtimes.
✨ RaTeX-CMP 是一个面向多端 UI 场景的数学公式渲染项目,基于 Kotlin Multiplatform 与 Compose Multiplatform 构建。核心渲染能力由 RaTeX 提供。
让同一套公式渲染能力可以在 Android、iOS、JVM Desktop、JS 和 Wasm 上复用,方便在Compose Multiplatform跨平台应用里统一接入数学排版与展示能力。
这个仓库以独立项目的方式维护,既适合作为库继续演进,也适合作为示例工程和集成参考来使用。
| 平台 | 架构 / 目标 | 备注 |
|---|---|---|
| Android |
arm64-v8a, armeabi-v7a, x86_64, x86
|
x86 目前未测试 |
| iOS | iPhone / Simulator | 通过 Kotlin Multiplatform Framework 集成 |
| JVM Desktop | Windows x86_64, macOS x86_64 / arm64, Linux x86_64 / arm64
|
Desktop native 库按当前机器支持的平台构建与发布 |
| Web |
js(IR), wasmJs
|
两个平台共享 webMain,通过 ratex-wasm npm 包加载 WASM |
| Android | iOS | JVM Desktop |
|---|---|---|
![]() |
![]() |
![]() |
如果你从 Maven Central 使用,确认项目仓库中已包含:
repositories {
mavenCentral()
}如果你是先本地验证再接入,也可以使用:
repositories {
mavenLocal()
mavenCentral()
}当前 KMP 主库坐标为:
implementation("io.github.darriousliu:ratex:0.1.14")在 Kotlin Multiplatform 项目中,通常添加到 commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.darriousliu:ratex:0.1.14")
}
}
}如果你要在 JVM Desktop 上运行,还需要额外添加当前平台对应的 native 运行时依赖:
kotlin {
sourceSets {
jvmMain.dependencies {
implementation("io.github.darriousliu:ratex:0.1.14")
runtimeOnly("io.github.darriousliu:ratex-native-darwin-aarch64:0.1.14")
}
}
}可选的 Desktop native 坐标有:
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在这个仓库里,Desktop native 库本身是独立发布的子模块;示例工程会按当前主机平台自动选择对应的运行时依赖。
最简单的用法是直接传入 LaTeX 字符串:
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),
)
}如果你想渲染行内公式,可以将 displayMode 设为 false:
RaTeX(
latex = """e^{i\pi}+1=0""",
fontSize = 20.sp,
displayMode = false,
)如果你希望颜色跟随当前 Material 主题文本色,也可以直接省略 color 参数,组件默认会读取 LocalContentColor.current。
如果你希望先解析,再在多个地方复用 DisplayList,可以使用 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,
)
}在 Text 中混排行内公式时,先通过 rememberBlockingRaTeXDisplayList 同步解析公式,再根据测量结果创建 InlineTextContent:
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("质能方程 ")
appendInlineContent(formulaId, formula)
append(" 描述了质量与能量的关系。")
},
inlineContent = inlineContent,
)
}注意:JS/Wasm 浏览器端在任何解析前,都必须先在协程中显式调用 RaTeXEngine.initialize()。
rememberRaTeXDisplayList / RaTeX(latex = ...) 会在初始化后异步加载字体并解析公式;如果要使用
rememberBlockingRaTeXDisplayList,还应预先异步调用 RaTeXFontLoader.ensureLoaded(),因此同步辅助
API 更适合 Android、iOS 和 JVM Desktop。
library:核心库模块desktop-native/*:JVM Desktop native 库发布模块example:共享示例模块,包含 Desktop、JS/Wasm 运行入口和新的 RaTeXShowcasePage
androidApp:Android 示例应用iosApp:iOS 示例工程build-logic:共享 Gradle 约定插件,封装 Desktop native 发布逻辑external/RaTeX:上游 RaTeX submodulegit clone https://github.com/darriousliu/RaTeX-CMP.git
cd RaTeX-CMP
git submodule update --init --recursive如果已经克隆过仓库但没有初始化 submodule,也可以只执行最后一行命令。
建议准备以下工具:
按平台不同,可能还会用到这些工具:
cargo-ndk
cargo-zigbuild 和 zig
在首次运行或构建某个平台之前,建议先安装该平台所需的 Rust target。
Android:
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-androidiOS:
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-iosJVM Desktop:
rustup target add
bash prepare-jvm-rust.sh --all 时,脚本会根据当前机器能力自动选择可构建目标,并自动执行
rustup target add
arm64 macOS 上,会构建 darwin-aarch64、darwin-x86-64、linux-aarch64、linux-x86-64
,不会尝试构建 windows-x86-64
如果你第一次运行项目,或者修改了底层 Rust 代码,通常需要先准备对应平台的本地产物。
Android:
bash prepare-android-rust.shiOS:
bash prepare-ios-rust.shJVM Desktop:
bash prepare-jvm-rust.sh准备当前机器可构建的全部 Desktop Rust 产物:
bash prepare-jvm-rust.sh --all如果你只是做 Kotlin / Compose 层开发,并且仓库里已经有可用产物,可以按需执行,而不必每次都重复准备。
JVM Desktop 示例:
./gradlew :example:runWindows 下可以使用:
.\gradlew.bat :example:runAndroid Debug 包:
./gradlew :androidApp:assembleDebugWindows 下可以使用:
.\gradlew.bat :androidApp:assembleDebugiOS 开发建议在 macOS 上打开 iosApp/iosApp.xcodeproj 进行调试。
Web 示例:
./gradlew :example:jsBrowserDevelopmentRun
./gradlew :example:wasmJsBrowserDevelopmentRunbuild-logic
external/RaTeX
初始化 submodule:
git submodule update --init --recursive运行 Desktop 示例:
./gradlew :example:run构建 Android 示例:
./gradlew :androidApp:assembleDebug运行 Web 示例:
./gradlew :example:jsBrowserDevelopmentRun
./gradlew :example:wasmJsBrowserDevelopmentRun准备 Android Rust 产物:
bash prepare-android-rust.sh准备 iOS Rust 产物:
bash prepare-ios-rust.sh准备 Desktop Rust 产物:
bash prepare-jvm-rust.sh准备当前机器可构建的全部 Desktop Rust 产物:
bash prepare-jvm-rust.sh --all发布库到 Maven Central:
发布前请先准备好发布凭据与签名配置。
发布当前机器支持的全部产物到 Maven Local:
./gradlew publishToMavenLocal这个命令会发布:
:library 的 KMP 主库这也是当前推荐的本地验证方式;如果命令成功,说明主库和当前机器可发布的 Desktop native 库都会进入本地 Maven 仓库。
例如:
arm64 macOS 上,会额外发布 ratex-native-darwin-aarch64、ratex-native-darwin-x86-64、ratex-native-linux-aarch64、ratex-native-linux-x86-64
Linux 上,会额外发布 ratex-native-linux-aarch64、ratex-native-linux-x86-64
Windows 上,会额外发布 ratex-native-windows-x86-64
发布当前机器支持的全部产物到 Maven Central:
./gradlew publishAndReleaseToMavenCentral单独发布 KMP 主库:
./gradlew :library:publishKotlinMultiplatformPublicationToMavenCentralRepository发布当前机器支持的全部 JVM Desktop native 库:
./gradlew publishSupportedDesktopNativePublicationsToMavenCentralRepository发布当前机器支持的全部 JVM Desktop native 库到 Maven Local:
./gradlew publishSupportedDesktopNativePublicationsToMavenLocal这个任务会自动执行:
prepare-jvm-rust.sh <target>
这些 Desktop native 子模块共享同一套 build-logic 预编译脚本插件配置,只在各自模块里声明目标平台、文件名、artifactId 和支持的宿主机范围。
例如:
arm64 macOS 上,会发布 darwin-aarch64、darwin-x86-64、linux-aarch64、linux-x86-64
Linux 上,会发布 linux-aarch64、linux-x86-64
Windows 上,会发布 windows-x86-64
感谢 RaTeX 项目提供的核心能力与开源基础。
也感谢 Kotlin Multiplatform、Compose Multiplatform、Rust 以及相关开源社区,让这个跨平台项目能够持续演进,并以更统一的方式服务多端应用开发。
✨ RaTeX-CMP 是一个面向多端 UI 场景的数学公式渲染项目,基于 Kotlin Multiplatform 与 Compose Multiplatform 构建。核心渲染能力由 RaTeX 提供。
让同一套公式渲染能力可以在 Android、iOS、JVM Desktop、JS 和 Wasm 上复用,方便在Compose Multiplatform跨平台应用里统一接入数学排版与展示能力。
这个仓库以独立项目的方式维护,既适合作为库继续演进,也适合作为示例工程和集成参考来使用。
| 平台 | 架构 / 目标 | 备注 |
|---|---|---|
| Android |
arm64-v8a, armeabi-v7a, x86_64, x86
|
x86 目前未测试 |
| iOS | iPhone / Simulator | 通过 Kotlin Multiplatform Framework 集成 |
| JVM Desktop | Windows x86_64, macOS x86_64 / arm64, Linux x86_64 / arm64
|
Desktop native 库按当前机器支持的平台构建与发布 |
| Web |
js(IR), wasmJs
|
两个平台共享 webMain,通过 ratex-wasm npm 包加载 WASM |
| Android | iOS | JVM Desktop |
|---|---|---|
![]() |
![]() |
![]() |
如果你从 Maven Central 使用,确认项目仓库中已包含:
repositories {
mavenCentral()
}如果你是先本地验证再接入,也可以使用:
repositories {
mavenLocal()
mavenCentral()
}当前 KMP 主库坐标为:
implementation("io.github.darriousliu:ratex:0.1.14")在 Kotlin Multiplatform 项目中,通常添加到 commonMain:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("io.github.darriousliu:ratex:0.1.14")
}
}
}如果你要在 JVM Desktop 上运行,还需要额外添加当前平台对应的 native 运行时依赖:
kotlin {
sourceSets {
jvmMain.dependencies {
implementation("io.github.darriousliu:ratex:0.1.14")
runtimeOnly("io.github.darriousliu:ratex-native-darwin-aarch64:0.1.14")
}
}
}可选的 Desktop native 坐标有:
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在这个仓库里,Desktop native 库本身是独立发布的子模块;示例工程会按当前主机平台自动选择对应的运行时依赖。
最简单的用法是直接传入 LaTeX 字符串:
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),
)
}如果你想渲染行内公式,可以将 displayMode 设为 false:
RaTeX(
latex = """e^{i\pi}+1=0""",
fontSize = 20.sp,
displayMode = false,
)如果你希望颜色跟随当前 Material 主题文本色,也可以直接省略 color 参数,组件默认会读取 LocalContentColor.current。
如果你希望先解析,再在多个地方复用 DisplayList,可以使用 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,
)
}在 Text 中混排行内公式时,先通过 rememberBlockingRaTeXDisplayList 同步解析公式,再根据测量结果创建 InlineTextContent:
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("质能方程 ")
appendInlineContent(formulaId, formula)
append(" 描述了质量与能量的关系。")
},
inlineContent = inlineContent,
)
}注意:JS/Wasm 浏览器端在任何解析前,都必须先在协程中显式调用 RaTeXEngine.initialize()。
rememberRaTeXDisplayList / RaTeX(latex = ...) 会在初始化后异步加载字体并解析公式;如果要使用
rememberBlockingRaTeXDisplayList,还应预先异步调用 RaTeXFontLoader.ensureLoaded(),因此同步辅助
API 更适合 Android、iOS 和 JVM Desktop。
library:核心库模块desktop-native/*:JVM Desktop native 库发布模块example:共享示例模块,包含 Desktop、JS/Wasm 运行入口和新的 RaTeXShowcasePage
androidApp:Android 示例应用iosApp:iOS 示例工程build-logic:共享 Gradle 约定插件,封装 Desktop native 发布逻辑external/RaTeX:上游 RaTeX submodulegit clone https://github.com/darriousliu/RaTeX-CMP.git
cd RaTeX-CMP
git submodule update --init --recursive如果已经克隆过仓库但没有初始化 submodule,也可以只执行最后一行命令。
建议准备以下工具:
按平台不同,可能还会用到这些工具:
cargo-ndk
cargo-zigbuild 和 zig
在首次运行或构建某个平台之前,建议先安装该平台所需的 Rust target。
Android:
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-androidiOS:
rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-iosJVM Desktop:
rustup target add
bash prepare-jvm-rust.sh --all 时,脚本会根据当前机器能力自动选择可构建目标,并自动执行
rustup target add
arm64 macOS 上,会构建 darwin-aarch64、darwin-x86-64、linux-aarch64、linux-x86-64
,不会尝试构建 windows-x86-64
如果你第一次运行项目,或者修改了底层 Rust 代码,通常需要先准备对应平台的本地产物。
Android:
bash prepare-android-rust.shiOS:
bash prepare-ios-rust.shJVM Desktop:
bash prepare-jvm-rust.sh准备当前机器可构建的全部 Desktop Rust 产物:
bash prepare-jvm-rust.sh --all如果你只是做 Kotlin / Compose 层开发,并且仓库里已经有可用产物,可以按需执行,而不必每次都重复准备。
JVM Desktop 示例:
./gradlew :example:runWindows 下可以使用:
.\gradlew.bat :example:runAndroid Debug 包:
./gradlew :androidApp:assembleDebugWindows 下可以使用:
.\gradlew.bat :androidApp:assembleDebugiOS 开发建议在 macOS 上打开 iosApp/iosApp.xcodeproj 进行调试。
Web 示例:
./gradlew :example:jsBrowserDevelopmentRun
./gradlew :example:wasmJsBrowserDevelopmentRunbuild-logic
external/RaTeX
初始化 submodule:
git submodule update --init --recursive运行 Desktop 示例:
./gradlew :example:run构建 Android 示例:
./gradlew :androidApp:assembleDebug运行 Web 示例:
./gradlew :example:jsBrowserDevelopmentRun
./gradlew :example:wasmJsBrowserDevelopmentRun准备 Android Rust 产物:
bash prepare-android-rust.sh准备 iOS Rust 产物:
bash prepare-ios-rust.sh准备 Desktop Rust 产物:
bash prepare-jvm-rust.sh准备当前机器可构建的全部 Desktop Rust 产物:
bash prepare-jvm-rust.sh --all发布库到 Maven Central:
发布前请先准备好发布凭据与签名配置。
发布当前机器支持的全部产物到 Maven Local:
./gradlew publishToMavenLocal这个命令会发布:
:library 的 KMP 主库这也是当前推荐的本地验证方式;如果命令成功,说明主库和当前机器可发布的 Desktop native 库都会进入本地 Maven 仓库。
例如:
arm64 macOS 上,会额外发布 ratex-native-darwin-aarch64、ratex-native-darwin-x86-64、ratex-native-linux-aarch64、ratex-native-linux-x86-64
Linux 上,会额外发布 ratex-native-linux-aarch64、ratex-native-linux-x86-64
Windows 上,会额外发布 ratex-native-windows-x86-64
发布当前机器支持的全部产物到 Maven Central:
./gradlew publishAndReleaseToMavenCentral单独发布 KMP 主库:
./gradlew :library:publishKotlinMultiplatformPublicationToMavenCentralRepository发布当前机器支持的全部 JVM Desktop native 库:
./gradlew publishSupportedDesktopNativePublicationsToMavenCentralRepository发布当前机器支持的全部 JVM Desktop native 库到 Maven Local:
./gradlew publishSupportedDesktopNativePublicationsToMavenLocal这个任务会自动执行:
prepare-jvm-rust.sh <target>
这些 Desktop native 子模块共享同一套 build-logic 预编译脚本插件配置,只在各自模块里声明目标平台、文件名、artifactId 和支持的宿主机范围。
例如:
arm64 macOS 上,会发布 darwin-aarch64、darwin-x86-64、linux-aarch64、linux-x86-64
Linux 上,会发布 linux-aarch64、linux-x86-64
Windows 上,会发布 windows-x86-64
感谢 RaTeX 项目提供的核心能力与开源基础。
也感谢 Kotlin Multiplatform、Compose Multiplatform、Rust 以及相关开源社区,让这个跨平台项目能够持续演进,并以更统一的方式服务多端应用开发。