2 * Copyright © 2006 Novell, Inc.
4 * Permission to use, copy, modify, distribute, and sell this software
5 * and its documentation for any purpose is hereby granted without
6 * fee, provided that the above copyright notice appear in all copies
7 * and that both that copyright notice and this permission notice
8 * appear in supporting documentation, and that the name of
9 * Novell, Inc. not be used in advertising or publicity pertaining to
10 * distribution of the software without specific, written prior
11 * permission. Novell, Inc. makes no representations about the
12 * suitability of this software for any purpose. It is provided "as
13 * is" without express or implied warranty.
15 * NOVELL, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
16 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
17 * FITNESS, IN NO EVENT SHALL RED HAT, INC. BE LIABLE FOR ANY SPECIAL,
18 * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
19 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
20 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
21 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 * Author: Robert O'Callahan <rocallahan@novell.com>
26 #include "cairo-test.h"
30 enum ExtentsType { FILL, STROKE, PATH };
32 enum Relation { EQUALS, APPROX_EQUALS, CONTAINS };
35 static cairo_bool_t within_tolerance(double x1, double y1,
37 double expected_x1, double expected_y1,
38 double expected_x2, double expected_y2,
41 return (fabs (expected_x1 - x1) < tolerance &&
42 fabs (expected_y1 - y1) < tolerance &&
43 fabs (expected_x2 - x2) < tolerance &&
44 fabs (expected_y2 - y2) < tolerance);
48 check_extents (const cairo_test_context_t *ctx,
49 const char *message, cairo_t *cr, enum ExtentsType type,
50 enum Relation relation,
51 double x, double y, double width, double height)
53 double ext_x1, ext_y1, ext_x2, ext_y2;
54 const char *type_string;
55 const char *relation_string;
61 cairo_fill_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
64 type_string = "stroke";
65 cairo_stroke_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
69 cairo_path_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
73 /* ignore results after an error occurs */
74 if (cairo_status (cr))
80 relation_string = "equal";
81 if (within_tolerance(x, y, x + width, y + height,
82 ext_x1, ext_y1, ext_x2, ext_y2,
83 cairo_get_tolerance(cr)))
87 relation_string = "approx. equal";
88 if (within_tolerance(x, y, x + width, y + height,
89 ext_x1, ext_y1, ext_x2, ext_y2,
94 relation_string = "contain";
95 if (width == 0 || height == 0) {
96 /* odd test that doesn't really test anything... */
99 if (ext_x1 <= x && ext_y1 <= y && ext_x2 >= x + width && ext_y2 >= y + height)
104 cairo_test_log (ctx, "Error: %s; %s extents (%g, %g) x (%g, %g) should %s (%g, %g) x (%g, %g)\n",
105 message, type_string,
106 ext_x1, ext_y1, ext_x2 - ext_x1, ext_y2 - ext_y1,
108 x, y, width, height);
112 static cairo_test_status_t
113 draw (cairo_t *cr, int width, int height)
115 const cairo_test_context_t *ctx = cairo_test_get_context (cr);
116 cairo_surface_t *surface;
119 const char string[] = "The quick brown fox jumps over the lazy dog.";
120 cairo_text_extents_t extents, scaled_font_extents;
121 cairo_status_t status;
124 surface = cairo_surface_create_similar (cairo_get_group_target (cr),
125 CAIRO_CONTENT_COLOR, 1000, 1000);
126 /* don't use cr accidentally */
128 cr2 = cairo_create (surface);
129 cairo_surface_destroy (surface);
131 cairo_set_line_width (cr2, 10);
132 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_MITER);
133 cairo_set_miter_limit (cr2, 100);
136 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
137 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
138 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 0, 0, 0, 0);
142 cairo_new_path (cr2);
143 cairo_move_to (cr2, 200, 400);
144 cairo_close_path (cr2);
145 phase = "Degenerate closed path";
146 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
147 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
148 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
150 cairo_new_path (cr2);
151 cairo_move_to (cr2, 200, 400);
152 cairo_rel_line_to (cr2, 0., 0.);
153 phase = "Degenerate line";
154 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
155 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
156 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
158 cairo_new_path (cr2);
159 cairo_move_to (cr2, 200, 400);
160 cairo_rel_curve_to (cr2, 0., 0., 0., 0., 0., 0.);
161 phase = "Degenerate curve";
162 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
163 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
164 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
166 cairo_new_path (cr2);
167 cairo_arc (cr2, 200, 400, 0., 0, 2 * M_PI);
168 phase = "Degenerate arc (R=0)";
169 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
170 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
171 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
173 cairo_new_path (cr2);
174 cairo_arc_negative (cr2, 200, 400, 0., 0, 2 * M_PI);
175 phase = "Degenerate negative arc (R=0)";
176 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
177 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
178 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
180 cairo_new_path (cr2);
181 cairo_arc (cr2, 200, 400, 10., 0, 0);
182 phase = "Degenerate arc (Θ=0)";
183 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
184 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
185 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 210, 400, 0, 0);
187 cairo_new_path (cr2);
188 cairo_arc_negative (cr2, 200, 400, 10., 0, 0);
189 phase = "Degenerate negative arc (Θ=0)";
190 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
191 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
192 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 210, 400, 0, 0);
194 cairo_new_path (cr2);
197 /* Test that with CAIRO_LINE_CAP_ROUND, we get "dots" from
198 * cairo_move_to; cairo_rel_line_to(0,0) */
201 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
202 cairo_set_line_width (cr2, 20);
204 cairo_move_to (cr2, 200, 400);
205 cairo_rel_line_to (cr2, 0, 0);
206 phase = "Single 'dot'";
207 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
208 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS, 190, 390, 20, 20);
209 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
211 /* Add another dot without starting a new path */
212 cairo_move_to (cr2, 100, 500);
213 cairo_rel_line_to (cr2, 0, 0);
214 phase = "Multiple 'dots'";
215 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
216 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS, 90, 390, 120, 120);
217 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 100, 400, 100, 100);
219 cairo_new_path (cr2);
223 /* http://bugs.freedesktop.org/show_bug.cgi?id=7965 */
224 phase = "A horizontal, open path";
226 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
227 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_ROUND);
228 cairo_move_to (cr2, 0, 180);
229 cairo_line_to (cr2, 750, 180);
230 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
231 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, -5, 175, 760, 10);
232 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 0, 180, 750, 0);
233 cairo_new_path (cr2);
236 phase = "A vertical, open path";
238 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
239 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_ROUND);
240 cairo_new_path (cr2);
241 cairo_move_to (cr2, 180, 0);
242 cairo_line_to (cr2, 180, 750);
243 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
244 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 175, -5, 10, 760);
245 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 180, 0, 0, 750);
246 cairo_new_path (cr2);
249 phase = "A degenerate open path";
251 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
252 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_ROUND);
253 cairo_new_path (cr2);
254 cairo_move_to (cr2, 180, 0);
255 cairo_line_to (cr2, 180, 0);
256 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
257 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 175, -5, 10, 10);
258 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 180, 0, 0, 0);
259 cairo_new_path (cr2);
262 phase = "Simple rect";
264 cairo_rectangle (cr2, 10, 10, 80, 80);
265 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 80, 80);
266 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 5, 5, 90, 90);
267 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 80, 80);
268 cairo_new_path (cr2);
273 cairo_rectangle (cr2, 10, 10, 10, 10);
274 cairo_rectangle (cr2, 20, 20, 10, 10);
275 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 20, 20);
276 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 5, 5, 30, 30);
277 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 20, 20);
278 cairo_new_path (cr2);
283 cairo_move_to (cr2, 10, 10);
284 cairo_line_to (cr2, 90, 90);
285 cairo_line_to (cr2, 90, 10);
286 cairo_close_path (cr2);
287 /* miter joins protrude 5*(1+sqrt(2)) above the top-left corner and to
288 the right of the bottom-right corner */
289 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 80, 80);
290 errors += !check_extents (ctx, phase, cr2, STROKE, CONTAINS, 0, 5, 95, 95);
291 errors += !check_extents (ctx, phase, cr2, PATH, CONTAINS, 10, 10, 80, 80);
292 cairo_new_path (cr2);
297 cairo_set_line_width (cr2, 4);
299 cairo_rectangle (cr2, 10, 10, 30, 30);
300 cairo_rectangle (cr2, 25, 10, 15, 30);
302 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_EVEN_ODD);
303 phase = "EVEN_ODD overlapping rectangles";
304 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 15, 30);
305 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 8, 8, 34, 34);
306 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 30, 30);
308 /* Test other fill rule with the same path. */
310 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_WINDING);
311 phase = "WINDING overlapping rectangles";
312 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 30, 30);
313 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 8, 8, 34, 34);
314 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 30, 30);
316 /* Now, change the direction of the second rectangle and test both
317 * fill rules again. */
318 cairo_new_path (cr2);
319 cairo_rectangle (cr2, 10, 10, 30, 30);
320 cairo_rectangle (cr2, 25, 40, 15, -30);
322 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_EVEN_ODD);
323 phase = "EVEN_ODD overlapping rectangles";
324 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 15, 30);
325 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 8, 8, 34, 34);
326 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 30, 30);
328 /* Test other fill rule with the same path. */
330 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_WINDING);
331 phase = "WINDING overlapping rectangles";
332 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 15, 30);
333 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 8, 8, 34, 34);
334 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 30, 30);
336 cairo_new_path (cr2);
340 /* http://bugs.freedesktop.org/show_bug.cgi?id=7245 */
343 cairo_arc (cr2, 250.0, 250.0, 157.0, 5.147, 3.432);
344 cairo_set_line_width (cr2, 154.0);
345 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS, 16, 38, 468, 446);
346 cairo_new_path (cr2);
351 cairo_select_font_face (cr2, CAIRO_TEST_FONT_FAMILY " Sans",
352 CAIRO_FONT_SLANT_NORMAL,
353 CAIRO_FONT_WEIGHT_NORMAL);
354 cairo_set_font_size (cr2, 12);
355 cairo_text_extents (cr2, string, &extents);
356 /* double check that the two methods of measuring the text agree... */
357 cairo_scaled_font_text_extents (cairo_get_scaled_font (cr2),
359 &scaled_font_extents);
360 if (memcmp (&extents, &scaled_font_extents, sizeof (extents))) {
361 cairo_test_log (ctx, "Error: cairo_text_extents() does not match cairo_scaled_font_text_extents() - font extents (%f, %f) x (%f, %f) should be (%f, %f) x (%f, %f)\n",
362 scaled_font_extents.x_bearing,
363 scaled_font_extents.y_bearing,
364 scaled_font_extents.width,
365 scaled_font_extents.height,
373 cairo_move_to (cr2, -extents.x_bearing, -extents.y_bearing);
374 cairo_text_path (cr2, string);
375 cairo_set_line_width (cr2, 2.0);
376 /* XXX: We'd like to be able to use EQUALS here, but currently
377 * when hinting is enabled freetype returns integer extents. See
378 * http://cairographics.org/todo */
379 errors += !check_extents (ctx, phase, cr2, FILL, APPROX_EQUALS,
380 0, 0, extents.width, extents.height);
381 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS,
382 -1, -1, extents.width+2, extents.height+2);
383 errors += !check_extents (ctx, phase, cr2, PATH, APPROX_EQUALS,
384 0, 0, extents.width, extents.height);
385 cairo_new_path (cr2);
388 phase = "User space, simple scale, getting extents with same transform";
390 cairo_scale (cr2, 2, 2);
391 cairo_rectangle (cr2, 5, 5, 40, 40);
392 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 5, 5, 40, 40);
393 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 50, 50);
394 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 5, 5, 40, 40);
395 cairo_new_path (cr2);
398 phase = "User space, simple scale, getting extents with no transform";
401 cairo_scale (cr2, 2, 2);
402 cairo_rectangle (cr2, 5, 5, 40, 40);
404 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 80, 80);
405 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 5, 5, 90, 90);
406 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 80, 80);
407 cairo_new_path (cr2);
410 phase = "User space, rotation, getting extents with transform";
412 cairo_rectangle (cr2, -50, -50, 50, 50);
413 cairo_rotate (cr2, -M_PI/4);
414 /* the path in user space is now (nearly) the square rotated by
415 45 degrees about the origin. Thus its x1 and x2 are both nearly 0.
416 This should show any bugs where we just transform device-space
417 x1,y1 and x2,y2 to get the extents. */
418 /* The largest axis-aligned square inside the rotated path has
419 side lengths 50*sqrt(2), so a bit over 35 on either side of
420 the axes. With the stroke width added to the rotated path,
421 the largest axis-aligned square is a bit over 38 on either side of
423 errors += !check_extents (ctx, phase, cr2, FILL, CONTAINS, -35, -35, 35, 35);
424 errors += !check_extents (ctx, phase, cr2, STROKE, CONTAINS, -38, -38, 38, 38);
425 errors += !check_extents (ctx, phase, cr2, PATH, CONTAINS, -35, -35, 35, 35);
426 cairo_new_path (cr2);
429 status = cairo_status (cr2);
433 return cairo_test_status_from_status (ctx, status);
435 return errors == 0 ? CAIRO_TEST_SUCCESS : CAIRO_TEST_FAILURE;
438 CAIRO_TEST (get_path_extents,
439 "Test cairo_fill_extents and cairo_stroke_extents",
440 "extents, path", /* keywords */
441 NULL, /* requirements */