Summary
compose-rich-editor is incompatible with Compose Multiplatform 1.10.3 when the input HTML contains a text-indent declaration whose value uses em, rem, or %. Loading such HTML into a RichTextState and rendering it via RichText / RichTextEditor crashes the layout pass with:
java.lang.IllegalStateException: Only Sp can convert to Px
at androidx.compose.ui.unit.Density.toPx--R2X_6o(Density.kt:114)
at androidx.compose.ui.text.platform.ParagraphBuilder.textStyleToParagraphStyle(ParagraphBuilder.skiko.kt:551)
After this throws once, the text node's layout cache stays null, so every subsequent draw of the same node also crashes:
java.lang.IllegalStateException: Internal Error: MultiParagraphLayoutCache could not provide TextLayoutResult during the draw phase.
Minimal reproduction
Compose Multiplatform Desktop, compose-plugin 1.10.3, com.mohamedrejeb.richeditor:richeditor-compose:1.0.0-rc14:
@Composable
fun App() {
val state = rememberRichTextState()
LaunchedEffect(Unit) {
// Any of these three triggers the crash:
state.setHtml("""<p style="text-indent: 2em">Hello</p>""")
// state.setHtml("""<p style="text-indent: 1.5rem">Hello</p>""")
// state.setHtml("""<p style="text-indent: 50%">Hello</p>""")
}
RichText(state = state, modifier = Modifier.fillMaxSize())
}
Real-world trigger: HTML pasted from Microsoft Word, Google Docs, or many web pages — these commonly emit text-indent: <n>em or text-indent: <n>% on <p> / <div> style attributes.
Why it crashes
parser/html/CssEncoder.kt, parseCssTextSize:
return when (unit) {
"px" -> value.sp
"pt" -> (value * 1.333f).sp
"em" -> value.em // ← Em
"rem" -> value.em // ← Em
"%" -> (value / 100f).em // ← Em
else -> TextUnit.Unspecified
}
parseCssTextIndent then feeds that result into TextIndent(textUnit, textUnit):
val textUnit = parseCssTextSize(cssTextIndent)
return if (textUnit.isSpecified) TextIndent(textUnit, textUnit) else null
So a TextIndent(Em, Em) ends up on the ParagraphStyle. Inside Compose Multiplatform 1.10.3, Skia's ParagraphBuilder.textStyleToParagraphStyle converts it via the raw Density.toPx(TextUnit):
// compose-ui-text-desktop:ParagraphBuilder.skiko.kt
textStyle.textIndent?.run {
with(density) {
pStyle.textIndent = SkTextIndent(firstLine.toPx(), restLine.toPx())
}
}
// compose-ui-unit:Density.kt
fun TextUnit.toPx(): Float {
checkPrecondition(type == TextUnitType.Sp) { \"Only Sp can convert to Px\" }
return toDp().toPx()
}
Density.toPx(TextUnit) only accepts Sp, so any Em value reaches a hard crash. Other text properties (e.g. lineHeight) are not affected because they go through a helper that resolves Em against the current fontSize before converting; only textIndent uses the raw Density.toPx(TextUnit).
Suggested fix
Two options:
-
Resolve em / rem / % to sp at parse time for text-indent, using a fixed default base font size (e.g. 16f). The result is stable and never crashes, at the cost of text-indent no longer scaling with the caller's actual fontSize — a fair trade given that CMP's TextIndent is absolute Sp anyway.
-
Drop unsupported units by returning null from parseCssTextIndent when the parsed unit is not Sp-convertible. The visual indent is silently lost but the editor stays alive.
Workaround
For anyone hitting this before the upstream fix lands, sanitize the HTML before calling setHtml — strip every text-indent declaration from inline styles. Match the property name exactly (e.g. via splitting on ; and comparing the lowercased property) so vendor-prefixed names like mso-text-indent from Word-pasted HTML are left untouched.
Versions
compose-rich-editor 1.0.0-rc14 (also reproduces on rc13)
- Compose Multiplatform
1.10.3 (Skia / JVM Desktop)
- macOS — failing code path is in
commonMain / skikoMain, so all Skia-backed targets are affected
Summary
compose-rich-editoris incompatible with Compose Multiplatform 1.10.3 when the input HTML contains atext-indentdeclaration whose value usesem,rem, or%. Loading such HTML into aRichTextStateand rendering it viaRichText/RichTextEditorcrashes the layout pass with:After this throws once, the text node's layout cache stays
null, so every subsequent draw of the same node also crashes:Minimal reproduction
Compose Multiplatform Desktop,
compose-plugin 1.10.3,com.mohamedrejeb.richeditor:richeditor-compose:1.0.0-rc14:Real-world trigger: HTML pasted from Microsoft Word, Google Docs, or many web pages — these commonly emit
text-indent: <n>emortext-indent: <n>%on<p>/<div>style attributes.Why it crashes
parser/html/CssEncoder.kt,parseCssTextSize:parseCssTextIndentthen feeds that result intoTextIndent(textUnit, textUnit):So a
TextIndent(Em, Em)ends up on theParagraphStyle. Inside Compose Multiplatform 1.10.3, Skia'sParagraphBuilder.textStyleToParagraphStyleconverts it via the rawDensity.toPx(TextUnit):Density.toPx(TextUnit)only acceptsSp, so anyEmvalue reaches a hard crash. Other text properties (e.g.lineHeight) are not affected because they go through a helper that resolvesEmagainst the currentfontSizebefore converting; onlytextIndentuses the rawDensity.toPx(TextUnit).Suggested fix
Two options:
Resolve
em/rem/%tospat parse time fortext-indent, using a fixed default base font size (e.g. 16f). The result is stable and never crashes, at the cost oftext-indentno longer scaling with the caller's actualfontSize— a fair trade given that CMP'sTextIndentis absolute Sp anyway.Drop unsupported units by returning
nullfromparseCssTextIndentwhen the parsed unit is not Sp-convertible. The visual indent is silently lost but the editor stays alive.Workaround
For anyone hitting this before the upstream fix lands, sanitize the HTML before calling
setHtml— strip everytext-indentdeclaration from inline styles. Match the property name exactly (e.g. via splitting on;and comparing the lowercased property) so vendor-prefixed names likemso-text-indentfrom Word-pasted HTML are left untouched.Versions
compose-rich-editor1.0.0-rc14 (also reproduces on rc13)1.10.3(Skia / JVM Desktop)commonMain/skikoMain, so all Skia-backed targets are affected