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