Evas font: Fixed memory leak when reloading an already-loaded face.
[framework/uifw/evas.git] / src / lib / engines / common / evas_font_ot.c
1 #include "evas_font_ot.h"
2
3 #ifdef OT_SUPPORT
4 # include <hb.h>
5 # include <hb-ft.h>
6 #endif
7
8 #include "evas_common.h"
9
10 #include <Eina.h>
11 #include "evas_font_private.h"
12
13 EAPI Eina_Bool
14 evas_common_font_ot_is_enabled(void)
15 {
16 #ifdef OT_SUPPORT
17    static int ret = -1;
18    const char *env;
19    if (ret != -1)
20      {
21         return ret;
22      }
23
24    env = getenv("EVAS_USE_OT");
25    if (env && atoi(env))
26      {
27         ret = EINA_TRUE;
28         return ret;
29      }
30 #endif
31    return EINA_FALSE;
32 }
33
34 #ifdef OT_SUPPORT
35 /* FIXME: doc. returns #items */
36 EAPI int
37 evas_common_font_ot_cluster_size_get(const Evas_Text_Props *props, size_t char_index, int orig_len)
38 {
39    int i;
40    int items;
41    int left_bound, right_bound;
42    size_t base_cluster = EVAS_FONT_OT_POS_GET(props->ot_data->items[char_index]);
43    for (i = (int) char_index ;
44          (i >= 0) &&
45          (EVAS_FONT_OT_POS_GET(props->ot_data->items[i]) == base_cluster) ;
46          i--)
47      ;
48    left_bound = i;
49    for (i = (int) char_index + 1;
50          (i < (int) props->ot_data->len) &&
51          (EVAS_FONT_OT_POS_GET(props->ot_data->items[i]) == base_cluster) ;
52          i++)
53      ;
54    right_bound = i;
55    if (right_bound == left_bound)
56      {
57         items = 1;
58      }
59    else if (props->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
60      {
61         if (left_bound < 0)
62           {
63              items = orig_len -
64                 props->ot_data->items[left_bound + 1].source_cluster;
65           }
66         else
67           {
68              items = props->ot_data->items[left_bound].source_cluster -
69                 props->ot_data->items[left_bound + 1].source_cluster;
70           }
71      }
72    else
73      {
74         if (right_bound == (int) props->ot_data->len)
75           {
76              items = orig_len -
77                 props->ot_data->items[right_bound - 1].source_cluster;
78           }
79         else
80           {
81              items = props->ot_data->items[right_bound].source_cluster -
82                 props->ot_data->items[right_bound - 1].source_cluster;
83           }
84      }
85    return (items > 0) ? items : 1;
86 }
87
88 EAPI void
89 evas_common_font_ot_load_face(void *_font)
90 {
91    RGBA_Font_Source *font = (RGBA_Font_Source *) _font;
92    /* Unload the face if by any chance it's already loaded */
93    evas_common_font_ot_unload_face(font);
94    font->hb.face = hb_ft_face_create(font->ft.face, NULL);
95 }
96
97 EAPI void
98 evas_common_font_ot_unload_face(void *_font)
99 {
100    RGBA_Font_Source *font = (RGBA_Font_Source *) _font;
101    if (!font->hb.face) return;
102    hb_face_destroy(font->hb.face);
103    font->hb.face = NULL;
104 }
105
106 static void
107 _evas_common_font_ot_shape(hb_buffer_t *buffer, RGBA_Font_Source *src)
108 {
109    hb_font_t   *hb_font;
110
111    hb_font = hb_ft_font_create(src->ft.face, NULL);
112
113    hb_shape(hb_font, src->hb.face, buffer, NULL, 0);
114    hb_font_destroy(hb_font);
115 }
116
117 /* Won't work in the middle of ligatures */
118 EAPI void
119 evas_common_font_ot_cutoff_text_props(Evas_Text_Props *props, int cutoff)
120 {
121    Evas_Font_OT_Data *new_data;
122    if ((cutoff <= 0) || (!props->ot_data) ||
123          (((size_t) cutoff) >= props->ot_data->len))
124      return;
125
126    new_data = malloc(sizeof(Evas_Font_OT_Data));
127    memcpy(new_data, props->ot_data, sizeof(Evas_Font_OT_Data));
128    new_data->refcount = 1;
129    new_data->len = cutoff;
130    new_data->items = malloc(cutoff * sizeof(Evas_Font_OT_Data_Item));
131
132    if (props->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
133      {
134         memcpy(new_data->items,
135               props->ot_data->items + (props->ot_data->len - cutoff),
136               cutoff * sizeof(Evas_Font_OT_Data_Item));
137      }
138    else
139      {
140         memcpy(new_data->items,
141               props->ot_data->items,
142               cutoff * sizeof(Evas_Font_OT_Data_Item));
143      }
144
145    evas_common_font_ot_props_unref(props->ot_data);
146    props->ot_data = new_data;
147 }
148
149 /* Won't work in the middle of ligatures
150  * aissumes ext doesn't have an already init ot_data
151  * we assume there's at least one char in each part */
152 EAPI void
153 evas_common_font_ot_split_text_props(Evas_Text_Props *base,
154       Evas_Text_Props *ext, int cutoff)
155 {
156    Evas_Font_OT_Data *new_data;
157    int i;
158    if ((cutoff <= 0) || (!base->ot_data) ||
159          (((size_t) cutoff) >= base->ot_data->len))
160      return;
161
162    ext->ot_data = calloc(1, sizeof(Evas_Font_OT_Data));
163    ext->ot_data->refcount = 1;
164    ext->ot_data->len = base->ot_data->len - cutoff;
165    ext->ot_data->items = calloc(ext->ot_data->len,
166          sizeof(Evas_Font_OT_Data_Item));
167
168    new_data = malloc(sizeof(Evas_Font_OT_Data));
169    memcpy(new_data, base->ot_data, sizeof(Evas_Font_OT_Data));
170    new_data->refcount = 1;
171    new_data->items = malloc(cutoff * sizeof(Evas_Font_OT_Data_Item));
172    new_data->len = cutoff;
173
174    if (base->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
175      {
176         memcpy(ext->ot_data->items, base->ot_data->items,
177               ext->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
178         memcpy(new_data->items,
179               base->ot_data->items + ext->ot_data->len,
180               cutoff * sizeof(Evas_Font_OT_Data_Item));
181      }
182    else
183      {
184         memcpy(ext->ot_data->items, base->ot_data->items + cutoff,
185               ext->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
186         memcpy(new_data->items, base->ot_data->items,
187               cutoff * sizeof(Evas_Font_OT_Data_Item));
188      }
189    evas_common_font_ot_props_unref(base->ot_data);
190    base->ot_data = new_data;
191
192    /* Adjust the offset of the clusters */
193      {
194         size_t min;
195         min = ext->ot_data->items[0].source_cluster;
196         for (i = 1 ; i < (int) ext->ot_data->len ; i++)
197           {
198              if (ext->ot_data->items[i].source_cluster < min)
199                {
200                   min = ext->ot_data->items[i].source_cluster;
201                }
202           }
203         for (i = 0 ; i < (int) ext->ot_data->len ; i++)
204           {
205              ext->ot_data->items[i].source_cluster -= min;
206           }
207         ext->ot_data->offset = base->ot_data->offset + min;
208      }
209 }
210
211 /* Won't work in the middle of ligatures
212  * assumes both are init correctly and that both are from the
213  * same origin item, i.e both have the same script + direction.
214  * assume item1 is logically first */
215 EAPI void
216 evas_common_font_ot_merge_text_props(Evas_Text_Props *item1,
217       const Evas_Text_Props *item2)
218 {
219    Evas_Font_OT_Data *new_data;
220    Evas_Font_OT_Data_Item *itr; /* Itr will be used for adding back
221                                          the offsets */
222    size_t len;
223    if (!item1->ot_data || !item2->ot_data)
224      return;
225    len = item1->ot_data->len + item2->ot_data->len;
226
227    new_data = malloc(sizeof(Evas_Font_OT_Data));
228    memcpy(new_data, item1->ot_data, sizeof(Evas_Font_OT_Data));
229    new_data->refcount = 1;
230    new_data->items = malloc(len * sizeof(Evas_Font_OT_Data_Item));
231    new_data->len = len;
232    if (item1->bidi.dir == EVAS_BIDI_DIRECTION_RTL)
233      {
234         memcpy(new_data->items, item2->ot_data->items,
235               item2->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
236         memcpy(new_data->items + item2->ot_data->len, item1->ot_data->items,
237               item1->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
238         itr = new_data->items;
239      }
240    else
241      {
242         memcpy(new_data->items, item1->ot_data->items,
243               item1->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
244         memcpy(new_data->items + item1->ot_data->len, item2->ot_data->items,
245               item2->ot_data->len * sizeof(Evas_Font_OT_Data_Item));
246         itr = new_data->items + item1->ot_data->len;
247      }
248    evas_common_font_ot_props_unref(item1->ot_data);
249    item1->ot_data = new_data;
250    /* Add back the offset of item2 to the newly created */
251    if (item2->ot_data->offset > 0)
252      {
253         int i;
254         for (i = 0 ; i < (int) item2->ot_data->len ; i++, itr++)
255           {
256              /* This must be > 0, just because this is how it works */
257              itr->source_cluster += item2->ot_data->offset -
258                 item1->ot_data->offset;
259           }
260      }
261 }
262
263 EAPI Eina_Bool
264 evas_common_font_ot_populate_text_props(void *_fn, const Eina_Unicode *text,
265       Evas_Text_Props *props, int len)
266 {
267    RGBA_Font *fn = (RGBA_Font *) _fn;
268    RGBA_Font_Int *fi;
269    hb_buffer_t *buffer;
270    hb_glyph_position_t *positions;
271    hb_glyph_info_t *infos;
272    int slen;
273    unsigned int i;
274    if (!evas_common_font_ot_is_enabled()) return EINA_TRUE;
275    if (props->ot_data)
276      {
277         evas_common_font_ot_props_unref(props->ot_data);
278      }
279    props->ot_data = calloc(1, sizeof(Evas_Font_OT_Data));
280    props->ot_data->refcount = 1;
281
282    fi = fn->fonts->data;
283    /* Load the font needed for this script */
284      {
285         /* Skip common chars */
286         const Eina_Unicode *tmp;
287         for (tmp = text ;
288               *tmp &&
289               evas_common_language_char_script_get(*tmp) == EVAS_SCRIPT_COMMON ;
290               tmp++)
291           ;
292         if (!*tmp && (tmp > text)) tmp--;
293         evas_common_font_glyph_search(fn, &fi, *tmp);
294      }
295    evas_common_font_int_reload(fi);
296    if (fi->src->current_size != fi->size)
297      {
298         FTLOCK();
299         FT_Activate_Size(fi->ft.size);
300         FTUNLOCK();
301         fi->src->current_size = fi->size;
302      }
303
304    if (len < 0)
305      {
306         slen = eina_unicode_strlen(text);
307      }
308    else
309      {
310         slen = len;
311      }
312
313    buffer = hb_buffer_create(slen);
314    hb_buffer_set_unicode_funcs(buffer, evas_common_language_unicode_funcs_get());
315    hb_buffer_set_language(buffer, hb_language_from_string(
316             evas_common_language_from_locale_get()));
317    hb_buffer_set_script(buffer, props->script);
318    hb_buffer_set_direction(buffer,
319          (props->bidi.dir == EVAS_BIDI_DIRECTION_RTL) ?
320          HB_DIRECTION_RTL : HB_DIRECTION_LTR);
321    /* FIXME: add run-time conversions if needed, which is very unlikely */
322    hb_buffer_add_utf32(buffer, (const uint32_t *) text, slen, 0, slen);
323
324    _evas_common_font_ot_shape(buffer, fi->src);
325
326    props->ot_data->len = hb_buffer_get_length(buffer);
327    props->ot_data->items = calloc(props->ot_data->len,
328          sizeof(Evas_Font_OT_Data_Item));
329    positions = hb_buffer_get_glyph_positions(buffer);
330    infos = hb_buffer_get_glyph_infos(buffer);
331    for (i = 0 ; i < props->ot_data->len ; i++)
332      {
333         props->ot_data->items[i].index = infos[i].codepoint;
334         props->ot_data->items[i].source_cluster = infos[i].cluster;
335         props->ot_data->items[i].x_advance = positions[i].x_advance;
336         props->ot_data->items[i].x_offset = positions[i].x_offset;
337         props->ot_data->items[i].y_offset = positions[i].y_offset;
338      }
339
340    hb_buffer_destroy(buffer);
341    evas_common_font_int_use_trim();
342
343    return EINA_FALSE;
344 }
345
346 EAPI void
347 evas_common_font_ot_props_ref(Evas_Font_OT_Data *data)
348 {
349    data->refcount++;
350 }
351
352 EAPI void
353 evas_common_font_ot_props_unref(Evas_Font_OT_Data *data)
354 {
355    OTLOCK();
356    if (--data->refcount == 0)
357      {
358         if (data->items)
359           free(data->items);
360         free(data);
361      }
362    OTUNLOCK();
363 }
364 #endif
365