1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2011 Red Hat, Inc.
3  *
4  * Author: Cosimo Cecchi <cosimoc@gnome.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "config.h"
21 
22 #include "gtkcssshadowvalueprivate.h"
23 
24 #include "gtkcairoblurprivate.h"
25 #include "gtkcsscolorvalueprivate.h"
26 #include "gtkcssnumbervalueprivate.h"
27 #include "gtkcssrgbavalueprivate.h"
28 #include "gtkstylecontextprivate.h"
29 #include "gtkrenderprivate.h"
30 #include "gtkpango.h"
31 
32 #include "fallback-c89.c"
33 #include <float.h>
34 
35 #define CORNER_MASK_CACHE_MAX_SIZE 2000U
36 
37 struct _GtkCssValue {
38   GTK_CSS_VALUE_BASE
39   guint inset :1;
40 
41   GtkCssValue *hoffset;
42   GtkCssValue *voffset;
43   GtkCssValue *radius;
44   GtkCssValue *spread;
45 
46   GtkCssValue *color;
47 };
48 
49 static GtkCssValue *    gtk_css_shadow_value_new (GtkCssValue *hoffset,
50                                                   GtkCssValue *voffset,
51                                                   GtkCssValue *radius,
52                                                   GtkCssValue *spread,
53                                                   gboolean     inset,
54                                                   GtkCssValue *color);
55 
56 static void
gtk_css_value_shadow_free(GtkCssValue * shadow)57 gtk_css_value_shadow_free (GtkCssValue *shadow)
58 {
59   _gtk_css_value_unref (shadow->hoffset);
60   _gtk_css_value_unref (shadow->voffset);
61   _gtk_css_value_unref (shadow->radius);
62   _gtk_css_value_unref (shadow->spread);
63   _gtk_css_value_unref (shadow->color);
64 
65   g_slice_free (GtkCssValue, shadow);
66 }
67 
68 static GtkCssValue *
gtk_css_value_shadow_compute(GtkCssValue * shadow,guint property_id,GtkStyleProviderPrivate * provider,GtkCssStyle * style,GtkCssStyle * parent_style)69 gtk_css_value_shadow_compute (GtkCssValue             *shadow,
70                               guint                    property_id,
71                               GtkStyleProviderPrivate *provider,
72                               GtkCssStyle             *style,
73                               GtkCssStyle             *parent_style)
74 {
75   GtkCssValue *hoffset, *voffset, *radius, *spread, *color;
76 
77   hoffset = _gtk_css_value_compute (shadow->hoffset, property_id, provider, style, parent_style);
78   voffset = _gtk_css_value_compute (shadow->voffset, property_id, provider, style, parent_style);
79   radius = _gtk_css_value_compute (shadow->radius, property_id, provider, style, parent_style);
80   spread = _gtk_css_value_compute (shadow->spread, property_id, provider, style, parent_style),
81   color = _gtk_css_value_compute (shadow->color, property_id, provider, style, parent_style);
82 
83   if (hoffset == shadow->hoffset &&
84       voffset == shadow->voffset &&
85       radius == shadow->radius &&
86       spread == shadow->spread &&
87       color == shadow->color)
88     {
89       _gtk_css_value_unref (hoffset);
90       _gtk_css_value_unref (voffset);
91       _gtk_css_value_unref (radius);
92       _gtk_css_value_unref (spread);
93       _gtk_css_value_unref (color);
94 
95       return _gtk_css_value_ref (shadow);
96     }
97 
98   return gtk_css_shadow_value_new (hoffset, voffset, radius, spread, shadow->inset, color);
99 }
100 
101 static gboolean
gtk_css_value_shadow_equal(const GtkCssValue * shadow1,const GtkCssValue * shadow2)102 gtk_css_value_shadow_equal (const GtkCssValue *shadow1,
103                             const GtkCssValue *shadow2)
104 {
105   return shadow1->inset == shadow2->inset
106       && _gtk_css_value_equal (shadow1->hoffset, shadow2->hoffset)
107       && _gtk_css_value_equal (shadow1->voffset, shadow2->voffset)
108       && _gtk_css_value_equal (shadow1->radius, shadow2->radius)
109       && _gtk_css_value_equal (shadow1->spread, shadow2->spread)
110       && _gtk_css_value_equal (shadow1->color, shadow2->color);
111 }
112 
113 static GtkCssValue *
gtk_css_value_shadow_transition(GtkCssValue * start,GtkCssValue * end,guint property_id,double progress)114 gtk_css_value_shadow_transition (GtkCssValue *start,
115                                  GtkCssValue *end,
116                                  guint        property_id,
117                                  double       progress)
118 {
119   if (start->inset != end->inset)
120     return NULL;
121 
122   return gtk_css_shadow_value_new (_gtk_css_value_transition (start->hoffset, end->hoffset, property_id, progress),
123                                    _gtk_css_value_transition (start->voffset, end->voffset, property_id, progress),
124                                    _gtk_css_value_transition (start->radius, end->radius, property_id, progress),
125                                    _gtk_css_value_transition (start->spread, end->spread, property_id, progress),
126                                    start->inset,
127                                    _gtk_css_value_transition (start->color, end->color, property_id, progress));
128 }
129 
130 static void
gtk_css_value_shadow_print(const GtkCssValue * shadow,GString * string)131 gtk_css_value_shadow_print (const GtkCssValue *shadow,
132                             GString           *string)
133 {
134   _gtk_css_value_print (shadow->hoffset, string);
135   g_string_append_c (string, ' ');
136   _gtk_css_value_print (shadow->voffset, string);
137   g_string_append_c (string, ' ');
138   if (_gtk_css_number_value_get (shadow->radius, 100) != 0 ||
139       _gtk_css_number_value_get (shadow->spread, 100) != 0)
140     {
141       _gtk_css_value_print (shadow->radius, string);
142       g_string_append_c (string, ' ');
143     }
144 
145   if (_gtk_css_number_value_get (shadow->spread, 100) != 0)
146     {
147       _gtk_css_value_print (shadow->spread, string);
148       g_string_append_c (string, ' ');
149     }
150 
151   _gtk_css_value_print (shadow->color, string);
152 
153   if (shadow->inset)
154     g_string_append (string, " inset");
155 
156 }
157 
158 static const GtkCssValueClass GTK_CSS_VALUE_SHADOW = {
159   gtk_css_value_shadow_free,
160   gtk_css_value_shadow_compute,
161   gtk_css_value_shadow_equal,
162   gtk_css_value_shadow_transition,
163   gtk_css_value_shadow_print
164 };
165 
166 static GtkCssValue *
gtk_css_shadow_value_new(GtkCssValue * hoffset,GtkCssValue * voffset,GtkCssValue * radius,GtkCssValue * spread,gboolean inset,GtkCssValue * color)167 gtk_css_shadow_value_new (GtkCssValue *hoffset,
168                           GtkCssValue *voffset,
169                           GtkCssValue *radius,
170                           GtkCssValue *spread,
171                           gboolean     inset,
172                           GtkCssValue *color)
173 {
174   GtkCssValue *retval;
175 
176   retval = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_SHADOW);
177 
178   retval->hoffset = hoffset;
179   retval->voffset = voffset;
180   retval->radius = radius;
181   retval->spread = spread;
182   retval->inset = inset;
183   retval->color = color;
184 
185   return retval;
186 }
187 
188 GtkCssValue *
_gtk_css_shadow_value_new_for_transition(GtkCssValue * target)189 _gtk_css_shadow_value_new_for_transition (GtkCssValue *target)
190 {
191   GdkRGBA transparent = { 0, 0, 0, 0 };
192 
193   g_return_val_if_fail (target->class == &GTK_CSS_VALUE_SHADOW, NULL);
194 
195   return gtk_css_shadow_value_new (_gtk_css_number_value_new (0, GTK_CSS_PX),
196                                    _gtk_css_number_value_new (0, GTK_CSS_PX),
197                                    _gtk_css_number_value_new (0, GTK_CSS_PX),
198                                    _gtk_css_number_value_new (0, GTK_CSS_PX),
199                                    target->inset,
200                                    _gtk_css_rgba_value_new_from_rgba (&transparent));
201 }
202 
203 static gboolean
value_is_done_parsing(GtkCssParser * parser)204 value_is_done_parsing (GtkCssParser *parser)
205 {
206   return _gtk_css_parser_is_eof (parser) ||
207          _gtk_css_parser_begins_with (parser, ',') ||
208          _gtk_css_parser_begins_with (parser, ';') ||
209          _gtk_css_parser_begins_with (parser, '}');
210 }
211 
212 GtkCssValue *
_gtk_css_shadow_value_parse(GtkCssParser * parser,gboolean box_shadow_mode)213 _gtk_css_shadow_value_parse (GtkCssParser *parser,
214                              gboolean      box_shadow_mode)
215 {
216   enum {
217     HOFFSET,
218     VOFFSET,
219     RADIUS,
220     SPREAD,
221     COLOR,
222     N_VALUES
223   };
224   GtkCssValue *values[N_VALUES] = { NULL, };
225   gboolean inset;
226   guint i;
227 
228   if (box_shadow_mode)
229     inset = _gtk_css_parser_try (parser, "inset", TRUE);
230   else
231     inset = FALSE;
232 
233   do
234   {
235     if (values[HOFFSET] == NULL &&
236         gtk_css_number_value_can_parse (parser))
237       {
238         values[HOFFSET] = _gtk_css_number_value_parse (parser,
239                                                        GTK_CSS_PARSE_LENGTH
240                                                        | GTK_CSS_NUMBER_AS_PIXELS);
241         if (values[HOFFSET] == NULL)
242           goto fail;
243 
244         values[VOFFSET] = _gtk_css_number_value_parse (parser,
245                                                        GTK_CSS_PARSE_LENGTH
246                                                        | GTK_CSS_NUMBER_AS_PIXELS);
247         if (values[VOFFSET] == NULL)
248           goto fail;
249 
250         if (gtk_css_number_value_can_parse (parser))
251           {
252             values[RADIUS] = _gtk_css_number_value_parse (parser,
253                                                           GTK_CSS_PARSE_LENGTH
254                                                           | GTK_CSS_POSITIVE_ONLY
255                                                           | GTK_CSS_NUMBER_AS_PIXELS);
256             if (values[RADIUS] == NULL)
257               goto fail;
258           }
259         else
260           values[RADIUS] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
261 
262         if (box_shadow_mode && gtk_css_number_value_can_parse (parser))
263           {
264             values[SPREAD] = _gtk_css_number_value_parse (parser,
265                                                           GTK_CSS_PARSE_LENGTH
266                                                           | GTK_CSS_NUMBER_AS_PIXELS);
267             if (values[SPREAD] == NULL)
268               goto fail;
269           }
270         else
271           values[SPREAD] = _gtk_css_number_value_new (0.0, GTK_CSS_PX);
272       }
273     else if (!inset && box_shadow_mode && _gtk_css_parser_try (parser, "inset", TRUE))
274       {
275         if (values[HOFFSET] == NULL)
276           goto fail;
277         inset = TRUE;
278         break;
279       }
280     else if (values[COLOR] == NULL)
281       {
282         values[COLOR] = _gtk_css_color_value_parse (parser);
283 
284         if (values[COLOR] == NULL)
285           goto fail;
286       }
287     else
288       {
289         /* We parsed everything and there's still stuff left?
290          * Pretend we didn't notice and let the normal code produce
291          * a 'junk at end of value' error */
292         goto fail;
293       }
294   }
295   while (values[HOFFSET] == NULL || !value_is_done_parsing (parser));
296 
297   if (values[COLOR] == NULL)
298     values[COLOR] = _gtk_css_color_value_new_current_color ();
299 
300   return gtk_css_shadow_value_new (values[HOFFSET], values[VOFFSET],
301                                    values[RADIUS], values[SPREAD],
302                                    inset, values[COLOR]);
303 
304 fail:
305   for (i = 0; i < N_VALUES; i++)
306     {
307       if (values[i])
308         _gtk_css_value_unref (values[i]);
309     }
310 
311   return NULL;
312 }
313 
314 static gboolean
needs_blur(const GtkCssValue * shadow)315 needs_blur (const GtkCssValue *shadow)
316 {
317   double radius = _gtk_css_number_value_get (shadow->radius, 0);
318 
319   /* The code doesn't actually do any blurring for radius 1, as it
320    * ends up with box filter size 1 */
321   if (radius <= 1.0)
322     return FALSE;
323 
324   return TRUE;
325 }
326 
327 static const cairo_user_data_key_t original_cr_key;
328 
329 static cairo_t *
gtk_css_shadow_value_start_drawing(const GtkCssValue * shadow,cairo_t * cr,GtkBlurFlags blur_flags)330 gtk_css_shadow_value_start_drawing (const GtkCssValue *shadow,
331                                     cairo_t           *cr,
332                                     GtkBlurFlags       blur_flags)
333 {
334   cairo_rectangle_int_t clip_rect;
335   cairo_surface_t *surface;
336   cairo_t *blur_cr;
337   gdouble radius, clip_radius;
338   gdouble x_scale, y_scale;
339   gboolean blur_x = (blur_flags & GTK_BLUR_X) != 0;
340   gboolean blur_y = (blur_flags & GTK_BLUR_Y) != 0;
341 
342   if (!needs_blur (shadow))
343     return cr;
344 
345   gdk_cairo_get_clip_rectangle (cr, &clip_rect);
346 
347   radius = _gtk_css_number_value_get (shadow->radius, 0);
348   clip_radius = _gtk_cairo_blur_compute_pixels (radius);
349 
350   x_scale = y_scale = 1;
351   cairo_surface_get_device_scale (cairo_get_target (cr), &x_scale, &y_scale);
352 
353   if (blur_flags & GTK_BLUR_REPEAT)
354     {
355       if (!blur_x)
356         clip_rect.width = 1;
357       if (!blur_y)
358         clip_rect.height = 1;
359     }
360 
361   /* Create a larger surface to center the blur. */
362   surface = cairo_surface_create_similar_image (cairo_get_target (cr),
363                                                 CAIRO_FORMAT_A8,
364                                                 x_scale * (clip_rect.width + (blur_x ? 2 * clip_radius : 0)),
365                                                 y_scale * (clip_rect.height + (blur_y ? 2 * clip_radius : 0)));
366   cairo_surface_set_device_scale (surface, x_scale, y_scale);
367   cairo_surface_set_device_offset (surface,
368                                     x_scale * ((blur_x ? clip_radius: 0) - clip_rect.x),
369                                     y_scale * ((blur_y ? clip_radius: 0) - clip_rect.y));
370 
371   blur_cr = cairo_create (surface);
372   cairo_set_user_data (blur_cr, &original_cr_key, cairo_reference (cr), (cairo_destroy_func_t) cairo_destroy);
373 
374   if (cairo_has_current_point (cr))
375     {
376       double x, y;
377 
378       cairo_get_current_point (cr, &x, &y);
379       cairo_move_to (blur_cr, x, y);
380     }
381 
382   return blur_cr;
383 }
384 
385 void
mask_surface_repeat(cairo_t * cr,cairo_surface_t * surface)386 mask_surface_repeat (cairo_t         *cr,
387                      cairo_surface_t *surface)
388 {
389     cairo_pattern_t *pattern;
390 
391     pattern = cairo_pattern_create_for_surface (surface);
392     cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
393 
394     cairo_mask (cr, pattern);
395 
396     cairo_pattern_destroy (pattern);
397 }
398 
399 static cairo_t *
gtk_css_shadow_value_finish_drawing(const GtkCssValue * shadow,cairo_t * cr,GtkBlurFlags blur_flags)400 gtk_css_shadow_value_finish_drawing (const GtkCssValue *shadow,
401                                      cairo_t           *cr,
402                                      GtkBlurFlags       blur_flags)
403 {
404   gdouble radius;
405   cairo_t *original_cr;
406   cairo_surface_t *surface;
407   gdouble x_scale;
408 
409   if (!needs_blur (shadow))
410     return cr;
411 
412   original_cr = cairo_get_user_data (cr, &original_cr_key);
413 
414   /* Blur the surface. */
415   surface = cairo_get_target (cr);
416   radius = _gtk_css_number_value_get (shadow->radius, 0);
417 
418   x_scale = 1;
419   cairo_surface_get_device_scale (cairo_get_target (cr), &x_scale, NULL);
420 
421   _gtk_cairo_blur_surface (surface, x_scale * radius, blur_flags);
422 
423   gdk_cairo_set_source_rgba (original_cr, _gtk_css_rgba_value_get_rgba (shadow->color));
424   if (blur_flags & GTK_BLUR_REPEAT)
425     mask_surface_repeat (original_cr, surface);
426   else
427     cairo_mask_surface (original_cr, surface, 0, 0);
428 
429   cairo_destroy (cr);
430 
431   cairo_surface_destroy (surface);
432 
433   return original_cr;
434 }
435 
436 static const cairo_user_data_key_t radius_key;
437 static const cairo_user_data_key_t layout_serial_key;
438 
G_DEFINE_QUARK(GtkCssShadowValue pango_cached_blurred_surface,pango_cached_blurred_surface)439 G_DEFINE_QUARK (GtkCssShadowValue pango_cached_blurred_surface, pango_cached_blurred_surface)
440 
441 static cairo_surface_t *
442 get_cached_pango_surface (PangoLayout       *layout,
443                           const GtkCssValue *shadow)
444 {
445   cairo_surface_t *cached_surface = g_object_get_qdata (G_OBJECT (layout), pango_cached_blurred_surface_quark ());
446   guint cached_radius, cached_serial;
447   guint radius, serial;
448 
449   if (!cached_surface)
450     return NULL;
451 
452   radius = _gtk_css_number_value_get (shadow->radius, 0);
453   cached_radius = GPOINTER_TO_UINT (cairo_surface_get_user_data (cached_surface, &radius_key));
454   if (radius != cached_radius)
455     return NULL;
456 
457   serial = pango_layout_get_serial (layout);
458   cached_serial = GPOINTER_TO_UINT (cairo_surface_get_user_data (cached_surface, &layout_serial_key));
459   if (serial != cached_serial)
460     return NULL;
461 
462   return cached_surface;
463 }
464 
465 static cairo_surface_t *
make_blurred_pango_surface(cairo_t * existing_cr,PangoLayout * layout,const GtkCssValue * shadow)466 make_blurred_pango_surface (cairo_t           *existing_cr,
467                             PangoLayout       *layout,
468                             const GtkCssValue *shadow)
469 {
470   cairo_surface_t *surface;
471   cairo_t *cr;
472   gdouble radius, clip_radius;
473   gdouble x_scale, y_scale;
474   PangoRectangle ink_rect;
475 
476   radius = _gtk_css_number_value_get (shadow->radius, 0);
477 
478   pango_layout_get_pixel_extents (layout, &ink_rect, NULL);
479   clip_radius = _gtk_cairo_blur_compute_pixels (radius);
480   x_scale = y_scale = 1;
481   cairo_surface_get_device_scale (cairo_get_target (existing_cr), &x_scale, &y_scale);
482 
483   surface = cairo_surface_create_similar_image (cairo_get_target (existing_cr),
484                                                 CAIRO_FORMAT_A8,
485                                                 x_scale * (ink_rect.width + 2 * clip_radius),
486                                                 y_scale * (ink_rect.height + 2 * clip_radius));
487   cairo_surface_set_device_scale (surface, x_scale, y_scale);
488   cairo_surface_set_device_offset (surface,
489                                    x_scale * (-ink_rect.x + clip_radius),
490                                    y_scale * (-ink_rect.y + clip_radius));
491   cr = cairo_create (surface);
492   cairo_move_to (cr, 0, 0);
493   _gtk_pango_fill_layout (cr, layout);
494   _gtk_cairo_blur_surface (surface, radius * x_scale, GTK_BLUR_X | GTK_BLUR_Y);
495 
496   cairo_destroy (cr);
497 
498   return surface;
499 }
500 
501 static cairo_surface_t *
get_blurred_pango_surface(cairo_t * cr,PangoLayout * layout,const GtkCssValue * shadow)502 get_blurred_pango_surface (cairo_t           *cr,
503                            PangoLayout       *layout,
504                            const GtkCssValue *shadow)
505 {
506   cairo_surface_t *surface;
507   guint radius, serial;
508 
509   surface = get_cached_pango_surface (layout, shadow);
510   if (!surface)
511     {
512       surface = make_blurred_pango_surface (cr, layout, shadow);
513 
514       /* Cache the surface on the PangoLayout */
515       radius = _gtk_css_number_value_get (shadow->radius, 0);
516       cairo_surface_set_user_data (surface, &radius_key, GUINT_TO_POINTER (radius), NULL);
517 
518       serial = pango_layout_get_serial (layout);
519       cairo_surface_set_user_data (surface, &layout_serial_key, GUINT_TO_POINTER (serial), NULL);
520 
521       g_object_set_qdata_full (G_OBJECT (layout), pango_cached_blurred_surface_quark (),
522                                surface, (GDestroyNotify) cairo_surface_destroy);
523     }
524 
525   return surface;
526 }
527 
528 void
_gtk_css_shadow_value_paint_layout(const GtkCssValue * shadow,cairo_t * cr,PangoLayout * layout)529 _gtk_css_shadow_value_paint_layout (const GtkCssValue *shadow,
530                                     cairo_t           *cr,
531                                     PangoLayout       *layout)
532 {
533   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
534 
535   /* We don't need to draw invisible shadows */
536   if (gtk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
537     return;
538 
539   if (!cairo_has_current_point (cr))
540     cairo_move_to (cr, 0, 0);
541 
542   cairo_save (cr);
543 
544   if (needs_blur (shadow))
545     {
546       cairo_surface_t *blurred_surface = get_blurred_pango_surface (cr, layout, shadow);
547       double x, y;
548       cairo_get_current_point (cr, &x, &y);
549       cairo_translate (cr, x, y);
550       cairo_translate (cr,
551                        _gtk_css_number_value_get (shadow->hoffset, 0),
552                        _gtk_css_number_value_get (shadow->voffset, 0));
553 
554       gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
555       cairo_mask_surface (cr, blurred_surface, 0, 0);
556     }
557   else
558     {
559       /* The no blur case -- just paint directly. */
560       cairo_rel_move_to (cr,
561                          _gtk_css_number_value_get (shadow->hoffset, 0),
562                          _gtk_css_number_value_get (shadow->voffset, 0));
563       gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
564       _gtk_pango_fill_layout (cr, layout);
565       cairo_rel_move_to (cr,
566                          - _gtk_css_number_value_get (shadow->hoffset, 0),
567                          - _gtk_css_number_value_get (shadow->voffset, 0));
568     }
569 
570   cairo_restore (cr);
571 }
572 
573 void
_gtk_css_shadow_value_paint_icon(const GtkCssValue * shadow,cairo_t * cr)574 _gtk_css_shadow_value_paint_icon (const GtkCssValue *shadow,
575 			          cairo_t           *cr)
576 {
577   cairo_pattern_t *pattern;
578 
579   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
580 
581   /* We don't need to draw invisible shadows */
582   if (gtk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
583     return;
584 
585   cairo_save (cr);
586   pattern = cairo_pattern_reference (cairo_get_source (cr));
587 
588   gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
589   cr = gtk_css_shadow_value_start_drawing (shadow, cr, GTK_BLUR_X | GTK_BLUR_Y);
590 
591   cairo_translate (cr,
592                    _gtk_css_number_value_get (shadow->hoffset, 0),
593                    _gtk_css_number_value_get (shadow->voffset, 0));
594   cairo_mask (cr, pattern);
595 
596   cr = gtk_css_shadow_value_finish_drawing (shadow, cr, GTK_BLUR_X | GTK_BLUR_Y);
597 
598   cairo_restore (cr);
599   cairo_pattern_destroy (pattern);
600 }
601 
602 gboolean
_gtk_css_shadow_value_get_inset(const GtkCssValue * shadow)603 _gtk_css_shadow_value_get_inset (const GtkCssValue *shadow)
604 {
605   g_return_val_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW, FALSE);
606 
607   return shadow->inset;
608 }
609 
610 void
_gtk_css_shadow_value_get_geometry(const GtkCssValue * shadow,gdouble * hoffset,gdouble * voffset,gdouble * radius,gdouble * spread)611 _gtk_css_shadow_value_get_geometry (const GtkCssValue *shadow,
612                                     gdouble           *hoffset,
613                                     gdouble           *voffset,
614                                     gdouble           *radius,
615                                     gdouble           *spread)
616 {
617   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
618 
619   if (hoffset != NULL)
620     *hoffset = _gtk_css_number_value_get (shadow->hoffset, 0);
621   if (voffset != NULL)
622     *voffset = _gtk_css_number_value_get (shadow->voffset, 0);
623 
624   if (radius != NULL)
625     *radius = _gtk_css_number_value_get (shadow->radius, 0);
626   if (spread != NULL)
627     *spread = _gtk_css_number_value_get (shadow->spread, 0);
628 }
629 
630 static gboolean
has_empty_clip(cairo_t * cr)631 has_empty_clip (cairo_t *cr)
632 {
633   double x1, y1, x2, y2;
634 
635   cairo_clip_extents (cr, &x1, &y1, &x2, &y2);
636   return x1 == x2 && y1 == y2;
637 }
638 
639 static void
draw_shadow(const GtkCssValue * shadow,cairo_t * cr,GtkRoundedBox * box,GtkRoundedBox * clip_box,GtkBlurFlags blur_flags)640 draw_shadow (const GtkCssValue   *shadow,
641 	     cairo_t             *cr,
642 	     GtkRoundedBox       *box,
643 	     GtkRoundedBox       *clip_box,
644 	     GtkBlurFlags         blur_flags)
645 {
646   cairo_t *shadow_cr;
647   gboolean do_blur;
648 
649   if (has_empty_clip (cr))
650     return;
651 
652   gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
653   do_blur = (blur_flags & (GTK_BLUR_X | GTK_BLUR_Y)) != 0;
654   if (do_blur)
655     shadow_cr = gtk_css_shadow_value_start_drawing (shadow, cr, blur_flags);
656   else
657     shadow_cr = cr;
658 
659   cairo_set_fill_rule (shadow_cr, CAIRO_FILL_RULE_EVEN_ODD);
660   _gtk_rounded_box_path (box, shadow_cr);
661   if (shadow->inset)
662     _gtk_rounded_box_clip_path (clip_box, shadow_cr);
663 
664   cairo_fill (shadow_cr);
665 
666   if (do_blur)
667     gtk_css_shadow_value_finish_drawing (shadow, shadow_cr, blur_flags);
668 }
669 
670 typedef struct {
671   gint radius;
672   /* rounded box corner */
673   gint corner_horizontal;
674   gint corner_vertical;
675 } CornerMask;
676 
677 static guint
corner_mask_hash(CornerMask * mask)678 corner_mask_hash (CornerMask *mask)
679 {
680   return ((guint)mask->radius) << 24 ^
681     ((guint)mask->corner_horizontal) << 12 ^
682     ((guint)mask->corner_vertical) << 0;
683 }
684 
685 static gboolean
corner_mask_equal(CornerMask * mask1,CornerMask * mask2)686 corner_mask_equal (CornerMask *mask1,
687                    CornerMask *mask2)
688 {
689   return
690     mask1->radius == mask2->radius &&
691     mask1->corner_horizontal == mask2->corner_horizontal &&
692     mask1->corner_vertical == mask2->corner_vertical;
693 }
694 
695 static gint
truncate_to_int(double val)696 truncate_to_int (double val)
697 {
698   if (isnan (val))
699     return 0;
700   if (val >= G_MAXINT)
701     return G_MAXINT;
702   if (val <= G_MININT)
703     return G_MININT;
704   return (gint) val;
705 }
706 
707 static inline gint
round_to_int(double val)708 round_to_int (double val)
709 {
710   return truncate_to_int (val + (val > 0 ? 0.5 : -0.5));
711 }
712 
713 static inline gint
quantize_to_int(double val)714 quantize_to_int (double val)
715 {
716   const double precision_factor = 10.0;
717   return round_to_int (val * precision_factor);
718 }
719 
720 static void
draw_shadow_corner(const GtkCssValue * shadow,cairo_t * cr,GtkRoundedBox * box,GtkRoundedBox * clip_box,GtkCssCorner corner,cairo_rectangle_int_t * drawn_rect)721 draw_shadow_corner (const GtkCssValue   *shadow,
722                     cairo_t             *cr,
723                     GtkRoundedBox       *box,
724                     GtkRoundedBox       *clip_box,
725                     GtkCssCorner         corner,
726                     cairo_rectangle_int_t *drawn_rect)
727 {
728   gdouble radius, clip_radius;
729   int x1, x2, x3, y1, y2, y3, x, y;
730   GtkRoundedBox corner_box;
731   cairo_t *mask_cr;
732   cairo_surface_t *mask;
733   cairo_pattern_t *pattern;
734   cairo_matrix_t matrix;
735   double sx, sy;
736   static GHashTable *corner_mask_cache = NULL;
737   double max_other;
738   CornerMask key;
739   gboolean overlapped;
740 
741   radius = _gtk_css_number_value_get (shadow->radius, 0);
742   clip_radius = _gtk_cairo_blur_compute_pixels (radius);
743 
744   overlapped = FALSE;
745   if (corner == GTK_CSS_TOP_LEFT || corner == GTK_CSS_BOTTOM_LEFT)
746     {
747       x1 = floor (box->box.x - clip_radius);
748       x2 = ceil (box->box.x + box->corner[corner].horizontal + clip_radius);
749       x = x1;
750       sx = 1;
751       max_other = MAX(box->corner[GTK_CSS_TOP_RIGHT].horizontal, box->corner[GTK_CSS_BOTTOM_RIGHT].horizontal);
752       x3 = floor (box->box.x + box->box.width - max_other - clip_radius);
753       if (x2 > x3)
754         overlapped = TRUE;
755     }
756   else
757     {
758       x1 = floor (box->box.x + box->box.width - box->corner[corner].horizontal - clip_radius);
759       x2 = ceil (box->box.x + box->box.width + clip_radius);
760       x = x2;
761       sx = -1;
762       max_other = MAX(box->corner[GTK_CSS_TOP_LEFT].horizontal, box->corner[GTK_CSS_BOTTOM_LEFT].horizontal);
763       x3 = ceil (box->box.x + max_other + clip_radius);
764       if (x3 > x1)
765         overlapped = TRUE;
766     }
767 
768   if (corner == GTK_CSS_TOP_LEFT || corner == GTK_CSS_TOP_RIGHT)
769     {
770       y1 = floor (box->box.y - clip_radius);
771       y2 = ceil (box->box.y + box->corner[corner].vertical + clip_radius);
772       y = y1;
773       sy = 1;
774       max_other = MAX(box->corner[GTK_CSS_BOTTOM_LEFT].vertical, box->corner[GTK_CSS_BOTTOM_RIGHT].vertical);
775       y3 = floor (box->box.y + box->box.height - max_other - clip_radius);
776       if (y2 > y3)
777         overlapped = TRUE;
778     }
779   else
780     {
781       y1 = floor (box->box.y + box->box.height - box->corner[corner].vertical - clip_radius);
782       y2 = ceil (box->box.y + box->box.height + clip_radius);
783       y = y2;
784       sy = -1;
785       max_other = MAX(box->corner[GTK_CSS_TOP_LEFT].vertical, box->corner[GTK_CSS_TOP_RIGHT].vertical);
786       y3 = ceil (box->box.y + max_other + clip_radius);
787       if (y3 > y1)
788         overlapped = TRUE;
789     }
790 
791   drawn_rect->x = x1;
792   drawn_rect->y = y1;
793   drawn_rect->width = x2 - x1;
794   drawn_rect->height = y2 - y1;
795 
796   cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
797   cairo_clip (cr);
798 
799   if (shadow->inset || overlapped)
800     {
801       /* Fall back to generic path if inset or if the corner radius
802          runs into each other */
803       draw_shadow (shadow, cr, box, clip_box, GTK_BLUR_X | GTK_BLUR_Y);
804       return;
805     }
806 
807   if (has_empty_clip (cr))
808     return;
809 
810   /* At this point we're drawing a blurred outset corner. The only
811    * things that affect the output of the blurred mask in this case
812    * is:
813    *
814    * What corner this is, which defines the orientation (sx,sy)
815    * and position (x,y)
816    *
817    * The blur radius (which also defines the clip_radius)
818    *
819    * The horizontal and vertical corner radius
820    *
821    * We apply the first position and orientation when drawing the
822    * mask, so we cache rendered masks based on the blur radius and the
823    * corner radius.
824    */
825   if (corner_mask_cache == NULL)
826     corner_mask_cache = g_hash_table_new_full ((GHashFunc)corner_mask_hash,
827                                                (GEqualFunc)corner_mask_equal,
828                                                g_free, (GDestroyNotify)cairo_surface_destroy);
829 
830   key.radius = quantize_to_int (radius);
831   key.corner_horizontal = quantize_to_int (box->corner[corner].horizontal);
832   key.corner_vertical = quantize_to_int (box->corner[corner].vertical);
833 
834   mask = g_hash_table_lookup (corner_mask_cache, &key);
835   if (mask == NULL)
836     {
837       mask = cairo_surface_create_similar_image (cairo_get_target (cr), CAIRO_FORMAT_A8,
838                                                  drawn_rect->width + clip_radius,
839                                                  drawn_rect->height + clip_radius);
840       mask_cr = cairo_create (mask);
841       _gtk_rounded_box_init_rect (&corner_box, clip_radius, clip_radius, 2*drawn_rect->width, 2*drawn_rect->height);
842       corner_box.corner[0] = box->corner[corner];
843       _gtk_rounded_box_path (&corner_box, mask_cr);
844       cairo_fill (mask_cr);
845       _gtk_cairo_blur_surface (mask, radius, GTK_BLUR_X | GTK_BLUR_Y);
846       cairo_destroy (mask_cr);
847 
848       if (g_hash_table_size (corner_mask_cache) >= CORNER_MASK_CACHE_MAX_SIZE)
849         {
850           GHashTableIter iter;
851           guint i = 0;
852 
853           g_hash_table_iter_init (&iter, corner_mask_cache);
854           while (g_hash_table_iter_next (&iter, NULL, NULL))
855             if (i++ % 4 == 0)
856               g_hash_table_iter_remove (&iter);
857         }
858       g_hash_table_insert (corner_mask_cache, g_memdup (&key, sizeof (key)), mask);
859     }
860 
861   gdk_cairo_set_source_rgba (cr, _gtk_css_rgba_value_get_rgba (shadow->color));
862   pattern = cairo_pattern_create_for_surface (mask);
863   cairo_matrix_init_identity (&matrix);
864   cairo_matrix_scale (&matrix, sx, sy);
865   cairo_matrix_translate (&matrix, -x, -y);
866   cairo_pattern_set_matrix (pattern, &matrix);
867   cairo_mask (cr, pattern);
868   cairo_pattern_destroy (pattern);
869 }
870 
871 static void
draw_shadow_side(const GtkCssValue * shadow,cairo_t * cr,GtkRoundedBox * box,GtkRoundedBox * clip_box,GtkCssSide side,cairo_rectangle_int_t * drawn_rect)872 draw_shadow_side (const GtkCssValue   *shadow,
873                   cairo_t             *cr,
874                   GtkRoundedBox       *box,
875                   GtkRoundedBox       *clip_box,
876                   GtkCssSide           side,
877                   cairo_rectangle_int_t *drawn_rect)
878 {
879   GtkBlurFlags blur_flags = GTK_BLUR_REPEAT;
880   gdouble radius, clip_radius;
881   int x1, x2, y1, y2;
882 
883   radius = _gtk_css_number_value_get (shadow->radius, 0);
884   clip_radius = _gtk_cairo_blur_compute_pixels (radius);
885 
886   if (side == GTK_CSS_TOP || side == GTK_CSS_BOTTOM)
887     {
888       blur_flags |= GTK_BLUR_Y;
889       x1 = floor (box->box.x - clip_radius);
890       x2 = ceil (box->box.x + box->box.width + clip_radius);
891     }
892   else if (side == GTK_CSS_LEFT)
893     {
894       x1 = floor (box->box.x -clip_radius);
895       x2 = ceil (box->box.x + clip_radius);
896     }
897   else
898     {
899       x1 = floor (box->box.x + box->box.width -clip_radius);
900       x2 = ceil (box->box.x + box->box.width + clip_radius);
901     }
902 
903   if (side == GTK_CSS_LEFT || side == GTK_CSS_RIGHT)
904     {
905       blur_flags |= GTK_BLUR_X;
906       y1 = floor (box->box.y - clip_radius);
907       y2 = ceil (box->box.y + box->box.height + clip_radius);
908     }
909   else if (side == GTK_CSS_TOP)
910     {
911       y1 = floor (box->box.y -clip_radius);
912       y2 = ceil (box->box.y + clip_radius);
913     }
914   else
915     {
916       y1 = floor (box->box.y + box->box.height -clip_radius);
917       y2 = ceil (box->box.y + box->box.height + clip_radius);
918     }
919 
920   drawn_rect->x = x1;
921   drawn_rect->y = y1;
922   drawn_rect->width = x2 - x1;
923   drawn_rect->height = y2 - y1;
924 
925   cairo_rectangle (cr, x1, y1, x2 - x1, y2 - y1);
926   cairo_clip (cr);
927   draw_shadow (shadow, cr, box, clip_box, blur_flags);
928 }
929 
930 void
_gtk_css_shadow_value_paint_box(const GtkCssValue * shadow,cairo_t * cr,const GtkRoundedBox * padding_box)931 _gtk_css_shadow_value_paint_box (const GtkCssValue   *shadow,
932                                  cairo_t             *cr,
933                                  const GtkRoundedBox *padding_box)
934 {
935   GtkRoundedBox box, clip_box;
936   double spread, radius, clip_radius, x, y, outside;
937   double x1c, y1c, x2c, y2c;
938 
939   g_return_if_fail (shadow->class == &GTK_CSS_VALUE_SHADOW);
940 
941   /* We don't need to draw invisible shadows */
942   if (gtk_rgba_is_clear (_gtk_css_rgba_value_get_rgba (shadow->color)))
943     return;
944 
945   cairo_clip_extents (cr, &x1c, &y1c, &x2c, &y2c);
946   if ((shadow->inset && !_gtk_rounded_box_intersects_rectangle (padding_box, x1c, y1c, x2c, y2c)) ||
947       (!shadow->inset && _gtk_rounded_box_contains_rectangle (padding_box, x1c, y1c, x2c, y2c)))
948     return;
949 
950   cairo_save (cr);
951 
952   spread = _gtk_css_number_value_get (shadow->spread, 0);
953   radius = _gtk_css_number_value_get (shadow->radius, 0);
954   clip_radius = _gtk_cairo_blur_compute_pixels (radius);
955   x = _gtk_css_number_value_get (shadow->hoffset, 0);
956   y = _gtk_css_number_value_get (shadow->voffset, 0);
957 
958   if (shadow->inset)
959     {
960       _gtk_rounded_box_path (padding_box, cr);
961       cairo_clip (cr);
962     }
963   else
964     {
965       cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
966       _gtk_rounded_box_path (padding_box, cr);
967       outside = spread + clip_radius + MAX (fabs (x), fabs (y));
968       clip_box = *padding_box;
969       _gtk_rounded_box_grow (&clip_box, outside, outside, outside, outside);
970       _gtk_rounded_box_clip_path (&clip_box, cr);
971 
972       cairo_clip (cr);
973     }
974 
975   box = *padding_box;
976   _gtk_rounded_box_move (&box, x, y);
977 
978   if (shadow->inset)
979     _gtk_rounded_box_shrink (&box, spread, spread, spread, spread);
980   else /* Outset */
981     _gtk_rounded_box_grow (&box, spread, spread, spread, spread);
982 
983   clip_box = *padding_box;
984   _gtk_rounded_box_shrink (&clip_box, -clip_radius, -clip_radius, -clip_radius, -clip_radius);
985 
986   if (!needs_blur (shadow))
987     draw_shadow (shadow, cr, &box, &clip_box, GTK_BLUR_NONE);
988   else
989     {
990       int i;
991       cairo_region_t *remaining;
992       cairo_rectangle_int_t r;
993 
994       /* For the blurred case we divide the rendering into 9 parts,
995        * 4 of the corners, 4 for the horizonat/vertical lines and
996        * one for the interior. We make the non-interior parts
997        * large enought to fit the full radius of the blur, so that
998        * the interior part can be drawn solidly.
999        */
1000 
1001       if (shadow->inset)
1002 	{
1003 	  /* In the inset case we want to paint the whole clip-box.
1004 	   * We could remove the part of "box" where the blur doesn't
1005 	   * reach, but computing that is a bit tricky since the
1006 	   * rounded corners are on the "inside" of it. */
1007 	  r.x = floor (clip_box.box.x);
1008 	  r.y = floor (clip_box.box.y);
1009 	  r.width = ceil (clip_box.box.x + clip_box.box.width) - r.x;
1010 	  r.height = ceil (clip_box.box.y + clip_box.box.height) - r.y;
1011 	  remaining = cairo_region_create_rectangle (&r);
1012 	}
1013       else
1014 	{
1015 	  /* In the outset case we want to paint the entire box, plus as far
1016 	   * as the radius reaches from it */
1017 	  r.x = floor (box.box.x - clip_radius);
1018 	  r.y = floor (box.box.y - clip_radius);
1019 	  r.width = ceil (box.box.x + box.box.width + clip_radius) - r.x;
1020 	  r.height = ceil (box.box.y + box.box.height + clip_radius) - r.y;
1021 
1022 	  remaining = cairo_region_create_rectangle (&r);
1023 	}
1024 
1025       /* First do the corners of box */
1026       for (i = 0; i < 4; i++)
1027 	{
1028 	  cairo_save (cr);
1029           /* Always clip with remaining to ensure we never draw any area twice */
1030           gdk_cairo_region (cr, remaining);
1031           cairo_clip (cr);
1032 	  draw_shadow_corner (shadow, cr, &box, &clip_box, i, &r);
1033 	  cairo_restore (cr);
1034 
1035 	  /* We drew the region, remove it from remaining */
1036 	  cairo_region_subtract_rectangle (remaining, &r);
1037 	}
1038 
1039       /* Then the sides */
1040       for (i = 0; i < 4; i++)
1041 	{
1042 	  cairo_save (cr);
1043           /* Always clip with remaining to ensure we never draw any area twice */
1044           gdk_cairo_region (cr, remaining);
1045           cairo_clip (cr);
1046 	  draw_shadow_side (shadow, cr, &box, &clip_box, i, &r);
1047 	  cairo_restore (cr);
1048 
1049 	  /* We drew the region, remove it from remaining */
1050 	  cairo_region_subtract_rectangle (remaining, &r);
1051 	}
1052 
1053       /* Then the rest, which needs no blurring */
1054 
1055       cairo_save (cr);
1056       gdk_cairo_region (cr, remaining);
1057       cairo_clip (cr);
1058       draw_shadow (shadow, cr, &box, &clip_box, GTK_BLUR_NONE);
1059       cairo_restore (cr);
1060 
1061       cairo_region_destroy (remaining);
1062     }
1063 
1064   cairo_restore (cr);
1065 }
1066