mirror of
https://github.com/fcitx5-android/fcitx5-android.git
synced 2026-08-02 04:44:35 +08:00
Reduce hardcoded special case in EmojiModifier
This commit is contained in:
parent
9409cb1112
commit
d0364b8387
@ -47,7 +47,7 @@ class PickerPagesAdapter(
|
||||
val list = if (textPaint != null) {
|
||||
arr.filter { textPaint.hasGlyph(it) }
|
||||
} else {
|
||||
arr.toList()
|
||||
arr.asList()
|
||||
}
|
||||
val chunks = list.chunked(density.pageSize)
|
||||
categories.add(cat to IntRange(pages.size, pages.size + chunks.size - 1))
|
||||
|
||||
@ -5,15 +5,16 @@
|
||||
|
||||
package org.fcitx.fcitx5.android.input.popup
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.icu.lang.UCharacter
|
||||
import android.icu.lang.UProperty
|
||||
import android.icu.text.UnicodeSet
|
||||
import android.os.Build
|
||||
import android.text.TextPaint
|
||||
import androidx.annotation.RequiresApi
|
||||
import org.fcitx.fcitx5.android.R
|
||||
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
|
||||
import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum
|
||||
import org.fcitx.fcitx5.android.utils.includes
|
||||
|
||||
object EmojiModifier {
|
||||
|
||||
@ -29,16 +30,12 @@ object EmojiModifier {
|
||||
/**
|
||||
* **Special Case 1:** Drop `U+FE0F` (Variation Selector-16) when combining with skin tone
|
||||
*/
|
||||
private val SpecialCase1 = intArrayOf(
|
||||
0x261D, // ☝️
|
||||
0x26F9, // ⛹️
|
||||
0x270C, // ✌️
|
||||
0x1F3CB, // 🏋️
|
||||
0x1F3CC, // 🏌️
|
||||
0x1F574, // 🕴️
|
||||
0x1F575, // 🕵️
|
||||
0x1F590, // 🖐️
|
||||
)
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
private fun shouldSkipVariationSelector16(ch: Int): Boolean {
|
||||
return UCharacter.hasBinaryProperty(ch, UProperty.EMOJI_MODIFIER_BASE)
|
||||
&& !UCharacter.hasBinaryProperty(ch, UProperty.EMOJI_PRESENTATION)
|
||||
}
|
||||
|
||||
private const val VariationSelector16 = 0xFE0F
|
||||
|
||||
/**
|
||||
@ -56,7 +53,7 @@ object EmojiModifier {
|
||||
}
|
||||
|
||||
private fun isModifiable(modifiable: BooleanArray): Boolean {
|
||||
val sum = modifiable.sumOf { if (it) 1 else 0 }
|
||||
val sum = modifiable.count { it }
|
||||
// bail if too crowded
|
||||
// eg. https://emojipedia.org/family-man-medium-light-skin-tone-woman-medium-light-skin-tone-girl-medium-light-skin-tone-boy-medium-light-skin-tone
|
||||
return sum == 1 || sum == 2
|
||||
@ -75,40 +72,59 @@ object EmojiModifier {
|
||||
return codePoints to modifiable
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
private fun buildEmoji(codePoints: IntArray, modifiable: BooleanArray, tone: SkinTone): String {
|
||||
return buildString {
|
||||
for (i in 0..<codePoints.size) {
|
||||
// skip U+FE0F if the preceding character is special
|
||||
if (i > 0 && codePoints[i] == VariationSelector16 &&
|
||||
SpecialCase1.includes(codePoints[i - 1]) &&
|
||||
tone != SkinTone.Default
|
||||
) continue
|
||||
var i = 0
|
||||
while (i < codePoints.size) {
|
||||
appendCodePoint(codePoints[i])
|
||||
if (modifiable[i]) {
|
||||
append(tone.value)
|
||||
if (tone != SkinTone.Default &&
|
||||
codePoints.getOrNull(i + 1) == VariationSelector16 &&
|
||||
shouldSkipVariationSelector16(codePoints[i])
|
||||
) i++
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val DefaultTextPaint = TextPaint()
|
||||
private val DefaultTextPaint by lazy {
|
||||
TextPaint()
|
||||
}
|
||||
|
||||
private val RGIEmojiSet by lazy {
|
||||
@SuppressLint("NewApi")
|
||||
UnicodeSet("[:RGI_Emoji:]").freeze()
|
||||
}
|
||||
|
||||
private fun isValidEmoji(emoji: String): Boolean {
|
||||
// UProperty.RGI_EMOJI is available on 34+
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
if (!RGIEmojiSet.contains(emoji)) return false
|
||||
}
|
||||
return DefaultTextPaint.hasGlyph(emoji)
|
||||
}
|
||||
|
||||
fun getPreferredTone(emoji: String): String {
|
||||
if (!isSupported()) return emoji
|
||||
val (codePoints, modifiable) = getCodePoints(emoji)
|
||||
if (!isModifiable(modifiable)) return emoji
|
||||
val candidate = buildEmoji(codePoints, modifiable, defaultSkinTone)
|
||||
return if (DefaultTextPaint.hasGlyph(candidate)) candidate else emoji
|
||||
val tone = defaultSkinTone
|
||||
if (tone == SkinTone.Default || !isModifiable(modifiable)) return emoji
|
||||
val candidate = buildEmoji(codePoints, modifiable, tone)
|
||||
return if (isValidEmoji(candidate)) candidate else emoji
|
||||
}
|
||||
|
||||
fun produceSkinTones(emoji: String): Array<String>? {
|
||||
if (!isSupported()) return null
|
||||
val (codePoints, modifiable) = getCodePoints(emoji)
|
||||
val tone = defaultSkinTone
|
||||
if (!isModifiable(modifiable)) return null
|
||||
val candidates = SkinTone.entries
|
||||
.filter { it != defaultSkinTone }
|
||||
.filter { it != tone }
|
||||
.map { buildEmoji(codePoints, modifiable, it) }
|
||||
.filter { DefaultTextPaint.hasGlyph(it) }
|
||||
.filter { isValidEmoji(it) }
|
||||
return if (candidates.isEmpty()) null else candidates.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user