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 check_extents (const cairo_test_context_t *ctx,
36 const char *message, cairo_t *cr, enum ExtentsType type,
37 enum Relation relation,
38 double x, double y, double width, double height)
40 double ext_x1, ext_y1, ext_x2, ext_y2;
41 const char *type_string;
42 const char *relation_string;
48 cairo_fill_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
51 type_string = "stroke";
52 cairo_stroke_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
56 cairo_path_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
60 /* ignore results after an error occurs */
61 if (cairo_status (cr))
67 relation_string = "equal";
68 if (ext_x1 == x && ext_y1 == y && ext_x2 == x + width && ext_y2 == y + height)
72 relation_string = "approx. equal";
73 if (fabs (ext_x1 - x) < 1. &&
74 fabs (ext_y1 - y) < 1. &&
75 fabs (ext_x2 - (x + width)) < 1. &&
76 fabs (ext_y2 - (y + height)) < 1.)
82 relation_string = "contain";
83 if (width == 0 || height == 0) {
84 /* odd test that doesn't really test anything... */
87 if (ext_x1 <= x && ext_y1 <= y && ext_x2 >= x + width && ext_y2 >= y + height)
92 cairo_test_log (ctx, "Error: %s; %s extents (%g, %g) x (%g, %g) should %s (%g, %g) x (%g, %g)\n",
94 ext_x1, ext_y1, ext_x2 - ext_x1, ext_y2 - ext_y1,
100 static cairo_test_status_t
101 draw (cairo_t *cr, int width, int height)
103 const cairo_test_context_t *ctx = cairo_test_get_context (cr);
104 cairo_surface_t *surface;
107 const char string[] = "The quick brown fox jumps over the lazy dog.";
108 cairo_text_extents_t extents, scaled_font_extents;
109 cairo_status_t status;
112 surface = cairo_surface_create_similar (cairo_get_group_target (cr),
113 CAIRO_CONTENT_COLOR, 1000, 1000);
114 /* don't use cr accidentally */
116 cr2 = cairo_create (surface);
117 cairo_surface_destroy (surface);
119 cairo_set_line_width (cr2, 10);
120 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_MITER);
121 cairo_set_miter_limit (cr2, 100);
124 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
125 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
126 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 0, 0, 0, 0);
130 cairo_new_path (cr2);
131 cairo_move_to (cr2, 200, 400);
132 cairo_close_path (cr2);
133 phase = "Degenerate closed path";
134 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
135 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
136 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
138 cairo_new_path (cr2);
139 cairo_move_to (cr2, 200, 400);
140 cairo_rel_line_to (cr2, 0., 0.);
141 phase = "Degenerate line";
142 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
143 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
144 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
146 cairo_new_path (cr2);
147 cairo_move_to (cr2, 200, 400);
148 cairo_rel_curve_to (cr2, 0., 0., 0., 0., 0., 0.);
149 phase = "Degenerate curve";
150 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
151 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
152 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
154 cairo_new_path (cr2);
155 cairo_arc (cr2, 200, 400, 0., 0, 2 * M_PI);
156 phase = "Degenerate arc (R=0)";
157 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
158 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
159 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
161 cairo_new_path (cr2);
162 cairo_arc_negative (cr2, 200, 400, 0., 0, 2 * M_PI);
163 phase = "Degenerate negative arc (R=0)";
164 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
165 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
166 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
168 cairo_new_path (cr2);
169 cairo_arc (cr2, 200, 400, 10., 0, 0);
170 phase = "Degenerate arc (Θ=0)";
171 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
172 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
173 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 210, 400, 0, 0);
175 cairo_new_path (cr2);
176 cairo_arc_negative (cr2, 200, 400, 10., 0, 0);
177 phase = "Degenerate negative arc (Θ=0)";
178 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
179 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
180 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 210, 400, 0, 0);
182 cairo_new_path (cr2);
185 /* Test that with CAIRO_LINE_CAP_ROUND, we get "dots" from
186 * cairo_move_to; cairo_rel_line_to(0,0) */
189 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
190 cairo_set_line_width (cr2, 20);
192 cairo_move_to (cr2, 200, 400);
193 cairo_rel_line_to (cr2, 0, 0);
194 phase = "Single 'dot'";
195 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
196 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS, 190, 390, 20, 20);
197 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
199 /* Add another dot without starting a new path */
200 cairo_move_to (cr2, 100, 500);
201 cairo_rel_line_to (cr2, 0, 0);
202 phase = "Multiple 'dots'";
203 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
204 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS, 90, 390, 120, 120);
205 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 100, 400, 100, 100);
207 cairo_new_path (cr2);
211 /* http://bugs.freedesktop.org/show_bug.cgi?id=7965 */
212 phase = "A horizontal, open path";
214 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
215 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_ROUND);
216 cairo_move_to (cr2, 0, 180);
217 cairo_line_to (cr2, 750, 180);
218 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
219 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, -5, 175, 760, 10);
220 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 0, 180, 750, 0);
221 cairo_new_path (cr2);
224 phase = "A vertical, open path";
226 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
227 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_ROUND);
228 cairo_new_path (cr2);
229 cairo_move_to (cr2, 180, 0);
230 cairo_line_to (cr2, 180, 750);
231 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
232 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 175, -5, 10, 760);
233 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 180, 0, 0, 750);
234 cairo_new_path (cr2);
237 phase = "A degenerate open path";
239 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
240 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_ROUND);
241 cairo_new_path (cr2);
242 cairo_move_to (cr2, 180, 0);
243 cairo_line_to (cr2, 180, 0);
244 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
245 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 175, -5, 10, 10);
246 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 180, 0, 0, 0);
247 cairo_new_path (cr2);
250 phase = "Simple rect";
252 cairo_rectangle (cr2, 10, 10, 80, 80);
253 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 80, 80);
254 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 5, 5, 90, 90);
255 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 80, 80);
256 cairo_new_path (cr2);
261 cairo_rectangle (cr2, 10, 10, 10, 10);
262 cairo_rectangle (cr2, 20, 20, 10, 10);
263 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 20, 20);
264 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 5, 5, 30, 30);
265 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 20, 20);
266 cairo_new_path (cr2);
271 cairo_move_to (cr2, 10, 10);
272 cairo_line_to (cr2, 90, 90);
273 cairo_line_to (cr2, 90, 10);
274 cairo_close_path (cr2);
275 /* miter joins protrude 5*(1+sqrt(2)) above the top-left corner and to
276 the right of the bottom-right corner */
277 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 80, 80);
278 errors += !check_extents (ctx, phase, cr2, STROKE, CONTAINS, 0, 5, 95, 95);
279 errors += !check_extents (ctx, phase, cr2, PATH, CONTAINS, 10, 10, 80, 80);
280 cairo_new_path (cr2);
285 cairo_set_line_width (cr2, 4);
287 cairo_rectangle (cr2, 10, 10, 30, 30);
288 cairo_rectangle (cr2, 25, 10, 15, 30);
290 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_EVEN_ODD);
291 phase = "EVEN_ODD overlapping rectangles";
292 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 15, 30);
293 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 8, 8, 34, 34);
294 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 30, 30);
296 /* Test other fill rule with the same path. */
298 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_WINDING);
299 phase = "WINDING overlapping rectangles";
300 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 30, 30);
301 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 8, 8, 34, 34);
302 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 30, 30);
304 /* Now, change the direction of the second rectangle and test both
305 * fill rules again. */
306 cairo_new_path (cr2);
307 cairo_rectangle (cr2, 10, 10, 30, 30);
308 cairo_rectangle (cr2, 25, 40, 15, -30);
310 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_EVEN_ODD);
311 phase = "EVEN_ODD overlapping rectangles";
312 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 15, 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 /* Test other fill rule with the same path. */
318 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_WINDING);
319 phase = "WINDING overlapping rectangles";
320 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 15, 30);
321 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 8, 8, 34, 34);
322 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 30, 30);
324 cairo_new_path (cr2);
328 /* http://bugs.freedesktop.org/show_bug.cgi?id=7245 */
331 cairo_arc (cr2, 250.0, 250.0, 157.0, 5.147, 3.432);
332 cairo_set_line_width (cr2, 154.0);
333 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS, 16, 38, 468, 446);
334 cairo_new_path (cr2);
339 cairo_select_font_face (cr2, CAIRO_TEST_FONT_FAMILY " Sans",
340 CAIRO_FONT_SLANT_NORMAL,
341 CAIRO_FONT_WEIGHT_NORMAL);
342 cairo_set_font_size (cr2, 12);
343 cairo_text_extents (cr2, string, &extents);
344 /* double check that the two methods of measuring the text agree... */
345 cairo_scaled_font_text_extents (cairo_get_scaled_font (cr2),
347 &scaled_font_extents);
348 if (memcmp (&extents, &scaled_font_extents, sizeof (extents))) {
349 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",
350 scaled_font_extents.x_bearing,
351 scaled_font_extents.y_bearing,
352 scaled_font_extents.width,
353 scaled_font_extents.height,
361 cairo_move_to (cr2, -extents.x_bearing, -extents.y_bearing);
362 cairo_text_path (cr2, string);
363 cairo_set_line_width (cr2, 2.0);
364 /* XXX: We'd like to be able to use EQUALS here, but currently
365 * when hinting is enabled freetype returns integer extents. See
366 * http://cairographics.org/todo */
367 errors += !check_extents (ctx, phase, cr2, FILL, APPROX_EQUALS,
368 0, 0, extents.width, extents.height);
369 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS,
370 -1, -1, extents.width+2, extents.height+2);
371 errors += !check_extents (ctx, phase, cr2, PATH, APPROX_EQUALS,
372 0, 0, extents.width, extents.height);
373 cairo_new_path (cr2);
376 phase = "User space, simple scale, getting extents with same transform";
378 cairo_scale (cr2, 2, 2);
379 cairo_rectangle (cr2, 5, 5, 40, 40);
380 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 5, 5, 40, 40);
381 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 50, 50);
382 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 5, 5, 40, 40);
383 cairo_new_path (cr2);
386 phase = "User space, simple scale, getting extents with no transform";
389 cairo_scale (cr2, 2, 2);
390 cairo_rectangle (cr2, 5, 5, 40, 40);
392 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 80, 80);
393 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 5, 5, 90, 90);
394 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 80, 80);
395 cairo_new_path (cr2);
398 phase = "User space, rotation, getting extents with transform";
400 cairo_rectangle (cr2, -50, -50, 50, 50);
401 cairo_rotate (cr2, -M_PI/4);
402 /* the path in user space is now (nearly) the square rotated by
403 45 degrees about the origin. Thus its x1 and x2 are both nearly 0.
404 This should show any bugs where we just transform device-space
405 x1,y1 and x2,y2 to get the extents. */
406 /* The largest axis-aligned square inside the rotated path has
407 side lengths 50*sqrt(2), so a bit over 35 on either side of
408 the axes. With the stroke width added to the rotated path,
409 the largest axis-aligned square is a bit over 38 on either side of
411 errors += !check_extents (ctx, phase, cr2, FILL, CONTAINS, -35, -35, 35, 35);
412 errors += !check_extents (ctx, phase, cr2, STROKE, CONTAINS, -38, -38, 38, 38);
413 errors += !check_extents (ctx, phase, cr2, PATH, CONTAINS, -35, -35, 35, 35);
414 cairo_new_path (cr2);
417 status = cairo_status (cr2);
421 return cairo_test_status_from_status (ctx, status);
423 return errors == 0 ? CAIRO_TEST_SUCCESS : CAIRO_TEST_FAILURE;
426 CAIRO_TEST (get_path_extents,
427 "Test cairo_fill_extents and cairo_stroke_extents",
428 "extents, path", /* keywords */
429 NULL, /* requirements */