Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
LatinIME
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Deploy
Releases
Package Registry
Container Registry
Model registry
Operate
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
keyboard
LatinIME
Commits
cbaa2280
Commit
cbaa2280
authored
1 week ago
by
tenextractor
Committed by
Aleksandras Kostarevas
1 week ago
Browse files
Options
Downloads
Patches
Plain Diff
Add Korean combiner
Signed-off-by:
Aleksandras Kostarevas
<
aleks076@protonmail.com
>
parent
5d6761fa
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
java/src/org/futo/inputmethod/event/combiners/KoreanCombiner.kt
+270
-0
270 additions, 0 deletions
...rc/org/futo/inputmethod/event/combiners/KoreanCombiner.kt
java/src/org/futo/inputmethod/v2keyboard/CombinerKind.kt
+3
-0
3 additions, 0 deletions
java/src/org/futo/inputmethod/v2keyboard/CombinerKind.kt
with
273 additions
and
0 deletions
java/src/org/futo/inputmethod/event/combiners/KoreanCombiner.kt
0 → 100644
+
270
−
0
View file @
cbaa2280
// Contributed by @tenextractor in https://github.com/futo-org/android-keyboard/pull/899
package
org.futo.inputmethod.event.combiners
import
android.text.TextUtils
import
org.futo.inputmethod.event.Combiner
import
org.futo.inputmethod.event.Event
import
org.futo.inputmethod.latin.common.Constants
/**
* Combiner for Korean/Hangul script.
* If [combineInitials] is true, pressing ㄱㄱ or any other doubleable initial twice combines it into
* ㄲ and so on (only in the initial position, final ㄱㄱ is always combined)
*/
class
KoreanCombiner
(
private
val
combineInitials
:
Boolean
=
false
):
Combiner
{
// General implementation:
// A StringBuilder called `buffer` stores a word of uncombined Hangul letters from keypresses.
// On every keypress these uncombined letters are converted to a list of combined syllable blocks
// using the toBlocks() function, and passed to the system using getCombiningStateFeedback()
companion
object
Data
{
private
val
initials
=
listOf
(
'ㄱ'
/*g*/, 'ㄲ' /*gg*/, 'ㄴ' /*n*/, 'ㄷ' /*d*/
,
'ㄸ'
/*dd*/, 'ㄹ' /*r*/, 'ㅁ' /*m*/, 'ㅂ' /*b*/
,
'ㅃ'
/*bb*/, 'ㅅ' /*s*/, 'ㅆ' /*ss*/, 'ㅇ' /*ng*/
,
'ㅈ'
/*j*/, 'ㅉ' /*jj*/, 'ㅊ' /*ch*/, 'ㅋ' /*k*/
,
'ㅌ'
/*t*/, 'ㅍ' /*p*/, 'ㅎ' /*h*/
).
withIndex
().
associate
{
it
.
value
to
it
.
index
}
// This is a map, mapping 'ㄱ' to 0, 'ㄲ' to 1 and so on, same with `finals`
// Only these characters can appear at the START of a Hangul syllable,
// and any syllable MUST have one of these
private
val
doubleableInitials
=
setOf
(
'ㄱ'
,
'ㄷ'
,
'ㅂ'
,
'ㅅ'
,
'ㅈ'
)
// These initials can be doubled, by adding 1 to the codepoint number.
// For example, ㄱ = U+3131, ㄲ = U+3132
private
val
finals
=
listOf
(
null
,
'ㄱ'
/*g*/, 'ㄲ' /*gg*/, 'ㄳ' /*gs*/
,
'ㄴ'
/*n*/, 'ㄵ' /*nj*/, 'ㄶ' /*nh*/, 'ㄷ' /*d*/
,
'ㄹ'
/*r*/, 'ㄺ' /*rg*/, 'ㄻ' /*rm*/, 'ㄼ' /*rb*/
,
'ㄽ'
/*rs*/, 'ㄾ' /*rt*/, 'ㄿ' /*rp*/, 'ㅀ' /*rh*/
,
'ㅁ'
/*m*/, 'ㅂ' /*b*/, 'ㅄ' /*bs*/, 'ㅅ' /*s*/
,
'ㅆ'
/*ss*/, 'ㅇ' /*ng*/, 'ㅈ' /*j*/, 'ㅊ' /*ch*/
,
'ㅋ'
/*k*/, 'ㅌ' /*t*/, 'ㅍ' /*p*/, 'ㅎ' /*h*/
).
withIndex
().
associate
{
it
.
value
to
it
.
index
}
// Only these characters can appear at the END of a Hangul syllable.
// These are optional, and any syllable can only have either no final, or one of these finals.
// The NULL at index 0 represents the possibility of having no final.
private
const
val
GA_LOCATION
=
'가'
.
code
//44032
// This value is important because, it is the start of the Unicode Hangul Syllables block,
// and the Unicode codepoint (U+XXXX number) for any Hangul syllable is given by the formula:
// syllable = [(initial) × 588 + (vowel) × 28 + (final)] + 44032
// where `initial`, `vowel` and `final` are the indices of the initial, vowel,
// and final (0 if no final) character in the above lists.
private
val
mergedClusters
=
mapOf
(
'ㅗ'
to
'ㅏ'
to
'ㅘ'
,
'ㅗ'
to
'ㅐ'
to
'ㅙ'
,
'ㅗ'
to
'ㅣ'
to
'ㅚ'
,
'ㅜ'
to
'ㅓ'
to
'ㅝ'
,
'ㅜ'
to
'ㅔ'
to
'ㅞ'
,
'ㅜ'
to
'ㅣ'
to
'ㅟ'
,
'ㅡ'
to
'ㅣ'
to
'ㅢ'
,
//vowels
'ㅏ'
to
'ㅏ'
to
'ㅑ'
,
'ㅐ'
to
'ㅐ'
to
'ㅒ'
,
'ㅓ'
to
'ㅓ'
to
'ㅕ'
,
'ㅔ'
to
'ㅔ'
to
'ㅖ'
,
'ㅗ'
to
'ㅗ'
to
'ㅛ'
,
'ㅜ'
to
'ㅜ'
to
'ㅠ'
,
//gboard 단모음 layout uses these mappings
'ㄱ'
to
'ㄱ'
to
'ㄲ'
,
'ㄱ'
to
'ㅅ'
to
'ㄳ'
,
'ㄴ'
to
'ㅈ'
to
'ㄵ'
,
'ㄴ'
to
'ㅎ'
to
'ㄶ'
,
'ㄹ'
to
'ㄱ'
to
'ㄺ'
,
'ㄹ'
to
'ㅁ'
to
'ㄻ'
,
'ㄹ'
to
'ㅂ'
to
'ㄼ'
,
'ㄹ'
to
'ㅅ'
to
'ㄽ'
,
'ㄹ'
to
'ㅌ'
to
'ㄾ'
,
'ㄹ'
to
'ㅍ'
to
'ㄿ'
,
'ㄹ'
to
'ㅎ'
to
'ㅀ'
,
'ㅂ'
to
'ㅅ'
to
'ㅄ'
,
'ㅅ'
to
'ㅅ'
to
'ㅆ'
//finals
)
// The `initials`, `vowels`, and `finals` mentioned above are not the simplest possible
// elements, and sometimes need to be constructed from multiple keypresses.
}
private
fun
toBlock
(
initial
:
Char
,
vowel
:
Char
,
final
:
Char
?):
Char
{
//merge initial, vowel and optional final letters into a hangul syllable block
//using this formula: syllable = [(initial) × 588 + (vowel) × 28 + (final)] + 44032
return
(
GA_LOCATION
+
588
*
initials
[
initial
]
!!
+
28
*(
vowel
.
code
-
0x314F
)
+
finals
[
final
]
!!
).
toChar
()
}
private
fun
isHangulLetter
(
char
:
Char
):
Boolean
{
return
char
.
code
in
0x3131
..
0x3163
}
private
fun
isInitial
(
char
:
Char
):
Boolean
{
return
initials
.
containsKey
(
char
)
}
private
fun
isVowel
(
char
:
Char
):
Boolean
{
return
char
.
code
in
0x314F
..
0x3163
}
private
fun
isFinal
(
char
:
Char
):
Boolean
{
return
finals
.
containsKey
(
char
)
}
private
val
buffer
=
StringBuilder
()
// This buffer holds a single word of UNCOMBINED Hangul letters.
private
fun
toBlocks
():
CharSequence
{
val
combined
=
StringBuilder
()
var
initial
:
Char
?
=
null
var
vowel
:
Char
?
=
null
var
final
:
Char
?
=
null
var
final2
:
Char
?
=
null
// State variables
for
(
char
in
buffer
)
{
if
(
initial
==
null
)
{
// Starting case
if
(!
isInitial
(
char
))
{
combined
.
append
(
char
)
continue
}
// If the current char is not an initial, just add it to the result
initial
=
char
continue
}
if
(
vowel
==
null
)
{
// There is an initial, but no vowel
if
(
combineInitials
&&
initial
==
char
&&
doubleableInitials
.
contains
(
initial
))
{
initial
=
(
initial
.
code
+
1
).
toChar
()
continue
}
// If current char is the same as the initial, then double the initial
// (adding 1 to ㄱ makes it ㄲ and so on)
if
(!
isVowel
(
char
))
{
combined
.
append
(
initial
)
if
(!
isInitial
(
char
))
{
combined
.
append
(
char
)
initial
=
null
continue
}
initial
=
char
continue
}
// After the double initial case earlier has been handled,
// if current char is not a vowel at this point, we are not getting a valid syllable
// Just output individual letters as they are
vowel
=
char
// char is a valid vowel
continue
}
if
(
final
==
null
)
{
// There is initial and vowel, but no final
val
possibleCluster
=
Pair
(
vowel
,
char
)
if
(
mergedClusters
.
containsKey
(
possibleCluster
))
{
vowel
=
mergedClusters
[
possibleCluster
]
!!
continue
}
// If char is another vowel character that can merge with the existing vowel,
// merge it
if
(!
isFinal
(
char
))
{
combined
.
append
(
toBlock
(
initial
,
vowel
,
null
))
vowel
=
null
if
(!
isInitial
(
char
))
{
combined
.
append
(
char
)
initial
=
null
continue
}
initial
=
char
continue
}
// current char is not a valid final
final
=
char
// char is a valid final
continue
}
if
(
final2
==
null
)
{
if
(
mergedClusters
.
containsKey
(
Pair
(
final
,
char
)))
{
final2
=
char
continue
}
// if it's a valid second final, add it
if
(
isVowel
(
char
))
{
combined
.
append
(
toBlock
(
initial
,
vowel
,
null
))
initial
=
final
vowel
=
char
final
=
null
continue
}
// if char is a vowel, start a new syllable, taking the final from the existing
// syllable together with the current char (vowel)
if
(
isInitial
(
char
))
{
combined
.
append
(
toBlock
(
initial
,
vowel
,
final
))
initial
=
char
vowel
=
null
final
=
null
continue
}
// after the double final case has been handled earlier, if char is an initial,
// start a new syllable with it
combined
.
append
(
char
)
// if it's neither a vowel nor an initial, just output it
continue
}
// Last case, now there is a vowel, initial, and both finals
if
(
isVowel
(
char
))
{
combined
.
append
(
toBlock
(
initial
,
vowel
,
final
))
initial
=
final2
vowel
=
char
final
=
null
final2
=
null
continue
}
// same vowel case as above, but now take only the second final for a new syllable
// instead of the first final
if
(
isInitial
(
char
))
{
val
finalCluster
=
mergedClusters
[
Pair
(
final
,
final2
)]
!!
combined
.
append
(
toBlock
(
initial
,
vowel
,
finalCluster
))
initial
=
char
vowel
=
null
final
=
null
final2
=
null
continue
}
// if char is an initial, start a new syllable
combined
.
append
(
char
)
// if it's neither an initial nor a vowel, just output it
continue
}
// Final iteration: output whatever is in initial, vowel and final variables
if
(
initial
!=
null
&&
vowel
==
null
)
{
combined
.
append
(
initial
)
}
if
(
initial
!=
null
&&
vowel
!=
null
&&
final2
==
null
)
{
combined
.
append
(
toBlock
(
initial
,
vowel
,
final
))
}
if
(
final2
!=
null
)
{
val
finalCluster
=
mergedClusters
[
Pair
(
final
,
final2
)]
!!
combined
.
append
(
toBlock
(
initial
!!
,
vowel
!!
,
finalCluster
))
}
return
combined
}
override
fun
processEvent
(
previousEvents
:
ArrayList
<
Event
>?,
event
:
Event
?):
Event
{
if
(
event
==
null
)
return
Event
.
createNotHandledEvent
()
val
keypress
=
event
.
mCodePoint
.
toChar
()
if
(!
isHangulLetter
(
keypress
))
{
if
(!
TextUtils
.
isEmpty
(
buffer
))
{
if
(
event
.
mKeyCode
==
Constants
.
CODE_DELETE
)
{
return
if
(
buffer
.
length
==
1
)
{
reset
()
Event
.
createHardwareKeypressEvent
(
0x20
,
Constants
.
CODE_SPACE
,
event
,
event
.
isKeyRepeat
)
// for some reason, this is needed, otherwise if there is only one letter
// in the buffer it won't be deleted
}
else
{
buffer
.
setLength
(
buffer
.
length
-
1
)
Event
.
createConsumedEvent
(
event
)
}
}
}
return
event
}
buffer
.
append
(
keypress
)
return
Event
.
createConsumedEvent
(
event
)
}
override
fun
getCombiningStateFeedback
():
CharSequence
{
return
toBlocks
()
}
override
fun
reset
()
{
buffer
.
setLength
(
0
)
}
}
This diff is collapsed.
Click to expand it.
java/src/org/futo/inputmethod/v2keyboard/CombinerKind.kt
+
3
−
0
View file @
cbaa2280
...
@@ -2,7 +2,10 @@ package org.futo.inputmethod.v2keyboard
...
@@ -2,7 +2,10 @@ package org.futo.inputmethod.v2keyboard
import
org.futo.inputmethod.event.Combiner
import
org.futo.inputmethod.event.Combiner
import
org.futo.inputmethod.event.DeadKeyCombiner
import
org.futo.inputmethod.event.DeadKeyCombiner
import
org.futo.inputmethod.event.combiners.KoreanCombiner
enum
class
CombinerKind
(
val
factory
:
()
->
Combiner
)
{
enum
class
CombinerKind
(
val
factory
:
()
->
Combiner
)
{
DeadKey
({
DeadKeyCombiner
()
}),
DeadKey
({
DeadKeyCombiner
()
}),
Korean
({
KoreanCombiner
()
}),
KoreanCombineInitials
({
KoreanCombiner
(
combineInitials
=
true
)
})
}
}
\ No newline at end of file
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment