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
36static PangoContext *context;
37
38static gboolean opt_hex_chars;
39
40static gboolean
41test_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
302static gchar *
303get_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
318static void
319test_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
376int
377main (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

source code of gtk/subprojects/pango/tests/test-break.c