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