Skip to content

Comments

Fix possible text styles cache corruption#2629

Merged
MatkovIvan merged 4 commits intojb-mainfrom
ivan.matkov/immutable-cache-key
Dec 9, 2025
Merged

Fix possible text styles cache corruption#2629
MatkovIvan merged 4 commits intojb-mainfrom
ivan.matkov/immutable-cache-key

Conversation

@MatkovIvan
Copy link
Member

@MatkovIvan MatkovIvan commented Dec 8, 2025

Fixes CMP-8028 Text color is sometimes randomly black on iOS when using Compose Multiplatform (BasicText)

The main change here is to make sure that keys in skTextStylesCache are immutable.
However, during the paragraph building, we need this class to be mutable to avoid unnecessary allocations. While it's possible to fix the issue with just a few lines (#2630), it seems really dangerous long term, so this PR replaces ComputedStyle to sealed intefrace with both variants to ensure immutability at compile time instead.

In addition to that, it contains a small cleanup of code around:

  • Add @InternalTextApi to functions with TODO: Remove from public, so we can actually remove them later
  • Optimize cleaning up styles cache: remove in-place during iteration to avoid allocation of extra list and additional look-ups

Testing

  • Existing tests on CI
  • Reproducer from CMP-8028
val textMeasurer = rememberTextMeasurer(cacheSize = 100)
Canvas(Modifier.background(Color.DarkGray)) {
    for (i in 0 until 100) {
        drawText(
            textLayoutResult = textMeasurer.measure(text = "$i"),
            color = Color.White,
            topLeft = Offset(x = i % 10 * 50f, y = i / 10 * 50f)
        )
    }
}

Additional auto tests are problematic because the issue reproduces only on K/N (both macOS and iOS) where we didn't have infrastructure for fonts-related testing yet.

Release Notes

Fixes - iOS

  • Fix possible text styles cache corruption (text color is sometimes randomly black)

override val topRatio: Float = -1f,
) : ComputedStyle {
private val _foregroundPaint = SkiaTextPaint()
fun getForegroundPaint(): SkPaint {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewers: this diff is easier to read in ignoring whitespace mode

@MatkovIvan MatkovIvan merged commit 9d6744f into jb-main Dec 9, 2025
17 checks passed
@MatkovIvan MatkovIvan deleted the ivan.matkov/immutable-cache-key branch December 9, 2025 11:14
MatkovIvan added a commit that referenced this pull request Dec 9, 2025
…ed between calls (#2634)

Follow up after debugging
#2629/[CMP-8028](https://youtrack.jetbrains.com/issue/CMP-8028)
This fix reduces the number of skia paragraph layout calls from 3 to 2
for each label in the next example:
```kt
val textMeasurer = rememberTextMeasurer(cacheSize = 100)
Canvas(Modifier.fillMaxSize().background(Color.DarkGray)) {
    for (i in 0 until 100) {
        drawText(
            textMeasurer = textMeasurer,
            text = "$i",
            topLeft = Offset(x = i % 10 * 50f, y = i / 10 * 50f),
            style = TextStyle.Default.copy(color = Color.White)
        )
    }
}
```

Unfortunately, a unit test for this behavior is not trivial because we
don't have infra to mock final classes even on JVM now

## Release Notes
N/A
siarb added a commit that referenced this pull request Jan 13, 2026
Fixes [CMP-8028](https://youtrack.jetbrains.com/issue/CMP-8028) Text
color is sometimes randomly black on iOS when using Compose
Multiplatform (BasicText)

It's a minimal, simplified version of #2629

## Testing
- Existing tests on CI
- Reproducer from
[CMP-8028](https://youtrack.jetbrains.com/issue/CMP-8028)
```kt
val textMeasurer = rememberTextMeasurer(cacheSize = 100)
Canvas(Modifier.background(Color.DarkGray)) {
    for (i in 0 until 100) {
        drawText(
            textLayoutResult = textMeasurer.measure(text = "$i"),
            color = Color.White,
            topLeft = Offset(x = i % 10 * 50f, y = i / 10 * 50f)
        )
    }
}
```

## Release Notes
### Fixes - iOS
- Fix possible text styles cache corruption (text color is sometimes
randomly black)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants