aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/libraries/evas/src/lib/engines/common/language/evas_bidi_utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/evas/src/lib/engines/common/language/evas_bidi_utils.c')
-rw-r--r--libraries/evas/src/lib/engines/common/language/evas_bidi_utils.c641
1 files changed, 641 insertions, 0 deletions
diff --git a/libraries/evas/src/lib/engines/common/language/evas_bidi_utils.c b/libraries/evas/src/lib/engines/common/language/evas_bidi_utils.c
new file mode 100644
index 0000000..9f7859b
--- /dev/null
+++ b/libraries/evas/src/lib/engines/common/language/evas_bidi_utils.c
@@ -0,0 +1,641 @@
1#include <string.h>
2#include <stdlib.h>
3
4#include "evas_common.h"
5#include "evas_bidi_utils.h"
6
7#include "evas_font_private.h"
8
9#ifdef BIDI_SUPPORT
10#include <fribidi/fribidi.h>
11/**
12 * @internal
13 * @addtogroup Evas_Utils
14 *
15 * @{
16 */
17/**
18 * @internal
19 * @addtogroup Evas_BiDi
20 *
21 * @{
22 */
23
24/**
25 * @internal
26 * @def _SAFE_FREE(x)
27 * checks if x is not NULL, if it's not, it's freed and set to NULL.
28 */
29#define _SAFE_FREE(x) \
30 do { \
31 if (x) \
32 { \
33 free(x); \
34 x = NULL; \
35 } \
36 } while(0)
37
38#if SIZEOF_FRIBIDICHAR != SIZEOF_EINA_UNICODE
39# define EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
40#endif
41
42#ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
43/* Convert bidichar to eina_unicode assume both are valid pointers */
44static Eina_Unicode *
45_evas_bidi_fribidichar_to_unicode(Eina_Unicode *dest, const FriBidiChar *src)
46{
47 Eina_Unicode *ret = dest;
48
49 while (*src)
50 *dest++ = *src++;
51 *dest = 0;
52 return ret;
53}
54
55/* Convert eina_unicode to bidi_char assume both are valid pointers */
56static FriBidiChar *
57_evas_bidi_unicode_to_fribidichar(FriBidiChar *dest, const Eina_Unicode *src)
58{
59 FriBidiChar *ret = dest;
60
61 while (*src)
62 *dest++ = *src++;
63 *dest = 0;
64 return ret;
65}
66#endif
67
68/**
69 * @internal
70 * Checks if the string has RTL characters.
71 *
72 * @param str The string to be checked
73 * @return #EINA_TRUE if true, #EINA_FALSE otherwise.
74 */
75Eina_Bool
76evas_bidi_is_rtl_str(const Eina_Unicode *str)
77{
78 EvasBiDiCharType type;
79
80 if (!str)
81 return EINA_FALSE;
82
83 for ( ; *str ; str++)
84 {
85 type = fribidi_get_bidi_type((FriBidiChar) *str);
86 if (FRIBIDI_IS_LETTER(type) && FRIBIDI_IS_RTL(type))
87 {
88 return EINA_TRUE;
89 }
90 }
91 return EINA_FALSE;
92}
93
94/**
95 * @internal
96 * Shapes the string ustr according to the bidi properties.
97 *
98 * @param str The string to shape
99 * @param bidi_props the bidi props to shaped according.
100 * @param start the start of the string to shape (offset in bidi_props)
101 * @param len the length of th string.
102 * @return #EINA_TRUE on success, #EINA_FALSE otherwise.
103 */
104EAPI Eina_Bool
105evas_bidi_shape_string(Eina_Unicode *eina_ustr, const Evas_BiDi_Paragraph_Props *bidi_props, size_t start, size_t len)
106{
107 FriBidiChar *ustr, *base_ustr = NULL;
108
109 if (!bidi_props)
110 return EINA_FALSE;
111
112 /* The size of fribidichar is different than eina_unicode, convert */
113#ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
114 base_ustr = ustr = calloc(len + 1, sizeof(FriBidiChar));
115 ustr = _evas_bidi_unicode_to_fribidichar(ustr, eina_ustr);
116#else
117 (void) base_ustr;
118 ustr = (FriBidiChar *) eina_ustr;
119#endif
120
121
122 EvasBiDiJoiningType *join_types = NULL;
123 join_types = (EvasBiDiJoiningType *) malloc(sizeof(EvasBiDiJoiningType) * len);
124 if (!join_types)
125 {
126 return EINA_FALSE;
127 }
128 fribidi_get_joining_types(ustr, len, join_types);
129
130 fribidi_join_arabic(bidi_props->char_types + start, len,
131 bidi_props->embedding_levels + start, join_types);
132
133
134 /* Actually modify the string */
135 fribidi_shape(FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
136 bidi_props->embedding_levels + start, len, join_types, ustr);
137
138 if (join_types) free(join_types);
139
140 /* Convert back */
141#ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
142 eina_ustr = _evas_bidi_fribidichar_to_unicode(eina_ustr, ustr);
143 if (base_ustr) free(base_ustr);
144#endif
145 return EINA_TRUE;
146}
147
148/**
149 * @internal
150 * Return a -1 terminated array of the indexes of the delimiters (passed in
151 * delim) found in the string. This result should be used with par_props_get.
152 *
153 * @param str The string to parse
154 * @param delim a list of delimiters to work with.
155 * @return returns a -1 terminated array of indexes according to positions of the delimiters found. NULL if there were none.
156 */
157int *
158evas_bidi_segment_idxs_get(const Eina_Unicode *str, const char *delim)
159{
160 Eina_Unicode *udelim;
161 const Eina_Unicode *str_base = str;
162 int *ret, *tmp_ret;
163 int ret_idx = 0, ret_len = 10; /* arbitrary choice */
164 udelim = eina_unicode_utf8_to_unicode(delim, NULL);
165 ret = malloc(ret_len * sizeof(int));
166 for ( ; *str ; str++)
167 {
168 const Eina_Unicode *del;
169 for (del = udelim ; *del ; del++)
170 {
171 if (*str == *del)
172 {
173 if (ret_idx >= ret_len)
174 {
175 /* arbitrary choice */
176 ret_len += 20;
177 tmp_ret = realloc(ret, ret_len * sizeof(int));
178 if (!tmp_ret)
179 {
180 free(ret);
181 return NULL;
182 }
183 }
184 ret[ret_idx++] = str - str_base;
185 break;
186 }
187 }
188 }
189 free(udelim);
190
191 /* If no indexes were found return NULL */
192 if (ret_idx == 0)
193 {
194 free(ret);
195 return NULL;
196 }
197
198 ret[ret_idx] = -1;
199 tmp_ret = realloc(ret, (ret_idx + 1) * sizeof(int));
200
201 return (tmp_ret) ? tmp_ret : ret;
202}
203
204/**
205 * @internal
206 * Allocates bidi properties according to ustr. First checks to see if the
207 * passed has rtl chars, if not, it returns NULL.
208 *
209 * Assumes all the segment_idxs are either -1 or legal, and > 0 indexes.
210 * Also assumes that the characters at the override points are of weak/neutral
211 * bidi type, otherwise unexpected results may occur.
212 *
213 * @param ustr The string to update according to.
214 * @param len The length of the string
215 * @param segment_idxs A -1 terminated array of points to start a new bidi analysis at (used for section high level bidi overrides). - NULL means none.
216 * @return returns allocated paragraph props on success, NULL otherwise.
217 */
218Evas_BiDi_Paragraph_Props *
219evas_bidi_paragraph_props_get(const Eina_Unicode *eina_ustr, size_t len,
220 int *segment_idxs)
221{
222 Evas_BiDi_Paragraph_Props *bidi_props = NULL;
223 EvasBiDiCharType *char_types = NULL;
224 EvasBiDiLevel *embedding_levels = NULL;
225 const FriBidiChar *ustr;
226 FriBidiChar *base_ustr = NULL;
227
228 if (!eina_ustr)
229 return NULL;
230
231
232 if (!evas_bidi_is_rtl_str(eina_ustr)) /* No need to handle bidi */
233 {
234 len = -1;
235 goto cleanup;
236 }
237
238 len = eina_unicode_strlen(eina_ustr);
239 /* The size of fribidichar s different than eina_unicode, convert */
240#ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
241 base_ustr = calloc(len + 1, sizeof(FriBidiChar));
242 base_ustr = _evas_bidi_unicode_to_fribidichar(base_ustr, eina_ustr);
243 ustr = base_ustr;
244#else
245 ustr = (const FriBidiChar *) eina_ustr;
246#endif
247
248 bidi_props = evas_bidi_paragraph_props_new();
249
250 /* Prep work for reordering */
251 char_types = (EvasBiDiCharType *) malloc(sizeof(EvasBiDiCharType) * len);
252 if (!char_types)
253 {
254 len = -2;
255 goto cleanup;
256 }
257 fribidi_get_bidi_types(ustr, len, char_types);
258
259 embedding_levels = (EvasBiDiLevel *)malloc(sizeof(EvasBiDiLevel) * len);
260 if (!embedding_levels)
261 {
262 len = -2;
263 goto cleanup;
264 }
265
266 if (segment_idxs)
267 {
268 size_t pos = 0;
269 int *itr;
270 EvasBiDiLevel base_level = 0;
271 EvasBiDiParType direction;
272
273 for (itr = segment_idxs ; *itr > 0 ; itr++)
274 {
275 direction = EVAS_BIDI_PARAGRAPH_NEUTRAL;
276 if (!fribidi_get_par_embedding_levels(char_types + pos,
277 *itr - pos,
278 &direction,
279 embedding_levels + pos))
280 {
281 len = -2;
282 goto cleanup;
283 }
284
285 /* Only on the first run */
286 if (itr == segment_idxs)
287 {
288 bidi_props->direction = direction;
289 /* adjust base_level to be 1 for rtl paragraphs, and 0 for
290 * ltr paragraphs. */
291 base_level =
292 EVAS_BIDI_PARAGRAPH_DIRECTION_IS_RTL(bidi_props) ? 1 : 0;
293 }
294
295 /* We want those chars at the override points to be on the base
296 * level and we also remove -2 cause we later increment them,
297 * just for simpler code paths */
298 embedding_levels[*itr] = base_level - 2;
299 pos = *itr + 1;
300 }
301
302 direction = EVAS_BIDI_PARAGRAPH_NEUTRAL;
303 if (!fribidi_get_par_embedding_levels(char_types + pos,
304 len - pos,
305 &direction,
306 embedding_levels + pos))
307 {
308 len = -2;
309 goto cleanup;
310 }
311
312 /* Increment all levels by 2 to emulate embedding. */
313 {
314 EvasBiDiLevel *bitr = embedding_levels, *end;
315 end = bitr + len;
316 for ( ; bitr < end ; bitr++)
317 {
318 *bitr += 2;
319 }
320 }
321 }
322 else
323 {
324 if (!fribidi_get_par_embedding_levels(char_types, len,
325 &bidi_props->direction, embedding_levels))
326 {
327 len = -2;
328 goto cleanup;
329 }
330 }
331
332
333 /* clean up */
334 if (bidi_props->embedding_levels)
335 {
336 free(bidi_props->embedding_levels);
337 }
338 bidi_props->embedding_levels = embedding_levels;
339
340 /* clean up */
341
342 if (bidi_props->char_types)
343 {
344 free(bidi_props->char_types);
345 }
346 bidi_props->char_types = char_types;
347
348 if (base_ustr) free(base_ustr);
349
350
351 return bidi_props;
352
353/* Cleanup */
354cleanup:
355 if (char_types) free(char_types);
356 if (embedding_levels) free(embedding_levels);
357 if (base_ustr) free(base_ustr);
358 if (bidi_props) evas_bidi_paragraph_props_unref(bidi_props); /* Clean up the bidi props */
359 return NULL;
360}
361
362/**
363 * @internal
364 * Copies dst to src and refs (doesn't copy) the paragraph props.
365 *
366 * @param src the props to copy
367 * @param dst the props to copy to.
368 */
369void
370evas_bidi_props_copy_and_ref(const Evas_BiDi_Props *src, Evas_BiDi_Props *dst)
371{
372 dst->dir = src->dir;
373}
374
375/**
376 * @internal
377 * Reorders ustr according to the bidi props.
378 *
379 * @param ustr the string to reorder. - Null is ok, will just populate the map.
380 * @param start the start of the line
381 * @param len the length of the line
382 * @param props the paragraph props to reorder according to
383 * @param _v_to_l The visual to logical map to populate - if NULL it won't populate it.
384 * @return #EINA_FALSE on success, #EINA_TRUE on error.
385 */
386Eina_Bool
387evas_bidi_props_reorder_line(Eina_Unicode *eina_ustr, size_t start, size_t len, const Evas_BiDi_Paragraph_Props *props, EvasBiDiStrIndex **_v_to_l)
388{
389 EvasBiDiStrIndex *v_to_l = NULL;
390 FriBidiChar *ustr = NULL, *base_ustr = NULL;
391
392 if (!props)
393 return EINA_FALSE;
394
395 if (eina_ustr)
396 {
397 /* The size of fribidichar is different than eina_unicode, convert */
398#ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
399 base_ustr = ustr = calloc(len + 1, sizeof(FriBidiChar));
400 ustr = _evas_bidi_unicode_to_fribidichar(ustr, eina_ustr);
401#else
402 ustr = (FriBidiChar *) eina_ustr;
403#endif
404 }
405
406
407 if (_v_to_l) {
408 size_t i;
409 v_to_l = *_v_to_l = calloc(len, sizeof(EvasBiDiStrIndex));
410 if (!v_to_l)
411 {
412 goto error;
413 }
414 /* init the array for fribidi */
415 for (i = 0 ; i < len ; i++)
416 {
417 v_to_l[i] = i;
418 }
419 }
420
421 {
422 EvasBiDiLevel *emb_lvl;
423 emb_lvl = malloc((start + len) * sizeof(EvasBiDiLevel));
424 memcpy(emb_lvl, props->embedding_levels,
425 (start + len) * sizeof(EvasBiDiLevel));
426 /* We pass v_to_l - start, because fribidi assumes start is the offset
427 * from the start of v_to_l as well, not just the props. */
428 if (!fribidi_reorder_line (FRIBIDI_FLAGS_DEFAULT, props->char_types,
429 len, start, props->direction, emb_lvl, ustr, v_to_l - start))
430 {
431 free(emb_lvl);
432 goto error;
433 }
434 free(emb_lvl);
435 }
436
437
438 /* The size of fribidichar is different than eina_unicode, convert */
439#ifdef EVAS_FRIBIDI_EINA_UNICODE_UNEQUAL
440 _evas_bidi_fribidichar_to_unicode(eina_ustr, base_ustr);
441 free(base_ustr);
442#endif
443 return EINA_FALSE;
444/* ERROR HANDLING */
445error:
446 if (base_ustr) free(base_ustr);
447 _SAFE_FREE(v_to_l);
448 return EINA_TRUE;
449}
450
451/**
452 * @internal
453 * Returns the end of the current run of text
454 *
455 * @param bidi_props the paragraph properties
456 * @param start where to start looking from
457 * @param len the length of the string
458 * @return the position of the end of the run (offset from
459 * bidi_props->props->start), 0 when there is no end (i.e all the text)
460 */
461int
462evas_bidi_end_of_run_get(const Evas_BiDi_Paragraph_Props *bidi_props,
463 size_t start, int len)
464{
465 EvasBiDiLevel *i;
466 EvasBiDiLevel base;
467
468 if (!bidi_props || (len <= 0))
469 return 0;
470
471 i = bidi_props->embedding_levels + start;
472 base = *i;
473 for ( ; (len > 0) && (base == *i) ; len--, i++)
474 ;
475
476 if (len == 0)
477 {
478 return 0;
479 }
480 return i - (bidi_props->embedding_levels + start);
481}
482
483/**
484 * @internal
485 * Returns the visual string index from the logical string index.
486 *
487 * @param v_to_l the visual to logical map
488 * @param len the length of the map.
489 * @param position the position to convert.
490 * @return on success the visual position, on failure the same position.
491 */
492EvasBiDiStrIndex
493evas_bidi_position_logical_to_visual(EvasBiDiStrIndex *v_to_l, int len, EvasBiDiStrIndex position)
494{
495 int i;
496 EvasBiDiStrIndex *ind;
497 if (position >= len || !v_to_l)
498 return position;
499
500 for (i = 0, ind = v_to_l ; i < len ; i++, ind++)
501 {
502 if (*ind == position)
503 {
504 return i;
505 }
506 }
507 return position;
508}
509
510/**
511 * @internal
512 * Returns the reversed pos of the index.
513 *
514 * @param dir the direction of the string
515 * @param len the length of the map.
516 * @param position the position to convert.
517 * @return on success the visual position, on failure the same position.
518 */
519EvasBiDiStrIndex
520evas_bidi_position_reverse(const Evas_BiDi_Props *props, int len, EvasBiDiStrIndex position)
521{
522 if (!props || position >= len)
523 return position;
524
525 return (props->dir == EVAS_BIDI_DIRECTION_RTL) ? (len - 1) - position : position;
526}
527
528/**
529 * @internal
530 * Checks if the char is rtl oriented. I.e even a neutral char can become rtl
531 * if surrounded by rtl chars.
532 *
533 * @param bidi_props The bidi paragraph properties
534 * @param start the base position
535 * @param index the offset from the base position.
536 * @return #EINA_TRUE if true, #EINA_FALSE otherwise.
537 */
538Eina_Bool
539evas_bidi_is_rtl_char(const Evas_BiDi_Paragraph_Props *bidi_props, size_t start, EvasBiDiStrIndex ind)
540{
541 if(!bidi_props || ind < 0)
542 return EINA_FALSE;
543 return (FRIBIDI_IS_RTL(
544 bidi_props->embedding_levels[ind + start]))
545 ? EINA_TRUE : EINA_FALSE;
546}
547
548Evas_BiDi_Paragraph_Props *
549evas_bidi_paragraph_props_new(void)
550{
551 Evas_BiDi_Paragraph_Props *ret;
552 ret = calloc(1, sizeof(Evas_BiDi_Paragraph_Props));
553 ret->direction = EVAS_BIDI_PARAGRAPH_NEUTRAL;
554 ret->refcount = 1;
555
556 return ret;
557}
558
559/**
560 * @internal
561 * Refs the bidi props.
562 *
563 * @param bidi_props the props to ref.
564 */
565Evas_BiDi_Paragraph_Props *
566evas_bidi_paragraph_props_ref(Evas_BiDi_Paragraph_Props *bidi_props)
567{
568 if (!bidi_props) return NULL;
569 BIDILOCK();
570
571 bidi_props->refcount++;
572 BIDIUNLOCK();
573 return bidi_props;
574}
575
576/**
577 * @internal
578 * Unrefs and potentially frees the props.
579 *
580 * @param bidi_props the properties to unref
581 */
582void
583evas_bidi_paragraph_props_unref(Evas_BiDi_Paragraph_Props *bidi_props)
584{
585 if (!bidi_props) return;
586 BIDILOCK();
587
588 if (--bidi_props->refcount == 0)
589 {
590 evas_bidi_paragraph_props_clean(bidi_props);
591 free(bidi_props);
592 }
593 BIDIUNLOCK();
594}
595
596
597/**
598 * @internal
599 * Cleans the paragraph properties.
600 *
601 * @param bidi_props the properties to clean.
602 */
603void
604evas_bidi_paragraph_props_clean(Evas_BiDi_Paragraph_Props *bidi_props)
605{
606 _SAFE_FREE(bidi_props->embedding_levels);
607 _SAFE_FREE(bidi_props->char_types);
608}
609
610/**
611 * @internal
612 * Cleans the bidi properties.
613 *
614 * @param bidi_props the properties to clean.
615 */
616void
617evas_bidi_props_clean(Evas_BiDi_Props *bidi_props)
618{
619 if (!bidi_props) return;
620 bidi_props->dir = EVAS_BIDI_DIRECTION_NEUTRAL;
621}
622/**
623 * @}
624 */
625/**
626 * @}
627 */
628#endif
629
630#if 0
631/* Good for debugging */
632static void
633dump_levels(Eina_Unicode *ustr, EvasBiDiLevel *emb)
634{
635 for ( ; *ustr ; ustr++, emb++)
636 {
637 printf("%lc %d\n", *ustr, *emb);
638 }
639}
640#endif
641