Repository navigation
Fix cursor metrics in SkikoParagraph - #3405
Vladimir Mazunin (mazunin-v-jb) merged 9 commits into
Conversation
|
test
Now I see that this isn’t a bug: when the user chooses a font size > lineH, overlap is expected. Will unroll second fix. |
Ivan Matkov (MatkovIvan)
left a comment
There was a problem hiding this comment.
Please resolve merge conflicts (rebase)
| private val emptyLineCursorMetrics: LineMetrics | ||
| get() = _emptyLineCursorMetrics ?: layouter | ||
| .emptyLineMetrics(paragraph) | ||
| .first() | ||
| .trimFirstAscent(defaultFont.metrics, layouter.textStyle) | ||
| .trimLastDescent(defaultFont.metrics, layouter.textStyle) | ||
| .also { _emptyLineCursorMetrics = it } |
There was a problem hiding this comment.
Why cannot we reuse receiveLineMetrics() here and need a separate resolve? Shouldn't we fix that variant instead (if it's wrong for some reason) and keep a single source of truth?
There was a problem hiding this comment.
receiveLineMetrics() chooses its source based on whether the whole text is empty,
for an empty paragraph it returns the same empty-line metrics, but for non-empty text it returns paragraph.lineMetrics, that's where the difference in cursor height comes from.
The cursor intentionally uses empty-line metrics so its height does not change after a new char typed on the empty/newline line
I can extract the empty-line metrics construction and trimming into a helper used by both receiveLineMetrics() and the cursor.
receiveLineMetrics() shouldn't return empty-line metrics for non-empty text, since paragraph.lineMetrics are also used for line geometry, while the empty-line metrics are only needed for the cursor.
There was a problem hiding this comment.
+1. receiveLineMetrics() is fine; the issue is emptyLineMetrics using fontSize instead of the font's intrinsic height. Fixing that removes the need for a second source (see my other comment).
There was a problem hiding this comment.
🟡 Changes recommended
Cursor sizing discards styled-font metrics, and the claimed baseline-layout fix is absent.
2 open issues
What changed in this PR
Adjusts Skiko cursor sizing to reduce height changes when typing into empty text fields.
Changes:
- Reuses cached empty-line metrics for cursors with explicit line heights.
- Adds an empty-versus-nonempty cursor-height regression test.
- Adds a
StrutStyleimport without changing paragraph layout.
| File | Description |
|---|---|
| compose/ui/ui-skiko/src/nonAndroidTest/kotlin/androidx/compose/ui/text/SkikoParagraphTest.kt | Tests consistent cursor height. |
| compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/SkikoParagraph.nonAndroid.kt | Changes cursor metric selection and caching. |
| compose/ui/ui-skiko/src/nonAndroidMain/kotlin/androidx/compose/ui/text/platform/SkikoParagraphBuilder.nonAndroid.kt | Adds an unused import. |
Review effort: Balanced
| (text.isNotEmpty() && !layouter.textStyle.lineHeight.isUnspecified) | ||
| val cursorMetrics = if (usesEmptyLineCursorMetrics && text.isNotEmpty()) { | ||
| emptyLineCursorMetrics |
Vladimir Mazunin (mazunin-v-jb)
left a comment
There was a problem hiding this comment.
Thanks for digging into this!
However, I think the fix lands in the wrong place, though: the caret is fine, but the empty-line metrics it is compared against are wrong.
emptyLineMetrics uses fontSize where Skia uses the font's intrinsic height (see the inline comments).
Could you please try aligning emptyLineMetrics with Skia's computeEmptyMetrics and reverting the getCursorRect changes?
That should fix the empty-field caret and keep the non-empty ones matching the line height.
| private val emptyLineCursorMetrics: LineMetrics | ||
| get() = _emptyLineCursorMetrics ?: layouter | ||
| .emptyLineMetrics(paragraph) | ||
| .first() | ||
| .trimFirstAscent(defaultFont.metrics, layouter.textStyle) | ||
| .trimLastDescent(defaultFont.metrics, layouter.textStyle) | ||
| .also { _emptyLineCursorMetrics = it } |
There was a problem hiding this comment.
+1. receiveLineMetrics() is fine; the issue is emptyLineMetrics using fontSize instead of the font's intrinsic height. Fixing that removes the need for a second source (see my other comment).
Vladimir Mazunin (mazunin-v-jb)
left a comment
There was a problem hiding this comment.
lgtm now, thank you!
384387b
into
JetBrains:jb-main


Fixes CMP-8371 Text cursor shrinks after typing begins when a TextField
There are 2 problems fixed.
Screen.Recording.2026-09-09.at.22.36.40.mov
Screen.Recording.2026-09-09.at.22.38.07.mov
(fix by SkikoParagraphBuilder.nonAndroid.kt file)
To reproduce:
Testing
Screen.Recording.2026-09-09.at.22.48.18.mov
Release Notes
Fixes - Multiple Platforms