mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-05-22 01:34:22 +00:00
Add hangul composer and sebeolsik 390 layout
This commit is contained in:
parent
13c548c79f
commit
eac79e9a7d
18 changed files with 881 additions and 1 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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
||||
}
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue