Compare commits

...

1 Commits

Author SHA1 Message Date
Jules Aguillon
3cb92fce84 Normalization of Korean characters
This changes how some characters are sent to the editor.
Characters specified in 'should_normalize_char' are not committed
immediately to the editor but instead added to the composing text.

The composing text is NFCKC-normalized. Resulting characters that are
not in 'should_normalize_char' committed, the rest is the composing
text.

Typing a character not specified here do not change: commitText is used
immediately.
2024-03-16 23:28:32 +01:00
2 changed files with 113 additions and 2 deletions

View File

@ -0,0 +1,104 @@
package juloo.keyboard2;
import android.view.inputmethod.EditorInfo;
import android.icu.text.Normalizer2;
import android.view.inputmethod.InputConnection;
/** Compose the characters specified in [should_normalize_char()] using unicode
normalization. The composing region is used to mark text where
normalization could happen if more characters are typed. */
public final class Autonormalization
{
/** Composing text waiting for more combinations. */
StringBuilder _c = new StringBuilder();
InputConnection _ic = null;
Normalizer2 _normalizer_cached = null;
public Autonormalization() {}
public void started(EditorInfo info, InputConnection ic)
{
_ic = ic;
_c.setLength(0);
}
/** If the characters could combine with a following character, update the
composing text and return [true]. If the typed chars can't combine, flush
the composing text and return [false], without committing the text. */
public boolean typed(CharSequence s)
{
if (_ic == null || s.length() == 0)
return false;
if (should_normalize_char(s.charAt(0)))
{
type_normalized(s);
return true;
}
flush();
return false;
}
/** Handle key events, might flush the composing text. Do not call [typed]. */
public void key_up(KeyValue kv)
{
switch (kv.getKind())
{
// [typed] will be called later
case Char:
case String: break;
// Do not change the composing text for these keys
case Modifier:
case Compose_pending: break;
// The other keys flush the composing text
case Event:
case Keyevent:
case Editing:
flush();
break;
}
}
/** If [typed()] has been called before, [flush()] must be called before
sending any other command to the [InputConnection]. */
void flush()
{
if (_ic == null)
return;
_c.setLength(0);
_ic.finishComposingText();
}
Normalizer2 normalizer()
{
if (_normalizer_cached == null)
_normalizer_cached = Normalizer2.getNFKCInstance();
return _normalizer_cached;
}
void type_normalized(CharSequence s)
{
Normalizer2 norm = normalizer();
norm.normalizeSecondAndAppend(_c, s);
/* Only keep the string of normalizable character at the end of [_c].
Commit the rest. */
int i = _c.length() - 1;
while (i > 0 && should_normalize_char(_c.charAt(i)))
i--;
if (i > 0)
{
_ic.commitText(_c.subSequence(0, i), 1);
_c.delete(0, i);
}
_ic.setComposingText(_c, 1);
}
/** Characters for which autonormalization will happen. */
boolean should_normalize_char(char c)
{
return (c >= '\u1100' && c <= '\u11FF') // Hangul Jamo
|| (c >= '\u3130' && c <= '\u318F') // Hangul Compatibility Jamo
|| (c >= '\uA960' && c <= '\uA97F') // Hangul Jamo Extended-A
|| (c >= '\uD7B0' && c <= '\uD7FF') // Hangul Jamo Extended-B
;
}
}

View File

@ -14,6 +14,7 @@ public final class KeyEventHandler implements Config.IKeyEventHandler
{ {
IReceiver _recv; IReceiver _recv;
Autocapitalisation _autocap; Autocapitalisation _autocap;
Autonormalization _autonorm;
/** State of the system modifiers. It is updated whether a modifier is down /** State of the system modifiers. It is updated whether a modifier is down
or up and a corresponding key event is sent. */ or up and a corresponding key event is sent. */
Pointers.Modifiers _mods; Pointers.Modifiers _mods;
@ -30,13 +31,16 @@ public final class KeyEventHandler implements Config.IKeyEventHandler
_recv = recv; _recv = recv;
_autocap = new Autocapitalisation(looper, _autocap = new Autocapitalisation(looper,
this.new Autocapitalisation_callback()); this.new Autocapitalisation_callback());
_autonorm = new Autonormalization();
_mods = Pointers.Modifiers.EMPTY; _mods = Pointers.Modifiers.EMPTY;
} }
/** Editing just started. */ /** Editing just started. */
public void started(EditorInfo info) public void started(EditorInfo info)
{ {
_autocap.started(info, _recv.getCurrentInputConnection()); InputConnection ic = _recv.getCurrentInputConnection();
_autocap.started(info, ic);
_autonorm.started(info, ic);
// Workaround a bug in Acode, which answers to [getExtractedText] but do // Workaround a bug in Acode, which answers to [getExtractedText] but do
// not react to [setSelection] while returning [true]. // not react to [setSelection] while returning [true].
// Note: Using & to workaround a bug in Acode, which sets several // Note: Using & to workaround a bug in Acode, which sets several
@ -86,6 +90,8 @@ public final class KeyEventHandler implements Config.IKeyEventHandler
return; return;
Pointers.Modifiers old_mods = _mods; Pointers.Modifiers old_mods = _mods;
update_meta_state(mods); update_meta_state(mods);
// Handles sending text
_autonorm.key_up(key);
switch (key.getKind()) switch (key.getKind())
{ {
case Char: send_text(String.valueOf(key.getChar())); break; case Char: send_text(String.valueOf(key.getChar())); break;
@ -192,7 +198,8 @@ public final class KeyEventHandler implements Config.IKeyEventHandler
InputConnection conn = _recv.getCurrentInputConnection(); InputConnection conn = _recv.getCurrentInputConnection();
if (conn == null) if (conn == null)
return; return;
conn.commitText(text, 1); if (!_autonorm.typed(text))
conn.commitText(text, 1);
_autocap.typed(text); _autocap.typed(text);
} }