some formatting

This commit is contained in:
Helium314 2023-09-15 16:36:25 +02:00
parent eee94f2924
commit a7a3465e9b
4 changed files with 168 additions and 168 deletions

View file

@ -17,12 +17,29 @@ import java.util.*
* feedback on the composing state and will typically be shown with different styling such as * feedback on the composing state and will typically be shown with different styling such as
* a colored background. * a colored background.
*/ */
class CombinerChain(initialText: String?) { class CombinerChain(initialText: String) {
// The already combined text, as described above // The already combined text, as described above
private val mCombinedText: StringBuilder private val mCombinedText = StringBuilder(initialText)
// The feedback on the composing state, as described above // The feedback on the composing state, as described above
private val mStateFeedback: SpannableStringBuilder private val mStateFeedback = SpannableStringBuilder()
private val mCombiners: ArrayList<Combiner> private val mCombiners = ArrayList<Combiner>()
/**
* Create an combiner chain.
*
* The combiner chain takes events as inputs and outputs code points and combining state.
* For example, if the input language is Japanese, the combining chain will typically perform
* kana conversion. This takes a string for initial text, taken to be present before the
* cursor: we'll start after this.
*
* @param initialText The text that has already been combined so far.
*/
init {
// The dead key combiner is always active, and always first
mCombiners.add(DeadKeyCombiner())
mCombiners.add(HangulCombiner())
}
fun reset() { fun reset() {
mCombinedText.setLength(0) mCombinedText.setLength(0)
mStateFeedback.clear() mStateFeedback.clear()
@ -45,15 +62,16 @@ class CombinerChain(initialText: String?) {
* @return the processed event. It may be the same event, or a consumed event, or a completely * @return the processed event. It may be the same event, or a consumed event, or a completely
* new event. However it may never be null. * new event. However it may never be null.
*/ */
fun processEvent(previousEvents: ArrayList<Event>?, fun processEvent(previousEvents: ArrayList<Event>?, newEvent: Event): Event {
newEvent: Event): Event {
val modifiablePreviousEvents = ArrayList(previousEvents!!) val modifiablePreviousEvents = ArrayList(previousEvents!!)
var event = newEvent var event = newEvent
for (combiner in mCombiners) { // A combiner can never return more than one event; it can return several for (combiner in mCombiners) {
// code points, but they should be encapsulated within one event. // A combiner can never return more than one event; it can return several
// code points, but they should be encapsulated within one event.
event = combiner.processEvent(modifiablePreviousEvents, event) event = combiner.processEvent(modifiablePreviousEvents, event)
if (event.isConsumed) { // If the event is consumed, then we don't pass it to subsequent combiners: if (event.isConsumed) {
// they should not see it at all. // If the event is consumed, then we don't pass it to subsequent combiners:
// they should not see it at all.
break break
} }
} }
@ -93,22 +111,4 @@ class CombinerChain(initialText: String?) {
return s.append(mStateFeedback) return s.append(mStateFeedback)
} }
/**
* Create an combiner chain.
*
* The combiner chain takes events as inputs and outputs code points and combining state.
* For example, if the input language is Japanese, the combining chain will typically perform
* kana conversion. This takes a string for initial text, taken to be present before the
* cursor: we'll start after this.
*
* @param initialText The text that has already been combined so far.
*/
init {
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

@ -17,62 +17,59 @@ import org.dslul.openboard.inputmethod.latin.common.StringUtils
* for example. * for example.
* The combiner should figure out what to do with this. * The combiner should figure out what to do with this.
*/ */
class Event private constructor(// The type of event - one of the constants above class Event private constructor(
private val mEventType: Int, // If applicable, this contains the string that should be input. // The type of event - one of the constants above
val mText: CharSequence?, // The code point associated with the event, if relevant. This is a unicode code point, and private val mEventType: Int,
// has nothing to do with other representations of the key. It is only relevant if this event // If applicable, this contains the string that should be input.
// is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point val mText: CharSequence?,
// associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when // The code point associated with the event, if relevant. This is a unicode code point, and
// it's not relevant. // has nothing to do with other representations of the key. It is only relevant if this event
val mCodePoint: Int, // The key code associated with the event, if relevant. This is relevant whenever this event // is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point
// has been triggered by a key press, but not for a gesture for example. This has conceptually // associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when
// no link to the code point, although keys that enter a straight code point may often set // it's not relevant.
// this to be equal to mCodePoint for convenience. If this is not a key, this must contain val mCodePoint: Int,
// NOT_A_KEY_CODE. // The key code associated with the event, if relevant. This is relevant whenever this event
// has been triggered by a key press, but not for a gesture for example. This has conceptually
// no link to the code point, although keys that enter a straight code point may often set
// this to be equal to mCodePoint for convenience. If this is not a key, this must contain
// NOT_A_KEY_CODE.
val mKeyCode: Int, val mKeyCode: Int,
// Coordinates of the touch event, if relevant. If useful, we may want to replace this with // Coordinates of the touch event, if relevant. If useful, we may want to replace this with
// a MotionEvent or something in the future. This is only relevant when the keypress is from // a MotionEvent or something in the future. This is only relevant when the keypress is from
// a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the // a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the
// future or some other awesome sauce. // future or some other awesome sauce.
val mX: Int, val mY: Int, val mX: Int, val mY: Int,
// If this is of type EVENT_TYPE_SUGGESTION_PICKED, this must not be null (and must be null in // If this is of type EVENT_TYPE_SUGGESTION_PICKED, this must not be null (and must be null in
// other cases). // other cases).
val mSuggestedWordInfo: SuggestedWordInfo?, val mSuggestedWordInfo: SuggestedWordInfo?,
// Some flags that can't go into the key code. It's a bit field of FLAG_* // Some flags that can't go into the key code. It's a bit field of FLAG_*
private val mFlags: Int, private val mFlags: Int,
// The next event, if any. Null if there is no next event yet. // The next event, if any. Null if there is no next event yet.
val mNextEvent: Event?// This logic may need to be refined in the future val mNextEvent: Event?
// This logic may need to be refined in the future
) { ) {
// Returns whether this is a function key like backspace, ctrl, settings... as opposed to keys // Returns whether this is a function key like backspace, ctrl, settings... as opposed to keys
// that result in input like letters or space. // that result in input like letters or space.
val isFunctionalKeyEvent: Boolean val isFunctionalKeyEvent: Boolean
get() =// This logic may need to be refined in the future get() = NOT_A_CODE_POINT == mCodePoint // This logic may need to be refined in the future
NOT_A_CODE_POINT == mCodePoint
// Returns whether this event is for a dead character. @see {@link #FLAG_DEAD} // Returns whether this event is for a dead character. @see {@link #FLAG_DEAD}
val isDead: Boolean val isDead: Boolean get() = 0 != FLAG_DEAD and mFlags
get() = 0 != FLAG_DEAD and mFlags
val isKeyRepeat: Boolean val isKeyRepeat: Boolean get() = 0 != FLAG_REPEAT and mFlags
get() = 0 != FLAG_REPEAT and mFlags
val isConsumed: Boolean val isConsumed: Boolean get() = 0 != FLAG_CONSUMED and mFlags
get() = 0 != FLAG_CONSUMED and mFlags
val isCombining: Boolean val isCombining: Boolean get() = 0 != FLAG_COMBINING and mFlags
get() = 0 != FLAG_COMBINING and mFlags
val isGesture: Boolean val isGesture: Boolean get() = EVENT_TYPE_GESTURE == mEventType
get() = EVENT_TYPE_GESTURE == mEventType
// Returns whether this is a fake key press from the suggestion strip. This happens with // Returns whether this is a fake key press from the suggestion strip. This happens with
// punctuation signs selected from the suggestion strip. // punctuation signs selected from the suggestion strip.
val isSuggestionStripPress: Boolean val isSuggestionStripPress: Boolean get() = EVENT_TYPE_SUGGESTION_PICKED == mEventType
get() = EVENT_TYPE_SUGGESTION_PICKED == mEventType
val isHandled: Boolean val isHandled: Boolean get() = EVENT_TYPE_NOT_HANDLED != mEventType
get() = EVENT_TYPE_NOT_HANDLED != mEventType
// A consumed event should input no text. // A consumed event should input no text.
val textToCommit: CharSequence? val textToCommit: CharSequence?
@ -90,20 +87,20 @@ class Event private constructor(// The type of event - one of the constants abov
companion object { companion object {
// Should the types below be represented by separate classes instead? It would be cleaner // Should the types below be represented by separate classes instead? It would be cleaner
// but probably a bit too much // but probably a bit too much
// An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard. // An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard.
const val EVENT_TYPE_NOT_HANDLED = 0 const val EVENT_TYPE_NOT_HANDLED = 0
// A key press that is part of input, for example pressing an alphabetic character on a // A key press that is part of input, for example pressing an alphabetic character on a
// hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later // hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later
// through combination. // through combination.
const val EVENT_TYPE_INPUT_KEYPRESS = 1 const val EVENT_TYPE_INPUT_KEYPRESS = 1
// A toggle event is triggered by a key that affects the previous character. An example would // A toggle event is triggered by a key that affects the previous character. An example would
// be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with // be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with
// repeated presses. // repeated presses.
const val EVENT_TYPE_TOGGLE = 2 const val EVENT_TYPE_TOGGLE = 2
// A mode event instructs the combiner to change modes. The canonical example would be the // A mode event instructs the combiner to change modes. The canonical example would be the
// hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard // hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard
// if handled at the combiner level. // if handled at the combiner level.
const val EVENT_TYPE_MODE_KEY = 3 const val EVENT_TYPE_MODE_KEY = 3
// An event corresponding to a gesture. // An event corresponding to a gesture.
const val EVENT_TYPE_GESTURE = 4 const val EVENT_TYPE_GESTURE = 4
@ -119,7 +116,7 @@ class Event private constructor(// The type of event - one of the constants abov
const val NOT_A_KEY_CODE = 0 const val NOT_A_KEY_CODE = 0
private const val FLAG_NONE = 0 private const val FLAG_NONE = 0
// This event is a dead character, usually input by a dead key. Examples include dead-acute // This event is a dead character, usually input by a dead key. Examples include dead-acute
// or dead-abovering. // or dead-abovering.
private const val FLAG_DEAD = 0x1 private const val FLAG_DEAD = 0x1
// This event is coming from a key repeat, software or hardware. // This event is coming from a key repeat, software or hardware.
private const val FLAG_REPEAT = 0x2 private const val FLAG_REPEAT = 0x2
@ -197,22 +194,20 @@ class Event private constructor(// The type of event - one of the constants abov
* or combination that outputs a string. * or combination that outputs a string.
* @param text the CharSequence associated with this event. * @param text the CharSequence associated with this event.
* @param keyCode the key code, or NOT_A_KEYCODE if not applicable. * @param keyCode the key code, or NOT_A_KEYCODE if not applicable.
* @param next the next event, or null if not applicable.
* @return an event for this text. * @return an event for this text.
*/ */
@JvmStatic @JvmStatic
fun createSoftwareTextEvent(text: CharSequence?, keyCode: Int): Event { 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, 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, return Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode,
Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
null /* suggestedWordInfo */, FLAG_NONE, next) null /* suggestedWordInfo */, FLAG_NONE, next)
} }
@JvmStatic
fun createSoftwareTextEvent(text: CharSequence?, keyCode: Int) =
createSoftwareTextEvent(text, keyCode, null)
/** /**
* Creates an input event representing the manual pick of a punctuation suggestion. * Creates an input event representing the manual pick of a punctuation suggestion.
* @return an event for this suggestion pick. * @return an event for this suggestion pick.
@ -220,7 +215,7 @@ class Event private constructor(// The type of event - one of the constants abov
@JvmStatic @JvmStatic
fun createPunctuationSuggestionPickedEvent( fun createPunctuationSuggestionPickedEvent(
suggestedWordInfo: SuggestedWordInfo): Event { suggestedWordInfo: SuggestedWordInfo): Event {
val primaryCode = suggestedWordInfo.mWord[0].toInt() val primaryCode = suggestedWordInfo.mWord[0].code
return Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, primaryCode, return Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, primaryCode,
NOT_A_KEY_CODE, Constants.SUGGESTION_STRIP_COORDINATE, NOT_A_KEY_CODE, Constants.SUGGESTION_STRIP_COORDINATE,
Constants.SUGGESTION_STRIP_COORDINATE, suggestedWordInfo, FLAG_NONE, Constants.SUGGESTION_STRIP_COORDINATE, suggestedWordInfo, FLAG_NONE,
@ -244,8 +239,8 @@ class Event private constructor(// The type of event - one of the constants abov
* @param source the event to copy the properties of. * @param source the event to copy the properties of.
* @return an identical event marked as consumed. * @return an identical event marked as consumed.
*/ */
fun createConsumedEvent(source: Event?): Event { // A consumed event should not input any text at all, so we pass the empty string as text. fun createConsumedEvent(source: Event): Event { // A consumed event should not input any text at all, so we pass the empty string as text.
return Event(source!!.mEventType, source.mText, source.mCodePoint, source.mKeyCode, return Event(source.mEventType, source.mText, source.mCodePoint, source.mKeyCode,
source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_CONSUMED, source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags or FLAG_CONSUMED,
source.mNextEvent) source.mNextEvent)
} }
@ -266,7 +261,7 @@ class Event private constructor(// The type of event - one of the constants abov
// This method is private - to create a new event, use one of the create* utility methods. // This method is private - to create a new event, use one of the create* utility methods.
init { init {
// Sanity checks // Sanity checks
// mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED // mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED
if (EVENT_TYPE_SUGGESTION_PICKED == mEventType) { if (EVENT_TYPE_SUGGESTION_PICKED == mEventType) {
if (null == mSuggestedWordInfo) { if (null == mSuggestedWordInfo) {
throw RuntimeException("Wrong event: SUGGESTION_PICKED event must have a " throw RuntimeException("Wrong event: SUGGESTION_PICKED event must have a "

View file

@ -6,22 +6,21 @@ import java.util.ArrayList
class HangulCombiner : Combiner { class HangulCombiner : Combiner {
val composingWord = StringBuilder() private val composingWord = StringBuilder()
val history: MutableList<HangulSyllable> = mutableListOf() val history: MutableList<HangulSyllable> = mutableListOf()
val syllable: HangulSyllable? get() = history.lastOrNull() private val syllable: HangulSyllable? get() = history.lastOrNull()
override fun processEvent(previousEvents: ArrayList<Event>?, event: Event): Event { override fun processEvent(previousEvents: ArrayList<Event>?, event: Event): Event {
if(event.mKeyCode == Constants.CODE_SHIFT) return event if (event.mKeyCode == Constants.CODE_SHIFT) return event
if(Character.isWhitespace(event.mCodePoint)) { if (Character.isWhitespace(event.mCodePoint)) {
val text = combiningStateFeedback val text = combiningStateFeedback
reset() reset()
return createEventChainFromSequence(text, event) return createEventChainFromSequence(text, event)
} else if(event.isFunctionalKeyEvent) { } else if (event.isFunctionalKeyEvent) {
if(event.mKeyCode == Constants.CODE_DELETE) { if(event.mKeyCode == Constants.CODE_DELETE) {
return when { return when {
history.size == 1 && composingWord.isEmpty() || history.size == 1 && composingWord.isEmpty() || history.isEmpty() && composingWord.length == 1 -> {
history.isEmpty() && composingWord.length == 1 -> {
reset() reset()
Event.createHardwareKeypressEvent(0x20, Constants.CODE_SPACE, event, event.isKeyRepeat) Event.createHardwareKeypressEvent(0x20, Constants.CODE_SPACE, event, event.isKeyRepeat)
} }
@ -42,37 +41,39 @@ class HangulCombiner : Combiner {
} else { } else {
val currentSyllable = syllable ?: HangulSyllable() val currentSyllable = syllable ?: HangulSyllable()
val jamo = HangulJamo.of(event.mCodePoint) val jamo = HangulJamo.of(event.mCodePoint)
if(!event.isCombining || jamo is HangulJamo.NonHangul) { if (!event.isCombining || jamo is HangulJamo.NonHangul) {
composingWord.append(currentSyllable.string) composingWord.append(currentSyllable.string)
composingWord.append(jamo.string) composingWord.append(jamo.string)
history.clear() history.clear()
} else { } else {
when(jamo) { when (jamo) {
is HangulJamo.Consonant -> { is HangulJamo.Consonant -> {
val initial = jamo.toInitial() val initial = jamo.toInitial()
val final = jamo.toFinal() val final = jamo.toFinal()
if(currentSyllable.initial != null && currentSyllable.medial != null) { if (currentSyllable.initial != null && currentSyllable.medial != null) {
if(currentSyllable.final == null) { if (currentSyllable.final == null) {
val combination = COMBINATION_TABLE_DUBEOLSIK[currentSyllable.initial.codePoint to (initial?.codePoint ?: -1)] val combination = COMBINATION_TABLE_DUBEOLSIK[currentSyllable.initial.codePoint to (initial?.codePoint ?: -1)]
if(combination != null) { history +=
history += currentSyllable.copy(initial = HangulJamo.Initial(combination)) if (combination != null) {
currentSyllable.copy(initial = HangulJamo.Initial(combination))
} else {
if (final != null) {
currentSyllable.copy(final = final)
} else { } else {
if(final != null) history += currentSyllable.copy(final = final)
else {
composingWord.append(currentSyllable.string) composingWord.append(currentSyllable.string)
history.clear() history.clear()
history += HangulSyllable(initial = initial) HangulSyllable(initial = initial)
} }
} }
} else { } else {
val pair = currentSyllable.final.codePoint to (final?.codePoint ?: -1) val pair = currentSyllable.final.codePoint to (final?.codePoint ?: -1)
val combination = COMBINATION_TABLE_DUBEOLSIK[pair] val combination = COMBINATION_TABLE_DUBEOLSIK[pair]
if(combination != null) { history += if (combination != null) {
history += currentSyllable.copy(final = HangulJamo.Final(combination, combinationPair = pair)) currentSyllable.copy(final = HangulJamo.Final(combination, combinationPair = pair))
} else { } else {
composingWord.append(currentSyllable.string) composingWord.append(currentSyllable.string)
history.clear() history.clear()
history += HangulSyllable(initial = initial) HangulSyllable(initial = initial)
} }
} }
} else { } else {
@ -83,20 +84,21 @@ class HangulCombiner : Combiner {
} }
is HangulJamo.Vowel -> { is HangulJamo.Vowel -> {
val medial = jamo.toMedial() val medial = jamo.toMedial()
if(currentSyllable.final == null) { if (currentSyllable.final == null) {
if(currentSyllable.medial != null) { history +=
if (currentSyllable.medial != null) {
val combination = COMBINATION_TABLE_DUBEOLSIK[currentSyllable.medial.codePoint to (medial?.codePoint ?: -1)] val combination = COMBINATION_TABLE_DUBEOLSIK[currentSyllable.medial.codePoint to (medial?.codePoint ?: -1)]
if(combination != null) { if (combination != null) {
history += currentSyllable.copy(medial = HangulJamo.Medial(combination)) currentSyllable.copy(medial = HangulJamo.Medial(combination))
} else { } else {
composingWord.append(currentSyllable.string) composingWord.append(currentSyllable.string)
history.clear() history.clear()
history += HangulSyllable(medial = medial) HangulSyllable(medial = medial)
} }
} else { } else {
history += currentSyllable.copy(medial = medial) currentSyllable.copy(medial = medial)
} }
} else if(currentSyllable.final.combinationPair != null) { } else if (currentSyllable.final.combinationPair != null) {
val pair = currentSyllable.final.combinationPair val pair = currentSyllable.final.combinationPair
history.removeAt(history.lastIndex) history.removeAt(history.lastIndex)
@ -119,45 +121,48 @@ class HangulCombiner : Combiner {
} }
} }
is HangulJamo.Initial -> { is HangulJamo.Initial -> {
if(currentSyllable.initial != null) { history +=
if (currentSyllable.initial != null) {
val combination = COMBINATION_TABLE_SEBEOLSIK[currentSyllable.initial.codePoint to jamo.codePoint] val combination = COMBINATION_TABLE_SEBEOLSIK[currentSyllable.initial.codePoint to jamo.codePoint]
if(combination != null && currentSyllable.medial == null && currentSyllable.final == null) { if (combination != null && currentSyllable.medial == null && currentSyllable.final == null) {
history += currentSyllable.copy(initial = HangulJamo.Initial(combination)) currentSyllable.copy(initial = HangulJamo.Initial(combination))
} else { } else {
composingWord.append(currentSyllable.string) composingWord.append(currentSyllable.string)
history.clear() history.clear()
history += HangulSyllable(initial = jamo) HangulSyllable(initial = jamo)
} }
} else { } else {
history += currentSyllable.copy(initial = jamo) currentSyllable.copy(initial = jamo)
} }
} }
is HangulJamo.Medial -> { is HangulJamo.Medial -> {
if(currentSyllable.medial != null) { history +=
if (currentSyllable.medial != null) {
val combination = COMBINATION_TABLE_SEBEOLSIK[currentSyllable.medial.codePoint to jamo.codePoint] val combination = COMBINATION_TABLE_SEBEOLSIK[currentSyllable.medial.codePoint to jamo.codePoint]
if(combination != null) { if (combination != null) {
history += currentSyllable.copy(medial = HangulJamo.Medial(combination)) currentSyllable.copy(medial = HangulJamo.Medial(combination))
} else { } else {
composingWord.append(currentSyllable.string) composingWord.append(currentSyllable.string)
history.clear() history.clear()
history += HangulSyllable(medial = jamo) HangulSyllable(medial = jamo)
} }
} else { } else {
history += currentSyllable.copy(medial = jamo) currentSyllable.copy(medial = jamo)
} }
} }
is HangulJamo.Final -> { is HangulJamo.Final -> {
if(currentSyllable.final != null) { history +=
if (currentSyllable.final != null) {
val combination = COMBINATION_TABLE_SEBEOLSIK[currentSyllable.final.codePoint to jamo.codePoint] val combination = COMBINATION_TABLE_SEBEOLSIK[currentSyllable.final.codePoint to jamo.codePoint]
if(combination != null) { if (combination != null) {
history += currentSyllable.copy(final = HangulJamo.Final(combination)) currentSyllable.copy(final = HangulJamo.Final(combination))
} else { } else {
composingWord.append(currentSyllable.string) composingWord.append(currentSyllable.string)
history.clear() history.clear()
history += HangulSyllable(final = jamo) HangulSyllable(final = jamo)
} }
} else { } else {
history += currentSyllable.copy(final = jamo) currentSyllable.copy(final = jamo)
} }
} }
// compiler bug? when it's not added, compiler complains that it's missing // compiler bug? when it's not added, compiler complains that it's missing
@ -190,8 +195,8 @@ class HangulCombiner : Combiner {
val ordinal: Int get() = codePoint - 0x1100 val ordinal: Int get() = codePoint - 0x1100
fun toConsonant(): Consonant? { fun toConsonant(): Consonant? {
val codePoint = COMPAT_CONSONANTS.getOrNull(CONVERT_INITIALS.indexOf(codePoint.toChar())) ?: return null val codePoint = COMPAT_CONSONANTS.getOrNull(CONVERT_INITIALS.indexOf(codePoint.toChar())) ?: return null
if(codePoint.toInt() == 0) return null if(codePoint.code == 0) return null
return Consonant(codePoint.toInt()) return Consonant(codePoint.code)
} }
} }
data class Medial(override val codePoint: Int) : HangulJamo() { data class Medial(override val codePoint: Int) : HangulJamo() {
@ -199,7 +204,7 @@ class HangulCombiner : Combiner {
val ordinal: Int get() = codePoint - 0x1161 val ordinal: Int get() = codePoint - 0x1161
fun toVowel(): Vowel? { fun toVowel(): Vowel? {
val codePoint = COMPAT_VOWELS.getOrNull(CONVERT_MEDIALS.indexOf(codePoint.toChar())) ?: return null val codePoint = COMPAT_VOWELS.getOrNull(CONVERT_MEDIALS.indexOf(codePoint.toChar())) ?: return null
return Vowel(codePoint.toInt()) return Vowel(codePoint.code)
} }
} }
data class Final(override val codePoint: Int, val combinationPair: Pair<Int, Int>? = null) : HangulJamo() { data class Final(override val codePoint: Int, val combinationPair: Pair<Int, Int>? = null) : HangulJamo() {
@ -207,8 +212,8 @@ class HangulCombiner : Combiner {
val ordinal: Int get() = codePoint - 0x11a7 val ordinal: Int get() = codePoint - 0x11a7
fun toConsonant(): Consonant? { fun toConsonant(): Consonant? {
val codePoint = COMPAT_CONSONANTS.getOrNull(CONVERT_FINALS.indexOf(codePoint.toChar())) ?: return null val codePoint = COMPAT_CONSONANTS.getOrNull(CONVERT_FINALS.indexOf(codePoint.toChar())) ?: return null
if(codePoint.toInt() == 0) return null if(codePoint.code == 0) return null
return Consonant(codePoint.toInt()) return Consonant(codePoint.code)
} }
} }
data class Consonant(override val codePoint: Int) : HangulJamo() { data class Consonant(override val codePoint: Int) : HangulJamo() {
@ -216,13 +221,13 @@ class HangulCombiner : Combiner {
val ordinal: Int get() = codePoint - 0x3131 val ordinal: Int get() = codePoint - 0x3131
fun toInitial(): Initial? { fun toInitial(): Initial? {
val codePoint = CONVERT_INITIALS.getOrNull(COMPAT_CONSONANTS.indexOf(codePoint.toChar())) ?: return null val codePoint = CONVERT_INITIALS.getOrNull(COMPAT_CONSONANTS.indexOf(codePoint.toChar())) ?: return null
if(codePoint.toInt() == 0) return null if(codePoint.code == 0) return null
return Initial(codePoint.toInt()) return Initial(codePoint.code)
} }
fun toFinal(): Final? { fun toFinal(): Final? {
val codePoint = CONVERT_FINALS.getOrNull(COMPAT_CONSONANTS.indexOf(codePoint.toChar())) ?: return null val codePoint = CONVERT_FINALS.getOrNull(COMPAT_CONSONANTS.indexOf(codePoint.toChar())) ?: return null
if(codePoint.toInt() == 0) return null if(codePoint.code == 0) return null
return Final(codePoint.toInt()) return Final(codePoint.code)
} }
} }
data class Vowel(override val codePoint: Int) : HangulJamo() { data class Vowel(override val codePoint: Int) : HangulJamo() {
@ -230,8 +235,8 @@ class HangulCombiner : Combiner {
val ordinal: Int get() = codePoint - 0x314f1 val ordinal: Int get() = codePoint - 0x314f1
fun toMedial(): Medial? { fun toMedial(): Medial? {
val codePoint = CONVERT_MEDIALS.getOrNull(COMPAT_VOWELS.indexOf(codePoint.toChar())) ?: return null val codePoint = CONVERT_MEDIALS.getOrNull(COMPAT_VOWELS.indexOf(codePoint.toChar())) ?: return null
if(codePoint.toInt() == 0) return null if(codePoint.code == 0) return null
return Medial(codePoint.toInt()) return Medial(codePoint.code)
} }
} }
companion object { companion object {
@ -265,7 +270,7 @@ class HangulCombiner : Combiner {
val uncombined: String get() = (initial?.string ?: "") + (medial?.string ?: "") + (final?.string ?: "") val uncombined: String get() = (initial?.string ?: "") + (medial?.string ?: "") + (final?.string ?: "")
val uncombinedCompat: String get() = (initial?.toConsonant()?.string ?: "") + val uncombinedCompat: String get() = (initial?.toConsonant()?.string ?: "") +
(medial?.toVowel()?.string ?: "") + (final?.toConsonant()?.string ?: "") (medial?.toVowel()?.string ?: "") + (final?.toConsonant()?.string ?: "")
val string: String get() = if(this.combinable) this.combined else this.uncombinedCompat val string: String get() = if (this.combinable) this.combined else this.uncombinedCompat
} }
companion object { companion object {
@ -320,7 +325,7 @@ class HangulCombiner : Combiner {
0x11ba to 0x11ba to 0x11bb // ㅆ 0x11ba to 0x11ba to 0x11bb // ㅆ
) )
private fun createEventChainFromSequence(text: CharSequence, originalEvent: Event): Event { private fun createEventChainFromSequence(text: CharSequence, originalEvent: Event): Event {
return Event.createSoftwareTextEvent(text, Constants.CODE_OUTPUT_TEXT, originalEvent); return Event.createSoftwareTextEvent(text, Constants.CODE_OUTPUT_TEXT, originalEvent)
} }
} }

View file

@ -22,7 +22,7 @@ object HangulEventDecoder {
else Event.createCombiningEvent(event) else Event.createCombiningEvent(event)
} }
val LAYOUT_DUBEOLSIK_STANDARD = mapOf<Int, Pair<Int, Int>>( private val LAYOUT_DUBEOLSIK_STANDARD = mapOf<Int, Pair<Int, Int>>(
45 to (0x3142 to 0x3143), 45 to (0x3142 to 0x3143),
51 to (0x3148 to 0x3149), 51 to (0x3148 to 0x3149),
33 to (0x3137 to 0x3138), 33 to (0x3137 to 0x3138),
@ -53,7 +53,7 @@ object HangulEventDecoder {
41 to (0x3161 to 0x3161) 41 to (0x3161 to 0x3161)
) )
val LAYOUT_SEBEOLSIK_390 = mapOf<Int, Pair<Int, Int>>( private val LAYOUT_SEBEOLSIK_390 = mapOf<Int, Pair<Int, Int>>(
8 to (0x11c2 to 0x11bd), 8 to (0x11c2 to 0x11bd),
9 to (0x11bb to 0x0040), 9 to (0x11bb to 0x0040),
10 to (0x11b8 to 0x0023), 10 to (0x11b8 to 0x0023),
@ -100,7 +100,7 @@ object HangulEventDecoder {
76 to (0x1169 to 0x003f) 76 to (0x1169 to 0x003f)
) )
val LAYOUT_SEBEOLSIK_FINAL = mapOf<Int, Pair<Int, Int>>( private val LAYOUT_SEBEOLSIK_FINAL = mapOf<Int, Pair<Int, Int>>(
68 to (0x002a to 0x203b), 68 to (0x002a to 0x203b),
8 to (0x11c2 to 0x11a9), 8 to (0x11c2 to 0x11a9),
@ -154,7 +154,7 @@ object HangulEventDecoder {
76 to (0x1169 to 0x0021) 76 to (0x1169 to 0x0021)
) )
val LAYOUTS = mapOf<String, Map<Int, Pair<Int, Int>>>( private val LAYOUTS = mapOf<String, Map<Int, Pair<Int, Int>>>(
"korean" to LAYOUT_DUBEOLSIK_STANDARD, "korean" to LAYOUT_DUBEOLSIK_STANDARD,
"korean_sebeolsik_390" to LAYOUT_SEBEOLSIK_390, "korean_sebeolsik_390" to LAYOUT_SEBEOLSIK_390,
"korean_sebeolsik_final" to LAYOUT_SEBEOLSIK_FINAL "korean_sebeolsik_final" to LAYOUT_SEBEOLSIK_FINAL