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*
hangul_keyboard_new()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
hangul_keyboard_get_value(const HangulKeyboard * keyboard,int key)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
hangul_keyboard_set_value(HangulKeyboard * keyboard,int key,ucschar value)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
hangul_keyboard_get_type(const HangulKeyboard * keyboard)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
hangul_keyboard_set_type(HangulKeyboard * keyboard,int type)408 hangul_keyboard_set_type(HangulKeyboard *keyboard, int type)
409 {
410     if (keyboard != NULL) {
411 	keyboard->type = type;
412     }
413 }
414 
415 void
hangul_keyboard_delete(HangulKeyboard * keyboard)416 hangul_keyboard_delete(HangulKeyboard *keyboard)
417 {
418     if (keyboard != NULL)
419 	free(keyboard);
420 }
421 
422 HangulCombination*
hangul_combination_new()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
hangul_combination_delete(HangulCombination * combination)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
hangul_combination_make_key(ucschar first,ucschar second)446 hangul_combination_make_key(ucschar first, ucschar second)
447 {
448     return first << 16 | second;
449 }
450 
451 bool
hangul_combination_set_data(HangulCombination * combination,ucschar * first,ucschar * second,ucschar * result,unsigned int n)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
hangul_combination_cmp(const void * p1,const void * p2)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
hangul_combination_combine(const HangulCombination * combination,ucschar first,ucschar second)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
hangul_buffer_is_empty(HangulBuffer * buffer)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
hangul_buffer_has_choseong(HangulBuffer * buffer)520 hangul_buffer_has_choseong(HangulBuffer *buffer)
521 {
522     return buffer->choseong != 0;
523 }
524 
525 static bool
hangul_buffer_has_jungseong(HangulBuffer * buffer)526 hangul_buffer_has_jungseong(HangulBuffer *buffer)
527 {
528     return buffer->jungseong != 0;
529 }
530 
531 static bool
hangul_buffer_has_jongseong(HangulBuffer * buffer)532 hangul_buffer_has_jongseong(HangulBuffer *buffer)
533 {
534     return buffer->jongseong != 0;
535 }
536 
537 static void
hangul_buffer_push(HangulBuffer * buffer,ucschar ch)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
hangul_buffer_pop(HangulBuffer * buffer)553 hangul_buffer_pop(HangulBuffer *buffer)
554 {
555     return buffer->stack[buffer->index--];
556 }
557 
558 static ucschar
hangul_buffer_peek(HangulBuffer * buffer)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
hangul_buffer_clear(HangulBuffer * buffer)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
hangul_buffer_get_jamo_string(HangulBuffer * buffer,ucschar * buf,int buflen)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
hangul_jaso_to_string(ucschar cho,ucschar jung,ucschar jong,ucschar * buf,int len)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
hangul_buffer_get_string(HangulBuffer * buffer,ucschar * buf,int buflen)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
hangul_buffer_backspace(HangulBuffer * buffer)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
hangul_ic_push(HangulInputContext * hic,ucschar c)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
hangul_ic_pop(HangulInputContext * hic)772 hangul_ic_pop(HangulInputContext *hic)
773 {
774     return hangul_buffer_pop(&hic->buffer);
775 }
776 
777 static inline ucschar
hangul_ic_peek(HangulInputContext * hic)778 hangul_ic_peek(HangulInputContext *hic)
779 {
780     return hangul_buffer_peek(&hic->buffer);
781 }
782 
783 static inline void
hangul_ic_save_preedit_string(HangulInputContext * hic)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
hangul_ic_append_commit_string(HangulInputContext * hic,ucschar ch)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
hangul_ic_save_commit_string(HangulInputContext * hic)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
hangul_ic_choseong_to_jongseong(HangulInputContext * hic,ucschar cho)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
hangul_ic_process_jamo(HangulInputContext * hic,ucschar ch)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
hangul_ic_process_jaso(HangulInputContext * hic,ucschar ch)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
hangul_ic_process_romaja(HangulInputContext * hic,int ascii,ucschar ch)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_dicompose(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
hangul_ic_process(HangulInputContext * hic,int ascii)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*
hangul_ic_get_preedit_string(HangulInputContext * hic)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*
hangul_ic_get_commit_string(HangulInputContext * hic)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
hangul_ic_reset(HangulInputContext * hic)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
hangul_ic_flush_internal(HangulInputContext * hic)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*
hangul_ic_flush(HangulInputContext * hic)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
hangul_ic_backspace(HangulInputContext * hic)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
hangul_ic_is_empty(HangulInputContext * hic)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
hangul_ic_has_choseong(HangulInputContext * hic)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
hangul_ic_has_jungseong(HangulInputContext * hic)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
hangul_ic_has_jongseong(HangulInputContext * hic)1512 hangul_ic_has_jongseong(HangulInputContext *hic)
1513 {
1514     return hangul_buffer_has_jongseong(&hic->buffer);
1515 }
1516 
1517 void
hangul_ic_set_output_mode(HangulInputContext * hic,int mode)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
hangul_ic_connect_translate(HangulInputContext * hic,HangulOnTranslate callback,void * user_data)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
hangul_ic_connect_transition(HangulInputContext * hic,HangulOnTransition callback,void * user_data)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 
hangul_ic_connect_callback(HangulInputContext * hic,const char * event,void * callback,void * user_data)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
hangul_ic_set_keyboard(HangulInputContext * hic,const HangulKeyboard * keyboard)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*
hangul_ic_get_keyboard_by_id(const char * id)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
hangul_ic_select_keyboard(HangulInputContext * hic,const char * id)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
hangul_ic_set_combination(HangulInputContext * hic,const HangulCombination * combination)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*
hangul_ic_new(const char * keyboard)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
hangul_ic_delete(HangulInputContext * hic)1691 hangul_ic_delete(HangulInputContext *hic)
1692 {
1693     if (hic == NULL)
1694 	return;
1695 
1696     free(hic);
1697 }
1698 
1699 unsigned int
hangul_ic_get_n_keyboards()1700 hangul_ic_get_n_keyboards()
1701 {
1702     return N_ELEMENTS(hangul_keyboards);
1703 }
1704 
1705 const char*
hangul_ic_get_keyboard_id(unsigned index_)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*
hangul_ic_get_keyboard_name(unsigned index_)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
hangul_ic_is_transliteration(HangulInputContext * hic)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 }
1760