Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new experimental web-specific API to preload fonts and images resources #5159

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
4 changes: 2 additions & 2 deletions components/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ org.gradle.configuration-cache=true
android.useAndroidX=true

#Versions
kotlin.version=1.9.23
kotlin.version=1.9.24
agp.version=8.2.2
compose.version=1.6.10
compose.version=1.7.0
deploy.version=0.1.0-SNAPSHOT

#Compose
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,15 +1,54 @@
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFontFamilyResolver
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.platform.Font
import androidx.compose.ui.window.CanvasBasedWindow
import components.resources.demo.shared.generated.resources.NotoColorEmoji
import components.resources.demo.shared.generated.resources.Res
import components.resources.demo.shared.generated.resources.Workbench_Regular
import components.resources.demo.shared.generated.resources.font_awesome
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.configureWebResources
import org.jetbrains.compose.resources.demo.shared.UseResources
import org.jetbrains.compose.resources.preloadAndCacheFont

@OptIn(ExperimentalComposeUiApi::class)
@OptIn(ExperimentalComposeUiApi::class, ExperimentalResourceApi::class)
fun main() {
configureWebResources {
// Not necessary - It's the same as the default. We add it here just to present this feature.
resourcePathMapping { path -> "./$path" }
}
CanvasBasedWindow("Resources demo + K/Wasm") {
UseResources()
val font1 by preloadAndCacheFont(Res.font.Workbench_Regular)
val font2 by preloadAndCacheFont(Res.font.font_awesome, FontWeight.Normal, FontStyle.Normal)
val emojiFont by preloadAndCacheFont(Res.font.NotoColorEmoji)
var fontsFallbackInitialiazed by remember { mutableStateOf(false) }

if (font1 != null && font2 != null && emojiFont != null && fontsFallbackInitialiazed) {
println("Fonts are ready")
UseResources()
} else {
Box(modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
println("Fonts are not ready yet")
}

val fontFamilyResolver = LocalFontFamilyResolver.current
LaunchedEffect(fontFamilyResolver, emojiFont) {
if (emojiFont != null) {
// we have an emoji on Strings tab
fontFamilyResolver.preload(FontFamily(listOf(emojiFont!!)))
fontsFallbackInitialiazed = true
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ fun painterResource(resource: DrawableResource): Painter {

private val emptyImageBitmap: ImageBitmap by lazy { ImageBitmap(1, 1) }

internal val ImageBitmap.isEmptyImageBitmapPlaceholder: Boolean
eymar marked this conversation as resolved.
Show resolved Hide resolved
get() = this == emptyImageBitmap

/**
* Retrieves an ImageBitmap using the specified drawable resource.
*
Expand Down Expand Up @@ -77,6 +80,9 @@ private val emptyImageVector: ImageVector by lazy {
ImageVector.Builder("emptyImageVector", 1.dp, 1.dp, 1f, 1f).build()
}

internal val ImageVector.isEmptyImageVectorPlaceholder: Boolean
eymar marked this conversation as resolved.
Show resolved Hide resolved
get() = this == emptyImageVector

/**
* Retrieves an ImageVector using the specified drawable resource.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ private val emptyFontBase64 =
private val defaultEmptyFont by lazy { Font("org.jetbrains.compose.emptyFont", Base64.decode(emptyFontBase64)) }

private val fontCache = AsyncCache<String, Font>()
internal val Font.isDefaultEmptyFont: Boolean
eymar marked this conversation as resolved.
Show resolved Hide resolved
get() = this == defaultEmptyFont

@Composable
actual fun Font(resource: FontResource, weight: FontWeight, style: FontStyle): Font {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package org.jetbrains.compose.resources

import androidx.compose.runtime.*
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight

/**
* Represents the configuration object for web resources.
*
Expand Down Expand Up @@ -50,4 +57,39 @@ internal fun getResourceUrl(windowOrigin: String, windowPathname: String, resour
path.startsWith("http://") || path.startsWith("https://") -> path
else -> windowOrigin + windowPathname + path
}
}

@ExperimentalResourceApi
@Composable
fun preloadAndCacheFont(
terrakok marked this conversation as resolved.
Show resolved Hide resolved
resource: FontResource,
weight: FontWeight = FontWeight.Normal,
style: FontStyle = FontStyle.Normal
): State<Font?> {
val resState = remember(resource, weight, style) { mutableStateOf<Font?>(null) }.apply {
value = Font(resource, weight, style).takeIf { !it.isDefaultEmptyFont }
}
return resState
}

@ExperimentalResourceApi
@Composable
fun preloadAndCacheImageResource(
resource: DrawableResource,
): State<ImageBitmap?> {
val resState = remember(resource) { mutableStateOf<ImageBitmap?>(null) }.apply {
value = imageResource(resource).takeIf { !it.isEmptyImageBitmapPlaceholder }
}
return resState
}

@ExperimentalResourceApi
@Composable
fun preloadAndCacheVectorResource(
resource: DrawableResource,
): State<ImageVector?> {
val resState = remember(resource) { mutableStateOf<ImageVector?>(null) }.apply {
value = vectorResource(resource).takeIf { !it.isEmptyImageVectorPlaceholder }
}
return resState
}