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 | |
34 | #include "pango/pango-item-private.h" |
35 | |
36 | |
37 | static PangoContext *context; |
38 | |
39 | static void |
40 | append_text (GString *s, |
41 | const char *text, |
42 | int len) |
43 | { |
44 | const char *p; |
45 | |
46 | for (p = text; p < text + len; p = g_utf8_next_char (p)) |
47 | { |
48 | gunichar ch = g_utf8_get_char (p); |
49 | if (ch == 0x0A || ch == 0x2028 || !g_unichar_isprint (c: ch)) |
50 | g_string_append_printf (string: s, format: "[%#04x]" , ch); |
51 | else |
52 | g_string_append_unichar (string: s, wc: ch); |
53 | } |
54 | } |
55 | |
56 | static gboolean |
57 | affects_itemization (PangoAttribute *attr, |
58 | gpointer data) |
59 | { |
60 | switch ((int)attr->klass->type) |
61 | { |
62 | /* These affect font selection */ |
63 | case PANGO_ATTR_LANGUAGE: |
64 | case PANGO_ATTR_FAMILY: |
65 | case PANGO_ATTR_STYLE: |
66 | case PANGO_ATTR_WEIGHT: |
67 | case PANGO_ATTR_VARIANT: |
68 | case PANGO_ATTR_STRETCH: |
69 | case PANGO_ATTR_SIZE: |
70 | case PANGO_ATTR_FONT_DESC: |
71 | case PANGO_ATTR_SCALE: |
72 | case PANGO_ATTR_FALLBACK: |
73 | case PANGO_ATTR_ABSOLUTE_SIZE: |
74 | case PANGO_ATTR_GRAVITY: |
75 | case PANGO_ATTR_GRAVITY_HINT: |
76 | case PANGO_ATTR_FONT_SCALE: |
77 | /* These are part of ItemProperties, so need to break runs */ |
78 | case PANGO_ATTR_LETTER_SPACING: |
79 | case PANGO_ATTR_SHAPE: |
80 | case PANGO_ATTR_RISE: |
81 | case PANGO_ATTR_BASELINE_SHIFT: |
82 | case PANGO_ATTR_LINE_HEIGHT: |
83 | case PANGO_ATTR_ABSOLUTE_LINE_HEIGHT: |
84 | case PANGO_ATTR_TEXT_TRANSFORM: |
85 | return TRUE; |
86 | default: |
87 | return FALSE; |
88 | } |
89 | } |
90 | |
91 | static void |
92 | apply_attributes_to_items (GList *items, |
93 | PangoAttrList *attrs) |
94 | { |
95 | GList *l; |
96 | PangoAttrIterator *iter; |
97 | |
98 | if (!attrs) |
99 | return; |
100 | |
101 | iter = pango_attr_list_get_iterator (list: attrs); |
102 | |
103 | for (l = items; l; l = l->next) |
104 | { |
105 | PangoItem *item = l->data; |
106 | pango_item_apply_attrs (item, iter); |
107 | } |
108 | |
109 | pango_attr_iterator_destroy (iterator: iter); |
110 | } |
111 | |
112 | static int |
113 | get_item_char_offset (PangoItem *item) |
114 | { |
115 | if (item->analysis.flags & PANGO_ANALYSIS_FLAG_HAS_CHAR_OFFSET) |
116 | return ((PangoItemPrivate *)item)->char_offset; |
117 | |
118 | return -1; |
119 | } |
120 | |
121 | static void |
122 | test_file (const gchar *filename, GString *string) |
123 | { |
124 | gchar *contents; |
125 | gsize length; |
126 | GError *error = NULL; |
127 | GString *s1, *s2, *s3, *s4, *s5, *s6, *s7; |
128 | char *test; |
129 | char *text; |
130 | PangoAttrList *attrs; |
131 | PangoAttrList *itemize_attrs; |
132 | GList *items, *l; |
133 | const char *sep = "" ; |
134 | |
135 | g_file_get_contents (filename, contents: &contents, length: &length, error: &error); |
136 | g_assert_no_error (error); |
137 | |
138 | test = contents; |
139 | |
140 | /* Skip initial comments */ |
141 | while (test[0] == '#') |
142 | test = strchr (s: test, c: '\n') + 1; |
143 | |
144 | pango_parse_markup (markup_text: test, length: -1, accel_marker: 0, attr_list: &attrs, text: &text, NULL, error: &error); |
145 | g_assert_no_error (error); |
146 | |
147 | s1 = g_string_new (init: "Items: " ); |
148 | s2 = g_string_new (init: "Font: " ); |
149 | s3 = g_string_new (init: "Script: " ); |
150 | s4 = g_string_new (init: "Lang: " ); |
151 | s5 = g_string_new (init: "Bidi: " ); |
152 | s6 = g_string_new (init: "Attrs: " ); |
153 | s7 = g_string_new (init: "Chars: " ); |
154 | |
155 | length = strlen (s: text); |
156 | if (text[length - 1] == '\n') |
157 | length--; |
158 | |
159 | itemize_attrs = pango_attr_list_filter (list: attrs, func: affects_itemization, NULL); |
160 | items = pango_itemize (context, text, start_index: 0, length, attrs: itemize_attrs, NULL); |
161 | |
162 | apply_attributes_to_items (items, attrs); |
163 | pango_attr_list_unref (list: itemize_attrs); |
164 | |
165 | for (l = items; l; l = l->next) |
166 | { |
167 | PangoItem *item = l->data; |
168 | PangoFontDescription *desc; |
169 | char *font; |
170 | int m; |
171 | GSList *a; |
172 | |
173 | desc = pango_font_describe (font: item->analysis.font); |
174 | font = pango_font_description_to_string (desc); |
175 | |
176 | if (l != items) |
177 | sep = "|" ; |
178 | g_string_append (string: s1, val: sep); |
179 | append_text (s: s1, text: text + item->offset, len: item->length); |
180 | |
181 | g_string_append_printf (string: s2, format: "%s%s" , sep, font); |
182 | g_string_append_printf (string: s3, format: "%s%s" , sep, get_script_name (s: item->analysis.script)); |
183 | g_string_append_printf (string: s4, format: "%s%s" , sep, pango_language_to_string (item->analysis.language)); |
184 | g_string_append_printf (string: s5, format: "%s%d" , sep, item->analysis.level); |
185 | g_string_append_printf (string: s6, format: "%s" , sep); |
186 | g_string_append_printf (string: s7, format: "%s%d(%d)" , sep, item->num_chars, get_item_char_offset (item)); |
187 | for (a = item->analysis.extra_attrs; a; a = a->next) |
188 | { |
189 | PangoAttribute *attr = a->data; |
190 | if (a != item->analysis.extra_attrs) |
191 | g_string_append (string: s6, val: "," ); |
192 | print_attribute (attr, string: s6); |
193 | } |
194 | |
195 | g_free (mem: font); |
196 | pango_font_description_free (desc); |
197 | |
198 | m = MAX (MAX (MAX (s1->len, s2->len), |
199 | MAX (s3->len, s4->len)), |
200 | MAX (s5->len, s6->len)); |
201 | |
202 | g_string_append_printf (string: s1, format: "%*s" , (int)(m - s1->len), "" ); |
203 | g_string_append_printf (string: s2, format: "%*s" , (int)(m - s2->len), "" ); |
204 | g_string_append_printf (string: s3, format: "%*s" , (int)(m - s3->len), "" ); |
205 | g_string_append_printf (string: s4, format: "%*s" , (int)(m - s4->len), "" ); |
206 | g_string_append_printf (string: s5, format: "%*s" , (int)(m - s5->len), "" ); |
207 | g_string_append_printf (string: s6, format: "%*s" , (int)(m - s6->len), "" ); |
208 | g_string_append_printf (string: s7, format: "%*s" , (int)(m - s7->len), "" ); |
209 | } |
210 | |
211 | g_string_append_printf (string, format: "%s\n" , test); |
212 | g_string_append_printf (string, format: "%s\n" , s1->str); |
213 | g_string_append_printf (string, format: "%s\n" , s7->str); |
214 | g_string_append_printf (string, format: "%s\n" , s2->str); |
215 | g_string_append_printf (string, format: "%s\n" , s3->str); |
216 | g_string_append_printf (string, format: "%s\n" , s4->str); |
217 | g_string_append_printf (string, format: "%s\n" , s5->str); |
218 | g_string_append_printf (string, format: "%s\n" , s6->str); |
219 | |
220 | g_string_free (string: s1, TRUE); |
221 | g_string_free (string: s2, TRUE); |
222 | g_string_free (string: s3, TRUE); |
223 | g_string_free (string: s4, TRUE); |
224 | g_string_free (string: s5, TRUE); |
225 | g_string_free (string: s6, TRUE); |
226 | g_string_free (string: s7, TRUE); |
227 | |
228 | g_list_free_full (list: items, free_func: (GDestroyNotify)pango_item_free); |
229 | pango_attr_list_unref (list: attrs); |
230 | g_free (mem: text); |
231 | g_free (mem: contents); |
232 | } |
233 | |
234 | static gchar * |
235 | get_expected_filename (const gchar *filename) |
236 | { |
237 | gchar *f, *p, *expected; |
238 | |
239 | f = g_strdup (str: filename); |
240 | p = strstr (haystack: f, needle: ".items" ); |
241 | if (p) |
242 | *p = 0; |
243 | expected = g_strconcat (string1: f, ".expected" , NULL); |
244 | |
245 | g_free (mem: f); |
246 | |
247 | return expected; |
248 | } |
249 | |
250 | static void |
251 | test_itemize (gconstpointer d) |
252 | { |
253 | const gchar *filename = d; |
254 | gchar *expected_file; |
255 | GError *error = NULL; |
256 | GString *dump; |
257 | gchar *diff; |
258 | PangoFontFamily **families; |
259 | int n_families; |
260 | gboolean found_cantarell; |
261 | |
262 | char *old_locale = g_strdup (str: setlocale (LC_ALL, NULL)); |
263 | setlocale (LC_ALL, locale: "en_US.UTF-8" ); |
264 | if (strstr (haystack: setlocale (LC_ALL, NULL), needle: "en_US" ) == NULL) |
265 | { |
266 | char *msg = g_strdup_printf (format: "Locale en_US.UTF-8 not available, skipping itemization %s" , filename); |
267 | g_test_skip (msg); |
268 | g_free (mem: msg); |
269 | g_free (mem: old_locale); |
270 | return; |
271 | } |
272 | |
273 | found_cantarell = FALSE; |
274 | pango_context_list_families (context, families: &families, n_families: &n_families); |
275 | for (int i = 0; i < n_families; i++) |
276 | { |
277 | if (strcmp (s1: pango_font_family_get_name (family: families[i]), s2: "Cantarell" ) == 0) |
278 | { |
279 | found_cantarell = TRUE; |
280 | break; |
281 | } |
282 | } |
283 | g_free (mem: families); |
284 | |
285 | if (!found_cantarell) |
286 | { |
287 | char *msg = g_strdup_printf (format: "Cantarell font not available, skipping itemization %s" , filename); |
288 | g_test_skip (msg); |
289 | g_free (mem: msg); |
290 | g_free (mem: old_locale); |
291 | return; |
292 | } |
293 | |
294 | expected_file = get_expected_filename (filename); |
295 | |
296 | dump = g_string_sized_new (dfl_size: 0); |
297 | |
298 | test_file (filename, string: dump); |
299 | |
300 | diff = diff_with_file (file: expected_file, text: dump->str, len: dump->len, error: &error); |
301 | g_assert_no_error (error); |
302 | |
303 | setlocale (LC_ALL, locale: old_locale); |
304 | g_free (mem: old_locale); |
305 | |
306 | if (diff && diff[0]) |
307 | { |
308 | char **lines = g_strsplit (string: diff, delimiter: "\n" , max_tokens: -1); |
309 | const char *line; |
310 | int i = 0; |
311 | |
312 | g_test_message (format: "Contents don't match expected contents" ); |
313 | |
314 | for (line = lines[0]; line != NULL; line = lines[++i]) |
315 | g_test_message (format: "%s" , line); |
316 | |
317 | g_test_fail (); |
318 | |
319 | g_strfreev (str_array: lines); |
320 | } |
321 | |
322 | g_free (mem: diff); |
323 | g_string_free (string: dump, TRUE); |
324 | g_free (mem: expected_file); |
325 | } |
326 | |
327 | int |
328 | main (int argc, char *argv[]) |
329 | { |
330 | GDir *dir; |
331 | GError *error = NULL; |
332 | const gchar *name; |
333 | gchar *path; |
334 | |
335 | context = pango_font_map_create_context (fontmap: pango_cairo_font_map_get_default ()); |
336 | pango_context_set_language (context, language: pango_language_from_string (language: "en-us" )); |
337 | |
338 | /* allow to easily generate expected output for new test cases */ |
339 | if (argc > 1 && argv[1][0] != '-') |
340 | { |
341 | GString *string; |
342 | |
343 | string = g_string_sized_new (dfl_size: 0); |
344 | test_file (filename: argv[1], string); |
345 | printf (format: "%s" , string->str); |
346 | |
347 | return 0; |
348 | } |
349 | |
350 | g_test_init (argc: &argc, argv: &argv, NULL); |
351 | |
352 | path = g_test_build_filename (file_type: G_TEST_DIST, first_path: "itemize" , NULL); |
353 | dir = g_dir_open (path, flags: 0, error: &error); |
354 | g_free (mem: path); |
355 | g_assert_no_error (error); |
356 | while ((name = g_dir_read_name (dir)) != NULL) |
357 | { |
358 | if (!strstr (haystack: name, needle: "items" )) |
359 | continue; |
360 | |
361 | path = g_strdup_printf (format: "/itemize/%s" , name); |
362 | g_test_add_data_func_full (testpath: path, test_data: g_test_build_filename (file_type: G_TEST_DIST, first_path: "itemize" , name, NULL), |
363 | test_func: test_itemize, data_free_func: g_free); |
364 | g_free (mem: path); |
365 | } |
366 | g_dir_close (dir); |
367 | |
368 | return g_test_run (); |
369 | } |
370 | |