diff options
Diffstat (limited to 'hangul/hangulinputcontext.c')
-rw-r--r-- | hangul/hangulinputcontext.c | 312 |
1 files changed, 311 insertions, 1 deletions
diff --git a/hangul/hangulinputcontext.c b/hangul/hangulinputcontext.c index ac5da5b..1f0cdfc 100644 --- a/hangul/hangulinputcontext.c +++ b/hangul/hangulinputcontext.c @@ -12,7 +12,6 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ @@ -29,6 +28,143 @@ #include "hangul.h" #include "hangulinternals.h" +/** + * @defgroup hangulic 한글 입력 기능 구현 + * + * @section hangulicusage Hangul Input Context의 사용법 + * 이 섹션에서는 한글 입력 기능을 구현하는 핵심 기능에 대해 설명한다. + * + * 먼저 preedit string과 commit string 이 두 용어에 대해서 설멍하겠다. + * 이 두가지 용어는 Unix 계열의 입력기 framework에서 널리 쓰이는 표현이다. + * + * preedit string은 아직 조합중으로 어플리케이션에 완전히 입력되지 않은 + * 스트링을 가리킨다. 일반적으로 한글 입력기에서는 역상으로 보이고 + * 일본 중국어 입력기에서는 underline이 붙어 나타난다. 아직 완성이 되지 + * 않은 스트링이므로 어플리케이션에 전달이 되지 않고 사라질 수도 있다. + * + * commit string은 조합이 완료되어 어플리케이션에 전달되는 스트링이다. + * 이 스트링은 실제 어플리케이션의 텍스트로 인식이 되므로 이 이후에는 + * 더이상 입력기가 관리할 수 있는 데이터가 아니다. + * + * 한글 입력과정은 다음과 같은 과정을 거치게 된다. + * 입력된 영문 키를 그에 해댱하는 한글 자모로 변환한후 한글 자모를 모아 + * 하나의 음절을 만든다. 여기까지 이루어지는 과정을 preedit string 형태로 + * 사용자에게 계속 보이게 하는 것이 필요하다. + * 그리고는 한글 음절이 완성되고나면 그 글자를 어플리케이션에 commit + * string 형태로 보내여 입력을 완료하는 것이다. 다음 키를 받게 되면 + * 이 과정을 반복해서 수행한다. + * + * libhangul에서 한글 조합 기능은 @ref HangulInputContext를 이용해서 구현하게 + * 되는데 기본 적인 방법은 @ref HangulInputContext에 사용자로부터의 입력을 + * 순서대로 전달하면서 그 상태가 바뀜에 따라서 preedit 나 commit 스트링을 + * 상황에 맞게 변화시키는 것이다. + * + * 입력 코드들은 GUI 코드와 밀접하게 붙어 있어서 키 이벤트를 받아서 + * 처리하도록 구현하는 것이 보통이다. 그런데 유닉스에는 많은 입력 프레임웍들이 + * 난립하고 있는 상황이어서 매 입력 프레임웍마다 한글 조합 루틴을 작성해서 + * 넣는 것은 비효율적이다. 간단한 API를 구현하여 여러 프레임웍에서 바로 + * 사용할 수 있도록 구현하는 편이 사용성이 높아지게 된다. + * + * 그래서 libhangul에서는 키 이벤트를 따로 재정의하지 않고 ASCII 코드를 + * 직접 사용하는 방향으로 재정의된 데이터가 많지 않도록 하였다. + * 실제 사용 방법은 말로 설명하는 것보다 샘플 코드를 사용하는 편이 + * 이해가 빠를 것이다. 그래서 대략적인 진행 과정을 샘플 코드로 + * 작성하였다. + * + * 아래 예제는 실제로는 존재하지 않는 GUI 라이브러리 코드를 사용하였다. + * 실제 GUI 코드를 사용하면 코드가 너무 길어져서 설명이 어렵고 코드가 + * 길어지면 핵심을 놓치기 쉽기 때문에 가공의 함수를 사용하였다. + * 또한 텍스트의 encoding conversion 관련된 부분도 생략하였다. + * 여기서 사용한 가공의 GUI 코드는 TWin으로 시작하게 하였다. + * + * @code + + HangulInputContext* hic = hangul_ic_new("2"); + ... + + // 아래는 키 입력만 처리하는 이벤트 루프이다. + // 실제 GUI코드는 이렇게 단순하진 않지만 + // 편의상 키 입력만 처리하는 코드로 작성하였다. + + TWinKeyEvent event = TWinGetKeyEvent(); // 키이벤트를 받는 이런 함수가 + // 있다고 치자 + while (ascii != 0) { + bool res; + if (event.isBackspace()) { + // backspace를 ascii로 변환하기가 좀 꺼림직해서 + // libhangul에서는 backspace 처리를 위한 + // 함수를 따로 만들었다. + res = hangul_ic_backspace(hic); + } else { + // 키 입력을 해당하는 ascii 코드로 변환한다. + // libhangul에서는 이 ascii 코드가 키 이벤트 + // 코드와 마찬가지다. + int ascii = event.getAscii(); + + // 키 입력을 받았으면 이것을 hic에 먼저 보낸다. + // 그래야 hic가 이 키를 사용할 것인지 아닌지를 판단할 수 있다. + // 함수가 true를 리턴하면 이 키를 사용했다는 의미이므로 + // GUI 코드가 이 키 입력을 프로세싱하지 않도록 해야 한다. + // 그렇지 않으면 한 키입력이 두번 프로세싱된다. + res = hangul_ic_process(hic, ascii); + } + + // hic는 한번 키입력을 받고 나면 내부 상태 변화가 일어나고 + // 완성된 글자를 어플리케이션에 보내야 하는 상황이 있을 수 있다. + // 이것을 HangulInputContext에서는 commit 스트링이 있는지로 + // 판단한다. commit 스트링을 받아봐서 스트링이 있다면 + // 그 스트링으로 입력이 완료된 걸로 본다. + const ucschar commit; + commit = hangul_ic_get_commit_string(hic); + if (commit[0] != 0) { // 스트링의 길이를 재서 commit 스트링이 있는지 + // 판단한다. + TWinInputUnicodeChars(commit); + } + + // 키입력 후에는 preedit string도 역시 변화하게 되는데 + // 입력기 프레임웍에서는 이 스트링을 화면에 보여주어야 + // 조합중인 글자가 화면에 표시가 되는 것이다. + const ucschar preedit; + preedit = hangul_ic_get_preedit_string(hic); + // 이 경우에는 스트링의 길이에 관계없이 항상 업데이트를 + // 해야 한다. 왜냐하면 이전에 조합중이던 글자가 있다가 + // 조합이 완료되면서 조합중인 상태의 글자가 없어질 수도 있기 때문에 + // 스트링의 길이에 관계없이 현재 상태의 스트링을 preedit + // 스트링으로 보여주면 되는 것이다. + TWinUpdatePreeditString(preedit); + + // 위 두작업이 끝난후에는 키 이벤트를 계속 프로세싱해야 하는지 + // 아닌지를 처리해야 한다. + // hic가 키 이벤트를 사용하지 않았다면 기본 GUI 코드에 계속해서 + // 키 이벤트 프로세싱을 진행하도록 해야 한다. + if (!res) + TWinForwardKeyEventToUI(ascii); + + ascii = GetKeyEvent(); + } + + hangul_ic_delete(hic); + + * @endcode + */ + +/** + * @file hangulinputcontext.c + */ + +/** + * @ingroup hangulic + * @typedef HangulInputContext + * @brief 한글 입력 상태를 관리하기 위한 오브젝트 + * + * libhangul에서 제공하는 한글 조합 루틴에서 상태 정보를 저장하는 opaque + * 데이타 오브젝트이다. 이 오브젝트에 키입력 정보를 순차적으로 보내주면서 + * preedit 스트링이나, commit 스트링을 받아서 처리하면 한글 입력 기능을 + * 손쉽게 구현할 수 있다. + * 내부의 데이터 멤버는 공개되어 있지 않다. 각각의 멤버는 accessor 함수로만 + * 참조하여야 한다. + */ + #ifndef TRUE #define TRUE 1 #endif @@ -1006,6 +1142,32 @@ flush: return false; } +/** + * @ingroup hangulic + * @brief 키 입력을 처리하여 실제로 한글 조합을 하는 함수 + * @param hic @ref HangulInputContext 오브젝트 + * @param ascii 키 이벤트 + * @return @ref HangulInputContext가 이 키를 사용했으면 true, + * 사용하지 않았으면 false + * + * ascii 값으로 주어진 키 이벤트를 받아서 내부의 한글 조합 상태를 + * 변화시키고, preedit, commit 스트링을 저장한다. + * + * libhangul의 키 이벤트 프로세스는 ASCII 코드 값을 기준으로 처리한다. + * 이 키 값은 US Qwerty 자판 배열에서의 키 값에 해당한다. + * 따라서 유럽어 자판을 사용하는 경우에는 해당 키의 ASCII 코드를 직접 + * 전달하면 안되고, 그 키가 US Qwerty 자판이었을 경우에 발생할 수 있는 + * ASCII 코드 값을 주어야 한다. + * 또한 ASCII 코드 이므로 Shift 상태는 대문자로 전달이 된다. + * Capslock이 눌린 경우에는 대소문자를 뒤바꾸어 보내주지 않으면 + * 마치 Shift가 눌린 것 처럼 동작할 수 있으므로 주의한다. + * preedit, commit 스트링은 hangul_ic_get_preedit_string(), + * hangul_ic_get_commit_string() 함수를 이용하여 구할 수 있다. + * + * 이 함수의 사용법에 대한 설명은 @ref hangulicusage 부분을 참조한다. + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시킨다. + */ bool hangul_ic_process(HangulInputContext *hic, int ascii) { @@ -1029,6 +1191,18 @@ hangul_ic_process(HangulInputContext *hic, int ascii) return hangul_ic_process_romaja(hic, ascii, c); } +/** + * @ingroup hangulic + * @brief 현재 상태의 preedit string을 구하는 함수 + * @param hic preedit string을 구하고자하는 입력 상태 object + * @return UCS4 preedit 스트링, 이 스트링은 @a hic 내부의 데이터이므로 + * 수정하거나 free해서는 안된다. + * + * 이 함수는 @a hic 내부의 현재 상태의 preedit string을 리턴한다. + * 따라서 hic가 다른 키 이벤트를 처리하고 나면 그 내용이 바뀔 수 있다. + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다. + */ const ucschar* hangul_ic_get_preedit_string(HangulInputContext *hic) { @@ -1038,6 +1212,18 @@ hangul_ic_get_preedit_string(HangulInputContext *hic) return hic->preedit_string; } +/** + * @ingroup hangulic + * @brief 현재 상태의 commit string을 구하는 함수 + * @param hic commit string을 구하고자하는 입력 상태 object + * @return UCS4 commit 스트링, 이 스트링은 @a hic 내부의 데이터이므로 + * 수정하거나 free해서는 안된다. + * + * 이 함수는 @a hic 내부의 현재 상태의 commit string을 리턴한다. + * 따라서 hic가 다른 키 이벤트를 처리하고 나면 그 내용이 바뀔 수 있다. + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다. + */ const ucschar* hangul_ic_get_commit_string(HangulInputContext *hic) { @@ -1047,6 +1233,20 @@ hangul_ic_get_commit_string(HangulInputContext *hic) return hic->commit_string; } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext를 초기상태로 되돌리는 함수 + * @param hic @ref HangulInputContext를 가리키는 포인터 + * + * 이 함수는 @a hic가 가리키는 @ref HangulInputContext의 상태를 + * 처음 상태로 되돌린다. preedit 스트링, commit 스트링, flush 스트링이 + * 없어지고, 입력되었던 키에 대한 기록이 없어진다. + * 영어 상태로 바뀌는 것이 아니다. + * + * 비교: hangul_ic_flush() + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시킨다. + */ void hangul_ic_reset(HangulInputContext *hic) { @@ -1071,6 +1271,23 @@ hangul_ic_flush_internal(HangulInputContext *hic) hangul_buffer_clear(&hic->buffer); } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext의 입력 상태를 완료하는 함수 + * @param hic @ref HangulInputContext를 가리키는 포인터 + * @return 조합 완료된 스트링, 스트링의 길이가 0이면 조합 완료된 스트링이 + * 없는 것 + * + * 이 함수는 @a hic가 가리키는 @ref HangulInputContext의 입력 상태를 완료한다. + * 조합중이던 스트링을 완성하여 리턴한다. 그리고 입력 상태가 초기 상태로 + * 되돌아 간다. 조합중이던 글자를 강제로 commit하고 싶을때 사용하는 함수다. + * 보통의 경우 입력 framework에서 focus가 나갈때 이 함수를 불러서 마지막 + * 상태를 완료해야 조합중이던 글자를 잃어버리지 않게 된다. + * + * 비교: hangul_ic_reset() + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시킨다. + */ const ucschar* hangul_ic_flush(HangulInputContext *hic) { @@ -1095,6 +1312,19 @@ hangul_ic_flush(HangulInputContext *hic) return hic->flushed_string; } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext가 backspace 키를 처리하도록 하는 함수 + * @param hic @ref HangulInputContext를 가리키는 포인터 + * @return @a hic가 키를 사용했으면 true, 사용하지 않았으면 false + * + * 이 함수는 @a hic가 가리키는 @ref HangulInputContext의 조합중이던 글자를 + * 뒤에서부터 하나 지우는 기능을 한다. backspace 키를 눌렀을 때 발생하는 + * 동작을 한다. 따라서 이 함수를 부르고 나면 preedit string이 바뀌므로 + * 반드시 업데이트를 해야 한다. + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시킨다. + */ bool hangul_ic_backspace(HangulInputContext *hic) { @@ -1218,24 +1448,60 @@ hangul_ic_dvorak_to_qwerty(int qwerty) return qwerty; } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext가 조합중인 글자를 가지고 있는지 확인하는 함수 + * @param hic @ref HangulInputContext를 가리키는 포인터 + * + * @ref HangulInputContext가 조합중인 글자가 있으면 true를 리턴한다. + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다. + */ bool hangul_ic_is_empty(HangulInputContext *hic) { return hangul_buffer_is_empty(&hic->buffer); } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext가 조합중인 초성을 가지고 있는지 확인하는 함수 + * @param hic @ref HangulInputContext를 가리키는 포인터 + * + * @ref HangulInputContext가 조합중인 글자가 초성이 있으면 true를 리턴한다. + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다. + */ bool hangul_ic_has_choseong(HangulInputContext *hic) { return hangul_buffer_has_choseong(&hic->buffer); } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext가 조합중인 중성을 가지고 있는지 확인하는 함수 + * @param hic @ref HangulInputContext를 가리키는 포인터 + * + * @ref HangulInputContext가 조합중인 글자가 중성이 있으면 true를 리턴한다. + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다. + */ bool hangul_ic_has_jungseong(HangulInputContext *hic) { return hangul_buffer_has_jungseong(&hic->buffer); } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext가 조합중인 종성을 가지고 있는지 확인하는 함수 + * @param hic @ref HangulInputContext를 가리키는 포인터 + * + * @ref HangulInputContext가 조합중인 글자가 종성이 있으면 true를 리턴한다. + * + * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다. + */ bool hangul_ic_has_jongseong(HangulInputContext *hic) { @@ -1304,6 +1570,27 @@ hangul_ic_set_keyboard(HangulInputContext *hic, const HangulKeyboard* keyboard) hic->keyboard = keyboard; } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext의 자판 배열을 바꾸는 함수 + * @param hic @ref HangulInputContext 오브젝트 + * @param id 선택하고자 하는 자판, 아래와 같은 값을 선택할 수 있다. + * @li "2" 두벌식 자판 + * @li "32" 세벌식 자판으로 두벌식의 배열을 가진 자판. + * 두벌식 사용자가 쉽게 세벌식 테스트를 할 수 있다. + * shift를 누르면 자음이 종성으로 동작한다. + * @li "3f" 세벌식 최종 + * @li "39" 세벌식 390 + * @li "3s" 세벌식 순아래 + * @li "3y" 세벌식 옛글 + * @li "ro" 로마자 방식 자판 + * @return 없음 + * + * 이 함수는 @ref HangulInputContext의 자판을 @a id로 지정된 것으로 변경한다. + * + * @remarks 이 함수는 @ref HangulInputContext의 내부 조합 상태에는 영향을 + * 미치지 않는다. 따라서 입력 중간에 자판을 변경하더라도 조합 상태는 유지된다. + */ void hangul_ic_select_keyboard(HangulInputContext *hic, const char* id) { @@ -1361,6 +1648,18 @@ hangul_ic_set_combination(HangulInputContext *hic, hic->combination = combination; } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext 오브젝트를 생성한다. + * @param keyboard 사용하고자 하는 키보드, 사용 가능한 값에 대해서는 + * hangul_ic_select_keyboard() 함수 설명을 참조한다. + * @return 새로 생성된 @ref HangulInputContext에 대한 포인터 + * + * 이 함수는 한글 조합 기능을 제공하는 @ref HangulInputContext 오브젝트를 + * 생성한다. 생성할때 지정한 자판은 나중에 hangul_ic_select_keyboard() 함수로 + * 다른 자판으로 변경이 가능하다. + * 더이상 사용하지 않을 때에는 hangul_ic_delete() 함수로 삭제해야 한다. + */ HangulInputContext* hangul_ic_new(const char* keyboard) { @@ -1390,6 +1689,17 @@ hangul_ic_new(const char* keyboard) return hic; } +/** + * @ingroup hangulic + * @brief @ref HangulInputContext를 삭제하는 함수 + * @param hic @ref HangulInputContext 오브젝트 + * + * @a hic가 가리키는 @ref HangulInputContext 오브젝트의 메모리를 해제한다. + * hangul_ic_new() 함수로 생성된 모든 @ref HangulInputContext 오브젝트는 + * 이 함수로 메모리해제를 해야 한다. + * 메모리 해제 과정에서 상태 변화는 일어나지 않으므로 마지막 입력된 + * 조합중이던 내용은 사라지게 된다. + */ void hangul_ic_delete(HangulInputContext *hic) { |