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, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Benjamin Otte <otte@gnome.org>
18  */
19 
20 #include "config.h"
21 
22 #include "gtkcssshorthandpropertyprivate.h"
23 
24 #include <cairo-gobject.h>
25 #include <math.h>
26 
27 #include "gtkcssarrayvalueprivate.h"
28 #include "gtkcssbgsizevalueprivate.h"
29 #include "gtkcssbordervalueprivate.h"
30 #include "gtkcsscolorvalueprivate.h"
31 #include "gtkcsscornervalueprivate.h"
32 #include "gtkcsseasevalueprivate.h"
33 #include "gtkcssenumvalueprivate.h"
34 #include "gtkcssimageprivate.h"
35 #include "gtkcssimagevalueprivate.h"
36 #include "gtkcssnumbervalueprivate.h"
37 #include "gtkcsspositionvalueprivate.h"
38 #include "gtkcssrepeatvalueprivate.h"
39 #include "gtkcssstringvalueprivate.h"
40 #include "gtkcssvalueprivate.h"
41 #include "gtktypebuiltins.h"
42 
43 /*** PARSING ***/
44 
45 static gboolean
value_is_done_parsing(GtkCssParser * parser)46 value_is_done_parsing (GtkCssParser *parser)
47 {
48   return gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_EOF) ||
49          gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_COMMA) ||
50          gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_SEMICOLON) ||
51          gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_CLOSE_CURLY);
52 }
53 
54 static gboolean
parse_four_numbers(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser,GtkCssNumberParseFlags flags)55 parse_four_numbers (GtkCssShorthandProperty  *shorthand,
56                     GtkCssValue             **values,
57                     GtkCssParser             *parser,
58                     GtkCssNumberParseFlags    flags)
59 {
60   guint i;
61 
62   for (i = 0; i < 4; i++)
63     {
64       if (!gtk_css_number_value_can_parse (parser))
65         break;
66 
67       values[i] = _gtk_css_number_value_parse (parser, flags);
68       if (values[i] == NULL)
69         return FALSE;
70     }
71 
72   if (i == 0)
73     {
74       gtk_css_parser_error_syntax (parser, "Expected a length");
75       return FALSE;
76     }
77 
78   for (; i < 4; i++)
79     {
80       values[i] = _gtk_css_value_ref (values[(i - 1) >> 1]);
81     }
82 
83   return TRUE;
84 }
85 
86 static gboolean
parse_margin(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)87 parse_margin (GtkCssShorthandProperty  *shorthand,
88               GtkCssValue             **values,
89               GtkCssParser             *parser)
90 {
91   return parse_four_numbers (shorthand,
92                              values,
93                              parser,
94                              GTK_CSS_PARSE_LENGTH);
95 }
96 
97 static gboolean
parse_padding(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)98 parse_padding (GtkCssShorthandProperty  *shorthand,
99                GtkCssValue             **values,
100                GtkCssParser             *parser)
101 {
102   return parse_four_numbers (shorthand,
103                              values,
104                              parser,
105                              GTK_CSS_POSITIVE_ONLY
106                              | GTK_CSS_PARSE_LENGTH);
107 }
108 
109 static gboolean
parse_border_width(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)110 parse_border_width (GtkCssShorthandProperty  *shorthand,
111                     GtkCssValue             **values,
112                     GtkCssParser             *parser)
113 {
114   return parse_four_numbers (shorthand,
115                              values,
116                              parser,
117                              GTK_CSS_POSITIVE_ONLY
118                              | GTK_CSS_PARSE_LENGTH);
119 }
120 
121 static gboolean
parse_border_radius(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)122 parse_border_radius (GtkCssShorthandProperty  *shorthand,
123                      GtkCssValue             **values,
124                      GtkCssParser             *parser)
125 {
126   GtkCssValue *x[4] = { NULL, }, *y[4] = { NULL, };
127   guint i;
128 
129   for (i = 0; i < 4; i++)
130     {
131       if (!gtk_css_number_value_can_parse (parser))
132         break;
133       x[i] = _gtk_css_number_value_parse (parser,
134                                           GTK_CSS_POSITIVE_ONLY
135                                           | GTK_CSS_PARSE_PERCENT
136                                           | GTK_CSS_PARSE_LENGTH);
137       if (x[i] == NULL)
138         goto fail;
139     }
140 
141   if (i == 0)
142     {
143       gtk_css_parser_error_syntax (parser, "Expected a number");
144       goto fail;
145     }
146 
147   /* The magic (i - 1) >> 1 below makes it take the correct value
148    * according to spec. Feel free to check the 4 cases
149    */
150   for (; i < 4; i++)
151     x[i] = _gtk_css_value_ref (x[(i - 1) >> 1]);
152 
153   if (gtk_css_parser_try_delim (parser, '/'))
154     {
155       for (i = 0; i < 4; i++)
156         {
157           if (!gtk_css_number_value_can_parse (parser))
158             break;
159           y[i] = _gtk_css_number_value_parse (parser,
160                                               GTK_CSS_POSITIVE_ONLY
161                                               | GTK_CSS_PARSE_PERCENT
162                                               | GTK_CSS_PARSE_LENGTH);
163           if (y[i] == NULL)
164             goto fail;
165         }
166 
167       if (i == 0)
168         {
169           gtk_css_parser_error_syntax (parser, "Expected a number");
170           goto fail;
171         }
172 
173       for (; i < 4; i++)
174         y[i] = _gtk_css_value_ref (y[(i - 1) >> 1]);
175     }
176   else
177     {
178       for (i = 0; i < 4; i++)
179         y[i] = _gtk_css_value_ref (x[i]);
180     }
181 
182   for (i = 0; i < 4; i++)
183     {
184       values[i] = _gtk_css_corner_value_new (x[i], y[i]);
185     }
186 
187   return TRUE;
188 
189 fail:
190   for (i = 0; i < 4; i++)
191     {
192       if (x[i])
193         _gtk_css_value_unref (x[i]);
194       if (y[i])
195         _gtk_css_value_unref (y[i]);
196     }
197   return FALSE;
198 }
199 
200 static gboolean
parse_border_color(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)201 parse_border_color (GtkCssShorthandProperty  *shorthand,
202                     GtkCssValue             **values,
203                     GtkCssParser             *parser)
204 {
205   guint i;
206 
207   for (i = 0; i < 4; i++)
208     {
209       values[i] = _gtk_css_color_value_parse (parser);
210       if (values[i] == NULL)
211         return FALSE;
212 
213       if (value_is_done_parsing (parser))
214         break;
215     }
216 
217   for (i++; i < 4; i++)
218     {
219       values[i] = _gtk_css_value_ref (values[(i - 1) >> 1]);
220     }
221 
222   return TRUE;
223 }
224 
225 static gboolean
parse_border_style(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)226 parse_border_style (GtkCssShorthandProperty  *shorthand,
227                     GtkCssValue             **values,
228                     GtkCssParser             *parser)
229 {
230   guint i;
231 
232   for (i = 0; i < 4; i++)
233     {
234       values[i] = _gtk_css_border_style_value_try_parse (parser);
235       if (values[i] == NULL)
236         break;
237     }
238 
239   if (i == 0)
240     {
241       gtk_css_parser_error_syntax (parser, "Expected a border style");
242       return FALSE;
243     }
244 
245   for (; i < 4; i++)
246     values[i] = _gtk_css_value_ref (values[(i - 1) >> 1]);
247 
248   return TRUE;
249 }
250 
251 static gboolean
parse_border_image(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)252 parse_border_image (GtkCssShorthandProperty  *shorthand,
253                     GtkCssValue             **values,
254                     GtkCssParser             *parser)
255 {
256   do
257     {
258       if (values[0] == NULL &&
259           (gtk_css_parser_has_ident (parser, "none") ||
260            _gtk_css_image_can_parse (parser)))
261         {
262           GtkCssImage *image;
263 
264           if (gtk_css_parser_try_ident (parser, "none"))
265             image = NULL;
266           else
267             {
268               image = _gtk_css_image_new_parse (parser);
269               if (image == NULL)
270                 return FALSE;
271             }
272 
273           values[0] = _gtk_css_image_value_new (image);
274         }
275       else if (values[3] == NULL &&
276                (values[3] = _gtk_css_border_repeat_value_try_parse (parser)))
277         {
278           /* please move along */
279         }
280       else if (values[1] == NULL)
281         {
282           values[1] = _gtk_css_border_value_parse (parser,
283                                                    GTK_CSS_PARSE_PERCENT
284                                                    | GTK_CSS_PARSE_NUMBER
285                                                    | GTK_CSS_POSITIVE_ONLY,
286                                                    FALSE,
287                                                    TRUE);
288           if (values[1] == NULL)
289             return FALSE;
290 
291           if (gtk_css_parser_try_delim (parser, '/'))
292             {
293               values[2] = _gtk_css_border_value_parse (parser,
294                                                        GTK_CSS_PARSE_PERCENT
295                                                        | GTK_CSS_PARSE_LENGTH
296                                                        | GTK_CSS_PARSE_NUMBER
297                                                        | GTK_CSS_POSITIVE_ONLY,
298                                                        TRUE,
299                                                        FALSE);
300               if (values[2] == NULL)
301                 return FALSE;
302             }
303         }
304       else
305         {
306           /* We parsed everything and there's still stuff left?
307            * Pretend we didn't notice and let the normal code produce
308            * a 'junk at end of value' error
309            */
310           break;
311         }
312     }
313   while (!value_is_done_parsing (parser));
314 
315   return TRUE;
316 }
317 
318 static gboolean
parse_border_side(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)319 parse_border_side (GtkCssShorthandProperty  *shorthand,
320                    GtkCssValue             **values,
321                    GtkCssParser             *parser)
322 {
323   do
324   {
325     if (values[0] == NULL &&
326         gtk_css_number_value_can_parse (parser))
327       {
328         values[0] = _gtk_css_number_value_parse (parser,
329                                                  GTK_CSS_POSITIVE_ONLY
330                                                  | GTK_CSS_PARSE_LENGTH);
331         if (values[0] == NULL)
332           return FALSE;
333       }
334     else if (values[1] == NULL &&
335              (values[1] = _gtk_css_border_style_value_try_parse (parser)))
336       {
337         /* Nothing to do */
338       }
339     else if (values[2] == NULL)
340       {
341         values[2] = _gtk_css_color_value_parse (parser);
342         if (values[2] == NULL)
343           return FALSE;
344       }
345     else
346       {
347         /* We parsed and there's still stuff left?
348          * Pretend we didn't notice and let the normal code produce
349          * a 'junk at end of value' error
350          */
351         break;
352       }
353   }
354   while (!value_is_done_parsing (parser));
355 
356   return TRUE;
357 }
358 
359 static gboolean
parse_border(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)360 parse_border (GtkCssShorthandProperty  *shorthand,
361               GtkCssValue             **values,
362               GtkCssParser             *parser)
363 {
364   do
365   {
366     if (values[0] == NULL &&
367         gtk_css_number_value_can_parse (parser))
368       {
369         values[0] = _gtk_css_number_value_parse (parser,
370                                                  GTK_CSS_POSITIVE_ONLY
371                                                  | GTK_CSS_PARSE_LENGTH);
372         if (values[0] == NULL)
373           return FALSE;
374         values[1] = _gtk_css_value_ref (values[0]);
375         values[2] = _gtk_css_value_ref (values[0]);
376         values[3] = _gtk_css_value_ref (values[0]);
377       }
378     else if (values[4] == NULL &&
379              (values[4] = _gtk_css_border_style_value_try_parse (parser)))
380       {
381         values[5] = _gtk_css_value_ref (values[4]);
382         values[6] = _gtk_css_value_ref (values[4]);
383         values[7] = _gtk_css_value_ref (values[4]);
384       }
385     else if (values[8] == NULL)
386       {
387         values[8] = _gtk_css_color_value_parse (parser);
388         if (values[8] == NULL)
389           return FALSE;
390 
391         values[9] = _gtk_css_value_ref (values[8]);
392         values[10] = _gtk_css_value_ref (values[8]);
393         values[11] = _gtk_css_value_ref (values[8]);
394       }
395     else
396       {
397         /* We parsed everything and there's still stuff left?
398          * Pretend we didn't notice and let the normal code produce
399          * a 'junk at end of value' error
400          */
401         break;
402       }
403   }
404   while (!value_is_done_parsing (parser));
405 
406   /* Note that border-image values are not set: according to the spec
407    * they just need to be reset when using the border shorthand
408    */
409 
410   return TRUE;
411 }
412 
413 static GtkCssValue *
_gtk_css_font_variant_value_try_parse(GtkCssParser * parser)414 _gtk_css_font_variant_value_try_parse (GtkCssParser *parser)
415 {
416   if (gtk_css_parser_try_ident (parser, "normal"))
417     return _gtk_css_ident_value_new ("normal");
418   else if (gtk_css_parser_try_ident (parser, "small-caps"))
419     return _gtk_css_ident_value_new ("small-caps");
420   return NULL;
421 }
422 
423 static gboolean
parse_font(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)424 parse_font (GtkCssShorthandProperty  *shorthand,
425             GtkCssValue             **values,
426             GtkCssParser             *parser)
427 {
428   gboolean parsed_one;
429 
430   do
431     {
432       parsed_one = FALSE;
433 
434       if (values[1] == NULL)
435         {
436           values[1] = _gtk_css_font_style_value_try_parse (parser);
437           parsed_one = parsed_one || values[1] != NULL;
438         }
439 
440       if (values[2] == NULL)
441         {
442           values[2] = _gtk_css_font_variant_value_try_parse (parser);
443           parsed_one = parsed_one || values[2] != NULL;
444         }
445 
446       if (values[3] == NULL)
447         {
448           values[3] = gtk_css_font_weight_value_try_parse (parser);
449           if (values[3] == NULL && gtk_css_number_value_can_parse (parser))
450             {
451               /* This needs to check for font-size, too */
452               GtkCssValue *num = _gtk_css_number_value_parse (parser,
453                                                               GTK_CSS_PARSE_NUMBER |
454                                                               GTK_CSS_PARSE_LENGTH |
455                                                               GTK_CSS_PARSE_PERCENT |
456                                                               GTK_CSS_POSITIVE_ONLY);
457               if (num == NULL)
458                 return FALSE;
459 
460               if (gtk_css_number_value_get_dimension (num) != GTK_CSS_DIMENSION_NUMBER)
461                 {
462                   values[5] = num;
463                   goto have_font_size;
464                 }
465 
466               values[3] = num;
467               if (_gtk_css_number_value_get (values[3], 100) < 1 ||
468                   _gtk_css_number_value_get (values[3], 100) > 1000)
469                 {
470                   gtk_css_parser_error_value (parser, "Font weight values must be between 1 and 1000");
471                   g_clear_pointer (&values[3], gtk_css_value_unref);
472                   return FALSE;
473                 }
474             }
475           parsed_one = parsed_one || values[3] != NULL;
476         }
477 
478       if (values[4] == NULL)
479         {
480           values[4] = _gtk_css_font_stretch_value_try_parse (parser);
481           parsed_one = parsed_one || values[4] != NULL;
482         }
483     }
484   while (parsed_one && !value_is_done_parsing (parser));
485 
486   values[5] = gtk_css_font_size_value_parse (parser);
487   if (values[5] == NULL)
488     return FALSE;
489 
490 have_font_size:
491   values[0] = gtk_css_font_family_value_parse (parser);
492   if (values[0] == NULL)
493     return FALSE;
494 
495   return TRUE;
496 }
497 
498 static gboolean
parse_one_background(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)499 parse_one_background (GtkCssShorthandProperty  *shorthand,
500                       GtkCssValue             **values,
501                       GtkCssParser             *parser)
502 {
503   GtkCssValue *value = NULL;
504 
505   do
506     {
507       /* the image part */
508       if (values[0] == NULL &&
509           (gtk_css_parser_has_ident (parser, "none") ||
510            _gtk_css_image_can_parse (parser)))
511         {
512           GtkCssImage *image;
513 
514           if (gtk_css_parser_try_ident (parser, "none"))
515             image = NULL;
516           else
517             {
518               image = _gtk_css_image_new_parse (parser);
519               if (image == NULL)
520                 return FALSE;
521             }
522 
523           values[0] = _gtk_css_image_value_new (image);
524         }
525       else if (values[1] == NULL &&
526                (value = _gtk_css_position_value_try_parse (parser)))
527         {
528           values[1] = value;
529           value = NULL;
530 
531           if (gtk_css_parser_try_delim (parser, '/') &&
532               (value = _gtk_css_bg_size_value_parse (parser)))
533             {
534               values[2] = value;
535               value = NULL;
536             }
537         }
538       else if (values[3] == NULL &&
539                (value = _gtk_css_background_repeat_value_try_parse (parser)))
540         {
541           values[3] = value;
542           value = NULL;
543         }
544       else if ((values[4] == NULL || values[5] == NULL) &&
545                (value = _gtk_css_area_value_try_parse (parser)))
546         {
547           values[4] = value;
548 
549           if (values[5] == NULL)
550             {
551               values[5] = values[4];
552               values[4] = NULL;
553             }
554           value = NULL;
555         }
556       else if (values[6] == NULL)
557         {
558           value = _gtk_css_color_value_parse (parser);
559           if (value == NULL)
560             values[6] = _gtk_css_value_ref (_gtk_css_style_property_get_initial_value
561                                             (_gtk_css_shorthand_property_get_subproperty (shorthand, 6)));
562           else
563             values[6] = value;
564 
565           value = NULL;
566         }
567       else
568         {
569           /* We parsed everything and there's still stuff left?
570            * Pretend we didn't notice and let the normal code produce
571            * a 'junk at end of value' error
572            */
573           break;
574         }
575     }
576   while (!value_is_done_parsing (parser));
577 
578   if (values[5] != NULL && values[4] == NULL)
579     values[4] = _gtk_css_value_ref (values[5]);
580 
581   return TRUE;
582 }
583 
584 static gboolean
parse_background(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)585 parse_background (GtkCssShorthandProperty  *shorthand,
586                   GtkCssValue             **values,
587                   GtkCssParser             *parser)
588 {
589   GtkCssValue *step_values[7];
590   GPtrArray *arrays[6];
591   guint i;
592 
593   for (i = 0; i < 6; i++)
594     {
595       arrays[i] = g_ptr_array_new ();
596       step_values[i] = NULL;
597     }
598 
599   step_values[6] = NULL;
600 
601   do {
602     if (!parse_one_background (shorthand, step_values, parser))
603       {
604         for (i = 0; i < 6; i++)
605           {
606             g_ptr_array_set_free_func (arrays[i], (GDestroyNotify) _gtk_css_value_unref);
607             g_ptr_array_unref (arrays[i]);
608           }
609         return FALSE;
610       }
611 
612       for (i = 0; i < 6; i++)
613         {
614           if (step_values[i] == NULL)
615             {
616               GtkCssValue *initial = _gtk_css_style_property_get_initial_value (
617                                          _gtk_css_shorthand_property_get_subproperty (shorthand, i));
618               step_values[i] = _gtk_css_value_ref (_gtk_css_array_value_get_nth (initial, 0));
619             }
620 
621           g_ptr_array_add (arrays[i], step_values[i]);
622           step_values[i] = NULL;
623         }
624   } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA));
625 
626   for (i = 0; i < 6; i++)
627     {
628       values[i] = _gtk_css_array_value_new_from_array ((GtkCssValue **) arrays[i]->pdata, arrays[i]->len);
629       g_ptr_array_unref (arrays[i]);
630     }
631 
632   values[6] = step_values[6];
633 
634   return TRUE;
635 }
636 
637 static gboolean
has_transition_property(GtkCssParser * parser,gpointer option_data,gpointer user_data)638 has_transition_property (GtkCssParser *parser,
639                          gpointer      option_data,
640                          gpointer      user_data)
641 {
642   return gtk_css_parser_has_token (parser, GTK_CSS_TOKEN_IDENT);
643 }
644 
645 static gboolean
parse_transition_property(GtkCssParser * parser,gpointer option_data,gpointer user_data)646 parse_transition_property (GtkCssParser *parser,
647                            gpointer      option_data,
648                            gpointer      user_data)
649 {
650   GtkCssValue **value = option_data;
651 
652   *value = _gtk_css_ident_value_try_parse (parser);
653   g_assert (*value);
654 
655   return TRUE;
656 }
657 
658 static gboolean
parse_transition_time(GtkCssParser * parser,gpointer option_data,gpointer user_data)659 parse_transition_time (GtkCssParser *parser,
660                        gpointer      option_data,
661                        gpointer      user_data)
662 {
663   GtkCssValue **value = option_data;
664 
665   *value = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_TIME);
666 
667   return *value != NULL;
668 }
669 
670 static gboolean
parse_transition_timing_function(GtkCssParser * parser,gpointer option_data,gpointer user_data)671 parse_transition_timing_function (GtkCssParser *parser,
672                                   gpointer      option_data,
673                                   gpointer      user_data)
674 {
675   GtkCssValue **value = option_data;
676 
677   *value = _gtk_css_ease_value_parse (parser);
678 
679   return *value != NULL;
680 }
681 
682 static gboolean
parse_one_transition(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)683 parse_one_transition (GtkCssShorthandProperty  *shorthand,
684                       GtkCssValue             **values,
685                       GtkCssParser             *parser)
686 {
687   const GtkCssParseOption options[] = {
688     { (void *) _gtk_css_ease_value_can_parse, parse_transition_timing_function, &values[3] },
689     { (void *) gtk_css_number_value_can_parse, parse_transition_time, &values[1] },
690     { (void *) gtk_css_number_value_can_parse, parse_transition_time, &values[2] },
691     { (void *) has_transition_property, parse_transition_property, &values[0] },
692   };
693 
694   return gtk_css_parser_consume_any (parser, options, G_N_ELEMENTS (options), NULL);
695 }
696 
697 static gboolean
parse_transition(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)698 parse_transition (GtkCssShorthandProperty  *shorthand,
699                   GtkCssValue             **values,
700                   GtkCssParser             *parser)
701 {
702   GtkCssValue *step_values[4];
703   GPtrArray *arrays[4];
704   guint i;
705 
706   for (i = 0; i < 4; i++)
707     {
708       arrays[i] = g_ptr_array_new ();
709       step_values[i] = NULL;
710     }
711 
712   do {
713     if (!parse_one_transition (shorthand, step_values, parser))
714       {
715         for (i = 0; i < 4; i++)
716           {
717             g_ptr_array_set_free_func (arrays[i], (GDestroyNotify) _gtk_css_value_unref);
718             g_ptr_array_unref (arrays[i]);
719           }
720         return FALSE;
721       }
722 
723       for (i = 0; i < 4; i++)
724         {
725           if (step_values[i] == NULL)
726             {
727               GtkCssValue *initial = _gtk_css_style_property_get_initial_value (
728                                          _gtk_css_shorthand_property_get_subproperty (shorthand, i));
729               step_values[i] = _gtk_css_value_ref (_gtk_css_array_value_get_nth (initial, 0));
730             }
731 
732           g_ptr_array_add (arrays[i], step_values[i]);
733           step_values[i] = NULL;
734         }
735   } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA));
736 
737   for (i = 0; i < 4; i++)
738     {
739       values[i] = _gtk_css_array_value_new_from_array ((GtkCssValue **) arrays[i]->pdata, arrays[i]->len);
740       g_ptr_array_unref (arrays[i]);
741     }
742 
743   return TRUE;
744 }
745 
746 static gboolean
parse_one_animation(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)747 parse_one_animation (GtkCssShorthandProperty  *shorthand,
748                      GtkCssValue             **values,
749                      GtkCssParser             *parser)
750 {
751   do
752     {
753       if (values[1] == NULL && gtk_css_parser_try_ident (parser, "infinite"))
754         {
755           values[1] = _gtk_css_number_value_new (HUGE_VAL, GTK_CSS_NUMBER);
756         }
757       else if ((values[1] == NULL || values[3] == NULL) &&
758                gtk_css_number_value_can_parse (parser))
759         {
760           GtkCssValue *value;
761 
762           value = _gtk_css_number_value_parse (parser,
763                                                GTK_CSS_POSITIVE_ONLY
764                                                | (values[1] == NULL ? GTK_CSS_PARSE_NUMBER : 0)
765                                                | (values[3] == NULL ? GTK_CSS_PARSE_TIME : 0));
766           if (value == NULL)
767             return FALSE;
768 
769           if (gtk_css_number_value_get_dimension (value) == GTK_CSS_DIMENSION_NUMBER)
770             values[1] = value;
771           else if (values[2] == NULL)
772             values[2] = value;
773           else
774             values[3] = value;
775         }
776       else if (values[4] == NULL &&
777                _gtk_css_ease_value_can_parse (parser))
778         {
779           values[4] = _gtk_css_ease_value_parse (parser);
780 
781           if (values[4] == NULL)
782             return FALSE;
783         }
784       else if (values[5] == NULL &&
785                (values[5] = _gtk_css_direction_value_try_parse (parser)))
786         {
787           /* nothing to do */
788         }
789       else if (values[6] == NULL &&
790                (values[6] = _gtk_css_fill_mode_value_try_parse (parser)))
791         {
792           /* nothing to do */
793         }
794       else if (values[0] == NULL &&
795                (values[0] = _gtk_css_ident_value_try_parse (parser)))
796         {
797           /* nothing to do */
798           /* keep in mind though that this needs to come last as fill modes, directions
799            * etc are valid idents */
800         }
801       else
802         {
803           /* We parsed everything and there's still stuff left?
804            * Pretend we didn't notice and let the normal code produce
805            * a 'junk at end of value' error */
806           break;
807         }
808     }
809   while (!value_is_done_parsing (parser));
810 
811   return TRUE;
812 }
813 
814 static gboolean
parse_animation(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)815 parse_animation (GtkCssShorthandProperty  *shorthand,
816                  GtkCssValue             **values,
817                  GtkCssParser             *parser)
818 {
819   GtkCssValue *step_values[7];
820   GPtrArray *arrays[7];
821   guint i;
822 
823   for (i = 0; i < 7; i++)
824     {
825       arrays[i] = g_ptr_array_new ();
826       step_values[i] = NULL;
827     }
828 
829   do {
830     if (!parse_one_animation (shorthand, step_values, parser))
831       {
832         for (i = 0; i < 7; i++)
833           {
834             g_ptr_array_set_free_func (arrays[i], (GDestroyNotify) _gtk_css_value_unref);
835             g_ptr_array_unref (arrays[i]);
836           }
837         return FALSE;
838       }
839 
840       for (i = 0; i < 7; i++)
841         {
842           if (step_values[i] == NULL)
843             {
844               GtkCssValue *initial = _gtk_css_style_property_get_initial_value (
845                                          _gtk_css_shorthand_property_get_subproperty (shorthand, i));
846               step_values[i] = _gtk_css_value_ref (_gtk_css_array_value_get_nth (initial, 0));
847             }
848 
849           g_ptr_array_add (arrays[i], step_values[i]);
850           step_values[i] = NULL;
851         }
852   } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA));
853 
854   for (i = 0; i < 7; i++)
855     {
856       values[i] = _gtk_css_array_value_new_from_array ((GtkCssValue **) arrays[i]->pdata, arrays[i]->len);
857       g_ptr_array_unref (arrays[i]);
858     }
859 
860   return TRUE;
861 }
862 
863 static gboolean
parse_text_decoration(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)864 parse_text_decoration (GtkCssShorthandProperty  *shorthand,
865                        GtkCssValue             **values,
866                        GtkCssParser             *parser)
867 {
868   GtkTextDecorationLine line = 0;
869 
870   do
871   {
872     GtkTextDecorationLine parsed_line;
873 
874     parsed_line = _gtk_css_text_decoration_line_try_parse_one (parser, line);
875 
876     if (parsed_line == 0 && line != 0)
877       {
878         gtk_css_parser_error_value (parser, "Invalid combination of text-decoration-line values");
879         return FALSE;
880       }
881 
882     if (parsed_line != line)
883       {
884         line = parsed_line;
885       }
886     else if (values[1] == NULL &&
887              (values[1] = _gtk_css_text_decoration_style_value_try_parse (parser)))
888       {
889         if (values[1] == NULL)
890           return FALSE;
891       }
892     else if (values[2] == NULL)
893       {
894         values[2] = _gtk_css_color_value_parse (parser);
895         if (values[2] == NULL)
896           return FALSE;
897       }
898     else
899       {
900         /* We parsed and there's still stuff left?
901          * Pretend we didn't notice and let the normal code produce
902          * a 'junk at end of value' error */
903         break;
904       }
905   }
906   while (!value_is_done_parsing (parser));
907 
908   if (line != 0)
909     {
910       values[0] = _gtk_css_text_decoration_line_value_new (line);
911       if (values[0] == NULL)
912         {
913           gtk_css_parser_error_value (parser, "Invalid combination of text-decoration-line values");
914           return FALSE;
915         }
916     }
917 
918   return TRUE;
919 }
920 
921 static gboolean
parse_font_variant(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)922 parse_font_variant (GtkCssShorthandProperty  *shorthand,
923                     GtkCssValue             **values,
924                     GtkCssParser             *parser)
925 {
926   if (gtk_css_parser_try_ident (parser, "normal"))
927     {
928       /* all initial values */
929     }
930   else if (gtk_css_parser_try_ident (parser, "none"))
931     {
932       /* all initial values, except for font-variant-ligatures */
933       values[0] = _gtk_css_font_variant_ligature_value_new (GTK_CSS_FONT_VARIANT_LIGATURE_NONE);
934     }
935   else
936     {
937       GtkCssFontVariantLigature ligatures;
938       GtkCssFontVariantNumeric numeric;
939       GtkCssFontVariantEastAsian east_asian;
940 
941       ligatures = 0;
942       numeric = 0;
943       east_asian = 0;
944       do {
945         GtkCssFontVariantLigature parsed_ligature;
946         GtkCssFontVariantNumeric parsed_numeric;
947         GtkCssFontVariantEastAsian parsed_east_asian;
948 
949         parsed_ligature = _gtk_css_font_variant_ligature_try_parse_one (parser, ligatures);
950         if (parsed_ligature == 0 && ligatures != 0)
951           {
952             gtk_css_parser_error_value (parser, "Invalid combination of ligature values");
953             return FALSE;
954           }
955         if (parsed_ligature == GTK_CSS_FONT_VARIANT_LIGATURE_NORMAL ||
956             parsed_ligature == GTK_CSS_FONT_VARIANT_LIGATURE_NONE)
957           {
958             gtk_css_parser_error_value (parser, "Unexpected ligature value");
959             return FALSE;
960           }
961         if (parsed_ligature != ligatures)
962           {
963             ligatures = parsed_ligature;
964             goto found;
965           }
966 
967         parsed_numeric = _gtk_css_font_variant_numeric_try_parse_one (parser, numeric);
968         if (parsed_numeric == 0 && numeric != 0)
969           {
970             gtk_css_parser_error_value (parser, "Invalid combination of numeric values");
971             return FALSE;
972           }
973         if (parsed_numeric == GTK_CSS_FONT_VARIANT_NUMERIC_NORMAL)
974           {
975             gtk_css_parser_error_value (parser, "Unexpected numeric value");
976             return FALSE;
977           }
978         if (parsed_numeric != numeric)
979           {
980             numeric = parsed_numeric;
981             goto found;
982           }
983 
984         parsed_east_asian = _gtk_css_font_variant_east_asian_try_parse_one (parser, east_asian);
985         if (parsed_east_asian == 0 && east_asian != 0)
986           {
987             gtk_css_parser_error_value (parser, "Invalid combination of east asian values");
988             return FALSE;
989           }
990         if (parsed_east_asian == GTK_CSS_FONT_VARIANT_EAST_ASIAN_NORMAL)
991           {
992             gtk_css_parser_error_value (parser, "Unexpected east asian value");
993             return FALSE;
994           }
995         if (parsed_east_asian != east_asian)
996           {
997             east_asian = parsed_east_asian;
998             goto found;
999           }
1000 
1001         if (values[1] == NULL)
1002           {
1003             values[1] = _gtk_css_font_variant_position_value_try_parse (parser);
1004             if (values[1])
1005               {
1006                 if (_gtk_css_font_variant_position_value_get (values[1]) == GTK_CSS_FONT_VARIANT_POSITION_NORMAL)
1007                   {
1008                     gtk_css_parser_error_value (parser, "Unexpected position value");
1009                     return FALSE;
1010                   }
1011                 goto found;
1012               }
1013           }
1014         if (values[2] == NULL)
1015           {
1016             values[2] = _gtk_css_font_variant_caps_value_try_parse (parser);
1017             if (values[2])
1018               {
1019                 if (_gtk_css_font_variant_caps_value_get (values[2]) == GTK_CSS_FONT_VARIANT_CAPS_NORMAL)
1020                   {
1021                     gtk_css_parser_error_value (parser, "Unexpected caps value");
1022                     return FALSE;
1023                   }
1024                 goto found;
1025               }
1026           }
1027 
1028         if (values[4] == NULL)
1029           {
1030             values[4] = _gtk_css_font_variant_alternate_value_try_parse (parser);
1031             if (values[4])
1032               {
1033                 if (_gtk_css_font_variant_alternate_value_get (values[4]) == GTK_CSS_FONT_VARIANT_ALTERNATE_NORMAL)
1034                   {
1035                     gtk_css_parser_error_value (parser, "Unexpected alternate value");
1036                     return FALSE;
1037                   }
1038                 goto found;
1039               }
1040           }
1041 
1042         gtk_css_parser_error_value (parser, "Unknown value for property");
1043         return FALSE;
1044 
1045 found:
1046         if (value_is_done_parsing (parser))
1047           break;
1048 
1049       } while (1);
1050 
1051       if (ligatures != 0)
1052         {
1053           values[0] = _gtk_css_font_variant_ligature_value_new (ligatures);
1054           if (values[0] == NULL)
1055             {
1056               gtk_css_parser_error_value (parser, "Invalid combination of ligature values");
1057               return FALSE;
1058             }
1059         }
1060 
1061       if (numeric != 0)
1062         {
1063           values[3] = _gtk_css_font_variant_numeric_value_new (numeric);
1064           if (values[3] == NULL)
1065             {
1066               gtk_css_parser_error_value (parser, "Invalid combination of numeric values");
1067               return FALSE;
1068             }
1069         }
1070 
1071       if (east_asian != 0)
1072         {
1073           values[5] = _gtk_css_font_variant_east_asian_value_new (east_asian);
1074           if (values[5] == NULL)
1075             {
1076               gtk_css_parser_error_value (parser, "Invalid combination of east asian values");
1077               return FALSE;
1078             }
1079         }
1080     }
1081 
1082   return TRUE;
1083 }
1084 
1085 static gboolean
parse_all(GtkCssShorthandProperty * shorthand,GtkCssValue ** values,GtkCssParser * parser)1086 parse_all (GtkCssShorthandProperty  *shorthand,
1087            GtkCssValue             **values,
1088            GtkCssParser             *parser)
1089 {
1090   gtk_css_parser_error_syntax (parser, "The 'all' property can only be set to 'initial', 'inherit' or 'unset'");
1091   return FALSE;
1092 }
1093 
1094 static void
gtk_css_shorthand_property_register(const char * name,const char ** subproperties,GtkCssShorthandPropertyParseFunc parse_func)1095 gtk_css_shorthand_property_register (const char                        *name,
1096                                      const char                       **subproperties,
1097                                      GtkCssShorthandPropertyParseFunc   parse_func)
1098 {
1099   GtkCssShorthandProperty *node;
1100 
1101   node = g_object_new (GTK_TYPE_CSS_SHORTHAND_PROPERTY,
1102                        "name", name,
1103                        "subproperties", subproperties,
1104                        NULL);
1105 
1106   node->parse = parse_func;
1107 }
1108 
1109 /* NB: return value is transfer: container */
1110 static const char **
get_all_subproperties(void)1111 get_all_subproperties (void)
1112 {
1113   const char **properties;
1114   guint i, n;
1115 
1116   n = _gtk_css_style_property_get_n_properties ();
1117   properties = g_new (const char *, n + 1);
1118   properties[n] = NULL;
1119 
1120   for (i = 0; i < n; i++)
1121     {
1122       properties[i] = _gtk_style_property_get_name (GTK_STYLE_PROPERTY (_gtk_css_style_property_lookup_by_id (i)));
1123     }
1124 
1125   return properties;
1126 }
1127 
1128 void
_gtk_css_shorthand_property_init_properties(void)1129 _gtk_css_shorthand_property_init_properties (void)
1130 {
1131   /* The order is important here, be careful when changing it */
1132   const char *font_subproperties[] = { "font-family", "font-style", "font-variant-caps", "font-weight", "font-stretch", "font-size", NULL };
1133   const char *margin_subproperties[] = { "margin-top", "margin-right", "margin-bottom", "margin-left", NULL };
1134   const char *padding_subproperties[] = { "padding-top", "padding-right", "padding-bottom", "padding-left", NULL };
1135   const char *border_width_subproperties[] = { "border-top-width", "border-right-width", "border-bottom-width", "border-left-width", NULL };
1136   const char *border_radius_subproperties[] = { "border-top-left-radius", "border-top-right-radius",
1137                                                 "border-bottom-right-radius", "border-bottom-left-radius", NULL };
1138   const char *border_color_subproperties[] = { "border-top-color", "border-right-color", "border-bottom-color", "border-left-color", NULL };
1139   const char *border_style_subproperties[] = { "border-top-style", "border-right-style", "border-bottom-style", "border-left-style", NULL };
1140   const char *border_image_subproperties[] = { "border-image-source", "border-image-slice", "border-image-width", "border-image-repeat", NULL };
1141   const char *border_top_subproperties[] = { "border-top-width", "border-top-style", "border-top-color", NULL };
1142   const char *border_right_subproperties[] = { "border-right-width", "border-right-style", "border-right-color", NULL };
1143   const char *border_bottom_subproperties[] = { "border-bottom-width", "border-bottom-style", "border-bottom-color", NULL };
1144   const char *border_left_subproperties[] = { "border-left-width", "border-left-style", "border-left-color", NULL };
1145   const char *border_subproperties[] = { "border-top-width", "border-right-width", "border-bottom-width", "border-left-width",
1146                                          "border-top-style", "border-right-style", "border-bottom-style", "border-left-style",
1147                                          "border-top-color", "border-right-color", "border-bottom-color", "border-left-color",
1148                                          "border-image-source", "border-image-slice", "border-image-width", "border-image-repeat", NULL };
1149   const char *outline_subproperties[] = { "outline-width", "outline-style", "outline-color", NULL };
1150   const char *background_subproperties[] = { "background-image", "background-position", "background-size", "background-repeat", "background-clip", "background-origin",
1151                                              "background-color", NULL };
1152   const char *transition_subproperties[] = { "transition-property", "transition-duration", "transition-delay", "transition-timing-function", NULL };
1153   const char *animation_subproperties[] = { "animation-name", "animation-iteration-count", "animation-duration", "animation-delay",
1154                                             "animation-timing-function", "animation-direction", "animation-fill-mode", NULL };
1155   const char *text_decoration_subproperties[] = { "text-decoration-line", "text-decoration-style", "text-decoration-color", NULL };
1156   const char *font_variant_subproperties[] = { "font-variant-ligatures", "font-variant-position", "font-variant-caps", "font-variant-numeric", "font-variant-alternates", "font-variant-east-asian", NULL };
1157 
1158   const char **all_subproperties;
1159 
1160   gtk_css_shorthand_property_register   ("font",
1161                                          font_subproperties,
1162                                          parse_font);
1163   gtk_css_shorthand_property_register   ("margin",
1164                                          margin_subproperties,
1165                                          parse_margin);
1166   gtk_css_shorthand_property_register   ("padding",
1167                                          padding_subproperties,
1168                                          parse_padding);
1169   gtk_css_shorthand_property_register   ("border-width",
1170                                          border_width_subproperties,
1171                                          parse_border_width);
1172   gtk_css_shorthand_property_register   ("border-radius",
1173                                          border_radius_subproperties,
1174                                          parse_border_radius);
1175   gtk_css_shorthand_property_register   ("border-color",
1176                                          border_color_subproperties,
1177                                          parse_border_color);
1178   gtk_css_shorthand_property_register   ("border-style",
1179                                          border_style_subproperties,
1180                                          parse_border_style);
1181   gtk_css_shorthand_property_register   ("border-image",
1182                                          border_image_subproperties,
1183                                          parse_border_image);
1184   gtk_css_shorthand_property_register   ("border-top",
1185                                          border_top_subproperties,
1186                                          parse_border_side);
1187   gtk_css_shorthand_property_register   ("border-right",
1188                                          border_right_subproperties,
1189                                          parse_border_side);
1190   gtk_css_shorthand_property_register   ("border-bottom",
1191                                          border_bottom_subproperties,
1192                                          parse_border_side);
1193   gtk_css_shorthand_property_register   ("border-left",
1194                                          border_left_subproperties,
1195                                          parse_border_side);
1196   gtk_css_shorthand_property_register   ("border",
1197                                          border_subproperties,
1198                                          parse_border);
1199   gtk_css_shorthand_property_register   ("outline",
1200                                          outline_subproperties,
1201                                          parse_border_side);
1202   gtk_css_shorthand_property_register   ("background",
1203                                          background_subproperties,
1204                                          parse_background);
1205   gtk_css_shorthand_property_register   ("transition",
1206                                          transition_subproperties,
1207                                          parse_transition);
1208   gtk_css_shorthand_property_register   ("animation",
1209                                          animation_subproperties,
1210                                          parse_animation);
1211   gtk_css_shorthand_property_register   ("text-decoration",
1212                                          text_decoration_subproperties,
1213                                          parse_text_decoration);
1214   gtk_css_shorthand_property_register   ("font-variant",
1215                                          font_variant_subproperties,
1216                                          parse_font_variant);
1217 
1218   all_subproperties = get_all_subproperties ();
1219   gtk_css_shorthand_property_register   ("all",
1220                                          all_subproperties,
1221                                          parse_all);
1222   g_free (all_subproperties);
1223 }
1224