
Vertical typesetting editor supporting mixed Mongolian/Manchu and CJK/Kana/Hangul/Latin, with correct vertical rendering, cursor/selection, selection handles, undo/redo, multi-column flow, theming and context-menu hooks.
A Compose Multiplatform text editor component for vertical typesetting — Mongolian, Manchu, Chinese, Japanese, Korean, and Latin mixed layout, rendered correctly in a single column or multi-column vertical flow.
Compose Multiplatform 竖排文字编辑器组件,支持蒙古文、满文、汉字、日文、韩文、英文混排,单列或多列竖排显示。
| Platform | Status |
|---|---|
| Android | ✅ |
| iOS | ✅ |
| Desktop (JVM) | ✅ |
maxColumns — limit visible columns, scroll horizontally for overflowenabled / readOnly / selectable
VTextFieldColors (follows MaterialTheme by default)onShowContextMenu — implement your own menu UIThe library module packaging is in progress. For now, copy the source into your project or use a local Maven publication.
库模块打包进行中,目前可将源码复制到项目中使用,或通过本地 Maven 发布引入。
var value by remember { mutableStateOf(TextFieldValue("汉字\nᠮᠣᠩᠭᠣᠯ ᠪᠢᠴᠢᠭ")) }
VTextField(
value = value,
onValueChange = { value = it },
modifier = Modifier.fillMaxSize(),
)// In commonMain resources: composeApp/src/commonMain/composeResources/font/NotoSansMongolian_Regular.ttf
val mongolianFont = FontFamily(Font(Res.font.NotoSansMongolian_Regular))
VTextField(
value = value,
onValueChange = { value = it },
verticalFontFamily = mongolianFont,
style = TextStyle(fontSize = 24.sp, color = Color.Black),
modifier = Modifier.fillMaxSize(),
)// maxColumns = 1 → single column, scrolls vertically on overflow
VTextField(
value = value,
onValueChange = { value = it },
verticalFontFamily = mongolianFont,
maxColumns = 1,
modifier = Modifier.height(200.dp),
)The library fires onShowContextMenu on long-press, double-tap, drag-select end, and handle drag end. Implement your own menu UI:
库在长按、双击、拖选结束、把手拖动结束时触发 onShowContextMenu,由调用方实现菜单 UI:
var showMenu by remember { mutableStateOf(false) }
var menuPos by remember { mutableStateOf(Offset.Zero) }
Box {
VTextField(
value = value,
onValueChange = { value = it },
verticalFontFamily = mongolianFont,
modifier = Modifier.fillMaxSize(),
onShowContextMenu = { position, hasSelection ->
menuPos = position
showMenu = true
},
)
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
offset = DpOffset(menuPos.x.dp, menuPos.y.dp),
) {
// Copy, Cut, Paste, Select All ...
}
}The library does not manage keyboard dismissal — handle it at the app level:
库不管键盘收起,在 app 层处理:
val focusManager = LocalFocusManager.current
Box(
modifier = Modifier
.fillMaxSize()
// Tap outside to dismiss keyboard
.pointerInput(Unit) { detectTapGestures { focusManager.clearFocus() } }
) {
VTextField(...)
// Optional: "Done" button above keyboard
if (isFocused) {
TextButton(
onClick = { focusManager.clearFocus() },
modifier = Modifier.align(Alignment.BottomEnd),
) {
Text("Done")
}
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
value |
TextFieldValue |
— | Current text and selection state / 当前文本与选区 |
onValueChange |
(TextFieldValue) -> Unit |
— | Called on text or selection change / 文本或选区变化回调 |
verticalFontFamily |
FontFamily |
FontFamily.Default |
Font for Mongolian / Manchu script only / 仅用于蒙古文/满文区段的字体 |
style |
TextStyle |
TextStyle.Default |
Text style (size, color, etc.) / 文字样式 |
colors |
VTextFieldColors |
VTextFieldDefaults.colors() |
Cursor / selection / handle colors / 光标、选区、把手颜色 |
placeholder |
String |
"" |
Shown when text is empty / 文字为空时显示 |
maxColumns |
Int |
Int.MAX_VALUE |
Max visible columns; overflow scrolls horizontally / 最多显示几列,超出横向滚动 |
enabled |
Boolean |
true |
If false, no interaction / 为 false 时不可交互 |
readOnly |
Boolean |
false |
Selectable but not editable / 可选取但不可编辑 |
selectable |
Boolean |
true |
Whether text can be selected / 是否允许选取 |
onShowContextMenu |
((Offset, Boolean) -> Unit)? |
null |
Context menu trigger; Boolean = hasSelection / 菜单触发回调,Boolean 表示是否有选区 |
ascentTrim |
Float |
0.15f |
Trim ascent to reduce inter-column spacing / 削减 Ascent 调整列间距 |
baselineShift |
Float |
0f |
Baseline shift / 基线偏移 |
fixedWidth |
Boolean |
true |
All columns same width (max line height) / 等宽列 |
VTextFieldDefaults.colors(
cursor: Color = MaterialTheme.colorScheme.primary,
selectionHandle: Color = MaterialTheme.colorScheme.primary,
selectionBackground: Color = LocalTextSelectionColors.current.backgroundColor,
)Correct rendering of Mongolian and Manchu script requires a font with proper OpenType vertical features (vert, vrt2).
蒙古文/满文的正确渲染依赖支持 OpenType 竖排特性(vert、vrt2)的字体。
| Platform | Built-in Mongolian font | Quality |
|---|---|---|
| Android | ❌ None | Renders as boxes without a custom font |
| iOS 12+ | ✅ Mongolian Baiti | Partial — Manchu / Sibe incomplete |
| macOS | ✅ Mongolian Baiti | Partial |
| Windows | ✅ Mongolian Baiti | Partial |
Android requires a bundled font. Other platforms are recommended to bundle one for best results.
Android 必须自带字体,其他平台建议自带以获得最佳效果。
Noto Sans Mongolian — Apache 2.0 license, free for commercial use, covers Mongolian, Manchu, Sibe, Todo, and more.
Noto Sans Mongolian — Apache 2.0 协议,免费商用,覆盖蒙古文、满文、锡伯文、托忒文等。
Download NotoSansMongolian-Regular.ttf and place it in:
composeApp/src/commonMain/composeResources/font/NotoSansMongolian_Regular.ttf
Use it:
// In a @Composable
val mongolianFont = FontFamily(Font(Res.font.NotoSansMongolian_Regular))
VTextField(
value = value,
onValueChange = { value = it },
verticalFontFamily = mongolianFont,
)
verticalFontFamilyis applied only to Mongolian / Manchu code point ranges. All other characters (CJK, Latin, Emoji, etc.) use the font fromstyle.
verticalFontFamily仅应用于蒙古文/满文码点区间,其他字符(汉字、拉丁文、Emoji 等)使用style中的字体。
Mongolian and Manchu script is stored "lying on its side" in Unicode — characters are encoded for horizontal rendering but displayed vertically. MMTextKit uses Compose's TextMeasurer to measure text horizontally, then rotates each "line" 90° to form a vertical column.
蒙古文/满文在 Unicode 中是"侧卧"存储的——字符按横排编码,但需要竖排显示。MMTextKit 使用 Compose 的 TextMeasurer 横向测量文字,再将每"行"旋转 90° 作为竖排的一"列"。
Upright characters (CJK, Kana, Hangul, Emoji): rotated back −90° after the column rotation, so they appear upright.
Mongolian / Manchu characters: follow the column rotation, preserving their natural vertical flow and ligatures.
直立字符(汉字、假名、韩文、Emoji):在列旋转后再反向旋转 −90°,保持直立。
蒙古文/满文字符:随列旋转,保持自然的竖排连写形态。
MIT © 2026 lzdev42
A Compose Multiplatform text editor component for vertical typesetting — Mongolian, Manchu, Chinese, Japanese, Korean, and Latin mixed layout, rendered correctly in a single column or multi-column vertical flow.
Compose Multiplatform 竖排文字编辑器组件,支持蒙古文、满文、汉字、日文、韩文、英文混排,单列或多列竖排显示。
| Platform | Status |
|---|---|
| Android | ✅ |
| iOS | ✅ |
| Desktop (JVM) | ✅ |
maxColumns — limit visible columns, scroll horizontally for overflowenabled / readOnly / selectable
VTextFieldColors (follows MaterialTheme by default)onShowContextMenu — implement your own menu UIThe library module packaging is in progress. For now, copy the source into your project or use a local Maven publication.
库模块打包进行中,目前可将源码复制到项目中使用,或通过本地 Maven 发布引入。
var value by remember { mutableStateOf(TextFieldValue("汉字\nᠮᠣᠩᠭᠣᠯ ᠪᠢᠴᠢᠭ")) }
VTextField(
value = value,
onValueChange = { value = it },
modifier = Modifier.fillMaxSize(),
)// In commonMain resources: composeApp/src/commonMain/composeResources/font/NotoSansMongolian_Regular.ttf
val mongolianFont = FontFamily(Font(Res.font.NotoSansMongolian_Regular))
VTextField(
value = value,
onValueChange = { value = it },
verticalFontFamily = mongolianFont,
style = TextStyle(fontSize = 24.sp, color = Color.Black),
modifier = Modifier.fillMaxSize(),
)// maxColumns = 1 → single column, scrolls vertically on overflow
VTextField(
value = value,
onValueChange = { value = it },
verticalFontFamily = mongolianFont,
maxColumns = 1,
modifier = Modifier.height(200.dp),
)The library fires onShowContextMenu on long-press, double-tap, drag-select end, and handle drag end. Implement your own menu UI:
库在长按、双击、拖选结束、把手拖动结束时触发 onShowContextMenu,由调用方实现菜单 UI:
var showMenu by remember { mutableStateOf(false) }
var menuPos by remember { mutableStateOf(Offset.Zero) }
Box {
VTextField(
value = value,
onValueChange = { value = it },
verticalFontFamily = mongolianFont,
modifier = Modifier.fillMaxSize(),
onShowContextMenu = { position, hasSelection ->
menuPos = position
showMenu = true
},
)
DropdownMenu(
expanded = showMenu,
onDismissRequest = { showMenu = false },
offset = DpOffset(menuPos.x.dp, menuPos.y.dp),
) {
// Copy, Cut, Paste, Select All ...
}
}The library does not manage keyboard dismissal — handle it at the app level:
库不管键盘收起,在 app 层处理:
val focusManager = LocalFocusManager.current
Box(
modifier = Modifier
.fillMaxSize()
// Tap outside to dismiss keyboard
.pointerInput(Unit) { detectTapGestures { focusManager.clearFocus() } }
) {
VTextField(...)
// Optional: "Done" button above keyboard
if (isFocused) {
TextButton(
onClick = { focusManager.clearFocus() },
modifier = Modifier.align(Alignment.BottomEnd),
) {
Text("Done")
}
}
}| Parameter | Type | Default | Description |
|---|---|---|---|
value |
TextFieldValue |
— | Current text and selection state / 当前文本与选区 |
onValueChange |
(TextFieldValue) -> Unit |
— | Called on text or selection change / 文本或选区变化回调 |
verticalFontFamily |
FontFamily |
FontFamily.Default |
Font for Mongolian / Manchu script only / 仅用于蒙古文/满文区段的字体 |
style |
TextStyle |
TextStyle.Default |
Text style (size, color, etc.) / 文字样式 |
colors |
VTextFieldColors |
VTextFieldDefaults.colors() |
Cursor / selection / handle colors / 光标、选区、把手颜色 |
placeholder |
String |
"" |
Shown when text is empty / 文字为空时显示 |
maxColumns |
Int |
Int.MAX_VALUE |
Max visible columns; overflow scrolls horizontally / 最多显示几列,超出横向滚动 |
enabled |
Boolean |
true |
If false, no interaction / 为 false 时不可交互 |
readOnly |
Boolean |
false |
Selectable but not editable / 可选取但不可编辑 |
selectable |
Boolean |
true |
Whether text can be selected / 是否允许选取 |
onShowContextMenu |
((Offset, Boolean) -> Unit)? |
null |
Context menu trigger; Boolean = hasSelection / 菜单触发回调,Boolean 表示是否有选区 |
ascentTrim |
Float |
0.15f |
Trim ascent to reduce inter-column spacing / 削减 Ascent 调整列间距 |
baselineShift |
Float |
0f |
Baseline shift / 基线偏移 |
fixedWidth |
Boolean |
true |
All columns same width (max line height) / 等宽列 |
VTextFieldDefaults.colors(
cursor: Color = MaterialTheme.colorScheme.primary,
selectionHandle: Color = MaterialTheme.colorScheme.primary,
selectionBackground: Color = LocalTextSelectionColors.current.backgroundColor,
)Correct rendering of Mongolian and Manchu script requires a font with proper OpenType vertical features (vert, vrt2).
蒙古文/满文的正确渲染依赖支持 OpenType 竖排特性(vert、vrt2)的字体。
| Platform | Built-in Mongolian font | Quality |
|---|---|---|
| Android | ❌ None | Renders as boxes without a custom font |
| iOS 12+ | ✅ Mongolian Baiti | Partial — Manchu / Sibe incomplete |
| macOS | ✅ Mongolian Baiti | Partial |
| Windows | ✅ Mongolian Baiti | Partial |
Android requires a bundled font. Other platforms are recommended to bundle one for best results.
Android 必须自带字体,其他平台建议自带以获得最佳效果。
Noto Sans Mongolian — Apache 2.0 license, free for commercial use, covers Mongolian, Manchu, Sibe, Todo, and more.
Noto Sans Mongolian — Apache 2.0 协议,免费商用,覆盖蒙古文、满文、锡伯文、托忒文等。
Download NotoSansMongolian-Regular.ttf and place it in:
composeApp/src/commonMain/composeResources/font/NotoSansMongolian_Regular.ttf
Use it:
// In a @Composable
val mongolianFont = FontFamily(Font(Res.font.NotoSansMongolian_Regular))
VTextField(
value = value,
onValueChange = { value = it },
verticalFontFamily = mongolianFont,
)
verticalFontFamilyis applied only to Mongolian / Manchu code point ranges. All other characters (CJK, Latin, Emoji, etc.) use the font fromstyle.
verticalFontFamily仅应用于蒙古文/满文码点区间,其他字符(汉字、拉丁文、Emoji 等)使用style中的字体。
Mongolian and Manchu script is stored "lying on its side" in Unicode — characters are encoded for horizontal rendering but displayed vertically. MMTextKit uses Compose's TextMeasurer to measure text horizontally, then rotates each "line" 90° to form a vertical column.
蒙古文/满文在 Unicode 中是"侧卧"存储的——字符按横排编码,但需要竖排显示。MMTextKit 使用 Compose 的 TextMeasurer 横向测量文字,再将每"行"旋转 90° 作为竖排的一"列"。
Upright characters (CJK, Kana, Hangul, Emoji): rotated back −90° after the column rotation, so they appear upright.
Mongolian / Manchu characters: follow the column rotation, preserving their natural vertical flow and ligatures.
直立字符(汉字、假名、韩文、Emoji):在列旋转后再反向旋转 −90°,保持直立。
蒙古文/满文字符:随列旋转,保持自然的竖排连写形态。
MIT © 2026 lzdev42