2018-01-04 19:52:25 +01:00
|
|
|
|
package jp.juggler.subwaytooter.util
|
|
|
|
|
|
|
|
|
|
import android.content.Context
|
2019-06-03 22:50:18 +02:00
|
|
|
|
import android.content.SharedPreferences
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import android.text.Spannable
|
|
|
|
|
import android.text.SpannableStringBuilder
|
|
|
|
|
import android.text.Spanned
|
2018-01-18 19:09:36 +01:00
|
|
|
|
import android.util.SparseBooleanArray
|
2019-09-14 22:09:52 +02:00
|
|
|
|
import androidx.annotation.DrawableRes
|
|
|
|
|
import jp.juggler.emoji.EmojiMap
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import jp.juggler.subwaytooter.App1
|
2018-01-18 19:09:36 +01:00
|
|
|
|
import jp.juggler.subwaytooter.Pref
|
2018-01-20 17:59:38 +01:00
|
|
|
|
import jp.juggler.subwaytooter.R
|
2018-03-10 17:22:13 +01:00
|
|
|
|
import jp.juggler.subwaytooter.api.entity.CustomEmoji
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import jp.juggler.subwaytooter.span.EmojiImageSpan
|
|
|
|
|
import jp.juggler.subwaytooter.span.HighlightSpan
|
|
|
|
|
import jp.juggler.subwaytooter.span.NetworkEmojiSpan
|
2019-09-14 22:09:52 +02:00
|
|
|
|
import jp.juggler.subwaytooter.span.createSpan
|
2018-01-04 19:52:25 +01:00
|
|
|
|
import jp.juggler.subwaytooter.table.HighlightWord
|
2018-12-01 00:02:18 +01:00
|
|
|
|
import jp.juggler.util.codePointBefore
|
|
|
|
|
import java.util.*
|
2018-01-20 17:59:38 +01:00
|
|
|
|
import java.util.regex.Pattern
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
object EmojiDecoder {
|
|
|
|
|
|
2019-06-03 22:50:18 +02:00
|
|
|
|
private const val cpColon = ':'.toInt()
|
|
|
|
|
|
|
|
|
|
private const val cpZwsp = '\u200B'.toInt()
|
|
|
|
|
|
|
|
|
|
fun customEmojiSeparator(pref : SharedPreferences) = if(Pref.bpCustomEmojiSeparatorZwsp(pref)) {
|
|
|
|
|
'\u200B'
|
|
|
|
|
} else {
|
|
|
|
|
' '
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-22 14:49:22 +01:00
|
|
|
|
// タンス側が落ち着いたら [^[:almun:]_] から [:space:]に切り替える
|
|
|
|
|
// private fun isHeadOrAfterWhitespace( s:CharSequence,index:Int):Boolean {
|
|
|
|
|
// val cp = s.codePointBefore(index)
|
|
|
|
|
// return cp == -1 || CharacterGroup.isWhitespace(cp)
|
|
|
|
|
// }
|
2018-01-21 17:47:13 +01:00
|
|
|
|
|
2018-03-22 14:49:22 +01:00
|
|
|
|
fun canStartShortCode(s : CharSequence, index : Int) : Boolean {
|
2019-09-14 22:09:52 +02:00
|
|
|
|
return when(val cp = s.codePointBefore(index)) {
|
2018-03-22 14:49:22 +01:00
|
|
|
|
- 1 -> true
|
|
|
|
|
cpColon -> false
|
2019-06-03 22:50:18 +02:00
|
|
|
|
cpZwsp -> true
|
2018-11-06 02:29:33 +01:00
|
|
|
|
// rubyの (Letter | Mark | Decimal_Number) はNG
|
|
|
|
|
// ftp://unicode.org/Public/5.1.0/ucd/UCD.html#General_Category_Values
|
2019-09-12 19:16:07 +02:00
|
|
|
|
else -> when(Character.getType(cp).toByte()) {
|
2018-11-06 02:29:33 +01:00
|
|
|
|
// Letter
|
|
|
|
|
// LCはエイリアスなので文字から得られることはないはず
|
2018-03-22 14:49:22 +01:00
|
|
|
|
Character.UPPERCASE_LETTER,
|
|
|
|
|
Character.LOWERCASE_LETTER,
|
|
|
|
|
Character.TITLECASE_LETTER,
|
|
|
|
|
Character.MODIFIER_LETTER,
|
|
|
|
|
Character.OTHER_LETTER -> false
|
2018-11-06 02:29:33 +01:00
|
|
|
|
// Mark
|
2018-03-22 14:49:22 +01:00
|
|
|
|
Character.NON_SPACING_MARK,
|
|
|
|
|
Character.COMBINING_SPACING_MARK,
|
|
|
|
|
Character.ENCLOSING_MARK -> false
|
2018-11-06 02:29:33 +01:00
|
|
|
|
// Decimal_Number
|
2018-03-22 14:49:22 +01:00
|
|
|
|
Character.DECIMAL_DIGIT_NUMBER -> false
|
|
|
|
|
|
|
|
|
|
else -> true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// https://mastodon.juggler.jp/@tateisu/99727683089280157
|
|
|
|
|
// https://github.com/tootsuite/mastodon/pull/5570 がマージされたらこっちに切り替える
|
|
|
|
|
// return cp == -1 || CharacterGroup.isWhitespace(cp)
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-12 19:16:07 +02:00
|
|
|
|
fun canStartHashtag(s : CharSequence, index : Int) : Boolean {
|
|
|
|
|
val cp = s.codePointBefore(index)
|
|
|
|
|
// HASHTAG_RE = /(?:^|[^\/\)\w])#(#{HASHTAG_NAME_RE})/i
|
2019-09-14 22:09:52 +02:00
|
|
|
|
return if(cp >= 0x80) {
|
2019-09-12 19:16:07 +02:00
|
|
|
|
true
|
2019-09-14 22:09:52 +02:00
|
|
|
|
} else when(cp.toChar()) {
|
2019-09-12 19:16:07 +02:00
|
|
|
|
'/' -> false
|
|
|
|
|
')' -> false
|
|
|
|
|
'_' -> false
|
|
|
|
|
in 'a' .. 'z' -> false
|
|
|
|
|
in 'A' .. 'Z' -> false
|
|
|
|
|
in '0' .. '9' -> false
|
|
|
|
|
else -> true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-22 14:49:22 +01:00
|
|
|
|
private class EmojiStringBuilder(internal val options : DecodeOptions) {
|
2018-01-18 19:09:36 +01:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
internal val sb = SpannableStringBuilder()
|
|
|
|
|
internal var normal_char_start = - 1
|
|
|
|
|
|
2018-01-21 17:47:13 +01:00
|
|
|
|
private fun openNormalText() {
|
2018-01-19 14:15:39 +01:00
|
|
|
|
if(normal_char_start == - 1) {
|
|
|
|
|
normal_char_start = sb.length
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
internal fun closeNormalText() {
|
|
|
|
|
if(normal_char_start != - 1) {
|
|
|
|
|
val end = sb.length
|
|
|
|
|
applyHighlight(normal_char_start, end)
|
|
|
|
|
normal_char_start = - 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun applyHighlight(start : Int, end : Int) {
|
2018-01-19 14:15:39 +01:00
|
|
|
|
val list = options.highlightTrie?.matchList(sb, start, end) ?: return
|
2018-01-18 19:09:36 +01:00
|
|
|
|
for(range in list) {
|
2019-11-15 07:13:59 +01:00
|
|
|
|
val word = HighlightWord.load(range.word) ?: continue
|
|
|
|
|
sb.setSpan(
|
|
|
|
|
HighlightSpan(word.color_fg, word.color_bg),
|
|
|
|
|
range.start,
|
|
|
|
|
range.end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
2019-12-19 10:18:09 +01:00
|
|
|
|
|
2019-11-15 07:13:59 +01:00
|
|
|
|
if(word.sound_type != HighlightWord.SOUND_TYPE_NONE) {
|
|
|
|
|
if(options.highlightSound == null) options.highlightSound = word
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2019-12-19 10:18:09 +01:00
|
|
|
|
|
|
|
|
|
if(word.speech != 0) {
|
2019-11-15 07:13:59 +01:00
|
|
|
|
if(options.highlightSpeech == null) options.highlightSpeech = word
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(options.highlightAny == null) options.highlightAny = word
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-19 14:15:39 +01:00
|
|
|
|
internal fun addNetworkEmojiSpan(text : String, url : String) {
|
|
|
|
|
closeNormalText()
|
|
|
|
|
val start = sb.length
|
|
|
|
|
sb.append(text)
|
|
|
|
|
val end = sb.length
|
|
|
|
|
sb.setSpan(
|
2018-11-06 02:29:33 +01:00
|
|
|
|
NetworkEmojiSpan(url, scale = options.enlargeCustomEmoji),
|
2018-01-19 14:15:39 +01:00
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-01 06:43:27 +02:00
|
|
|
|
internal fun addImageSpan(text : String, @DrawableRes res_id : Int) {
|
2018-04-30 16:01:00 +02:00
|
|
|
|
val context = options.context
|
|
|
|
|
if(context == null) {
|
2018-05-01 06:43:27 +02:00
|
|
|
|
openNormalText()
|
|
|
|
|
sb.append(text)
|
2018-04-30 16:01:00 +02:00
|
|
|
|
} else {
|
|
|
|
|
closeNormalText()
|
|
|
|
|
val start = sb.length
|
|
|
|
|
sb.append(text)
|
|
|
|
|
val end = sb.length
|
|
|
|
|
sb.setSpan(
|
2020-01-04 15:17:19 +01:00
|
|
|
|
EmojiImageSpan(context, res_id ,scale=options.enlargeEmoji),
|
2018-04-30 16:01:00 +02:00
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
|
|
|
|
}
|
2018-01-19 14:15:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-19 10:18:09 +01:00
|
|
|
|
internal fun addImageSpan(text : String, er : EmojiMap.EmojiResource) {
|
2019-09-14 22:09:52 +02:00
|
|
|
|
val context = options.context
|
|
|
|
|
if(context == null) {
|
|
|
|
|
openNormalText()
|
|
|
|
|
sb.append(text)
|
|
|
|
|
} else {
|
|
|
|
|
closeNormalText()
|
|
|
|
|
val start = sb.length
|
|
|
|
|
sb.append(text)
|
|
|
|
|
val end = sb.length
|
|
|
|
|
sb.setSpan(
|
2020-01-04 15:17:19 +01:00
|
|
|
|
er.createSpan(context,scale=options.enlargeEmoji),
|
2019-09-14 22:09:52 +02:00
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-12-19 10:18:09 +01:00
|
|
|
|
|
2019-09-14 22:09:52 +02:00
|
|
|
|
val evs = EmojiMap.EmojiResource(0)
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
internal fun addUnicodeString(s : String) {
|
|
|
|
|
var i = 0
|
|
|
|
|
val end = s.length
|
|
|
|
|
while(i < end) {
|
|
|
|
|
val remain = end - i
|
|
|
|
|
var emoji : String? = null
|
2019-09-14 22:09:52 +02:00
|
|
|
|
var emojiResource : EmojiMap.EmojiResource? = null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2019-09-14 22:09:52 +02:00
|
|
|
|
for(j in EmojiMap.utf16_max_length downTo 1) {
|
2018-01-21 17:47:13 +01:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
if(j > remain) continue
|
2018-01-21 17:47:13 +01:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
val check = s.substring(i, i + j)
|
2018-01-21 17:47:13 +01:00
|
|
|
|
|
2019-09-14 22:09:52 +02:00
|
|
|
|
emojiResource = EmojiMap.sUTF16ToEmojiResource[check] ?: continue
|
2018-01-21 17:47:13 +01:00
|
|
|
|
|
2018-01-19 14:15:39 +01:00
|
|
|
|
emoji = if(j < remain && s[i + j].toInt() == 0xFE0E) {
|
|
|
|
|
// 絵文字バリエーション・シーケンス(EVS)のU+FE0E(VS-15)が直後にある場合
|
|
|
|
|
// その文字を絵文字化しない
|
2019-09-14 22:09:52 +02:00
|
|
|
|
emojiResource = evs
|
2018-01-19 14:15:39 +01:00
|
|
|
|
s.substring(i, i + j + 1)
|
|
|
|
|
} else {
|
|
|
|
|
check
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2018-01-21 17:47:13 +01:00
|
|
|
|
|
2018-01-19 14:15:39 +01:00
|
|
|
|
break
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-09-14 22:09:52 +02:00
|
|
|
|
if(emojiResource != null && emoji != null) {
|
|
|
|
|
if(emojiResource == evs) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
// 絵文字バリエーション・シーケンス(EVS)のU+FE0E(VS-15)が直後にある場合
|
|
|
|
|
// その文字を絵文字化しない
|
2018-01-19 14:15:39 +01:00
|
|
|
|
openNormalText()
|
2018-01-04 19:52:25 +01:00
|
|
|
|
sb.append(emoji)
|
|
|
|
|
} else {
|
2019-09-14 22:09:52 +02:00
|
|
|
|
addImageSpan(emoji, emojiResource)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2018-01-10 16:47:35 +01:00
|
|
|
|
i += emoji.length
|
2018-01-04 19:52:25 +01:00
|
|
|
|
} else {
|
2018-01-19 14:15:39 +01:00
|
|
|
|
openNormalText()
|
|
|
|
|
val length = Character.charCount(s.codePointAt(i))
|
|
|
|
|
if(length == 1) {
|
2018-04-30 16:01:00 +02:00
|
|
|
|
val c = s[i ++]
|
|
|
|
|
sb.append(
|
|
|
|
|
when(c) {
|
2018-11-06 02:29:33 +01:00
|
|
|
|
// https://github.com/tateisu/SubwayTooter/issues/69
|
2018-04-30 16:01:00 +02:00
|
|
|
|
'\u00AD' -> '-'
|
|
|
|
|
else -> c
|
|
|
|
|
}
|
|
|
|
|
)
|
2018-01-19 14:15:39 +01:00
|
|
|
|
} else {
|
|
|
|
|
sb.append(s.substring(i, i + length))
|
|
|
|
|
i += length
|
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-01-19 14:15:39 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private const val codepointColon = ':'.toInt()
|
2019-12-19 10:18:09 +01:00
|
|
|
|
private const val codepointAtmark = '@'.toInt()
|
2018-01-19 14:15:39 +01:00
|
|
|
|
|
2019-12-19 10:18:09 +01:00
|
|
|
|
private val shortCodeCharacterSet =
|
|
|
|
|
SparseBooleanArray().apply {
|
|
|
|
|
for(c in 'A' .. 'Z') put(c.toInt(), true)
|
|
|
|
|
for(c in 'a' .. 'z') put(c.toInt(), true)
|
|
|
|
|
for(c in '0' .. '9') put(c.toInt(), true)
|
|
|
|
|
for(c in "+-_@:") put(c.toInt(), true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private val profileEmojiCharacterSet =
|
|
|
|
|
shortCodeCharacterSet.clone().apply {
|
|
|
|
|
for(c in ".") put(c.toInt(), true)
|
|
|
|
|
}
|
2018-01-19 14:15:39 +01:00
|
|
|
|
|
|
|
|
|
private interface ShortCodeSplitterCallback {
|
|
|
|
|
fun onString(part : String) // shortcode以外の文字列
|
2019-06-03 22:50:18 +02:00
|
|
|
|
fun onShortCode(
|
|
|
|
|
prevCodePoint : Int,
|
|
|
|
|
part : String,
|
|
|
|
|
name : String
|
|
|
|
|
) // part : ":shortcode:", name : "shortcode"
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun splitShortCode(
|
2018-01-19 14:15:39 +01:00
|
|
|
|
s : String,
|
2019-12-19 10:18:09 +01:00
|
|
|
|
startArg:Int = 0,
|
|
|
|
|
end :Int = s.length,
|
2018-01-19 14:15:39 +01:00
|
|
|
|
callback : ShortCodeSplitterCallback
|
2018-01-04 19:52:25 +01:00
|
|
|
|
) {
|
2019-12-19 10:18:09 +01:00
|
|
|
|
var i = startArg
|
2018-01-04 19:52:25 +01:00
|
|
|
|
while(i < end) {
|
2018-01-19 14:15:39 +01:00
|
|
|
|
|
2019-12-19 10:18:09 +01:00
|
|
|
|
// ":"以外を読み飛ばす
|
|
|
|
|
var start = i
|
2018-08-15 04:06:25 +02:00
|
|
|
|
loop@ while(i < end) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
val c = s.codePointAt(i)
|
2019-12-19 10:18:09 +01:00
|
|
|
|
if(c == codepointColon) break@loop
|
|
|
|
|
i += Character.charCount(c)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2019-12-19 10:18:09 +01:00
|
|
|
|
if(i > start) callback.onString(s.substring(start, i))
|
2018-01-19 14:15:39 +01:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
if(i >= end) break
|
|
|
|
|
|
|
|
|
|
start = i ++ // start=コロンの位置 i=その次の位置
|
|
|
|
|
|
2018-01-19 14:15:39 +01:00
|
|
|
|
// 閉じるコロンを探す
|
|
|
|
|
var posEndColon = - 1
|
2019-12-19 10:18:09 +01:00
|
|
|
|
var countAtmark = 0
|
2018-01-04 19:52:25 +01:00
|
|
|
|
while(i < end) {
|
2018-01-19 14:15:39 +01:00
|
|
|
|
val cp = s.codePointAt(i)
|
|
|
|
|
if(cp == codepointColon) {
|
|
|
|
|
posEndColon = i
|
2018-01-04 19:52:25 +01:00
|
|
|
|
break
|
|
|
|
|
}
|
2019-12-19 10:18:09 +01:00
|
|
|
|
|
|
|
|
|
// ベスフレの @user@host 絵文字は . を考慮する必要がある
|
|
|
|
|
if(cp == codepointAtmark) ++ countAtmark
|
|
|
|
|
val allowedCodepointMap = when {
|
|
|
|
|
countAtmark >= 2 -> profileEmojiCharacterSet
|
|
|
|
|
else -> shortCodeCharacterSet
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2019-12-19 10:18:09 +01:00
|
|
|
|
if(! allowedCodepointMap.get(cp, false)) break
|
|
|
|
|
|
2018-01-19 14:15:39 +01:00
|
|
|
|
i += Character.charCount(cp)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-19 14:15:39 +01:00
|
|
|
|
// 閉じるコロンが見つからないか、shortcodeが短すぎるなら
|
|
|
|
|
// startの位置のコロンだけを処理して残りは次のループで処理する
|
2019-12-19 10:18:09 +01:00
|
|
|
|
if(posEndColon == - 1 || posEndColon - start < 2) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
callback.onString(":")
|
|
|
|
|
i = start + 1
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-19 10:18:09 +01:00
|
|
|
|
val prevCodePoint = when {
|
|
|
|
|
start <= 0 -> 0x20
|
|
|
|
|
else -> s.codePointBefore(start)
|
2019-01-06 09:20:37 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
callback.onShortCode(
|
2019-01-06 09:20:37 +01:00
|
|
|
|
prevCodePoint,
|
|
|
|
|
s.substring(start, posEndColon + 1), // ":shortcode:"
|
|
|
|
|
s.substring(start + 1, posEndColon) // "shortcode"
|
2018-01-04 19:52:25 +01:00
|
|
|
|
)
|
|
|
|
|
|
2018-01-19 14:15:39 +01:00
|
|
|
|
i = posEndColon + 1 // コロンの次の位置
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-20 17:59:38 +01:00
|
|
|
|
private val reNicoru = Pattern.compile("\\Anicoru\\d*\\z", Pattern.CASE_INSENSITIVE)
|
|
|
|
|
private val reHohoemi = Pattern.compile("\\Ahohoemi\\d*\\z", Pattern.CASE_INSENSITIVE)
|
|
|
|
|
|
2018-03-22 14:49:22 +01:00
|
|
|
|
fun decodeEmoji(options : DecodeOptions, s : String) : Spannable {
|
2018-01-21 17:47:13 +01:00
|
|
|
|
|
|
|
|
|
val builder = EmojiStringBuilder(options)
|
2018-03-22 14:49:22 +01:00
|
|
|
|
|
2018-01-18 19:09:36 +01:00
|
|
|
|
val emojiMapCustom = options.emojiMapCustom
|
|
|
|
|
val emojiMapProfile = options.emojiMapProfile
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2020-01-28 03:16:20 +01:00
|
|
|
|
val useEmojioneShortcode = when(val context = options.context) {
|
|
|
|
|
null -> false
|
|
|
|
|
else -> Pref.bpEmojioneShortcode(App1.getAppState(context).pref)
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-19 10:18:09 +01:00
|
|
|
|
splitShortCode(s, callback = object : ShortCodeSplitterCallback {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
override fun onString(part : String) {
|
2018-01-18 19:09:36 +01:00
|
|
|
|
builder.addUnicodeString(part)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
2019-06-03 22:50:18 +02:00
|
|
|
|
override fun onShortCode(prevCodePoint : Int, part : String, name : String) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-03-10 17:22:13 +01:00
|
|
|
|
// フレニコのプロフ絵文字
|
|
|
|
|
if(emojiMapProfile != null && name.length >= 2 && name[0] == '@') {
|
|
|
|
|
val emojiProfile = emojiMapProfile[name] ?: emojiMapProfile[name.substring(1)]
|
|
|
|
|
if(emojiProfile != null) {
|
|
|
|
|
val url = emojiProfile.url
|
|
|
|
|
if(url.isNotEmpty()) {
|
|
|
|
|
builder.addNetworkEmojiSpan(part, url)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-03-22 14:49:22 +01:00
|
|
|
|
|
2018-01-18 19:09:36 +01:00
|
|
|
|
// カスタム絵文字
|
|
|
|
|
val emojiCustom = emojiMapCustom?.get(name)
|
|
|
|
|
if(emojiCustom != null) {
|
|
|
|
|
val url = when {
|
|
|
|
|
Pref.bpDisableEmojiAnimation(App1.pref) && emojiCustom.static_url?.isNotEmpty() == true -> emojiCustom.static_url
|
|
|
|
|
else -> emojiCustom.url
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
2018-01-18 19:09:36 +01:00
|
|
|
|
builder.addNetworkEmojiSpan(part, url)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-03-10 17:22:13 +01:00
|
|
|
|
// 通常の絵文字
|
2020-01-28 03:16:20 +01:00
|
|
|
|
if( useEmojioneShortcode ){
|
|
|
|
|
val info =
|
|
|
|
|
EmojiMap.sShortNameToEmojiInfo[name.toLowerCase(Locale.JAPAN).replace('-', '_')]
|
|
|
|
|
if(info != null) {
|
|
|
|
|
builder.addImageSpan(part, info.er)
|
|
|
|
|
return
|
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-20 17:59:38 +01:00
|
|
|
|
when {
|
2018-01-21 17:47:13 +01:00
|
|
|
|
reHohoemi.matcher(name).find() -> builder.addImageSpan(
|
|
|
|
|
part,
|
|
|
|
|
R.drawable.emoji_hohoemi
|
|
|
|
|
)
|
|
|
|
|
reNicoru.matcher(name).find() -> builder.addImageSpan(
|
|
|
|
|
part,
|
|
|
|
|
R.drawable.emoji_nicoru
|
|
|
|
|
)
|
2018-01-20 17:59:38 +01:00
|
|
|
|
else -> builder.addUnicodeString(part)
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2018-01-18 19:09:36 +01:00
|
|
|
|
builder.closeNormalText()
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
2018-01-18 19:09:36 +01:00
|
|
|
|
return builder.sb
|
2018-01-04 19:52:25 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 投稿などの際、表示は不要だがショートコード=>Unicodeの解決を行いたい場合がある
|
|
|
|
|
// カスタム絵文字の変換も行わない
|
2018-03-22 14:49:22 +01:00
|
|
|
|
fun decodeShortCode(
|
|
|
|
|
s : String,
|
|
|
|
|
emojiMapCustom : HashMap<String, CustomEmoji>? = null
|
|
|
|
|
) : String {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
val sb = StringBuilder()
|
|
|
|
|
|
2019-12-19 10:18:09 +01:00
|
|
|
|
splitShortCode(s, callback = object : ShortCodeSplitterCallback {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
override fun onString(part : String) {
|
|
|
|
|
sb.append(part)
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-03 22:50:18 +02:00
|
|
|
|
override fun onShortCode(prevCodePoint : Int, part : String, name : String) {
|
2018-03-10 17:22:13 +01:00
|
|
|
|
|
|
|
|
|
// カスタム絵文字にマッチするなら変換しない
|
|
|
|
|
val emojiCustom = emojiMapCustom?.get(name)
|
|
|
|
|
if(emojiCustom != null) {
|
|
|
|
|
sb.append(part)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// カスタム絵文字ではなく通常の絵文字のショートコードなら絵文字に変換する
|
2019-12-19 10:18:09 +01:00
|
|
|
|
val info =
|
|
|
|
|
EmojiMap.sShortNameToEmojiInfo[name.toLowerCase(Locale.JAPAN).replace('-', '_')]
|
2018-01-04 19:52:25 +01:00
|
|
|
|
sb.append(info?.unified ?: part)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return sb.toString()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 入力補完用。絵文字ショートコード一覧を部分一致で絞り込む
|
2018-01-18 19:09:36 +01:00
|
|
|
|
internal fun searchShortCode(
|
|
|
|
|
context : Context,
|
|
|
|
|
prefix : String,
|
|
|
|
|
limit : Int
|
|
|
|
|
) : ArrayList<CharSequence> {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
val dst = ArrayList<CharSequence>()
|
2019-09-14 22:09:52 +02:00
|
|
|
|
for(shortCode in EmojiMap.sShortNameList) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
if(dst.size >= limit) break
|
|
|
|
|
if(! shortCode.contains(prefix)) continue
|
|
|
|
|
|
2019-09-14 22:09:52 +02:00
|
|
|
|
val info = EmojiMap.sShortNameToEmojiInfo[shortCode] ?: continue
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
|
|
val sb = SpannableStringBuilder()
|
|
|
|
|
val start = 0
|
2018-01-19 14:15:39 +01:00
|
|
|
|
sb.append(' ')
|
2018-01-04 19:52:25 +01:00
|
|
|
|
val end = sb.length
|
|
|
|
|
|
2018-01-18 19:09:36 +01:00
|
|
|
|
sb.setSpan(
|
2019-09-14 22:09:52 +02:00
|
|
|
|
info.er.createSpan(context),
|
2018-01-18 19:09:36 +01:00
|
|
|
|
start,
|
|
|
|
|
end,
|
|
|
|
|
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
|
|
|
)
|
2018-01-19 14:15:39 +01:00
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
sb.append(' ')
|
2018-01-19 14:15:39 +01:00
|
|
|
|
.append(':')
|
|
|
|
|
.append(shortCode)
|
|
|
|
|
.append(':')
|
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
dst.add(sb)
|
|
|
|
|
}
|
|
|
|
|
return dst
|
|
|
|
|
}
|
|
|
|
|
}
|