1 /**
2  * @file lv_bidi.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_bidi_private.h"
10 #include "lv_text_private.h"
11 #include "lv_types.h"
12 #include "../stdlib/lv_mem.h"
13 #include "../stdlib/lv_string.h"
14 
15 #if LV_USE_BIDI
16 
17 /*********************
18  *      DEFINES
19  *********************/
20 #define LV_BIDI_BRACKET_DEPTH   4
21 
22 // Highest bit of the 16-bit pos_conv value specifies whether this pos is RTL or not
23 #define GET_POS(x) ((x) & 0x7FFF)
24 #define IS_RTL_POS(x) (((x) & 0x8000) != 0)
25 #define SET_RTL_POS(x, is_rtl) (GET_POS(x) | ((is_rtl)? 0x8000: 0))
26 
27 /**********************
28  *      TYPEDEFS
29  **********************/
30 typedef struct {
31     uint32_t bracket_pos;
32     lv_base_dir_t dir;
33 } bracket_stack_t;
34 
35 typedef struct {
36     bracket_stack_t br_stack[LV_BIDI_BRACKET_DEPTH];
37     uint8_t br_stack_p;
38 } lv_bidi_ctx_t;
39 
40 /**********************
41  *  STATIC PROTOTYPES
42  **********************/
43 
44 static uint32_t lv_bidi_get_next_paragraph(const char * txt);
45 static lv_base_dir_t lv_bidi_get_letter_dir(uint32_t letter);
46 static bool lv_bidi_letter_is_weak(uint32_t letter);
47 static bool lv_bidi_letter_is_rtl(uint32_t letter);
48 static bool lv_bidi_letter_is_neutral(uint32_t letter);
49 
50 static lv_base_dir_t get_next_run(lv_bidi_ctx_t * ctx, const char * txt, lv_base_dir_t base_dir, uint32_t max_len,
51                                   uint32_t * len,
52                                   uint16_t  * pos_conv_len);
53 static void rtl_reverse(char * dest, const char * src, uint32_t len, uint16_t * pos_conv_out, uint16_t pos_conv_rd_base,
54                         uint16_t pos_conv_len);
55 static uint32_t char_change_to_pair(uint32_t letter);
56 static lv_base_dir_t bracket_process(lv_bidi_ctx_t * ctx, const char * txt, uint32_t next_pos, uint32_t len,
57                                      uint32_t letter,
58                                      lv_base_dir_t base_dir);
59 static void fill_pos_conv(uint16_t * out, uint16_t len, uint16_t index);
60 static uint32_t get_txt_len(const char * txt, uint32_t max_len);
61 
62 /**********************
63  *  STATIC VARIABLES
64  **********************/
65 static const uint8_t bracket_left[] = {"<({["};
66 static const uint8_t bracket_right[] = {">)}]"};
67 static const char * custom_neutrals = NULL;
68 
69 /**********************
70  *      MACROS
71  **********************/
72 
73 /**********************
74  *   GLOBAL FUNCTIONS
75  **********************/
76 
lv_bidi_process(const char * str_in,char * str_out,lv_base_dir_t base_dir)77 void lv_bidi_process(const char * str_in, char * str_out, lv_base_dir_t base_dir)
78 {
79     if(base_dir == LV_BASE_DIR_AUTO) base_dir = lv_bidi_detect_base_dir(str_in);
80 
81     uint32_t par_start = 0;
82     uint32_t par_len;
83 
84     while(str_in[par_start] == '\n' || str_in[par_start] == '\r') {
85         str_out[par_start] = str_in[par_start];
86         par_start ++;
87     }
88 
89     while(str_in[par_start] != '\0') {
90         par_len = lv_bidi_get_next_paragraph(&str_in[par_start]);
91         lv_bidi_process_paragraph(&str_in[par_start], &str_out[par_start], par_len, base_dir, NULL, 0);
92         par_start += par_len;
93 
94         while(str_in[par_start] == '\n' || str_in[par_start] == '\r') {
95             str_out[par_start] = str_in[par_start];
96             par_start ++;
97         }
98     }
99 
100     str_out[par_start] = '\0';
101 }
102 
103 /**
104  * Auto-detect the direction of a text based on the first strong character
105  * @param txt the text to process
106  * @return `LV_BASE_DIR_LTR` or `LV_BASE_DIR_RTL`
107  */
lv_bidi_detect_base_dir(const char * txt)108 lv_base_dir_t lv_bidi_detect_base_dir(const char * txt)
109 {
110     uint32_t i = 0;
111     uint32_t letter;
112     while(txt[i] != '\0') {
113         letter = lv_text_encoded_next(txt, &i);
114 
115         lv_base_dir_t dir;
116         dir = lv_bidi_get_letter_dir(letter);
117         if(dir == LV_BASE_DIR_RTL || dir == LV_BASE_DIR_LTR) return dir;
118     }
119 
120     /*If there were no strong char earlier return with the default base dir*/
121     if(LV_BIDI_BASE_DIR_DEF == LV_BASE_DIR_AUTO) return LV_BASE_DIR_LTR;
122     else return LV_BIDI_BASE_DIR_DEF;
123 }
124 
lv_bidi_get_logical_pos(const char * str_in,char ** bidi_txt,uint32_t len,lv_base_dir_t base_dir,uint32_t visual_pos,bool * is_rtl)125 uint16_t lv_bidi_get_logical_pos(const char * str_in, char ** bidi_txt, uint32_t len, lv_base_dir_t base_dir,
126                                  uint32_t visual_pos, bool * is_rtl)
127 {
128     uint32_t pos_conv_len = get_txt_len(str_in, len);
129     char * buf = lv_malloc(len + 1);
130     if(buf == NULL) return (uint16_t) -1;
131 
132     uint16_t * pos_conv_buf = lv_malloc(pos_conv_len * sizeof(uint16_t));
133     if(pos_conv_buf == NULL) {
134         lv_free(buf);
135         return (uint16_t) -1;
136     }
137 
138     if(bidi_txt) *bidi_txt = buf;
139 
140     lv_bidi_process_paragraph(str_in, bidi_txt ? *bidi_txt : NULL, len, base_dir, pos_conv_buf, pos_conv_len);
141 
142     if(is_rtl) *is_rtl = IS_RTL_POS(pos_conv_buf[visual_pos]);
143 
144     if(bidi_txt == NULL) lv_free(buf);
145     uint16_t res = GET_POS(pos_conv_buf[visual_pos]);
146     lv_free(pos_conv_buf);
147     return res;
148 }
149 
lv_bidi_get_visual_pos(const char * str_in,char ** bidi_txt,uint16_t len,lv_base_dir_t base_dir,uint32_t logical_pos,bool * is_rtl)150 uint16_t lv_bidi_get_visual_pos(const char * str_in, char ** bidi_txt, uint16_t len, lv_base_dir_t base_dir,
151                                 uint32_t logical_pos, bool * is_rtl)
152 {
153     uint32_t pos_conv_len = get_txt_len(str_in, len);
154     char * buf = lv_malloc(len + 1);
155     if(buf == NULL) return (uint16_t) -1;
156 
157     uint16_t * pos_conv_buf = lv_malloc(pos_conv_len * sizeof(uint16_t));
158     if(pos_conv_buf == NULL) {
159         lv_free(buf);
160         return (uint16_t) -1;
161     }
162 
163     if(bidi_txt) *bidi_txt = buf;
164 
165     lv_bidi_process_paragraph(str_in, bidi_txt ? *bidi_txt : NULL, len, base_dir, pos_conv_buf, pos_conv_len);
166 
167     for(uint16_t i = 0; i < pos_conv_len; i++) {
168         if(GET_POS(pos_conv_buf[i]) == logical_pos) {
169 
170             if(is_rtl) *is_rtl = IS_RTL_POS(pos_conv_buf[i]);
171             lv_free(pos_conv_buf);
172 
173             if(bidi_txt == NULL) lv_free(buf);
174             return i;
175         }
176     }
177     lv_free(pos_conv_buf);
178     if(bidi_txt == NULL) lv_free(buf);
179     return (uint16_t) -1;
180 }
181 
lv_bidi_process_paragraph(const char * str_in,char * str_out,uint32_t len,lv_base_dir_t base_dir,uint16_t * pos_conv_out,uint16_t pos_conv_len)182 void lv_bidi_process_paragraph(const char * str_in, char * str_out, uint32_t len, lv_base_dir_t base_dir,
183                                uint16_t * pos_conv_out, uint16_t pos_conv_len)
184 {
185     uint32_t run_len = 0;
186     lv_base_dir_t run_dir;
187     uint32_t rd = 0;
188     uint32_t wr;
189     uint16_t pos_conv_run_len = 0;
190     uint16_t pos_conv_rd = 0;
191     uint16_t pos_conv_wr;
192 
193     if(base_dir == LV_BASE_DIR_AUTO) base_dir = lv_bidi_detect_base_dir(str_in);
194     if(base_dir == LV_BASE_DIR_RTL) {
195         wr = len;
196         pos_conv_wr = pos_conv_len;
197     }
198     else {
199         wr = 0;
200         pos_conv_wr = 0;
201     }
202 
203     if(str_out) str_out[len] = '\0';
204 
205     lv_base_dir_t dir = base_dir;
206 
207     /*Empty the bracket stack*/
208     lv_bidi_ctx_t ctx;
209     lv_memzero(&ctx, sizeof(ctx));
210 
211     /*Process neutral chars in the beginning*/
212     while(rd < len) {
213         uint32_t letter = lv_text_encoded_next(str_in, &rd);
214         pos_conv_rd++;
215         dir = lv_bidi_get_letter_dir(letter);
216         if(dir == LV_BASE_DIR_NEUTRAL)  dir = bracket_process(&ctx, str_in, rd, len, letter, base_dir);
217         else if(dir != LV_BASE_DIR_WEAK) break;
218     }
219 
220     if(rd && str_in[rd] != '\0' && rd < len) {
221         lv_text_encoded_prev(str_in, &rd);
222         pos_conv_rd--;
223     }
224 
225     if(rd) {
226         if(base_dir == LV_BASE_DIR_LTR) {
227             if(str_out) {
228                 lv_memcpy(&str_out[wr], str_in, rd);
229                 wr += rd;
230             }
231             if(pos_conv_out) {
232                 fill_pos_conv(&pos_conv_out[pos_conv_wr], pos_conv_rd, 0);
233                 pos_conv_wr += pos_conv_rd;
234             }
235         }
236         else {
237             wr -= rd;
238             pos_conv_wr -= pos_conv_rd;
239             rtl_reverse(str_out ? &str_out[wr] : NULL, str_in, rd, pos_conv_out ? &pos_conv_out[pos_conv_wr] : NULL, 0,
240                         pos_conv_rd);
241         }
242     }
243 
244     /*Get and process the runs*/
245 
246     while(rd < len && str_in[rd]) {
247         run_dir = get_next_run(&ctx, &str_in[rd], base_dir, len - rd, &run_len, &pos_conv_run_len);
248 
249         if(base_dir == LV_BASE_DIR_LTR) {
250             if(run_dir == LV_BASE_DIR_LTR) {
251                 if(str_out) lv_memcpy(&str_out[wr], &str_in[rd], run_len);
252                 if(pos_conv_out) fill_pos_conv(&pos_conv_out[pos_conv_wr], pos_conv_run_len, pos_conv_rd);
253             }
254             else rtl_reverse(str_out ? &str_out[wr] : NULL, &str_in[rd], run_len, pos_conv_out ? &pos_conv_out[pos_conv_wr] : NULL,
255                                  pos_conv_rd, pos_conv_run_len);
256             wr += run_len;
257             pos_conv_wr += pos_conv_run_len;
258         }
259         else {
260             wr -= run_len;
261             pos_conv_wr -= pos_conv_run_len;
262             if(run_dir == LV_BASE_DIR_LTR) {
263                 if(str_out) lv_memcpy(&str_out[wr], &str_in[rd], run_len);
264                 if(pos_conv_out) fill_pos_conv(&pos_conv_out[pos_conv_wr], pos_conv_run_len, pos_conv_rd);
265             }
266             else rtl_reverse(str_out ? &str_out[wr] : NULL, &str_in[rd], run_len, pos_conv_out ? &pos_conv_out[pos_conv_wr] : NULL,
267                                  pos_conv_rd, pos_conv_run_len);
268         }
269 
270         rd += run_len;
271         pos_conv_rd += pos_conv_run_len;
272     }
273 }
274 
lv_bidi_calculate_align(lv_text_align_t * align,lv_base_dir_t * base_dir,const char * txt)275 void lv_bidi_calculate_align(lv_text_align_t * align, lv_base_dir_t * base_dir, const char * txt)
276 {
277     if(*base_dir == LV_BASE_DIR_AUTO) *base_dir = lv_bidi_detect_base_dir(txt);
278 
279     if(*align == LV_TEXT_ALIGN_AUTO) {
280         if(*base_dir == LV_BASE_DIR_RTL) *align = LV_TEXT_ALIGN_RIGHT;
281         else *align = LV_TEXT_ALIGN_LEFT;
282     }
283 }
284 
lv_bidi_set_custom_neutrals_static(const char * neutrals)285 void lv_bidi_set_custom_neutrals_static(const char * neutrals)
286 {
287     custom_neutrals = neutrals;
288 }
289 
290 /**********************
291  *   STATIC FUNCTIONS
292  **********************/
293 
294 /**
295  * Get the next paragraph from a text
296  * @param txt the text to process
297  * @return the length of the current paragraph in byte count
298  */
lv_bidi_get_next_paragraph(const char * txt)299 static uint32_t lv_bidi_get_next_paragraph(const char * txt)
300 {
301     uint32_t i = 0;
302 
303     lv_text_encoded_next(txt, &i);
304 
305     while(txt[i] != '\0' && txt[i] != '\n' && txt[i] != '\r') {
306         lv_text_encoded_next(txt, &i);
307     }
308 
309     return i;
310 }
311 
312 /**
313  * Get the direction of a character
314  * @param letter a Unicode character
315  * @return `LV_BASE_DIR_RTL/LTR/WEAK/NEUTRAL`
316  */
lv_bidi_get_letter_dir(uint32_t letter)317 static lv_base_dir_t lv_bidi_get_letter_dir(uint32_t letter)
318 {
319     if(lv_bidi_letter_is_rtl(letter)) return LV_BASE_DIR_RTL;
320     if(lv_bidi_letter_is_neutral(letter)) return LV_BASE_DIR_NEUTRAL;
321     if(lv_bidi_letter_is_weak(letter)) return LV_BASE_DIR_WEAK;
322 
323     return LV_BASE_DIR_LTR;
324 }
325 /**
326  * Tell whether a character is weak or not
327  * @param letter a Unicode character
328  * @return true/false
329  */
lv_bidi_letter_is_weak(uint32_t letter)330 static bool lv_bidi_letter_is_weak(uint32_t letter)
331 {
332     uint32_t i = 0;
333     static const char weaks[] = "0123456789";
334 
335     do {
336         uint32_t x = lv_text_encoded_next(weaks, &i);
337         if(letter == x) {
338             return true;
339         }
340     } while(weaks[i] != '\0');
341 
342     return false;
343 }
344 /**
345  * Tell whether a character is RTL or not
346  * @param letter a Unicode character
347  * @return true/false
348  */
lv_bidi_letter_is_rtl(uint32_t letter)349 static bool lv_bidi_letter_is_rtl(uint32_t letter)
350 {
351     if(letter == 0x202E) return true;               /*Unicode of LV_BIDI_RLO*/
352 
353     /*Check for Persian and Arabic characters [https://en.wikipedia.org/wiki/Arabic_script_in_Unicode]*/
354     if(letter >= 0x600 && letter <= 0x6FF) return true;
355     if(letter >= 0xFB50 && letter <= 0xFDFF) return true;
356     if(letter >= 0xFE70 && letter <= 0xFEFF) return true;
357 
358     /*Check for Hebrew characters [https://en.wikipedia.org/wiki/Unicode_and_HTML_for_the_Hebrew_alphabet]*/
359     if(letter >= 0x590 && letter <= 0x5FF) return true;
360     if(letter >= 0xFB1D && letter <= 0xFB4F) return true;
361 
362     return false;
363 }
364 
365 /**
366  * Tell whether a character is neutral or not
367  * @param letter a Unicode character
368  * @return true/false
369  */
lv_bidi_letter_is_neutral(uint32_t letter)370 static bool lv_bidi_letter_is_neutral(uint32_t letter)
371 {
372     uint16_t i;
373     const char * neutrals = " \t\n\r.,:;'\"`!?%/\\-=()[]{}<>@#&$|";
374     if(custom_neutrals) {
375         neutrals = custom_neutrals;
376     }
377 
378     for(i = 0; neutrals[i] != '\0'; i++) {
379         if(letter == (uint32_t)neutrals[i]) return true;
380     }
381 
382     return false;
383 }
384 
get_txt_len(const char * txt,uint32_t max_len)385 static uint32_t get_txt_len(const char * txt, uint32_t max_len)
386 {
387     uint32_t len = 0;
388     uint32_t i   = 0;
389 
390     while(i < max_len && txt[i] != '\0') {
391         lv_text_encoded_next(txt, &i);
392         len++;
393     }
394 
395     return len;
396 }
397 
fill_pos_conv(uint16_t * out,uint16_t len,uint16_t index)398 static void fill_pos_conv(uint16_t * out, uint16_t len, uint16_t index)
399 {
400     uint16_t i;
401     for(i = 0; i < len; i++) {
402         out[i] = SET_RTL_POS(index, false);
403         index++;
404     }
405 }
406 
get_next_run(lv_bidi_ctx_t * ctx,const char * txt,lv_base_dir_t base_dir,uint32_t max_len,uint32_t * len,uint16_t * pos_conv_len)407 static lv_base_dir_t get_next_run(lv_bidi_ctx_t * ctx, const char * txt, lv_base_dir_t base_dir, uint32_t max_len,
408                                   uint32_t * len,
409                                   uint16_t  * pos_conv_len)
410 {
411     uint32_t i = 0;
412     uint32_t letter;
413 
414     uint16_t pos_conv_i = 0;
415 
416     letter = lv_text_encoded_next(txt, NULL);
417     lv_base_dir_t dir = lv_bidi_get_letter_dir(letter);
418     if(dir == LV_BASE_DIR_NEUTRAL)  dir = bracket_process(ctx, txt, 0, max_len, letter, base_dir);
419 
420     /*Find the first strong char. Skip the neutrals*/
421     while(dir == LV_BASE_DIR_NEUTRAL || dir == LV_BASE_DIR_WEAK) {
422         letter = lv_text_encoded_next(txt, &i);
423 
424         pos_conv_i++;
425         dir = lv_bidi_get_letter_dir(letter);
426         if(dir == LV_BASE_DIR_NEUTRAL)  dir = bracket_process(ctx, txt, i, max_len, letter, base_dir);
427 
428         if(dir == LV_BASE_DIR_LTR || dir == LV_BASE_DIR_RTL)  break;
429 
430         if(i >= max_len || txt[i] == '\0' || txt[i] == '\n' || txt[i] == '\r') {
431             *len = i;
432             *pos_conv_len = pos_conv_i;
433             return base_dir;
434         }
435     }
436 
437     lv_base_dir_t run_dir = dir;
438 
439     uint32_t i_prev = i;
440     uint32_t i_last_strong = i;
441     uint16_t pos_conv_i_prev = pos_conv_i;
442     uint16_t pos_conv_i_last_strong = pos_conv_i;
443 
444     /*Find the next char which has different direction*/
445     lv_base_dir_t next_dir = base_dir;
446     while(i_prev < max_len && txt[i] != '\0' && txt[i] != '\n' && txt[i] != '\r') {
447         letter = lv_text_encoded_next(txt, &i);
448         pos_conv_i++;
449         next_dir  = lv_bidi_get_letter_dir(letter);
450         if(next_dir == LV_BASE_DIR_NEUTRAL)  next_dir = bracket_process(ctx, txt, i, max_len, letter, base_dir);
451 
452         if(next_dir == LV_BASE_DIR_WEAK) {
453             if(run_dir == LV_BASE_DIR_RTL) {
454                 if(base_dir == LV_BASE_DIR_RTL) {
455                     next_dir = LV_BASE_DIR_LTR;
456                 }
457             }
458         }
459 
460         /*New dir found?*/
461         if((next_dir == LV_BASE_DIR_RTL || next_dir == LV_BASE_DIR_LTR) && next_dir != run_dir) {
462             /*Include neutrals if `run_dir == base_dir`*/
463             if(run_dir == base_dir) {
464                 *len = i_prev;
465                 *pos_conv_len = pos_conv_i_prev;
466             }
467             /*Exclude neutrals if `run_dir != base_dir`*/
468             else {
469                 *len = i_last_strong;
470                 *pos_conv_len = pos_conv_i_last_strong;
471             }
472 
473             return run_dir;
474         }
475 
476         if(next_dir != LV_BASE_DIR_NEUTRAL) {
477             i_last_strong = i;
478             pos_conv_i_last_strong = pos_conv_i;
479         }
480 
481         i_prev = i;
482         pos_conv_i_prev = pos_conv_i;
483     }
484 
485     /*Handle end of of string. Apply `base_dir` on trailing neutrals*/
486 
487     /*Include neutrals if `run_dir == base_dir`*/
488     if(run_dir == base_dir) {
489         *len = i_prev;
490         *pos_conv_len = pos_conv_i_prev;
491     }
492     /*Exclude neutrals if `run_dir != base_dir`*/
493     else {
494         *len = i_last_strong;
495         *pos_conv_len = pos_conv_i_last_strong;
496     }
497 
498     return run_dir;
499 }
500 
rtl_reverse(char * dest,const char * src,uint32_t len,uint16_t * pos_conv_out,uint16_t pos_conv_rd_base,uint16_t pos_conv_len)501 static void rtl_reverse(char * dest, const char * src, uint32_t len, uint16_t * pos_conv_out, uint16_t pos_conv_rd_base,
502                         uint16_t pos_conv_len)
503 {
504     uint32_t i = len;
505     uint32_t wr = 0;
506     uint16_t pos_conv_i = pos_conv_len;
507     uint16_t pos_conv_wr = 0;
508 
509     while(i) {
510         uint32_t letter = lv_text_encoded_prev(src, &i);
511         uint16_t pos_conv_letter = --pos_conv_i;
512 
513         /*Keep weak letters (numbers) as LTR*/
514         if(lv_bidi_letter_is_weak(letter)) {
515             uint32_t last_weak = i;
516             uint32_t first_weak = i;
517             uint16_t pos_conv_last_weak = pos_conv_i;
518             uint16_t pos_conv_first_weak = pos_conv_i;
519             while(i) {
520                 letter = lv_text_encoded_prev(src, &i);
521                 pos_conv_letter = --pos_conv_i;
522 
523                 /*No need to call `char_change_to_pair` because there not such chars here*/
524 
525                 /*Finish on non-weak char*/
526                 /*but treat number and currency related chars as weak*/
527                 if(lv_bidi_letter_is_weak(letter) == false && letter != '.' && letter != ',' && letter != '$' && letter != '%') {
528                     lv_text_encoded_next(src, &i);   /*Rewind one letter*/
529                     pos_conv_i++;
530                     first_weak = i;
531                     pos_conv_first_weak = pos_conv_i;
532                     break;
533                 }
534             }
535             if(i == 0) {
536                 first_weak = 0;
537                 pos_conv_first_weak = 0;
538             }
539 
540             if(dest) lv_memcpy(&dest[wr], &src[first_weak], last_weak - first_weak + 1);
541             if(pos_conv_out) fill_pos_conv(&pos_conv_out[pos_conv_wr], pos_conv_last_weak - pos_conv_first_weak + 1,
542                                                pos_conv_rd_base + pos_conv_first_weak);
543             wr += last_weak - first_weak + 1;
544             pos_conv_wr += pos_conv_last_weak - pos_conv_first_weak + 1;
545         }
546 
547         /*Simply store in reversed order*/
548         else {
549             uint32_t letter_size = lv_text_encoded_size((const char *)&src[i]);
550             /*Swap arithmetical symbols*/
551             if(letter_size == 1) {
552                 uint32_t new_letter = letter = char_change_to_pair(letter);
553                 if(dest) dest[wr] = (uint8_t)new_letter;
554                 if(pos_conv_out) pos_conv_out[pos_conv_wr] = SET_RTL_POS(pos_conv_rd_base + pos_conv_letter, true);
555                 wr++;
556                 pos_conv_wr++;
557             }
558             /*Just store the letter*/
559             else {
560                 if(dest) lv_memcpy(&dest[wr], &src[i], letter_size);
561                 if(pos_conv_out) pos_conv_out[pos_conv_wr] = SET_RTL_POS(pos_conv_rd_base + pos_conv_i, true);
562                 wr += letter_size;
563                 pos_conv_wr++;
564             }
565         }
566     }
567 }
568 
char_change_to_pair(uint32_t letter)569 static uint32_t char_change_to_pair(uint32_t letter)
570 {
571 
572     uint8_t i;
573     for(i = 0; bracket_left[i] != '\0'; i++) {
574         if(letter == bracket_left[i]) return bracket_right[i];
575     }
576 
577     for(i = 0; bracket_right[i] != '\0'; i++) {
578         if(letter == bracket_right[i]) return bracket_left[i];
579     }
580 
581     return letter;
582 }
583 
bracket_process(lv_bidi_ctx_t * ctx,const char * txt,uint32_t next_pos,uint32_t len,uint32_t letter,lv_base_dir_t base_dir)584 static lv_base_dir_t bracket_process(lv_bidi_ctx_t * ctx, const char * txt, uint32_t next_pos, uint32_t len,
585                                      uint32_t letter,
586                                      lv_base_dir_t base_dir)
587 {
588     lv_base_dir_t bracket_dir = LV_BASE_DIR_NEUTRAL;
589 
590     uint8_t i;
591     /*Is the letter an opening bracket?*/
592     for(i = 0; bracket_left[i] != '\0'; i++) {
593         if(bracket_left[i] == letter) {
594             /*If so find its matching closing bracket.
595              *If a char with base dir. direction is found then the brackets will have `base_dir` direction*/
596             uint32_t txt_i = next_pos;
597             while(txt_i < len) {
598                 uint32_t letter_next = lv_text_encoded_next(txt, &txt_i);
599                 if(letter_next == bracket_right[i]) {
600                     /*Closing bracket found*/
601                     break;
602                 }
603                 else {
604                     /*Save the dir*/
605                     lv_base_dir_t letter_dir = lv_bidi_get_letter_dir(letter_next);
606                     if(letter_dir == base_dir) {
607                         bracket_dir = base_dir;
608                     }
609                 }
610             }
611 
612             /*There were no matching closing bracket*/
613             if(txt_i > len)  return LV_BASE_DIR_NEUTRAL;
614 
615             /*There where a strong char with base dir in the bracket so the dir is found.*/
616             if(bracket_dir != LV_BASE_DIR_NEUTRAL && bracket_dir != LV_BASE_DIR_WEAK) break;
617 
618             /*If there were no matching strong chars in the brackets then check the previous chars*/
619             txt_i = next_pos;
620             if(txt_i) lv_text_encoded_prev(txt, &txt_i);
621             while(txt_i > 0) {
622                 uint32_t letter_next = lv_text_encoded_prev(txt, &txt_i);
623                 lv_base_dir_t letter_dir = lv_bidi_get_letter_dir(letter_next);
624                 if(letter_dir == LV_BASE_DIR_LTR || letter_dir == LV_BASE_DIR_RTL) {
625                     bracket_dir = letter_dir;
626                     break;
627                 }
628             }
629 
630             /*There where a previous strong char which can be used*/
631             if(bracket_dir != LV_BASE_DIR_NEUTRAL) break;
632 
633             /*There were no strong chars before the bracket, so use the base dir.*/
634             if(txt_i == 0) bracket_dir = base_dir;
635 
636             break;
637         }
638     }
639 
640     /*The letter was an opening bracket*/
641     if(bracket_left[i] != '\0') {
642 
643         if(bracket_dir == LV_BASE_DIR_NEUTRAL || ctx->br_stack_p == LV_BIDI_BRACKET_DEPTH) return LV_BASE_DIR_NEUTRAL;
644 
645         ctx->br_stack[ctx->br_stack_p].bracket_pos = i;
646         ctx->br_stack[ctx->br_stack_p].dir = bracket_dir;
647 
648         ctx->br_stack_p++;
649         return bracket_dir;
650     }
651     else if(ctx->br_stack_p > 0) {
652         /*Is the letter a closing bracket of the last opening?*/
653         if(letter == bracket_right[ctx->br_stack[ctx->br_stack_p - 1].bracket_pos]) {
654             bracket_dir = ctx->br_stack[ctx->br_stack_p - 1].dir;
655             ctx->br_stack_p--;
656             return bracket_dir;
657         }
658     }
659 
660     return LV_BASE_DIR_NEUTRAL;
661 }
662 
663 #endif /*LV_USE_BIDI*/
664