1 | /* Pango |
2 | * testiter.c: Test pangolayoutiter.c |
3 | * |
4 | * Copyright (C) 2005 Amit Aronovitch |
5 | * Copyright (C) 2005 Red Hat, Inc |
6 | * |
7 | * This library is free software; you can redistribute it and/or |
8 | * modify it under the terms of the GNU Library General Public |
9 | * License as published by the Free Software Foundation; either |
10 | * version 2 of the License, or (at your option) any later version. |
11 | * |
12 | * This library is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 | * Library General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU Library General Public |
18 | * License along with this library; if not, write to the |
19 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
20 | * Boston, MA 02111-1307, USA. |
21 | */ |
22 | |
23 | #undef G_DISABLE_ASSERT |
24 | #undef G_LOG_DOMAIN |
25 | |
26 | #include <stdarg.h> |
27 | #include <stdio.h> |
28 | #include <stdlib.h> |
29 | #include <string.h> |
30 | |
31 | #include <glib.h> |
32 | |
33 | #include <pango/pangocairo.h> |
34 | |
35 | static void verbose (const char *format, ...) G_GNUC_PRINTF (1, 2); |
36 | static void |
37 | verbose (const char *format, ...) |
38 | { |
39 | #ifdef VERBOSE |
40 | va_list vap; |
41 | |
42 | va_start (vap, format); |
43 | vfprintf (stderr, format, vap); |
44 | va_end (vap); |
45 | #endif |
46 | } |
47 | |
48 | #define LAYOUT_WIDTH (80 * PANGO_SCALE) |
49 | |
50 | /* Note: The test expects that any newline sequence is of length 1 |
51 | * use \n (not \r\n) in the test texts. |
52 | * I think the iterator itself should support \r\n without trouble, |
53 | * but there are comments in layout-iter.c suggesting otherwise. |
54 | */ |
55 | const char *test_texts[] = |
56 | { |
57 | /* English with embedded RTL runs (from ancient-hebrew.org) */ |
58 | "The Hebrew word \xd7\x90\xd7\x93\xd7\x9d\xd7\x94 (adamah) is the feminine form of \xd7\x90\xd7\x93\xd7\x9d meaning \"ground\"\n" , |
59 | /* Arabic, with vowel marks (from Sura Al Fatiha) */ |
60 | "\xd8\xa8\xd9\x90\xd8\xb3\xd9\x92\xd9\x85\xd9\x90 \xd8\xa7\xd9\x84\xd9\x84\xd9\x91\xd9\x87\xd9\x90 \xd8\xa7\xd9\x84\xd8\xb1\xd9\x91\xd9\x8e\xd8\xad\xd9\x92\xd9\x85\xd9\x80\xd9\x8e\xd9\x86\xd9\x90 \xd8\xa7\xd9\x84\xd8\xb1\xd9\x91\xd9\x8e\xd8\xad\xd9\x90\xd9\x8a\xd9\x85\xd9\x90\n\xd8\xa7\xd9\x84\xd9\x92\xd8\xad\xd9\x8e\xd9\x85\xd9\x92\xd8\xaf\xd9\x8f \xd9\x84\xd9\x84\xd9\x91\xd9\x87\xd9\x90 \xd8\xb1\xd9\x8e\xd8\xa8\xd9\x91\xd9\x90 \xd8\xa7\xd9\x84\xd9\x92\xd8\xb9\xd9\x8e\xd8\xa7\xd9\x84\xd9\x8e\xd9\x85\xd9\x90\xd9\x8a\xd9\x86\xd9\x8e\n" , |
61 | /* Arabic, with embedded LTR runs (from a Linux guide) */ |
62 | "\xd8\xa7\xd9\x84\xd9\x85\xd8\xaa\xd8\xba\xd9\x8a\xd8\xb1 LC_ALL \xd9\x8a\xd8\xba\xd9\x8a\xd9\x8a\xd8\xb1 \xd9\x83\xd9\x84 \xd8\xa7\xd9\x84\xd9\x85\xd8\xaa\xd8\xba\xd9\x8a\xd8\xb1\xd8\xa7\xd8\xaa \xd8\xa7\xd9\x84\xd8\xaa\xd9\x8a \xd8\xaa\xd8\xa8\xd8\xaf\xd8\xa3 \xd8\xa8\xd8\xa7\xd9\x84\xd8\xb1\xd9\x85\xd8\xb2 LC." , |
63 | /* Hebrew, with vowel marks (from Genesis) */ |
64 | "\xd7\x91\xd6\xbc\xd6\xb0\xd7\xa8\xd6\xb5\xd7\x90\xd7\xa9\xd7\x81\xd6\xb4\xd7\x99\xd7\xaa, \xd7\x91\xd6\xbc\xd6\xb8\xd7\xa8\xd6\xb8\xd7\x90 \xd7\x90\xd6\xb1\xd7\x9c\xd6\xb9\xd7\x94\xd6\xb4\xd7\x99\xd7\x9d, \xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb7\xd7\xa9\xd6\xbc\xd7\x81\xd6\xb8\xd7\x9e\xd6\xb7\xd7\x99\xd6\xb4\xd7\x9d, \xd7\x95\xd6\xb0\xd7\x90\xd6\xb5\xd7\xaa \xd7\x94\xd6\xb8\xd7\x90\xd6\xb8\xd7\xa8\xd6\xb6\xd7\xa5" , |
65 | /* Hebrew, with embedded LTR runs (from a Linux guide) */ |
66 | "\xd7\x94\xd7\xa7\xd7\x9c\xd7\x93\xd7\x94 \xd7\xa2\xd7\x9c \xd7\xa9\xd7\xa0\xd7\x99 \xd7\x94 SHIFT\xd7\x99\xd7\x9d (\xd7\x99\xd7\x9e\xd7\x99\xd7\x9f \xd7\x95\xd7\xa9\xd7\x9e\xd7\x90\xd7\x9c \xd7\x91\xd7\x99\xd7\x97\xd7\x93) \xd7\x90\xd7\x9e\xd7\x95\xd7\xa8\xd7\x99\xd7\x9d \xd7\x9c\xd7\x94\xd7\x93\xd7\x9c\xd7\x99\xd7\xa7 \xd7\x90\xd7\xaa \xd7\xa0\xd7\x95\xd7\xa8\xd7\xaa \xd7\x94 Scroll Lock , \xd7\x95\xd7\x9c\xd7\x94\xd7\xa2\xd7\x91\xd7\x99\xd7\xa8 \xd7\x90\xd7\x95\xd7\xaa\xd7\xa0\xd7\x95 \xd7\x9c\xd7\x9e\xd7\xa6\xd7\x91 \xd7\x9b\xd7\xaa\xd7\x99\xd7\x91\xd7\x94 \xd7\x91\xd7\xa2\xd7\x91\xd7\xa8\xd7\x99\xd7\xaa." , |
67 | /* Different line terminators */ |
68 | "AAAA\nBBBB\nCCCC\n" , |
69 | "DDDD\rEEEE\rFFFF\r" , |
70 | "GGGG\r\nHHHH\r\nIIII\r\n" , |
71 | "asdf" , |
72 | NULL |
73 | }; |
74 | |
75 | /* char iteration test: |
76 | * - Total num of iterations match number of chars |
77 | * - GlyphString's index_to_x positions match those returned by the Iter |
78 | */ |
79 | static void |
80 | iter_char_test (PangoLayout *layout) |
81 | { |
82 | PangoRectangle extents, run_extents; |
83 | PangoLayoutIter *iter; |
84 | PangoLayoutRun *run; |
85 | int num_chars; |
86 | int i, index, offset; |
87 | int leading_x, trailing_x, x0, x1; |
88 | gboolean iter_next_ok, rtl; |
89 | const char *text, *ptr; |
90 | |
91 | text = pango_layout_get_text (layout); |
92 | num_chars = g_utf8_strlen (p: text, max: -1); |
93 | |
94 | iter = pango_layout_get_iter (layout); |
95 | iter_next_ok = TRUE; |
96 | |
97 | for (i = 0 ; i < num_chars; ++i) |
98 | { |
99 | gchar *char_str; |
100 | g_assert (iter_next_ok); |
101 | |
102 | index = pango_layout_iter_get_index (iter); |
103 | ptr = text + index; |
104 | char_str = g_strndup (str: ptr, g_utf8_next_char (ptr) - ptr); |
105 | verbose (format: "i=%d (visual), index = %d '%s':\n" , |
106 | i, index, char_str); |
107 | g_free (mem: char_str); |
108 | |
109 | pango_layout_iter_get_char_extents (iter, logical_rect: &extents); |
110 | verbose (format: " char extents: x=%d,y=%d w=%d,h=%d\n" , |
111 | extents.x, extents.y, |
112 | extents.width, extents.height); |
113 | |
114 | run = pango_layout_iter_get_run (iter); |
115 | |
116 | if (run) |
117 | { |
118 | PangoFontDescription *desc; |
119 | char *str; |
120 | |
121 | /* Get needed data for the GlyphString */ |
122 | pango_layout_iter_get_run_extents(iter, NULL, logical_rect: &run_extents); |
123 | offset = run->item->offset; |
124 | rtl = run->item->analysis.level%2; |
125 | desc = pango_font_describe (font: run->item->analysis.font); |
126 | str = pango_font_description_to_string (desc); |
127 | verbose (format: " (current run: font=%s,offset=%d,x=%d,len=%d,rtl=%d)\n" , |
128 | str, offset, run_extents.x, run->item->length, rtl); |
129 | g_free (mem: str); |
130 | pango_font_description_free (desc); |
131 | |
132 | /* Calculate expected x result using index_to_x */ |
133 | pango_glyph_string_index_to_x (glyphs: run->glyphs, |
134 | text: (char *)(text + offset), length: run->item->length, |
135 | analysis: &run->item->analysis, |
136 | index_: index - offset, FALSE, x_pos: &leading_x); |
137 | pango_glyph_string_index_to_x (glyphs: run->glyphs, |
138 | text: (char *)(text + offset), length: run->item->length, |
139 | analysis: &run->item->analysis, |
140 | index_: index - offset, TRUE, x_pos: &trailing_x); |
141 | |
142 | x0 = run_extents.x + MIN (leading_x, trailing_x); |
143 | x1 = run_extents.x + MAX (leading_x, trailing_x); |
144 | |
145 | verbose (format: " (index_to_x ind=%d: expected x=%d, width=%d)\n" , |
146 | index - offset, x0, x1 - x0); |
147 | |
148 | g_assert (extents.x == x0); |
149 | g_assert (extents.width == x1 - x0); |
150 | } |
151 | else |
152 | { |
153 | /* We're on a line terminator */ |
154 | } |
155 | |
156 | iter_next_ok = pango_layout_iter_next_char (iter); |
157 | verbose (format: "more to go? %d\n" , iter_next_ok); |
158 | } |
159 | |
160 | /* There should be one character position iterator for each character in the |
161 | * input string */ |
162 | g_assert (!iter_next_ok); |
163 | |
164 | pango_layout_iter_free (iter); |
165 | } |
166 | |
167 | static void |
168 | iter_cluster_test (PangoLayout *layout) |
169 | { |
170 | PangoRectangle extents; |
171 | PangoLayoutIter *iter; |
172 | int index; |
173 | gboolean iter_next_ok; |
174 | PangoLayoutLine *last_line = NULL; |
175 | int expected_next_x = 0; |
176 | |
177 | iter = pango_layout_get_iter (layout); |
178 | iter_next_ok = TRUE; |
179 | |
180 | while (iter_next_ok) |
181 | { |
182 | PangoLayoutLine *line = pango_layout_iter_get_line (iter); |
183 | |
184 | /* Every cluster is part of a run */ |
185 | g_assert (pango_layout_iter_get_run (iter)); |
186 | |
187 | index = pango_layout_iter_get_index (iter); |
188 | |
189 | pango_layout_iter_get_cluster_extents (iter, NULL, logical_rect: &extents); |
190 | |
191 | iter_next_ok = pango_layout_iter_next_cluster (iter); |
192 | |
193 | verbose (format: "index = %d:\n" , index); |
194 | verbose (format: " cluster extents: x=%d,y=%d w=%d,h=%d\n" , |
195 | extents.x, extents.y, |
196 | extents.width, extents.height); |
197 | verbose (format: "more to go? %d\n" , iter_next_ok); |
198 | |
199 | /* All the clusters on a line should be next to each other and occupy |
200 | * the entire line. They advance linearly from left to right */ |
201 | g_assert (extents.width >= 0); |
202 | |
203 | if (last_line == line) |
204 | g_assert (extents.x == expected_next_x); |
205 | |
206 | expected_next_x = extents.x + extents.width; |
207 | |
208 | last_line = line; |
209 | } |
210 | |
211 | g_assert (!iter_next_ok); |
212 | |
213 | pango_layout_iter_free (iter); |
214 | } |
215 | |
216 | static void |
217 | test_layout_iter (void) |
218 | { |
219 | const char **ptext; |
220 | PangoFontMap *fontmap; |
221 | PangoContext *context; |
222 | PangoFontDescription *font_desc; |
223 | PangoLayout *layout; |
224 | |
225 | fontmap = pango_cairo_font_map_get_default (); |
226 | context = pango_font_map_create_context (fontmap); |
227 | font_desc = pango_font_description_from_string (str: "cantarell 11" ); |
228 | pango_context_set_font_description (context, desc: font_desc); |
229 | |
230 | layout = pango_layout_new (context); |
231 | pango_layout_set_width (layout, LAYOUT_WIDTH); |
232 | |
233 | for (ptext = test_texts; *ptext != NULL; ++ptext) |
234 | { |
235 | verbose (format: "--------- checking next text ----------\n" ); |
236 | verbose (format: " <%s>\n" , *ptext); |
237 | verbose ( format: "len=%ld, bytes=%ld\n" , |
238 | (long)g_utf8_strlen (p: *ptext, max: -1), (long)strlen (s: *ptext)); |
239 | |
240 | pango_layout_set_text (layout, text: *ptext, length: -1); |
241 | iter_char_test (layout); |
242 | iter_cluster_test (layout); |
243 | } |
244 | |
245 | g_object_unref (object: layout); |
246 | g_object_unref (object: context); |
247 | pango_font_description_free (desc: font_desc); |
248 | } |
249 | |
250 | static void |
251 | test_glyphitem_iter (void) |
252 | { |
253 | PangoFontMap *fontmap; |
254 | PangoContext *context; |
255 | PangoFontDescription *font_desc; |
256 | PangoLayout *layout; |
257 | PangoLayoutLine *line; |
258 | const char *text; |
259 | GSList *l; |
260 | |
261 | fontmap = pango_cairo_font_map_get_default (); |
262 | context = pango_font_map_create_context (fontmap); |
263 | font_desc = pango_font_description_from_string (str: "cantarell 11" ); |
264 | pango_context_set_font_description (context, desc: font_desc); |
265 | |
266 | layout = pango_layout_new (context); |
267 | /* This shouldn't form any ligatures. */ |
268 | pango_layout_set_text (layout, text: "test تست" , length: -1); |
269 | text = pango_layout_get_text (layout); |
270 | |
271 | line = pango_layout_get_line (layout, line: 0); |
272 | for (l = line->runs; l; l = l->next) |
273 | { |
274 | PangoGlyphItem *run = l->data; |
275 | int direction; |
276 | |
277 | for (direction = 0; direction < 2; direction++) |
278 | { |
279 | PangoGlyphItemIter iter; |
280 | gboolean have_cluster; |
281 | PangoGlyphItemIter *iter2; |
282 | |
283 | for (have_cluster = direction ? |
284 | pango_glyph_item_iter_init_start (iter: &iter, glyph_item: run, text) : |
285 | pango_glyph_item_iter_init_end (iter: &iter, glyph_item: run, text); |
286 | have_cluster; |
287 | have_cluster = direction ? |
288 | pango_glyph_item_iter_next_cluster (iter: &iter) : |
289 | pango_glyph_item_iter_prev_cluster (iter: &iter)) |
290 | { |
291 | verbose (format: "start index %d end index %d\n" , iter.start_index, iter.end_index); |
292 | g_assert_true (iter.start_index < iter.end_index); |
293 | g_assert_true (iter.start_index + 2 >= iter.end_index); |
294 | g_assert_true (iter.start_char + 1 == iter.end_char); |
295 | |
296 | iter2 = pango_glyph_item_iter_copy (orig: &iter); |
297 | g_assert_true (iter2->start_glyph == iter.start_glyph); |
298 | g_assert_true (iter2->start_index == iter.start_index); |
299 | g_assert_true (iter2->start_char == iter.start_char); |
300 | g_assert_true (iter2->end_glyph == iter.end_glyph); |
301 | g_assert_true (iter2->end_index == iter.end_index); |
302 | g_assert_true (iter2->end_char == iter.end_char); |
303 | pango_glyph_item_iter_free (iter: iter2); |
304 | } |
305 | } |
306 | } |
307 | |
308 | g_object_unref (object: layout); |
309 | g_object_unref (object: context); |
310 | pango_font_description_free (desc: font_desc); |
311 | } |
312 | |
313 | int |
314 | main (int argc, char *argv[]) |
315 | { |
316 | g_test_init (argc: &argc, argv: &argv, NULL); |
317 | |
318 | g_test_add_func (testpath: "/layout/iter" , test_func: test_layout_iter); |
319 | g_test_add_func (testpath: "/layout/glyphitem-iter" , test_func: test_glyphitem_iter); |
320 | |
321 | return g_test_run (); |
322 | } |
323 | |