1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org>
3  * Copyright (C) 2011 Red Hat, Inc.
4  *
5  * Authors: Carlos Garnacho <carlosg@gnome.org>
6  *          Cosimo Cecchi <cosimoc@gnome.org>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 
24 #include "gtkrenderborderprivate.h"
25 
26 #include <cairo-gobject.h>
27 #include <math.h>
28 
29 #include "gtkcssbordervalueprivate.h"
30 #include "gtkcssenumvalueprivate.h"
31 #include "gtkcssimagevalueprivate.h"
32 #include "gtkcssnumbervalueprivate.h"
33 #include "gtkcssrepeatvalueprivate.h"
34 #include "gtkcssrgbavalueprivate.h"
35 #include "gtkcssstyleprivate.h"
36 #include "gtkhslaprivate.h"
37 #include "gtkroundedboxprivate.h"
38 
39 /* this is in case round() is not provided by the compiler,
40  * such as in the case of C89 compilers, like MSVC
41  */
42 #include "fallback-c89.c"
43 
44 typedef struct _GtkBorderImage GtkBorderImage;
45 
46 struct _GtkBorderImage {
47   GtkCssImage *source;
48 
49   GtkCssValue *slice;
50   GtkCssValue *width;
51   GtkCssValue *repeat;
52 };
53 
54 static gboolean
gtk_border_image_init(GtkBorderImage * image,GtkCssStyle * style)55 gtk_border_image_init (GtkBorderImage *image,
56                        GtkCssStyle    *style)
57 {
58   image->source = _gtk_css_image_value_get_image (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_IMAGE_SOURCE));
59   if (image->source == NULL)
60     return FALSE;
61 
62   image->slice = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_IMAGE_SLICE);
63   image->width = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_IMAGE_WIDTH);
64   image->repeat = gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_IMAGE_REPEAT);
65 
66   return TRUE;
67 }
68 
69 typedef struct _GtkBorderImageSliceSize GtkBorderImageSliceSize;
70 struct _GtkBorderImageSliceSize {
71   double offset;
72   double size;
73 };
74 
75 static void
gtk_border_image_compute_border_size(GtkBorderImageSliceSize sizes[3],double offset,double area_size,double start_border_width,double end_border_width,const GtkCssValue * start_border,const GtkCssValue * end_border)76 gtk_border_image_compute_border_size (GtkBorderImageSliceSize  sizes[3],
77                                       double                   offset,
78                                       double                   area_size,
79                                       double                   start_border_width,
80                                       double                   end_border_width,
81                                       const GtkCssValue       *start_border,
82                                       const GtkCssValue       *end_border)
83 {
84   double start, end;
85 
86   if (gtk_css_number_value_get_dimension (start_border) == GTK_CSS_DIMENSION_NUMBER)
87     start = start_border_width * _gtk_css_number_value_get (start_border, 100);
88   else
89     start = _gtk_css_number_value_get (start_border, area_size);
90   if (gtk_css_number_value_get_dimension (end_border) == GTK_CSS_DIMENSION_NUMBER)
91     end = end_border_width * _gtk_css_number_value_get (end_border, 100);
92   else
93     end = _gtk_css_number_value_get (end_border, area_size);
94 
95   /* XXX: reduce vertical and horizontal by the same factor */
96   if (start + end > area_size)
97     {
98       start = start * area_size / (start + end);
99       end = end * area_size / (start + end);
100     }
101 
102   sizes[0].offset = offset;
103   sizes[0].size = start;
104   sizes[1].offset = offset + start;
105   sizes[1].size = area_size - start - end;
106   sizes[2].offset = offset + area_size - end;
107   sizes[2].size = end;
108 }
109 
110 static void
gtk_border_image_render_slice(cairo_t * cr,cairo_surface_t * slice,double slice_width,double slice_height,double x,double y,double width,double height,GtkCssRepeatStyle hrepeat,GtkCssRepeatStyle vrepeat)111 gtk_border_image_render_slice (cairo_t           *cr,
112                                cairo_surface_t   *slice,
113                                double             slice_width,
114                                double             slice_height,
115                                double             x,
116                                double             y,
117                                double             width,
118                                double             height,
119                                GtkCssRepeatStyle  hrepeat,
120                                GtkCssRepeatStyle  vrepeat)
121 {
122   double hscale, vscale;
123   double xstep, ystep;
124   cairo_extend_t extend = CAIRO_EXTEND_PAD;
125   cairo_matrix_t matrix;
126   cairo_pattern_t *pattern;
127 
128   /* We can't draw center tiles yet */
129   g_assert (hrepeat == GTK_CSS_REPEAT_STYLE_STRETCH || vrepeat == GTK_CSS_REPEAT_STYLE_STRETCH);
130 
131   hscale = width / slice_width;
132   vscale = height / slice_height;
133   xstep = width;
134   ystep = height;
135 
136   switch (hrepeat)
137     {
138     case GTK_CSS_REPEAT_STYLE_REPEAT:
139       extend = CAIRO_EXTEND_REPEAT;
140       hscale = vscale;
141       break;
142     case GTK_CSS_REPEAT_STYLE_SPACE:
143       {
144         double space, n;
145 
146         extend = CAIRO_EXTEND_NONE;
147         hscale = vscale;
148 
149         xstep = hscale * slice_width;
150         n = floor (width / xstep);
151         space = (width - n * xstep) / (n + 1);
152         xstep += space;
153         x += space;
154         width -= 2 * space;
155       }
156       break;
157     case GTK_CSS_REPEAT_STYLE_STRETCH:
158       break;
159     case GTK_CSS_REPEAT_STYLE_ROUND:
160       extend = CAIRO_EXTEND_REPEAT;
161       hscale = width / (slice_width * MAX (round (width / (slice_width * vscale)), 1));
162       break;
163     default:
164       g_assert_not_reached ();
165       break;
166     }
167 
168   switch (vrepeat)
169     {
170     case GTK_CSS_REPEAT_STYLE_REPEAT:
171       extend = CAIRO_EXTEND_REPEAT;
172       vscale = hscale;
173       break;
174     case GTK_CSS_REPEAT_STYLE_SPACE:
175       {
176         double space, n;
177 
178         extend = CAIRO_EXTEND_NONE;
179         vscale = hscale;
180 
181         ystep = vscale * slice_height;
182         n = floor (height / ystep);
183         space = (height - n * ystep) / (n + 1);
184         ystep += space;
185         y += space;
186         height -= 2 * space;
187       }
188       break;
189     case GTK_CSS_REPEAT_STYLE_STRETCH:
190       break;
191     case GTK_CSS_REPEAT_STYLE_ROUND:
192       extend = CAIRO_EXTEND_REPEAT;
193       vscale = height / (slice_height * MAX (round (height / (slice_height * hscale)), 1));
194       break;
195     default:
196       g_assert_not_reached ();
197       break;
198     }
199 
200   pattern = cairo_pattern_create_for_surface (slice);
201 
202   cairo_matrix_init_translate (&matrix,
203                                hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_width / 2 : 0,
204                                vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_height / 2 : 0);
205   cairo_matrix_scale (&matrix, 1 / hscale, 1 / vscale);
206   cairo_matrix_translate (&matrix,
207                           hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - width / 2 : 0,
208                           vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - height / 2 : 0);
209 
210   cairo_pattern_set_matrix (pattern, &matrix);
211   cairo_pattern_set_extend (pattern, extend);
212 
213   cairo_save (cr);
214   cairo_translate (cr, x, y);
215 
216   for (y = 0; y < height; y += ystep)
217     {
218       for (x = 0; x < width; x += xstep)
219         {
220           cairo_save (cr);
221           cairo_translate (cr, x, y);
222           cairo_set_source (cr, pattern);
223           cairo_rectangle (cr, 0, 0, xstep, ystep);
224           cairo_fill (cr);
225           cairo_restore (cr);
226         }
227     }
228 
229   cairo_restore (cr);
230 
231   cairo_pattern_destroy (pattern);
232 }
233 
234 static void
gtk_border_image_compute_slice_size(GtkBorderImageSliceSize sizes[3],int surface_size,int start_size,int end_size)235 gtk_border_image_compute_slice_size (GtkBorderImageSliceSize sizes[3],
236                                      int                     surface_size,
237                                      int                     start_size,
238                                      int                     end_size)
239 {
240   sizes[0].size = MIN (start_size, surface_size);
241   sizes[0].offset = 0;
242 
243   sizes[2].size = MIN (end_size, surface_size);
244   sizes[2].offset = surface_size - sizes[2].size;
245 
246   sizes[1].size = MAX (0, surface_size - sizes[0].size - sizes[2].size);
247   sizes[1].offset = sizes[0].size;
248 }
249 
250 static void
gtk_border_image_render(GtkBorderImage * image,const double border_width[4],cairo_t * cr,gdouble x,gdouble y,gdouble width,gdouble height)251 gtk_border_image_render (GtkBorderImage   *image,
252                          const double      border_width[4],
253                          cairo_t          *cr,
254                          gdouble           x,
255                          gdouble           y,
256                          gdouble           width,
257                          gdouble           height)
258 {
259   cairo_surface_t *surface, *slice;
260   GtkBorderImageSliceSize vertical_slice[3], horizontal_slice[3];
261   GtkBorderImageSliceSize vertical_border[3], horizontal_border[3];
262   double source_width, source_height;
263   int h, v;
264 
265   _gtk_css_image_get_concrete_size (image->source,
266                                     0, 0,
267                                     width, height,
268                                     &source_width, &source_height);
269 
270   /* XXX: Optimize for (source_width == width && source_height == height) */
271 
272   surface = _gtk_css_image_get_surface (image->source,
273                                         cairo_get_target (cr),
274                                         source_width, source_height);
275 
276   gtk_border_image_compute_slice_size (horizontal_slice,
277                                        source_width,
278                                        _gtk_css_number_value_get (_gtk_css_border_value_get_left (image->slice), source_width),
279                                        _gtk_css_number_value_get (_gtk_css_border_value_get_right (image->slice), source_width));
280   gtk_border_image_compute_slice_size (vertical_slice,
281                                        source_height,
282                                        _gtk_css_number_value_get (_gtk_css_border_value_get_top (image->slice), source_height),
283                                        _gtk_css_number_value_get (_gtk_css_border_value_get_bottom (image->slice), source_height));
284   gtk_border_image_compute_border_size (horizontal_border,
285                                         x,
286                                         width,
287                                         border_width[GTK_CSS_LEFT],
288                                         border_width[GTK_CSS_RIGHT],
289                                         _gtk_css_border_value_get_left (image->width),
290                                         _gtk_css_border_value_get_right (image->width));
291   gtk_border_image_compute_border_size (vertical_border,
292                                         y,
293                                         height,
294                                         border_width[GTK_CSS_TOP],
295                                         border_width[GTK_CSS_BOTTOM],
296                                         _gtk_css_border_value_get_top (image->width),
297                                         _gtk_css_border_value_get_bottom(image->width));
298 
299   for (v = 0; v < 3; v++)
300     {
301       if (vertical_slice[v].size == 0 ||
302           vertical_border[v].size == 0)
303         continue;
304 
305       for (h = 0; h < 3; h++)
306         {
307           if (horizontal_slice[h].size == 0 ||
308               horizontal_border[h].size == 0)
309             continue;
310 
311           if (h == 1 && v == 1)
312             continue;
313 
314           slice = cairo_surface_create_for_rectangle (surface,
315                                                       horizontal_slice[h].offset,
316                                                       vertical_slice[v].offset,
317                                                       horizontal_slice[h].size,
318                                                       vertical_slice[v].size);
319 
320           gtk_border_image_render_slice (cr,
321                                          slice,
322                                          horizontal_slice[h].size,
323                                          vertical_slice[v].size,
324                                          horizontal_border[h].offset,
325                                          vertical_border[v].offset,
326                                          horizontal_border[h].size,
327                                          vertical_border[v].size,
328                                          h == 1 ? _gtk_css_border_repeat_value_get_x (image->repeat) : GTK_CSS_REPEAT_STYLE_STRETCH,
329                                          v == 1 ? _gtk_css_border_repeat_value_get_y (image->repeat) : GTK_CSS_REPEAT_STYLE_STRETCH);
330 
331           cairo_surface_destroy (slice);
332         }
333     }
334 
335   cairo_surface_destroy (surface);
336 }
337 
338 static void
hide_border_sides(double border[4],GtkBorderStyle border_style[4],guint hidden_side)339 hide_border_sides (double         border[4],
340                    GtkBorderStyle border_style[4],
341                    guint          hidden_side)
342 {
343   guint i;
344 
345   for (i = 0; i < 4; i++)
346     {
347       if (hidden_side & (1 << i))
348         border[i] = 0;
349     }
350 }
351 
352 static void
render_frame_fill(cairo_t * cr,GtkRoundedBox * border_box,const double border_width[4],GdkRGBA colors[4],guint hidden_side)353 render_frame_fill (cairo_t       *cr,
354                    GtkRoundedBox *border_box,
355                    const double   border_width[4],
356                    GdkRGBA        colors[4],
357                    guint          hidden_side)
358 {
359   GtkRoundedBox padding_box;
360   guint i, j;
361 
362   padding_box = *border_box;
363   _gtk_rounded_box_shrink (&padding_box,
364                            border_width[GTK_CSS_TOP],
365                            border_width[GTK_CSS_RIGHT],
366                            border_width[GTK_CSS_BOTTOM],
367                            border_width[GTK_CSS_LEFT]);
368 
369   if (hidden_side == 0 &&
370       gdk_rgba_equal (&colors[0], &colors[1]) &&
371       gdk_rgba_equal (&colors[0], &colors[2]) &&
372       gdk_rgba_equal (&colors[0], &colors[3]))
373     {
374       gdk_cairo_set_source_rgba (cr, &colors[0]);
375 
376       _gtk_rounded_box_path (border_box, cr);
377       _gtk_rounded_box_path (&padding_box, cr);
378       cairo_fill (cr);
379     }
380   else
381     {
382       for (i = 0; i < 4; i++)
383         {
384           if (hidden_side & (1 << i))
385             continue;
386 
387           for (j = 0; j < 4; j++)
388             {
389               if (hidden_side & (1 << j))
390                 continue;
391 
392               if (i == j ||
393                   (gdk_rgba_equal (&colors[i], &colors[j])))
394                 {
395                   /* We were already painted when i == j */
396                   if (i > j)
397                     break;
398 
399                   if (j == 0)
400                     _gtk_rounded_box_path_top (border_box, &padding_box, cr);
401                   else if (j == 1)
402                     _gtk_rounded_box_path_right (border_box, &padding_box, cr);
403                   else if (j == 2)
404                     _gtk_rounded_box_path_bottom (border_box, &padding_box, cr);
405                   else if (j == 3)
406                     _gtk_rounded_box_path_left (border_box, &padding_box, cr);
407                 }
408             }
409           /* We were already painted when i == j */
410           if (i > j)
411             continue;
412 
413           gdk_cairo_set_source_rgba (cr, &colors[i]);
414 
415           cairo_fill (cr);
416         }
417     }
418 }
419 
420 static void
set_stroke_style(cairo_t * cr,double line_width,GtkBorderStyle style,double length)421 set_stroke_style (cairo_t        *cr,
422                   double          line_width,
423                   GtkBorderStyle  style,
424                   double          length)
425 {
426   double segments[2];
427   double n;
428 
429   cairo_set_line_width (cr, line_width);
430 
431   if (style == GTK_BORDER_STYLE_DOTTED)
432     {
433       n = round (0.5 * length / line_width);
434 
435       segments[0] = 0;
436       segments[1] = n ? length / n : 2;
437       cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
438 
439       cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
440       cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
441     }
442   else
443     {
444       n = length / line_width;
445       /* Optimize the common case of an integer-sized rectangle
446        * Again, we care about focus rectangles.
447        */
448       if (n == nearbyint (n))
449         {
450           segments[0] = line_width;
451           segments[1] = 2 * line_width;
452         }
453       else
454         {
455           n = round ((1. / 3) * n);
456 
457           segments[0] = n ? (1. / 3) * length / n : 1;
458           segments[1] = 2 * segments[0];
459         }
460       cairo_set_dash (cr, segments, G_N_ELEMENTS (segments), 0);
461 
462       cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
463       cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
464     }
465 }
466 
467 static void
render_frame_stroke(cairo_t * cr,GtkRoundedBox * border_box,const double border_width[4],GdkRGBA colors[4],guint hidden_side,GtkBorderStyle stroke_style)468 render_frame_stroke (cairo_t       *cr,
469                      GtkRoundedBox *border_box,
470                      const double   border_width[4],
471                      GdkRGBA        colors[4],
472                      guint          hidden_side,
473                      GtkBorderStyle stroke_style)
474 {
475   gboolean different_colors, different_borders;
476   GtkRoundedBox stroke_box;
477   guint i;
478 
479   different_colors = !gdk_rgba_equal (&colors[0], &colors[1]) ||
480                      !gdk_rgba_equal (&colors[0], &colors[2]) ||
481                      !gdk_rgba_equal (&colors[0], &colors[3]);
482   different_borders = border_width[0] != border_width[1] ||
483                       border_width[0] != border_width[2] ||
484                       border_width[0] != border_width[3] ;
485 
486   stroke_box = *border_box;
487   _gtk_rounded_box_shrink (&stroke_box,
488                            border_width[GTK_CSS_TOP] / 2.0,
489                            border_width[GTK_CSS_RIGHT] / 2.0,
490                            border_width[GTK_CSS_BOTTOM] / 2.0,
491                            border_width[GTK_CSS_LEFT] / 2.0);
492 
493   if (!different_colors && !different_borders && hidden_side == 0)
494     {
495       double length = 0;
496 
497       /* FAST PATH:
498        * Mostly expected to trigger for focus rectangles */
499       for (i = 0; i < 4; i++)
500         {
501           length += _gtk_rounded_box_guess_length (&stroke_box, i);
502         }
503 
504       _gtk_rounded_box_path (&stroke_box, cr);
505       gdk_cairo_set_source_rgba (cr, &colors[0]);
506       set_stroke_style (cr, border_width[0], stroke_style, length);
507       cairo_stroke (cr);
508     }
509   else
510     {
511       GtkRoundedBox padding_box;
512 
513       padding_box = *border_box;
514       _gtk_rounded_box_shrink (&padding_box,
515                                border_width[GTK_CSS_TOP],
516                                border_width[GTK_CSS_RIGHT],
517                                border_width[GTK_CSS_BOTTOM],
518                                border_width[GTK_CSS_LEFT]);
519 
520       for (i = 0; i < 4; i++)
521         {
522           if (hidden_side & (1 << i))
523             continue;
524 
525           if (border_width[i] == 0)
526             continue;
527 
528           cairo_save (cr);
529 
530           if (i == 0)
531             _gtk_rounded_box_path_top (border_box, &padding_box, cr);
532           else if (i == 1)
533             _gtk_rounded_box_path_right (border_box, &padding_box, cr);
534           else if (i == 2)
535             _gtk_rounded_box_path_bottom (border_box, &padding_box, cr);
536           else if (i == 3)
537             _gtk_rounded_box_path_left (border_box, &padding_box, cr);
538           cairo_clip (cr);
539 
540           _gtk_rounded_box_path_side (&stroke_box, cr, i);
541 
542           gdk_cairo_set_source_rgba (cr, &colors[i]);
543           set_stroke_style (cr,
544                             border_width[i],
545                             stroke_style,
546                             _gtk_rounded_box_guess_length (&stroke_box, i));
547           cairo_stroke (cr);
548 
549           cairo_restore (cr);
550         }
551     }
552 }
553 
554 static void
color_shade(const GdkRGBA * color,gdouble factor,GdkRGBA * color_return)555 color_shade (const GdkRGBA *color,
556              gdouble        factor,
557              GdkRGBA       *color_return)
558 {
559   GtkHSLA hsla;
560 
561   _gtk_hsla_init_from_rgba (&hsla, color);
562   _gtk_hsla_shade (&hsla, &hsla, factor);
563   _gdk_rgba_init_from_hsla (color_return, &hsla);
564 }
565 
566 static void
render_border(cairo_t * cr,GtkRoundedBox * border_box,const double border_width[4],guint hidden_side,GdkRGBA colors[4],GtkBorderStyle border_style[4])567 render_border (cairo_t       *cr,
568                GtkRoundedBox *border_box,
569                const double   border_width[4],
570                guint          hidden_side,
571                GdkRGBA        colors[4],
572                GtkBorderStyle border_style[4])
573 {
574   guint i, j;
575 
576   cairo_save (cr);
577 
578   cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
579 
580   for (i = 0; i < 4; i++)
581     {
582       if (hidden_side & (1 << i))
583         continue;
584 
585       /* NB: code below divides by this value */
586       /* a border smaller than this will not noticably modify
587        * pixels on screen, and since we don't compare with 0,
588        * we'll use this value */
589       if (border_width[i] < 1.0 / 1024)
590         continue;
591 
592       switch (border_style[i])
593         {
594         case GTK_BORDER_STYLE_NONE:
595         case GTK_BORDER_STYLE_HIDDEN:
596         case GTK_BORDER_STYLE_SOLID:
597           break;
598         case GTK_BORDER_STYLE_INSET:
599           if (i == 1 || i == 2)
600             color_shade (&colors[i], 1.8, &colors[i]);
601           break;
602         case GTK_BORDER_STYLE_OUTSET:
603           if (i == 0 || i == 3)
604             color_shade (&colors[i], 1.8, &colors[i]);
605           break;
606         case GTK_BORDER_STYLE_DOTTED:
607         case GTK_BORDER_STYLE_DASHED:
608           {
609             guint dont_draw = hidden_side;
610 
611             for (j = 0; j < 4; j++)
612               {
613                 if (border_style[j] == border_style[i])
614                   hidden_side |= (1 << j);
615                 else
616                   dont_draw |= (1 << j);
617               }
618 
619             render_frame_stroke (cr, border_box, border_width, colors, dont_draw, border_style[i]);
620           }
621           break;
622         case GTK_BORDER_STYLE_DOUBLE:
623           {
624             GtkRoundedBox other_box;
625             double other_border[4];
626             guint dont_draw = hidden_side;
627 
628             for (j = 0; j < 4; j++)
629               {
630                 if (border_style[j] == GTK_BORDER_STYLE_DOUBLE)
631                   hidden_side |= (1 << j);
632                 else
633                   dont_draw |= (1 << j);
634 
635                 other_border[j] = border_width[j] / 3;
636               }
637 
638             render_frame_fill (cr, border_box, other_border, colors, dont_draw);
639 
640             other_box = *border_box;
641             _gtk_rounded_box_shrink (&other_box,
642                                      2 * other_border[GTK_CSS_TOP],
643                                      2 * other_border[GTK_CSS_RIGHT],
644                                      2 * other_border[GTK_CSS_BOTTOM],
645                                      2 * other_border[GTK_CSS_LEFT]);
646             render_frame_fill (cr, &other_box, other_border, colors, dont_draw);
647           }
648           break;
649         case GTK_BORDER_STYLE_GROOVE:
650         case GTK_BORDER_STYLE_RIDGE:
651           {
652             GtkRoundedBox other_box;
653             GdkRGBA other_colors[4];
654             guint dont_draw = hidden_side;
655             double other_border[4];
656 
657             for (j = 0; j < 4; j++)
658               {
659                 other_colors[j] = colors[j];
660                 if ((j == 0 || j == 3) ^ (border_style[j] == GTK_BORDER_STYLE_RIDGE))
661                   color_shade (&other_colors[j], 1.8, &other_colors[j]);
662                 else
663                   color_shade (&colors[j], 1.8, &colors[j]);
664                 if (border_style[j] == GTK_BORDER_STYLE_GROOVE ||
665                     border_style[j] == GTK_BORDER_STYLE_RIDGE)
666                   hidden_side |= (1 << j);
667                 else
668                   dont_draw |= (1 << j);
669                 other_border[j] = border_width[j] / 2;
670               }
671 
672             render_frame_fill (cr, border_box, other_border, colors, dont_draw);
673 
674             other_box = *border_box;
675             _gtk_rounded_box_shrink (&other_box,
676                                      other_border[GTK_CSS_TOP],
677                                      other_border[GTK_CSS_RIGHT],
678                                      other_border[GTK_CSS_BOTTOM],
679                                      other_border[GTK_CSS_LEFT]);
680             render_frame_fill (cr, &other_box, other_border, other_colors, dont_draw);
681           }
682           break;
683         default:
684           g_assert_not_reached ();
685           break;
686         }
687     }
688 
689   render_frame_fill (cr, border_box, border_width, colors, hidden_side);
690 
691   cairo_restore (cr);
692 }
693 
694 gboolean
gtk_css_style_render_has_border(GtkCssStyle * style)695 gtk_css_style_render_has_border (GtkCssStyle *style)
696 {
697   if (_gtk_css_image_value_get_image (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_IMAGE_SOURCE)))
698     return TRUE;
699 
700   return _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_WIDTH), 100) > 0
701       || _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_RIGHT_WIDTH), 100) > 0
702       || _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_WIDTH), 100) > 0
703       || _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_LEFT_WIDTH), 100) > 0;
704 }
705 
706 void
gtk_css_style_render_border(GtkCssStyle * style,cairo_t * cr,gdouble x,gdouble y,gdouble width,gdouble height,guint hidden_side,GtkJunctionSides junction)707 gtk_css_style_render_border (GtkCssStyle      *style,
708                              cairo_t          *cr,
709                              gdouble           x,
710                              gdouble           y,
711                              gdouble           width,
712                              gdouble           height,
713                              guint             hidden_side,
714                              GtkJunctionSides  junction)
715 {
716   GtkBorderImage border_image;
717   double border_width[4];
718 
719   border_width[0] = _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_WIDTH), 100);
720   border_width[1] = _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_RIGHT_WIDTH), 100);
721   border_width[2] = _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_WIDTH), 100);
722   border_width[3] = _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_LEFT_WIDTH), 100);
723 
724   if (gtk_border_image_init (&border_image, style))
725     {
726       gtk_border_image_render (&border_image, border_width, cr, x, y, width, height);
727     }
728   else
729     {
730       GtkBorderStyle border_style[4];
731       GtkRoundedBox border_box;
732       GdkRGBA colors[4];
733 
734       /* Optimize the most common case of "This widget has no border" */
735       if (border_width[0] == 0 &&
736           border_width[1] == 0 &&
737           border_width[2] == 0 &&
738           border_width[3] == 0)
739         return;
740 
741       border_style[0] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_STYLE));
742       border_style[1] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_RIGHT_STYLE));
743       border_style[2] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_STYLE));
744       border_style[3] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_LEFT_STYLE));
745 
746       hide_border_sides (border_width, border_style, hidden_side);
747 
748       colors[0] = *_gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_COLOR));
749       colors[1] = *_gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_RIGHT_COLOR));
750       colors[2] = *_gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_COLOR));
751       colors[3] = *_gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_LEFT_COLOR));
752 
753       _gtk_rounded_box_init_rect (&border_box, x, y, width, height);
754       _gtk_rounded_box_apply_border_radius_for_style (&border_box, style, junction);
755 
756       render_border (cr, &border_box, border_width, hidden_side, colors, border_style);
757     }
758 }
759 
760 gboolean
gtk_css_style_render_border_get_clip(GtkCssStyle * style,gdouble x,gdouble y,gdouble width,gdouble height,GdkRectangle * out_clip)761 gtk_css_style_render_border_get_clip (GtkCssStyle  *style,
762                                       gdouble       x,
763                                       gdouble       y,
764                                       gdouble       width,
765                                       gdouble       height,
766                                       GdkRectangle *out_clip)
767 {
768   if (!gtk_css_style_render_has_border (style))
769     return FALSE;
770 
771   out_clip->x = floor (x);
772   out_clip->y = floor (y);
773   out_clip->width = ceil (x + width) - out_clip->x;
774   out_clip->height = ceil (y + height) - out_clip->y;
775 
776   return TRUE;
777 }
778 
779 gboolean
gtk_css_style_render_has_outline(GtkCssStyle * style)780 gtk_css_style_render_has_outline (GtkCssStyle *style)
781 {
782   return _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_OUTLINE_WIDTH), 100) > 0;
783 }
784 
785 static void
compute_outline_rect(GtkCssStyle * style,gdouble x,gdouble y,gdouble width,gdouble height,cairo_rectangle_t * out_rect)786 compute_outline_rect (GtkCssStyle       *style,
787                       gdouble            x,
788                       gdouble            y,
789                       gdouble            width,
790                       gdouble            height,
791                       cairo_rectangle_t *out_rect)
792 {
793   double offset, owidth;
794 
795   owidth = _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_OUTLINE_WIDTH), 100);
796   offset = _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_OUTLINE_OFFSET), 100);
797 
798   if (width <= -2 * offset)
799     {
800       x += width / 2;
801       out_rect->x = x - owidth;
802       out_rect->width = 2 * owidth;
803     }
804   else
805     {
806       out_rect->x = x - offset - owidth;
807       out_rect->width = width + 2 * (offset + owidth);
808     }
809 
810   if (height <= -2 * offset)
811     {
812       y += height / 2;
813       out_rect->y = y - owidth;
814       out_rect->height = 2 * owidth;
815     }
816   else
817     {
818       out_rect->y = y - offset - owidth;
819       out_rect->height = height + 2 * (offset + owidth);
820     }
821 
822 }
823 
824 void
gtk_css_style_render_outline(GtkCssStyle * style,cairo_t * cr,gdouble x,gdouble y,gdouble width,gdouble height)825 gtk_css_style_render_outline (GtkCssStyle *style,
826                               cairo_t     *cr,
827                               gdouble      x,
828                               gdouble      y,
829                               gdouble      width,
830                               gdouble      height)
831 {
832   GtkBorderStyle border_style[4];
833   GtkRoundedBox border_box;
834   double border_width[4];
835   GdkRGBA colors[4];
836 
837   border_style[0] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_OUTLINE_STYLE));
838   if (border_style[0] != GTK_BORDER_STYLE_NONE)
839     {
840       cairo_rectangle_t rect;
841 
842       compute_outline_rect (style, x, y, width, height, &rect);
843 
844       border_style[1] = border_style[2] = border_style[3] = border_style[0];
845       border_width[0] = _gtk_css_number_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_OUTLINE_WIDTH), 100);
846       border_width[3] = border_width[2] = border_width[1] = border_width[0];
847       colors[0] = *_gtk_css_rgba_value_get_rgba (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_OUTLINE_COLOR));
848       colors[3] = colors[2] = colors[1] = colors[0];
849 
850       _gtk_rounded_box_init_rect (&border_box, rect.x, rect.y, rect.width, rect.height);
851       _gtk_rounded_box_apply_outline_radius_for_style (&border_box, style, GTK_JUNCTION_NONE);
852 
853       render_border (cr, &border_box, border_width, 0, colors, border_style);
854     }
855 }
856 
857 gboolean
gtk_css_style_render_outline_get_clip(GtkCssStyle * style,gdouble x,gdouble y,gdouble width,gdouble height,GdkRectangle * out_clip)858 gtk_css_style_render_outline_get_clip (GtkCssStyle  *style,
859                                        gdouble       x,
860                                        gdouble       y,
861                                        gdouble       width,
862                                        gdouble       height,
863                                        GdkRectangle *out_clip)
864 {
865   cairo_rectangle_t rect;
866 
867   if (!gtk_css_style_render_has_outline (style))
868     return FALSE;
869 
870   compute_outline_rect (style, x, y, width, height, &rect);
871 
872   out_clip->x = floor (rect.x);
873   out_clip->y = floor (rect.y);
874   out_clip->width = ceil (rect.x + rect.width) - out_clip->x;
875   out_clip->height = ceil (rect.y + rect.height) - out_clip->y;
876 
877   return TRUE;
878 }
879