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