JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.24k stars 1.18k forks source link

Can't use TextMeasurer in application scope (LocalFontFamilyResolver / LocalLayoutDirection not present) #3289

Closed kirill-grouchnikov closed 1 month ago

kirill-grouchnikov commented 1 year ago

Desktop version 1.4.0 on macOS

Run this:

@OptIn(ExperimentalTextApi::class)
fun main() = application {
    val textMeasurer = rememberTextMeasurer()
    Window(onCloseRequest = ::exitApplication) {
    }
}

And it crashes with

Exception in thread "main" java.lang.IllegalStateException: CompositionLocal LocalFontFamilyResolver not present
    at androidx.compose.ui.platform.CompositionLocalsKt.noLocalProvidedFor(CompositionLocals.kt:219)
    at androidx.compose.ui.platform.CompositionLocalsKt.access$noLocalProvidedFor(CompositionLocals.kt:1)
    at androidx.compose.ui.platform.CompositionLocalsKt$LocalFontFamilyResolver$1.invoke(CompositionLocals.kt:110)
    at androidx.compose.ui.platform.CompositionLocalsKt$LocalFontFamilyResolver$1.invoke(CompositionLocals.kt:109)
    at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
    at androidx.compose.runtime.LazyValueHolder.getCurrent(ValueHolders.kt:29)
    at androidx.compose.runtime.LazyValueHolder.getValue(ValueHolders.kt:31)
    at androidx.compose.runtime.ComposerImpl.resolveCompositionLocal(Composer.kt:2070)
    at androidx.compose.runtime.ComposerImpl.consume(Composer.kt:2038)
    at androidx.compose.ui.text.TextMeasurerHelperKt.rememberTextMeasurer(TextMeasurerHelper.kt:53)
    at org.pushingpixels.aurora.demo.ComposableSingletons$MainKt$lambda-2$1.invoke(main.kt:10)
    at org.pushingpixels.aurora.demo.ComposableSingletons$MainKt$lambda-2$1.invoke(main.kt:9)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at androidx.compose.ui.window.Application_desktopKt$application$1$1.invoke(Application.desktop.kt:115)
    at androidx.compose.ui.window.Application_desktopKt$application$1$1.invoke(Application.desktop.kt:114)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:116)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1$1.invoke(Application.desktop.kt:226)
    at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1$1.invoke(Application.desktop.kt:225)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
    at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1.invoke(Application.desktop.kt:221)
    at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2$1.invoke(Application.desktop.kt:219)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
    at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
    at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:37)
    at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3353)
    at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3343)
    at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:341)
    at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source)
    at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3343)
    at androidx.compose.runtime.ComposerImpl.composeContent$runtime(Composer.kt:3278)
    at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:587)
    at androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:966)
    at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:519)
    at androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2.invokeSuspend(Application.desktop.kt:219)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:313)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Why is LocalFontFamilyResolver.current crashing in the application scope? And the same for LocalLayoutDirection.current

m-sasha commented 1 year ago

Indeed we're currently providing all the common composition locals only inside the window scope.

m-sasha commented 1 year ago

I'm not sure whether this should be considered a bug or a feature request.

kirill-grouchnikov commented 1 year ago

Another example where this is needed outside of the window scope.

The recently added functions in TextPainter to draw text in a multiplatform way require either TextMeasurer or TextLayoutResult. The later needs FontFamily.Resolver to be created. So in both cases, in order to use DrawScope.drawText we need these common composition locals to be available.

A common example of using DrawScope.drawText outside of the window scope would be to dynamically create things like icons and overlay them with text elements (or dynamically generate icons or images with text elements) for subsequent use as icons on buttons or somewhere else.

okushnikov commented 3 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.