Skip to content

Commit

Permalink
Add a way to use fonts installed on the system (#898)
Browse files Browse the repository at this point in the history
## Proposed Changes

- Add `SystemFont` class to be able to use fonts installed on the
system.

## API Change

```diff
+androidx.compose.ui.text.platform.SystemFont
```

## Testing

Usage looks like this:
```kt
FontFamily(SystemFont("Menlo")),
FontFamily(SystemFont("Times New Roman", FontWeight.Bold)),
FontFamily(SystemFont("Webdings")),
```
<img width="1003" alt="Screenshot 2023-11-09 at 12 18 02"
src="https://github.com/JetBrains/compose-multiplatform-core/assets/1836384/0c36d4aa-6bc9-47e2-94dd-edb09fd242ac">
  • Loading branch information
MatkovIvan authored and mazunin-v-jb committed Dec 7, 2023
1 parent 1851a69 commit e3655ac
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 24 deletions.
10 changes: 10 additions & 0 deletions compose/ui/ui-text/api/desktop/ui-text.api
Original file line number Diff line number Diff line change
Expand Up @@ -1384,6 +1384,16 @@ public final class androidx/compose/ui/text/platform/SkiaParagraph_skikoKt {
public static final fun toSkPlaceholderAlignment-do9X-Gg (I)Lorg/jetbrains/skia/paragraph/PlaceholderAlignment;
}

public final class androidx/compose/ui/text/platform/SystemFont : androidx/compose/ui/text/platform/PlatformFont {
public static final field $stable I
public synthetic fun <init> (Ljava/lang/String;Landroidx/compose/ui/text/font/FontWeight;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;Landroidx/compose/ui/text/font/FontWeight;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun getIdentity ()Ljava/lang/String;
public fun getStyle-_-LCdwA ()I
public fun getWeight ()Landroidx/compose/ui/text/font/FontWeight;
public fun toString ()Ljava/lang/String;
}

public final class androidx/compose/ui/text/style/BaselineShift {
public static final field Companion Landroidx/compose/ui/text/style/BaselineShift$Companion;
public static final synthetic fun box-impl (F)Landroidx/compose/ui/text/style/BaselineShift;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
package androidx.compose.ui.text.platform

import org.jetbrains.skia.Typeface as SkTypeface
import org.jetbrains.skia.FontStyle as SkFontStyle
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.font.*
import androidx.compose.ui.util.fastForEach
import java.io.File
import java.security.MessageDigest
import org.jetbrains.skia.Data
import org.jetbrains.skia.FontSlant
import org.jetbrains.skia.FontWidth
import org.jetbrains.skia.makeFromFile

actual sealed class PlatformFont : Font {
Expand Down Expand Up @@ -163,6 +164,7 @@ internal actual fun loadTypeface(font: Font): SkTypeface {
is ResourceFont -> typefaceResource(font.name)
is FileFont -> SkTypeface.makeFromFile(font.file.toString())
is LoadedFont -> SkTypeface.makeFromData(Data.makeFromBytes(font.data))
is SystemFont -> SkTypeface.makeFromName(font.identity, font.skFontStyle)
}
}

Expand All @@ -176,6 +178,12 @@ private fun typefaceResource(resourceName: String): SkTypeface {
return SkTypeface.makeFromData(Data.makeFromBytes(bytes))
}

private val Font.skFontStyle: SkFontStyle get() = SkFontStyle(
weight = weight.weight,
width = FontWidth.NORMAL,
slant = if (style == FontStyle.Italic) FontSlant.ITALIC else FontSlant.UPRIGHT
)

internal actual fun currentPlatform(): Platform {
val name = System.getProperty("os.name")
return when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.SkiaFontLoader
import androidx.compose.ui.text.platform.Font
import androidx.compose.ui.text.platform.GenericFontFamiliesMapping
import androidx.compose.ui.text.platform.Typeface
import androidx.compose.ui.text.platform.aliases
import com.google.common.truth.Truth
import org.jetbrains.skia.Data
import org.jetbrains.skia.Typeface
Expand Down Expand Up @@ -66,10 +66,10 @@ class DesktopFontTest {
@Test
fun ensureRegistered() {
Truth.assertThat(fontLoader.loadPlatformTypes(FontFamily.Cursive).aliases)
.isEqualTo(GenericFontFamiliesMapping[FontFamily.Cursive.name])
.isEqualTo(FontFamily.Cursive.aliases)

Truth.assertThat(fontLoader.loadPlatformTypes(FontFamily.Default).aliases)
.isEqualTo(GenericFontFamiliesMapping[FontFamily.SansSerif.name])
.isEqualTo(FontFamily.SansSerif.aliases)

Truth.assertThat(fontLoader.loadPlatformTypes(loadedFontFamily).aliases)
.isEqualTo(listOf("Sample Font"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,17 @@ import org.jetbrains.skia.Typeface as SkTypeface
import androidx.compose.ui.text.Cache
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.text.ExpireAfterAccessCache
import androidx.compose.ui.text.WeakKeysCache
import androidx.compose.ui.text.font.*
import androidx.compose.ui.text.font.DefaultFontFamily
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontListFontFamily
import androidx.compose.ui.text.font.FontLoadingStrategy
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.font.GenericFontFamily
import androidx.compose.ui.text.font.LoadedFontFamily
import androidx.compose.ui.text.font.Typeface
import androidx.compose.ui.text.font.createFontFamilyResolver
import org.jetbrains.skia.FontMgr
import org.jetbrains.skia.paragraph.FontCollection
import org.jetbrains.skia.paragraph.TypefaceFontProvider
Expand All @@ -31,6 +39,20 @@ expect sealed class PlatformFont() : Font {
internal val cacheKey: String
}

/**
* A Font that already installed in system.
*/
@ExperimentalTextApi
class SystemFont(
override val identity: String,
override val weight: FontWeight = FontWeight.Normal,
override val style: FontStyle = FontStyle.Normal
) : PlatformFont() {
override fun toString(): String {
return "SystemInstalledFont(identity='$identity', weight=$weight, style=$style)"
}
}

/**
* Defines a Font using byte array with loaded font data.
*
Expand Down Expand Up @@ -91,10 +113,11 @@ fun Font(
style: FontStyle = FontStyle.Normal
): Font = LoadedFont(identity, data, weight, style)

internal class SkiaBackedTypeface(
val alias: String?,
private class SkiaBackedTypeface(
alias: String?,
val nativeTypeface: SkTypeface
) : Typeface {
val alias = alias ?: nativeTypeface.familyName
override val fontFamily: FontFamily? = null
}

Expand Down Expand Up @@ -150,11 +173,6 @@ internal class FontCache {
fonts.setAssetFontManager(fontProvider)
}

private fun mapGenericFontFamily(generic: GenericFontFamily): List<String> {
return GenericFontFamiliesMapping[generic.name]
?: error("Unknown generic font family ${generic.name}")
}

internal fun load(font: PlatformFont): FontLoadResult {
val typeface = typefacesCache.get(font.cacheKey) {
loadTypeface(font)
Expand Down Expand Up @@ -183,19 +201,23 @@ internal class FontCache {
private fun ensureRegistered(fontFamily: FontFamily): List<String> =
when (fontFamily) {
is FontListFontFamily -> {
// not supported
throw IllegalArgumentException(
"Don't load FontListFontFamily through ensureRegistered: $fontFamily"
)
val fonts = fontFamily.fonts.filterIsInstance<SystemFont>()
if (fonts.size == fontFamily.fonts.size) {
fonts.map { it.identity }
} else {
// not supported
throw IllegalArgumentException(
"Don't load FontListFontFamily through ensureRegistered: $fontFamily"
)
}
}
is LoadedFontFamily -> {
val typeface = fontFamily.typeface as SkiaBackedTypeface
val alias = typeface.alias ?: typeface.nativeTypeface.familyName
ensureRegistered(typeface.nativeTypeface, alias)
listOf(alias)
ensureRegistered(typeface.nativeTypeface, typeface.alias)
listOf(typeface.alias)
}
is GenericFontFamily -> mapGenericFontFamily(fontFamily)
FontFamily.Default -> mapGenericFontFamily(FontFamily.SansSerif)
is GenericFontFamily -> fontFamily.aliases
is DefaultFontFamily -> FontFamily.SansSerif.aliases
else -> throw IllegalArgumentException("Unknown font family type: $fontFamily")
}
}
Expand All @@ -214,7 +236,11 @@ internal enum class Platform {
internal expect fun currentPlatform(): Platform
internal expect fun loadTypeface(font: Font): SkTypeface

internal val GenericFontFamiliesMapping: Map<String, List<String>> by lazy {
internal val GenericFontFamily.aliases
get() = GenericFontFamiliesMapping[name]
?: error("Unknown generic font family $name")

private val GenericFontFamiliesMapping: Map<String, List<String>> by lazy {
when (currentPlatform()) {
Platform.Linux ->
mapOf(
Expand Down

0 comments on commit e3655ac

Please sign in to comment.