1 | /* Pango |
2 | * |
3 | * Copyright (C) 2021 Matthias Clasen |
4 | * |
5 | * This library is free software; you can redistribute it and/or |
6 | * modify it under the terms of the GNU Library General Public |
7 | * License as published by the Free Software Foundation; either |
8 | * version 2 of the License, or (at your option) any later version. |
9 | * |
10 | * This library is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | * Library General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU Library General Public |
16 | * License along with this library; if not, write to the |
17 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
18 | * Boston, MA 02111-1307, USA. |
19 | */ |
20 | |
21 | #include "config.h" |
22 | |
23 | #include <glib.h> |
24 | #include <pango/pangocairo.h> |
25 | #include <pango/pangocairo-fc.h> |
26 | #include <gio/gio.h> |
27 | |
28 | static void |
29 | test_serialize_attr_list (void) |
30 | { |
31 | const char *valid[] = { |
32 | "5 16 style italic" , |
33 | "0 10 foreground red, 5 15 weight bold, 0 200 font-desc \"Sans Small-Caps 10\"" , |
34 | "0 10 foreground red\n5 15 weight bold\n0 200 font-desc \"Sans Small-Caps 10\"" , |
35 | " 0 10 fallback false,\n 5 15 weight semilight\n\n \n \n" , |
36 | "0 100 font-desc \"Cantarell, Sans, Italic Ultra-Light 64\", 10 11 weight 100" , |
37 | "0 -1 size 10" , |
38 | "0 1 weight 700, 2 4 weight book" , |
39 | "0 200 rise 100\n5 15 family Times\n10 11 size 10240\n11 100 fallback 0\n30 60 stretch 2\n" , |
40 | "" |
41 | }; |
42 | const char *roundtripped[] = { |
43 | "5 16 style italic" , |
44 | "0 10 foreground #ffff00000000\n5 15 weight bold\n0 200 font-desc \"Sans Small-Caps 10\"" , |
45 | "0 10 foreground #ffff00000000\n5 15 weight bold\n0 200 font-desc \"Sans Small-Caps 10\"" , |
46 | "0 10 fallback false\n5 15 weight semilight" , |
47 | "0 100 font-desc \"Cantarell,Sans Ultra-Light Italic 64\"\n10 11 weight thin" , |
48 | "0 4294967295 size 10" , |
49 | "0 1 weight bold\n2 4 weight book" , |
50 | "0 200 rise 100\n5 15 family Times\n10 11 size 10240\n11 100 fallback false\n30 60 stretch condensed" , |
51 | "" |
52 | }; |
53 | const char *invalid[] = { |
54 | "not an attr list" , |
55 | "0 -1 FOREGROUND xyz" , |
56 | ",,bla.wewq" , |
57 | }; |
58 | |
59 | for (int i = 0; i < G_N_ELEMENTS (valid); i++) |
60 | { |
61 | PangoAttrList *attrs; |
62 | char *str; |
63 | |
64 | attrs = pango_attr_list_from_string (text: valid[i]); |
65 | g_assert_nonnull (attrs); |
66 | str = pango_attr_list_to_string (list: attrs); |
67 | g_assert_cmpstr (str, ==, roundtripped[i]); |
68 | g_free (mem: str); |
69 | pango_attr_list_unref (list: attrs); |
70 | } |
71 | |
72 | for (int i = 0; i < G_N_ELEMENTS (invalid); i++) |
73 | { |
74 | PangoAttrList *attrs; |
75 | |
76 | attrs = pango_attr_list_from_string (text: invalid[i]); |
77 | g_assert_null (attrs); |
78 | } |
79 | } |
80 | |
81 | static void |
82 | test_serialize_tab_array (void) |
83 | { |
84 | const char *valid[] = { |
85 | "0 10 100 200 400" , |
86 | "0px 10px 100px 200px 400px" , |
87 | " 0 10 " , |
88 | "20 10" , |
89 | "left:10px right:20px center:30px decimal:40px" , |
90 | "decimal:10240:94" , |
91 | "" |
92 | }; |
93 | const char *roundtripped[] = { |
94 | "0\n10\n100\n200\n400" , |
95 | "0px\n10px\n100px\n200px\n400px" , |
96 | "0\n10" , |
97 | "20\n10" , |
98 | "10px\nright:20px\ncenter:30px\ndecimal:40px" , |
99 | "decimal:10240:94" , |
100 | "" |
101 | }; |
102 | const char *invalid[] = { |
103 | "not a tabarray" , |
104 | "-10:-20" , |
105 | "10ps 20pu" , |
106 | "10, 20" , |
107 | "10 20px 30" , |
108 | }; |
109 | |
110 | for (int i = 0; i < G_N_ELEMENTS (valid); i++) |
111 | { |
112 | PangoTabArray *tabs; |
113 | char *str; |
114 | |
115 | tabs = pango_tab_array_from_string (text: valid[i]); |
116 | g_assert_nonnull (tabs); |
117 | str = pango_tab_array_to_string (tab_array: tabs); |
118 | g_assert_cmpstr (str, ==, roundtripped[i]); |
119 | g_free (mem: str); |
120 | pango_tab_array_free (tab_array: tabs); |
121 | } |
122 | |
123 | for (int i = 0; i < G_N_ELEMENTS (invalid); i++) |
124 | { |
125 | PangoTabArray *tabs; |
126 | |
127 | tabs = pango_tab_array_from_string (text: invalid[i]); |
128 | g_assert_null (tabs); |
129 | } |
130 | } |
131 | |
132 | static void |
133 | test_serialize_font (void) |
134 | { |
135 | PangoContext *context; |
136 | PangoFontDescription *desc; |
137 | PangoFont *font; |
138 | PangoFont *font2; |
139 | GBytes *bytes; |
140 | GBytes *bytes2; |
141 | GError *error = NULL; |
142 | const char *expected = |
143 | "{\n" |
144 | " \"description\" : \"Cantarell 20 @wght=600\",\n" |
145 | " \"checksum\" : \"5bcb6ee14ee9d210b2e91d643de1fe456e9d1aea770983fdb05951545efebbe2\",\n" |
146 | " \"variations\" : {\n" |
147 | " \"wght\" : 5583\n" |
148 | " },\n" |
149 | " \"matrix\" : [\n" |
150 | " 1,\n" |
151 | " -0,\n" |
152 | " -0,\n" |
153 | " 1,\n" |
154 | " 0,\n" |
155 | " 0\n" |
156 | " ]\n" |
157 | "}" ; |
158 | |
159 | context = pango_font_map_create_context (fontmap: pango_cairo_font_map_get_default ()); |
160 | desc = pango_font_description_from_string (str: "Cantarell Italic 20 @wght=600" ); |
161 | font = pango_context_load_font (context, desc); |
162 | |
163 | bytes = pango_font_serialize (font); |
164 | g_assert_cmpstr (g_bytes_get_data (bytes, NULL), ==, expected); |
165 | |
166 | font2 = pango_font_deserialize (context, bytes, error: &error); |
167 | g_assert_no_error (error); |
168 | g_assert_nonnull (font2); |
169 | |
170 | bytes2 = pango_font_serialize (font: font2); |
171 | g_assert_true (g_bytes_equal (bytes2, bytes)); |
172 | |
173 | g_object_unref (object: font); |
174 | g_object_unref (object: font2); |
175 | pango_font_description_free (desc); |
176 | |
177 | g_bytes_unref (bytes); |
178 | g_bytes_unref (bytes: bytes2); |
179 | g_object_unref (object: context); |
180 | } |
181 | |
182 | static void |
183 | test_serialize_layout_minimal (void) |
184 | { |
185 | const char *test = |
186 | "{\n" |
187 | " \"text\" : \"Almost nothing\"\n" |
188 | "}\n" ; |
189 | |
190 | PangoContext *context; |
191 | GBytes *bytes; |
192 | PangoLayout *layout; |
193 | GError *error = NULL; |
194 | GBytes *out_bytes; |
195 | const char *str; |
196 | |
197 | context = pango_font_map_create_context (fontmap: pango_cairo_font_map_get_default ()); |
198 | |
199 | bytes = g_bytes_new_static (data: test, size: strlen (s: test) + 1); |
200 | |
201 | layout = pango_layout_deserialize (context, bytes, flags: PANGO_LAYOUT_DESERIALIZE_DEFAULT, error: &error); |
202 | g_assert_no_error (error); |
203 | g_assert_true (PANGO_IS_LAYOUT (layout)); |
204 | g_assert_cmpstr (pango_layout_get_text (layout), ==, "Almost nothing" ); |
205 | g_assert_null (pango_layout_get_attributes (layout)); |
206 | g_assert_null (pango_layout_get_tabs (layout)); |
207 | g_assert_null (pango_layout_get_font_description (layout)); |
208 | g_assert_cmpint (pango_layout_get_alignment (layout), ==, PANGO_ALIGN_LEFT); |
209 | g_assert_cmpint (pango_layout_get_width (layout), ==, -1); |
210 | |
211 | out_bytes = pango_layout_serialize (layout, flags: PANGO_LAYOUT_SERIALIZE_DEFAULT); |
212 | str = g_bytes_get_data (bytes: out_bytes, NULL); |
213 | |
214 | g_assert_cmpstr (str, ==, test); |
215 | |
216 | g_bytes_unref (bytes: out_bytes); |
217 | |
218 | g_object_unref (object: layout); |
219 | g_bytes_unref (bytes); |
220 | g_object_unref (object: context); |
221 | } |
222 | |
223 | static void |
224 | test_serialize_layout_valid (void) |
225 | { |
226 | const char *test = |
227 | "{\n" |
228 | " \"text\" : \"Some fun with layouts!\",\n" |
229 | " \"attributes\" : [\n" |
230 | " {\n" |
231 | " \"end\" : 4,\n" |
232 | " \"type\" : \"foreground\",\n" |
233 | " \"value\" : \"#ffff00000000\"\n" |
234 | " },\n" |
235 | " {\n" |
236 | " \"start\" : 5,\n" |
237 | " \"end\" : 8,\n" |
238 | " \"type\" : \"foreground\",\n" |
239 | " \"value\" : \"#000080800000\"\n" |
240 | " },\n" |
241 | " {\n" |
242 | " \"start\" : 14,\n" |
243 | " \"type\" : \"foreground\",\n" |
244 | " \"value\" : \"#ffff0000ffff\"\n" |
245 | " }\n" |
246 | " ],\n" |
247 | " \"font\" : \"Sans Bold 32\",\n" |
248 | " \"tabs\" : {\n" |
249 | " \"positions-in-pixels\" : true,\n" |
250 | " \"positions\" : [\n" |
251 | " {\n" |
252 | " \"position\" : 0,\n" |
253 | " \"alignment\" : \"left\",\n" |
254 | " \"decimal-point\" : 0\n" |
255 | " },\n" |
256 | " {\n" |
257 | " \"position\" : 50,\n" |
258 | " \"alignment\" : \"center\",\n" |
259 | " \"decimal-point\" : 0\n" |
260 | " },\n" |
261 | " {\n" |
262 | " \"position\" : 100,\n" |
263 | " \"alignment\" : \"decimal\",\n" |
264 | " \"decimal-point\" : 94\n" |
265 | " }\n" |
266 | " ]\n" |
267 | " },\n" |
268 | " \"alignment\" : \"center\",\n" |
269 | " \"width\" : 350000,\n" |
270 | " \"line-spacing\" : 1.5\n" |
271 | "}\n" ; |
272 | |
273 | PangoContext *context; |
274 | GBytes *bytes; |
275 | PangoLayout *layout; |
276 | PangoTabArray *tabs; |
277 | GError *error = NULL; |
278 | GBytes *out_bytes; |
279 | char *s; |
280 | |
281 | context = pango_font_map_create_context (fontmap: pango_cairo_font_map_get_default ()); |
282 | |
283 | bytes = g_bytes_new_static (data: test, size: strlen (s: test) + 1); |
284 | |
285 | layout = pango_layout_deserialize (context, bytes, flags: PANGO_LAYOUT_DESERIALIZE_DEFAULT, error: &error); |
286 | g_assert_no_error (error); |
287 | g_assert_true (PANGO_IS_LAYOUT (layout)); |
288 | g_assert_cmpstr (pango_layout_get_text (layout), ==, "Some fun with layouts!" ); |
289 | g_assert_nonnull (pango_layout_get_attributes (layout)); |
290 | tabs = pango_layout_get_tabs (layout); |
291 | g_assert_nonnull (tabs); |
292 | pango_tab_array_free (tab_array: tabs); |
293 | s = pango_font_description_to_string (desc: pango_layout_get_font_description (layout)); |
294 | g_assert_cmpstr (s, ==, "Sans Bold 32" ); |
295 | g_free (mem: s); |
296 | g_assert_cmpint (pango_layout_get_alignment (layout), ==, PANGO_ALIGN_CENTER); |
297 | g_assert_cmpint (pango_layout_get_width (layout), ==, 350000); |
298 | g_assert_cmpfloat_with_epsilon (pango_layout_get_line_spacing (layout), 1.5, 0.0001); |
299 | |
300 | out_bytes = pango_layout_serialize (layout, flags: PANGO_LAYOUT_SERIALIZE_DEFAULT); |
301 | |
302 | if (strcmp (s1: g_bytes_get_data (bytes: out_bytes, NULL), s2: g_bytes_get_data (bytes, NULL)) != 0) |
303 | { |
304 | g_print (format: "expected:\n%s\ngot:\n%s\n" , |
305 | (char *)g_bytes_get_data (bytes, NULL), |
306 | (char *)g_bytes_get_data (bytes: out_bytes, NULL)); |
307 | } |
308 | |
309 | g_assert_cmpstr (g_bytes_get_data (out_bytes, NULL), ==, g_bytes_get_data (bytes, NULL)); |
310 | |
311 | g_bytes_unref (bytes: out_bytes); |
312 | g_bytes_unref (bytes); |
313 | |
314 | g_object_unref (object: layout); |
315 | g_object_unref (object: context); |
316 | } |
317 | |
318 | static void |
319 | test_serialize_layout_context (void) |
320 | { |
321 | const char *test = |
322 | "{\n" |
323 | " \"context\" : {\n" |
324 | " \"base-gravity\" : \"east\",\n" |
325 | " \"language\" : \"de-de\",\n" |
326 | " \"round-glyph-positions\" : false\n" |
327 | " },\n" |
328 | " \"text\" : \"Some fun with layouts!\"\n" |
329 | "}\n" ; |
330 | |
331 | PangoContext *context; |
332 | GBytes *bytes; |
333 | PangoLayout *layout; |
334 | GError *error = NULL; |
335 | |
336 | context = pango_font_map_create_context (fontmap: pango_cairo_font_map_get_default ()); |
337 | |
338 | bytes = g_bytes_new_static (data: test, size: strlen (s: test) + 1); |
339 | |
340 | layout = pango_layout_deserialize (context, bytes, flags: PANGO_LAYOUT_DESERIALIZE_CONTEXT, error: &error); |
341 | g_assert_no_error (error); |
342 | g_assert_true (PANGO_IS_LAYOUT (layout)); |
343 | g_assert_cmpstr (pango_layout_get_text (layout), ==, "Some fun with layouts!" ); |
344 | |
345 | g_assert_cmpint (pango_context_get_base_gravity (context), ==, PANGO_GRAVITY_EAST); |
346 | g_assert_true (pango_context_get_language (context) == pango_language_from_string ("de-de" )); |
347 | g_assert_false (pango_context_get_round_glyph_positions (context)); |
348 | |
349 | g_object_unref (object: layout); |
350 | g_bytes_unref (bytes); |
351 | g_object_unref (object: context); |
352 | } |
353 | |
354 | static void |
355 | test_serialize_layout_invalid (void) |
356 | { |
357 | struct { |
358 | const char *json; |
359 | int expected_error; |
360 | } test[] = { |
361 | { |
362 | "{\n" |
363 | " \"attributes\" : [\n" |
364 | " {\n" |
365 | " \"type\" : \"caramba\"\n" |
366 | " }\n" |
367 | " ]\n" |
368 | "}\n" , |
369 | PANGO_LAYOUT_DESERIALIZE_INVALID_VALUE |
370 | }, |
371 | { |
372 | "{\n" |
373 | " \"attributes\" : [\n" |
374 | " {\n" |
375 | " \"type\" : \"weight\"\n" |
376 | " }\n" |
377 | " ]\n" |
378 | "}\n" , |
379 | PANGO_LAYOUT_DESERIALIZE_MISSING_VALUE |
380 | }, |
381 | { |
382 | "{\n" |
383 | " \"attributes\" : [\n" |
384 | " {\n" |
385 | " \"type\" : \"alignment\",\n" |
386 | " \"value\" : \"nonsense\"\n" |
387 | " }\n" |
388 | " ]\n" |
389 | "}\n" , |
390 | PANGO_LAYOUT_DESERIALIZE_INVALID_VALUE |
391 | }, |
392 | { |
393 | "{\n" |
394 | " \"alignment\" : \"nonsense\"\n" |
395 | "}\n" , |
396 | PANGO_LAYOUT_DESERIALIZE_INVALID_VALUE |
397 | }, |
398 | { |
399 | "{\n" |
400 | " \"attributes\" : {\n" |
401 | " \"name\" : \"This is wrong\"\n" |
402 | " }\n" |
403 | "}\n" , |
404 | 0, |
405 | } |
406 | }; |
407 | |
408 | PangoContext *context; |
409 | |
410 | context = pango_font_map_create_context (fontmap: pango_cairo_font_map_get_default ()); |
411 | |
412 | for (int i = 0; i < G_N_ELEMENTS (test); i++) |
413 | { |
414 | GBytes *bytes; |
415 | PangoLayout *layout; |
416 | GError *error = NULL; |
417 | |
418 | bytes = g_bytes_new_static (data: test[i].json, size: strlen (s: test[i].json) + 1); |
419 | layout = pango_layout_deserialize (context, bytes, flags: PANGO_LAYOUT_DESERIALIZE_DEFAULT, error: &error); |
420 | g_assert_null (layout); |
421 | if (test[i].expected_error) |
422 | g_assert_error (error, PANGO_LAYOUT_DESERIALIZE_ERROR, test[i].expected_error); |
423 | else |
424 | g_assert_nonnull (error); |
425 | g_bytes_unref (bytes); |
426 | g_clear_error (err: &error); |
427 | } |
428 | |
429 | g_object_unref (object: context); |
430 | } |
431 | |
432 | static void |
433 | install_fonts (void) |
434 | { |
435 | char *dir; |
436 | FcConfig *config; |
437 | PangoFontMap *map; |
438 | char *path; |
439 | gsize len; |
440 | char *conf; |
441 | |
442 | dir = g_test_build_filename (file_type: G_TEST_DIST, first_path: "fonts" , NULL); |
443 | |
444 | map = g_object_new (PANGO_TYPE_CAIRO_FC_FONT_MAP, NULL); |
445 | |
446 | config = FcConfigCreate (); |
447 | |
448 | path = g_test_build_filename (file_type: G_TEST_DIST, first_path: "fonts/fonts.conf" , NULL); |
449 | g_file_get_contents (filename: path, contents: &conf, length: &len, NULL); |
450 | |
451 | if (!FcConfigParseAndLoadFromMemory (config, buffer: (const FcChar8 *) conf, TRUE)) |
452 | g_error ("Failed to parse fontconfig configuration" ); |
453 | |
454 | g_free (mem: conf); |
455 | g_free (mem: path); |
456 | |
457 | FcConfigAppFontAddDir (config, dir: (const FcChar8 *) dir); |
458 | pango_fc_font_map_set_config (PANGO_FC_FONT_MAP (map), fcconfig: config); |
459 | FcConfigDestroy (config); |
460 | |
461 | pango_cairo_font_map_set_default (PANGO_CAIRO_FONT_MAP (map)); |
462 | |
463 | g_object_unref (object: map); |
464 | |
465 | g_free (mem: dir); |
466 | } |
467 | |
468 | int |
469 | main (int argc, char *argv[]) |
470 | { |
471 | g_test_init (argc: &argc, argv: &argv, NULL); |
472 | |
473 | install_fonts (); |
474 | |
475 | g_test_add_func (testpath: "/serialize/attr-list" , test_func: test_serialize_attr_list); |
476 | g_test_add_func (testpath: "/serialize/tab-array" , test_func: test_serialize_tab_array); |
477 | g_test_add_func (testpath: "/serialize/font" , test_func: test_serialize_font); |
478 | g_test_add_func (testpath: "/serialize/layout/minimal" , test_func: test_serialize_layout_minimal); |
479 | g_test_add_func (testpath: "/serialize/layout/valid" , test_func: test_serialize_layout_valid); |
480 | g_test_add_func (testpath: "/serialize/layout/context" , test_func: test_serialize_layout_context); |
481 | g_test_add_func (testpath: "/serialize/layout/invalid" , test_func: test_serialize_layout_invalid); |
482 | |
483 | return g_test_run (); |
484 | } |
485 | |