Add hangul composer and sebeolsik 390 layout

This commit is contained in:
Lee0701 2020-06-29 15:50:31 +09:00
parent 13c548c79f
commit eac79e9a7d
18 changed files with 881 additions and 1 deletions

View file

@ -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()
}

View file

@ -0,0 +1,200 @@
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) return event
if(Character.isWhitespace(event.mCodePoint)) {
val text = combiningStateFeedback
reset()
return createEventChainFromSequence(text, event)
}
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(jamo is HangulJamo.NonHangul) {
val text = combiningStateFeedback
reset()
return createEventChainFromSequence(text, event)
} else {
when(jamo) {
is HangulJamo.Initial -> {
if(currentSyllable.initial != null) {
composingWord.append(currentSyllable.string)
history.clear()
history += HangulSyllable(initial = jamo)
}
else {
history += currentSyllable.copy(initial = jamo)
}
}
is HangulJamo.Medial -> {
if(currentSyllable.medial != null) {
composingWord.append(currentSyllable.string)
history.clear()
history += HangulSyllable(medial = jamo)
} else {
history += currentSyllable.copy(medial = jamo)
}
}
is HangulJamo.Final -> {
if(currentSyllable.final != null) {
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) : 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 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 {
private fun createEventChainFromSequence(text: CharSequence, originalEvent: Event?): Event? {
var index = text.length
if (index <= 0) {
return originalEvent
}
var lastEvent: Event? = originalEvent
do {
val codePoint = Character.codePointBefore(text, index)
lastEvent = Event.Companion.createHardwareKeypressEvent(codePoint,
originalEvent!!.mKeyCode, lastEvent, false /* isKeyRepeat */)
index -= Character.charCount(codePoint)
} while (index > 0)
return lastEvent
}
}
}

View file

@ -0,0 +1,30 @@
package org.dslul.openboard.inputmethod.event
import android.view.KeyEvent
import org.dslul.openboard.inputmethod.latin.RichInputMethodSubtype
object HangulHardwareKeyDecoder {
@JvmStatic
fun decode(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
return Event.createHardwareKeypressEvent(codePoint, event.keyCode, null, event.repeatCount != 0)
}
val LAYOUT_DUBEOLSIK_STANDARD = mapOf<Int, Pair<Int, Int>>(
)
val LAYOUT_SEBEOLSIK_390 = mapOf<Int, Pair<Int, Int>>(
)
val LAYOUT_SEBEOLSIK_FINAL = mapOf<Int, Pair<Int, Int>>(
)
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
)
}

View file

@ -54,6 +54,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.HangulHardwareKeyDecoder;
import org.dslul.openboard.inputmethod.event.HardwareEventDecoder;
import org.dslul.openboard.inputmethod.event.HardwareKeyboardEventDecoder;
import org.dslul.openboard.inputmethod.event.InputTransaction;
@ -1755,7 +1756,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 = HangulHardwareKeyDecoder.decode(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(),

View file

@ -0,0 +1,42 @@
/*
* Copyright (C) 2012 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.latin.define;
public final class ProductionFlags {
private ProductionFlags() {
// This class is not publicly instantiable.
}
public static final boolean IS_HARDWARE_KEYBOARD_SUPPORTED = true;
/**
* Include all suggestions from all dictionaries in
* {@link org.dslul.openboard.inputmethod.latin.SuggestedWords#mRawSuggestions}.
*/
public static final boolean INCLUDE_RAW_SUGGESTIONS = false;
/**
* When false, the metrics logging is not yet ready to be enabled.
*/
public static final boolean IS_METRICS_LOGGING_SUPPORTED = false;
/**
* When {@code false}, the split keyboard is not yet ready to be enabled.
*/
public static final boolean IS_SPLIT_KEYBOARD_SUPPORTED = true;
}

View file

@ -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;
public static final String LANGUAGE_GEORGIAN = "ka";
@ -77,8 +78,10 @@ public class ScriptUtils {
mLanguageCodeToScriptCode.put("te", SCRIPT_TELUGU);
mLanguageCodeToScriptCode.put("th", SCRIPT_THAI);
mLanguageCodeToScriptCode.put("uk", SCRIPT_CYRILLIC);
mLanguageCodeToScriptCode.put("ko", SCRIPT_HANGUL);
NON_UPPERCASE_SCRIPTS.add(LANGUAGE_GEORGIAN);
NON_UPPERCASE_SCRIPTS.add("ko");
}
@ -187,6 +190,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: