/* * Twidere - Twitter client for Android * * Copyright (C) 2012-2017 Mariotaku Lee * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.mariotaku.twidere.extension import android.graphics.drawable.Drawable import android.text.Spannable import android.text.Spanned import android.widget.TextView import org.mariotaku.commons.text.CodePointArray import org.mariotaku.commons.text.get import org.mariotaku.twidere.text.style.EmojiSpan import org.mariotaku.twidere.text.util.EmojiEditableFactory import org.mariotaku.twidere.text.util.EmojiSpannableFactory import org.mariotaku.twidere.util.ExternalThemeManager /** * Created by mariotaku on 2017/5/13. */ fun TextView.setupEmojiFactory() { if (isInEditMode) return setSpannableFactory(EmojiSpannableFactory(this)) setEditableFactory(EmojiEditableFactory(this)) } fun ExternalThemeManager.Emoji.applyTo(text: Spannable, textStart: Int = 0, textLength: Int = text.length) { if (!isSupported) return val array = CodePointArray(text) var arrayIdx = array.length() - 1 while (arrayIdx >= 0) { val codePoint = array[arrayIdx] if (isEmoji(codePoint)) { val arrayEnd = arrayIdx + 1 var arrayIdxOffset = 0 val textIdx = array.indexOfText(codePoint, arrayIdx) var textIdxOffset = 0 var skippedIndex = 0 if (textIdx == -1 || textIdx < textStart) { arrayIdx-- continue } val textEnd = textIdx + Character.charCount(codePoint) if (arrayIdx > 0) { val prevCodePoint = array[arrayIdx - 1] when { isRegionalIndicatorSymbol(codePoint) -> if (isRegionalIndicatorSymbol(prevCodePoint)) { arrayIdxOffset = -1 textIdxOffset = -Character.charCount(prevCodePoint) skippedIndex = -1 } isModifier(codePoint) -> if (isEmoji(prevCodePoint)) { arrayIdxOffset = -1 textIdxOffset = -Character.charCount(prevCodePoint) skippedIndex = -1 } isKeyCap(codePoint) -> if (isPhoneNumberSymbol(prevCodePoint)) { arrayIdxOffset = -1 textIdxOffset = -Character.charCount(prevCodePoint) skippedIndex = -1 } isZeroWidthJoin(prevCodePoint) -> { var notValidControlCount = 0 var charCount = 0 for (i in arrayIdx - 1 downTo 0) { val cp = array.get(i) charCount += Character.charCount(cp) if (isZeroWidthJoin(cp) || isVariationSelector(cp)) { // Ignore notValidControlCount = 0 continue } notValidControlCount++ if (notValidControlCount > 1 || i == 0) { arrayIdxOffset = i - arrayIdx + 1 textIdxOffset = -charCount + Character.charCount(cp) skippedIndex = i - arrayIdx + 1 break } } } } } if (textEnd > textStart + textLength) { arrayIdx-- continue } var spans = text.getSpans(textIdx + textIdxOffset, textEnd, EmojiSpan::class.java) if (spans.isEmpty()) { var drawable: Drawable? = getEmojiDrawableFor(*array[arrayIdx + arrayIdxOffset..arrayEnd]) if (drawable == null) { // Not emoji combination, just use fallback textIdxOffset = 0 arrayIdxOffset = 0 skippedIndex = 0 spans = text.getSpans(textIdx + textIdxOffset, textEnd, EmojiSpan::class.java) if (spans.isEmpty()) { drawable = getEmojiDrawableFor(*array[arrayIdx + arrayIdxOffset..arrayEnd]) } } if (drawable != null) { text.setSpan(EmojiSpan(drawable), textIdx + textIdxOffset, textEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } } arrayIdx += skippedIndex } arrayIdx-- } } private fun isVariationSelector(codePoint: Int): Boolean { return codePoint == 0xfe0f } private fun isZeroWidthJoin(codePoint: Int): Boolean { return codePoint == 0x200d } private fun isPhoneNumberSymbol(codePoint: Int): Boolean { return codePoint == 0x0023 || codePoint == 0x002a || codePoint in 0x0030..0x0039 } private fun isModifier(codePoint: Int): Boolean { return codePoint in 0x1f3fb..0x1f3ff } private fun isEmoji(codePoint: Int): Boolean { return !Character.isLetterOrDigit(codePoint) } private fun isRegionalIndicatorSymbol(codePoint: Int): Boolean { return codePoint in 0x1f1e6..0x1f1ff } private fun isKeyCap(codePoint: Int): Boolean { return codePoint == 0x20e3 }