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) {
|
val list = if (textPaint != null) {
|
||||||
arr.filter { textPaint.hasGlyph(it) }
|
arr.filter { textPaint.hasGlyph(it) }
|
||||||
} else {
|
} else {
|
||||||
arr.toList()
|
arr.asList()
|
||||||
}
|
}
|
||||||
val chunks = list.chunked(density.pageSize)
|
val chunks = list.chunked(density.pageSize)
|
||||||
categories.add(cat to IntRange(pages.size, pages.size + chunks.size - 1))
|
categories.add(cat to IntRange(pages.size, pages.size + chunks.size - 1))
|
||||||
|
|||||||
@ -5,15 +5,16 @@
|
|||||||
|
|
||||||
package org.fcitx.fcitx5.android.input.popup
|
package org.fcitx.fcitx5.android.input.popup
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.icu.lang.UCharacter
|
import android.icu.lang.UCharacter
|
||||||
import android.icu.lang.UProperty
|
import android.icu.lang.UProperty
|
||||||
|
import android.icu.text.UnicodeSet
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.TextPaint
|
import android.text.TextPaint
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import org.fcitx.fcitx5.android.R
|
import org.fcitx.fcitx5.android.R
|
||||||
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
|
import org.fcitx.fcitx5.android.data.prefs.AppPrefs
|
||||||
import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum
|
import org.fcitx.fcitx5.android.data.prefs.ManagedPreferenceEnum
|
||||||
import org.fcitx.fcitx5.android.utils.includes
|
|
||||||
|
|
||||||
object EmojiModifier {
|
object EmojiModifier {
|
||||||
|
|
||||||
@ -29,16 +30,12 @@ object EmojiModifier {
|
|||||||
/**
|
/**
|
||||||
* **Special Case 1:** Drop `U+FE0F` (Variation Selector-16) when combining with skin tone
|
* **Special Case 1:** Drop `U+FE0F` (Variation Selector-16) when combining with skin tone
|
||||||
*/
|
*/
|
||||||
private val SpecialCase1 = intArrayOf(
|
@RequiresApi(Build.VERSION_CODES.P)
|
||||||
0x261D, // ☝️
|
private fun shouldSkipVariationSelector16(ch: Int): Boolean {
|
||||||
0x26F9, // ⛹️
|
return UCharacter.hasBinaryProperty(ch, UProperty.EMOJI_MODIFIER_BASE)
|
||||||
0x270C, // ✌️
|
&& !UCharacter.hasBinaryProperty(ch, UProperty.EMOJI_PRESENTATION)
|
||||||
0x1F3CB, // 🏋️
|
}
|
||||||
0x1F3CC, // 🏌️
|
|
||||||
0x1F574, // 🕴️
|
|
||||||
0x1F575, // 🕵️
|
|
||||||
0x1F590, // 🖐️
|
|
||||||
)
|
|
||||||
private const val VariationSelector16 = 0xFE0F
|
private const val VariationSelector16 = 0xFE0F
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,7 +53,7 @@ object EmojiModifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isModifiable(modifiable: BooleanArray): Boolean {
|
private fun isModifiable(modifiable: BooleanArray): Boolean {
|
||||||
val sum = modifiable.sumOf { if (it) 1 else 0 }
|
val sum = modifiable.count { it }
|
||||||
// bail if too crowded
|
// 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
|
// 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
|
return sum == 1 || sum == 2
|
||||||
@ -75,40 +72,59 @@ object EmojiModifier {
|
|||||||
return codePoints to modifiable
|
return codePoints to modifiable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.P)
|
||||||
private fun buildEmoji(codePoints: IntArray, modifiable: BooleanArray, tone: SkinTone): String {
|
private fun buildEmoji(codePoints: IntArray, modifiable: BooleanArray, tone: SkinTone): String {
|
||||||
return buildString {
|
return buildString {
|
||||||
for (i in 0..<codePoints.size) {
|
var i = 0
|
||||||
// skip U+FE0F if the preceding character is special
|
while (i < codePoints.size) {
|
||||||
if (i > 0 && codePoints[i] == VariationSelector16 &&
|
|
||||||
SpecialCase1.includes(codePoints[i - 1]) &&
|
|
||||||
tone != SkinTone.Default
|
|
||||||
) continue
|
|
||||||
appendCodePoint(codePoints[i])
|
appendCodePoint(codePoints[i])
|
||||||
if (modifiable[i]) {
|
if (modifiable[i]) {
|
||||||
append(tone.value)
|
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 {
|
fun getPreferredTone(emoji: String): String {
|
||||||
if (!isSupported()) return emoji
|
if (!isSupported()) return emoji
|
||||||
val (codePoints, modifiable) = getCodePoints(emoji)
|
val (codePoints, modifiable) = getCodePoints(emoji)
|
||||||
if (!isModifiable(modifiable)) return emoji
|
val tone = defaultSkinTone
|
||||||
val candidate = buildEmoji(codePoints, modifiable, defaultSkinTone)
|
if (tone == SkinTone.Default || !isModifiable(modifiable)) return emoji
|
||||||
return if (DefaultTextPaint.hasGlyph(candidate)) candidate else emoji
|
val candidate = buildEmoji(codePoints, modifiable, tone)
|
||||||
|
return if (isValidEmoji(candidate)) candidate else emoji
|
||||||
}
|
}
|
||||||
|
|
||||||
fun produceSkinTones(emoji: String): Array<String>? {
|
fun produceSkinTones(emoji: String): Array<String>? {
|
||||||
if (!isSupported()) return null
|
if (!isSupported()) return null
|
||||||
val (codePoints, modifiable) = getCodePoints(emoji)
|
val (codePoints, modifiable) = getCodePoints(emoji)
|
||||||
|
val tone = defaultSkinTone
|
||||||
if (!isModifiable(modifiable)) return null
|
if (!isModifiable(modifiable)) return null
|
||||||
val candidates = SkinTone.entries
|
val candidates = SkinTone.entries
|
||||||
.filter { it != defaultSkinTone }
|
.filter { it != tone }
|
||||||
.map { buildEmoji(codePoints, modifiable, it) }
|
.map { buildEmoji(codePoints, modifiable, it) }
|
||||||
.filter { DefaultTextPaint.hasGlyph(it) }
|
.filter { isValidEmoji(it) }
|
||||||
return if (candidates.isEmpty()) null else candidates.toTypedArray()
|
return if (candidates.isEmpty()) null else candidates.toTypedArray()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user