1 | /* Pango |
2 | * test-bidi.c: Test bidi apis |
3 | * |
4 | * Copyright (C) 2021 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 <locale.h> |
23 | #include <pango/pango.h> |
24 | #include <pango/pangocairo.h> |
25 | |
26 | static PangoContext *context; |
27 | |
28 | G_GNUC_BEGIN_IGNORE_DEPRECATIONS |
29 | |
30 | static void |
31 | test_mirror_char (void) |
32 | { |
33 | /* just some samples */ |
34 | struct { |
35 | gunichar a; |
36 | gunichar b; |
37 | } tests[] = { |
38 | { '(', ')' }, |
39 | { '<', '>' }, |
40 | { '[', ']' }, |
41 | { '{', '}' }, |
42 | { 0x00ab, 0x00bb }, |
43 | { 0x2045, 0x2046 }, |
44 | { 0x226e, 0x226f }, |
45 | }; |
46 | |
47 | for (int i = 0; i < G_N_ELEMENTS (tests); i++) |
48 | { |
49 | gboolean ret; |
50 | gunichar ch; |
51 | |
52 | ret = pango_get_mirror_char (ch: tests[i].a, mirrored_ch: &ch); |
53 | g_assert_true (ret); |
54 | g_assert_true (ch == tests[i].b); |
55 | ret = pango_get_mirror_char (ch: tests[i].b, mirrored_ch: &ch); |
56 | g_assert_true (ret); |
57 | g_assert_true (ch == tests[i].a); |
58 | } |
59 | } |
60 | |
61 | static void |
62 | test_bidi_type_for_unichar (void) |
63 | { |
64 | /* one representative from each class we support */ |
65 | g_assert_true (pango_bidi_type_for_unichar ('a') == PANGO_BIDI_TYPE_L); |
66 | g_assert_true (pango_bidi_type_for_unichar (0x202a) == PANGO_BIDI_TYPE_LRE); |
67 | g_assert_true (pango_bidi_type_for_unichar (0x202d) == PANGO_BIDI_TYPE_LRO); |
68 | g_assert_true (pango_bidi_type_for_unichar (0x05d0) == PANGO_BIDI_TYPE_R); |
69 | g_assert_true (pango_bidi_type_for_unichar (0x0627) == PANGO_BIDI_TYPE_AL); |
70 | g_assert_true (pango_bidi_type_for_unichar (0x202b) == PANGO_BIDI_TYPE_RLE); |
71 | g_assert_true (pango_bidi_type_for_unichar (0x202e) == PANGO_BIDI_TYPE_RLO); |
72 | g_assert_true (pango_bidi_type_for_unichar (0x202c) == PANGO_BIDI_TYPE_PDF); |
73 | g_assert_true (pango_bidi_type_for_unichar ('0') == PANGO_BIDI_TYPE_EN); |
74 | g_assert_true (pango_bidi_type_for_unichar ('+') == PANGO_BIDI_TYPE_ES); |
75 | g_assert_true (pango_bidi_type_for_unichar ('#') == PANGO_BIDI_TYPE_ET); |
76 | g_assert_true (pango_bidi_type_for_unichar (0x601) == PANGO_BIDI_TYPE_AN); |
77 | g_assert_true (pango_bidi_type_for_unichar (',') == PANGO_BIDI_TYPE_CS); |
78 | g_assert_true (pango_bidi_type_for_unichar (0x0301) == PANGO_BIDI_TYPE_NSM); |
79 | g_assert_true (pango_bidi_type_for_unichar (0x200d) == PANGO_BIDI_TYPE_BN); |
80 | g_assert_true (pango_bidi_type_for_unichar (0x2029) == PANGO_BIDI_TYPE_B); |
81 | g_assert_true (pango_bidi_type_for_unichar (0x000b) == PANGO_BIDI_TYPE_S); |
82 | g_assert_true (pango_bidi_type_for_unichar (' ') == PANGO_BIDI_TYPE_WS); |
83 | g_assert_true (pango_bidi_type_for_unichar ('!') == PANGO_BIDI_TYPE_ON); |
84 | /* these are new */ |
85 | g_assert_true (pango_bidi_type_for_unichar (0x2066) == PANGO_BIDI_TYPE_LRI); |
86 | g_assert_true (pango_bidi_type_for_unichar (0x2067) == PANGO_BIDI_TYPE_RLI); |
87 | g_assert_true (pango_bidi_type_for_unichar (0x2068) == PANGO_BIDI_TYPE_FSI); |
88 | g_assert_true (pango_bidi_type_for_unichar (0x2069) == PANGO_BIDI_TYPE_PDI); |
89 | } |
90 | |
91 | static void |
92 | test_unichar_direction (void) |
93 | { |
94 | struct { |
95 | gunichar ch; |
96 | PangoDirection dir; |
97 | } tests[] = { |
98 | { 'a', PANGO_DIRECTION_LTR }, |
99 | { '0', PANGO_DIRECTION_NEUTRAL }, |
100 | { '.', PANGO_DIRECTION_NEUTRAL }, |
101 | { '(', PANGO_DIRECTION_NEUTRAL }, |
102 | { 0x05d0, PANGO_DIRECTION_RTL }, |
103 | }; |
104 | |
105 | for (int i = 0; i < G_N_ELEMENTS (tests); i++) |
106 | { |
107 | g_assert_true (pango_unichar_direction (tests[i].ch) == tests[i].dir); |
108 | } |
109 | } |
110 | |
111 | G_GNUC_END_IGNORE_DEPRECATIONS |
112 | |
113 | static void |
114 | test_bidi_embedding_levels (void) |
115 | { |
116 | /* Examples taken from https://www.w3.org/International/articles/inline-bidi-markup/uba-basics */ |
117 | struct { |
118 | const char *text; |
119 | PangoDirection dir; |
120 | const char *levels; |
121 | PangoDirection out_dir; |
122 | } tests[] = { |
123 | { "bahrain مصر kuwait" , PANGO_DIRECTION_LTR, "\0\0\0\0\0\0\0\0\1\1\1\0\0\0\0\0\0\0" , PANGO_DIRECTION_LTR }, |
124 | { "bahrain مصر kuwait" , PANGO_DIRECTION_WEAK_LTR, "\0\0\0\0\0\0\0\0\1\1\1\0\0\0\0\0\0\0" , PANGO_DIRECTION_LTR }, |
125 | { "bahrain مصر kuwait" , PANGO_DIRECTION_RTL, "\2\2\2\2\2\2\2\1\1\1\1\1\2\2\2\2\2\2" , PANGO_DIRECTION_RTL }, |
126 | { "bahrain مصر kuwait" , PANGO_DIRECTION_WEAK_RTL, "\0\0\0\0\0\0\0\0\1\1\1\0\0\0\0\0\0\0" , PANGO_DIRECTION_LTR }, |
127 | { "The title is مفتاح معايير الويب in Arabic." , PANGO_DIRECTION_LTR, "\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\0\0\0" , PANGO_DIRECTION_LTR }, |
128 | { "The title is مفتاح معايير الويب, in Arabic." , PANGO_DIRECTION_LTR, "\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\0\0\0\0" , PANGO_DIRECTION_LTR }, |
129 | { "The title is مفتاح معايير الويب! in Arabic." , PANGO_DIRECTION_LTR, "\0\0\0\0\0\0\0\0\0\0\0\0\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\0\0\0\0\0\0\0\0\0\0" , PANGO_DIRECTION_LTR }, // FIXME |
130 | { "one two ثلاثة 1234 خمسة" , PANGO_DIRECTION_LTR, "\0\0\0\0\0\0\0\0\1\1\1\1\1\1\2\2\2\2\1\1\1\1\1" , PANGO_DIRECTION_LTR }, |
131 | { "one two ثلاثة ١٢٣٤ خمسة" , PANGO_DIRECTION_LTR, "\0\0\0\0\0\0\0\0\1\1\1\1\1\1\2\2\2\2\1\1\1\1\1" , PANGO_DIRECTION_LTR }, |
132 | { "abאב12cd" , PANGO_DIRECTION_LTR, "\0\0\1\1\2\2\0\0" }, |
133 | { "abאבxycd" , PANGO_DIRECTION_LTR, "\0\0\1\1\1\2\2\2\0\0" }, |
134 | |
135 | }; |
136 | |
137 | for (int i = 0; i < G_N_ELEMENTS (tests); i++) |
138 | { |
139 | const char *text = tests[i].text; |
140 | PangoDirection dir = tests[i].dir; |
141 | guint8 *levels; |
142 | gsize len; |
143 | |
144 | levels = pango_log2vis_get_embedding_levels (text, length: -1, pbase_dir: &dir); |
145 | |
146 | len = g_utf8_strlen (p: text, max: -1); |
147 | |
148 | if (memcmp (s1: levels, s2: tests[i].levels, n: sizeof (guint8) * len) != 0) |
149 | { |
150 | for (int j = 0; j < len; j++) |
151 | g_print (format: "\\%d" , levels[j]); |
152 | g_print (format: "\n" ); |
153 | } |
154 | g_assert_true (memcmp (levels, tests[i].levels, sizeof (guint8) * len) == 0); |
155 | g_assert_true (dir == tests[i].out_dir); |
156 | |
157 | g_free (mem: levels); |
158 | } |
159 | } |
160 | |
161 | /* Some basic tests for pango_layout_move_cursor_visually inside |
162 | * a single PangoLayoutLine: |
163 | * - check that we actually move the cursor in the right direction |
164 | * - check that we get through the line with at most n steps |
165 | * - check that we don't skip legitimate cursor positions |
166 | */ |
167 | static void |
168 | test_move_cursor_line (void) |
169 | { |
170 | const char *tests[] = { |
171 | "abc😂️def" , |
172 | "abcאבגdef" , |
173 | "אבabcב" , |
174 | "aאב12b" , |
175 | "paragraph" , // soft hyphens |
176 | }; |
177 | PangoLayout *layout; |
178 | gboolean fail = FALSE; |
179 | |
180 | layout = pango_layout_new (context); |
181 | |
182 | for (int i = 0; i < G_N_ELEMENTS (tests); i++) |
183 | { |
184 | const char *text; |
185 | gsize n_chars; |
186 | int index; |
187 | int start_index; |
188 | gboolean trailing; |
189 | PangoRectangle s_pos, old_s_pos; |
190 | PangoRectangle w_pos, old_w_pos; |
191 | PangoLayoutLine *line; |
192 | struct { |
193 | int direction; |
194 | gboolean strong; |
195 | } params[] = { |
196 | { 1, TRUE }, |
197 | { 1, FALSE }, |
198 | { -1, TRUE }, |
199 | { -1, FALSE }, |
200 | }; |
201 | int *strong_cursor; |
202 | int *weak_cursor; |
203 | gboolean *met_cursor; |
204 | const PangoLogAttr *attrs; |
205 | int n_attrs; |
206 | int j; |
207 | const char *p; |
208 | |
209 | pango_layout_set_text (layout, text: tests[i], length: -1); |
210 | |
211 | text = pango_layout_get_text (layout); |
212 | line = pango_layout_get_line_readonly (layout, line: 0); |
213 | |
214 | n_chars = g_utf8_strlen (p: text, max: -1); |
215 | |
216 | attrs = pango_layout_get_log_attrs_readonly (layout, n_attrs: &n_attrs); |
217 | strong_cursor = g_new (int, n_attrs); |
218 | weak_cursor = g_new (int, n_attrs); |
219 | met_cursor = g_new (gboolean, n_attrs); |
220 | for (j = 0, p = text; j < n_attrs; j++, p = g_utf8_next_char (p)) |
221 | { |
222 | if (attrs[j].is_cursor_position) |
223 | { |
224 | pango_layout_get_cursor_pos (layout, index_: p - text, strong_pos: &s_pos, weak_pos: &w_pos); |
225 | strong_cursor[j] = s_pos.x; |
226 | weak_cursor[j] = w_pos.x; |
227 | } |
228 | else |
229 | strong_cursor[j] = weak_cursor[j] = -1; |
230 | } |
231 | |
232 | for (j = 0; j < G_N_ELEMENTS (params); j++) |
233 | { |
234 | gboolean ok; |
235 | |
236 | if (g_test_verbose ()) |
237 | g_print (format: "'%s' %s %s :\t" , |
238 | text, |
239 | params[j].direction > 0 ? "->" : "<-" , |
240 | params[j].strong ? "strong" : "weak" ); |
241 | |
242 | if ((pango_layout_line_get_resolved_direction (line) == PANGO_DIRECTION_LTR) == (params[j].direction > 0)) |
243 | start_index = 0; |
244 | else |
245 | start_index = strlen (s: text); |
246 | |
247 | index = start_index; |
248 | |
249 | memset (s: met_cursor, c: 0, n: sizeof (gboolean) * n_attrs); |
250 | |
251 | pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &s_pos, weak_pos: &w_pos); |
252 | for (int l = 0; l < n_attrs; l++) |
253 | { |
254 | if ((params[j].strong && strong_cursor[l] == s_pos.x) || |
255 | (!params[j].strong && weak_cursor[l] == w_pos.x)) |
256 | met_cursor[l] = TRUE; |
257 | } |
258 | |
259 | ok = TRUE; |
260 | |
261 | for (int k = 0; k <= n_chars; k++) |
262 | { |
263 | old_s_pos = s_pos; |
264 | old_w_pos = w_pos; |
265 | pango_layout_move_cursor_visually (layout, strong: params[j].strong, |
266 | old_index: index, old_trailing: 0, |
267 | direction: params[j].direction, |
268 | new_index: &index, new_trailing: &trailing); |
269 | |
270 | while (trailing--) |
271 | index = g_utf8_next_char (text + index) - text; |
272 | |
273 | g_assert (index == -1 || index == G_MAXINT || |
274 | (0 <= index && index <= strlen (tests[i]))); |
275 | |
276 | if (index == -1 || index == G_MAXINT) |
277 | break; |
278 | |
279 | pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &s_pos, weak_pos: &w_pos); |
280 | for (int l = 0; l < n_attrs; l++) |
281 | { |
282 | if ((params[j].strong && strong_cursor[l] == s_pos.x) || |
283 | (!params[j].strong && weak_cursor[l] == w_pos.x)) |
284 | met_cursor[l] = TRUE; |
285 | } |
286 | |
287 | if ((params[j].direction > 0 && params[j].strong && old_s_pos.x >= s_pos.x) || |
288 | (params[j].direction < 0 && params[j].strong && old_s_pos.x <= s_pos.x) || |
289 | (params[j].direction > 0 && !params[j].strong && old_w_pos.x >= w_pos.x) || |
290 | (params[j].direction < 0 && !params[j].strong && old_w_pos.x <= w_pos.x)) |
291 | { |
292 | if (g_test_verbose ()) |
293 | g_print (format: "(wrong move)\t" ); |
294 | ok = FALSE; |
295 | break; |
296 | } |
297 | } |
298 | |
299 | if (ok) |
300 | for (int l = 0; l < n_attrs; l++) |
301 | { |
302 | if (!(met_cursor[l] || |
303 | (params[j].strong && strong_cursor[l] == -1) || |
304 | (!params[j].strong && weak_cursor[l] == -1))) |
305 | { |
306 | if (g_test_verbose ()) |
307 | g_print (format: "(missed cursor)\t" ); |
308 | ok = FALSE; |
309 | } |
310 | } |
311 | |
312 | if (ok) |
313 | if (!(index >= strlen (s: text) || index <= 0)) |
314 | { |
315 | if (g_test_verbose ()) |
316 | g_print (format: "(got stuck)\t" ); |
317 | ok = FALSE; |
318 | } |
319 | |
320 | if (g_test_verbose ()) |
321 | g_print (format: "%s\n" , ok ? "ok" : "not ok" ); |
322 | |
323 | fail = fail || !ok; |
324 | } |
325 | |
326 | g_free (mem: strong_cursor); |
327 | g_free (mem: weak_cursor); |
328 | g_free (mem: met_cursor); |
329 | } |
330 | |
331 | g_object_unref (object: layout); |
332 | |
333 | if (fail) |
334 | g_test_fail (); |
335 | } |
336 | |
337 | static void |
338 | test_move_cursor_para (void) |
339 | { |
340 | struct { |
341 | const char *text; |
342 | int width; |
343 | } tests[] = { |
344 | { "This paragraph should actually have multiple lines, unlike all the other wannabe äöü paragraph tests in this ugh test-case. Grow some lines!\n" , 188 }, |
345 | { "你好 Hello שלום Γειά σας" , 40 }, |
346 | { "你好 Hello שלום Γειά σας" , 60 }, |
347 | { "你好 Hello שלום Γειά σας" , 80 }, |
348 | { "line 1
line 2
line 3\nline 4\r\nline 5" , -1 }, // various separators |
349 | { "some text, some more text,\n\n even more text" , 60 }, |
350 | { "long word" , 40 }, |
351 | { "זוהי השורה הראשונה" "\n" "זוהי השורה השנייה" "\n" "זוהי השורה השלישית" , 200 }, |
352 | }; |
353 | PangoLayout *layout; |
354 | PangoRectangle pos, old_pos; |
355 | int index; |
356 | int trailing; |
357 | const char *text; |
358 | int line_no; |
359 | PangoLayoutLine *line; |
360 | PangoRectangle ext; |
361 | PangoLayoutIter *iter; |
362 | |
363 | layout = pango_layout_new (context); |
364 | |
365 | for (int i = 0; i < G_N_ELEMENTS (tests); i++) |
366 | { |
367 | pango_layout_set_text (layout, text: tests[i].text, length: -1); |
368 | text = pango_layout_get_text (layout); |
369 | if (tests[i].width > 0) |
370 | pango_layout_set_width (layout, width: tests[i].width * PANGO_SCALE); |
371 | else |
372 | pango_layout_set_width (layout, width: -1); |
373 | |
374 | index = 0; |
375 | pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &pos, NULL); |
376 | |
377 | while (index < G_MAXINT) |
378 | { |
379 | old_pos = pos; |
380 | |
381 | pango_layout_index_to_line_x (layout, index_: index, FALSE, line: &line_no, NULL); |
382 | line = pango_layout_get_line (layout, line: line_no); |
383 | iter = pango_layout_get_iter (layout); |
384 | while (pango_layout_iter_get_line (iter) != line) |
385 | pango_layout_iter_next_line (iter); |
386 | pango_layout_iter_get_line_extents (iter, NULL, logical_rect: &ext); |
387 | pango_layout_iter_free (iter); |
388 | |
389 | pango_layout_move_cursor_visually (layout, TRUE, |
390 | old_index: index, old_trailing: 0, |
391 | direction: 1, |
392 | new_index: &index, new_trailing: &trailing); |
393 | while (trailing--) |
394 | index = g_utf8_next_char (text + index) - text; |
395 | |
396 | g_assert (index == -1 || index == G_MAXINT || |
397 | (0 <= index && index <= strlen (tests[i].text))); |
398 | |
399 | if (index == -1 || index == G_MAXINT) |
400 | break; |
401 | |
402 | if (index >= strlen (s: tests[i].text) - 1) |
403 | continue; |
404 | |
405 | pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &pos, NULL); |
406 | |
407 | // assert that we are either moving to the right |
408 | // or jumping to the next line |
409 | g_assert_true (pos.y >= ext.y + ext.height || pos.x > old_pos.x); |
410 | // no invisible cursors, please |
411 | g_assert_true (pos.height > 1024); |
412 | } |
413 | |
414 | /* and now backwards */ |
415 | index = strlen (s: text); |
416 | pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &pos, NULL); |
417 | |
418 | while (index > -1) |
419 | { |
420 | old_pos = pos; |
421 | |
422 | pango_layout_index_to_line_x (layout, index_: index, FALSE, line: &line_no, NULL); |
423 | line = pango_layout_get_line (layout, line: line_no); |
424 | iter = pango_layout_get_iter (layout); |
425 | while (pango_layout_iter_get_line (iter) != line) |
426 | pango_layout_iter_next_line (iter); |
427 | pango_layout_iter_get_line_extents (iter, NULL, logical_rect: &ext); |
428 | pango_layout_iter_free (iter); |
429 | |
430 | pango_layout_move_cursor_visually (layout, TRUE, |
431 | old_index: index, old_trailing: 0, |
432 | direction: -1, |
433 | new_index: &index, new_trailing: &trailing); |
434 | while (trailing--) |
435 | index = g_utf8_next_char (text + index) - text; |
436 | |
437 | g_assert (index == -1 || index == G_MAXINT || |
438 | (0 <= index && index <= strlen (tests[i].text))); |
439 | |
440 | if (index == -1 || index == G_MAXINT) |
441 | break; |
442 | |
443 | pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &pos, NULL); |
444 | |
445 | // assert that we are either moving to the left |
446 | // or jumping to the previous line |
447 | g_assert_true (pos.y < ext.y || pos.x < old_pos.x); |
448 | // no invisible cursors, please |
449 | g_assert_true (pos.height > 1024); |
450 | } |
451 | } |
452 | |
453 | g_object_unref (object: layout); |
454 | } |
455 | |
456 | static void |
457 | test_sinhala_cursor (void) |
458 | { |
459 | const char *text = "ර් á " ; |
460 | PangoLayout *layout; |
461 | const char *p; |
462 | const PangoLogAttr *attrs; |
463 | int n, i; |
464 | |
465 | layout = pango_layout_new (context); |
466 | |
467 | pango_layout_set_text (layout, text, length: -1); |
468 | |
469 | if (pango_layout_get_unknown_glyphs_count (layout) > 0) |
470 | { |
471 | g_object_unref (object: layout); |
472 | g_test_skip (msg: "missing Sinhala fonts" ); |
473 | return; |
474 | } |
475 | |
476 | attrs = pango_layout_get_log_attrs_readonly (layout, n_attrs: &n); |
477 | |
478 | for (i = 0, p = text; *p; i++, p = g_utf8_next_char (p)) |
479 | { |
480 | int index = p - text; |
481 | PangoRectangle strong, weak; |
482 | |
483 | if (!attrs[i].is_cursor_position) |
484 | continue; |
485 | |
486 | g_assert_true (pango_layout_get_direction (layout, index) == PANGO_DIRECTION_LTR); |
487 | |
488 | pango_layout_get_cursor_pos (layout, index_: index, strong_pos: &strong, weak_pos: &weak); |
489 | g_assert_true (strong.x == weak.x); |
490 | g_assert_true (strong.width == weak.width); |
491 | } |
492 | } |
493 | |
494 | int |
495 | main (int argc, char *argv[]) |
496 | { |
497 | PangoFontMap *fontmap; |
498 | |
499 | setlocale (LC_ALL, locale: "" ); |
500 | |
501 | fontmap = pango_cairo_font_map_get_default (); |
502 | context = pango_font_map_create_context (fontmap); |
503 | |
504 | g_test_init (argc: &argc, argv: &argv, NULL); |
505 | |
506 | g_test_add_func (testpath: "/bidi/mirror-char" , test_func: test_mirror_char); |
507 | g_test_add_func (testpath: "/bidi/type-for-unichar" , test_func: test_bidi_type_for_unichar); |
508 | g_test_add_func (testpath: "/bidi/unichar-direction" , test_func: test_unichar_direction); |
509 | g_test_add_func (testpath: "/bidi/embedding-levels" , test_func: test_bidi_embedding_levels); |
510 | g_test_add_func (testpath: "/bidi/move-cursor-line" , test_func: test_move_cursor_line); |
511 | g_test_add_func (testpath: "/bidi/move-cursor-para" , test_func: test_move_cursor_para); |
512 | g_test_add_func (testpath: "/bidi/sinhala-cursor" , test_func: test_sinhala_cursor); |
513 | |
514 | return g_test_run (); |
515 | } |
516 | |