絵文字データをJavaコードではなくデータファイルから読む

This commit is contained in:
tateisu 2021-02-20 20:22:22 +09:00
parent 0f490f6a59
commit 7450b837e7
23 changed files with 18154 additions and 29327 deletions

View File

@ -120,6 +120,7 @@
<w>renotes</w>
<w>repeatly</w>
<w>samsung</w>
<w>scna</w>
<w>sephiroth</w>
<w>sharedpref</w>
<w>shortcode</w>

File diff suppressed because it is too large Load Diff

View File

@ -1,64 +0,0 @@
package jp.juggler.subwaytooter.emoji
import java.io.*
class JavaCodeWriter(file: File) : AutoCloseable {
companion object {
const val lineFeed = "\u000a"
}
private val writer = OutputStreamWriter(BufferedOutputStream(FileOutputStream(file)), Charsets.UTF_8)
private var linesInFunction = 0
var functionsCount = 0
override fun close() {
writer.flush()
writer.close()
}
fun print(x: String) {
writer.write(x, 0, x.length)
}
fun println(x: String) {
print(x)
print(lineFeed)
writer.flush()
}
fun addCode(code: String) {
// open new function
if (linesInFunction == 0) {
++functionsCount
println("\n\tprivate static void init$functionsCount(EmojiMap e){")
}
// write code
print("\t\t")
println(code)
// close function
if (++linesInFunction > 100) {
println("\t}")
linesInFunction = 0
}
}
fun closeFunction() {
if (linesInFunction > 0) {
println("\t}")
linesInFunction = 0
}
}
fun writeDefinition(s: String) {
println("\t$s")
println("")
}
}

View File

@ -749,19 +749,13 @@ class App {
}
log.w("nameChars: [${nameChars.sorted().joinToString("")}]")
// JSONコードを出力する
val outFile = "EmojiMapInitializer.java"
JavaCodeWriter(File(outFile)).use { jcw ->
jcw.println(
"""
package jp.juggler.emoji;
public final class EmojiMapInitializer {
""".trimIndent()
)
val outFile = "emoji_map.txt"
UnixPrinter(File(outFile)).use { writer ->
for (emoji in emojiMap.values.sortedBy { it.key }) {
val codeSet = emoji.codeSet
val codeSet = emoji.codeSet.sorted()
// asciiコードだけの絵文字は処理しない
if (codeSet.isEmpty()) {
log.w("skip emoji ${emoji.unified} ${emoji.resName} that has no valid codes")
@ -775,16 +769,15 @@ public final class EmojiMapInitializer {
// 画像リソースIDとUnicodeシーケンスの関連付けを出力する
val strResName = emoji.resName
if (File("assets/$strResName.svg").isFile) {
writer.println("s1:$strResName.svg//${emoji.imageFiles.first().second}")
} else {
writer.println("s1d:$strResName//${emoji.imageFiles.first().second}")
}
codeSet.forEach { code ->
val javaChars = code.makeUtf16()
if(javaChars.isEmpty()) error("too short code! ${emoji.resName}")
if (File("assets/$strResName.svg").isFile) {
jcw.addCode("e.code(\"$javaChars\", \"$strResName.svg\"); // ${code.from} ${emoji.imageFiles.first().second}")
} else {
jcw.addCode("e.code(\"$javaChars\", R.drawable.$strResName); // ${code.from} ${emoji.imageFiles.first().second}")
}
val raw = code.toRawString()
if(raw.isEmpty()) error("too short code! ${emoji.resName}")
writer.println("s2:$raw//${code.from}")
}
}
@ -798,39 +791,29 @@ public final class EmojiMapInitializer {
// 投稿時にshortcodeをユニコードに変換するため、shortcodeとUTF-16シーケンスの関連付けを出力する
for (name in emoji.shortNames.map { it.name }.toSet().sorted()) {
val froms = emoji.shortNames.filter { it.name == name }.map { it.cameFrom }.sorted()
val javaChars = unified.makeUtf16()
jcw.addCode("e.name(\"${name}\", \"$javaChars\"); // ${froms.joinToString(",")}")
writer.println("n:$name,${unified.toRawString()}//${froms.joinToString(",")}")
}
}
categoryNames.values.forEach { category ->
writer.println("c1:${category.enumId}")
category.eachEmoji { emoji ->
if (emoji.skip) return@eachEmoji
val shortName = emoji.shortNames.first()
jcw.addCode("e.category( EmojiMap.${category.enumId}, \"${shortName.name}\"); // ${shortName.cameFrom}")
writer.println("c2:${shortName.name}//${shortName.cameFrom}")
}
}
val enumId = "CATEGORY_OTHER"
writer.println("c1:${enumId}")
emojiMap.values
.filter { it.usedInCategory == null && !it.isToneVariation }
.sortedBy { it.shortNames.first() }
.forEach { emoji ->
if (emoji.skip) return@forEach
val enumId = "CATEGORY_OTHER"
val shortName = emoji.shortNames.first()
jcw.addCode("e.category( EmojiMap.$enumId, \"${shortName.name}\"); // ${shortName.cameFrom}")
writer.println("c2:${shortName.name}//${shortName.cameFrom}")
}
jcw.closeFunction()
jcw.println("\tstatic void initAll(EmojiMap e){")
jcw.println("\t\te.utf16MaxLength=$utf16_max_length;")
for (i in 1..jcw.functionsCount) {
jcw.println("\t\tinit$i(e);")
}
jcw.println("\t}")
jcw.println("}")
}
log.d("wrote $outFile")

View File

@ -423,6 +423,8 @@ class ActMain : AsyncActivity(), View.OnClickListener,
app_state = App1.getAppState(this)
pref = App1.pref
EmojiDecoder.handleUnicodeEmoji = Pref.bpInAppUnicodeEmoji(pref)
density = app_state.density
acct_pad_lr = (0.5f + 4f * density).toInt()

View File

@ -18,6 +18,7 @@ import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.engine.executor.GlideExecutor
import com.bumptech.glide.load.model.GlideUrl
import jp.juggler.emoji.EmojiMap
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.table.*
import jp.juggler.subwaytooter.util.CustomEmojiCache
@ -289,8 +290,11 @@ class App1 : Application() {
fun prepare(app_context : Context, caller : String) : AppState {
var state = appStateX
if(state != null) return state
log.d("initialize AppState. caller=$caller")
// initialize EmojiMap
EmojiMap.load(app_context)
// initialize Conscrypt
Security.insertProviderAt(

View File

@ -766,6 +766,7 @@ val appSettingRoot = AppSettingItem(null, SettingType.Section, R.string.app_sett
sw(Pref.bpEmojioneShortcode, R.string.emojione_shortcode_support) {
desc = R.string.emojione_shortcode_support_desc
}
sw(Pref.bpInAppUnicodeEmoji, R.string.in_app_unicode_emoji)
}
section(R.string.color) {

View File

@ -2630,7 +2630,7 @@ internal class ItemViewHolder(
if (code == null) {
EmojiPicker(activity, access_info, closeOnSelected = true) { name, instance, _, _, _ ->
val item = EmojiMap.sMap.shortNameToEmojiInfo[name]
val item = EmojiMap.shortNameToEmojiInfo[name]
val newCode = if (item == null || instance != null) {
":$name:"
} else {

View File

@ -445,6 +445,11 @@ object Pref {
false
)
val bpInAppUnicodeEmoji = BooleanPref(
"InAppUnicodeEmoji",
true
)
// int
val ipBackButtonAction = IntPref("back_button_action", 0)

View File

@ -387,7 +387,7 @@ fun SpannableStringBuilder.appendMisskeyReaction(
val end = this.length
this.setSpan(
EmojiMap.sMap.utf16ToEmojiResource[emojiUtf16] !!.createSpan(context),
EmojiMap.utf16ToEmojiResource[emojiUtf16] !!.createSpan(context),
start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)

View File

@ -326,7 +326,7 @@ internal class ViewHolderHeaderProfile(
if(whoDetail?.locked ?: who.locked) {
append(" ")
val info = EmojiMap.sMap.shortNameToEmojiInfo["lock"]
val info = EmojiMap.shortNameToEmojiInfo["lock"]
if(info != null) {
appendSpan("locked", info.er.createSpan(activity))
} else {
@ -336,7 +336,7 @@ internal class ViewHolderHeaderProfile(
if(who.bot) {
append(" ")
val info = EmojiMap.sMap.shortNameToEmojiInfo["robot_face"]
val info = EmojiMap.shortNameToEmojiInfo["robot_face"]
if(info != null) {
appendSpan("bot", info.er.createSpan(activity))
} else {
@ -346,7 +346,7 @@ internal class ViewHolderHeaderProfile(
if(who.suspended) {
append(" ")
val info = EmojiMap.sMap.shortNameToEmojiInfo["x"]
val info = EmojiMap.shortNameToEmojiInfo["x"]
if(info != null) {
appendSpan("suspended", info.er.createSpan(activity))
} else {

View File

@ -1324,7 +1324,7 @@ object Action_Toot {
if (code == null) {
if (!bSet) error("will not happen")
EmojiPicker(activity, access_info, closeOnSelected = true) { name, instance, _, _, _ ->
val item = EmojiMap.sMap.shortNameToEmojiInfo[name]
val item = EmojiMap.shortNameToEmojiInfo[name]
val newCode = if (item == null || instance != null) {
":$name:"
} else {

View File

@ -334,7 +334,7 @@ class EmojiPicker(
val tone = viewRoot.findViewById<View>(selected_tone).tag as SkinTone
for(suffix in tone.suffix_list) {
val new_name = name + suffix
val info = EmojiMap.sMap.shortNameToEmojiInfo[new_name]
val info = EmojiMap.shortNameToEmojiInfo[new_name]
if(info != null) return new_name
}
return name
@ -383,7 +383,7 @@ class EmojiPicker(
}
else -> ArrayList<EmojiItem>().apply {
EmojiMap.sMap.categoryMap.get(category_id)?.emoji_list?.forEach { name ->
EmojiMap.categoryMap.get(category_id)?.emoji_list?.forEach { name ->
add(EmojiItem(name, null))
}
}
@ -481,8 +481,10 @@ class EmojiPicker(
item.name
}
val info = EmojiMap.sMap.shortNameToEmojiInfo[name]
if(info != null) {
val info = EmojiMap.shortNameToEmojiInfo[name]
if(info==null){
log.w("missing emoji for $name")
}else {
val er = info.er
if(er.isSvg) {
Glide.with(activity)
@ -520,11 +522,11 @@ class EmojiPicker(
selected(name, item.instance, customEmoji = emoji_url_map[item.name])
} else {
// 普通の絵文字
var ei = EmojiMap.sMap.shortNameToEmojiInfo[name] ?: return
var ei = EmojiMap.shortNameToEmojiInfo[name] ?: return
if(page.hasSkinTone) {
val sv = applySkinTone(name)
val tmp = EmojiMap.sMap.shortNameToEmojiInfo[sv]
val tmp = EmojiMap.shortNameToEmojiInfo[sv]
if(tmp != null) {
ei = tmp
name = sv

View File

@ -32,6 +32,8 @@ object EmojiDecoder {
private const val cpZwsp = '\u200B'.toInt()
var handleUnicodeEmoji = true
fun customEmojiSeparator(pref: SharedPreferences) = if (Pref.bpCustomEmojiSeparatorZwsp(pref)) {
'\u200B'
} else {
@ -184,13 +186,20 @@ object EmojiDecoder {
}
fun addUnicodeString(s: String) {
if(!handleUnicodeEmoji){
openNormalText()
sb.append(s)
return
}
var i = 0
val end = s.length
// 絵文字ではない部分をコピーする
fun normalCopy(initialJ: Int): Boolean {
var j = initialJ
while (j < end && !EmojiMap.sMap.isStartChar(s[j])) {
while (j < end && !EmojiMap.isStartChar(s[j])) {
j += min(end - j, Character.charCount(s.codePointAt(j)))
}
if (j <= i) return false
@ -207,7 +216,7 @@ object EmojiDecoder {
if (normalCopy(i) && i >= end) break
// 絵文字コードを探索
val result = EmojiMap.sMap.utf16Trie.get(s,i,end)
val result = EmojiMap.utf16Trie.get(s,i,end)
if (result == null) {
// 見つからなかったら、通常テキストを1文字以上コピーする
normalCopy(i + min(end-i, Character.charCount(s.codePointAt(i))))
@ -268,7 +277,6 @@ object EmojiDecoder {
val urlList = ArrayList<IntRange>().apply {
val m = reUrl.matcher(s)
while (m.find()) {
log.d("urlList ${m.start()}..${m.end()}")
add(m.start()..m.end())
}
}
@ -374,7 +382,7 @@ object EmojiDecoder {
// 通常の絵文字
if (useEmojioneShortcode) {
val info =
EmojiMap.sMap.shortNameToEmojiInfo[name.toLowerCase(Locale.JAPAN).replace('-', '_')]
EmojiMap.shortNameToEmojiInfo[name.toLowerCase(Locale.JAPAN).replace('-', '_')]
if (info != null) {
builder.addImageSpan(part, info.er)
return
@ -426,7 +434,7 @@ object EmojiDecoder {
// カスタム絵文字ではなく通常の絵文字のショートコードなら絵文字に変換する
val info =
EmojiMap.sMap.shortNameToEmojiInfo[name.toLowerCase(Locale.JAPAN).replace('-', '_')]
EmojiMap.shortNameToEmojiInfo[name.toLowerCase(Locale.JAPAN).replace('-', '_')]
sb.append(info?.unified ?: part)
}
})
@ -441,11 +449,11 @@ object EmojiDecoder {
limit: Int
): ArrayList<CharSequence> {
val dst = ArrayList<CharSequence>()
for (shortCode in EmojiMap.sMap.shortNameList) {
for (shortCode in EmojiMap.shortNameList) {
if (dst.size >= limit) break
if (!shortCode.contains(prefix)) continue
val info = EmojiMap.sMap.shortNameToEmojiInfo[shortCode] ?: continue
val info = EmojiMap.shortNameToEmojiInfo[shortCode] ?: continue
val sb = SpannableStringBuilder()
val start = 0

View File

@ -1026,7 +1026,7 @@ class PostHelper(
bInstanceHasCustomEmoji: Boolean
): SpannableStringBuilder {
val item = EmojiMap.sMap.shortNameToEmojiInfo[name]
val item = EmojiMap.shortNameToEmojiInfo[name]
val separator = EmojiDecoder.customEmojiSeparator(pref)
if (item == null || instance != null) {
// カスタム絵文字は常にshortcode表現

View File

@ -1071,5 +1071,6 @@
<string name="visibility_limited">限定(サークル)</string>
<string name="visibility_mutual">相互</string>
<string name="server_has_no_support_of_visibility">公開範囲\'%1$s\'はこのサーバで使えません</string>
<string name="in_app_unicode_emoji">アプリ内蔵のUnicode絵文字を使う(アプリ再起動が必要)</string>
</resources>

View File

@ -1085,4 +1085,5 @@
<string name="visibility_mutual">Mutual</string>
<string name="server_has_no_support_of_visibility">Server has no support of visibility \'%1$s\'.</string>
<string name="show_emoji_picker_other_category">Show emoji picker other category</string>
<string name="in_app_unicode_emoji">use in-app Unicode Emoji (app restart required)</string>
</resources>

View File

@ -28,12 +28,6 @@ android {
}
dependencies {
testImplementation "junit:junit:$junit_version"
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.appcompat:appcompat:$appcompat_version"
}

File diff suppressed because it is too large Load Diff

View File

@ -1,140 +0,0 @@
package jp.juggler.emoji;
import android.util.SparseArray;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
public class EmojiMap {
public static final int CATEGORY_PEOPLE = 0;
public static final int CATEGORY_NATURE = 1;
public static final int CATEGORY_FOODS = 2;
public static final int CATEGORY_ACTIVITY = 3;
public static final int CATEGORY_PLACES = 4;
public static final int CATEGORY_OBJECTS = 5;
public static final int CATEGORY_SYMBOLS = 6;
public static final int CATEGORY_FLAGS = 7;
public static final int CATEGORY_OTHER = 8;
public static class Category {
public final int category_id; // this is String resource id;
public final ArrayList< String > emoji_list = new ArrayList<>();
public Category( int category_id ){
this.category_id = category_id;
}
}
public static class EmojiResource {
@DrawableRes
public final int drawableId;
@Nullable
public final String assetsName;
public EmojiResource( @DrawableRes int drawableId ){
this.drawableId = drawableId;
this.assetsName = null;
}
EmojiResource( @NonNull String assetsName ){
this.drawableId = 0;
this.assetsName = assetsName;
}
public boolean isSvg(){
return assetsName != null;
}
}
public static class EmojiInfo {
@NonNull public final String unified;
@NonNull public final EmojiResource er;
public EmojiInfo( @NonNull String unified, @NonNull EmojiResource er ){
this.unified = unified;
this.er = er;
}
}
// 表示に使う絵文字のユニコードシーケンスから画像リソースIDへのマップ
public int utf16MaxLength;
public final HashMap< String, EmojiResource> utf16ToEmojiResource = new HashMap<>();
public final EmojiTrie<EmojiResource> utf16Trie = new EmojiTrie<>();
// 表示と投稿に使う絵文字のショートコードから画像リソースIDとユニコードシーケンスへのマップ
public final HashMap< String, EmojiInfo> shortNameToEmojiInfo = new HashMap<>();
// 入力補完に使う絵文字のショートコードのソートされたリスト
public final ArrayList< String > shortNameList = new ArrayList<>();
// ピッカーに使うカテゴリのリスト
public final SparseArray<Category> categoryMap = new SparseArray<>();
// 素の数字とcopyright,registered, trademark は絵文字にしない
private boolean isIgnored( @NonNull String code ){
int c = code.charAt( 0 );
return code.length() == 1 && c <= 0xae || c == 0x2122;
}
// PNG
// private void code( @NonNull String code, @DrawableRes int resId ){
// if( isIgnored( code ) ) return;
// EmojiResource res = new EmojiResource( resId );
// utf16ToEmojiResource.put( code, res);
// utf16Trie.append(code,0,res);
// }
// Assets
void code(@NonNull String code, @NonNull String assetsName ){
if( isIgnored( code ) ) return;
EmojiResource res = new EmojiResource( assetsName );
utf16ToEmojiResource.put( code, res);
utf16Trie.append(code,0,res);
}
void name( @NonNull String name, @NonNull String unified ){
if( isIgnored( unified ) ) return;
EmojiResource er = utf16ToEmojiResource.get( unified );
if( er == null ) throw new IllegalStateException( "missing emoji for code " + unified );
shortNameToEmojiInfo.put( name, new EmojiInfo( unified, er ) );
shortNameList.add( name );
}
void category(@StringRes int string_id, @NonNull String name ){
EmojiMap.Category c = categoryMap.get( string_id );
if( c == null ){
c = new EmojiMap.Category( string_id );
categoryMap.put( string_id, c );
}
c.emoji_list.add( name );
}
private EmojiMap(){
EmojiMapInitializer.initAll(this);
Collections.sort( shortNameList );
}
public static final EmojiMap sMap = new EmojiMap();
//////////////////////////////////////////////////////
public boolean isStartChar(char c) {
return utf16Trie.hasNext(c);
}
}

View File

@ -0,0 +1,190 @@
package jp.juggler.emoji
import android.content.Context
import android.content.res.AssetManager
import android.util.Log
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.collection.SparseArrayCompat
import java.io.EOFException
import java.io.InputStream
import java.util.*
object EmojiMap {
const val CATEGORY_PEOPLE = 0
const val CATEGORY_NATURE = 1
const val CATEGORY_FOODS = 2
const val CATEGORY_ACTIVITY = 3
const val CATEGORY_PLACES = 4
const val CATEGORY_OBJECTS = 5
const val CATEGORY_SYMBOLS = 6
const val CATEGORY_FLAGS = 7
const val CATEGORY_OTHER = 8
class Category{
val emoji_list = ArrayList<String>()
}
class EmojiResource(
// SVGの場合はasset resourceの名前
val assetsName: String? = null,
// PNGの場合はdrawable id
@DrawableRes val drawableId: Int =0,
){
val isSvg: Boolean
get() = assetsName != null
}
class EmojiInfo(val unified: String, val er: EmojiResource)
// 表示に使う。絵文字のユニコードシーケンスから画像リソースIDへのマップ
val utf16ToEmojiResource = HashMap<String, EmojiResource>()
val utf16Trie = EmojiTrie<EmojiResource>()
// 表示と投稿に使う。絵文字のショートコードから画像リソースIDとユニコードシーケンスへのマップ
val shortNameToEmojiInfo = HashMap<String, EmojiInfo>()
// 入力補完に使う。絵文字のショートコードのソートされたリスト
val shortNameList = ArrayList<String>()
// ピッカーに使う。カテゴリのリスト
val categoryMap = SparseArrayCompat<Category>()
/////////////////////////////////////////////////////////////////
private fun readStream(assetManager:AssetManager, inStream:InputStream){
val categoryNameMap = HashMap<String,Category>().apply{
fun a(@StringRes id:Int,name:String){
val c = categoryMap.get(id)
?: Category().also{ categoryMap.put(id,it)}
put(name, c)
}
a(CATEGORY_PEOPLE,"CATEGORY_PEOPLE")
a(CATEGORY_NATURE,"CATEGORY_NATURE")
a(CATEGORY_FOODS,"CATEGORY_FOODS")
a(CATEGORY_ACTIVITY,"CATEGORY_ACTIVITY")
a(CATEGORY_PLACES,"CATEGORY_PLACES")
a(CATEGORY_OBJECTS,"CATEGORY_OBJECTS")
a(CATEGORY_SYMBOLS,"CATEGORY_SYMBOLS")
a(CATEGORY_FLAGS,"CATEGORY_FLAGS")
a(CATEGORY_OTHER,"CATEGORY_OTHER")
}
// 素の数字とcopyright,registered, trademark は絵文字にしない
fun isIgnored(code: String): Boolean {
val c = code[0].toInt()
return code.length == 1 && c <= 0xae || c == 0x2122
}
fun addCode(code: String, er:EmojiResource) {
if (isIgnored(code)) return
utf16ToEmojiResource[code] = er
utf16Trie.append(code, 0, er)
}
// private fun addCode(code: String, @DrawableRes resId:Int ) =
// addCode(code,EmojiResource(resId))
//
// private fun addCode(code: String, assetsName: String) =
// addCode(code,EmojiResource(assetsName))
fun addName(name: String, unified: String) {
if (isIgnored(unified)) return
val er = utf16ToEmojiResource[unified]
?: throw IllegalStateException("missing emoji for code $unified")
shortNameToEmojiInfo[name] = EmojiInfo(unified, er)
shortNameList.add(name)
}
fun addCategoryItem(name: String,category:Category) {
category.emoji_list.add(name)
}
val reComment="""\s*//.*""".toRegex()
val reLineHeader="""\A(\w+):""".toRegex()
val assetsSet = assetManager.list("")!!.toSet()
var lastEmoji : EmojiResource? = null
var lastCategory:Category? = null
var lno = 0
fun readEmojiDataLine(rawLine:String){
++lno
var line = rawLine.replace(reComment, "").trim()
val head = reLineHeader.find(line)?.groupValues?.elementAtOrNull(1)
?: error("missing line header. line=$lno $line")
line = line.substring(head.length + 1)
try {
when (head) {
"s1" -> {
if (!assetsSet.contains(line)) error("missing assets. line=$lno $line")
lastEmoji = EmojiResource(assetsName = line)
}
"s2" -> addCode(
line,
lastEmoji
?: error("missing lastEmoji. line=$lno $line")
)
"n" -> {
val cols = line.split(",", limit = 2)
if (cols.size != 2) error("invalid name,code. line=$lno $line")
addName(cols[0], cols[1])
}
"c1" -> {
lastCategory = categoryNameMap[line]
?: error("missing category name. line=$lno $line")
}
"c2" -> addCategoryItem(
line,
lastCategory
?: error("missing lastCategory. line=$lno $line")
)
}
}catch(ex:Throwable){
Log.e("SubwayTooter", "EmojiMap load error: lno=$lno line=$line",ex)
}
}
val lineFeed = 0x0a.toByte()
val buffer = ByteArray(4096)
var used = inStream.read(buffer,0,buffer.size)
if(used <=0) throw EOFException("unexpected EOF")
while(true) {
var lineStart = 0
while (lineStart < used) {
var i = lineStart
while (i < used && buffer[i] != lineFeed) ++i
if (i >= used) break
if (i > lineStart) {
val line = String(buffer, lineStart, i - lineStart, Charsets.UTF_8)
readEmojiDataLine(line)
}
lineStart = i + 1
}
buffer.copyInto(buffer, 0, lineStart, used)
used -= lineStart
val nRead = inStream.read(buffer, used, buffer.size - used)
if (nRead <= 0) {
if (used > 0) throw EOFException("unexpected EOF")
break
}
used += nRead
}
}
fun load(appContext: Context){
val assetManager = appContext.assets !!
assetManager.open("emoji_map.txt").use{
readStream(assetManager,it)
}
shortNameList.sort()
}
//////////////////////////////////////////////////////
fun isStartChar(c: Char): Boolean {
return utf16Trie.hasNext(c)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,10 @@ package jp.juggler.emoji
import androidx.collection.SparseArrayCompat
data class EmojiTrieResult<T>(val data: T, val endPos: Int)
class EmojiTrie<T> {
data class Result<T>(val data: T, val endPos: Int)
var data: T? = null
val map = SparseArrayCompat<EmojiTrie<T>>()
@ -21,13 +22,11 @@ class EmojiTrie<T> {
fun hasNext(c: Char) = map.containsKey(c.toInt())
fun get(src: String, offset: Int, end: Int): EmojiTrieResult<T>? {
fun get(src: String, offset: Int, end: Int): Result<T>? {
// 長い方を優先するので、先に子を調べる
if (offset < end)
map[src[offset].toInt()]?.get(src, offset + 1, end)
?.let { return it }
return this.data?.let { EmojiTrieResult(it, offset) }
return this.data?.let { Result(it, offset) }
}
}