]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssimage.c
css: Implement repeating linear gradients
[~andy/gtk] / gtk / gtkcssimage.c
1 /*
2  * Copyright © 2011 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 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, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Authors: Benjamin Otte <otte@gnome.org>
19  */
20
21 #include "config.h"
22
23 #include "gtkcssimageprivate.h"
24
25 /* for the types only */
26 #include "gtk/gtkcssimagegradientprivate.h"
27 #include "gtk/gtkcssimagelinearprivate.h"
28 #include "gtk/gtkcssimageurlprivate.h"
29 #include "gtk/gtkcssimagewin32private.h"
30
31 G_DEFINE_ABSTRACT_TYPE (GtkCssImage, _gtk_css_image, G_TYPE_OBJECT)
32
33 static int
34 gtk_css_image_real_get_width (GtkCssImage *image)
35 {
36   return 0;
37 }
38
39 static int
40 gtk_css_image_real_get_height (GtkCssImage *image)
41 {
42   return 0;
43 }
44
45 static double
46 gtk_css_image_real_get_aspect_ratio (GtkCssImage *image)
47 {
48   int width, height;
49
50   width = _gtk_css_image_get_width (image);
51   height = _gtk_css_image_get_height (image);
52
53   if (width && height)
54     return (double) width / height;
55   else
56     return 0;
57 }
58
59 static GtkCssImage *
60 gtk_css_image_real_compute (GtkCssImage     *image,
61                             GtkStyleContext *context)
62 {
63   return g_object_ref (image);
64 }
65
66 static void
67 _gtk_css_image_class_init (GtkCssImageClass *klass)
68 {
69   klass->get_width = gtk_css_image_real_get_width;
70   klass->get_height = gtk_css_image_real_get_height;
71   klass->get_aspect_ratio = gtk_css_image_real_get_aspect_ratio;
72   klass->compute = gtk_css_image_real_compute;
73 }
74
75 static void
76 _gtk_css_image_init (GtkCssImage *image)
77 {
78 }
79
80 int
81 _gtk_css_image_get_width (GtkCssImage *image)
82 {
83   GtkCssImageClass *klass;
84
85   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
86
87   klass = GTK_CSS_IMAGE_GET_CLASS (image);
88
89   return klass->get_width (image);
90 }
91
92 int
93 _gtk_css_image_get_height (GtkCssImage *image)
94 {
95   GtkCssImageClass *klass;
96
97   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
98
99   klass = GTK_CSS_IMAGE_GET_CLASS (image);
100
101   return klass->get_height (image);
102 }
103
104 double
105 _gtk_css_image_get_aspect_ratio (GtkCssImage *image)
106 {
107   GtkCssImageClass *klass;
108
109   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), 0);
110
111   klass = GTK_CSS_IMAGE_GET_CLASS (image);
112
113   return klass->get_aspect_ratio (image);
114 }
115
116 GtkCssImage *
117 _gtk_css_image_compute (GtkCssImage     *image,
118                         GtkStyleContext *context)
119 {
120   GtkCssImageClass *klass;
121
122   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL);
123   g_return_val_if_fail (GTK_IS_STYLE_CONTEXT (context), NULL);
124
125   klass = GTK_CSS_IMAGE_GET_CLASS (image);
126
127   return klass->compute (image, context);
128 }
129
130 void
131 _gtk_css_image_draw (GtkCssImage        *image,
132                      cairo_t            *cr,
133                      double              width,
134                      double              height)
135 {
136   GtkCssImageClass *klass;
137
138   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
139   g_return_if_fail (cr != NULL);
140
141   cairo_save (cr);
142
143   klass = GTK_CSS_IMAGE_GET_CLASS (image);
144
145   klass->draw (image, cr, width, height);
146
147   cairo_restore (cr);
148 }
149
150 void
151 _gtk_css_image_print (GtkCssImage *image,
152                       GString     *string)
153 {
154   GtkCssImageClass *klass;
155
156   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
157   g_return_if_fail (string != NULL);
158
159   klass = GTK_CSS_IMAGE_GET_CLASS (image);
160
161   klass->print (image, string);
162 }
163
164 /* Applies the algorithm outlined in
165  * http://dev.w3.org/csswg/css3-images/#default-sizing
166  */
167 void
168 _gtk_css_image_get_concrete_size (GtkCssImage *image,
169                                   double       specified_width,
170                                   double       specified_height,
171                                   double       default_width,
172                                   double       default_height,
173                                   double      *concrete_width,
174                                   double      *concrete_height)
175 {
176   double image_width, image_height, image_aspect;
177
178   g_return_if_fail (GTK_IS_CSS_IMAGE (image));
179   g_return_if_fail (specified_width >= 0);
180   g_return_if_fail (specified_height >= 0);
181   g_return_if_fail (default_width > 0);
182   g_return_if_fail (default_height > 0);
183   g_return_if_fail (concrete_width != NULL);
184   g_return_if_fail (concrete_height != NULL);
185
186   /* If the specified size is a definite width and height,
187    * the concrete object size is given that width and height.
188    */
189   if (specified_width && specified_height)
190     {
191       *concrete_width = specified_width;
192       *concrete_height = specified_height;
193       return;
194     }
195
196   image_width  = _gtk_css_image_get_width (image);
197   image_height = _gtk_css_image_get_height (image);
198   image_aspect = _gtk_css_image_get_aspect_ratio (image);
199
200   /* If the specified size has neither a definite width nor height,
201    * and has no additional contraints, the dimensions of the concrete
202    * object size are calculated as follows:
203    */
204   if (specified_width == 0.0 && specified_height == 0.0)
205     {
206       /* If the object has only an intrinsic aspect ratio,
207        * the concrete object size must have that aspect ratio,
208        * and additionally be as large as possible without either
209        * its height or width exceeding the height or width of the
210        * default object size.
211        */
212       if (image_aspect > 0 && image_width == 0 && image_height == 0)
213         {
214           if (image_aspect * default_height > default_width)
215             {
216               *concrete_width = default_height * image_aspect;
217               *concrete_height = default_height;
218             }
219           else
220             {
221               *concrete_width = default_width;
222               *concrete_height = default_width / image_aspect;
223             }
224         }
225
226       /* Otherwise, the width and height of the concrete object
227        * size is the same as the object's intrinsic width and
228        * intrinsic height, if they exist.
229        * If the concrete object size is still missing a width or
230        * height, and the object has an intrinsic aspect ratio,
231        * the missing dimension is calculated from the present
232        * dimension and the intrinsic aspect ratio.
233        * Otherwise, the missing dimension is taken from the default
234        * object size. 
235        */
236       if (image_width)
237         *concrete_width = image_width;
238       else if (image_aspect)
239         *concrete_width = image_height * image_aspect;
240       else
241         *concrete_width = default_width;
242
243       if (image_height)
244         *concrete_height = image_height;
245       else if (image_aspect)
246         *concrete_height = image_width / image_aspect;
247       else
248         *concrete_height = default_height;
249
250       return;
251     }
252
253   /* If the specified size has only a width or height, but not both,
254    * then the concrete object size is given that specified width or height.
255    * The other dimension is calculated as follows:
256    * If the object has an intrinsic aspect ratio, the missing dimension of
257    * the concrete object size is calculated using the intrinsic aspect-ratio
258    * and the present dimension.
259    * Otherwise, if the missing dimension is present in the object's intrinsic
260    * dimensions, the missing dimension is taken from the object's intrinsic
261    * dimensions.
262    * Otherwise, the missing dimension of the concrete object size is taken
263    * from the default object size. 
264    */
265   if (specified_width)
266     {
267       *concrete_width = specified_width;
268       if (image_aspect)
269         *concrete_height = specified_width / image_aspect;
270       else if (image_height)
271         *concrete_height = image_height;
272       else
273         *concrete_height = default_height;
274     }
275   else
276     {
277       *concrete_height = specified_height;
278       if (image_aspect)
279         *concrete_width = specified_height * image_aspect;
280       else if (image_width)
281         *concrete_width = image_width;
282       else
283         *concrete_width = default_width;
284     }
285 }
286
287 cairo_surface_t *
288 _gtk_css_image_get_surface (GtkCssImage     *image,
289                             cairo_surface_t *target,
290                             int              surface_width,
291                             int              surface_height)
292 {
293   cairo_surface_t *result;
294   cairo_t *cr;
295
296   g_return_val_if_fail (GTK_IS_CSS_IMAGE (image), NULL);
297   g_return_val_if_fail (surface_width > 0, NULL);
298   g_return_val_if_fail (surface_height > 0, NULL);
299
300   if (target)
301     result = cairo_surface_create_similar (target,
302                                            CAIRO_CONTENT_COLOR_ALPHA,
303                                            surface_width,
304                                            surface_height);
305   else
306     result = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
307                                          surface_width,
308                                          surface_height);
309
310   cr = cairo_create (result);
311   _gtk_css_image_draw (image, cr, surface_width, surface_height);
312   cairo_destroy (cr);
313
314   return result;
315 }
316
317 GType
318 gtk_css_image_get_parser_type (GtkCssParser *parser)
319 {
320   static const struct {
321     const char *prefix;
322     GType (* type_func) (void);
323   } image_types[] = {
324     { "url", _gtk_css_image_url_get_type },
325     { "-gtk-gradient", _gtk_css_image_gradient_get_type },
326     { "-gtk-win32-theme-part", _gtk_css_image_win32_get_type },
327     { "linear-gradient", _gtk_css_image_linear_get_type },
328     { "repeating-linear-gradient", _gtk_css_image_linear_get_type }
329   };
330   guint i;
331
332   for (i = 0; i < G_N_ELEMENTS (image_types); i++)
333     {
334       if (_gtk_css_parser_has_prefix (parser, image_types[i].prefix))
335         return image_types[i].type_func ();
336     }
337
338   return G_TYPE_INVALID;
339 }
340
341 /**
342  * _gtk_css_image_can_parse:
343  * @parser: a css parser
344  *
345  * Checks if the parser can potentially parse the given stream as an
346  * image from looking at the first token of @parser. This is useful for
347  * implementing shorthand properties. A successful parse of an image
348  * can not be guaranteed.
349  *
350  * Returns: %TURE if it looks like an image.
351  **/
352 gboolean
353 _gtk_css_image_can_parse (GtkCssParser *parser)
354 {
355   return gtk_css_image_get_parser_type (parser) != G_TYPE_INVALID;
356 }
357
358 GtkCssImage *
359 _gtk_css_image_new_parse (GtkCssParser *parser,
360                           GFile        *base)
361 {
362   GtkCssImageClass *klass;
363   GtkCssImage *image;
364   GType image_type;
365
366   g_return_val_if_fail (parser != NULL, NULL);
367   g_return_val_if_fail (G_IS_FILE (base), NULL);
368
369   image_type = gtk_css_image_get_parser_type (parser);
370   if (image_type == G_TYPE_INVALID)
371     {
372       _gtk_css_parser_error (parser, "Not a valid image");
373       return NULL;
374     }
375
376   image = g_object_new (image_type, NULL);
377
378   klass = GTK_CSS_IMAGE_GET_CLASS (image);
379   if (!klass->parse (image, parser, base))
380     {
381       g_object_unref (image);
382       return NULL;
383     }
384
385   return image;
386 }
387