mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-17 23:42:55 +00:00
Merge branch 'hangul' into hangul_update
todo: make it compile move dictionary to dict repo (wordlist?) test
This commit is contained in:
commit
0c8f1dc333
39 changed files with 2342 additions and 6 deletions
|
@ -107,6 +107,7 @@ class CombinerChain(initialText: String?) {
|
|||
mCombiners = ArrayList()
|
||||
// The dead key combiner is always active, and always first
|
||||
mCombiners.add(DeadKeyCombiner())
|
||||
mCombiners.add(HangulCombiner())
|
||||
mCombinedText = StringBuilder(initialText!!)
|
||||
mStateFeedback = SpannableStringBuilder()
|
||||
}
|
||||
|
|
|
@ -60,6 +60,9 @@ class Event private constructor(// The type of event - one of the constants abov
|
|||
val isConsumed: Boolean
|
||||
get() = 0 != FLAG_CONSUMED and mFlags
|
||||
|
||||
val isCombining: Boolean
|
||||
get() = 0 != FLAG_COMBINING and mFlags
|
||||
|
||||
val isGesture: Boolean
|
||||
get() = EVENT_TYPE_GESTURE == mEventType
|
||||
|
||||
|
@ -122,6 +125,8 @@ class Event private constructor(// The type of event - one of the constants abov
|
|||
private const val FLAG_REPEAT = 0x2
|
||||
// This event has already been consumed.
|
||||
private const val FLAG_CONSUMED = 0x4
|
||||
// This event is a combining character, usually a hangul input.
|
||||
private const val FLAG_COMBINING = 0x8
|
||||
|
||||
@JvmStatic
|
||||
fun createSoftwareKeypressEvent(codePoint: Int, keyCode: Int,
|
||||
|
@ -201,6 +206,13 @@ class Event private constructor(// The type of event - one of the constants abov
|
|||
null /* suggestedWordInfo */, FLAG_NONE, null /* next */)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun createSoftwareTextEvent(text: CharSequence?, keyCode: Int, next: Event): Event {
|
||||
return Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode,
|
||||
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
|
||||
null /* suggestedWordInfo */, FLAG_NONE, next)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an input event representing the manual pick of a punctuation suggestion.
|
||||
* @return an event for this suggestion pick.
|
||||
|
@ -238,6 +250,12 @@ class Event private constructor(// The type of event - one of the constants abov
|
|||
source.mNextEvent)
|
||||
}
|
||||
|
||||
fun createCombiningEvent(source: Event): Event {
|
||||
return Event(source.mEventType, source.mText, source.mCodePoint, source.mKeyCode,
|
||||
source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_COMBINING,
|
||||
source.mNextEvent)
|
||||
}
|
||||
|
||||
fun createNotHandledEvent(): Event {
|
||||
return Event(EVENT_TYPE_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
|
||||
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
package org.dslul.openboard.inputmethod.event
|
||||
|
||||
import org.dslul.openboard.inputmethod.latin.common.Constants
|
||||
import java.lang.StringBuilder
|
||||
import java.util.ArrayList
|
||||
|
||||
class HangulCombiner : Combiner {
|
||||
|
||||
val composingWord = StringBuilder()
|
||||
|
||||
val history: MutableList<HangulSyllable> = mutableListOf()
|
||||
val syllable: HangulSyllable? get() = history.lastOrNull()
|
||||
|
||||
override fun processEvent(previousEvents: ArrayList<Event>?, event: Event?): Event? {
|
||||
if(event == null || event.mKeyCode == Constants.CODE_SHIFT) return event
|
||||
if(Character.isWhitespace(event.mCodePoint)) {
|
||||
val text = combiningStateFeedback
|
||||
reset()
|
||||
return createEventChainFromSequence(text, event)
|
||||
} else if(event.isFunctionalKeyEvent) {
|
||||
if(event.mKeyCode == Constants.CODE_DELETE) {
|
||||
return when {
|
||||
history.size == 1 && composingWord.isEmpty() ||
|
||||
history.isEmpty() && composingWord.length == 1 -> {
|
||||
reset()
|
||||
Event.createHardwareKeypressEvent(0x20, Constants.CODE_SPACE, event, event.isKeyRepeat)
|
||||
}
|
||||
history.isNotEmpty() -> {
|
||||
history.removeAt(history.lastIndex)
|
||||
Event.createConsumedEvent(event)
|
||||
}
|
||||
composingWord.isNotEmpty() -> {
|
||||
composingWord.deleteCharAt(composingWord.lastIndex)
|
||||
Event.createConsumedEvent(event)
|
||||
}
|
||||
else -> event
|
||||
}
|
||||
}
|
||||
val text = combiningStateFeedback
|
||||
reset()
|
||||
return createEventChainFromSequence(text, event)
|
||||
} else {
|
||||
val currentSyllable = syllable ?: HangulSyllable()
|
||||
val jamo = HangulJamo.of(event.mCodePoint)
|
||||
if(!event.isCombining || jamo is HangulJamo.NonHangul) {
|
||||
composingWord.append(currentSyllable.string)
|
||||
composingWord.append(jamo.string)
|
||||
history.clear()
|
||||
} else {
|
||||
when(jamo) {
|
||||
is HangulJamo.Consonant -> {
|
||||
val initial = jamo.toInitial()
|
||||
val final = jamo.toFinal()
|
||||
if(currentSyllable.initial != null && currentSyllable.medial != null) {
|
||||
if(currentSyllable.final == null) {
|
||||
val combination = COMBINATION_TABLE_DUBEOLSIK[currentSyllable.initial.codePoint to (initial?.codePoint ?: -1)]
|
||||
if(combination != null) {
|
||||
history += currentSyllable.copy(initial = HangulJamo.Initial(combination))
|
||||
} else {
|
||||
if(final != null) history += currentSyllable.copy(final = final)
|
||||
else {
|
||||
composingWord.append(currentSyllable.string)
|
||||
history.clear()
|
||||
history += HangulSyllable(initial = initial)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val pair = currentSyllable.final.codePoint to (final?.codePoint ?: -1)
|
||||
val combination = COMBINATION_TABLE_DUBEOLSIK[pair]
|
||||
if(combination != null) {
|
||||
history += currentSyllable.copy(final = HangulJamo.Final(combination, combinationPair = pair))
|
||||
} else {
|
||||
composingWord.append(currentSyllable.string)
|
||||
history.clear()
|
||||
history += HangulSyllable(initial = initial)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
composingWord.append(currentSyllable.string)
|
||||
history.clear()
|
||||
history += HangulSyllable(initial = initial)
|
||||
}
|
||||
}
|
||||
is HangulJamo.Vowel -> {
|
||||
val medial = jamo.toMedial()
|
||||
if(currentSyllable.final == null) {
|
||||
if(currentSyllable.medial != null) {
|
||||
val combination = COMBINATION_TABLE_DUBEOLSIK[currentSyllable.medial.codePoint to (medial?.codePoint ?: -1)]
|
||||
if(combination != null) {
|
||||
history += currentSyllable.copy(medial = HangulJamo.Medial(combination))
|
||||
} else {
|
||||
composingWord.append(currentSyllable.string)
|
||||
history.clear()
|
||||
history += HangulSyllable(medial = medial)
|
||||
}
|
||||
} else {
|
||||
history += currentSyllable.copy(medial = medial)
|
||||
}
|
||||
} else if(currentSyllable.final.combinationPair != null) {
|
||||
val pair = currentSyllable.final.combinationPair
|
||||
|
||||
history.removeAt(history.lastIndex)
|
||||
val final = HangulJamo.Final(pair.first)
|
||||
history += currentSyllable.copy(final = final)
|
||||
composingWord.append(syllable?.string ?: "")
|
||||
history.clear()
|
||||
val initial = HangulJamo.Final(pair.second).toConsonant()?.toInitial()
|
||||
val newSyllable = HangulSyllable(initial = initial)
|
||||
history += newSyllable
|
||||
history += newSyllable.copy(medial = medial)
|
||||
} else {
|
||||
history.removeAt(history.lastIndex)
|
||||
composingWord.append(syllable?.string ?: "")
|
||||
history.clear()
|
||||
val initial = currentSyllable.final.toConsonant()?.toInitial()
|
||||
val newSyllable = HangulSyllable(initial = initial)
|
||||
history += newSyllable
|
||||
history += newSyllable.copy(medial = medial)
|
||||
}
|
||||
}
|
||||
is HangulJamo.Initial -> {
|
||||
if(currentSyllable.initial != null) {
|
||||
val combination = COMBINATION_TABLE_SEBEOLSIK[currentSyllable.initial.codePoint to jamo.codePoint]
|
||||
if(combination != null && currentSyllable.medial == null && currentSyllable.final == null) {
|
||||
history += currentSyllable.copy(initial = HangulJamo.Initial(combination))
|
||||
} else {
|
||||
composingWord.append(currentSyllable.string)
|
||||
history.clear()
|
||||
history += HangulSyllable(initial = jamo)
|
||||
}
|
||||
} else {
|
||||
history += currentSyllable.copy(initial = jamo)
|
||||
}
|
||||
}
|
||||
is HangulJamo.Medial -> {
|
||||
if(currentSyllable.medial != null) {
|
||||
val combination = COMBINATION_TABLE_SEBEOLSIK[currentSyllable.medial.codePoint to jamo.codePoint]
|
||||
if(combination != null) {
|
||||
history += currentSyllable.copy(medial = HangulJamo.Medial(combination))
|
||||
} else {
|
||||
composingWord.append(currentSyllable.string)
|
||||
history.clear()
|
||||
history += HangulSyllable(medial = jamo)
|
||||
}
|
||||
} else {
|
||||
history += currentSyllable.copy(medial = jamo)
|
||||
}
|
||||
}
|
||||
is HangulJamo.Final -> {
|
||||
if(currentSyllable.final != null) {
|
||||
val combination = COMBINATION_TABLE_SEBEOLSIK[currentSyllable.final.codePoint to jamo.codePoint]
|
||||
if(combination != null) {
|
||||
history += currentSyllable.copy(final = HangulJamo.Final(combination))
|
||||
} else {
|
||||
composingWord.append(currentSyllable.string)
|
||||
history.clear()
|
||||
history += HangulSyllable(final = jamo)
|
||||
}
|
||||
} else {
|
||||
history += currentSyllable.copy(final = jamo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Event.createConsumedEvent(event)
|
||||
}
|
||||
|
||||
override val combiningStateFeedback: CharSequence
|
||||
get() = composingWord.toString() + (syllable?.string ?: "")
|
||||
|
||||
override fun reset() {
|
||||
composingWord.setLength(0)
|
||||
history.clear()
|
||||
}
|
||||
|
||||
sealed class HangulJamo {
|
||||
abstract val codePoint: Int
|
||||
abstract val modern: Boolean
|
||||
val string: String get() = codePoint.toChar().toString()
|
||||
data class NonHangul(override val codePoint: Int) : HangulJamo() {
|
||||
override val modern: Boolean get() = false
|
||||
}
|
||||
data class Initial(override val codePoint: Int) : HangulJamo() {
|
||||
override val modern: Boolean get() = codePoint in 0x1100 .. 0x1112
|
||||
val ordinal: Int get() = codePoint - 0x1100
|
||||
fun toConsonant(): Consonant? {
|
||||
val codePoint = COMPAT_CONSONANTS.getOrNull(CONVERT_INITIALS.indexOf(codePoint.toChar())) ?: return null
|
||||
if(codePoint.toInt() == 0) return null
|
||||
return Consonant(codePoint.toInt())
|
||||
}
|
||||
}
|
||||
data class Medial(override val codePoint: Int) : HangulJamo() {
|
||||
override val modern: Boolean get() = codePoint in 1161 .. 0x1175
|
||||
val ordinal: Int get() = codePoint - 0x1161
|
||||
fun toVowel(): Vowel? {
|
||||
val codePoint = COMPAT_VOWELS.getOrNull(CONVERT_MEDIALS.indexOf(codePoint.toChar())) ?: return null
|
||||
return Vowel(codePoint.toInt())
|
||||
}
|
||||
}
|
||||
data class Final(override val codePoint: Int, val combinationPair: Pair<Int, Int>? = null) : HangulJamo() {
|
||||
override val modern: Boolean get() = codePoint in 0x11a8 .. 0x11c2
|
||||
val ordinal: Int get() = codePoint - 0x11a7
|
||||
fun toConsonant(): Consonant? {
|
||||
val codePoint = COMPAT_CONSONANTS.getOrNull(CONVERT_FINALS.indexOf(codePoint.toChar())) ?: return null
|
||||
if(codePoint.toInt() == 0) return null
|
||||
return Consonant(codePoint.toInt())
|
||||
}
|
||||
}
|
||||
data class Consonant(override val codePoint: Int) : HangulJamo() {
|
||||
override val modern: Boolean get() = codePoint in 0x3131 .. 0x314e
|
||||
val ordinal: Int get() = codePoint - 0x3131
|
||||
fun toInitial(): Initial? {
|
||||
val codePoint = CONVERT_INITIALS.getOrNull(COMPAT_CONSONANTS.indexOf(codePoint.toChar())) ?: return null
|
||||
if(codePoint.toInt() == 0) return null
|
||||
return Initial(codePoint.toInt())
|
||||
}
|
||||
fun toFinal(): Final? {
|
||||
val codePoint = CONVERT_FINALS.getOrNull(COMPAT_CONSONANTS.indexOf(codePoint.toChar())) ?: return null
|
||||
if(codePoint.toInt() == 0) return null
|
||||
return Final(codePoint.toInt())
|
||||
}
|
||||
}
|
||||
data class Vowel(override val codePoint: Int) : HangulJamo() {
|
||||
override val modern: Boolean get() = codePoint in 0x314f .. 0x3163
|
||||
val ordinal: Int get() = codePoint - 0x314f1
|
||||
fun toMedial(): Medial? {
|
||||
val codePoint = CONVERT_MEDIALS.getOrNull(COMPAT_VOWELS.indexOf(codePoint.toChar())) ?: return null
|
||||
if(codePoint.toInt() == 0) return null
|
||||
return Medial(codePoint.toInt())
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
const val COMPAT_CONSONANTS = "ㄱㄲㄳㄴㄵㄶㄷㄸㄹㄺㄻㄼㄽㄾㄿㅀㅁㅂㅃㅄㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ"
|
||||
const val COMPAT_VOWELS = "ㅏㅐㅑㅒㅓㅔㅕㅖㅗㅘㅙㅚㅛㅜㅝㅞㅟㅠㅡㅢㅣ"
|
||||
const val CONVERT_INITIALS = "ᄀᄁ\u0000ᄂ\u0000\u0000ᄃᄄᄅ\u0000\u0000\u0000\u0000\u0000\u0000\u0000ᄆᄇᄈ\u0000ᄉᄊᄋᄌᄍᄎᄏᄐᄑᄒ"
|
||||
const val CONVERT_MEDIALS = "ᅡᅢᅣᅤᅥᅦᅧᅨᅩᅪᅫᅬᅭᅮᅯᅰᅱᅲᅳᅴᅵ"
|
||||
const val CONVERT_FINALS = "ᆨᆩᆪᆫᆬᆭᆮ\u0000ᆯᆰᆱᆲᆳᆴᆵᆶᆷᆸ\u0000ᆹᆺᆻᆼᆽ\u0000ᆾᆿᇀᇁᇂ"
|
||||
fun of(codePoint: Int): HangulJamo {
|
||||
return when(codePoint) {
|
||||
in 0x3131 .. 0x314e -> Consonant(codePoint)
|
||||
in 0x314f .. 0x3163 -> Vowel(codePoint)
|
||||
in 0x1100 .. 0x115f -> Initial(codePoint)
|
||||
in 0x1160 .. 0x11a7 -> Medial(codePoint)
|
||||
in 0x11a8 .. 0x11ff -> Final(codePoint)
|
||||
else -> NonHangul(codePoint)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class HangulSyllable(
|
||||
val initial: HangulJamo.Initial? = null,
|
||||
val medial: HangulJamo.Medial? = null,
|
||||
val final: HangulJamo.Final? = null
|
||||
) {
|
||||
val combinable: Boolean get() = (initial?.modern ?: false) && (medial?.modern ?: false) && (final?.modern ?: true)
|
||||
val combined: String get() = (0xac00 + (initial?.ordinal ?: 0) * 21 * 28
|
||||
+ (medial?.ordinal ?: 0) * 28
|
||||
+ (final?.ordinal ?: 0)).toChar().toString()
|
||||
val uncombined: String get() = (initial?.string ?: "") + (medial?.string ?: "") + (final?.string ?: "")
|
||||
val uncombinedCompat: String get() = (initial?.toConsonant()?.string ?: "") +
|
||||
(medial?.toVowel()?.string ?: "") + (final?.toConsonant()?.string ?: "")
|
||||
val string: String get() = if(this.combinable) this.combined else this.uncombinedCompat
|
||||
}
|
||||
|
||||
companion object {
|
||||
val COMBINATION_TABLE_DUBEOLSIK = mapOf<Pair<Int, Int>, Int>(
|
||||
0x1169 to 0x1161 to 0x116a,
|
||||
0x1169 to 0x1162 to 0x116b,
|
||||
0x1169 to 0x1175 to 0x116c,
|
||||
0x116e to 0x1165 to 0x116f,
|
||||
0x116e to 0x1166 to 0x1170,
|
||||
0x116e to 0x1175 to 0x1171,
|
||||
0x1173 to 0x1175 to 0x1174,
|
||||
|
||||
0x11a8 to 0x11ba to 0x11aa,
|
||||
0x11ab to 0x11bd to 0x11ac,
|
||||
0x11ab to 0x11c2 to 0x11ad,
|
||||
0x11af to 0x11a8 to 0x11b0,
|
||||
0x11af to 0x11b7 to 0x11b1,
|
||||
0x11af to 0x11b8 to 0x11b2,
|
||||
0x11af to 0x11ba to 0x11b3,
|
||||
0x11af to 0x11c0 to 0x11b4,
|
||||
0x11af to 0x11c1 to 0x11b5,
|
||||
0x11af to 0x11c2 to 0x11b6,
|
||||
0x11b8 to 0x11ba to 0x11b9
|
||||
)
|
||||
val COMBINATION_TABLE_SEBEOLSIK = mapOf<Pair<Int, Int>, Int>(
|
||||
0x1100 to 0x1100 to 0x1101, // ㄲ
|
||||
0x1103 to 0x1103 to 0x1104, // ㄸ
|
||||
0x1107 to 0x1107 to 0x1108, // ㅃ
|
||||
0x1109 to 0x1109 to 0x110a, // ㅆ
|
||||
0x110c to 0x110c to 0x110d, // ㅉ
|
||||
|
||||
0x1169 to 0x1161 to 0x116a, // ㅘ
|
||||
0x1169 to 0x1162 to 0x116b, // ㅙ
|
||||
0x1169 to 0x1175 to 0x116c, // ㅚ
|
||||
0x116e to 0x1165 to 0x116f, // ㅝ
|
||||
0x116e to 0x1166 to 0x1170, // ㅞ
|
||||
0x116e to 0x1175 to 0x1171, // ㅟ
|
||||
0x1173 to 0x1175 to 0x1174, // ㅢ
|
||||
|
||||
0x11a8 to 0x11a8 to 0x11a9, // ㄲ
|
||||
0x11a8 to 0x11ba to 0x11aa, // ㄳ
|
||||
0x11ab to 0x11bd to 0x11ac, // ㄵ
|
||||
0x11ab to 0x11c2 to 0x11ad, // ㄶ
|
||||
0x11af to 0x11a8 to 0x11b0, // ㄺ
|
||||
0x11af to 0x11b7 to 0x11b1, // ㄻ
|
||||
0x11af to 0x11b8 to 0x11b2, // ㄼ
|
||||
0x11af to 0x11ba to 0x11b3, // ㄽ
|
||||
0x11af to 0x11c0 to 0x11b4, // ㄾ
|
||||
0x11af to 0x11c1 to 0x11b5, // ㄿ
|
||||
0x11af to 0x11c2 to 0x11b6, // ㅀ
|
||||
0x11b8 to 0x11ba to 0x11b9, // ㅄ
|
||||
0x11ba to 0x11ba to 0x11bb // ㅆ
|
||||
)
|
||||
private fun createEventChainFromSequence(text: CharSequence, originalEvent: Event): Event {
|
||||
return Event.createSoftwareTextEvent(text, Constants.CODE_OUTPUT_TEXT, originalEvent);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
package org.dslul.openboard.inputmethod.event
|
||||
|
||||
import android.view.KeyEvent
|
||||
import org.dslul.openboard.inputmethod.latin.RichInputMethodSubtype
|
||||
|
||||
import org.dslul.openboard.inputmethod.event.HangulCombiner.HangulJamo
|
||||
|
||||
object HangulEventDecoder {
|
||||
|
||||
@JvmStatic
|
||||
fun decodeHardwareKeyEvent(subtype: RichInputMethodSubtype, event: KeyEvent, defaultEvent: Event): Event {
|
||||
val layout = LAYOUTS[subtype.keyboardLayoutSetName] ?: return defaultEvent
|
||||
val codePoint = layout[event.keyCode]?.let { if(event.isShiftPressed) it.second else it.first } ?: return defaultEvent
|
||||
val hardwareEvent = Event.createHardwareKeypressEvent(codePoint, event.keyCode, null, event.repeatCount != 0)
|
||||
return decodeSoftwareKeyEvent(hardwareEvent)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun decodeSoftwareKeyEvent(event: Event): Event {
|
||||
if(event.isCombining) return event
|
||||
return if(HangulJamo.of(event.mCodePoint) is HangulJamo.NonHangul) event
|
||||
else Event.createCombiningEvent(event)
|
||||
}
|
||||
|
||||
val LAYOUT_DUBEOLSIK_STANDARD = mapOf<Int, Pair<Int, Int>>(
|
||||
45 to (0x3142 to 0x3143),
|
||||
51 to (0x3148 to 0x3149),
|
||||
33 to (0x3137 to 0x3138),
|
||||
46 to (0x3131 to 0x3132),
|
||||
48 to (0x3145 to 0x3146),
|
||||
53 to (0x315b to 0x315b),
|
||||
49 to (0x3155 to 0x3155),
|
||||
37 to (0x3151 to 0x3151),
|
||||
43 to (0x3150 to 0x3152),
|
||||
44 to (0x3154 to 0x3156),
|
||||
|
||||
29 to (0x3141 to 0x3141),
|
||||
47 to (0x3134 to 0x3134),
|
||||
32 to (0x3147 to 0x3147),
|
||||
34 to (0x3139 to 0x3139),
|
||||
35 to (0x314e to 0x314e),
|
||||
36 to (0x3157 to 0x3157),
|
||||
38 to (0x3153 to 0x3153),
|
||||
39 to (0x314f to 0x314f),
|
||||
40 to (0x3163 to 0x3163),
|
||||
|
||||
54 to (0x314b to 0x314b),
|
||||
52 to (0x314c to 0x314c),
|
||||
31 to (0x314a to 0x314a),
|
||||
50 to (0x314d to 0x314d),
|
||||
30 to (0x3160 to 0x3160),
|
||||
42 to (0x315c to 0x315c),
|
||||
41 to (0x3161 to 0x3161)
|
||||
)
|
||||
|
||||
val LAYOUT_SEBEOLSIK_390 = mapOf<Int, Pair<Int, Int>>(
|
||||
8 to (0x11c2 to 0x11bd),
|
||||
9 to (0x11bb to 0x0040),
|
||||
10 to (0x11b8 to 0x0023),
|
||||
11 to (0x116d to 0x0024),
|
||||
12 to (0x1172 to 0x0025),
|
||||
13 to (0x1163 to 0x005e),
|
||||
14 to (0x1168 to 0x0026),
|
||||
15 to (0x1174 to 0x002a),
|
||||
16 to (0x116e to 0x0028),
|
||||
7 to (0x110f to 0x0029),
|
||||
|
||||
45 to (0x11ba to 0x11c1),
|
||||
51 to (0x11af to 0x11c0),
|
||||
33 to (0x1167 to 0x11bf),
|
||||
46 to (0x1162 to 0x1164),
|
||||
48 to (0x1165 to 0x003b),
|
||||
53 to (0x1105 to 0x003c),
|
||||
49 to (0x1103 to 0x0037),
|
||||
37 to (0x1106 to 0x0038),
|
||||
43 to (0x110e to 0x0039),
|
||||
44 to (0x1111 to 0x003e),
|
||||
|
||||
29 to (0x11bc to 0x11ae),
|
||||
47 to (0x11ab to 0x11ad),
|
||||
32 to (0x1175 to 0x11b0),
|
||||
34 to (0x1161 to 0x11a9),
|
||||
35 to (0x1173 to 0x002f),
|
||||
36 to (0x1102 to 0x0027),
|
||||
38 to (0x110b to 0x0034),
|
||||
39 to (0x1100 to 0x0035),
|
||||
40 to (0x110c to 0x0036),
|
||||
74 to (0x1107 to 0x003a),
|
||||
75 to (0x1110 to 0x0022),
|
||||
|
||||
54 to (0x11b7 to 0x11be),
|
||||
52 to (0x11a8 to 0x11b9),
|
||||
31 to (0x1166 to 0x11b1),
|
||||
50 to (0x1169 to 0x11b6),
|
||||
30 to (0x116e to 0x0021),
|
||||
42 to (0x1109 to 0x0030),
|
||||
41 to (0x1112 to 0x0031),
|
||||
55 to (0x002c to 0x0032),
|
||||
56 to (0x002e to 0x0033),
|
||||
76 to (0x1169 to 0x003f)
|
||||
)
|
||||
|
||||
val LAYOUT_SEBEOLSIK_FINAL = mapOf<Int, Pair<Int, Int>>(
|
||||
68 to (0x002a to 0x203b),
|
||||
|
||||
8 to (0x11c2 to 0x11a9),
|
||||
9 to (0x11bb to 0x11b0),
|
||||
10 to (0x11b8 to 0x11bd),
|
||||
11 to (0x116d to 0x11b5),
|
||||
12 to (0x1172 to 0x11b4),
|
||||
13 to (0x1163 to 0x003d),
|
||||
14 to (0x1168 to 0x201c),
|
||||
15 to (0x1174 to 0x201d),
|
||||
16 to (0x116e to 0x0027),
|
||||
7 to (0x110f to 0x007e),
|
||||
69 to (0x0029 to 0x003b),
|
||||
70 to (0x003e to 0x002b),
|
||||
|
||||
45 to (0x11ba to 0x11c1),
|
||||
51 to (0x11af to 0x11c0),
|
||||
33 to (0x1167 to 0x11ac),
|
||||
46 to (0x1162 to 0x11b6),
|
||||
48 to (0x1165 to 0x11b3),
|
||||
53 to (0x1105 to 0x0035),
|
||||
49 to (0x1103 to 0x0036),
|
||||
37 to (0x1106 to 0x0037),
|
||||
43 to (0x110e to 0x0038),
|
||||
44 to (0x1111 to 0x0039),
|
||||
71 to (0x0028 to 0x0025),
|
||||
72 to (0x003c to 0x002f),
|
||||
73 to (0x003a to 0x005c),
|
||||
|
||||
29 to (0x11bc to 0x11ae),
|
||||
47 to (0x11ab to 0x11ad),
|
||||
32 to (0x1175 to 0x11b2),
|
||||
34 to (0x1161 to 0x11b1),
|
||||
35 to (0x1173 to 0x1164),
|
||||
36 to (0x1102 to 0x0030),
|
||||
38 to (0x110b to 0x0031),
|
||||
39 to (0x1100 to 0x0032),
|
||||
40 to (0x110c to 0x0033),
|
||||
74 to (0x1107 to 0x0034),
|
||||
75 to (0x1110 to 0x00b7),
|
||||
|
||||
54 to (0x11b7 to 0x11be),
|
||||
52 to (0x11a8 to 0x11b9),
|
||||
31 to (0x1166 to 0x11bf),
|
||||
50 to (0x1169 to 0x11aa),
|
||||
30 to (0x116e to 0x003f),
|
||||
42 to (0x1109 to 0x002d),
|
||||
41 to (0x1112 to 0x0022),
|
||||
55 to (0x002c to 0x002c),
|
||||
56 to (0x002e to 0x002e),
|
||||
76 to (0x1169 to 0x0021)
|
||||
)
|
||||
|
||||
val LAYOUTS = mapOf<String, Map<Int, Pair<Int, Int>>>(
|
||||
"korean" to LAYOUT_DUBEOLSIK_STANDARD,
|
||||
"korean_sebeolsik_390" to LAYOUT_SEBEOLSIK_390,
|
||||
"korean_sebeolsik_final" to LAYOUT_SEBEOLSIK_FINAL
|
||||
)
|
||||
|
||||
}
|
|
@ -19,6 +19,31 @@ package org.dslul.openboard.inputmethod.keyboard.internal;
|
|||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* !!!!! DO NOT EDIT THIS FILE !!!!!
|
||||
*
|
||||
* This file is generated by tools/make-keyboard-text. The base template file is
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.dslul.openboard.inputmethod.keyboard.internal;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* !!!!! DO NOT EDIT THIS FILE !!!!!
|
||||
*
|
||||
|
@ -2519,7 +2544,23 @@ public final class KeyboardTextsTable {
|
|||
/* keyspec_currency */ "\u20B9",
|
||||
};
|
||||
|
||||
/* Locale ky: Kyrgyz */
|
||||
/* Locale ko: Korean */
|
||||
private static final String[] TEXTS_ko = {
|
||||
/* morekeys_a ~ */
|
||||
null, null, null, null, null,
|
||||
/* ~ morekeys_i */
|
||||
// Label for "switch to alphabetic" key.
|
||||
// U+0995: "ᄀ" HANGUL LETTER KIYEOK
|
||||
// U+0996: "ㄴ" HANGUL LETTER NIEUN
|
||||
// U+0997: "ㄷ" HANGUL LETTER TIKEUT
|
||||
/* keylabel_to_alpha */ "\u3131\u3134\u3137",
|
||||
/* morekeys_n ~ */
|
||||
null, null, null, null, null,
|
||||
/* ~ single_quotes */
|
||||
// U+20B9: "₩" FULLWIDTH WON SIGN
|
||||
/* keyspec_currency */ "\uFFE6",
|
||||
};
|
||||
|
||||
private static final String[] TEXTS_ky = {
|
||||
/* morekeys_a ~ */
|
||||
null, null, null, null,
|
||||
|
@ -4300,6 +4341,7 @@ public final class KeyboardTextsTable {
|
|||
"kk" , TEXTS_kk, /* 15/129 Kazakh */
|
||||
"km" , TEXTS_km, /* 2/130 Khmer */
|
||||
"kn" , TEXTS_kn, /* 2/ 12 Kannada */
|
||||
"ko" , TEXTS_ko, /* 2/ 12 Korean */
|
||||
"ky" , TEXTS_ky, /* 10/ 92 Kyrgyz */
|
||||
"lo" , TEXTS_lo, /* 2/ 12 Lao */
|
||||
"lt" , TEXTS_lt, /* 18/ 22 Lithuanian */
|
||||
|
|
|
@ -24,6 +24,7 @@ import android.util.Log;
|
|||
import org.dslul.openboard.inputmethod.latin.utils.DictionaryInfoUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.Normalizer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Locale;
|
||||
|
@ -70,7 +71,12 @@ public final class DictionaryFactory {
|
|||
new ReadOnlyBinaryDictionary(f.mFilename, f.mOffset, f.mLength,
|
||||
false /* useFullEditDistance */, locale, Dictionary.TYPE_MAIN);
|
||||
if (readOnlyBinaryDictionary.isValidDictionary()) {
|
||||
dictList.add(readOnlyBinaryDictionary);
|
||||
if(locale.getLanguage().equals("ko")) {
|
||||
// Use KoreanDictionary for Korean locale
|
||||
dictList.add(new KoreanDictionary(readOnlyBinaryDictionary));
|
||||
} else {
|
||||
dictList.add(readOnlyBinaryDictionary);
|
||||
}
|
||||
} else {
|
||||
readOnlyBinaryDictionary.close();
|
||||
// Prevent this dictionary to do any further harm.
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package org.dslul.openboard.inputmethod.latin;
|
||||
|
||||
import org.dslul.openboard.inputmethod.event.HangulCombiner;
|
||||
import org.dslul.openboard.inputmethod.latin.common.ComposedData;
|
||||
import org.dslul.openboard.inputmethod.latin.settings.SettingsValuesForSuggestion;
|
||||
|
||||
import java.text.Normalizer;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/*
|
||||
* For Korean dictionary, there are too many cases of characters to store on dictionary, which makes it slow.
|
||||
* To solve that, Unicode normalization is used to decompose Hangul syllables into Hangul jamos.
|
||||
*/
|
||||
public class KoreanDictionary extends Dictionary {
|
||||
|
||||
private static final String COMPAT_JAMO = HangulCombiner.HangulJamo.COMPAT_CONSONANTS + HangulCombiner.HangulJamo.COMPAT_VOWELS;
|
||||
private static final String STANDARD_JAMO = HangulCombiner.HangulJamo.CONVERT_INITIALS + HangulCombiner.HangulJamo.CONVERT_MEDIALS;
|
||||
|
||||
private final Dictionary mDictionary;
|
||||
|
||||
public KoreanDictionary(Dictionary dictionary) {
|
||||
super(dictionary.mDictType, dictionary.mLocale);
|
||||
mDictionary = dictionary;
|
||||
}
|
||||
|
||||
private String processInput(String input) {
|
||||
String normalized = Normalizer.normalize(input, Normalizer.Form.NFD);
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (char c : normalized.toCharArray()) {
|
||||
int index = COMPAT_JAMO.indexOf(c);
|
||||
if (index == -1) result.append(c);
|
||||
else result.append(STANDARD_JAMO.charAt(index));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String processOutput(String output) {
|
||||
return Normalizer.normalize(output, Normalizer.Form.NFC);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayList<SuggestedWords.SuggestedWordInfo> getSuggestions(ComposedData composedData, NgramContext ngramContext, long proximityInfoHandle, SettingsValuesForSuggestion settingsValuesForSuggestion, int sessionId, float weightForLocale, float[] inOutWeightOfLangModelVsSpatialModel) {
|
||||
composedData = new ComposedData(composedData.mInputPointers, composedData.mIsBatchMode, processInput(composedData.mTypedWord));
|
||||
ArrayList<SuggestedWords.SuggestedWordInfo> suggestions = mDictionary.getSuggestions(composedData, ngramContext, proximityInfoHandle, settingsValuesForSuggestion, sessionId, weightForLocale, inOutWeightOfLangModelVsSpatialModel);
|
||||
ArrayList<SuggestedWords.SuggestedWordInfo> result = new ArrayList<>();
|
||||
for (SuggestedWords.SuggestedWordInfo info : suggestions) {
|
||||
result.add(new SuggestedWords.SuggestedWordInfo(processOutput(info.mWord), info.mPrevWordsContext,
|
||||
info.mScore, info.mKindAndFlags, info.mSourceDict, info.mIndexOfTouchPointOfSecondWord, info.mAutoCommitFirstWordConfidence));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInDictionary(String word) {
|
||||
return mDictionary.isInDictionary(processInput(word));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getFrequency(String word) {
|
||||
return mDictionary.getFrequency(processInput(word));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxFrequencyOfExactMatches(String word) {
|
||||
return mDictionary.getMaxFrequencyOfExactMatches(processInput(word));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean same(char[] word, int length, String typedWord) {
|
||||
word = processInput(new String(word)).toCharArray();
|
||||
typedWord = processInput(typedWord);
|
||||
return mDictionary.same(word, length, typedWord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
mDictionary.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinishInput() {
|
||||
mDictionary.onFinishInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return mDictionary.isInitialized();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldAutoCommit(SuggestedWords.SuggestedWordInfo candidate) {
|
||||
return mDictionary.shouldAutoCommit(candidate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserSpecific() {
|
||||
return mDictionary.isUserSpecific();
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ import org.dslul.openboard.inputmethod.compat.ViewOutlineProviderCompatUtils;
|
|||
import org.dslul.openboard.inputmethod.compat.ViewOutlineProviderCompatUtils.InsetsUpdater;
|
||||
import org.dslul.openboard.inputmethod.dictionarypack.DictionaryPackConstants;
|
||||
import org.dslul.openboard.inputmethod.event.Event;
|
||||
import org.dslul.openboard.inputmethod.event.HangulEventDecoder;
|
||||
import org.dslul.openboard.inputmethod.event.HardwareEventDecoder;
|
||||
import org.dslul.openboard.inputmethod.event.HardwareKeyboardEventDecoder;
|
||||
import org.dslul.openboard.inputmethod.event.InputTransaction;
|
||||
|
@ -1865,7 +1866,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen
|
|||
// If the event is not handled by LatinIME, we just pass it to the parent implementation.
|
||||
// If it's handled, we return true because we did handle it.
|
||||
if (event.isHandled()) {
|
||||
mInputLogic.onCodeInput(mSettings.getCurrent(), event,
|
||||
Event hangulDecodedEvent = HangulEventDecoder.decodeHardwareKeyEvent(mKeyboardSwitcher.getKeyboard().mId.mSubtype, keyEvent, event);
|
||||
mInputLogic.onCodeInput(mSettings.getCurrent(), hangulDecodedEvent,
|
||||
mKeyboardSwitcher.getKeyboardShiftMode(),
|
||||
// TODO: this is not necessarily correct for a hardware keyboard right now
|
||||
mKeyboardSwitcher.getCurrentKeyboardScriptId(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.dslul.openboard.inputmethod.latin.define
|
||||
|
||||
object ProductionFlags {
|
||||
const val IS_HARDWARE_KEYBOARD_SUPPORTED = false
|
||||
const val IS_HARDWARE_KEYBOARD_SUPPORTED = true
|
||||
|
||||
/**
|
||||
* Include all suggestions from all dictionaries in
|
||||
|
|
|
@ -34,6 +34,7 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import org.dslul.openboard.inputmethod.compat.SuggestionSpanUtils;
|
||||
import org.dslul.openboard.inputmethod.event.Event;
|
||||
import org.dslul.openboard.inputmethod.event.HangulEventDecoder;
|
||||
import org.dslul.openboard.inputmethod.event.InputTransaction;
|
||||
import org.dslul.openboard.inputmethod.keyboard.Keyboard;
|
||||
import org.dslul.openboard.inputmethod.keyboard.KeyboardSwitcher;
|
||||
|
@ -444,9 +445,10 @@ public final class InputLogic {
|
|||
public InputTransaction onCodeInput(final SettingsValues settingsValues,
|
||||
@NonNull final Event event, final int keyboardShiftMode,
|
||||
final int currentKeyboardScriptId, final LatinIME.UIHandler handler) {
|
||||
final Event hangulDecodedEvent = HangulEventDecoder.decodeSoftwareKeyEvent(event);
|
||||
mWordBeingCorrectedByCursor = null;
|
||||
mJustRevertedACommit = false;
|
||||
final Event processedEvent = mWordComposer.processEvent(event);
|
||||
final Event processedEvent = mWordComposer.processEvent(hangulDecodedEvent);
|
||||
final InputTransaction inputTransaction = new InputTransaction(settingsValues,
|
||||
processedEvent, SystemClock.uptimeMillis(), mSpaceState,
|
||||
getActualCapsMode(settingsValues, keyboardShiftMode));
|
||||
|
@ -737,6 +739,9 @@ public final class InputLogic {
|
|||
// line, so that does affect the contents of the editor.
|
||||
inputTransaction.setDidAffectContents();
|
||||
break;
|
||||
case Constants.CODE_OUTPUT_TEXT:
|
||||
mWordComposer.applyProcessedEvent(event);
|
||||
break;
|
||||
case Constants.CODE_START_ONE_HANDED_MODE:
|
||||
case Constants.CODE_STOP_ONE_HANDED_MODE:
|
||||
// Note: One-handed mode activation is being
|
||||
|
|
|
@ -49,6 +49,7 @@ public class ScriptUtils {
|
|||
public static final int SCRIPT_TELUGU = 16;
|
||||
public static final int SCRIPT_THAI = 17;
|
||||
public static final int SCRIPT_BULGARIAN = 18;
|
||||
public static final int SCRIPT_HANGUL = 19;
|
||||
|
||||
private static final TreeMap<String, Integer> mLanguageCodeToScriptCode;
|
||||
private final static ArraySet<Integer> UPPERCASE_SCRIPTS = new ArraySet<>();
|
||||
|
@ -75,6 +76,7 @@ public class ScriptUtils {
|
|||
mLanguageCodeToScriptCode.put("te", SCRIPT_TELUGU);
|
||||
mLanguageCodeToScriptCode.put("th", SCRIPT_THAI);
|
||||
mLanguageCodeToScriptCode.put("uk", SCRIPT_CYRILLIC);
|
||||
mLanguageCodeToScriptCode.put("ko", SCRIPT_HANGUL);
|
||||
|
||||
// only Latin, Cyrillic, Greek and Armenian have upper/lower case
|
||||
// https://unicode.org/faq/casemap_charprop.html#3
|
||||
|
@ -192,6 +194,13 @@ public class ScriptUtils {
|
|||
case SCRIPT_THAI:
|
||||
// Thai unicode block is U+0E00..U+0E7F
|
||||
return (codePoint >= 0xE00 && codePoint <= 0xE7F);
|
||||
case SCRIPT_HANGUL:
|
||||
return (codePoint >= 0xAC00 && codePoint <= 0xD7A3
|
||||
|| codePoint >= 0x3131 && codePoint <= 0x318E
|
||||
|| codePoint >= 0x1100 && codePoint <= 0x11FF
|
||||
|| codePoint >= 0xA960 && codePoint <= 0xA97C
|
||||
|| codePoint >= 0xD7B0 && codePoint <= 0xD7C6
|
||||
|| codePoint >= 0xD7CB && codePoint <= 0xD7FB);
|
||||
case SCRIPT_UNKNOWN:
|
||||
return true;
|
||||
default:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue