wlt: fix shl_hook API changes
[platform/upstream/kmscon.git] / src / font_pango.c
1 /*
2  * kmscon - Pango font backend
3  *
4  * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@googlemail.com>
5  * Copyright (c) 2011 University of Tuebingen
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining
8  * a copy of this software and associated documentation files
9  * (the "Software"), to deal in the Software without restriction, including
10  * without limitation the rights to use, copy, modify, merge, publish,
11  * distribute, sublicense, and/or sell copies of the Software, and to
12  * permit persons to whom the Software is furnished to do so, subject to
13  * the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included
16  * in all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24  * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25  */
26
27 /**
28  * SECTION:font_pango.c
29  * @short_description: Pango font backend
30  * @include: font.h
31  *
32  * The pango backend uses pango and freetype2 to render glyphs into memory
33  * buffers. It uses a hashmap to cache all rendered glyphs of a single
34  * font-face. Therefore, rendering should be very fast. Also, when loading a
35  * glyph it pre-renders all common (mostly ASCII) characters, so it can measure
36  * the font and return a valid font hight/width.
37  *
38  * This is a _full_ font backend, that is, it provides every feature you expect
39  * from a font renderer. It does glyph substitution if a specific font face does
40  * not provide a requested glyph, it does correct font loading, it does
41  * italic/bold fonts correctly and more.
42  * However, this also means it pulls in a lot of dependencies including glib,
43  * pango, freetype2 and more.
44  */
45
46 #include <errno.h>
47 #include <glib.h>
48 #include <pango/pango.h>
49 #include <pango/pangoft2.h>
50 #include <pthread.h>
51 #include <stdbool.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include "font.h"
55 #include "log.h"
56 #include "shl_dlist.h"
57 #include "shl_hashtable.h"
58 #include "tsm_unicode.h"
59 #include "uterm_video.h"
60
61 #define LOG_SUBSYSTEM "font_pango"
62
63 struct face {
64         unsigned long ref;
65         struct shl_dlist list;
66
67         struct kmscon_font_attr attr;
68         struct kmscon_font_attr real_attr;
69         unsigned int baseline;
70         PangoContext *ctx;
71         pthread_mutex_t glyph_lock;
72         struct shl_hashtable *glyphs;
73 };
74
75 static pthread_mutex_t manager_mutex = PTHREAD_MUTEX_INITIALIZER;
76 static unsigned long manager__refcnt;
77 static PangoFontMap *manager__lib;
78 static struct shl_dlist manager__list = SHL_DLIST_INIT(manager__list);
79
80 static void manager_lock()
81 {
82         pthread_mutex_lock(&manager_mutex);
83 }
84
85 static void manager_unlock()
86 {
87         pthread_mutex_unlock(&manager_mutex);
88 }
89
90 static int manager__ref()
91 {
92         if (!manager__refcnt++) {
93                 manager__lib = pango_ft2_font_map_new();
94                 if (!manager__lib) {
95                         log_warn("cannot create font map");
96                         --manager__refcnt;
97                         return -EFAULT;
98                 }
99         }
100
101         return 0;
102 }
103
104 static void manager__unref()
105 {
106         if (!--manager__refcnt) {
107                 g_object_unref(manager__lib);
108                 manager__lib = NULL;
109         }
110 }
111
112 static int get_glyph(struct face *face, struct kmscon_glyph **out,
113                      uint32_t id, const uint32_t *ch, size_t len)
114 {
115         struct kmscon_glyph *glyph;
116         PangoLayout *layout;
117         PangoRectangle rec;
118         PangoLayoutLine *line;
119         FT_Bitmap bitmap;
120         unsigned int cwidth;
121         size_t ulen, cnt;
122         char *val;
123         bool res;
124         int ret;
125
126         if (!len)
127                 return -ERANGE;
128         cwidth = tsm_ucs4_get_width(*ch);
129         if (!cwidth)
130                 return -ERANGE;
131
132         pthread_mutex_lock(&face->glyph_lock);
133         res = shl_hashtable_find(face->glyphs, (void**)&glyph,
134                                  (void*)(long)id);
135         pthread_mutex_unlock(&face->glyph_lock);
136         if (res) {
137                 *out = glyph;
138                 return 0;
139         }
140
141         manager_lock();
142
143         glyph = malloc(sizeof(*glyph));
144         if (!glyph) {
145                 log_error("cannot allocate memory for new glyph");
146                 ret = -ENOMEM;
147                 goto out_unlock;
148         }
149         memset(glyph, 0, sizeof(*glyph));
150         glyph->width = cwidth;
151
152         layout = pango_layout_new(face->ctx);
153
154         /* render one line only */
155         pango_layout_set_height(layout, 0);
156
157         /* no line spacing */
158         pango_layout_set_spacing(layout, 0);
159
160         val = tsm_ucs4_to_utf8_alloc(ch, len, &ulen);
161         if (!val) {
162                 ret = -ERANGE;
163                 goto out_glyph;
164         }
165         pango_layout_set_text(layout, val, ulen);
166         free(val);
167
168         cnt = pango_layout_get_line_count(layout);
169         if (cnt == 0) {
170                 ret = -ERANGE;
171                 goto out_glyph;
172         }
173
174         line = pango_layout_get_line_readonly(layout, 0);
175
176         pango_layout_line_get_pixel_extents(line, NULL, &rec);
177         glyph->buf.width = face->real_attr.width * cwidth;
178         glyph->buf.height = face->real_attr.height;
179         glyph->buf.stride = glyph->buf.width;
180         glyph->buf.format = UTERM_FORMAT_GREY;
181
182         if (!glyph->buf.width || !glyph->buf.height) {
183                 ret = -ERANGE;
184                 goto out_glyph;
185         }
186
187         glyph->buf.data = malloc(glyph->buf.height * glyph->buf.stride);
188         if (!glyph->buf.data) {
189                 log_error("cannot allocate bitmap memory");
190                 ret = -ENOMEM;
191                 goto out_glyph;
192         }
193         memset(glyph->buf.data, 0, glyph->buf.height * glyph->buf.stride);
194
195         bitmap.rows = glyph->buf.height;
196         bitmap.width = glyph->buf.width;
197         bitmap.pitch = glyph->buf.stride;
198         bitmap.num_grays = 256;
199         bitmap.pixel_mode = FT_PIXEL_MODE_GRAY;
200         bitmap.buffer = glyph->buf.data;
201
202         pango_ft2_render_layout_line(&bitmap, line, -rec.x, -rec.y);
203
204         pthread_mutex_lock(&face->glyph_lock);
205         ret = shl_hashtable_insert(face->glyphs, (void*)(long)id, glyph);
206         pthread_mutex_unlock(&face->glyph_lock);
207         if (ret) {
208                 log_error("cannot add glyph to hashtable");
209                 goto out_buffer;
210         }
211
212         *out = glyph;
213         goto out_layout;
214
215 out_buffer:
216         free(glyph->buf.data);
217 out_glyph:
218         free(glyph);
219 out_layout:
220         g_object_unref(layout);
221 out_unlock:
222         manager_unlock();
223         return ret;
224 }
225
226 static void free_glyph(void *data)
227 {
228         struct kmscon_glyph *glyph = data;
229
230         free(glyph->buf.data);
231         free(glyph);
232 }
233
234 static int manager_get_face(struct face **out, struct kmscon_font_attr *attr)
235 {
236         struct shl_dlist *iter;
237         struct face *face, *f;
238         PangoFontDescription *desc;
239         PangoLayout *layout;
240         PangoRectangle rec;
241         int ret, num;
242         const char *str;
243
244         manager_lock();
245
246         shl_dlist_for_each(iter, &manager__list) {
247                 face = shl_dlist_entry(iter, struct face, list);
248                 if (kmscon_font_attr_match(&face->attr, attr)) {
249                         ++face->ref;
250                         *out = face;
251                         ret = 0;
252                         goto out_unlock;
253                 }
254         }
255
256         ret = manager__ref();
257         if (ret)
258                 goto out_unlock;
259
260         face = malloc(sizeof(*face));
261         if (!face) {
262                 log_error("cannot allocate memory for new face");
263                 ret = -ENOMEM;
264                 goto err_manager;
265         }
266         memset(face, 0, sizeof(*face));
267         face->ref = 1;
268         memcpy(&face->attr, attr, sizeof(*attr));
269
270         ret = pthread_mutex_init(&face->glyph_lock, NULL);
271         if (ret) {
272                 log_error("cannot initialize glyph lock");
273                 goto err_free;
274         }
275
276         ret = shl_hashtable_new(&face->glyphs, shl_direct_hash,
277                                 shl_direct_equal, NULL, free_glyph);
278         if (ret) {
279                 log_error("cannot allocate hashtable");
280                 goto err_lock;
281         }
282
283         face->ctx = pango_font_map_create_context(manager__lib);
284         pango_context_set_base_dir(face->ctx, PANGO_DIRECTION_LTR);
285         pango_context_set_language(face->ctx, pango_language_get_default());
286
287         desc = pango_font_description_from_string(attr->name);
288         pango_font_description_set_absolute_size(desc,
289                                         PANGO_SCALE * face->attr.height);
290         pango_font_description_set_weight(desc,
291                         attr->bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL);
292         pango_font_description_set_style(desc,
293                         attr->italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
294         pango_font_description_set_variant(desc, PANGO_VARIANT_NORMAL);
295         pango_font_description_set_stretch(desc, PANGO_STRETCH_NORMAL);
296         pango_font_description_set_gravity(desc, PANGO_GRAVITY_SOUTH);
297         pango_context_set_font_description(face->ctx, desc);
298         pango_font_description_free(desc);
299
300         /* measure font */
301         layout = pango_layout_new(face->ctx);
302         pango_layout_set_height(layout, 0);
303         pango_layout_set_spacing(layout, 0);
304         str = "abcdefghijklmnopqrstuvwxyz"
305               "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
306               "@!\"$%&/()=?\\}][{°^~+*#'<>|-_.:,;`´";
307         num = strlen(str);
308         pango_layout_set_text(layout, str, num);
309         pango_layout_get_pixel_extents(layout, NULL, &rec);
310
311         memcpy(&face->real_attr, &face->attr, sizeof(face->attr));
312         face->real_attr.height = rec.height;
313         face->real_attr.width = rec.width / num + 1;
314         face->baseline = PANGO_PIXELS_CEIL(pango_layout_get_baseline(layout));
315         g_object_unref(layout);
316
317         kmscon_font_attr_normalize(&face->real_attr);
318         if (!face->real_attr.height || !face->real_attr.width) {
319                 log_warning("invalid scaled font sizes");
320                 ret = -EFAULT;
321                 goto err_face;
322         }
323
324         /* The real metrics probably differ from the requested metrics so try
325          * again to find a suitable cached font. */
326         shl_dlist_for_each(iter, &manager__list) {
327                 f = shl_dlist_entry(iter, struct face, list);
328                 if (kmscon_font_attr_match(&f->real_attr, &face->real_attr)) {
329                         ++f->ref;
330                         *out = f;
331                         ret = 0;
332                         goto err_face;
333                 }
334         }
335
336         shl_dlist_link(&manager__list, &face->list);
337         *out = face;
338         ret = 0;
339         goto out_unlock;
340
341 err_face:
342         g_object_unref(face->ctx);
343         shl_hashtable_free(face->glyphs);
344 err_lock:
345         pthread_mutex_destroy(&face->glyph_lock);
346 err_free:
347         free(face);
348 err_manager:
349         manager__unref();
350 out_unlock:
351         manager_unlock();
352         return ret;
353 }
354
355 static void manager_put_face(struct face *face)
356 {
357         manager_lock();
358
359         if (!--face->ref) {
360                 shl_dlist_unlink(&face->list);
361                 shl_hashtable_free(face->glyphs);
362                 pthread_mutex_destroy(&face->glyph_lock);
363                 g_object_unref(face->ctx);
364                 free(face);
365                 manager__unref();
366         }
367
368         manager_unlock();
369 }
370
371 static int kmscon_font_pango_init(struct kmscon_font *out,
372                                   const struct kmscon_font_attr *attr)
373 {
374         struct face *face = NULL;
375         int ret;
376
377         memcpy(&out->attr, attr, sizeof(*attr));
378         kmscon_font_attr_normalize(&out->attr);
379
380         log_debug("loading pango font %s", out->attr.name);
381
382         ret = manager_get_face(&face, &out->attr);
383         if (ret)
384                 return ret;
385         memcpy(&out->attr, &face->real_attr, sizeof(out->attr));
386         out->baseline = face->baseline;
387
388         out->data = face;
389         return 0;
390 }
391
392 static void kmscon_font_pango_destroy(struct kmscon_font *font)
393 {
394         struct face *face;
395
396         log_debug("unloading pango font");
397         face = font->data;
398         manager_put_face(face);
399 }
400
401 static int kmscon_font_pango_render(struct kmscon_font *font, uint32_t id,
402                                     const uint32_t *ch, size_t len,
403                                     const struct kmscon_glyph **out)
404 {
405         struct kmscon_glyph *glyph;
406         int ret;
407
408         ret = get_glyph(font->data, &glyph, id, ch, len);
409         if (ret)
410                 return ret;
411
412         *out = glyph;
413         return 0;
414 }
415
416 static int kmscon_font_pango_render_empty(struct kmscon_font *font,
417                                           const struct kmscon_glyph **out)
418 {
419         static const uint32_t empty_char = ' ';
420         return kmscon_font_pango_render(font, empty_char, &empty_char, 1, out);
421 }
422
423 static int kmscon_font_pango_render_inval(struct kmscon_font *font,
424                                           const struct kmscon_glyph **out)
425 {
426         static const uint32_t question_mark = '?';
427         return kmscon_font_pango_render(font, question_mark, &question_mark, 1,
428                                         out);
429 }
430
431 struct kmscon_font_ops kmscon_font_pango_ops = {
432         .name = "pango",
433         .owner = NULL,
434         .init = kmscon_font_pango_init,
435         .destroy = kmscon_font_pango_destroy,
436         .render = kmscon_font_pango_render,
437         .render_empty = kmscon_font_pango_render_empty,
438         .render_inval = kmscon_font_pango_render_inval,
439 };