Branch data Line data Source code
1 : : /* decomp.c - Character decomposition.
2 : : *
3 : : * Copyright (C) 1999, 2000 Tom Tromey
4 : : * Copyright 2000 Red Hat, Inc.
5 : : *
6 : : * SPDX-License-Identifier: LGPL-2.1-or-later
7 : : *
8 : : * This library is free software; you can redistribute it and/or
9 : : * modify it under the terms of the GNU Lesser General Public
10 : : * License as published by the Free Software Foundation; either
11 : : * version 2.1 of the License, or (at your option) any later version.
12 : : *
13 : : * This library is distributed in the hope that it will be useful,
14 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 : : * Lesser General Public License for more details.
17 : : *
18 : : * You should have received a copy of the GNU Lesser General Public License
19 : : * along with this library; if not, see <http://www.gnu.org/licenses/>.
20 : : */
21 : :
22 : : #include "config.h"
23 : :
24 : : #include <stdlib.h>
25 : :
26 : : #include "gunicode.h"
27 : : #include "gunidecomp.h"
28 : : #include "gmem.h"
29 : : #include "gtestutils.h"
30 : : #include "gunicomp.h"
31 : : #include "gunicodeprivate.h"
32 : :
33 : :
34 : : #define CC_PART1(Page, Char) \
35 : : ((combining_class_table_part1[Page] >= G_UNICODE_MAX_TABLE_INDEX) \
36 : : ? (combining_class_table_part1[Page] - G_UNICODE_MAX_TABLE_INDEX) \
37 : : : (cclass_data[combining_class_table_part1[Page]][Char]))
38 : :
39 : : #define CC_PART2(Page, Char) \
40 : : ((combining_class_table_part2[Page] >= G_UNICODE_MAX_TABLE_INDEX) \
41 : : ? (combining_class_table_part2[Page] - G_UNICODE_MAX_TABLE_INDEX) \
42 : : : (cclass_data[combining_class_table_part2[Page]][Char]))
43 : :
44 : : #define COMBINING_CLASS(Char) \
45 : : (((Char) <= G_UNICODE_LAST_CHAR_PART1) \
46 : : ? CC_PART1 ((Char) >> 8, (Char) & 0xff) \
47 : : : (((Char) >= 0xe0000 && (Char) <= G_UNICODE_LAST_CHAR) \
48 : : ? CC_PART2 (((Char) - 0xe0000) >> 8, (Char) & 0xff) \
49 : : : 0))
50 : :
51 : : /**
52 : : * g_unichar_combining_class:
53 : : * @uc: a Unicode character
54 : : *
55 : : * Determines the canonical combining class of a Unicode character.
56 : : *
57 : : * Returns: the combining class of the character
58 : : *
59 : : * Since: 2.14
60 : : **/
61 : : gint
62 : 132 : g_unichar_combining_class (gunichar uc)
63 : : {
64 : 132 : return COMBINING_CLASS (uc);
65 : : }
66 : :
67 : : /* constants for hangul syllable [de]composition */
68 : : #define SBase 0xAC00
69 : : #define LBase 0x1100
70 : : #define VBase 0x1161
71 : : #define TBase 0x11A7
72 : : #define LCount 19
73 : : #define VCount 21
74 : : #define TCount 28
75 : : #define NCount (VCount * TCount)
76 : : #define SCount (LCount * NCount)
77 : :
78 : : /**
79 : : * g_unicode_canonical_ordering:
80 : : * @string: (array length=len) (element-type gunichar): a UCS-4 encoded string.
81 : : * @len: the maximum length of @string to use.
82 : : *
83 : : * Computes the canonical ordering of a string in-place.
84 : : * This rearranges decomposed characters in the string
85 : : * according to their combining classes. See the Unicode
86 : : * manual for more information.
87 : : **/
88 : : void
89 : 2125515 : g_unicode_canonical_ordering (gunichar *string,
90 : : gsize len)
91 : : {
92 : : gsize i;
93 : 2125515 : int swap = 1;
94 : :
95 : 4278530 : while (swap)
96 : : {
97 : : int last;
98 : 2153015 : swap = 0;
99 : 2153015 : last = COMBINING_CLASS (string[0]);
100 : 4425960 : for (i = 0; i < len - 1; ++i)
101 : : {
102 : 2272945 : int next = COMBINING_CLASS (string[i + 1]);
103 : 2272945 : if (next != 0 && last > next)
104 : : {
105 : : gsize j;
106 : : /* Percolate item leftward through string. */
107 : 125072 : for (j = i + 1; j > 0; --j)
108 : : {
109 : : gunichar t;
110 : 125048 : if (COMBINING_CLASS (string[j - 1]) <= next)
111 : 51252 : break;
112 : 73796 : t = string[j];
113 : 73796 : string[j] = string[j - 1];
114 : 73796 : string[j - 1] = t;
115 : 73796 : swap = 1;
116 : 36898 : }
117 : : /* We're re-entering the loop looking at the old
118 : : character again. */
119 : 51276 : next = last;
120 : 25638 : }
121 : 2272945 : last = next;
122 : 1122925 : }
123 : : }
124 : 2125515 : }
125 : :
126 : : /* http://www.unicode.org/unicode/reports/tr15/#Hangul
127 : : * r should be null or have sufficient space. Calling with r == NULL will
128 : : * only calculate the result_len; however, a buffer with space for three
129 : : * characters will always be big enough. */
130 : : static void
131 : 586178 : decompose_hangul (gunichar s,
132 : : gunichar *r,
133 : : gsize *result_len)
134 : : {
135 : 586178 : gint SIndex = s - SBase;
136 : 586178 : gint TIndex = SIndex % TCount;
137 : :
138 : 586178 : if (r)
139 : : {
140 : 270764 : r[0] = LBase + SIndex / NCount;
141 : 270764 : r[1] = VBase + (SIndex % NCount) / TCount;
142 : 135382 : }
143 : :
144 : 586178 : if (TIndex)
145 : : {
146 : 560338 : if (r)
147 : 258632 : r[2] = TBase + TIndex;
148 : 560338 : *result_len = 3;
149 : 280169 : }
150 : : else
151 : 25840 : *result_len = 2;
152 : 586178 : }
153 : :
154 : : /* returns a pointer to a null-terminated UTF-8 string */
155 : : static const gchar *
156 : 7243846 : find_decomposition (gunichar ch,
157 : : gboolean compat)
158 : : {
159 : 7243846 : int start = 0;
160 : 7243846 : int end = G_N_ELEMENTS (decomp_table);
161 : :
162 : 7243846 : if (ch >= decomp_table[start].ch &&
163 : 6765762 : ch <= decomp_table[end - 1].ch)
164 : : {
165 : 20462410 : while (TRUE)
166 : 18924550 : {
167 : 40932106 : int half = (start + end) / 2;
168 : 40932106 : if (ch == decomp_table[half].ch)
169 : : {
170 : : int offset;
171 : :
172 : 337914 : if (compat)
173 : : {
174 : 169006 : offset = decomp_table[half].compat_offset;
175 : 169006 : if (offset == G_UNICODE_NOT_PRESENT_OFFSET)
176 : 68416 : offset = decomp_table[half].canon_offset;
177 : 84466 : }
178 : : else
179 : : {
180 : 168908 : offset = decomp_table[half].canon_offset;
181 : 168908 : if (offset == G_UNICODE_NOT_PRESENT_OFFSET)
182 : 100318 : return NULL;
183 : : }
184 : :
185 : 237596 : return &(decomp_expansion_string[offset]);
186 : : }
187 : 40594192 : else if (half == start)
188 : 2751808 : break;
189 : 37842384 : else if (ch > decomp_table[half].ch)
190 : 16867392 : start = half;
191 : : else
192 : 20974992 : end = half;
193 : : }
194 : 1375656 : }
195 : :
196 : 6905932 : return NULL;
197 : 3589419 : }
198 : :
199 : : /**
200 : : * g_unicode_canonical_decomposition:
201 : : * @ch: a Unicode character.
202 : : * @result_len: location to store the length of the return value.
203 : : *
204 : : * Computes the canonical decomposition of a Unicode character.
205 : : *
206 : : * Returns: a newly allocated string of Unicode characters.
207 : : * @result_len is set to the resulting length of the string.
208 : : *
209 : : * Deprecated: 2.30: Use the more flexible g_unichar_fully_decompose()
210 : : * instead.
211 : : **/
212 : : gunichar *
213 : 32 : g_unicode_canonical_decomposition (gunichar ch,
214 : : gsize *result_len)
215 : : {
216 : : const gchar *decomp;
217 : : const gchar *p;
218 : : gunichar *r;
219 : :
220 : : /* Hangul syllable */
221 : 32 : if (ch >= SBase && ch < SBase + SCount)
222 : : {
223 : 8 : decompose_hangul (ch, NULL, result_len);
224 : 8 : r = g_malloc (*result_len * sizeof (gunichar));
225 : 8 : decompose_hangul (ch, r, result_len);
226 : 4 : }
227 : 24 : else if ((decomp = find_decomposition (ch, FALSE)) != NULL)
228 : : {
229 : : /* Found it. */
230 : : int i;
231 : :
232 : 20 : *result_len = g_utf8_strlen (decomp, -1);
233 : 20 : r = g_malloc (*result_len * sizeof (gunichar));
234 : :
235 : 60 : for (p = decomp, i = 0; *p != '\0'; p = g_utf8_next_char (p), i++)
236 : 40 : r[i] = g_utf8_get_char (p);
237 : 10 : }
238 : : else
239 : : {
240 : : /* Not in our table. */
241 : 4 : r = g_malloc (sizeof (gunichar));
242 : 4 : *r = ch;
243 : 4 : *result_len = 1;
244 : : }
245 : :
246 : 32 : return r;
247 : : }
248 : :
249 : : /* L,V => LV and LV,T => LVT */
250 : : static gboolean
251 : 693700 : combine_hangul (gunichar a,
252 : : gunichar b,
253 : : gunichar *result)
254 : : {
255 : 693700 : gint LIndex = a - LBase;
256 : 693700 : gint SIndex = a - SBase;
257 : :
258 : 693700 : gint VIndex = b - VBase;
259 : 693700 : gint TIndex = b - TBase;
260 : :
261 : 693700 : if (0 <= LIndex && LIndex < LCount
262 : 352119 : && 0 <= VIndex && VIndex < VCount)
263 : : {
264 : 225876 : *result = SBase + (LIndex * VCount + VIndex) * TCount;
265 : 225876 : return TRUE;
266 : : }
267 : 467824 : else if (0 <= SIndex && SIndex < SCount && (SIndex % TCount) == 0
268 : 217683 : && 0 < TIndex && TIndex < TCount)
269 : : {
270 : 215536 : *result = a + TIndex;
271 : 215536 : return TRUE;
272 : : }
273 : :
274 : 252288 : return FALSE;
275 : 333348 : }
276 : :
277 : : #define CI(Page, Char) \
278 : : ((compose_table[Page] >= G_UNICODE_MAX_TABLE_INDEX) \
279 : : ? (compose_table[Page] - G_UNICODE_MAX_TABLE_INDEX) \
280 : : : (compose_data[compose_table[Page]][Char]))
281 : :
282 : : #define COMPOSE_INDEX(Char) \
283 : : (((Char >> 8) > (COMPOSE_TABLE_LAST)) ? 0 : CI((Char) >> 8, (Char) & 0xff))
284 : :
285 : : static gboolean
286 : 693700 : combine (gunichar a,
287 : : gunichar b,
288 : : gunichar *result)
289 : : {
290 : : gushort index_a, index_b;
291 : :
292 : 693700 : if (combine_hangul (a, b, result))
293 : 441412 : return TRUE;
294 : :
295 : 252288 : index_a = COMPOSE_INDEX(a);
296 : :
297 : 252288 : if (index_a >= COMPOSE_FIRST_SINGLE_START && index_a < COMPOSE_SECOND_START)
298 : : {
299 : 16830 : if (b == compose_first_single[index_a - COMPOSE_FIRST_SINGLE_START][0])
300 : : {
301 : 13690 : *result = compose_first_single[index_a - COMPOSE_FIRST_SINGLE_START][1];
302 : 13690 : return TRUE;
303 : : }
304 : : else
305 : 3140 : return FALSE;
306 : : }
307 : :
308 : 235458 : index_b = COMPOSE_INDEX(b);
309 : :
310 : 235458 : if (index_b >= COMPOSE_SECOND_SINGLE_START && index_b < COMPOSE_EITHER_START)
311 : : {
312 : 708 : if (a == compose_second_single[index_b - COMPOSE_SECOND_SINGLE_START][0])
313 : : {
314 : 638 : *result = compose_second_single[index_b - COMPOSE_SECOND_SINGLE_START][1];
315 : 638 : return TRUE;
316 : : }
317 : : else
318 : 70 : return FALSE;
319 : : }
320 : :
321 : 234750 : if (index_a >= COMPOSE_FIRST_START && index_a < COMPOSE_FIRST_SINGLE_START &&
322 : 110939 : index_b >= COMPOSE_SECOND_START && index_b < COMPOSE_SECOND_SINGLE_START)
323 : : {
324 : 65307 : gunichar res = compose_array[index_a - COMPOSE_FIRST_START][index_b - COMPOSE_SECOND_START];
325 : :
326 : 65307 : if (res)
327 : : {
328 : 59625 : *result = res;
329 : 59625 : return TRUE;
330 : : }
331 : 2841 : }
332 : :
333 : 175125 : if (index_a >= COMPOSE_EITHER_START &&
334 : 1524 : index_b >= COMPOSE_EITHER_START)
335 : : {
336 : 2706 : gunichar res = compose_either_array[index_a - COMPOSE_EITHER_START][index_b - COMPOSE_EITHER_START];
337 : :
338 : 2706 : if (res)
339 : : {
340 : 2324 : *result = res;
341 : 2324 : return TRUE;
342 : : }
343 : 191 : }
344 : :
345 : 172801 : return FALSE;
346 : 333348 : }
347 : :
348 : : gunichar *
349 : 807857 : _g_utf8_normalize_wc (const gchar *str,
350 : : gssize max_len,
351 : : GNormalizeMode mode)
352 : : {
353 : : gsize n_wc;
354 : : gsize tmp;
355 : : gunichar *wc_buffer;
356 : : const char *p;
357 : : gsize last_start;
358 : 807857 : gboolean do_compat = (mode == G_NORMALIZE_NFKC ||
359 : 300825 : mode == G_NORMALIZE_NFKD);
360 : 807857 : gboolean do_compose = (mode == G_NORMALIZE_NFC ||
361 : 300998 : mode == G_NORMALIZE_NFKC);
362 : :
363 : : /* Do a first pass to work out the length of the normalised string so we can
364 : : * allocate a buffer. */
365 : 807857 : n_wc = 0;
366 : 807857 : p = str;
367 : 2494609 : while ((max_len < 0 || p < str + max_len) && *p)
368 : : {
369 : : gsize char_len;
370 : : const gchar *decomp;
371 : : const char *next, *between;
372 : : gunichar wc;
373 : :
374 : 1686804 : next = g_utf8_next_char (p);
375 : : /* Avoid reading truncated multibyte characters
376 : : which run past the end of the buffer */
377 : 1686804 : if (max_len < 0)
378 : : {
379 : : /* Does the character contain a NUL terminator? */
380 : 4293289 : for (between = &p[1]; between < next; between++)
381 : : {
382 : 2641640 : if (G_UNLIKELY (!*between))
383 : 10 : return NULL;
384 : 1320555 : }
385 : 824275 : }
386 : : else
387 : : {
388 : 35145 : if (G_UNLIKELY (next > str + max_len))
389 : 12 : return NULL;
390 : : }
391 : 1686782 : char_len = next - p;
392 : 1686782 : wc = g_utf8_get_char_validated (p, char_len);
393 : :
394 : 1686782 : if (G_UNLIKELY (wc == (gunichar) -1 || wc == (gunichar) -2))
395 : : {
396 : 30 : return NULL;
397 : : }
398 : 1686752 : else if (wc >= SBase && wc < SBase + SCount)
399 : 135355 : {
400 : : gsize result_len;
401 : 270710 : decompose_hangul (wc, NULL, &result_len);
402 : 270710 : if (G_UNLIKELY (!g_size_checked_add (&tmp, n_wc, result_len)))
403 : 0 : return NULL;
404 : 270710 : n_wc = tmp;
405 : 135355 : }
406 : : else
407 : : {
408 : 1416042 : decomp = find_decomposition (wc, do_compat);
409 : :
410 : 1416042 : if (decomp)
411 : : {
412 : 110719 : gsize add = (gsize) g_utf8_strlen (decomp, -1);
413 : 110719 : if (G_UNLIKELY (!g_size_checked_add (&tmp, n_wc, add)))
414 : 0 : return NULL;
415 : 110719 : n_wc = tmp;
416 : 55341 : }
417 : : else
418 : 1305323 : n_wc++;
419 : : }
420 : :
421 : 1686752 : p = next;
422 : : }
423 : :
424 : : /* Allocate the buffer for the result. */
425 : 807805 : if (G_UNLIKELY (!g_size_checked_add (&tmp, n_wc, 1)))
426 : 0 : return NULL;
427 : :
428 : 807805 : wc_buffer = g_new (gunichar, tmp);
429 : :
430 : : /* Do another pass to fill the buffer with the normalised string. */
431 : 807805 : last_start = 0;
432 : 807805 : n_wc = 0;
433 : 807805 : p = str;
434 : 2494349 : while ((max_len < 0 || p < str + max_len) && *p)
435 : : {
436 : 1686544 : gunichar wc = g_utf8_get_char (p);
437 : : const gchar *decomp;
438 : : int cc;
439 : 1686544 : gsize old_n_wc = n_wc;
440 : :
441 : 1686544 : if (wc >= SBase && wc < SBase + SCount)
442 : 135354 : {
443 : : gsize result_len;
444 : 270708 : decompose_hangul (wc, wc_buffer + n_wc, &result_len);
445 : 270708 : n_wc += result_len;
446 : 135354 : }
447 : : else
448 : : {
449 : 1415836 : decomp = find_decomposition (wc, do_compat);
450 : :
451 : 1415836 : if (decomp)
452 : : {
453 : : const char *pd;
454 : 323865 : for (pd = decomp; *pd != '\0'; pd = g_utf8_next_char (pd))
455 : 213146 : wc_buffer[n_wc++] = g_utf8_get_char (pd);
456 : 55341 : }
457 : : else
458 : 1305117 : wc_buffer[n_wc++] = wc;
459 : : }
460 : :
461 : : /* Each code path above *must* have appended at least gunichar to wc_buffer. */
462 : 1686544 : g_assert (n_wc > old_n_wc);
463 : :
464 : 1686544 : cc = COMBINING_CLASS (wc_buffer[old_n_wc]);
465 : :
466 : 1686544 : if (cc == 0)
467 : : {
468 : 1317718 : g_unicode_canonical_ordering (wc_buffer + last_start, n_wc - last_start);
469 : 1317718 : last_start = old_n_wc;
470 : 642609 : }
471 : :
472 : 1686544 : p = g_utf8_next_char (p);
473 : : }
474 : :
475 : 807805 : if (n_wc > 0)
476 : : {
477 : 807797 : g_unicode_canonical_ordering (wc_buffer + last_start, n_wc - last_start);
478 : 807797 : last_start = n_wc;
479 : 401157 : (void) last_start;
480 : 401157 : }
481 : :
482 : 807805 : wc_buffer[n_wc] = 0;
483 : :
484 : : /* All decomposed and reordered */
485 : :
486 : 807805 : if (do_compose && n_wc > 0)
487 : : {
488 : : gsize i, j;
489 : 478865 : int last_cc = 0;
490 : 478865 : last_start = 0;
491 : :
492 : 1577406 : for (i = 0; i < n_wc; i++)
493 : : {
494 : 1170839 : int cc = COMBINING_CLASS (wc_buffer[i]);
495 : :
496 : 1170839 : if (i > 0 &&
497 : 802613 : (last_cc == 0 || last_cc < cc) &&
498 : 1022529 : combine (wc_buffer[last_start], wc_buffer[i],
499 : 691418 : &wc_buffer[last_start]))
500 : : {
501 : 810550 : for (j = i + 1; j < n_wc; j++)
502 : 292889 : wc_buffer[j-1] = wc_buffer[j];
503 : 517661 : n_wc--;
504 : 517661 : i--;
505 : :
506 : 517661 : if (i == last_start)
507 : 505755 : last_cc = 0;
508 : : else
509 : 11906 : last_cc = COMBINING_CLASS (wc_buffer[i-1]);
510 : :
511 : 517661 : continue;
512 : : }
513 : :
514 : 580880 : if (cc == 0)
515 : 502870 : last_start = i;
516 : :
517 : 580880 : last_cc = cc;
518 : 238065 : }
519 : 200542 : }
520 : :
521 : 880099 : wc_buffer[n_wc] = 0;
522 : :
523 : 880099 : return wc_buffer;
524 : 473481 : }
525 : :
526 : : /**
527 : : * g_utf8_normalize:
528 : : * @str: a UTF-8 encoded string.
529 : : * @len: length of @str, in bytes, or -1 if @str is nul-terminated.
530 : : * @mode: the type of normalization to perform.
531 : : *
532 : : * Converts a string into canonical form, standardizing
533 : : * such issues as whether a character with an accent
534 : : * is represented as a base character and combining
535 : : * accent or as a single precomposed character. The
536 : : * string has to be valid UTF-8, otherwise %NULL is
537 : : * returned. You should generally call g_utf8_normalize()
538 : : * before comparing two Unicode strings.
539 : : *
540 : : * The normalization mode %G_NORMALIZE_DEFAULT only
541 : : * standardizes differences that do not affect the
542 : : * text content, such as the above-mentioned accent
543 : : * representation. %G_NORMALIZE_ALL also standardizes
544 : : * the "compatibility" characters in Unicode, such
545 : : * as SUPERSCRIPT THREE to the standard forms
546 : : * (in this case DIGIT THREE). Formatting information
547 : : * may be lost but for most text operations such
548 : : * characters should be considered the same.
549 : : *
550 : : * %G_NORMALIZE_DEFAULT_COMPOSE and %G_NORMALIZE_ALL_COMPOSE
551 : : * are like %G_NORMALIZE_DEFAULT and %G_NORMALIZE_ALL,
552 : : * but returned a result with composed forms rather
553 : : * than a maximally decomposed form. This is often
554 : : * useful if you intend to convert the string to
555 : : * a legacy encoding or pass it to a system with
556 : : * less capable Unicode handling.
557 : : *
558 : : * Returns: (nullable): a newly allocated string, that
559 : : * is the normalized form of @str, or %NULL if @str
560 : : * is not valid UTF-8.
561 : : **/
562 : : gchar *
563 : 807380 : g_utf8_normalize (const gchar *str,
564 : : gssize len,
565 : : GNormalizeMode mode)
566 : : {
567 : 807380 : gunichar *result_wc = _g_utf8_normalize_wc (str, len, mode);
568 : 807380 : gchar *result = NULL;
569 : :
570 : 807380 : if (G_LIKELY (result_wc != NULL))
571 : : {
572 : 807328 : result = g_ucs4_to_utf8 (result_wc, -1, NULL, NULL, NULL);
573 : 807328 : g_free (result_wc);
574 : 401161 : }
575 : :
576 : 807380 : return result;
577 : : }
578 : :
579 : : static gboolean
580 : 2254766 : decompose_hangul_step (gunichar ch,
581 : : gunichar *a,
582 : : gunichar *b)
583 : : {
584 : : gint SIndex, TIndex;
585 : :
586 : 2254766 : if (ch < SBase || ch >= SBase + SCount)
587 : 2232414 : return FALSE; /* not a hangul syllable */
588 : :
589 : 22352 : SIndex = ch - SBase;
590 : 22352 : TIndex = SIndex % TCount;
591 : :
592 : 22352 : if (TIndex)
593 : : {
594 : : /* split LVT -> LV,T */
595 : 21550 : *a = ch - TIndex;
596 : 21550 : *b = TBase + TIndex;
597 : 10775 : }
598 : : else
599 : : {
600 : : /* split LV -> L,V */
601 : 802 : *a = LBase + SIndex / NCount;
602 : 802 : *b = VBase + (SIndex % NCount) / TCount;
603 : : }
604 : :
605 : 22352 : return TRUE;
606 : 1127383 : }
607 : :
608 : : /**
609 : : * g_unichar_decompose:
610 : : * @ch: a Unicode character
611 : : * @a: (out) (not optional): return location for the first component of @ch
612 : : * @b: (out) (not optional): return location for the second component of @ch
613 : : *
614 : : * Performs a single decomposition step of the
615 : : * Unicode canonical decomposition algorithm.
616 : : *
617 : : * This function does not include compatibility
618 : : * decompositions. It does, however, include algorithmic
619 : : * Hangul Jamo decomposition, as well as 'singleton'
620 : : * decompositions which replace a character by a single
621 : : * other character. In the case of singletons `*b` will
622 : : * be set to zero.
623 : : *
624 : : * If @ch is not decomposable, `*a` is set to @ch and `*b`
625 : : * is set to zero.
626 : : *
627 : : * Note that the way Unicode decomposition pairs are
628 : : * defined, it is guaranteed that @b would not decompose
629 : : * further, but @a may itself decompose. To get the full
630 : : * canonical decomposition for @ch, one would need to
631 : : * recursively call this function on @a. Or use
632 : : * g_unichar_fully_decompose().
633 : : *
634 : : * See
635 : : * [UAX#15](http://unicode.org/reports/tr15/)
636 : : * for details.
637 : : *
638 : : * Returns: %TRUE if the character could be decomposed
639 : : *
640 : : * Since: 2.30
641 : : */
642 : : gboolean
643 : 2254766 : g_unichar_decompose (gunichar ch,
644 : : gunichar *a,
645 : : gunichar *b)
646 : : {
647 : 2254766 : gint start = 0;
648 : 2254766 : gint end = G_N_ELEMENTS (decomp_step_table);
649 : :
650 : 2254766 : if (decompose_hangul_step (ch, a, b))
651 : 22352 : return TRUE;
652 : :
653 : : /* TODO use bsearch() */
654 : 2232414 : if (ch >= decomp_step_table[start].ch &&
655 : 2229958 : ch <= decomp_step_table[end - 1].ch)
656 : : {
657 : 2350308 : while (TRUE)
658 : 2154339 : {
659 : 4700616 : gint half = (start + end) / 2;
660 : 4700616 : const decomposition_step *p = &(decomp_step_table[half]);
661 : 4700616 : if (ch == p->ch)
662 : : {
663 : 4186 : *a = p->a;
664 : 4186 : *b = p->b;
665 : 4186 : return TRUE;
666 : : }
667 : 4696430 : else if (half == start)
668 : 387752 : break;
669 : 4308678 : else if (ch > p->ch)
670 : 2955286 : start = half;
671 : : else
672 : 1353392 : end = half;
673 : : }
674 : 193876 : }
675 : :
676 : 2228228 : *a = ch;
677 : 2228228 : *b = 0;
678 : :
679 : 2228228 : return FALSE;
680 : 1127383 : }
681 : :
682 : : /**
683 : : * g_unichar_compose:
684 : : * @a: a Unicode character
685 : : * @b: a Unicode character
686 : : * @ch: (out) (not optional): return location for the composed character
687 : : *
688 : : * Performs a single composition step of the
689 : : * Unicode canonical composition algorithm.
690 : : *
691 : : * This function includes algorithmic Hangul Jamo composition,
692 : : * but it is not exactly the inverse of g_unichar_decompose().
693 : : * No composition can have either of @a or @b equal to zero.
694 : : * To be precise, this function composes if and only if
695 : : * there exists a Primary Composite P which is canonically
696 : : * equivalent to the sequence <@a,@b>. See the Unicode
697 : : * Standard for the definition of Primary Composite.
698 : : *
699 : : * If @a and @b do not compose a new character, @ch is set to zero.
700 : : *
701 : : * See
702 : : * [UAX#15](http://unicode.org/reports/tr15/)
703 : : * for details.
704 : : *
705 : : * Returns: %TRUE if the characters could be composed
706 : : *
707 : : * Since: 2.30
708 : : */
709 : : gboolean
710 : 90 : g_unichar_compose (gunichar a,
711 : : gunichar b,
712 : : gunichar *ch)
713 : : {
714 : 90 : if (combine (a, b, ch))
715 : 28 : return TRUE;
716 : :
717 : 62 : *ch = 0;
718 : 62 : return FALSE;
719 : 45 : }
720 : :
721 : : /**
722 : : * g_unichar_fully_decompose:
723 : : * @ch: a Unicode character.
724 : : * @compat: whether perform canonical or compatibility decomposition
725 : : * @result: (optional) (out caller-allocates): location to store decomposed result, or %NULL
726 : : * @result_len: length of @result
727 : : *
728 : : * Computes the canonical or compatibility decomposition of a
729 : : * Unicode character. For compatibility decomposition,
730 : : * pass %TRUE for @compat; for canonical decomposition
731 : : * pass %FALSE for @compat.
732 : : *
733 : : * The decomposed sequence is placed in @result. Only up to
734 : : * @result_len characters are written into @result. The length
735 : : * of the full decomposition (irrespective of @result_len) is
736 : : * returned by the function. For canonical decomposition,
737 : : * currently all decompositions are of length at most 4, but
738 : : * this may change in the future (very unlikely though).
739 : : * At any rate, Unicode does guarantee that a buffer of length
740 : : * 18 is always enough for both compatibility and canonical
741 : : * decompositions, so that is the size recommended. This is provided
742 : : * as %G_UNICHAR_MAX_DECOMPOSITION_LENGTH.
743 : : *
744 : : * See
745 : : * [UAX#15](http://unicode.org/reports/tr15/)
746 : : * for details.
747 : : *
748 : : * Returns: the length of the full decomposition.
749 : : *
750 : : * Since: 2.30
751 : : **/
752 : : gsize
753 : 4456688 : g_unichar_fully_decompose (gunichar ch,
754 : : gboolean compat,
755 : : gunichar *result,
756 : : gsize result_len)
757 : : {
758 : : const gchar *decomp;
759 : : const gchar *p;
760 : :
761 : : /* Hangul syllable */
762 : 4456688 : if (ch >= SBase && ch < SBase + SCount)
763 : : {
764 : : gsize len, i;
765 : : gunichar buffer[3];
766 : 44744 : decompose_hangul (ch, result ? buffer : NULL, &len);
767 : 44744 : if (result)
768 : 132 : for (i = 0; i < len && i < result_len; i++)
769 : 108 : result[i] = buffer[i];
770 : 44744 : return len;
771 : : }
772 : 4411944 : else if ((decomp = find_decomposition (ch, compat)) != NULL)
773 : : {
774 : : /* Found it. */
775 : : gsize len, i;
776 : :
777 : 16138 : len = g_utf8_strlen (decomp, -1);
778 : :
779 : 16332 : for (p = decomp, i = 0; i < len && i < result_len; p = g_utf8_next_char (p), i++)
780 : 194 : result[i] = g_utf8_get_char (p);
781 : :
782 : 16138 : return len;
783 : : }
784 : :
785 : : /* Does not decompose */
786 : 4395806 : if (result && result_len >= 1)
787 : 28 : *result = ch;
788 : 4395806 : return 1;
789 : 2228344 : }
|