hello again, has anyone figured out how to load fo...
# compose-web
j
hello again, has anyone figured out how to load fonts in compose/canvas?
a
Yes using webpack and loading fonts via require statements
I’m on my phone but there is a way
d
I made sure my font file was hosted somewhere. Then, I used the font-face CSS rule to name my font and set it to the target URL (https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face) Then, I created a CSS style which references the name of my font with a font family tag (https://www.w3schools.com/cssref/pr_font_font-family.asp). I'd paste code here but I'm using a custom framework so it wouldn't help too much. But you can use stylesheets (https://github.com/JetBrains/compose-jb/blob/master/tutorials/Web/Style_Dsl/README.md#stylesheet)
j
@David Herman correct me if i’m wrong, but this looks like it’s for the compose/dom approach not the compose/canvas approach
@agrosner any update on this? i found my own workaround, but i’m interested if there’s a more canonical way. thank you kindly
d
@jeran sorry yes, for the DOM.
a
sure ill send some code snippets
not sure if its the officially recommended way
caches the fonts using
require
.
Copy code
data class FontResourceImpl(private val font: AsyncFont, private val fileName: String) : FontResource {
    override val realFont: Font
        @Composable
        get() = fontOrHelp(fileName, font)
}
Copy code
interface FontResource {
    @get:Composable
    val realFont: Font
}

expect fun findFontResource(font: AsyncFont): FontResource?

data class AsyncFont(
    val identity: String,
    override val style: FontStyle,
    override val weight: FontWeight,
    val fontName: String
) : Font {
    override val loadingStrategy: FontLoadingStrategy = FontLoadingStrategy.Async
}
Copy code
@Composable
fun FontStyleLocal.resolveFontFamily(): FontFamily {
    val fonts = **.fonts
    val font = when (this) {
        FontStyleLocal.BaseBold -> fonts.bold
        FontStyleLocal.BaseItalics -> fonts.italics
        FontStyleLocal.BaseRegular -> fonts.regular
        FontStyleLocal.Primary -> fonts.primary
    }
    val found = findFontResource(font)?.realFont ?: Font(
        identity = "",
        data = ByteArray(0),
    )
    return FontFamily(
        found
    )
}
composable to load font, we have our own design system so this just maps a token to the font and resource (probably dont need first portion of function)
add in
webpack.config.d
next to source a file I call it `01-resources.js`:
Copy code
config.module.rules.push({
    test: /\.(woff|woff2|eot|ttf|otf)$/i,
    loader: "file-loader"
})
this will instruct webpack to include font resource files and enable loading them from js / kt code using require
using
Copy code
implementation(npm("copy-webpack-plugin", "9.0.0"))
                implementation(npm("file-loader", "6.2.0"))
and fonts are included in
@jeran
j
wow. thanks @agrosner! i’ve got another solution using window.fetch that i can post later. but gonna play around with this a bit first
a
np! cool
j
@agrosner - this was helpful, thanks! @jeran - did you ever get the
window.fetch
solution working? trying to decide best path forward so I was curious if there are alternate options to implement this
j
ah forgot about this! i actually didn’t get around to testing out andrew’s solution, so I didn’t post cause i wanted to do a comparison first. the code i have works fine and looks something like this:
Copy code
window.fetch(
    Request(url),
).then { response ->
    response.arrayBuffer()
}.then { arrayBuffer ->
    val byteLength = arrayBuffer.byteLength
    val byteArray = ByteArray(byteLength)
    val uInt8Array = Uint8Array(arrayBuffer)
    for (i in 0 until byteLength) {
        byteArray[i] = uInt8Array[i]
    }
    return@then androidx.compose.ui.text.platform.Font(
        identity = url,
        data = byteArray,
        weight = weight,
        style = style,
    )
}.await()
where url is a url to a ttf, weight and style are corresponding
FontWeight
and
FontStyle
and you can use it something like this:
Copy code
@Composable
fun rememberFont(
    url: String,
    weight: FontWeight,
    style: FontStyle,
): Font? {
    var font by remember { mutableStateOf<Font?>(null) }
    LaunchedEffect(path, weight, style) {
        font = loadFont(url, weight, style)
    }
    return font
}
j
thank you for providing this information! I will try this out
j
@agrosner @Jeff T have either of you seen an issue with performance and custom fonts? i’ve been hunting down a performance issue in our little compose/canvas app, and from what i can tell rendering BasicText() composables can be slow if you use a custom font. it isn’t related to downloading the fonts, because that only happens once for us and we do that ahead of time anyway. and it also seems to be based on the number of composables. e.g. filling up the whole screen with lorem ipsum is fine if its a single text composable, but rendering 50 text composables with less text takes longer. also seems to be somewhat font dependent, seems like the Inter font takes longer than Aleo for example.
i wonder if it has to do with the size of the font 🤔. but the bytearray should already be in memory at this point…
https://github.com/JetBrains/androidx/pull/336 ok so, i actually think this was fixed 5 days ago lol. well, just a heads up then that this change is coming
j
thanks for this info! i still haven't incorporated my custom font yet, but I plan to do so in the coming days. I'll keep an eye out for this fix.
325 Views