evas/cserve2: Use width and horizontal bearing from
[profile/ivi/evas.git] / src / lib / engines / common / evas_text_utils.c
1 #include "evas_common.h"
2 #include "evas_font_private.h"
3 #include "evas_text_utils.h"
4 #include "language/evas_bidi_utils.h"
5 #include "language/evas_language_utils.h"
6 #include "evas_font_ot.h"
7
8 #define PROPS_CHANGE(Props) Props->changed = EINA_TRUE;
9
10 void
11 evas_common_text_props_bidi_set(Evas_Text_Props *props,
12       Evas_BiDi_Paragraph_Props *bidi_par_props, size_t start)
13 {
14 #ifdef BIDI_SUPPORT
15    props->bidi.dir = (evas_bidi_is_rtl_char(
16             bidi_par_props,
17             0,
18             start)) ? EVAS_BIDI_DIRECTION_RTL : EVAS_BIDI_DIRECTION_LTR;
19 #else
20    (void) start;
21    (void) bidi_par_props;
22    props->bidi.dir = EVAS_BIDI_DIRECTION_LTR;
23 #endif
24    PROPS_CHANGE(props);
25 }
26
27 void
28 evas_common_text_props_script_set(Evas_Text_Props *props, Evas_Script_Type scr)
29 {
30    props->script = scr;
31    PROPS_CHANGE(props);
32 }
33
34 void
35 evas_common_text_props_content_copy_and_ref(Evas_Text_Props *dst,
36       const Evas_Text_Props *src)
37 {
38    memcpy(dst, src, sizeof(Evas_Text_Props));
39    evas_common_text_props_content_ref(dst);
40 }
41
42 void
43 evas_common_text_props_content_ref(Evas_Text_Props *props)
44 {
45    /* No content in this case */
46    if (!props->info)
47       return;
48
49    props->info->refcount++;
50 }
51
52 void
53 evas_common_text_props_content_unref(Evas_Text_Props *props)
54 {
55    /* No content in this case */
56    if (!props->info)
57       return;
58
59    if (--(props->info->refcount) == 0)
60      {
61         if (props->info->glyph)
62           free(props->info->glyph);
63 #ifdef OT_SUPPORT
64         if (props->info->ot)
65           free(props->info->ot);
66 #endif
67         free(props->info);
68         props->info = NULL;
69      }
70 }
71
72 static int
73 _evas_common_text_props_cluster_move(const Evas_Text_Props *props, int pos,
74       Eina_Bool right)
75 {
76    int prop_pos = evas_common_text_props_index_find(props, pos);
77    if (!right && (prop_pos > 0))
78      {
79 #ifdef OT_SUPPORT
80         return props->info->ot[props->start + prop_pos - 1].source_cluster -
81            props->text_offset;
82 #else
83         return props->start + prop_pos - 1 - props->text_offset;
84 #endif
85      }
86    else if (right && (prop_pos < (int) (props->len - 1)))
87      {
88 #ifdef OT_SUPPORT
89         return props->info->ot[props->start + prop_pos + 1].source_cluster -
90            props->text_offset;
91 #else
92         return props->start + prop_pos + 1 - props->text_offset;
93 #endif
94      }
95
96    return pos;
97 }
98
99 EAPI int
100 evas_common_text_props_cluster_next(const Evas_Text_Props *props, int pos)
101 {
102    Eina_Bool right;
103    /* Move right if we are in a non-rtl text */
104    right = (props->bidi.dir != EVAS_BIDI_DIRECTION_RTL);
105    return _evas_common_text_props_cluster_move(props, pos, right);
106 }
107
108 EAPI int
109 evas_common_text_props_cluster_prev(const Evas_Text_Props *props, int pos)
110 {
111    Eina_Bool right;
112    /* Move right if we are in an rtl text */
113    right = (props->bidi.dir == EVAS_BIDI_DIRECTION_RTL);
114    return _evas_common_text_props_cluster_move(props, pos, right);
115 }
116
117 /* Returns the index of the logical char in the props. */
118 EAPI int
119 evas_common_text_props_index_find(const Evas_Text_Props *props, int _cutoff)
120 {
121 #ifdef OT_SUPPORT
122    Evas_Font_OT_Info *ot_info;
123    int min = 0;
124    int max = props->len - 1;
125    int mid;
126
127    _cutoff += props->text_offset;
128    ot_info = props->info->ot + props->start;
129    /* Should get us closer to the right place. */
130    if ((min <= _cutoff) && (_cutoff <= max))
131       mid = _cutoff;
132    else
133       mid = (min + max) / 2;
134
135    if (props->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
136      {
137         /* Monotonic in a descending order */
138         do
139           {
140              if (_cutoff > (int) ot_info[mid].source_cluster)
141                 max = mid - 1;
142              else if (_cutoff < (int) ot_info[mid].source_cluster)
143                 min = mid + 1;
144              else
145                 break;
146
147              mid = (min + max) / 2;
148           }
149         while (min <= max);
150      }
151    else
152      {
153         /* Monotonic in an ascending order */
154         do
155           {
156              if (_cutoff < (int) ot_info[mid].source_cluster)
157                 max = mid - 1;
158              else if (_cutoff > (int) ot_info[mid].source_cluster)
159                 min = mid + 1;
160              else
161                 break;
162
163              mid = (min + max) / 2;
164           }
165         while (min <= max);
166      }
167
168    /* If we didn't find, abort */
169    if (min > max)
170       return -1;
171
172    ot_info += mid;
173    if (props->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
174      {
175         /* Walk to the last one of the same cluster */
176         for ( ; mid < (int) props->len ; mid++, ot_info++)
177           {
178              if (ot_info->source_cluster != (size_t) _cutoff)
179                 break;
180           }
181         mid--;
182      }
183    else
184      {
185         /* Walk to the last one of the same cluster */
186         for ( ; mid >= 0 ; mid--, ot_info--)
187           {
188              if (ot_info->source_cluster != (size_t) _cutoff)
189                 break;
190           }
191         mid++;
192      }
193
194    return mid;
195 #else
196    return _cutoff;
197    (void) props;
198 #endif
199 }
200
201 /* Won't work in the middle of ligatures, assumes cutoff < len.
202  * Also won't work in the middle of indic words, should handle that in a
203  * smart way. */
204 EAPI void
205 evas_common_text_props_split(Evas_Text_Props *base,
206       Evas_Text_Props *ext, int _cutoff)
207 {
208    size_t cutoff;
209
210    /* Translate text cutoff pos to string object cutoff point */
211 #ifdef OT_SUPPORT
212    _cutoff = evas_common_text_props_index_find(base, _cutoff);
213
214    if (_cutoff >= 0)
215      {
216         cutoff = (size_t) _cutoff;
217      }
218    else
219      {
220         ERR("Couldn't find the cutoff position. Is it inside a cluster?");
221         return;
222      }
223 #else
224    cutoff = (size_t) _cutoff;
225 #endif
226
227    evas_common_text_props_content_copy_and_ref(ext, base);
228    if (base->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
229      {
230         ext->start = base->start;
231         ext->len = cutoff + 1;
232         base->start = base->start + ext->len;
233         base->len = base->len - ext->len;
234
235 #ifdef OT_SUPPORT
236         ext->text_offset =
237            ext->info->ot[ext->start + ext->len - 1].source_cluster;
238 #else
239         ext->text_offset = base->text_offset + base->len;
240 #endif
241      }
242    else
243      {
244         ext->start = base->start + cutoff;
245         ext->len = base->len - cutoff;
246         base->len = cutoff;
247
248 #ifdef OT_SUPPORT
249         ext->text_offset = ext->info->ot[ext->start].source_cluster;
250 #else
251         ext->text_offset = base->text_offset + base->len;
252 #endif
253      }
254    ext->text_len = base->text_len - (ext->text_offset - base->text_offset);
255    base->text_len = (ext->text_offset - base->text_offset);
256    PROPS_CHANGE(base);
257    PROPS_CHANGE(ext);
258 }
259
260 /* Won't work in the middle of ligatures */
261 EAPI void
262 evas_common_text_props_merge(Evas_Text_Props *item1,
263       const Evas_Text_Props *item2)
264 {
265    if (item1->info != item2->info)
266      {
267         ERR("tried merge back items that weren't together in the first place.");
268         return;
269      }
270    if (item1->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
271      {
272         item1->start = item2->start;
273      }
274
275    item1->len += item2->len;
276    item1->text_len += item2->text_len;
277    PROPS_CHANGE(item1);
278 }
279
280 EAPI Eina_Bool
281 evas_common_text_props_content_create(void *_fi, const Eina_Unicode *text,
282       Evas_Text_Props *text_props, const Evas_BiDi_Paragraph_Props *par_props,
283       size_t par_pos, int len)
284 {
285    RGBA_Font_Int *fi = (RGBA_Font_Int *) _fi;
286
287    if (text_props->info)
288      {
289         evas_common_text_props_content_unref(text_props);
290      }
291    if (len == 0)
292      {
293         text_props->info = NULL;
294         text_props->start = text_props->len = text_props->text_offset = 0;
295      }
296    text_props->info = calloc(1, sizeof(Evas_Text_Props_Info));
297
298    text_props->font_instance = fi;
299
300    evas_common_font_int_reload(fi);
301    if (fi->src->current_size != fi->size)
302      {
303         FTLOCK();
304         FT_Activate_Size(fi->ft.size);
305         FTUNLOCK();
306         fi->src->current_size = fi->size;
307      }
308
309    text_props->changed = EINA_TRUE;
310
311 #ifdef OT_SUPPORT
312    size_t char_index;
313    Evas_Font_Glyph_Info *gl_itr;
314    Evas_Coord pen_x = 0, adjust_x = 0;
315    (void) par_props;
316    (void) par_pos;
317
318    evas_common_font_ot_populate_text_props(text, text_props, len);
319
320    gl_itr = text_props->info->glyph;
321    for (char_index = 0 ; char_index < text_props->len ; char_index++)
322      {
323         FT_UInt idx;
324         RGBA_Font_Glyph *fg;
325         Eina_Bool is_replacement = EINA_FALSE;
326         /* If we got a malformed index, show the replacement char instead */
327         if (gl_itr->index == 0)
328           {
329              gl_itr->index = evas_common_get_char_index(fi, REPLACEMENT_CHAR);
330              is_replacement = EINA_TRUE;
331           }
332         idx = gl_itr->index;
333         LKL(fi->ft_mutex);
334         fg = evas_common_font_int_cache_glyph_get(fi, idx);
335         if (!fg)
336           {
337              LKU(fi->ft_mutex);
338              continue;
339           }
340         LKU(fi->ft_mutex);
341
342         gl_itr->x_bear = fg->x_bear;
343         gl_itr->width = fg->width;
344         /* text_props->info->glyph[char_index].advance =
345          * text_props->info->glyph[char_index].index =
346          * already done by the ot function */
347         if (EVAS_FONT_CHARACTER_IS_INVISIBLE(
348               text[text_props->info->ot[char_index].source_cluster]))
349           {
350              gl_itr->index = 0;
351              /* Reduce the current advance */
352              if (gl_itr > text_props->info->glyph)
353                {
354                   adjust_x -= gl_itr->pen_after - (gl_itr - 1)->pen_after;
355                }
356              else
357                {
358                   adjust_x -= gl_itr->pen_after;
359                }
360           }
361         else
362           {
363              if (is_replacement)
364                {
365                   /* Update the advance accordingly */
366                   adjust_x += (pen_x + (fg->glyph->advance.x >> 16)) -
367                      gl_itr->pen_after;
368                }
369              pen_x = gl_itr->pen_after;
370           }
371         gl_itr->pen_after += adjust_x;
372
373         fi = text_props->font_instance;
374         gl_itr++;
375      }
376 #else
377    /* We are walking the string in visual ordering */
378    Evas_Font_Glyph_Info *gl_itr;
379    Eina_Bool use_kerning;
380    FT_UInt prev_index;
381    FT_Face pface = NULL;
382    Evas_Coord pen_x = 0;
383    int adv_d, i;
384 #if !defined(OT_SUPPORT) && defined(BIDI_SUPPORT)
385    Eina_Unicode *base_str = NULL;
386    if (text_props->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
387      {
388         text = base_str = eina_unicode_strndup(text, len);
389         evas_bidi_shape_string(base_str, par_props, par_pos, len);
390      }
391 #else
392    (void) par_props;
393    (void) par_pos;
394 #endif
395
396    FTLOCK();
397    use_kerning = FT_HAS_KERNING(fi->src->ft.face);
398    FTUNLOCK();
399    prev_index = 0;
400
401    i = len;
402    text_props->info->glyph = calloc(len,
403                                     sizeof(Evas_Font_Glyph_Info));
404
405    if (text_props->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
406      {
407         text += len - 1;
408         adv_d = -1;
409      }
410    else
411      {
412         adv_d = 1;
413      }
414
415    gl_itr = text_props->info->glyph;
416    for ( ; i > 0 ; gl_itr++, text += adv_d, i--)
417      {
418         FT_UInt idx;
419         RGBA_Font_Glyph *fg;
420         int _gl, kern;
421         Evas_Coord adv;
422         _gl = *text;
423         if (_gl == 0) break;
424
425         idx = evas_common_get_char_index(fi, _gl);
426         if (idx == 0)
427           {
428              idx = evas_common_get_char_index(fi, REPLACEMENT_CHAR);
429           }
430
431         fg = evas_common_font_int_cache_glyph_get(fi, idx);
432         if (!fg) continue;
433         kern = 0;
434
435         if ((use_kerning) && (prev_index) && (idx) &&
436             (pface == fi->src->ft.face))
437           {
438              if (evas_common_font_query_kerning(fi, prev_index, idx, &kern))
439                {
440                   pen_x += kern;
441                   (gl_itr - 1)->pen_after +=
442                      EVAS_FONT_ROUND_26_6_TO_INT(kern);
443                }
444           }
445
446         pface = fi->src->ft.face;
447
448         gl_itr->index = idx;
449         gl_itr->x_bear = fg->x_bear;
450         adv = fg->glyph->advance.x >> 10;
451         gl_itr->width = fg->width;
452
453         if (EVAS_FONT_CHARACTER_IS_INVISIBLE(_gl))
454           {
455              gl_itr->index = 0;
456           }
457         else
458           {
459              pen_x += adv;
460           }
461
462         gl_itr->pen_after = EVAS_FONT_ROUND_26_6_TO_INT(pen_x);
463
464         prev_index = idx;
465      }
466    text_props->len = len;
467 # if !defined(OT_SUPPORT) && defined(BIDI_SUPPORT)
468    if (base_str)
469       free(base_str);
470 # endif
471 #endif
472    text_props->text_len = len;
473    text_props->info->refcount = 1;
474    return EINA_TRUE;
475 }