1 | /* Pango |
2 | * test-break.c: Test Pango line breaking |
3 | * |
4 | * Copyright (C) 2019 Red Hat, Inc |
5 | * |
6 | * This library is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Library General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2 of the License, or (at your option) any later version. |
10 | * |
11 | * This library is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Library General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Library General Public |
17 | * License along with this library; if not, write to the |
18 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
19 | * Boston, MA 02111-1307, USA. |
20 | */ |
21 | |
22 | #include <glib.h> |
23 | #include <string.h> |
24 | #include <locale.h> |
25 | |
26 | #ifndef G_OS_WIN32 |
27 | #include <unistd.h> |
28 | #endif |
29 | |
30 | #include "config.h" |
31 | #include <pango/pangocairo.h> |
32 | #include "test-common.h" |
33 | #include "validate-log-attrs.h" |
34 | |
35 | |
36 | static PangoContext *context; |
37 | |
38 | static gboolean opt_hex_chars; |
39 | |
40 | static gboolean |
41 | test_file (const gchar *filename, GString *string) |
42 | { |
43 | gchar *contents; |
44 | gsize length; |
45 | GError *error = NULL; |
46 | PangoLogAttr *attrs; |
47 | const PangoLogAttr *attrs2; |
48 | int len; |
49 | int len2; |
50 | char *p; |
51 | int i; |
52 | GString *s1, *s2, *s3, *s4, *s5, *s6; |
53 | int m; |
54 | char *test; |
55 | char *text; |
56 | PangoAttrList *attributes; |
57 | PangoLayout *layout; |
58 | PangoLayout *layout2; |
59 | |
60 | g_file_get_contents (filename, contents: &contents, length: &length, error: &error); |
61 | g_assert_no_error (error); |
62 | |
63 | test = contents; |
64 | |
65 | /* Skip initial comments */ |
66 | while (test[0] == '#') |
67 | test = strchr (s: test, c: '\n') + 1; |
68 | |
69 | length = strlen (s: test); |
70 | len = g_utf8_strlen (p: test, max: -1) + 1; |
71 | |
72 | pango_parse_markup (markup_text: test, length: -1, accel_marker: 0, attr_list: &attributes, text: &text, NULL, error: &error); |
73 | g_assert_no_error (error); |
74 | |
75 | layout = pango_layout_new (context); |
76 | pango_layout_set_text (layout, text, length); |
77 | pango_layout_set_attributes (layout, attrs: attributes); |
78 | |
79 | #if 0 |
80 | if (pango_layout_get_unknown_glyphs_count (layout) > 0) |
81 | { |
82 | char *msg = g_strdup_printf ("Missing glyphs - skipping %s. Maybe fonts are missing?" , filename); |
83 | if (g_test_initialized()) |
84 | g_test_skip (msg); |
85 | else |
86 | g_warning ("%s" , msg); |
87 | g_free (msg); |
88 | g_free (contents); |
89 | g_object_unref (layout); |
90 | pango_attr_list_unref (attributes); |
91 | g_free (text); |
92 | return FALSE; |
93 | } |
94 | #endif |
95 | |
96 | pango_layout_get_log_attrs (layout, attrs: &attrs, n_attrs: &len); |
97 | attrs2 = pango_layout_get_log_attrs_readonly (layout, n_attrs: &len2); |
98 | |
99 | g_assert_cmpint (len, ==, len2); |
100 | g_assert_true (memcmp (attrs, attrs2, sizeof (PangoLogAttr) * len) == 0); |
101 | if (!pango_validate_log_attrs (text, length, log_attrs: attrs, attrs_len: len, error: &error)) |
102 | { |
103 | g_warning ("%s: Log attrs invalid: %s" , filename, error->message); |
104 | // g_assert_not_reached (); |
105 | } |
106 | |
107 | layout2 = pango_layout_copy (src: layout); |
108 | attrs2 = pango_layout_get_log_attrs_readonly (layout: layout2, n_attrs: &len2); |
109 | |
110 | g_assert_cmpint (len, ==, len2); |
111 | g_assert_true (memcmp (attrs, attrs2, sizeof (PangoLogAttr) * len) == 0); |
112 | |
113 | g_object_unref (object: layout2); |
114 | |
115 | s1 = g_string_new (init: "Breaks: " ); |
116 | s2 = g_string_new (init: "Whitespace: " ); |
117 | s3 = g_string_new (init: "Sentences:" ); |
118 | s4 = g_string_new (init: "Words:" ); |
119 | s5 = g_string_new (init: "Graphemes:" ); |
120 | s6 = g_string_new (init: "Hyphens:" ); |
121 | |
122 | g_string_append (string, val: "Text: " ); |
123 | |
124 | m = MAX (MAX (MAX (s1->len, s2->len), MAX (s3->len, s4->len)), s5->len); |
125 | |
126 | g_string_append_printf (string: s1, format: "%*s" , (int)(m - s1->len), "" ); |
127 | g_string_append_printf (string: s2, format: "%*s" , (int)(m - s2->len), "" ); |
128 | g_string_append_printf (string: s3, format: "%*s" , (int)(m - s3->len), "" ); |
129 | g_string_append_printf (string: s4, format: "%*s" , (int)(m - s4->len), "" ); |
130 | g_string_append_printf (string: s5, format: "%*s" , (int)(m - s5->len), "" ); |
131 | g_string_append_printf (string: s6, format: "%*s" , (int)(m - s6->len), "" ); |
132 | g_string_append_printf (string, format: "%*s" , (int)(m - strlen (s: "Text: " )), "" ); |
133 | |
134 | for (i = 0, p = text; i < len; i++, p = g_utf8_next_char (p)) |
135 | { |
136 | PangoLogAttr log = attrs[i]; |
137 | int b = 0; |
138 | int w = 0; |
139 | int o = 0; |
140 | int s = 0; |
141 | int g = 0; |
142 | int h = 0; |
143 | |
144 | if (log.is_mandatory_break) |
145 | { |
146 | g_string_append (string: s1, val: "L" ); |
147 | b++; |
148 | } |
149 | else if (log.is_line_break) |
150 | { |
151 | g_string_append (string: s1, val: "l" ); |
152 | b++; |
153 | } |
154 | if (log.is_char_break) |
155 | { |
156 | g_string_append (string: s1, val: "c" ); |
157 | b++; |
158 | } |
159 | |
160 | if (log.is_expandable_space) |
161 | { |
162 | g_string_append (string: s2, val: "x" ); |
163 | w++; |
164 | } |
165 | else if (log.is_white) |
166 | { |
167 | g_string_append (string: s2, val: "w" ); |
168 | w++; |
169 | } |
170 | |
171 | if (log.is_sentence_boundary) |
172 | { |
173 | g_string_append (string: s3, val: "b" ); |
174 | s++; |
175 | } |
176 | if (log.is_sentence_start) |
177 | { |
178 | g_string_append (string: s3, val: "s" ); |
179 | s++; |
180 | } |
181 | if (log.is_sentence_end) |
182 | { |
183 | g_string_append (string: s3, val: "e" ); |
184 | s++; |
185 | } |
186 | |
187 | if (log.is_word_boundary) |
188 | { |
189 | g_string_append (string: s4, val: "b" ); |
190 | o++; |
191 | } |
192 | if (log.is_word_start) |
193 | { |
194 | g_string_append (string: s4, val: "s" ); |
195 | o++; |
196 | } |
197 | if (log.is_word_end) |
198 | { |
199 | g_string_append (string: s4, val: "e" ); |
200 | o++; |
201 | } |
202 | |
203 | if (log.is_cursor_position) |
204 | { |
205 | g_string_append (string: s5, val: "b" ); |
206 | g++; |
207 | } |
208 | |
209 | if (log.break_removes_preceding) |
210 | { |
211 | g_string_append (string: s6, val: "r" ); |
212 | h++; |
213 | } |
214 | if (log.break_inserts_hyphen) |
215 | { |
216 | g_string_append (string: s6, val: "i" ); |
217 | h++; |
218 | } |
219 | |
220 | m = MAX (MAX (MAX (b, w), MAX (o, s)), MAX (g, h)); |
221 | |
222 | g_string_append_printf (string, format: "%*s" , m, "" ); |
223 | g_string_append_printf (string: s1, format: "%*s" , m - b, "" ); |
224 | g_string_append_printf (string: s2, format: "%*s" , m - w, "" ); |
225 | g_string_append_printf (string: s3, format: "%*s" , m - s, "" ); |
226 | g_string_append_printf (string: s4, format: "%*s" , m - o, "" ); |
227 | g_string_append_printf (string: s5, format: "%*s" , m - g, "" ); |
228 | g_string_append_printf (string: s6, format: "%*s" , m - h, "" ); |
229 | |
230 | if (i < len - 1) |
231 | { |
232 | gunichar ch = g_utf8_get_char (p); |
233 | if (ch == 0x20) |
234 | { |
235 | g_string_append (string, val: "[ ]" ); |
236 | g_string_append (string: s1, val: " " ); |
237 | g_string_append (string: s2, val: " " ); |
238 | g_string_append (string: s3, val: " " ); |
239 | g_string_append (string: s4, val: " " ); |
240 | g_string_append (string: s5, val: " " ); |
241 | g_string_append (string: s6, val: " " ); |
242 | } |
243 | else if (!opt_hex_chars && |
244 | g_unichar_isgraph (c: ch) && |
245 | !(g_unichar_type (c: ch) == G_UNICODE_LINE_SEPARATOR || |
246 | g_unichar_type (c: ch) == G_UNICODE_PARAGRAPH_SEPARATOR)) |
247 | { |
248 | g_string_append_unichar (string, wc: 0x2066); // LRI |
249 | g_string_append_unichar (string, wc: ch); |
250 | g_string_append_unichar (string, wc: 0x2069); // PDI |
251 | g_string_append (string: s1, val: " " ); |
252 | g_string_append (string: s2, val: " " ); |
253 | g_string_append (string: s3, val: " " ); |
254 | g_string_append (string: s4, val: " " ); |
255 | g_string_append (string: s5, val: " " ); |
256 | g_string_append (string: s6, val: " " ); |
257 | } |
258 | else |
259 | { |
260 | char *str = g_strdup_printf (format: "[%#04x]" , ch); |
261 | g_string_append (string, val: str); |
262 | g_string_append_printf (string: s1, format: "%*s" , (int)strlen (s: str), "" ); |
263 | g_string_append_printf (string: s2, format: "%*s" , (int)strlen (s: str), "" ); |
264 | g_string_append_printf (string: s3, format: "%*s" , (int)strlen (s: str), "" ); |
265 | g_string_append_printf (string: s4, format: "%*s" , (int)strlen (s: str), "" ); |
266 | g_string_append_printf (string: s5, format: "%*s" , (int)strlen (s: str), "" ); |
267 | g_string_append_printf (string: s6, format: "%*s" , (int)strlen (s: str), "" ); |
268 | g_free (mem: str); |
269 | } |
270 | } |
271 | } |
272 | g_string_append (string, val: "\n" ); |
273 | g_string_append_len (string, val: s1->str, len: s1->len); |
274 | g_string_append (string, val: "\n" ); |
275 | g_string_append_len (string, val: s2->str, len: s2->len); |
276 | g_string_append (string, val: "\n" ); |
277 | g_string_append_len (string, val: s3->str, len: s3->len); |
278 | g_string_append (string, val: "\n" ); |
279 | g_string_append_len (string, val: s4->str, len: s4->len); |
280 | g_string_append (string, val: "\n" ); |
281 | g_string_append_len (string, val: s5->str, len: s5->len); |
282 | g_string_append (string, val: "\n" ); |
283 | g_string_append_len (string, val: s6->str, len: s6->len); |
284 | g_string_append (string, val: "\n" ); |
285 | |
286 | g_string_free (string: s1, TRUE); |
287 | g_string_free (string: s2, TRUE); |
288 | g_string_free (string: s3, TRUE); |
289 | g_string_free (string: s4, TRUE); |
290 | g_string_free (string: s5, TRUE); |
291 | g_string_free (string: s6, TRUE); |
292 | |
293 | g_object_unref (object: layout); |
294 | g_free (mem: attrs); |
295 | g_free (mem: contents); |
296 | g_free (mem: text); |
297 | pango_attr_list_unref (list: attributes); |
298 | |
299 | return TRUE; |
300 | } |
301 | |
302 | static gchar * |
303 | get_expected_filename (const gchar *filename) |
304 | { |
305 | gchar *f, *p, *expected; |
306 | |
307 | f = g_strdup (str: filename); |
308 | p = strstr (haystack: f, needle: ".break" ); |
309 | if (p) |
310 | *p = 0; |
311 | expected = g_strconcat (string1: f, ".expected" , NULL); |
312 | |
313 | g_free (mem: f); |
314 | |
315 | return expected; |
316 | } |
317 | |
318 | static void |
319 | test_break (gconstpointer d) |
320 | { |
321 | const gchar *filename = d; |
322 | gchar *expected_file; |
323 | GError *error = NULL; |
324 | GString *dump; |
325 | gchar *diff; |
326 | |
327 | char *old_locale = g_strdup (str: setlocale (LC_ALL, NULL)); |
328 | setlocale (LC_ALL, locale: "en_US.UTF-8" ); |
329 | if (strstr (haystack: setlocale (LC_ALL, NULL), needle: "en_US" ) == NULL) |
330 | { |
331 | char *msg = g_strdup_printf (format: "Locale en_US.UTF-8 not available, skipping break %s" , filename); |
332 | g_test_skip (msg); |
333 | g_free (mem: msg); |
334 | g_free (mem: old_locale); |
335 | return; |
336 | } |
337 | |
338 | dump = g_string_sized_new (dfl_size: 0); |
339 | |
340 | if (!test_file (filename, string: dump)) |
341 | { |
342 | g_free (mem: old_locale); |
343 | g_string_free (string: dump, TRUE); |
344 | return; |
345 | } |
346 | |
347 | expected_file = get_expected_filename (filename); |
348 | |
349 | diff = diff_with_file (file: expected_file, text: dump->str, len: dump->len, error: &error); |
350 | g_assert_no_error (error); |
351 | |
352 | setlocale (LC_ALL, locale: old_locale); |
353 | g_free (mem: old_locale); |
354 | |
355 | if (diff && diff[0]) |
356 | { |
357 | char **lines = g_strsplit (string: diff, delimiter: "\n" , max_tokens: -1); |
358 | const char *line; |
359 | int i = 0; |
360 | |
361 | g_test_message (format: "Contents don't match expected contents" ); |
362 | |
363 | for (line = lines[0]; line != NULL; line = lines[++i]) |
364 | g_test_message (format: "%s" , line); |
365 | |
366 | g_test_fail (); |
367 | |
368 | g_strfreev (str_array: lines); |
369 | } |
370 | |
371 | g_free (mem: diff); |
372 | g_string_free (string: dump, TRUE); |
373 | g_free (mem: expected_file); |
374 | } |
375 | |
376 | int |
377 | main (int argc, char *argv[]) |
378 | { |
379 | GDir *dir; |
380 | GError *error = NULL; |
381 | const gchar *name; |
382 | gchar *path; |
383 | gboolean opt_legend = 0; |
384 | GOptionContext *option_context; |
385 | GOptionEntry entries[] = { |
386 | { "hex-chars" , 0, 0, G_OPTION_ARG_NONE, &opt_hex_chars, "Print all chars in hex" , NULL }, |
387 | { "legend" , 0, 0, G_OPTION_ARG_NONE, &opt_legend, "Explain the output" , NULL }, |
388 | { NULL, 0 }, |
389 | }; |
390 | |
391 | option_context = g_option_context_new (parameter_string: "" ); |
392 | g_option_context_add_main_entries (context: option_context, entries, NULL); |
393 | g_option_context_set_ignore_unknown_options (context: option_context, TRUE); |
394 | if (!g_option_context_parse (context: option_context, argc: &argc, argv: &argv, error: &error)) |
395 | { |
396 | g_error ("failed to parse options: %s" , error->message); |
397 | return 1; |
398 | } |
399 | g_option_context_free (context: option_context); |
400 | |
401 | setlocale (LC_ALL, locale: "" ); |
402 | |
403 | context = pango_font_map_create_context (fontmap: pango_cairo_font_map_get_default ()); |
404 | |
405 | if (opt_legend) |
406 | { |
407 | g_print (format: "test-break uses the following symbols for log attrs\n\n" ); |
408 | g_print (format: "Breaks: Words: Graphemes:\n" |
409 | " L - mandatory break b - word boundary b - grapheme boundary\n" |
410 | " l - line break s - word start\n" |
411 | " c - char break e - word end\n" |
412 | "\n" |
413 | "Whitespace: Sentences: Hyphens:\n" |
414 | " x - expandable space b - sentence boundary i - insert hyphen\n" |
415 | " w - whitespace s - sentence start r - remove preceding\n" |
416 | " e - sentence end\n" ); |
417 | return 0; |
418 | } |
419 | |
420 | if (argc > 1 && argv[1][0] != '-') |
421 | { |
422 | GString *string; |
423 | |
424 | string = g_string_sized_new (dfl_size: 0); |
425 | test_file (filename: argv[1], string); |
426 | g_print (format: "%s" , string->str); |
427 | |
428 | g_string_free (string, TRUE); |
429 | |
430 | return 0; |
431 | } |
432 | |
433 | g_test_init (argc: &argc, argv: &argv, NULL); |
434 | |
435 | path = g_test_build_filename (file_type: G_TEST_DIST, first_path: "breaks" , NULL); |
436 | dir = g_dir_open (path, flags: 0, error: &error); |
437 | g_free (mem: path); |
438 | g_assert_no_error (error); |
439 | while ((name = g_dir_read_name (dir)) != NULL) |
440 | { |
441 | if (!strstr (haystack: name, needle: "break" )) |
442 | continue; |
443 | |
444 | #ifndef HAVE_LIBTHAI |
445 | /* four.break involves Thai, so only test it when we have libthai */ |
446 | if (strstr (haystack: name, needle: "four.break" )) |
447 | continue; |
448 | #endif |
449 | |
450 | path = g_strdup_printf (format: "/break/%s" , name); |
451 | g_test_add_data_func_full (testpath: path, test_data: g_test_build_filename (file_type: G_TEST_DIST, first_path: "breaks" , name, NULL), |
452 | test_func: test_break, data_free_func: g_free); |
453 | g_free (mem: path); |
454 | } |
455 | g_dir_close (dir); |
456 | |
457 | return g_test_run (); |
458 | } |
459 | |