Skip to content

Commit

Permalink
Noto image & animation sizes are now part of emoji details, which all…
Browse files Browse the repository at this point in the history
…ows to size the emoji container before downloading the image or animation
  • Loading branch information
SalomonBrys committed May 28, 2024
1 parent ab439bb commit b0469b3
Show file tree
Hide file tree
Showing 22 changed files with 874 additions and 212 deletions.
4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ allprojects {
tasks.configureEach {
if (name == "kotlinStoreYarnLock") enabled = false
}
}
}

val genEmojis = tasks.create<GenEmojis>("genEmojis")
15 changes: 15 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,26 @@ plugins {

repositories {
mavenCentral()

maven("https://css4j.github.io/maven/") {
mavenContent { releasesOnly() }
content {
includeGroup("com.github.css4j")
includeGroup("io.sf.carte")
includeGroup("io.sf.jclf")
}
}

}

//noinspection UseTomlInstead
dependencies {
implementation(gradleApi())
implementation(gradleKotlinDsl())

implementation("com.squareup.moshi:moshi:1.15.1")
implementation("com.squareup.moshi:moshi-kotlin:1.15.1")
implementation("com.squareup.okhttp3:okhttp:4.12.0")

implementation("com.github.weisj:jsvg:1.4.0")
}
9 changes: 6 additions & 3 deletions buildSrc/src/main/kotlin/Annotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ data class AnnotatedForm(
val altForms: List<Form>,
val emoticons: List<String>,
val aliases: List<String>,
val notoAnimated: Boolean
val hasNotoImage: Boolean,
val hasNotoAnimation: Boolean
)

@OptIn(ExperimentalStdlibApi::class)
Expand Down Expand Up @@ -61,7 +62,8 @@ fun annotate(grouppedForms: GrouppedForms, notoJsonFile: File): List<AnnotatedFo
altForms = forms - mainForm,
emoticons = notoEmoji.emoticons,
aliases = aliases,
notoAnimated = notoEmoji.animated
hasNotoImage = true,
hasNotoAnimation = notoEmoji.animated
)
}

Expand All @@ -75,7 +77,8 @@ fun annotate(grouppedForms: GrouppedForms, notoJsonFile: File): List<AnnotatedFo
altForms = forms - mainForm,
emoticons = emptyList(),
aliases = emptyList(),
notoAnimated = false
hasNotoImage = false,
hasNotoAnimation = false
)
}

Expand Down
53 changes: 51 additions & 2 deletions buildSrc/src/main/kotlin/EmojiFiles.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import java.io.File


Expand All @@ -16,7 +19,7 @@ private fun skinToneIntIndices2CharIndices(code: List<Int>, intIndices: List<Int

internal typealias AnnotatedFormTree = Map<String, Map<String, List<AnnotatedForm>>>

internal fun genEmojiFiles(outputDir: File, annotatedForms: List<AnnotatedForm>): AnnotatedFormTree {
internal fun genEmojiFiles(outputDir: File, annotatedForms: List<AnnotatedForm>, cacheDir: File): AnnotatedFormTree {
val ids = LinkedHashMap<String, LinkedHashMap<String, ArrayList<AnnotatedForm>>>()
annotatedForms
.forEach { annotatedForm ->
Expand All @@ -28,6 +31,51 @@ internal fun genEmojiFiles(outputDir: File, annotatedForms: List<AnnotatedForm>)
ids.getOrPut(groupId) { LinkedHashMap() }.getOrPut(subgroupId) { ArrayList() }.add(annotatedForm)
val doubleSkinToneZWJ = annotatedForm.mainForm.doubleSkinToneZWJs["minimally-qualified"] ?: annotatedForm.mainForm.doubleSkinToneZWJs["fully-qualified"]
val unqualifiedForm = annotatedForm.altForms.firstOrNull { it.entry.type == "unqualified" }

val notoCode = annotatedForm.mainForm.entry.code.joinToString("_") { it.toString(radix = 16) }

var notoImageRatio = 0f
if (annotatedForm.hasNotoImage) {
val file = cacheDir.resolve("$notoCode.svg")
val bytes = file.readBytes()
if (bytes.isNotEmpty()) {
val xml = bytes.toString(Charsets.UTF_8)
val svg = Regex("<svg\\s[^>]+>").find(xml)
?: error("${file.absolutePath}: Could not find svg markup")
val w = Regex("width=\"(?<w>\\d+(?:\\.\\d+)?)(?:px)?\"").find(svg.value)?.groups?.get("w")?.value?.toFloat()
val h = Regex("height=\"(?<h>\\d+(?:\\.\\d+)?)(?:px)?\"").find(svg.value)?.groups?.get("h")?.value?.toFloat()
if (w != null && h != null) {
notoImageRatio = w / h
} else {
val viewBox = Regex("viewBox=\"-?\\d+(?:\\.\\d+)? -?\\d+(?:\\.\\d+)? (?<w>-?\\d+(?:\\.\\d+)?) (?<h>-?\\d+(?:\\.\\d+)?)\"").find(svg.value)
if (viewBox != null) {
val vbW = viewBox.groups["w"]!!.value.toFloat()
val vbH = viewBox.groups["h"]!!.value.toFloat()
notoImageRatio = vbW / vbH
} else {
error("${file.absolutePath}: Could not find neither viewBox nor width & height.")
}
}
}
}

var notoAnimationRatio = 0f
if (annotatedForm.hasNotoAnimation) {
val file = cacheDir.resolve("$notoCode.json")
val json = file.readText()
if (json.isNotEmpty()) {
@OptIn(ExperimentalStdlibApi::class)
val adapter = Moshi.Builder()
.addLast(KotlinJsonAdapterFactory())
.build()
.adapter<Map<String, Any>>()
val map = adapter.fromJson(json) ?: error("${file.absolutePath}: Could not parse JSON.")
val w = (map["w"] as? Number)?.toFloat() ?: error("${file.absolutePath}: Could not find w.")
val h = (map["h"] as? Number)?.toFloat() ?: error("${file.absolutePath}: Could not find h.")
notoAnimationRatio = w / h
}
}

val (itf, impl) = when {
annotatedForm.mainForm.skinToneIndices == null && doubleSkinToneZWJ == null && unqualifiedForm?.skinToneIndices == null -> "Emoji" to "EmojiImpl"
annotatedForm.mainForm.skinToneIndices == null && unqualifiedForm?.skinToneIndices?.size == 1 -> "SkinTone1Emoji" to "UnqualifiedSkinTone1EmojiImpl"
Expand Down Expand Up @@ -64,7 +112,8 @@ internal fun genEmojiFiles(outputDir: File, annotatedForms: List<AnnotatedForm>)
writer.appendLine(" unicodeVersion = UnicodeVersion(${annotatedForm.mainForm.entry.version[0]}, ${annotatedForm.mainForm.entry.version[1]}),")
writer.appendLine(" aliases = listOf(\"${annotatedForm.mainForm.entry.description.kebabCase()}\", ${annotatedForm.aliases.joinToString { "\"$it\"" }}),")
writer.appendLine(" emoticons = listOf(${emoticons.joinToString { "\"$it\"" }}),")
writer.appendLine(" notoAnimated = ${annotatedForm.notoAnimated},")
writer.appendLine(" notoImageRatio = ${notoImageRatio}f,")
writer.appendLine(" notoAnimationRatio = ${notoAnimationRatio}f,")
writer.appendLine(" ),")
when (impl) {
"UnqualifiedSkinTone1EmojiImpl" -> {
Expand Down
32 changes: 20 additions & 12 deletions buildSrc/src/main/kotlin/GenEmojis.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,40 @@ abstract class GenEmojis : DefaultTask() {
abstract val notoJsonFile: RegularFileProperty

@get:OutputDirectory
abstract val genDirectory: DirectoryProperty
abstract val genEmojiDirectory: DirectoryProperty

@get:OutputDirectory
abstract val genNotoDirectory: DirectoryProperty

@get:OutputDirectory
abstract val cacheDirectory: DirectoryProperty

init {
group = "build"
unicodeTextFile.convention(project.layout.projectDirectory.file("src/emoji/emoji-test.txt"))
notoJsonFile.convention(project.layout.projectDirectory.file("src/emoji/emoji_15_0_ordering.json"))
genDirectory.convention(project.layout.buildDirectory.dir("gen/emoji"))
unicodeTextFile.convention(project.layout.projectDirectory.file("definitions/emoji-test.txt"))
notoJsonFile.convention(project.layout.projectDirectory.file("definitions/emoji_15_0_ordering.json"))
genEmojiDirectory.convention(project.layout.buildDirectory.dir("gen/emoji"))
genNotoDirectory.convention(project.layout.buildDirectory.dir("gen/noto"))
cacheDirectory.convention(project.layout.buildDirectory.dir("cache/noto"))
}

// Generates emojis


@OptIn(ExperimentalStdlibApi::class)
@TaskAction
private fun execute() {
val entries = getEntriesFromFile(unicodeTextFile.get().asFile)
val forms = entriesToForms(entries)
val annotatedForms = annotate(forms, notoJsonFile.get().asFile)

val outputDir = genDirectory.get().asFile
outputDir.deleteRecursively()
outputDir.mkdirs()
val cacheDir = cacheDirectory.get().asFile
cacheDir.mkdirs()
downloadNotoFiles(annotatedForms, cacheDir)

val tree = genEmojiFiles(outputDir, annotatedForms)
val emojiOutputDir = genEmojiDirectory.get().asFile
emojiOutputDir.deleteRecursively()
emojiOutputDir.mkdirs()

genCollections(outputDir, tree)
val tree = genEmojiFiles(emojiOutputDir, annotatedForms, cacheDir)
genCollections(emojiOutputDir, tree)
}

}
45 changes: 45 additions & 0 deletions buildSrc/src/main/kotlin/NotoFiles.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit


private fun ExecutorService.download(client: OkHttpClient, url: String, file: File) {
if (file.exists()) return

submit {
val request = Request.Builder()
.url(url)
.get()
.build()
file.outputStream().use { output ->
client.newCall(request).execute().use { response ->
if (!response.isSuccessful) error(response)
response.body!!.byteStream().use { input ->
input.copyTo(output)
}
}
}
}
}

internal fun downloadNotoFiles(forms: List<AnnotatedForm>, cacheDir: File) {

val httpClient = OkHttpClient()
val exec = Executors.newFixedThreadPool(16)

forms.forEach { form ->
val code = form.mainForm.entry.code.joinToString("_") { it.toString(radix = 16) }
if (form.hasNotoImage) {
exec.download(httpClient, "https://fonts.gstatic.com/s/e/notoemoji/latest/$code/emoji.svg", cacheDir.resolve("$code.svg"))
}
if (form.hasNotoAnimation) {
exec.download(httpClient, "https://fonts.gstatic.com/s/e/notoemoji/latest/$code/lottie.json", cacheDir.resolve("$code.json"))
}
}

exec.shutdown()
exec.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package org.kodein.emoji.compose.demo

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ProvideTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -46,9 +47,10 @@ fun App() {
}
NotoAnimatedEmoji(
emoji = Emoji.ImpSmile,
modifier = Modifier.size(64.dp),
modifier = Modifier.height(92.dp),
iterations = 2,
stopAt = 0.76f
stopAt = 0.76f,
placeholder = { Box(it) }
)
}
}
Expand Down
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit b0469b3

Please sign in to comment.