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 |
46 | value_is_done_parsing (GtkCssParser *parser) |
47 | { |
48 | return gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_EOF) || |
49 | gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_COMMA) || |
50 | gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_SEMICOLON) || |
51 | gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_CLOSE_CURLY); |
52 | } |
53 | |
54 | static gboolean |
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 (self: parser, format: "Expected a length" ); |
75 | return FALSE; |
76 | } |
77 | |
78 | for (; i < 4; i++) |
79 | { |
80 | values[i] = _gtk_css_value_ref (value: values[(i - 1) >> 1]); |
81 | } |
82 | |
83 | return TRUE; |
84 | } |
85 | |
86 | static gboolean |
87 | parse_margin (GtkCssShorthandProperty *shorthand, |
88 | GtkCssValue **values, |
89 | GtkCssParser *parser) |
90 | { |
91 | return parse_four_numbers (shorthand, |
92 | values, |
93 | parser, |
94 | flags: GTK_CSS_PARSE_LENGTH); |
95 | } |
96 | |
97 | static gboolean |
98 | parse_padding (GtkCssShorthandProperty *shorthand, |
99 | GtkCssValue **values, |
100 | GtkCssParser *parser) |
101 | { |
102 | return parse_four_numbers (shorthand, |
103 | values, |
104 | parser, |
105 | flags: GTK_CSS_POSITIVE_ONLY |
106 | | GTK_CSS_PARSE_LENGTH); |
107 | } |
108 | |
109 | static gboolean |
110 | parse_border_width (GtkCssShorthandProperty *shorthand, |
111 | GtkCssValue **values, |
112 | GtkCssParser *parser) |
113 | { |
114 | return parse_four_numbers (shorthand, |
115 | values, |
116 | parser, |
117 | flags: GTK_CSS_POSITIVE_ONLY |
118 | | GTK_CSS_PARSE_LENGTH); |
119 | } |
120 | |
121 | static gboolean |
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 | flags: 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 (self: parser, format: "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 (value: x[(i - 1) >> 1]); |
152 | |
153 | if (gtk_css_parser_try_delim (self: parser, codepoint: '/')) |
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 | flags: 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 (self: parser, format: "Expected a number" ); |
170 | goto fail; |
171 | } |
172 | |
173 | for (; i < 4; i++) |
174 | y[i] = _gtk_css_value_ref (value: y[(i - 1) >> 1]); |
175 | } |
176 | else |
177 | { |
178 | for (i = 0; i < 4; i++) |
179 | y[i] = _gtk_css_value_ref (value: x[i]); |
180 | } |
181 | |
182 | for (i = 0; i < 4; i++) |
183 | { |
184 | values[i] = _gtk_css_corner_value_new (x: x[i], y: 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 (value: x[i]); |
194 | if (y[i]) |
195 | _gtk_css_value_unref (value: y[i]); |
196 | } |
197 | return FALSE; |
198 | } |
199 | |
200 | static gboolean |
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 (value: values[(i - 1) >> 1]); |
220 | } |
221 | |
222 | return TRUE; |
223 | } |
224 | |
225 | static gboolean |
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 (self: parser, format: "Expected a border style" ); |
242 | return FALSE; |
243 | } |
244 | |
245 | for (; i < 4; i++) |
246 | values[i] = _gtk_css_value_ref (value: values[(i - 1) >> 1]); |
247 | |
248 | return TRUE; |
249 | } |
250 | |
251 | static gboolean |
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 (self: parser, ident: "none" ) || |
260 | _gtk_css_image_can_parse (parser))) |
261 | { |
262 | GtkCssImage *image; |
263 | |
264 | if (gtk_css_parser_try_ident (self: parser, ident: "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 | flags: 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 (self: parser, codepoint: '/')) |
292 | { |
293 | values[2] = _gtk_css_border_value_parse (parser, |
294 | flags: 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 |
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 | flags: 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 |
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 | flags: GTK_CSS_POSITIVE_ONLY |
371 | | GTK_CSS_PARSE_LENGTH); |
372 | if (values[0] == NULL) |
373 | return FALSE; |
374 | values[1] = _gtk_css_value_ref (value: values[0]); |
375 | values[2] = _gtk_css_value_ref (value: values[0]); |
376 | values[3] = _gtk_css_value_ref (value: 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 (value: values[4]); |
382 | values[6] = _gtk_css_value_ref (value: values[4]); |
383 | values[7] = _gtk_css_value_ref (value: 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 (value: values[8]); |
392 | values[10] = _gtk_css_value_ref (value: values[8]); |
393 | values[11] = _gtk_css_value_ref (value: 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 * |
414 | _gtk_css_font_variant_value_try_parse (GtkCssParser *parser) |
415 | { |
416 | if (gtk_css_parser_try_ident (self: parser, ident: "normal" )) |
417 | return _gtk_css_ident_value_new (ident: "normal" ); |
418 | else if (gtk_css_parser_try_ident (self: parser, ident: "small-caps" )) |
419 | return _gtk_css_ident_value_new (ident: "small-caps" ); |
420 | return NULL; |
421 | } |
422 | |
423 | static gboolean |
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 | flags: 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 (value: 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 (number: values[3], one_hundred_percent: 100) < 1 || |
468 | _gtk_css_number_value_get (number: values[3], one_hundred_percent: 100) > 1000) |
469 | { |
470 | gtk_css_parser_error_value (self: parser, format: "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 |
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 (self: parser, ident: "none" ) || |
510 | _gtk_css_image_can_parse (parser))) |
511 | { |
512 | GtkCssImage *image; |
513 | |
514 | if (gtk_css_parser_try_ident (self: parser, ident: "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 (self: parser, codepoint: '/') && |
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 (value: _gtk_css_style_property_get_initial_value |
561 | (property: _gtk_css_shorthand_property_get_subproperty (shorthand, property: 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 (value: values[5]); |
580 | |
581 | return TRUE; |
582 | } |
583 | |
584 | static gboolean |
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, values: step_values, parser)) |
603 | { |
604 | for (i = 0; i < 6; i++) |
605 | { |
606 | g_ptr_array_set_free_func (array: arrays[i], element_free_func: (GDestroyNotify) _gtk_css_value_unref); |
607 | g_ptr_array_unref (array: 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 | property: _gtk_css_shorthand_property_get_subproperty (shorthand, property: i)); |
618 | step_values[i] = _gtk_css_value_ref (value: _gtk_css_array_value_get_nth (value: initial, i: 0)); |
619 | } |
620 | |
621 | g_ptr_array_add (array: arrays[i], data: step_values[i]); |
622 | step_values[i] = NULL; |
623 | } |
624 | } while (gtk_css_parser_try_token (self: parser, token_type: GTK_CSS_TOKEN_COMMA)); |
625 | |
626 | for (i = 0; i < 6; i++) |
627 | { |
628 | values[i] = _gtk_css_array_value_new_from_array (values: (GtkCssValue **) arrays[i]->pdata, n_values: arrays[i]->len); |
629 | g_ptr_array_unref (array: arrays[i]); |
630 | } |
631 | |
632 | values[6] = step_values[6]; |
633 | |
634 | return TRUE; |
635 | } |
636 | |
637 | static gboolean |
638 | has_transition_property (GtkCssParser *parser, |
639 | gpointer option_data, |
640 | gpointer user_data) |
641 | { |
642 | return gtk_css_parser_has_token (self: parser, token_type: GTK_CSS_TOKEN_IDENT); |
643 | } |
644 | |
645 | static gboolean |
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 |
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, flags: GTK_CSS_PARSE_TIME); |
666 | |
667 | return *value != NULL; |
668 | } |
669 | |
670 | static gboolean |
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 |
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 |
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, values: step_values, parser)) |
714 | { |
715 | for (i = 0; i < 4; i++) |
716 | { |
717 | g_ptr_array_set_free_func (array: arrays[i], element_free_func: (GDestroyNotify) _gtk_css_value_unref); |
718 | g_ptr_array_unref (array: 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 | property: _gtk_css_shorthand_property_get_subproperty (shorthand, property: i)); |
729 | step_values[i] = _gtk_css_value_ref (value: _gtk_css_array_value_get_nth (value: initial, i: 0)); |
730 | } |
731 | |
732 | g_ptr_array_add (array: arrays[i], data: step_values[i]); |
733 | step_values[i] = NULL; |
734 | } |
735 | } while (gtk_css_parser_try_token (self: parser, token_type: GTK_CSS_TOKEN_COMMA)); |
736 | |
737 | for (i = 0; i < 4; i++) |
738 | { |
739 | values[i] = _gtk_css_array_value_new_from_array (values: (GtkCssValue **) arrays[i]->pdata, n_values: arrays[i]->len); |
740 | g_ptr_array_unref (array: arrays[i]); |
741 | } |
742 | |
743 | return TRUE; |
744 | } |
745 | |
746 | static gboolean |
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 (self: parser, ident: "infinite" )) |
754 | { |
755 | values[1] = _gtk_css_number_value_new (HUGE_VAL, unit: 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 | flags: 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 |
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, values: step_values, parser)) |
831 | { |
832 | for (i = 0; i < 7; i++) |
833 | { |
834 | g_ptr_array_set_free_func (array: arrays[i], element_free_func: (GDestroyNotify) _gtk_css_value_unref); |
835 | g_ptr_array_unref (array: 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 | property: _gtk_css_shorthand_property_get_subproperty (shorthand, property: i)); |
846 | step_values[i] = _gtk_css_value_ref (value: _gtk_css_array_value_get_nth (value: initial, i: 0)); |
847 | } |
848 | |
849 | g_ptr_array_add (array: arrays[i], data: step_values[i]); |
850 | step_values[i] = NULL; |
851 | } |
852 | } while (gtk_css_parser_try_token (self: parser, token_type: GTK_CSS_TOKEN_COMMA)); |
853 | |
854 | for (i = 0; i < 7; i++) |
855 | { |
856 | values[i] = _gtk_css_array_value_new_from_array (values: (GtkCssValue **) arrays[i]->pdata, n_values: arrays[i]->len); |
857 | g_ptr_array_unref (array: arrays[i]); |
858 | } |
859 | |
860 | return TRUE; |
861 | } |
862 | |
863 | static gboolean |
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, base: line); |
875 | |
876 | if (parsed_line == 0 && line != 0) |
877 | { |
878 | gtk_css_parser_error_value (self: parser, format: "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 (self: parser, format: "Invalid combination of text-decoration-line values" ); |
914 | return FALSE; |
915 | } |
916 | } |
917 | |
918 | return TRUE; |
919 | } |
920 | |
921 | static gboolean |
922 | parse_font_variant (GtkCssShorthandProperty *shorthand, |
923 | GtkCssValue **values, |
924 | GtkCssParser *parser) |
925 | { |
926 | if (gtk_css_parser_try_ident (self: parser, ident: "normal" )) |
927 | { |
928 | /* all initial values */ |
929 | } |
930 | else if (gtk_css_parser_try_ident (self: parser, ident: "none" )) |
931 | { |
932 | /* all initial values, except for font-variant-ligatures */ |
933 | values[0] = _gtk_css_font_variant_ligature_value_new (ligatures: 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, base: ligatures); |
950 | if (parsed_ligature == 0 && ligatures != 0) |
951 | { |
952 | gtk_css_parser_error_value (self: parser, format: "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 (self: parser, format: "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, base: numeric); |
968 | if (parsed_numeric == 0 && numeric != 0) |
969 | { |
970 | gtk_css_parser_error_value (self: parser, format: "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 (self: parser, format: "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, base: east_asian); |
985 | if (parsed_east_asian == 0 && east_asian != 0) |
986 | { |
987 | gtk_css_parser_error_value (self: parser, format: "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 (self: parser, format: "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 (value: values[1]) == GTK_CSS_FONT_VARIANT_POSITION_NORMAL) |
1007 | { |
1008 | gtk_css_parser_error_value (self: parser, format: "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 (value: values[2]) == GTK_CSS_FONT_VARIANT_CAPS_NORMAL) |
1020 | { |
1021 | gtk_css_parser_error_value (self: parser, format: "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 (value: values[4]) == GTK_CSS_FONT_VARIANT_ALTERNATE_NORMAL) |
1034 | { |
1035 | gtk_css_parser_error_value (self: parser, format: "Unexpected alternate value" ); |
1036 | return FALSE; |
1037 | } |
1038 | goto found; |
1039 | } |
1040 | } |
1041 | |
1042 | gtk_css_parser_error_value (self: parser, format: "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 (self: parser, format: "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 (self: parser, format: "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 (self: parser, format: "Invalid combination of east asian values" ); |
1077 | return FALSE; |
1078 | } |
1079 | } |
1080 | } |
1081 | |
1082 | return TRUE; |
1083 | } |
1084 | |
1085 | static gboolean |
1086 | parse_all (GtkCssShorthandProperty *shorthand, |
1087 | GtkCssValue **values, |
1088 | GtkCssParser *parser) |
1089 | { |
1090 | gtk_css_parser_error_syntax (self: parser, format: "The 'all' property can only be set to 'initial', 'inherit' or 'unset'" ); |
1091 | return FALSE; |
1092 | } |
1093 | |
1094 | static void |
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 | first_property_name: "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 ** |
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 |
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 (name: "font" , |
1161 | subproperties: font_subproperties, |
1162 | parse_func: parse_font); |
1163 | gtk_css_shorthand_property_register (name: "margin" , |
1164 | subproperties: margin_subproperties, |
1165 | parse_func: parse_margin); |
1166 | gtk_css_shorthand_property_register (name: "padding" , |
1167 | subproperties: padding_subproperties, |
1168 | parse_func: parse_padding); |
1169 | gtk_css_shorthand_property_register (name: "border-width" , |
1170 | subproperties: border_width_subproperties, |
1171 | parse_func: parse_border_width); |
1172 | gtk_css_shorthand_property_register (name: "border-radius" , |
1173 | subproperties: border_radius_subproperties, |
1174 | parse_func: parse_border_radius); |
1175 | gtk_css_shorthand_property_register (name: "border-color" , |
1176 | subproperties: border_color_subproperties, |
1177 | parse_func: parse_border_color); |
1178 | gtk_css_shorthand_property_register (name: "border-style" , |
1179 | subproperties: border_style_subproperties, |
1180 | parse_func: parse_border_style); |
1181 | gtk_css_shorthand_property_register (name: "border-image" , |
1182 | subproperties: border_image_subproperties, |
1183 | parse_func: parse_border_image); |
1184 | gtk_css_shorthand_property_register (name: "border-top" , |
1185 | subproperties: border_top_subproperties, |
1186 | parse_func: parse_border_side); |
1187 | gtk_css_shorthand_property_register (name: "border-right" , |
1188 | subproperties: border_right_subproperties, |
1189 | parse_func: parse_border_side); |
1190 | gtk_css_shorthand_property_register (name: "border-bottom" , |
1191 | subproperties: border_bottom_subproperties, |
1192 | parse_func: parse_border_side); |
1193 | gtk_css_shorthand_property_register (name: "border-left" , |
1194 | subproperties: border_left_subproperties, |
1195 | parse_func: parse_border_side); |
1196 | gtk_css_shorthand_property_register (name: "border" , |
1197 | subproperties: border_subproperties, |
1198 | parse_func: parse_border); |
1199 | gtk_css_shorthand_property_register (name: "outline" , |
1200 | subproperties: outline_subproperties, |
1201 | parse_func: parse_border_side); |
1202 | gtk_css_shorthand_property_register (name: "background" , |
1203 | subproperties: background_subproperties, |
1204 | parse_func: parse_background); |
1205 | gtk_css_shorthand_property_register (name: "transition" , |
1206 | subproperties: transition_subproperties, |
1207 | parse_func: parse_transition); |
1208 | gtk_css_shorthand_property_register (name: "animation" , |
1209 | subproperties: animation_subproperties, |
1210 | parse_func: parse_animation); |
1211 | gtk_css_shorthand_property_register (name: "text-decoration" , |
1212 | subproperties: text_decoration_subproperties, |
1213 | parse_func: parse_text_decoration); |
1214 | gtk_css_shorthand_property_register (name: "font-variant" , |
1215 | subproperties: font_variant_subproperties, |
1216 | parse_func: parse_font_variant); |
1217 | |
1218 | all_subproperties = get_all_subproperties (); |
1219 | gtk_css_shorthand_property_register (name: "all" , |
1220 | subproperties: all_subproperties, |
1221 | parse_func: parse_all); |
1222 | g_free (mem: all_subproperties); |
1223 | } |
1224 | |