hangul_jongseong_{dicompose -> decompose}
[platform/core/uifw/libhangul.git] / hangul / hangulinputcontext.c
1 /* libhangul
2  * Copyright (C) 2004 - 2009 Choe Hwanjin
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
16  */
17
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <inttypes.h>
26 #include <limits.h>
27
28 #include "hangul-gettext.h"
29 #include "hangul.h"
30 #include "hangulinternals.h"
31
32 /**
33  * @defgroup hangulic 한글 입력 기능 구현
34  * 
35  * @section hangulicusage Hangul Input Context의 사용법
36  * 이 섹션에서는 한글 입력 기능을 구현하는 핵심 기능에 대해 설명한다.
37  *
38  * 먼저 preedit string과 commit string 이 두 용어에 대해서 설멍하겠다.
39  * 이 두가지 용어는 Unix 계열의 입력기 framework에서 널리 쓰이는 표현이다.
40  *
41  * preedit string은 아직 조합중으로 어플리케이션에 완전히 입력되지 않은 
42  * 스트링을 가리킨다. 일반적으로 한글 입력기에서는 역상으로 보이고
43  * 일본 중국어 입력기에서는 underline이 붙어 나타난다. 아직 완성이 되지
44  * 않은 스트링이므로 어플리케이션에 전달이 되지 않고 사라질 수도 있다.
45  *
46  * commit string은 조합이 완료되어 어플리케이션에 전달되는 스트링이다.
47  * 이 스트링은 실제 어플리케이션의 텍스트로 인식이 되므로 이 이후에는
48  * 더이상 입력기가 관리할 수 있는 데이터가 아니다.
49  *
50  * 한글 입력과정은 다음과 같은 과정을 거치게 된다.
51  * 입력된 영문 키를 그에 해댱하는 한글 자모로 변환한후 한글 자모를 모아
52  * 하나의 음절을 만든다. 여기까지 이루어지는 과정을 preedit string 형태로
53  * 사용자에게 계속 보이게 하는 것이 필요하다.
54  * 그리고는 한글 음절이 완성되고나면 그 글자를 어플리케이션에 commit 
55  * string 형태로 보내여 입력을 완료하는 것이다. 다음 키를 받게 되면 
56  * 이 과정을 반복해서 수행한다.
57  * 
58  * libhangul에서 한글 조합 기능은 @ref HangulInputContext를 이용해서 구현하게
59  * 되는데 기본 적인 방법은 @ref HangulInputContext에 사용자로부터의 입력을
60  * 순서대로 전달하면서 그 상태가 바뀜에 따라서 preedit 나 commit 스트링을
61  * 상황에 맞게 변화시키는 것이다.
62  * 
63  * 입력 코드들은 GUI 코드와 밀접하게 붙어 있어서 키 이벤트를 받아서
64  * 처리하도록 구현하는 것이 보통이다. 그런데 유닉스에는 많은 입력 프레임웍들이
65  * 난립하고 있는 상황이어서 매 입력 프레임웍마다 한글 조합 루틴을 작성해서
66  * 넣는 것은 비효율적이다. 간단한 API를 구현하여 여러 프레임웍에서 바로 
67  * 사용할 수 있도록 구현하는 편이 사용성이 높아지게 된다.
68  *
69  * 그래서 libhangul에서는 키 이벤트를 따로 재정의하지 않고 ASCII 코드를 
70  * 직접 사용하는 방향으로 재정의된 데이터가 많지 않도록 하였다.
71  * 실제 사용 방법은 말로 설명하는 것보다 샘플 코드를 사용하는 편이
72  * 이해가 빠를 것이다. 그래서 대략적인 진행 과정을 샘플 코드로 
73  * 작성하였다.
74  *
75  * 아래 예제는 실제로는 존재하지 않는 GUI 라이브러리 코드를 사용하였다.
76  * 실제 GUI 코드를 사용하면 코드가 너무 길어져서 설명이 어렵고 코드가
77  * 길어지면 핵심을 놓치기 쉽기 때문에 가공의 함수를 사용하였다.
78  * 또한 텍스트의 encoding conversion 관련된 부분도 생략하였다.
79  * 여기서 사용한 가공의 GUI 코드는 TWin으로 시작하게 하였다.
80  *    
81  * @code
82
83     HangulInputContext* hic = hangul_ic_new("2");
84     ...
85
86     // 아래는 키 입력만 처리하는 이벤트 루프이다.
87     // 실제 GUI코드는 이렇게 단순하진 않지만
88     // 편의상 키 입력만 처리하는 코드로 작성하였다.
89
90     TWinKeyEvent event = TWinGetKeyEvent(); // 키이벤트를 받는 이런 함수가
91                                             // 있다고 치자
92     while (ascii != 0) {
93         bool res;
94         if (event.isBackspace()) {
95             // backspace를 ascii로 변환하기가 좀 꺼림직해서
96             // libhangul에서는 backspace 처리를 위한 
97             // 함수를 따로 만들었다.
98             res = hangul_ic_backspace(hic);
99         } else {
100             // 키 입력을 해당하는 ascii 코드로 변환한다.
101             // libhangul에서는 이 ascii 코드가 키 이벤트
102             // 코드와 마찬가지다.
103             int ascii = event.getAscii();
104
105             // 키 입력을 받았으면 이것을 hic에 먼저 보낸다.
106             // 그래야 hic가 이 키를 사용할 것인지 아닌지를 판단할 수 있다.
107             // 함수가 true를 리턴하면 이 키를 사용했다는 의미이므로 
108             // GUI 코드가 이 키 입력을 프로세싱하지 않도록 해야 한다.
109             // 그렇지 않으면 한 키입력이 두번 프로세싱된다.
110             res = hangul_ic_process(hic, ascii);
111         }
112         
113         // hic는 한번 키입력을 받고 나면 내부 상태 변화가 일어나고
114         // 완성된 글자를 어플리케이션에 보내야 하는 상황이 있을 수 있다.
115         // 이것을 HangulInputContext에서는 commit 스트링이 있는지로
116         // 판단한다. commit 스트링을 받아봐서 스트링이 있다면 
117         // 그 스트링으로 입력이 완료된 걸로 본다.
118         const ucschar commit;
119         commit = hangul_ic_get_commit_string(hic);
120         if (commit[0] != 0) {   // 스트링의 길이를 재서 commit 스트링이 있는지
121                                 // 판단한다.
122             TWinInputUnicodeChars(commit);
123         }
124
125         // 키입력 후에는 preedit string도 역시 변화하게 되는데
126         // 입력기 프레임웍에서는 이 스트링을 화면에 보여주어야
127         // 조합중인 글자가 화면에 표시가 되는 것이다.
128         const ucschar preedit;
129         preedit = hangul_ic_get_preedit_string(hic);
130         // 이 경우에는 스트링의 길이에 관계없이 항상 업데이트를 
131         // 해야 한다. 왜냐하면 이전에 조합중이던 글자가 있다가
132         // 조합이 완료되면서 조합중인 상태의 글자가 없어질 수도 있기 때문에
133         // 스트링의 길이에 관계없이 현재 상태의 스트링을 preedit 
134         // 스트링으로 보여주면 되는 것이다.
135         TWinUpdatePreeditString(preedit);
136
137         // 위 두작업이 끝난후에는 키 이벤트를 계속 프로세싱해야 하는지 
138         // 아닌지를 처리해야 한다.
139         // hic가 키 이벤트를 사용하지 않았다면 기본 GUI 코드에 계속해서
140         // 키 이벤트 프로세싱을 진행하도록 해야 한다.
141         if (!res)
142             TWinForwardKeyEventToUI(ascii);
143
144         ascii = GetKeyEvent();
145     }
146
147     hangul_ic_delete(hic);
148      
149  * @endcode
150  */
151
152 /**
153  * @file hangulinputcontext.c
154  */
155
156 /**
157  * @ingroup hangulic
158  * @typedef HangulInputContext
159  * @brief 한글 입력 상태를 관리하기 위한 오브젝트
160  *
161  * libhangul에서 제공하는 한글 조합 루틴에서 상태 정보를 저장하는 opaque
162  * 데이타 오브젝트이다. 이 오브젝트에 키입력 정보를 순차적으로 보내주면서
163  * preedit 스트링이나, commit 스트링을 받아서 처리하면 한글 입력 기능을
164  * 손쉽게 구현할 수 있다.
165  * 내부의 데이터 멤버는 공개되어 있지 않다. 각각의 멤버는 accessor 함수로만
166  * 참조하여야 한다.
167  */
168
169 #ifndef TRUE
170 #define TRUE 1
171 #endif
172
173 #ifndef FALSE
174 #define FALSE 0
175 #endif
176
177 #define HANGUL_KEYBOARD_TABLE_SIZE 0x80
178
179 typedef void   (*HangulOnTranslate)  (HangulInputContext*,
180                                       int,
181                                       ucschar*,
182                                       void*);
183 typedef bool   (*HangulOnTransition) (HangulInputContext*,
184                                       ucschar,
185                                       const ucschar*,
186                                       void*);
187
188 typedef struct _HangulCombinationItem HangulCombinationItem;
189
190 struct _HangulKeyboard {
191     int type;
192     const char* id;
193     const char* name;
194     const ucschar* table;
195     const HangulCombination* combination;
196 };
197
198 struct _HangulCombinationItem {
199     uint32_t key;
200     ucschar code;
201 };
202
203 struct _HangulCombination {
204     int size;
205     HangulCombinationItem *table;
206 };
207
208 struct _HangulBuffer {
209     ucschar choseong;
210     ucschar jungseong;
211     ucschar jongseong;
212
213     ucschar stack[12];
214     int     index;
215 };
216
217 struct _HangulInputContext {
218     int type;
219
220     const HangulKeyboard*    keyboard;
221
222     HangulBuffer buffer;
223     int output_mode;
224
225     ucschar preedit_string[64];
226     ucschar commit_string[64];
227     ucschar flushed_string[64];
228
229     HangulOnTranslate   on_translate;
230     void*               on_translate_data;
231
232     HangulOnTransition  on_transition;
233     void*               on_transition_data;
234
235     unsigned int use_jamo_mode_only : 1;
236 };
237
238 #include "hangulkeyboard.h"
239
240 static const HangulCombination hangul_combination_default = {
241     N_ELEMENTS(hangul_combination_table_default),
242     (HangulCombinationItem*)hangul_combination_table_default
243 };
244
245 static const HangulCombination hangul_combination_romaja = {
246     N_ELEMENTS(hangul_combination_table_romaja),
247     (HangulCombinationItem*)hangul_combination_table_romaja
248 };
249
250 static const HangulCombination hangul_combination_full = {
251     N_ELEMENTS(hangul_combination_table_full),
252     (HangulCombinationItem*)hangul_combination_table_full
253 };
254
255 static const HangulCombination hangul_combination_ahn = {
256     N_ELEMENTS(hangul_combination_table_ahn),
257     (HangulCombinationItem*)hangul_combination_table_ahn
258 };
259
260 static const HangulKeyboard hangul_keyboard_2 = {
261     HANGUL_KEYBOARD_TYPE_JAMO,
262     "2", 
263     N_("Dubeolsik"), 
264     (ucschar*)hangul_keyboard_table_2,
265     &hangul_combination_default
266 };
267
268 static const HangulKeyboard hangul_keyboard_2y = {
269     HANGUL_KEYBOARD_TYPE_JAMO,
270     "2y", 
271     N_("Dubeolsik Yetgeul"), 
272     (ucschar*)hangul_keyboard_table_2y,
273     &hangul_combination_full
274 };
275
276 static const HangulKeyboard hangul_keyboard_32 = {
277     HANGUL_KEYBOARD_TYPE_JASO,
278     "32",
279     N_("Sebeolsik Dubeol Layout"),
280     (ucschar*)hangul_keyboard_table_32,
281     &hangul_combination_default
282 };
283
284 static const HangulKeyboard hangul_keyboard_390 = {
285     HANGUL_KEYBOARD_TYPE_JASO,
286     "39",
287     N_("Sebeolsik 390"),
288     (ucschar*)hangul_keyboard_table_390,
289     &hangul_combination_default
290 };
291
292 static const HangulKeyboard hangul_keyboard_3final = {
293     HANGUL_KEYBOARD_TYPE_JASO,
294     "3f",
295     N_("Sebeolsik Final"),
296     (ucschar*)hangul_keyboard_table_3final,
297     &hangul_combination_default
298 };
299
300 static const HangulKeyboard hangul_keyboard_3sun = {
301     HANGUL_KEYBOARD_TYPE_JASO,
302     "3s",
303     N_("Sebeolsik Noshift"),
304     (ucschar*)hangul_keyboard_table_3sun,
305     &hangul_combination_default
306 };
307
308 static const HangulKeyboard hangul_keyboard_3yet = {
309     HANGUL_KEYBOARD_TYPE_JASO,
310     "3y",
311     N_("Sebeolsik Yetgeul"),
312     (ucschar*)hangul_keyboard_table_3yet,
313     &hangul_combination_full
314 };
315
316 static const HangulKeyboard hangul_keyboard_romaja = {
317     HANGUL_KEYBOARD_TYPE_ROMAJA,
318     "ro",
319     N_("Romaja"),
320     (ucschar*)hangul_keyboard_table_romaja,
321     &hangul_combination_romaja
322 };
323
324 static const HangulKeyboard hangul_keyboard_ahn = {
325     HANGUL_KEYBOARD_TYPE_JASO,
326     "ahn",
327     N_("Ahnmatae"),
328     (ucschar*)hangul_keyboard_table_ahn,
329     &hangul_combination_ahn
330 };
331
332 static const HangulKeyboard* hangul_keyboards[] = {
333     &hangul_keyboard_2,
334     &hangul_keyboard_2y,
335     &hangul_keyboard_390,
336     &hangul_keyboard_3final,
337     &hangul_keyboard_3sun,
338     &hangul_keyboard_3yet,
339     &hangul_keyboard_32,
340     &hangul_keyboard_romaja,
341     &hangul_keyboard_ahn,
342 };
343
344
345 static void    hangul_buffer_push(HangulBuffer *buffer, ucschar ch);
346 static ucschar hangul_buffer_pop (HangulBuffer *buffer);
347 static ucschar hangul_buffer_peek(HangulBuffer *buffer);
348
349 static void    hangul_buffer_clear(HangulBuffer *buffer);
350 static int     hangul_buffer_get_string(HangulBuffer *buffer, ucschar*buf, int buflen);
351 static int     hangul_buffer_get_jamo_string(HangulBuffer *buffer, ucschar *buf, int buflen);
352
353 static void    hangul_ic_flush_internal(HangulInputContext *hic);
354
355 HangulKeyboard*
356 hangul_keyboard_new()
357 {
358     HangulKeyboard *keyboard = malloc(sizeof(HangulKeyboard));
359     if (keyboard != NULL) {
360         ucschar* table = malloc(sizeof(ucschar) * HANGUL_KEYBOARD_TABLE_SIZE);
361         if (table != NULL) {
362             int i;
363             for (i = 0; i < HANGUL_KEYBOARD_TABLE_SIZE; i++)
364                 table[i] = 0;
365
366             keyboard->table = table;
367             return keyboard;
368         }
369         free(keyboard);
370     }
371
372     return NULL;
373 }
374
375 static ucschar
376 hangul_keyboard_get_value(const HangulKeyboard *keyboard, int key)
377 {
378     if (keyboard != NULL) {
379         if (key >= 0 && key < HANGUL_KEYBOARD_TABLE_SIZE)
380             return keyboard->table[key];
381     }
382
383     return 0;
384 }
385
386 void
387 hangul_keyboard_set_value(HangulKeyboard *keyboard, int key, ucschar value)
388 {
389     if (keyboard != NULL) {
390         if (key >= 0 && key < N_ELEMENTS(keyboard->table)) {
391             ucschar* table = (ucschar*)keyboard->table;
392             table[key] = value;
393         }
394     }
395 }
396
397 static int
398 hangul_keyboard_get_type(const HangulKeyboard *keyboard)
399 {
400     int type = 0;
401     if (keyboard != NULL) {
402         type = keyboard->type;
403     }
404     return type;
405 }
406
407 void
408 hangul_keyboard_set_type(HangulKeyboard *keyboard, int type)
409 {
410     if (keyboard != NULL) {
411         keyboard->type = type;
412     }
413 }
414
415 void
416 hangul_keyboard_delete(HangulKeyboard *keyboard)
417 {
418     if (keyboard != NULL)
419         free(keyboard);
420 }
421
422 HangulCombination*
423 hangul_combination_new()
424 {
425     HangulCombination *combination = malloc(sizeof(HangulCombination));
426     if (combination != NULL) {
427         combination->size = 0;
428         combination->table = NULL;
429         return combination;
430     }
431
432     return NULL;
433 }
434
435 void
436 hangul_combination_delete(HangulCombination *combination)
437 {
438     if (combination != NULL) {
439         if (combination->table != NULL)
440             free(combination->table);
441         free(combination);
442     }
443 }
444
445 static uint32_t
446 hangul_combination_make_key(ucschar first, ucschar second)
447 {
448     return first << 16 | second;
449 }
450
451 bool
452 hangul_combination_set_data(HangulCombination* combination, 
453                             ucschar* first, ucschar* second, ucschar* result,
454                             unsigned int n)
455 {
456     if (combination == NULL)
457         return false;
458
459     if (n == 0 || n > ULONG_MAX / sizeof(HangulCombinationItem))
460         return false;
461
462     combination->table = malloc(sizeof(HangulCombinationItem) * n);
463     if (combination->table != NULL) {
464         int i;
465
466         combination->size = n;
467         for (i = 0; i < n; i++) {
468             combination->table[i].key = hangul_combination_make_key(first[i], second[i]);
469             combination->table[i].code = result[i];
470         }
471         return true;
472     }
473
474     return false;
475 }
476
477 static int 
478 hangul_combination_cmp(const void* p1, const void* p2)
479 {
480     const HangulCombinationItem *item1 = p1;
481     const HangulCombinationItem *item2 = p2;
482
483     /* key는 unsigned int이므로 단순히 빼서 리턴하면 안된다.
484      * 두 수의 차가 큰 경우 int로 변환하면서 음수가 될 수 있다. */
485     if (item1->key < item2->key)
486         return -1;
487     else if (item1->key > item2->key)
488         return 1;
489     else
490         return 0;
491 }
492
493 ucschar
494 hangul_combination_combine(const HangulCombination* combination,
495                            ucschar first, ucschar second)
496 {
497     HangulCombinationItem *res;
498     HangulCombinationItem key;
499
500     if (combination == NULL)
501         return 0;
502
503     key.key = hangul_combination_make_key(first, second);
504     res = bsearch(&key, combination->table, combination->size,
505                   sizeof(combination->table[0]), hangul_combination_cmp);
506     if (res != NULL)
507         return res->code;
508
509     return 0;
510 }
511
512 static bool
513 hangul_buffer_is_empty(HangulBuffer *buffer)
514 {
515     return buffer->choseong == 0 && buffer->jungseong == 0 &&
516            buffer->jongseong == 0;
517 }
518
519 static bool
520 hangul_buffer_has_choseong(HangulBuffer *buffer)
521 {
522     return buffer->choseong != 0;
523 }
524
525 static bool
526 hangul_buffer_has_jungseong(HangulBuffer *buffer)
527 {
528     return buffer->jungseong != 0;
529 }
530
531 static bool
532 hangul_buffer_has_jongseong(HangulBuffer *buffer)
533 {
534     return buffer->jongseong != 0;
535 }
536
537 static void
538 hangul_buffer_push(HangulBuffer *buffer, ucschar ch)
539 {
540     if (hangul_is_choseong(ch)) {
541         buffer->choseong = ch;
542     } else if (hangul_is_jungseong(ch)) {
543         buffer->jungseong = ch;
544     } else if (hangul_is_jongseong(ch)) {
545         buffer->jongseong = ch;
546     } else {
547     }
548
549     buffer->stack[++buffer->index] = ch;
550 }
551
552 static ucschar
553 hangul_buffer_pop(HangulBuffer *buffer)
554 {
555     return buffer->stack[buffer->index--];
556 }
557
558 static ucschar
559 hangul_buffer_peek(HangulBuffer *buffer)
560 {
561     if (buffer->index < 0)
562         return 0;
563
564     return buffer->stack[buffer->index];
565 }
566
567 static void
568 hangul_buffer_clear(HangulBuffer *buffer)
569 {
570     buffer->choseong = 0;
571     buffer->jungseong = 0;
572     buffer->jongseong = 0;
573
574     buffer->index = -1;
575     buffer->stack[0]  = 0;
576     buffer->stack[1]  = 0;
577     buffer->stack[2]  = 0;
578     buffer->stack[3]  = 0;
579     buffer->stack[4]  = 0;
580     buffer->stack[5]  = 0;
581     buffer->stack[6]  = 0;
582     buffer->stack[7]  = 0;
583     buffer->stack[8]  = 0;
584     buffer->stack[9]  = 0;
585     buffer->stack[10] = 0;
586     buffer->stack[11] = 0;
587 }
588
589 static int
590 hangul_buffer_get_jamo_string(HangulBuffer *buffer, ucschar *buf, int buflen)
591 {
592     int n = 0;
593
594     if (buffer->choseong || buffer->jungseong || buffer->jongseong) {
595         if (buffer->choseong) {
596             buf[n++] = buffer->choseong;
597         } else {
598             buf[n++] = HANGUL_CHOSEONG_FILLER;
599         }
600         if (buffer->jungseong) {
601             buf[n++] = buffer->jungseong;
602         } else {
603             buf[n++] = HANGUL_JUNGSEONG_FILLER;
604         }
605         if (buffer->jongseong) {
606             buf[n++] = buffer->jongseong;
607         }
608     }
609
610     buf[n] = 0;
611
612     return n;
613 }
614
615 static int
616 hangul_jaso_to_string(ucschar cho, ucschar jung, ucschar jong,
617                       ucschar *buf, int len)
618 {
619     ucschar ch = 0;
620     int n = 0;
621
622     if (cho) {
623         if (jung) {
624             /* have cho, jung, jong or no jong */
625             ch = hangul_jamo_to_syllable(cho, jung, jong);
626             if (ch != 0) {
627                 buf[n++] = ch;
628             } else {
629                 /* 한글 음절로 표현 불가능한 경우 */
630                 buf[n++] = cho;
631                 buf[n++] = jung;
632                 if (jong != 0)
633                     buf[n++] = jong;
634             }
635         } else {
636             if (jong) {
637                 /* have cho, jong */
638                 buf[n++] = cho;
639                 buf[n++] = HANGUL_JUNGSEONG_FILLER;
640                 buf[n++] = jong;
641             } else {
642                 /* have cho */
643                 ch = hangul_jamo_to_cjamo(cho);
644                 if (hangul_is_cjamo(ch)) {
645                     buf[n++] = ch;
646                 } else {
647                     buf[n++] = cho;
648                     buf[n++] = HANGUL_JUNGSEONG_FILLER;
649                 }
650             }
651         }
652     } else {
653         if (jung) {
654             if (jong) {
655                 /* have jung, jong */
656                 buf[n++] = HANGUL_CHOSEONG_FILLER;
657                 buf[n++] = jung;
658                 buf[n++] = jong;
659             } else {
660                 /* have jung */
661                 ch = hangul_jamo_to_cjamo(jung);
662                 if (hangul_is_cjamo(ch)) {
663                     buf[n++] = ch;
664                 } else {
665                     buf[n++] = HANGUL_CHOSEONG_FILLER;
666                     buf[n++] = jung;
667                 }
668             }
669         } else {
670             if (jong) { 
671                 /* have jong */
672                 ch = hangul_jamo_to_cjamo(jong);
673                 if (hangul_is_cjamo(ch)) {
674                     buf[n++] = ch;
675                 } else {
676                     buf[n++] = HANGUL_CHOSEONG_FILLER;
677                     buf[n++] = HANGUL_JUNGSEONG_FILLER;
678                     buf[n++] = jong;
679                 }
680             } else {
681                 /* have nothing */
682                 buf[n] = 0;
683             }
684         }
685     }
686     buf[n] = 0;
687
688     return n;
689 }
690
691 static int
692 hangul_buffer_get_string(HangulBuffer *buffer, ucschar *buf, int buflen)
693 {
694     return hangul_jaso_to_string(buffer->choseong,
695                                  buffer->jungseong,
696                                  buffer->jongseong,
697                                  buf, buflen);
698 }
699
700 static bool
701 hangul_buffer_backspace(HangulBuffer *buffer)
702 {
703     if (buffer->index >= 0) {
704         ucschar ch = hangul_buffer_pop(buffer);
705         if (ch == 0)
706             return false;
707
708         if (buffer->index >= 0) {
709             if (hangul_is_choseong(ch)) {
710                 ch = hangul_buffer_peek(buffer);
711                 buffer->choseong = hangul_is_choseong(ch) ? ch : 0;
712                 return true;
713             } else if (hangul_is_jungseong(ch)) {
714                 ch = hangul_buffer_peek(buffer);
715                 buffer->jungseong = hangul_is_jungseong(ch) ? ch : 0;
716                 return true;
717             } else if (hangul_is_jongseong(ch)) {
718                 ch = hangul_buffer_peek(buffer);
719                 buffer->jongseong = hangul_is_jongseong(ch) ? ch : 0;
720                 return true;
721             }
722         } else {
723             buffer->choseong = 0;
724             buffer->jungseong = 0;
725             buffer->jongseong = 0;
726             return true;
727         }
728     }
729     return false;
730 }
731
732 static inline bool
733 hangul_ic_push(HangulInputContext *hic, ucschar c)
734 {
735     ucschar buf[64] = { 0, };
736     if (hic->on_transition != NULL) {
737         ucschar cho, jung, jong;
738         if (hangul_is_choseong(c)) {
739             cho  = c;
740             jung = hic->buffer.jungseong;
741             jong = hic->buffer.jongseong;
742         } else if (hangul_is_jungseong(c)) {
743             cho  = hic->buffer.choseong;
744             jung = c;
745             jong = hic->buffer.jongseong;
746         } else if (hangul_is_jongseong(c)) {
747             cho  = hic->buffer.choseong;
748             jung = hic->buffer.jungseong;
749             jong = c;
750         } else {
751             hangul_ic_flush_internal(hic);
752             return false;
753         }
754
755         hangul_jaso_to_string(cho, jung, jong, buf, N_ELEMENTS(buf));
756         if (!hic->on_transition(hic, c, buf, hic->on_transition_data)) {
757             hangul_ic_flush_internal(hic);
758             return false;
759         }
760     } else {
761         if (!hangul_is_jamo(c)) {
762             hangul_ic_flush_internal(hic);
763             return false;
764         }
765     }
766
767     hangul_buffer_push(&hic->buffer, c);
768     return true;
769 }
770
771 static inline ucschar
772 hangul_ic_pop(HangulInputContext *hic)
773 {
774     return hangul_buffer_pop(&hic->buffer);
775 }
776
777 static inline ucschar
778 hangul_ic_peek(HangulInputContext *hic)
779 {
780     return hangul_buffer_peek(&hic->buffer);
781 }
782
783 static inline void
784 hangul_ic_save_preedit_string(HangulInputContext *hic)
785 {
786     if (hic->output_mode == HANGUL_OUTPUT_JAMO) {
787         hangul_buffer_get_jamo_string(&hic->buffer,
788                                       hic->preedit_string,
789                                       N_ELEMENTS(hic->preedit_string));
790     } else {
791         hangul_buffer_get_string(&hic->buffer,
792                                  hic->preedit_string,
793                                  N_ELEMENTS(hic->preedit_string));
794     }
795 }
796
797 static inline void
798 hangul_ic_append_commit_string(HangulInputContext *hic, ucschar ch)
799 {
800     int i;
801
802     for (i = 0; i < N_ELEMENTS(hic->commit_string); i++) {
803         if (hic->commit_string[i] == 0)
804             break;
805     }
806
807     if (i + 1 < N_ELEMENTS(hic->commit_string)) {
808         hic->commit_string[i++] = ch;
809         hic->commit_string[i] = 0;
810     }
811 }
812
813 static inline void
814 hangul_ic_save_commit_string(HangulInputContext *hic)
815 {
816     ucschar *string = hic->commit_string;
817     int len = N_ELEMENTS(hic->commit_string);
818
819     while (len > 0) {
820         if (*string == 0)
821             break;
822         len--;
823         string++;
824     }
825
826     if (hic->output_mode == HANGUL_OUTPUT_JAMO) {
827         hangul_buffer_get_jamo_string(&hic->buffer, string, len);
828     } else {
829         hangul_buffer_get_string(&hic->buffer, string, len);
830     }
831
832     hangul_buffer_clear(&hic->buffer);
833 }
834
835 static ucschar
836 hangul_ic_choseong_to_jongseong(HangulInputContext* hic, ucschar cho)
837 {
838     ucschar jong = hangul_choseong_to_jongseong(cho);
839     if (hangul_is_jongseong_conjoinable(jong)) {
840         return jong;
841     } else {
842         /* 옛글 조합 규칙을 사용하는 자판의 경우에는 종성이 conjoinable
843          * 하지 않아도 상관없다 */
844         if (hic->keyboard->combination == &hangul_combination_full) {
845             return jong;
846         }
847     }
848
849     return 0;
850 }
851
852 static bool
853 hangul_ic_process_jamo(HangulInputContext *hic, ucschar ch)
854 {
855     ucschar jong;
856     ucschar combined;
857
858     if (!hangul_is_jamo(ch) && ch > 0) {
859         hangul_ic_save_commit_string(hic);
860         hangul_ic_append_commit_string(hic, ch);
861         return true;
862     }
863
864     if (hic->buffer.jongseong) {
865         if (hangul_is_choseong(ch)) {
866             jong = hangul_ic_choseong_to_jongseong(hic, ch);
867             combined = hangul_combination_combine(hic->keyboard->combination,
868                                               hic->buffer.jongseong, jong);
869             if (hangul_is_jongseong(combined)) {
870                 if (!hangul_ic_push(hic, combined)) {
871                     if (!hangul_ic_push(hic, ch)) {
872                         return false;
873                     }
874                 }
875             } else {
876                 hangul_ic_save_commit_string(hic);
877                 if (!hangul_ic_push(hic, ch)) {
878                     return false;
879                 }
880             }
881         } else if (hangul_is_jungseong(ch)) {
882             ucschar pop, peek;
883             pop = hangul_ic_pop(hic);
884             peek = hangul_ic_peek(hic);
885
886             if (hangul_is_jongseong(peek)) {
887                 ucschar choseong = hangul_jongseong_get_diff(peek,
888                                                  hic->buffer.jongseong);
889                 if (choseong == 0) {
890                     hangul_ic_save_commit_string(hic);
891                     if (!hangul_ic_push(hic, ch)) {
892                         return false;
893                     }
894                 } else {
895                     hic->buffer.jongseong = peek;
896                     hangul_ic_save_commit_string(hic);
897                     hangul_ic_push(hic, choseong);
898                     if (!hangul_ic_push(hic, ch)) {
899                         return false;
900                     }
901                 }
902             } else {
903                 hic->buffer.jongseong = 0;
904                 hangul_ic_save_commit_string(hic);
905                 hangul_ic_push(hic, hangul_jongseong_to_choseong(pop));
906                 if (!hangul_ic_push(hic, ch)) {
907                     return false;
908                 }
909             }
910         } else {
911             goto flush;
912         }
913     } else if (hic->buffer.jungseong) {
914         if (hangul_is_choseong(ch)) {
915             if (hic->buffer.choseong) {
916                 jong = hangul_ic_choseong_to_jongseong(hic, ch);
917                 if (hangul_is_jongseong(jong)) {
918                     if (!hangul_ic_push(hic, jong)) {
919                         if (!hangul_ic_push(hic, ch)) {
920                             return false;
921                         }
922                     }
923                 } else {
924                     hangul_ic_save_commit_string(hic);
925                     if (!hangul_ic_push(hic, ch)) {
926                         return false;
927                     }
928                 }
929             } else {
930                 if (!hangul_ic_push(hic, ch)) {
931                     if (!hangul_ic_push(hic, ch)) {
932                         return false;
933                     }
934                 }
935             }
936         } else if (hangul_is_jungseong(ch)) {
937             combined = hangul_combination_combine(hic->keyboard->combination,
938                                                   hic->buffer.jungseong, ch);
939             if (hangul_is_jungseong(combined)) {
940                 if (!hangul_ic_push(hic, combined)) {
941                     return false;
942                 }
943             } else {
944                 hangul_ic_save_commit_string(hic);
945                 if (!hangul_ic_push(hic, ch)) {
946                     return false;
947                 }
948             }
949         } else {
950             goto flush;
951         }
952     } else if (hic->buffer.choseong) {
953         if (hangul_is_choseong(ch)) {
954             combined = hangul_combination_combine(hic->keyboard->combination,
955                                                   hic->buffer.choseong, ch);
956             if (!hangul_ic_push(hic, combined)) {
957                 if (!hangul_ic_push(hic, ch)) {
958                     return false;
959                 }
960             }
961         } else {
962             if (!hangul_ic_push(hic, ch)) {
963                 if (!hangul_ic_push(hic, ch)) {
964                     return false;
965                 }
966             }
967         }
968     } else {
969         if (!hangul_ic_push(hic, ch)) {
970             return false;
971         }
972     }
973
974     hangul_ic_save_preedit_string(hic);
975     return true;
976
977 flush:
978     hangul_ic_flush_internal(hic);
979     return false;
980 }
981
982 static bool
983 hangul_ic_process_jaso(HangulInputContext *hic, ucschar ch)
984 {
985     if (hangul_is_choseong(ch)) {
986         if (hic->buffer.choseong == 0) {
987             if (!hangul_ic_push(hic, ch)) {
988                 if (!hangul_ic_push(hic, ch)) {
989                     return false;
990                 }
991             }
992         } else {
993             ucschar choseong = 0;
994             if (hangul_is_choseong(hangul_ic_peek(hic))) {
995                 choseong = hangul_combination_combine(hic->keyboard->combination,
996                                                   hic->buffer.choseong, ch);
997             }
998             if (choseong) {
999                 if (!hangul_ic_push(hic, choseong)) {
1000                     if (!hangul_ic_push(hic, choseong)) {
1001                         return false;
1002                     }
1003                 }
1004             } else {
1005                 hangul_ic_save_commit_string(hic);
1006                 if (!hangul_ic_push(hic, ch)) {
1007                     return false;
1008                 }
1009             }
1010         }
1011     } else if (hangul_is_jungseong(ch)) {
1012         if (hic->buffer.jungseong == 0) {
1013             if (!hangul_ic_push(hic, ch)) {
1014                 if (!hangul_ic_push(hic, ch)) {
1015                     return false;
1016                 }
1017             }
1018         } else {
1019             ucschar jungseong = 0;
1020             if (hangul_is_jungseong(hangul_ic_peek(hic))) {
1021                 jungseong = hangul_combination_combine(hic->keyboard->combination,
1022                                                  hic->buffer.jungseong, ch);
1023             }
1024             if (jungseong) {
1025                 if (!hangul_ic_push(hic, jungseong)) {
1026                     if (!hangul_ic_push(hic, jungseong)) {
1027                         return false;
1028                     }
1029                 }
1030             } else {
1031                 hangul_ic_save_commit_string(hic);
1032                 if (!hangul_ic_push(hic, ch)) {
1033                     if (!hangul_ic_push(hic, ch)) {
1034                         return false;
1035                     }
1036                 }
1037             }
1038         }
1039     } else if (hangul_is_jongseong(ch)) {
1040         if (hic->buffer.jongseong == 0) {
1041             if (!hangul_ic_push(hic, ch)) {
1042                 if (!hangul_ic_push(hic, ch)) {
1043                     return false;
1044                 }
1045             }
1046         } else {
1047             ucschar jongseong = 0;
1048             if (hangul_is_jongseong(hangul_ic_peek(hic))) {
1049                 jongseong = hangul_combination_combine(hic->keyboard->combination,
1050                                                    hic->buffer.jongseong, ch);
1051             }
1052             if (jongseong) {
1053                 if (!hangul_ic_push(hic, jongseong)) {
1054                     if (!hangul_ic_push(hic, jongseong)) {
1055                         return false;
1056                     }
1057                 }
1058             } else {
1059                 hangul_ic_save_commit_string(hic);
1060                 if (!hangul_ic_push(hic, ch)) {
1061                     if (!hangul_ic_push(hic, ch)) {
1062                         return false;
1063                     }
1064                 }
1065             }
1066         }
1067     } else if (ch > 0) {
1068         hangul_ic_save_commit_string(hic);
1069         hangul_ic_append_commit_string(hic, ch);
1070     } else {
1071         hangul_ic_save_commit_string(hic);
1072         return false;
1073     }
1074
1075     hangul_ic_save_preedit_string(hic);
1076     return true;
1077 }
1078
1079 static bool
1080 hangul_ic_process_romaja(HangulInputContext *hic, int ascii, ucschar ch)
1081 {
1082     ucschar jong;
1083     ucschar combined;
1084
1085     if (!hangul_is_jamo(ch) && ch > 0) {
1086         hangul_ic_save_commit_string(hic);
1087         hangul_ic_append_commit_string(hic, ch);
1088         return true;
1089     }
1090
1091     if (isupper(ascii)) {
1092         hangul_ic_save_commit_string(hic);
1093     }
1094
1095     if (hic->buffer.jongseong) {
1096         if (ascii == 'x' || ascii == 'X') {
1097             ch = 0x110c;
1098             hangul_ic_save_commit_string(hic);
1099             if (!hangul_ic_push(hic, ch)) {
1100                 return false;
1101             }
1102         } else if (hangul_is_choseong(ch) || hangul_is_jongseong(ch)) {
1103             if (hangul_is_jongseong(ch))
1104                 jong = ch;
1105             else
1106                 jong = hangul_ic_choseong_to_jongseong(hic, ch);
1107             combined = hangul_combination_combine(hic->keyboard->combination,
1108                                               hic->buffer.jongseong, jong);
1109             if (hangul_is_jongseong(combined)) {
1110                 if (!hangul_ic_push(hic, combined)) {
1111                     if (!hangul_ic_push(hic, ch)) {
1112                         return false;
1113                     }
1114                 }
1115             } else {
1116                 hangul_ic_save_commit_string(hic);
1117                 if (!hangul_ic_push(hic, ch)) {
1118                     return false;
1119                 }
1120             }
1121         } else if (hangul_is_jungseong(ch)) {
1122             if (hic->buffer.jongseong == 0x11bc) {
1123                 hangul_ic_save_commit_string(hic);
1124                 hic->buffer.choseong = 0x110b;
1125                 hangul_ic_push(hic, ch);
1126             } else {
1127                 ucschar pop, peek;
1128                 pop = hangul_ic_pop(hic);
1129                 peek = hangul_ic_peek(hic);
1130
1131                 if (hangul_is_jungseong(peek)) {
1132                     if (pop == 0x11aa) {
1133                         hic->buffer.jongseong = 0x11a8;
1134                         pop = 0x11ba;
1135                     } else {
1136                         hic->buffer.jongseong = 0;
1137                     }
1138                     hangul_ic_save_commit_string(hic);
1139                     hangul_ic_push(hic, hangul_jongseong_to_choseong(pop));
1140                     if (!hangul_ic_push(hic, ch)) {
1141                         return false;
1142                     }
1143                 } else {
1144                     ucschar choseong = 0, jongseong = 0; 
1145                     hangul_jongseong_decompose(hic->buffer.jongseong,
1146                                                &jongseong, &choseong);
1147                     hic->buffer.jongseong = jongseong;
1148                     hangul_ic_save_commit_string(hic);
1149                     hangul_ic_push(hic, choseong);
1150                     if (!hangul_ic_push(hic, ch)) {
1151                         return false;
1152                     }
1153                 }
1154             }
1155         } else {
1156             goto flush;
1157         }
1158     } else if (hic->buffer.jungseong) {
1159         if (hangul_is_choseong(ch)) {
1160             if (hic->buffer.choseong) {
1161                 jong = hangul_ic_choseong_to_jongseong(hic, ch);
1162                 if (hangul_is_jongseong(jong)) {
1163                     if (!hangul_ic_push(hic, jong)) {
1164                         if (!hangul_ic_push(hic, ch)) {
1165                             return false;
1166                         }
1167                     }
1168                 } else {
1169                     hangul_ic_save_commit_string(hic);
1170                     if (!hangul_ic_push(hic, ch)) {
1171                         return false;
1172                     }
1173                 }
1174             } else {
1175                 if (!hangul_ic_push(hic, ch)) {
1176                     if (!hangul_ic_push(hic, ch)) {
1177                         return false;
1178                     }
1179                 }
1180             }
1181         } else if (hangul_is_jungseong(ch)) {
1182             combined = hangul_combination_combine(hic->keyboard->combination,
1183                                                   hic->buffer.jungseong, ch);
1184             if (hangul_is_jungseong(combined)) {
1185                 if (!hangul_ic_push(hic, combined)) {
1186                     return false;
1187                 }
1188             } else {
1189                 hangul_ic_save_commit_string(hic);
1190                 hic->buffer.choseong = 0x110b;
1191                 if (!hangul_ic_push(hic, ch)) {
1192                     return false;
1193                 }
1194             }
1195         } else if (hangul_is_jongseong(ch)) {
1196             if (!hangul_ic_push(hic, ch)) {
1197                 if (!hangul_ic_push(hic, ch)) {
1198                     return false;
1199                 }
1200             }
1201         } else {
1202             goto flush;
1203         }
1204     } else if (hic->buffer.choseong) {
1205         if (hangul_is_choseong(ch)) {
1206             combined = hangul_combination_combine(hic->keyboard->combination,
1207                                                   hic->buffer.choseong, ch);
1208             if (combined == 0) {
1209                 hic->buffer.jungseong = 0x1173;
1210                 hangul_ic_flush_internal(hic);
1211                 if (!hangul_ic_push(hic, ch)) {
1212                     return false;
1213                 }
1214             } else {
1215                 if (!hangul_ic_push(hic, combined)) {
1216                     if (!hangul_ic_push(hic, ch)) {
1217                         return false;
1218                     }
1219                 }
1220             }
1221         } else if (hangul_is_jongseong(ch)) {
1222             hic->buffer.jungseong = 0x1173;
1223             hangul_ic_save_commit_string(hic);
1224             if (ascii == 'x' || ascii == 'X')
1225                 ch = 0x110c;
1226             if (!hangul_ic_push(hic, ch)) {
1227                 return false;
1228             }
1229         } else {
1230             if (!hangul_ic_push(hic, ch)) {
1231                 if (!hangul_ic_push(hic, ch)) {
1232                     return false;
1233                 }
1234             }
1235         }
1236     } else {
1237         if (ascii == 'x' || ascii == 'X') {
1238             ch = 0x110c;
1239         }
1240
1241         if (!hangul_ic_push(hic, ch)) {
1242             return false;
1243         } else {
1244             if (hic->buffer.choseong == 0 && hic->buffer.jungseong != 0)
1245                 hic->buffer.choseong = 0x110b;
1246         }
1247     }
1248
1249     hangul_ic_save_preedit_string(hic);
1250     return true;
1251
1252 flush:
1253     hangul_ic_flush_internal(hic);
1254     return false;
1255 }
1256
1257 /**
1258  * @ingroup hangulic
1259  * @brief 키 입력을 처리하여 실제로 한글 조합을 하는 함수
1260  * @param hic @ref HangulInputContext 오브젝트
1261  * @param ascii 키 이벤트
1262  * @return @ref HangulInputContext가 이 키를 사용했으면 true,
1263  *           사용하지 않았으면 false
1264  *
1265  * ascii 값으로 주어진 키 이벤트를 받아서 내부의 한글 조합 상태를
1266  * 변화시키고, preedit, commit 스트링을 저장한다.
1267  *
1268  * libhangul의 키 이벤트 프로세스는 ASCII 코드 값을 기준으로 처리한다.
1269  * 이 키 값은 US Qwerty 자판 배열에서의 키 값에 해당한다.
1270  * 따라서 유럽어 자판을 사용하는 경우에는 해당 키의 ASCII 코드를 직접
1271  * 전달하면 안되고, 그 키가 US Qwerty 자판이었을 경우에 발생할 수 있는 
1272  * ASCII 코드 값을 주어야 한다.
1273  * 또한 ASCII 코드 이므로 Shift 상태는 대문자로 전달이 된다.
1274  * Capslock이 눌린 경우에는 대소문자를 뒤바꾸어 보내주지 않으면 
1275  * 마치 Shift가 눌린 것 처럼 동작할 수 있으므로 주의한다.
1276  * preedit, commit 스트링은 hangul_ic_get_preedit_string(),
1277  * hangul_ic_get_commit_string() 함수를 이용하여 구할 수 있다.
1278  * 
1279  * 이 함수의 사용법에 대한 설명은 @ref hangulicusage 부분을 참조한다.
1280  *
1281  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시킨다.
1282  */
1283 bool
1284 hangul_ic_process(HangulInputContext *hic, int ascii)
1285 {
1286     ucschar c;
1287
1288     if (hic == NULL)
1289         return false;
1290
1291     hic->preedit_string[0] = 0;
1292     hic->commit_string[0] = 0;
1293
1294     c = hangul_keyboard_get_value(hic->keyboard, ascii);
1295     if (hic->on_translate != NULL)
1296         hic->on_translate(hic, ascii, &c, hic->on_translate_data);
1297
1298     if (hangul_keyboard_get_type(hic->keyboard) == HANGUL_KEYBOARD_TYPE_JAMO)
1299         return hangul_ic_process_jamo(hic, c);
1300     else if (hangul_keyboard_get_type(hic->keyboard) == HANGUL_KEYBOARD_TYPE_JASO)
1301         return hangul_ic_process_jaso(hic, c);
1302     else
1303         return hangul_ic_process_romaja(hic, ascii, c);
1304 }
1305
1306 /**
1307  * @ingroup hangulic
1308  * @brief 현재 상태의 preedit string을 구하는 함수
1309  * @param hic preedit string을 구하고자하는 입력 상태 object
1310  * @return UCS4 preedit 스트링, 이 스트링은 @a hic 내부의 데이터이므로 
1311  *         수정하거나 free해서는 안된다.
1312  * 
1313  * 이 함수는  @a hic 내부의 현재 상태의 preedit string을 리턴한다.
1314  * 따라서 hic가 다른 키 이벤트를 처리하고 나면 그 내용이 바뀔 수 있다.
1315  * 
1316  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다.
1317  */
1318 const ucschar*
1319 hangul_ic_get_preedit_string(HangulInputContext *hic)
1320 {
1321     if (hic == NULL)
1322         return NULL;
1323
1324     return hic->preedit_string;
1325 }
1326
1327 /**
1328  * @ingroup hangulic
1329  * @brief 현재 상태의 commit string을 구하는 함수
1330  * @param hic commit string을 구하고자하는 입력 상태 object
1331  * @return UCS4 commit 스트링, 이 스트링은 @a hic 내부의 데이터이므로 
1332  *         수정하거나 free해서는 안된다.
1333  * 
1334  * 이 함수는  @a hic 내부의 현재 상태의 commit string을 리턴한다.
1335  * 따라서 hic가 다른 키 이벤트를 처리하고 나면 그 내용이 바뀔 수 있다.
1336  *
1337  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다.
1338  */
1339 const ucschar*
1340 hangul_ic_get_commit_string(HangulInputContext *hic)
1341 {
1342     if (hic == NULL)
1343         return NULL;
1344
1345     return hic->commit_string;
1346 }
1347
1348 /**
1349  * @ingroup hangulic
1350  * @brief @ref HangulInputContext를 초기상태로 되돌리는 함수
1351  * @param hic @ref HangulInputContext를 가리키는 포인터
1352  * 
1353  * 이 함수는 @a hic가 가리키는 @ref HangulInputContext의 상태를 
1354  * 처음 상태로 되돌린다. preedit 스트링, commit 스트링, flush 스트링이
1355  * 없어지고, 입력되었던 키에 대한 기록이 없어진다.
1356  * 영어 상태로 바뀌는 것이 아니다.
1357  *
1358  * 비교: hangul_ic_flush()
1359  *
1360  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시킨다.
1361  */
1362 void
1363 hangul_ic_reset(HangulInputContext *hic)
1364 {
1365     if (hic == NULL)
1366         return;
1367
1368     hic->preedit_string[0] = 0;
1369     hic->commit_string[0] = 0;
1370     hic->flushed_string[0] = 0;
1371
1372     hangul_buffer_clear(&hic->buffer);
1373 }
1374
1375 /* append current preedit to the commit buffer.
1376  * this function does not clear previously made commit string. */
1377 static void
1378 hangul_ic_flush_internal(HangulInputContext *hic)
1379 {
1380     hic->preedit_string[0] = 0;
1381
1382     hangul_ic_save_commit_string(hic);
1383     hangul_buffer_clear(&hic->buffer);
1384 }
1385
1386 /**
1387  * @ingroup hangulic
1388  * @brief @ref HangulInputContext의 입력 상태를 완료하는 함수
1389  * @param hic @ref HangulInputContext를 가리키는 포인터
1390  * @return 조합 완료된 스트링, 스트링의 길이가 0이면 조합 완료된 스트링이 
1391  *        없는 것
1392  *
1393  * 이 함수는 @a hic가 가리키는 @ref HangulInputContext의 입력 상태를 완료한다.
1394  * 조합중이던 스트링을 완성하여 리턴한다. 그리고 입력 상태가 초기 상태로 
1395  * 되돌아 간다. 조합중이던 글자를 강제로 commit하고 싶을때 사용하는 함수다.
1396  * 보통의 경우 입력 framework에서 focus가 나갈때 이 함수를 불러서 마지막 
1397  * 상태를 완료해야 조합중이던 글자를 잃어버리지 않게 된다.
1398  *
1399  * 비교: hangul_ic_reset()
1400  *
1401  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시킨다.
1402  */
1403 const ucschar*
1404 hangul_ic_flush(HangulInputContext *hic)
1405 {
1406     if (hic == NULL)
1407         return NULL;
1408
1409     // get the remaining string and clear the buffer
1410     hic->preedit_string[0] = 0;
1411     hic->commit_string[0] = 0;
1412     hic->flushed_string[0] = 0;
1413
1414     if (hic->output_mode == HANGUL_OUTPUT_JAMO) {
1415         hangul_buffer_get_jamo_string(&hic->buffer, hic->flushed_string,
1416                                  N_ELEMENTS(hic->flushed_string));
1417     } else {
1418         hangul_buffer_get_string(&hic->buffer, hic->flushed_string,
1419                                  N_ELEMENTS(hic->flushed_string));
1420     }
1421
1422     hangul_buffer_clear(&hic->buffer);
1423
1424     return hic->flushed_string;
1425 }
1426
1427 /**
1428  * @ingroup hangulic
1429  * @brief @ref HangulInputContext가 backspace 키를 처리하도록 하는 함수
1430  * @param hic @ref HangulInputContext를 가리키는 포인터
1431  * @return @a hic가 키를 사용했으면 true, 사용하지 않았으면 false
1432  * 
1433  * 이 함수는 @a hic가 가리키는 @ref HangulInputContext의 조합중이던 글자를
1434  * 뒤에서부터 하나 지우는 기능을 한다. backspace 키를 눌렀을 때 발생하는 
1435  * 동작을 한다. 따라서 이 함수를 부르고 나면 preedit string이 바뀌므로
1436  * 반드시 업데이트를 해야 한다.
1437  *
1438  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시킨다.
1439  */
1440 bool
1441 hangul_ic_backspace(HangulInputContext *hic)
1442 {
1443     int ret;
1444
1445     if (hic == NULL)
1446         return false;
1447
1448     hic->preedit_string[0] = 0;
1449     hic->commit_string[0] = 0;
1450
1451     ret = hangul_buffer_backspace(&hic->buffer);
1452     if (ret)
1453         hangul_ic_save_preedit_string(hic);
1454     return ret;
1455 }
1456
1457 /**
1458  * @ingroup hangulic
1459  * @brief @ref HangulInputContext가 조합중인 글자를 가지고 있는지 확인하는 함수
1460  * @param hic @ref HangulInputContext를 가리키는 포인터
1461  *
1462  * @ref HangulInputContext가 조합중인 글자가 있으면 true를 리턴한다.
1463  *
1464  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다.
1465  */
1466 bool
1467 hangul_ic_is_empty(HangulInputContext *hic)
1468 {
1469     return hangul_buffer_is_empty(&hic->buffer);
1470 }
1471
1472 /**
1473  * @ingroup hangulic
1474  * @brief @ref HangulInputContext가 조합중인 초성을 가지고 있는지 확인하는 함수
1475  * @param hic @ref HangulInputContext를 가리키는 포인터
1476  *
1477  * @ref HangulInputContext가 조합중인 글자가 초성이 있으면 true를 리턴한다.
1478  *
1479  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다.
1480  */
1481 bool
1482 hangul_ic_has_choseong(HangulInputContext *hic)
1483 {
1484     return hangul_buffer_has_choseong(&hic->buffer);
1485 }
1486
1487 /**
1488  * @ingroup hangulic
1489  * @brief @ref HangulInputContext가 조합중인 중성을 가지고 있는지 확인하는 함수
1490  * @param hic @ref HangulInputContext를 가리키는 포인터
1491  *
1492  * @ref HangulInputContext가 조합중인 글자가 중성이 있으면 true를 리턴한다.
1493  *
1494  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다.
1495  */
1496 bool
1497 hangul_ic_has_jungseong(HangulInputContext *hic)
1498 {
1499     return hangul_buffer_has_jungseong(&hic->buffer);
1500 }
1501
1502 /**
1503  * @ingroup hangulic
1504  * @brief @ref HangulInputContext가 조합중인 종성을 가지고 있는지 확인하는 함수
1505  * @param hic @ref HangulInputContext를 가리키는 포인터
1506  *
1507  * @ref HangulInputContext가 조합중인 글자가 종성이 있으면 true를 리턴한다.
1508  *
1509  * @remarks 이 함수는 @ref HangulInputContext의 상태를 변화 시키지 않는다.
1510  */
1511 bool
1512 hangul_ic_has_jongseong(HangulInputContext *hic)
1513 {
1514     return hangul_buffer_has_jongseong(&hic->buffer);
1515 }
1516
1517 void
1518 hangul_ic_set_output_mode(HangulInputContext *hic, int mode)
1519 {
1520     if (hic == NULL)
1521         return;
1522
1523     if (!hic->use_jamo_mode_only)
1524         hic->output_mode = mode;
1525 }
1526
1527 void
1528 hangul_ic_connect_translate (HangulInputContext* hic,
1529                              HangulOnTranslate callback,
1530                              void* user_data)
1531 {
1532     if (hic != NULL) {
1533         hic->on_translate      = callback;
1534         hic->on_translate_data = user_data;
1535     }
1536 }
1537
1538 void
1539 hangul_ic_connect_transition(HangulInputContext* hic,
1540                              HangulOnTransition callback,
1541                              void* user_data)
1542 {
1543     if (hic != NULL) {
1544         hic->on_transition      = callback;
1545         hic->on_transition_data = user_data;
1546     }
1547 }
1548
1549 void hangul_ic_connect_callback(HangulInputContext* hic, const char* event,
1550                                 void* callback, void* user_data)
1551 {
1552     if (hic == NULL || event == NULL)
1553         return;
1554
1555     if (strcasecmp(event, "translate") == 0) {
1556         hic->on_translate      = (HangulOnTranslate)callback;
1557         hic->on_translate_data = user_data;
1558     } else if (strcasecmp(event, "transition") == 0) {
1559         hic->on_transition      = (HangulOnTransition)callback;
1560         hic->on_transition_data = user_data;
1561     }
1562 }
1563
1564 void
1565 hangul_ic_set_keyboard(HangulInputContext *hic, const HangulKeyboard* keyboard)
1566 {
1567     if (hic == NULL || keyboard == NULL)
1568         return;
1569
1570     hic->keyboard = keyboard;
1571 }
1572
1573 static const HangulKeyboard*
1574 hangul_ic_get_keyboard_by_id(const char* id)
1575 {
1576     unsigned i;
1577     unsigned n;
1578
1579     /* hangul_keyboards 테이블은 id 순으로 정렬되어 있지 않으므로
1580      * binary search를 할수 없고 linear search를 한다. */
1581     n = hangul_ic_get_n_keyboards();
1582     for (i = 0; i < n; ++i) {
1583         const HangulKeyboard* keyboard = hangul_keyboards[i];
1584         if (strcmp(id, keyboard->id) == 0) {
1585             return keyboard;
1586         }
1587     }
1588
1589     return NULL;
1590 }
1591
1592 /**
1593  * @ingroup hangulic
1594  * @brief @ref HangulInputContext의 자판 배열을 바꾸는 함수
1595  * @param hic @ref HangulInputContext 오브젝트
1596  * @param id 선택하고자 하는 자판, 아래와 같은 값을 선택할 수 있다.
1597  *          @li "2"   두벌식 자판
1598  *          @li "32"  세벌식 자판으로 두벌식의 배열을 가진 자판.
1599  *                    두벌식 사용자가 쉽게 세벌식 테스트를 할 수 있다.
1600  *                    shift를 누르면 자음이 종성으로 동작한다.
1601  *          @li "3f"  세벌식 최종
1602  *          @li "39"  세벌식 390
1603  *          @li "3s"  세벌식 순아래
1604  *          @li "3y"  세벌식 옛글
1605  *          @li "ro"  로마자 방식 자판
1606  * @return 없음
1607  * 
1608  * 이 함수는 @ref HangulInputContext의 자판을 @a id로 지정된 것으로 변경한다.
1609  * 
1610  * @remarks 이 함수는 @ref HangulInputContext의 내부 조합 상태에는 영향을
1611  * 미치지 않는다.  따라서 입력 중간에 자판을 변경하더라도 조합 상태는 유지된다.
1612  */
1613 void
1614 hangul_ic_select_keyboard(HangulInputContext *hic, const char* id)
1615 {
1616     const HangulKeyboard* keyboard;
1617
1618     if (hic == NULL)
1619         return;
1620
1621     if (id == NULL)
1622         id = "2";
1623
1624     keyboard = hangul_ic_get_keyboard_by_id(id);
1625     if (keyboard != NULL) {
1626         hic->keyboard = keyboard;
1627     } else {
1628         hic->keyboard = &hangul_keyboard_2;
1629     }
1630 }
1631
1632 void
1633 hangul_ic_set_combination(HangulInputContext *hic,
1634                           const HangulCombination* combination)
1635 {
1636 }
1637
1638 /**
1639  * @ingroup hangulic
1640  * @brief @ref HangulInputContext 오브젝트를 생성한다.
1641  * @param keyboard 사용하고자 하는 키보드, 사용 가능한 값에 대해서는
1642  *      hangul_ic_select_keyboard() 함수 설명을 참조한다.
1643  * @return 새로 생성된 @ref HangulInputContext에 대한 포인터
1644  * 
1645  * 이 함수는 한글 조합 기능을 제공하는 @ref HangulInputContext 오브젝트를 
1646  * 생성한다. 생성할때 지정한 자판은 나중에 hangul_ic_select_keyboard() 함수로
1647  * 다른 자판으로 변경이 가능하다.
1648  * 더이상 사용하지 않을 때에는 hangul_ic_delete() 함수로 삭제해야 한다.
1649  */
1650 HangulInputContext*
1651 hangul_ic_new(const char* keyboard)
1652 {
1653     HangulInputContext *hic;
1654
1655     hic = malloc(sizeof(HangulInputContext));
1656     if (hic == NULL)
1657         return NULL;
1658
1659     hic->preedit_string[0] = 0;
1660     hic->commit_string[0] = 0;
1661     hic->flushed_string[0] = 0;
1662
1663     hic->on_translate      = NULL;
1664     hic->on_translate_data = NULL;
1665
1666     hic->on_transition      = NULL;
1667     hic->on_transition_data = NULL;
1668
1669     hic->use_jamo_mode_only = FALSE;
1670
1671     hangul_ic_set_output_mode(hic, HANGUL_OUTPUT_SYLLABLE);
1672     hangul_ic_select_keyboard(hic, keyboard);
1673
1674     hangul_buffer_clear(&hic->buffer);
1675
1676     return hic;
1677 }
1678
1679 /**
1680  * @ingroup hangulic
1681  * @brief @ref HangulInputContext를 삭제하는 함수
1682  * @param hic @ref HangulInputContext 오브젝트
1683  * 
1684  * @a hic가 가리키는 @ref HangulInputContext 오브젝트의 메모리를 해제한다.
1685  * hangul_ic_new() 함수로 생성된 모든 @ref HangulInputContext 오브젝트는
1686  * 이 함수로 메모리해제를 해야 한다.
1687  * 메모리 해제 과정에서 상태 변화는 일어나지 않으므로 마지막 입력된 
1688  * 조합중이던 내용은 사라지게 된다.
1689  */
1690 void
1691 hangul_ic_delete(HangulInputContext *hic)
1692 {
1693     if (hic == NULL)
1694         return;
1695
1696     free(hic);
1697 }
1698
1699 unsigned int
1700 hangul_ic_get_n_keyboards()
1701 {
1702     return N_ELEMENTS(hangul_keyboards);
1703 }
1704
1705 const char*
1706 hangul_ic_get_keyboard_id(unsigned index_)
1707 {
1708     if (index_ < N_ELEMENTS(hangul_keyboards)) {
1709         return hangul_keyboards[index_]->id;
1710     }
1711
1712     return NULL;
1713 }
1714
1715 const char*
1716 hangul_ic_get_keyboard_name(unsigned index_)
1717 {
1718 #ifdef ENABLE_NLS
1719     static bool isGettextInitialized = false;
1720     if (!isGettextInitialized) {
1721         isGettextInitialized = true;
1722         bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
1723         bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
1724     }
1725 #endif
1726
1727     if (index_ < N_ELEMENTS(hangul_keyboards)) {
1728         return _(hangul_keyboards[index_]->name);
1729     }
1730
1731     return NULL;
1732 }
1733
1734 /**
1735  * @ingroup hangulic
1736  * @brief 주어진 hic가 transliteration method인지 판별
1737  * @param hic 상태를 알고자 하는 HangulInputContext 포인터
1738  * @return hic가 transliteration method인 경우 true를 리턴, 아니면 false
1739  *
1740  * 이 함수는 @a hic 가 transliteration method인지 판별하는 함수다.
1741  * 이 함수가 false를 리턴할 경우에는 process 함수에 keycode를 넘기기 전에
1742  * 키보드 자판 배열에 독립적인 값으로 변환한 후 넘겨야 한다.
1743  * 그렇지 않으면 유럽어 자판과 한국어 자판을 같이 쓸때 한글 입력이 제대로
1744  * 되지 않는다.
1745  */
1746 bool
1747 hangul_ic_is_transliteration(HangulInputContext *hic)
1748 {
1749     int type;
1750
1751     if (hic == NULL)
1752         return false;
1753
1754     type = hangul_keyboard_get_type(hic->keyboard);
1755     if (type == HANGUL_KEYBOARD_TYPE_ROMAJA)
1756         return true;
1757
1758     return false;
1759 }