1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2017 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 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  * Author: Matthias Clasen
18  */
19 
20 #include "config.h"
21 
22 #include <gtk/css/gtkcss.h>
23 #include "gtk/css/gtkcsstokenizerprivate.h"
24 #include "gtk/css/gtkcssparserprivate.h"
25 #include "gtkcssnumbervalueprivate.h"
26 #include "gtkcssfontvariationsvalueprivate.h"
27 
28 struct _GtkCssValue {
29   GTK_CSS_VALUE_BASE
30   GHashTable *axes;
31 };
32 
33 static GtkCssValue *default_font_variations;
34 
35 static GtkCssValue *gtk_css_font_variations_value_new_empty (void);
36 
37 static void
gtk_css_font_variations_value_add_axis(GtkCssValue * value,const char * name,GtkCssValue * coord)38 gtk_css_font_variations_value_add_axis (GtkCssValue *value,
39                                         const char  *name,
40                                         GtkCssValue *coord)
41 {
42   g_hash_table_insert (value->axes, g_strdup (name), coord);
43 }
44 
45 
46 static void
gtk_css_value_font_variations_free(GtkCssValue * value)47 gtk_css_value_font_variations_free (GtkCssValue *value)
48 {
49   g_hash_table_unref (value->axes);
50 
51   g_slice_free (GtkCssValue, value);
52 }
53 
54 static GtkCssValue *
gtk_css_value_font_variations_compute(GtkCssValue * specified,guint property_id,GtkStyleProvider * provider,GtkCssStyle * style,GtkCssStyle * parent_style)55 gtk_css_value_font_variations_compute (GtkCssValue      *specified,
56                                        guint             property_id,
57                                        GtkStyleProvider *provider,
58                                        GtkCssStyle      *style,
59                                        GtkCssStyle      *parent_style)
60 {
61   return _gtk_css_value_ref (specified);
62 }
63 
64 static gboolean
gtk_css_value_font_variations_equal(const GtkCssValue * value1,const GtkCssValue * value2)65 gtk_css_value_font_variations_equal (const GtkCssValue *value1,
66                                      const GtkCssValue *value2)
67 {
68   gpointer name, coord1, coord2;
69   GHashTableIter iter;
70 
71   if (g_hash_table_size (value1->axes) != g_hash_table_size (value2->axes))
72     return FALSE;
73 
74   g_hash_table_iter_init (&iter, value1->axes);
75   while (g_hash_table_iter_next (&iter, &name, &coord1))
76     {
77       coord2 = g_hash_table_lookup (value2->axes, name);
78       if (coord2 == NULL)
79         return FALSE;
80 
81       if (!_gtk_css_value_equal (coord1, coord2))
82         return FALSE;
83     }
84 
85   return TRUE;
86 }
87 
88 static GtkCssValue *
gtk_css_value_font_variations_transition(GtkCssValue * start,GtkCssValue * end,guint property_id,double progress)89 gtk_css_value_font_variations_transition (GtkCssValue *start,
90                                           GtkCssValue *end,
91                                           guint        property_id,
92                                           double       progress)
93 {
94   const char *name;
95   GtkCssValue *start_coord, *end_coord;
96   GHashTableIter iter;
97   GtkCssValue *result, *transition;
98 
99   /* XXX: For value that are only in start or end but not both,
100    * we don't transition but just keep the value.
101    * That causes an abrupt transition to the new value at the end.
102    */
103 
104   result = gtk_css_font_variations_value_new_empty ();
105 
106   g_hash_table_iter_init (&iter, start->axes);
107   while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&start_coord))
108     {
109       end_coord = g_hash_table_lookup (end->axes, name);
110       if (end_coord == NULL)
111         transition = _gtk_css_value_ref (start_coord);
112       else
113         transition = _gtk_css_value_transition (start_coord, end_coord, property_id, progress);
114 
115       gtk_css_font_variations_value_add_axis (result, name, transition);
116     }
117 
118   g_hash_table_iter_init (&iter, end->axes);
119   while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&end_coord))
120     {
121       start_coord = g_hash_table_lookup (start->axes, name);
122       if (start_coord != NULL)
123         continue;
124 
125       gtk_css_font_variations_value_add_axis (result, name, _gtk_css_value_ref (end_coord));
126     }
127 
128   return result;
129 }
130 
131 static void
gtk_css_value_font_variations_print(const GtkCssValue * value,GString * string)132 gtk_css_value_font_variations_print (const GtkCssValue *value,
133                                      GString           *string)
134 {
135   GHashTableIter iter;
136   const char *name;
137   GtkCssValue *coord;
138   gboolean first = TRUE;
139 
140   if (value == default_font_variations)
141     {
142       g_string_append (string, "normal");
143       return;
144     }
145 
146   g_hash_table_iter_init (&iter, value->axes);
147   while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&coord))
148     {
149       if (first)
150         first = FALSE;
151       else
152         g_string_append (string, ", ");
153       g_string_append_printf (string, "\"%s\" ", name);
154       _gtk_css_value_print (coord, string);
155     }
156 }
157 
158 static const GtkCssValueClass GTK_CSS_VALUE_FONT_VARIATIONS = {
159   "GtkCssFontVariationsValue",
160   gtk_css_value_font_variations_free,
161   gtk_css_value_font_variations_compute,
162   gtk_css_value_font_variations_equal,
163   gtk_css_value_font_variations_transition,
164   NULL,
165   NULL,
166   gtk_css_value_font_variations_print
167 };
168 
169 static GtkCssValue *
gtk_css_font_variations_value_new_empty(void)170 gtk_css_font_variations_value_new_empty (void)
171 {
172   GtkCssValue *result;
173 
174   result = _gtk_css_value_new (GtkCssValue, &GTK_CSS_VALUE_FONT_VARIATIONS);
175   result->axes = g_hash_table_new_full (g_str_hash, g_str_equal,
176                                         g_free,
177                                         (GDestroyNotify) _gtk_css_value_unref);
178   result->is_computed = TRUE;
179 
180   return result;
181 }
182 
183 GtkCssValue *
gtk_css_font_variations_value_new_default(void)184 gtk_css_font_variations_value_new_default (void)
185 {
186   if (default_font_variations == NULL)
187     default_font_variations = gtk_css_font_variations_value_new_empty ();
188 
189   return _gtk_css_value_ref (default_font_variations);
190 }
191 
192 static gboolean
is_valid_opentype_tag(const char * s)193 is_valid_opentype_tag (const char *s)
194 {
195   if (strlen (s) != 4)
196     return FALSE;
197 
198   if (s[0] < 0x20 || s[0] > 0x7e ||
199       s[1] < 0x20 || s[1] > 0x7e ||
200       s[2] < 0x20 || s[2] > 0x7e ||
201       s[3] < 0x20 || s[3] > 0x7e)
202     return FALSE;
203 
204   return TRUE;
205 }
206 
207 GtkCssValue *
gtk_css_font_variations_value_parse(GtkCssParser * parser)208 gtk_css_font_variations_value_parse (GtkCssParser *parser)
209 {
210   GtkCssValue *result, *coord;
211   char *name;
212 
213   if (gtk_css_parser_try_ident (parser, "normal"))
214     return gtk_css_font_variations_value_new_default ();
215 
216   result = gtk_css_font_variations_value_new_empty ();
217 
218   do {
219     name = gtk_css_parser_consume_string (parser);
220     if (name == NULL)
221       {
222         _gtk_css_value_unref (result);
223         return NULL;
224       }
225 
226     if (!is_valid_opentype_tag (name))
227       {
228         gtk_css_parser_error_value (parser, "Not a valid OpenType tag.");
229         g_free (name);
230         _gtk_css_value_unref (result);
231         return NULL;
232       }
233 
234     coord = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER);
235     if (coord == NULL)
236       {
237         g_free (name);
238         _gtk_css_value_unref (result);
239         return NULL;
240       }
241 
242     gtk_css_font_variations_value_add_axis (result, name, coord);
243     g_free (name);
244   } while (gtk_css_parser_try_token (parser, GTK_CSS_TOKEN_COMMA));
245 
246   return result;
247 }
248 
249 char *
gtk_css_font_variations_value_get_variations(GtkCssValue * value)250 gtk_css_font_variations_value_get_variations (GtkCssValue *value)
251 {
252   GtkCssValue *coord;
253   GHashTableIter iter;
254   gboolean first = TRUE;
255   const char *name;
256   GString *string;
257 
258   g_return_val_if_fail (value->class == &GTK_CSS_VALUE_FONT_VARIATIONS, NULL);
259 
260   if (value == default_font_variations)
261     return NULL;
262 
263   string = g_string_new ("");
264 
265   g_hash_table_iter_init (&iter, value->axes);
266   while (g_hash_table_iter_next (&iter, (gpointer *)&name, (gpointer *)&coord))
267     {
268       if (first)
269         first = FALSE;
270       else
271         g_string_append (string, ",");
272       g_string_append_printf (string, "%s=%g", name,
273                               _gtk_css_number_value_get (coord, 100));
274     }
275 
276   return g_string_free (string, FALSE);
277 }
278