Skip to content

Commit

Permalink
Add API to access fonts from resources. (#509)
Browse files Browse the repository at this point in the history
* Add API to access fonts from resources.

* More font locations
  • Loading branch information
olonho authored Mar 1, 2022
1 parent 108f7eb commit e8b3b44
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 11 deletions.
1 change: 0 additions & 1 deletion skiko/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import org.gradle.api.tasks.testing.AbstractTestTask
import org.jetbrains.compose.internal.publishing.MavenCentralProperties
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.gradle.api.tasks.testing.logging.TestExceptionFormat

plugins {
Expand Down
60 changes: 50 additions & 10 deletions skiko/src/awtMain/kotlin/org/jetbrains/skiko/FontInterop.awt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import java.awt.Font
import java.awt.FontFormatException
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap

Expand Down Expand Up @@ -42,6 +43,8 @@ class AwtFontManager(fontPaths: Array<String> = emptyArray()) {
OS.Linux -> {
val pathsToCheck = arrayOf(
System.getProperty("user.home") + File.separator + ".fonts",
"/usr/share/fonts",
"/usr/local/share/fonts",
"/usr/share/fonts/truetype",
"/usr/share/fonts/TTF"
)
Expand All @@ -61,33 +64,50 @@ class AwtFontManager(fontPaths: Array<String> = emptyArray()) {
}
}

private fun isFont(extension: String): Boolean {
return extension == "ttf" ||
extension == "ttc" ||
extension == "otf"
}

private fun findFontFiles(paths: List<String>): List<File> {
val files = mutableListOf<File>()
paths.forEach { path ->
val fontDirectory = File(path)
if (fontDirectory.exists()) {
fontDirectory.walk().filter { it.isFile && it.extension.lowercase() == "ttf" }.forEach {
fontDirectory.walk().filter { it.isFile && isFont(it.extension.lowercase()) }.forEach {
files.add(it)
}
}
}
return files
}

private fun addFontFromFile(file: File): Boolean {
val f = try {
FileInputStream(file.absolutePath).use {
Font.createFont(Font.TRUETYPE_FONT, it)
}
} catch (e: FontFormatException){
return false
} catch (e: IOException) {
return false
}
val name = f.family

val list = fontsMap.computeIfAbsent(name) { mutableListOf() }
synchronized(list) {
list.add(FontDescriptor(file.absoluteFile, f.style))
}
return true
}

private suspend fun cacheAllFonts() {
fontsMap.clear()
val fontFiles = findFontFiles(customFontPaths) + findFontFiles(systemFontsPaths())
for (file in fontFiles) {
try {
val f = FileInputStream(file.absolutePath).use {
Font.createFont(Font.TRUETYPE_FONT, it)
}
val name = f.family

val list = fontsMap.computeIfAbsent(name) { mutableListOf() }
synchronized(list) {
list.add(FontDescriptor(file.absoluteFile, f.style))
}
addFontFromFile(file)
yield()
} catch (e: FontFormatException) {
} catch (e: IOException) {
Expand Down Expand Up @@ -186,6 +206,26 @@ class AwtFontManager(fontPaths: Array<String> = emptyArray()) {
customFontPaths += path
}

/**
* Add custom resource entry as a font known to this resource manager.
*
* @return true, if font was found and identified, and false otherwise
*/
fun addResourceFont(resource: String, loader: ClassLoader = Thread.currentThread().contextClassLoader): Boolean {
val res = loader.getResourceAsStream(resource) ?: ClassLoader.getSystemResourceAsStream(resource) ?: return false
val file = File.createTempFile("tmp", ".ttf")
file.deleteOnExit()
FileOutputStream(file).use { out ->
out.write(res.readAllBytes())
}
return addFontFromFile(file).also {
if (it)
customFontPaths += file.absolutePath
else
file.delete()
}
}

/**
* If all AWT fonts were cached. Check this property before using non-suspend version
* of font conversion APIs.
Expand Down
15 changes: 15 additions & 0 deletions skiko/src/awtTest/kotlin/org/jetbrains/skiko/AwtFontInterop.kt
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,19 @@ class AwtFontInterop {
assertTrue("Font must be file", path.exists() && path.isFile)
}
}

// This test is disabled due to convoluted setup of tests.
// @Test
fun addCustomResource() {
runTest {
assumeOk()
val fontManager = AwtFontManager()
assertTrue("Custom resource must be found",
fontManager.addResourceFont("/fonts/JetBrainsMono-Bold.ttf", Library.javaClass.classLoader))
val path = fontManager.findFontFamilyFile("JetBrains Mono")
assertTrue("Custom font must be found", path != null)
path!!
assertTrue("Font must be file", path.exists() && path.isFile)
}
}
}

0 comments on commit e8b3b44

Please sign in to comment.