1 /*
2 * Copyright (C) 2017 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
18 */
19
20 #include "config.h"
21
22 #include "gsk/gsk.h"
23 #include "gsk/gskrendernodeprivate.h"
24 #include "gskpango.h"
25 #include "gtksnapshotprivate.h"
26 #include "gtktextlayoutprivate.h"
27 #include "gtktextviewprivate.h"
28 #include "gtkwidgetprivate.h"
29 #include "gtkcsscolorvalueprivate.h"
30
31 #include <math.h>
32
33 #include <pango/pango.h>
34 #include <cairo.h>
35
G_DEFINE_TYPE(GskPangoRenderer,gsk_pango_renderer,PANGO_TYPE_RENDERER)36 G_DEFINE_TYPE (GskPangoRenderer, gsk_pango_renderer, PANGO_TYPE_RENDERER)
37
38 void
39 gsk_pango_renderer_set_state (GskPangoRenderer *crenderer,
40 GskPangoRendererState state)
41 {
42 g_return_if_fail (GSK_IS_PANGO_RENDERER (crenderer));
43
44 crenderer->state = state;
45 }
46
47 void
gsk_pango_renderer_set_shape_handler(GskPangoRenderer * crenderer,GskPangoShapeHandler handler)48 gsk_pango_renderer_set_shape_handler (GskPangoRenderer *crenderer,
49 GskPangoShapeHandler handler)
50 {
51 g_return_if_fail (GSK_IS_PANGO_RENDERER (crenderer));
52
53 crenderer->shape_handler = handler;
54 }
55
56 static void
get_color(GskPangoRenderer * crenderer,PangoRenderPart part,GdkRGBA * rgba)57 get_color (GskPangoRenderer *crenderer,
58 PangoRenderPart part,
59 GdkRGBA *rgba)
60 {
61 const PangoColor *color = pango_renderer_get_color ((PangoRenderer *) (crenderer), part);
62 const guint16 a = pango_renderer_get_alpha ((PangoRenderer *) (crenderer), part);
63
64 if (color)
65 {
66 rgba->red = color->red / 65535.;
67 rgba->green = color->green / 65535.;
68 rgba->blue = color->blue / 65535.;
69 rgba->alpha = a ? a / 65535. : crenderer->fg_color->alpha;
70 }
71 else
72 {
73 *rgba = *crenderer->fg_color;
74 if (a)
75 rgba->alpha = a / 65535.;
76 }
77 }
78
79 static void
set_color(GskPangoRenderer * crenderer,PangoRenderPart part,cairo_t * cr)80 set_color (GskPangoRenderer *crenderer,
81 PangoRenderPart part,
82 cairo_t *cr)
83 {
84 GdkRGBA rgba = { 0, 0, 0, 1 };
85
86 get_color (crenderer, part, &rgba);
87 gdk_cairo_set_source_rgba (cr, &rgba);
88 }
89
90 static void
gsk_pango_renderer_draw_glyph_item(PangoRenderer * renderer,const char * text,PangoGlyphItem * glyph_item,int x,int y)91 gsk_pango_renderer_draw_glyph_item (PangoRenderer *renderer,
92 const char *text,
93 PangoGlyphItem *glyph_item,
94 int x,
95 int y)
96 {
97 GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
98 GdkRGBA color;
99
100 get_color (crenderer, PANGO_RENDER_PART_FOREGROUND, &color);
101
102 gtk_snapshot_append_text (crenderer->snapshot,
103 glyph_item->item->analysis.font,
104 glyph_item->glyphs,
105 &color,
106 (float) x / PANGO_SCALE,
107 (float) y / PANGO_SCALE);
108 }
109
110 static void
gsk_pango_renderer_draw_rectangle(PangoRenderer * renderer,PangoRenderPart part,int x,int y,int width,int height)111 gsk_pango_renderer_draw_rectangle (PangoRenderer *renderer,
112 PangoRenderPart part,
113 int x,
114 int y,
115 int width,
116 int height)
117 {
118 GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
119 GdkRGBA rgba;
120
121 get_color (crenderer, part, &rgba);
122
123 gtk_snapshot_append_color (crenderer->snapshot,
124 &rgba,
125 &GRAPHENE_RECT_INIT ((double)x / PANGO_SCALE,
126 (double)y / PANGO_SCALE,
127 (double)width / PANGO_SCALE,
128 (double)height / PANGO_SCALE));
129 }
130
131 static void
gsk_pango_renderer_draw_trapezoid(PangoRenderer * renderer,PangoRenderPart part,double y1_,double x11,double x21,double y2,double x12,double x22)132 gsk_pango_renderer_draw_trapezoid (PangoRenderer *renderer,
133 PangoRenderPart part,
134 double y1_,
135 double x11,
136 double x21,
137 double y2,
138 double x12,
139 double x22)
140 {
141 GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
142 PangoLayout *layout;
143 PangoRectangle ink_rect;
144 cairo_t *cr;
145 double x, y;
146
147 layout = pango_renderer_get_layout (renderer);
148 if (!layout)
149 return;
150
151 pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
152 cr = gtk_snapshot_append_cairo (crenderer->snapshot,
153 &GRAPHENE_RECT_INIT (ink_rect.x, ink_rect.y,
154 ink_rect.width, ink_rect.height));
155 set_color (crenderer, part, cr);
156
157 x = y = 0;
158 cairo_user_to_device_distance (cr, &x, &y);
159 cairo_identity_matrix (cr);
160 cairo_translate (cr, x, y);
161
162 cairo_move_to (cr, x11, y1_);
163 cairo_line_to (cr, x21, y1_);
164 cairo_line_to (cr, x22, y2);
165 cairo_line_to (cr, x12, y2);
166 cairo_close_path (cr);
167
168 cairo_fill (cr);
169
170 cairo_destroy (cr);
171 }
172
173 static void
gsk_pango_renderer_draw_error_underline(PangoRenderer * renderer,int x,int y,int width,int height)174 gsk_pango_renderer_draw_error_underline (PangoRenderer *renderer,
175 int x,
176 int y,
177 int width,
178 int height)
179 {
180 GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
181 double xx, yy, ww, hh;
182 GdkRGBA rgba;
183 GskRoundedRect dot;
184
185 xx = (double)x / PANGO_SCALE;
186 yy = (double)y / PANGO_SCALE;
187 ww = (double)width / PANGO_SCALE;
188 hh = (double)height / PANGO_SCALE;
189
190 get_color (crenderer, PANGO_RENDER_PART_UNDERLINE, &rgba);
191
192 gtk_snapshot_push_repeat (crenderer->snapshot,
193 &GRAPHENE_RECT_INIT (xx, yy, ww, hh),
194 NULL);
195
196 gsk_rounded_rect_init_from_rect (&dot,
197 &GRAPHENE_RECT_INIT (xx, yy, hh, hh),
198 hh / 2);
199
200 gtk_snapshot_push_rounded_clip (crenderer->snapshot, &dot);
201 gtk_snapshot_append_color (crenderer->snapshot, &rgba, &dot.bounds);
202 gtk_snapshot_pop (crenderer->snapshot);
203 gtk_snapshot_append_color (crenderer->snapshot,
204 &(GdkRGBA) { 0.f, 0.f, 0.f, 0.f },
205 &GRAPHENE_RECT_INIT (xx, yy, 1.5 * hh, hh));
206
207 gtk_snapshot_pop (crenderer->snapshot);
208 }
209
210 static void
gsk_pango_renderer_draw_shape(PangoRenderer * renderer,PangoAttrShape * attr,int x,int y)211 gsk_pango_renderer_draw_shape (PangoRenderer *renderer,
212 PangoAttrShape *attr,
213 int x,
214 int y)
215 {
216 GskPangoRenderer *crenderer = (GskPangoRenderer *) (renderer);
217 PangoLayout *layout;
218 PangoCairoShapeRendererFunc shape_renderer;
219 gpointer shape_renderer_data;
220 double base_x = (double)x / PANGO_SCALE;
221 double base_y = (double)y / PANGO_SCALE;
222 gboolean handled = FALSE;
223
224 if (crenderer->shape_handler)
225 {
226 double shape_x = base_x;
227 double shape_y = (double) (y + attr->logical_rect.y) / PANGO_SCALE;
228
229 if (shape_x != 0 || shape_y != 0)
230 {
231 gtk_snapshot_save (crenderer->snapshot);
232 gtk_snapshot_translate (crenderer->snapshot, &GRAPHENE_POINT_INIT (shape_x, shape_y));
233 }
234
235 handled = crenderer->shape_handler (attr,
236 crenderer->snapshot,
237 (double)attr->logical_rect.width / PANGO_SCALE,
238 (double)attr->logical_rect.height / PANGO_SCALE);
239 if (shape_x != 0 || shape_y != 0)
240 gtk_snapshot_restore (crenderer->snapshot);
241 }
242
243 if (!handled)
244 {
245 cairo_t *cr;
246 PangoRectangle ink_rect;
247
248 layout = pango_renderer_get_layout (renderer);
249 if (!layout)
250 return;
251
252 pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
253 cr = gtk_snapshot_append_cairo (crenderer->snapshot,
254 &GRAPHENE_RECT_INIT (ink_rect.x, ink_rect.y,
255 ink_rect.width, ink_rect.height));
256 shape_renderer = pango_cairo_context_get_shape_renderer (pango_layout_get_context (layout),
257 &shape_renderer_data);
258
259 if (!shape_renderer)
260 {
261 cairo_destroy (cr);
262 return;
263 }
264
265 set_color (crenderer, PANGO_RENDER_PART_FOREGROUND, cr);
266
267 cairo_move_to (cr, base_x, base_y);
268
269 shape_renderer (cr, attr, FALSE, shape_renderer_data);
270
271 cairo_destroy (cr);
272 }
273 }
274
275 static void
text_renderer_set_rgba(GskPangoRenderer * crenderer,PangoRenderPart part,const GdkRGBA * rgba)276 text_renderer_set_rgba (GskPangoRenderer *crenderer,
277 PangoRenderPart part,
278 const GdkRGBA *rgba)
279 {
280 PangoRenderer *renderer = PANGO_RENDERER (crenderer);
281 PangoColor color = { 0, };
282 guint16 alpha;
283
284 if (rgba)
285 {
286 color.red = (guint16)(rgba->red * 65535);
287 color.green = (guint16)(rgba->green * 65535);
288 color.blue = (guint16)(rgba->blue * 65535);
289 alpha = (guint16)(rgba->alpha * 65535);
290 pango_renderer_set_color (renderer, part, &color);
291 pango_renderer_set_alpha (renderer, part, alpha);
292 }
293 else
294 {
295 pango_renderer_set_color (renderer, part, NULL);
296 pango_renderer_set_alpha (renderer, part, 0);
297 }
298 }
299
300 static GtkTextAppearance *
get_item_appearance(PangoItem * item)301 get_item_appearance (PangoItem *item)
302 {
303 GSList *tmp_list = item->analysis.extra_attrs;
304
305 while (tmp_list)
306 {
307 PangoAttribute *attr = tmp_list->data;
308
309 if (attr->klass->type == gtk_text_attr_appearance_type)
310 return &((GtkTextAttrAppearance *)attr)->appearance;
311
312 tmp_list = tmp_list->next;
313 }
314
315 return NULL;
316 }
317
318 static void
gsk_pango_renderer_prepare_run(PangoRenderer * renderer,PangoLayoutRun * run)319 gsk_pango_renderer_prepare_run (PangoRenderer *renderer,
320 PangoLayoutRun *run)
321 {
322 GskPangoRenderer *crenderer = GSK_PANGO_RENDERER (renderer);
323 const GdkRGBA *bg_rgba = NULL;
324 const GdkRGBA *fg_rgba = NULL;
325 GtkTextAppearance *appearance;
326
327 PANGO_RENDERER_CLASS (gsk_pango_renderer_parent_class)->prepare_run (renderer, run);
328
329 appearance = get_item_appearance (run->item);
330
331 if (appearance == NULL)
332 return;
333
334 if (appearance->draw_bg && crenderer->state == GSK_PANGO_RENDERER_NORMAL)
335 bg_rgba = appearance->bg_rgba;
336 else
337 bg_rgba = NULL;
338
339 text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_BACKGROUND, bg_rgba);
340
341 if (crenderer->state == GSK_PANGO_RENDERER_SELECTED &&
342 GTK_IS_TEXT_VIEW (crenderer->widget))
343 {
344 GtkCssNode *node;
345 GtkCssValue *value;
346
347 node = gtk_text_view_get_selection_node ((GtkTextView *)crenderer->widget);
348 value = gtk_css_node_get_style (node)->core->color;
349 fg_rgba = gtk_css_color_value_get_rgba (value);
350 }
351 else if (crenderer->state == GSK_PANGO_RENDERER_CURSOR && gtk_widget_has_focus (crenderer->widget))
352 {
353 GtkCssNode *node;
354 GtkCssValue *value;
355
356 node = gtk_widget_get_css_node (crenderer->widget);
357 value = gtk_css_node_get_style (node)->background->background_color;
358 fg_rgba = gtk_css_color_value_get_rgba (value);
359 }
360 else
361 fg_rgba = appearance->fg_rgba;
362
363 text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_FOREGROUND, fg_rgba);
364
365 if (appearance->strikethrough_rgba)
366 text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_STRIKETHROUGH, appearance->strikethrough_rgba);
367 else
368 text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_STRIKETHROUGH, fg_rgba);
369
370 if (appearance->underline_rgba)
371 text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_UNDERLINE, appearance->underline_rgba);
372 else if (appearance->underline == PANGO_UNDERLINE_ERROR)
373 {
374 if (!crenderer->error_color)
375 {
376 static const GdkRGBA red = { 1, 0, 0, 1 };
377 crenderer->error_color = gdk_rgba_copy (&red);
378 }
379
380 text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_UNDERLINE, crenderer->error_color);
381 }
382 else
383 text_renderer_set_rgba (crenderer, PANGO_RENDER_PART_UNDERLINE, fg_rgba);
384 }
385
386 static void
gsk_pango_renderer_init(GskPangoRenderer * renderer G_GNUC_UNUSED)387 gsk_pango_renderer_init (GskPangoRenderer *renderer G_GNUC_UNUSED)
388 {
389 }
390
391 static void
gsk_pango_renderer_class_init(GskPangoRendererClass * klass)392 gsk_pango_renderer_class_init (GskPangoRendererClass *klass)
393 {
394 PangoRendererClass *renderer_class = PANGO_RENDERER_CLASS (klass);
395
396 renderer_class->draw_glyph_item = gsk_pango_renderer_draw_glyph_item;
397 renderer_class->draw_rectangle = gsk_pango_renderer_draw_rectangle;
398 renderer_class->draw_trapezoid = gsk_pango_renderer_draw_trapezoid;
399 renderer_class->draw_error_underline = gsk_pango_renderer_draw_error_underline;
400 renderer_class->draw_shape = gsk_pango_renderer_draw_shape;
401 renderer_class->prepare_run = gsk_pango_renderer_prepare_run;
402 }
403
404 static GskPangoRenderer *cached_renderer = NULL; /* MT-safe */
405 G_LOCK_DEFINE_STATIC (cached_renderer);
406
407 GskPangoRenderer *
gsk_pango_renderer_acquire(void)408 gsk_pango_renderer_acquire (void)
409 {
410 GskPangoRenderer *renderer;
411
412 if (G_LIKELY (G_TRYLOCK (cached_renderer)))
413 {
414 if (G_UNLIKELY (!cached_renderer))
415 {
416 cached_renderer = g_object_new (GSK_TYPE_PANGO_RENDERER, NULL);
417 cached_renderer->is_cached_renderer = TRUE;
418 }
419
420 renderer = cached_renderer;
421
422 /* Reset to standard state */
423 renderer->state = GSK_PANGO_RENDERER_NORMAL;
424 renderer->shape_handler = NULL;
425 }
426 else
427 {
428 renderer = g_object_new (GSK_TYPE_PANGO_RENDERER, NULL);
429 }
430
431 return renderer;
432 }
433
434 void
gsk_pango_renderer_release(GskPangoRenderer * renderer)435 gsk_pango_renderer_release (GskPangoRenderer *renderer)
436 {
437 if (G_LIKELY (renderer->is_cached_renderer))
438 {
439 renderer->widget = NULL;
440 renderer->snapshot = NULL;
441
442 if (renderer->error_color)
443 {
444 gdk_rgba_free (renderer->error_color);
445 renderer->error_color = NULL;
446 }
447
448 G_UNLOCK (cached_renderer);
449 }
450 else
451 g_object_unref (renderer);
452 }
453
454 /**
455 * gtk_snapshot_append_layout:
456 * @snapshot: a `GtkSnapshot`
457 * @layout: the `PangoLayout` to render
458 * @color: the foreground color to render the layout in
459 *
460 * Creates render nodes for rendering @layout in the given foregound @color
461 * and appends them to the current node of @snapshot without changing the
462 * current node.
463 **/
464 void
gtk_snapshot_append_layout(GtkSnapshot * snapshot,PangoLayout * layout,const GdkRGBA * color)465 gtk_snapshot_append_layout (GtkSnapshot *snapshot,
466 PangoLayout *layout,
467 const GdkRGBA *color)
468 {
469 GskPangoRenderer *crenderer;
470
471 g_return_if_fail (snapshot != NULL);
472 g_return_if_fail (PANGO_IS_LAYOUT (layout));
473
474 crenderer = gsk_pango_renderer_acquire ();
475
476 crenderer->snapshot = snapshot;
477 crenderer->fg_color = color;
478
479 pango_renderer_draw_layout (PANGO_RENDERER (crenderer), layout, 0, 0);
480
481 gsk_pango_renderer_release (crenderer);
482 }
483