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