1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2011 Benjamin Otte <otte@gnome.org>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser 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  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "config.h"
19 
20 #include "gtkroundedboxprivate.h"
21 
22 #include "gtkcsscornervalueprivate.h"
23 #include "gtkcssnumbervalueprivate.h"
24 #include "gtkcsstypesprivate.h"
25 
26 #include "gtkprivate.h"
27 
28 #include <string.h>
29 
30 typedef struct {
31   double angle1;
32   double angle2;
33   gboolean negative;
34 } Arc;
35 
36 static inline guint
mem_hash(gconstpointer v,int len)37 mem_hash (gconstpointer v, int len)
38 {
39   const signed char *p;
40   const signed char *end;
41   guint32 h = 5381;
42 
43   p = v;
44   end = p + len;
45   for (;  p < end; p++)
46     h = (h << 5) + h + *p;
47 
48   return h;
49 }
50 
51 static guint
arc_path_hash(Arc * arc)52 arc_path_hash (Arc *arc)
53 {
54   return mem_hash ((gconstpointer)arc, sizeof (Arc));
55 }
56 
57 static gboolean
arc_path_equal(Arc * arc1,Arc * arc2)58 arc_path_equal (Arc *arc1,
59                 Arc *arc2)
60 {
61   return arc1->angle1 == arc2->angle1 &&
62          arc1->angle2 == arc2->angle2 &&
63          arc1->negative == arc2->negative;
64 }
65 
66 /* We need the path to start with a line-to */
67 static cairo_path_t *
fixup_path(cairo_path_t * path)68 fixup_path (cairo_path_t *path)
69 {
70   cairo_path_data_t *data;
71 
72   data = &path->data[0];
73   if (data->header.type == CAIRO_PATH_MOVE_TO)
74     data->header.type = CAIRO_PATH_LINE_TO;
75 
76   return path;
77 }
78 
79 static void
append_arc(cairo_t * cr,double angle1,double angle2,gboolean negative)80 append_arc (cairo_t *cr, double angle1, double angle2, gboolean negative)
81 {
82   static GHashTable *arc_path_cache;
83   Arc key;
84   cairo_path_t *arc;
85 
86   memset (&key, 0, sizeof (Arc));
87   key.angle1 = angle1;
88   key.angle2 = angle2;
89   key.negative = negative;
90 
91   if (arc_path_cache == NULL)
92     arc_path_cache = g_hash_table_new_full ((GHashFunc)arc_path_hash,
93                                             (GEqualFunc)arc_path_equal,
94                                             g_free, (GDestroyNotify)cairo_path_destroy);
95 
96   arc = g_hash_table_lookup (arc_path_cache, &key);
97   if (arc == NULL)
98     {
99       cairo_surface_t *surface;
100       cairo_t *tmp;
101 
102       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 1, 1);
103       tmp = cairo_create (surface);
104 
105       if (negative)
106         cairo_arc_negative (tmp, 0.0, 0.0, 1.0, angle1, angle2);
107       else
108         cairo_arc (tmp, 0.0, 0.0, 1.0, angle1, angle2);
109 
110       arc = fixup_path (cairo_copy_path (tmp));
111       g_hash_table_insert (arc_path_cache, g_memdup2 (&key, sizeof (key)), arc);
112 
113       cairo_destroy (tmp);
114       cairo_surface_destroy (surface);
115     }
116 
117   cairo_append_path (cr, arc);
118 }
119 
120 static void
_cairo_ellipsis(cairo_t * cr,double xc,double yc,double xradius,double yradius,double angle1,double angle2)121 _cairo_ellipsis (cairo_t *cr,
122 	         double xc, double yc,
123 	         double xradius, double yradius,
124 	         double angle1, double angle2)
125 {
126   cairo_matrix_t save;
127 
128   if (xradius <= 0.0 || yradius <= 0.0)
129     {
130       cairo_line_to (cr, xc, yc);
131       return;
132     }
133 
134   cairo_get_matrix (cr, &save);
135   cairo_translate (cr, xc, yc);
136   cairo_scale (cr, xradius, yradius);
137   append_arc (cr, angle1, angle2, FALSE);
138   cairo_set_matrix (cr, &save);
139 }
140 
141 static void
_cairo_ellipsis_negative(cairo_t * cr,double xc,double yc,double xradius,double yradius,double angle1,double angle2)142 _cairo_ellipsis_negative (cairo_t *cr,
143                           double xc, double yc,
144                           double xradius, double yradius,
145                           double angle1, double angle2)
146 {
147   cairo_matrix_t save;
148 
149   if (xradius <= 0.0 || yradius <= 0.0)
150     {
151       cairo_line_to (cr, xc, yc);
152       return;
153     }
154 
155   cairo_get_matrix (cr, &save);
156   cairo_translate (cr, xc, yc);
157   cairo_scale (cr, xradius, yradius);
158   append_arc (cr, angle1, angle2, TRUE);
159   cairo_set_matrix (cr, &save);
160 }
161 
162 double
_gtk_rounded_box_guess_length(const GskRoundedRect * box,GtkCssSide side)163 _gtk_rounded_box_guess_length (const GskRoundedRect *box,
164                                GtkCssSide            side)
165 {
166   double length;
167   GtkCssSide before, after;
168 
169   before = side;
170   after = (side + 1) % 4;
171 
172   if (side & 1)
173     length = box->bounds.size.height
174              - box->corner[before].height
175              - box->corner[after].height;
176   else
177     length = box->bounds.size.width
178              - box->corner[before].width
179              - box->corner[after].width;
180 
181   length += G_PI * 0.125 * (box->corner[before].width
182                             + box->corner[before].height
183                             + box->corner[after].width
184                             + box->corner[after].height);
185 
186   return length;
187 }
188 
189 void
_gtk_rounded_box_path_side(const GskRoundedRect * box,cairo_t * cr,GtkCssSide side)190 _gtk_rounded_box_path_side (const GskRoundedRect *box,
191                             cairo_t              *cr,
192                             GtkCssSide            side)
193 {
194   switch (side)
195     {
196     case GTK_CSS_TOP:
197       _cairo_ellipsis (cr,
198                        box->bounds.origin.x + box->corner[GSK_CORNER_TOP_LEFT].width,
199                        box->bounds.origin.y + box->corner[GSK_CORNER_TOP_LEFT].height,
200                        box->corner[GSK_CORNER_TOP_LEFT].width,
201                        box->corner[GSK_CORNER_TOP_LEFT].height,
202                        5 * G_PI_4, 3 * G_PI_2);
203       _cairo_ellipsis (cr,
204                        box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_TOP_RIGHT].width,
205                        box->bounds.origin.y + box->corner[GSK_CORNER_TOP_RIGHT].height,
206                        box->corner[GSK_CORNER_TOP_RIGHT].width,
207                        box->corner[GSK_CORNER_TOP_RIGHT].height,
208                        - G_PI_2, -G_PI_4);
209       break;
210     case GTK_CSS_RIGHT:
211       _cairo_ellipsis (cr,
212                        box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_TOP_RIGHT].width,
213                        box->bounds.origin.y + box->corner[GSK_CORNER_TOP_RIGHT].height,
214                        box->corner[GSK_CORNER_TOP_RIGHT].width,
215                        box->corner[GSK_CORNER_TOP_RIGHT].height,
216                        - G_PI_4, 0);
217       _cairo_ellipsis (cr,
218                        box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
219                        box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
220                        box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
221                        box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
222                        0, G_PI_4);
223       break;
224     case GTK_CSS_BOTTOM:
225       _cairo_ellipsis (cr,
226                        box->bounds.origin.x + box->bounds.size.width - box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
227                        box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
228                        box->corner[GSK_CORNER_BOTTOM_RIGHT].width,
229                        box->corner[GSK_CORNER_BOTTOM_RIGHT].height,
230                        G_PI_4, G_PI_2);
231       _cairo_ellipsis (cr,
232                        box->bounds.origin.x + box->corner[GSK_CORNER_BOTTOM_LEFT].width,
233                        box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_LEFT].height,
234                        box->corner[GSK_CORNER_BOTTOM_LEFT].width,
235                        box->corner[GSK_CORNER_BOTTOM_LEFT].height,
236                        G_PI_2, 3 * G_PI_4);
237       break;
238     case GTK_CSS_LEFT:
239       _cairo_ellipsis (cr,
240                        box->bounds.origin.x + box->corner[GSK_CORNER_BOTTOM_LEFT].width,
241                        box->bounds.origin.y + box->bounds.size.height - box->corner[GSK_CORNER_BOTTOM_LEFT].height,
242                        box->corner[GSK_CORNER_BOTTOM_LEFT].width,
243                        box->corner[GSK_CORNER_BOTTOM_LEFT].height,
244                        3 * G_PI_4, G_PI);
245       _cairo_ellipsis (cr,
246                        box->bounds.origin.x + box->corner[GSK_CORNER_TOP_LEFT].width,
247                        box->bounds.origin.y + box->corner[GSK_CORNER_TOP_LEFT].height,
248                        box->corner[GSK_CORNER_TOP_LEFT].width,
249                        box->corner[GSK_CORNER_TOP_LEFT].height,
250                        G_PI, 5 * G_PI_4);
251       break;
252     default:
253       g_assert_not_reached ();
254       break;
255     }
256 }
257 
258 void
_gtk_rounded_box_path_top(const GskRoundedRect * outer,const GskRoundedRect * inner,cairo_t * cr)259 _gtk_rounded_box_path_top (const GskRoundedRect *outer,
260                            const GskRoundedRect *inner,
261                            cairo_t              *cr)
262 {
263   double start_angle, middle_angle, end_angle;
264 
265   if (outer->bounds.origin.y == inner->bounds.origin.y)
266     return;
267 
268   if (outer->bounds.origin.x == inner->bounds.origin.x)
269     start_angle = G_PI;
270   else
271     start_angle = 5 * G_PI_4;
272   middle_angle = 3 * G_PI_2;
273   if (outer->bounds.origin.x + outer->bounds.size.width == inner->bounds.origin.x + inner->bounds.size.width)
274     end_angle = 0;
275   else
276     end_angle = 7 * G_PI_4;
277 
278   cairo_new_sub_path (cr);
279 
280   _cairo_ellipsis (cr,
281                    outer->bounds.origin.x + outer->corner[GSK_CORNER_TOP_LEFT].width,
282                    outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_LEFT].height,
283                    outer->corner[GSK_CORNER_TOP_LEFT].width,
284                    outer->corner[GSK_CORNER_TOP_LEFT].height,
285                    start_angle, middle_angle);
286   _cairo_ellipsis (cr,
287                    outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_TOP_RIGHT].width,
288                    outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_RIGHT].height,
289                    outer->corner[GSK_CORNER_TOP_RIGHT].width,
290                    outer->corner[GSK_CORNER_TOP_RIGHT].height,
291                    middle_angle, end_angle);
292 
293   _cairo_ellipsis_negative (cr,
294                             inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_TOP_RIGHT].width,
295                             inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_RIGHT].height,
296                             inner->corner[GSK_CORNER_TOP_RIGHT].width,
297                             inner->corner[GSK_CORNER_TOP_RIGHT].height,
298                             end_angle, middle_angle);
299   _cairo_ellipsis_negative (cr,
300                             inner->bounds.origin.x + inner->corner[GSK_CORNER_TOP_LEFT].width,
301                             inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_LEFT].height,
302                             inner->corner[GSK_CORNER_TOP_LEFT].width,
303                             inner->corner[GSK_CORNER_TOP_LEFT].height,
304                             middle_angle, start_angle);
305 
306   cairo_close_path (cr);
307 }
308 
309 void
_gtk_rounded_box_path_right(const GskRoundedRect * outer,const GskRoundedRect * inner,cairo_t * cr)310 _gtk_rounded_box_path_right (const GskRoundedRect *outer,
311                              const GskRoundedRect *inner,
312                              cairo_t              *cr)
313 {
314   double start_angle, middle_angle, end_angle;
315 
316   if (outer->bounds.origin.x + outer->bounds.size.width == inner->bounds.origin.x + inner->bounds.size.width)
317     return;
318 
319   if (outer->bounds.origin.y == inner->bounds.origin.y)
320     start_angle = 3 * G_PI_2;
321   else
322     start_angle = 7 * G_PI_4;
323   middle_angle = 0;
324   if (outer->bounds.origin.y + outer->bounds.size.height == inner->bounds.origin.y + inner->bounds.size.height)
325     end_angle = G_PI_2;
326   else
327     end_angle = G_PI_4;
328 
329   cairo_new_sub_path (cr);
330 
331   _cairo_ellipsis (cr,
332                    outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_TOP_RIGHT].width,
333                    outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_RIGHT].height,
334                    outer->corner[GSK_CORNER_TOP_RIGHT].width,
335                    outer->corner[GSK_CORNER_TOP_RIGHT].height,
336                    start_angle, middle_angle);
337   _cairo_ellipsis (cr,
338                    outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
339                    outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
340                    outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
341                    outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
342                    middle_angle, end_angle);
343 
344   _cairo_ellipsis_negative (cr,
345                             inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
346                             inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
347                             inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
348                             inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
349                             end_angle, middle_angle);
350   _cairo_ellipsis_negative (cr,
351                             inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_TOP_RIGHT].width,
352                             inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_RIGHT].height,
353                             inner->corner[GSK_CORNER_TOP_RIGHT].width,
354                             inner->corner[GSK_CORNER_TOP_RIGHT].height,
355                             middle_angle, start_angle);
356 
357   cairo_close_path (cr);
358 }
359 
360 void
_gtk_rounded_box_path_bottom(const GskRoundedRect * outer,const GskRoundedRect * inner,cairo_t * cr)361 _gtk_rounded_box_path_bottom (const GskRoundedRect *outer,
362                               const GskRoundedRect *inner,
363                               cairo_t              *cr)
364 {
365   double start_angle, middle_angle, end_angle;
366 
367   if (outer->bounds.origin.y + outer->bounds.size.height == inner->bounds.origin.y + inner->bounds.size.height)
368     return;
369 
370   if (outer->bounds.origin.x + outer->bounds.size.width == inner->bounds.origin.x + inner->bounds.size.width)
371     start_angle = 0;
372   else
373     start_angle = G_PI_4;
374   middle_angle = G_PI_2;
375   if (outer->bounds.origin.x == inner->bounds.origin.x)
376     end_angle = G_PI;
377   else
378     end_angle = 3 * G_PI_4;
379 
380   cairo_new_sub_path (cr);
381 
382   _cairo_ellipsis (cr,
383                    outer->bounds.origin.x + outer->bounds.size.width - outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
384                    outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
385                    outer->corner[GSK_CORNER_BOTTOM_RIGHT].width,
386                    outer->corner[GSK_CORNER_BOTTOM_RIGHT].height,
387                    start_angle, middle_angle);
388   _cairo_ellipsis (cr,
389                    outer->bounds.origin.x + outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
390                    outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
391                    outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
392                    outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
393                    middle_angle, end_angle);
394 
395   _cairo_ellipsis_negative (cr,
396                             inner->bounds.origin.x + inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
397                             inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
398                             inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
399                             inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
400                             end_angle, middle_angle);
401   _cairo_ellipsis_negative (cr,
402                             inner->bounds.origin.x + inner->bounds.size.width - inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
403                             inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
404                             inner->corner[GSK_CORNER_BOTTOM_RIGHT].width,
405                             inner->corner[GSK_CORNER_BOTTOM_RIGHT].height,
406                             middle_angle, start_angle);
407 
408   cairo_close_path (cr);
409 }
410 
411 void
_gtk_rounded_box_path_left(const GskRoundedRect * outer,const GskRoundedRect * inner,cairo_t * cr)412 _gtk_rounded_box_path_left (const GskRoundedRect *outer,
413                             const GskRoundedRect *inner,
414                             cairo_t              *cr)
415 {
416   double start_angle, middle_angle, end_angle;
417 
418   if (outer->bounds.origin.x == inner->bounds.origin.x)
419     return;
420 
421   if (outer->bounds.origin.y + outer->bounds.size.height == inner->bounds.origin.y + inner->bounds.size.height)
422     start_angle = G_PI_2;
423   else
424     start_angle = 3 * G_PI_4;
425   middle_angle = G_PI;
426   if (outer->bounds.origin.y == inner->bounds.origin.y)
427     end_angle = 3 * G_PI_2;
428   else
429     end_angle = 5 * G_PI_4;
430 
431   cairo_new_sub_path (cr);
432 
433   _cairo_ellipsis (cr,
434                    outer->bounds.origin.x + outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
435                    outer->bounds.origin.y + outer->bounds.size.height - outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
436                    outer->corner[GSK_CORNER_BOTTOM_LEFT].width,
437                    outer->corner[GSK_CORNER_BOTTOM_LEFT].height,
438                    start_angle, middle_angle);
439   _cairo_ellipsis (cr,
440                    outer->bounds.origin.x + outer->corner[GSK_CORNER_TOP_LEFT].width,
441                    outer->bounds.origin.y + outer->corner[GSK_CORNER_TOP_LEFT].height,
442                    outer->corner[GSK_CORNER_TOP_LEFT].width,
443                    outer->corner[GSK_CORNER_TOP_LEFT].height,
444                    middle_angle, end_angle);
445 
446   _cairo_ellipsis_negative (cr,
447                             inner->bounds.origin.x + inner->corner[GSK_CORNER_TOP_LEFT].width,
448                             inner->bounds.origin.y + inner->corner[GSK_CORNER_TOP_LEFT].height,
449                             inner->corner[GSK_CORNER_TOP_LEFT].width,
450                             inner->corner[GSK_CORNER_TOP_LEFT].height,
451                             end_angle, middle_angle);
452   _cairo_ellipsis_negative (cr,
453                             inner->bounds.origin.x + inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
454                             inner->bounds.origin.y + inner->bounds.size.height - inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
455                             inner->corner[GSK_CORNER_BOTTOM_LEFT].width,
456                             inner->corner[GSK_CORNER_BOTTOM_LEFT].height,
457                             middle_angle, start_angle);
458 
459   cairo_close_path (cr);
460 }
461 
462