1/* Pango
2 * test-shape.c: Test Pango shaping
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#include <hb-ot.h>
27
28#ifndef G_OS_WIN32
29#include <unistd.h>
30#endif
31
32#include "config.h"
33#include <pango/pangocairo.h>
34#include "test-common.h"
35
36
37static PangoContext *context;
38
39gboolean opt_hex_chars;
40
41static void
42append_text (GString *s,
43 const char *text,
44 int len)
45{
46 const char *p;
47
48 for (p = text; p < text + len; p = g_utf8_next_char (p))
49 {
50 gunichar ch = g_utf8_get_char (p);
51 if (ch == ' ')
52 g_string_append (string: s, val: "[ ]");
53 else if (opt_hex_chars)
54 g_string_append_printf (string: s, format: "[%#04x]", ch);
55 else if (ch == 0x0A || ch == 0x2028 || !g_unichar_isprint (c: ch))
56 g_string_append_printf (string: s, format: "[%#04x]", ch);
57 else
58 g_string_append_unichar (string: s, wc: ch);
59 }
60}
61
62static gboolean
63affects_itemization (PangoAttribute *attr,
64 gpointer data)
65{
66 switch ((int)attr->klass->type)
67 {
68 /* These affect font selection */
69 case PANGO_ATTR_LANGUAGE:
70 case PANGO_ATTR_FAMILY:
71 case PANGO_ATTR_STYLE:
72 case PANGO_ATTR_WEIGHT:
73 case PANGO_ATTR_VARIANT:
74 case PANGO_ATTR_STRETCH:
75 case PANGO_ATTR_SIZE:
76 case PANGO_ATTR_FONT_DESC:
77 case PANGO_ATTR_SCALE:
78 case PANGO_ATTR_FALLBACK:
79 case PANGO_ATTR_ABSOLUTE_SIZE:
80 case PANGO_ATTR_GRAVITY:
81 case PANGO_ATTR_GRAVITY_HINT:
82 /* These are part of ItemProperties, so need to break runs */
83 case PANGO_ATTR_SHAPE:
84 case PANGO_ATTR_RISE:
85 case PANGO_ATTR_UNDERLINE:
86 case PANGO_ATTR_STRIKETHROUGH:
87 case PANGO_ATTR_LETTER_SPACING:
88 return TRUE;
89 default:
90 return FALSE;
91 }
92}
93
94static gboolean
95affects_break_or_shape (PangoAttribute *attr,
96 gpointer data)
97{
98 switch ((int)attr->klass->type)
99 {
100 /* Affects breaks */
101 case PANGO_ATTR_ALLOW_BREAKS:
102 /* Affects shaping */
103 case PANGO_ATTR_INSERT_HYPHENS:
104 case PANGO_ATTR_FONT_FEATURES:
105 case PANGO_ATTR_SHOW:
106 return TRUE;
107 default:
108 return FALSE;
109 }
110}
111
112static void
113apply_attributes_to_items (GList *items,
114 PangoAttrList *attrs)
115{
116 GList *l;
117 PangoAttrIterator *iter;
118
119 if (!attrs)
120 return;
121
122 iter = pango_attr_list_get_iterator (list: attrs);
123
124 for (l = items; l; l = l->next)
125 {
126 PangoItem *item = l->data;
127 pango_item_apply_attrs (item, iter);
128 }
129
130 pango_attr_iterator_destroy (iterator: iter);
131}
132
133static void
134test_file (const gchar *filename, GString *string)
135{
136 gchar *contents;
137 gsize length;
138 GError *error = NULL;
139 char *test;
140 char *text;
141 PangoAttrList *attrs;
142 PangoAttrList *itemize_attrs;
143 PangoAttrList *shape_attrs;
144 GList *items, *l;
145 GString *s1, *s2, *s3, *s4, *s5, *s6, *s7, *s8, *s9;
146 char *p1;
147 const char *sep = "";
148
149 if (!g_file_get_contents (filename, contents: &contents, length: &length, error: &error))
150 {
151 fprintf (stderr, format: "%s\n", error->message);
152 g_error_free (error);
153 return;
154 }
155
156 test = contents;
157
158 /* Skip initial comments */
159 while (test[0] == '#')
160 test = strchr (s: test, c: '\n') + 1;
161
162 if (!pango_parse_markup (markup_text: test, length: -1, accel_marker: 0, attr_list: &attrs, text: &text, NULL, error: &error))
163 {
164 fprintf (stderr, format: "%s\n", error->message);
165 g_error_free (error);
166 return;
167 }
168
169 s1 = g_string_new (init: "Text: ");
170 s2 = g_string_new (init: "Glyphs: ");
171 s3 = g_string_new (init: "Cluster: ");
172 s4 = g_string_new (init: "Width: ");
173 s5 = g_string_new (init: "Direction: ");
174 s6 = g_string_new (init: "Item: ");
175 s7 = g_string_new (init: "Offset: ");
176 s8 = g_string_new (init: "Class: ");
177 s9 = g_string_new (init: "Color: ");
178
179 length = strlen (s: text);
180 if (text[length - 1] == '\n')
181 length--;
182
183 itemize_attrs = pango_attr_list_filter (list: attrs, func: affects_itemization, NULL);
184 shape_attrs = pango_attr_list_filter (list: attrs, func: affects_break_or_shape, NULL);
185
186 items = pango_itemize (context, text, start_index: 0, length, attrs: itemize_attrs, NULL);
187 apply_attributes_to_items (items, attrs: shape_attrs);
188
189 pango_attr_list_unref (list: itemize_attrs);
190 pango_attr_list_unref (list: shape_attrs);
191
192 for (l = items; l; l = l->next)
193 {
194 PangoItem *item = l->data;
195 PangoGlyphString *glyphs;
196 gboolean rtl = item->analysis.level % 2;
197 PangoGlyphItem glyph_item;
198 int i;
199
200 glyphs = pango_glyph_string_new ();
201 /* FIXME: get log attrs */
202 pango_shape_item (item, paragraph_text: text, paragraph_length: length, NULL, glyphs, flags: 0);
203
204 glyph_item.item = item;
205 glyph_item.glyphs = glyphs;
206 pango_glyph_item_apply_attrs (glyph_item: &glyph_item, text, list: attrs);
207
208 g_string_append (string: s1, val: sep);
209 g_string_append (string: s2, val: sep);
210 g_string_append (string: s3, val: sep);
211 g_string_append (string: s4, val: sep);
212 g_string_append (string: s5, val: sep);
213 g_string_append (string: s6, val: sep);
214 g_string_append (string: s7, val: sep);
215 g_string_append (string: s8, val: sep);
216 g_string_append (string: s9, val: sep);
217 sep = "|";
218
219 g_string_append_printf (string: s6, format: "%d(%d)", item->num_chars, item->length);
220 g_string_append (string: s5, val: rtl ? "<" : ">");
221
222 for (i = 0; i < glyphs->num_glyphs; i++)
223 {
224 int len;
225 PangoGlyphInfo *gi = &glyphs->glyphs[i];
226
227
228 if (gi->attr.is_cluster_start && i > 0)
229 {
230 g_string_append (string: s1, val: " ");
231 g_string_append (string: s2, val: "|");
232 g_string_append (string: s3, val: "|");
233 g_string_append (string: s4, val: "|");
234 g_string_append (string: s5, val: " ");
235 g_string_append (string: s6, val: " ");
236 g_string_append (string: s7, val: "|");
237 g_string_append (string: s8, val: "|");
238 g_string_append (string: s9, val: "|");
239 }
240
241 char *p;
242 p = text + item->offset + glyphs->log_clusters[i];
243 if (rtl)
244 {
245 if (i > 0)
246 p1 = text + item->offset + glyphs->log_clusters[i - 1];
247 else
248 p1 = text + item->offset + item->length;
249 }
250 else
251 {
252 if (i + 1 < glyphs->num_glyphs)
253 p1 = text + item->offset + glyphs->log_clusters[i + 1];
254 else
255 p1 = text + item->offset + item->length;
256 }
257 append_text (s: s1, text: p, len: p1 - p);
258 if (gi->glyph & PANGO_GLYPH_UNKNOWN_FLAG)
259 g_string_append_printf (string: s2, format: "(%d)", gi->glyph & ~PANGO_GLYPH_UNKNOWN_FLAG);
260 else
261 g_string_append_printf (string: s2, format: "[%d]", gi->glyph);
262 g_string_append_printf (string: s4, format: "%d ", gi->geometry.width);
263 g_string_append_printf (string: s7, format: "%d,%d ", gi->geometry.x_offset, gi->geometry.y_offset);
264 if (gi->attr.is_cluster_start)
265 g_string_append_printf (string: s3, format: "%d ", item->offset + glyphs->log_clusters[i]);
266 switch (hb_ot_layout_get_glyph_class (face: hb_font_get_face (font: pango_font_get_hb_font (font: item->analysis.font)), glyph: gi->glyph))
267 {
268 case HB_OT_LAYOUT_GLYPH_CLASS_UNCLASSIFIED:
269 g_string_append (string: s8, val: "u");
270 break;
271 case HB_OT_LAYOUT_GLYPH_CLASS_BASE_GLYPH:
272 g_string_append (string: s8, val: "b");
273 break;
274 case HB_OT_LAYOUT_GLYPH_CLASS_LIGATURE:
275 g_string_append (string: s8, val: "l");
276 break;
277 case HB_OT_LAYOUT_GLYPH_CLASS_MARK:
278 g_string_append (string: s8, val: "m");
279 break;
280 case HB_OT_LAYOUT_GLYPH_CLASS_COMPONENT:
281 g_string_append (string: s8, val: "c");
282 break;
283 default:
284 g_assert_not_reached ();
285 }
286 if (gi->attr.is_color)
287 g_string_append_printf (string: s9, format: "c");
288 len = 0;
289 len = MAX (len, g_utf8_strlen (s1->str, s1->len));
290 len = MAX (len, g_utf8_strlen (s2->str, s2->len));
291 len = MAX (len, g_utf8_strlen (s3->str, s3->len));
292 len = MAX (len, g_utf8_strlen (s4->str, s4->len));
293 len = MAX (len, g_utf8_strlen (s5->str, s5->len));
294 len = MAX (len, g_utf8_strlen (s6->str, s6->len));
295 len = MAX (len, g_utf8_strlen (s7->str, s7->len));
296 len = MAX (len, g_utf8_strlen (s8->str, s8->len));
297 len = MAX (len, g_utf8_strlen (s9->str, s9->len));
298 g_string_append_printf (string: s1, format: "%*s", len - (int)g_utf8_strlen (p: s1->str, max: s1->len), "");
299 g_string_append_printf (string: s2, format: "%*s", len - (int)g_utf8_strlen (p: s2->str, max: s2->len), "");
300 g_string_append_printf (string: s3, format: "%*s", len - (int)g_utf8_strlen (p: s3->str, max: s3->len), "");
301 g_string_append_printf (string: s4, format: "%*s", len - (int)g_utf8_strlen (p: s4->str, max: s4->len), "");
302 g_string_append_printf (string: s5, format: "%*s", len - (int)g_utf8_strlen (p: s5->str, max: s5->len), "");
303 g_string_append_printf (string: s6, format: "%*s", len - (int)g_utf8_strlen (p: s6->str, max: s6->len), "");
304 g_string_append_printf (string: s7, format: "%*s", len - (int)g_utf8_strlen (p: s7->str, max: s7->len), "");
305 g_string_append_printf (string: s8, format: "%*s", len - (int)g_utf8_strlen (p: s8->str, max: s8->len), "");
306 g_string_append_printf (string: s9, format: "%*s", len - (int)g_utf8_strlen (p: s9->str, max: s9->len), "");
307 }
308
309 pango_glyph_string_free (string: glyphs);
310 }
311
312 g_string_append_printf (string, format: "%s\n", test);
313 g_string_append_printf (string, format: "%s\n", s6->str);
314 g_string_append_printf (string, format: "%s\n", s1->str);
315 g_string_append_printf (string, format: "%s\n", s5->str);
316 g_string_append_printf (string, format: "%s\n", s3->str);
317 g_string_append_printf (string, format: "%s\n", s2->str);
318 g_string_append_printf (string, format: "%s\n", s8->str);
319 g_string_append_printf (string, format: "%s\n", s9->str);
320 g_string_append_printf (string, format: "%s\n", s4->str);
321 g_string_append_printf (string, format: "%s\n", s7->str);
322
323 g_string_free (string: s1, TRUE);
324 g_string_free (string: s2, TRUE);
325 g_string_free (string: s3, TRUE);
326 g_string_free (string: s4, TRUE);
327 g_string_free (string: s5, TRUE);
328 g_string_free (string: s6, TRUE);
329 g_string_free (string: s7, TRUE);
330 g_string_free (string: s8, TRUE);
331 g_string_free (string: s9, TRUE);
332
333 g_list_free_full (list: items, free_func: (GDestroyNotify)pango_item_free);
334 g_free (mem: contents);
335 g_free (mem: text);
336
337 pango_attr_list_unref (list: attrs);
338}
339
340static gchar *
341get_expected_filename (const gchar *filename)
342{
343 gchar *f, *p, *expected;
344
345 f = g_strdup (str: filename);
346 p = strstr (haystack: f, needle: ".shape");
347 if (p)
348 *p = 0;
349 expected = g_strconcat (string1: f, ".expected", NULL);
350
351 g_free (mem: f);
352
353 return expected;
354}
355
356static void
357test_shape (gconstpointer d)
358{
359 const gchar *filename = d;
360 gchar *expected_file;
361 GError *error = NULL;
362 GString *dump;
363 gchar *diff;
364
365 expected_file = get_expected_filename (filename);
366
367 dump = g_string_sized_new (dfl_size: 0);
368
369 test_file (filename, string: dump);
370
371 diff = diff_with_file (file: expected_file, text: dump->str, len: dump->len, error: &error);
372 g_assert_no_error (error);
373
374 if (diff && diff[0])
375 {
376 g_test_message (format: "Contents don't match expected contents");
377 g_test_message (format: "%s", diff);
378 g_test_fail ();
379 g_free (mem: diff);
380 }
381
382 g_string_free (string: dump, TRUE);
383 g_free (mem: expected_file);
384}
385
386int
387main (int argc, char *argv[])
388{
389 GDir *dir;
390 GError *error = NULL;
391 const gchar *name;
392 gchar *path;
393 GOptionContext *option_context;
394 GOptionEntry entries[] = {
395 { "hex-chars", '0', 0, G_OPTION_ARG_NONE, &opt_hex_chars, "Print all chars in hex", NULL },
396 { NULL, 0 },
397 };
398
399 option_context = g_option_context_new (parameter_string: "");
400 g_option_context_add_main_entries (context: option_context, entries, NULL);
401 g_option_context_set_ignore_unknown_options (context: option_context, TRUE);
402 if (!g_option_context_parse (context: option_context, argc: &argc, argv: &argv, error: &error))
403 {
404 g_error ("failed to parse options: %s", error->message);
405 return 1;
406 }
407 g_option_context_free (context: option_context);
408
409 setlocale (LC_ALL, locale: "");
410
411 g_test_init (argc: &argc, argv: &argv, NULL);
412
413 context = pango_font_map_create_context (fontmap: pango_cairo_font_map_get_default ());
414
415 /* allow to easily generate expected output for new test cases */
416 if (argc > 1)
417 {
418 GString *string;
419
420 string = g_string_sized_new (dfl_size: 0);
421 test_file (filename: argv[1], string);
422 printf (format: "%s", string->str);
423
424 return 0;
425 }
426
427 path = g_test_build_filename (file_type: G_TEST_DIST, first_path: "shape", NULL);
428 dir = g_dir_open (path, flags: 0, error: &error);
429 g_free (mem: path);
430
431 if (g_error_matches (error, G_FILE_ERROR, code: G_FILE_ERROR_NOENT))
432 {
433 g_error_free (error);
434 return 0;
435 }
436
437 g_assert_no_error (error);
438 while ((name = g_dir_read_name (dir)) != NULL)
439 {
440 if (!strstr (haystack: name, needle: "items"))
441 continue;
442
443 path = g_strdup_printf (format: "/shape/%s", name);
444 g_test_add_data_func_full (testpath: path, test_data: g_test_build_filename (file_type: G_TEST_DIST, first_path: "shape", name, NULL),
445 test_func: test_shape, data_free_func: g_free);
446 g_free (mem: path);
447 }
448 g_dir_close (dir);
449
450 return g_test_run ();
451}
452

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