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, 100, 100);
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_rel_line_to (cr2, 0., 0.);
133 phase = "Degenerate line";
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_curve_to (cr2, 0., 0., 0., 0., 0., 0.);
141 phase = "Degenerate curve";
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_arc (cr2, 200, 400, 0., 0, 2 * M_PI);
148 phase = "Degenerate arc (R=0)";
149 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
150 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
151 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
153 cairo_new_path (cr2);
154 cairo_arc_negative (cr2, 200, 400, 0., 0, 2 * M_PI);
155 phase = "Degenerate negative arc (R=0)";
156 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
157 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
158 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
160 cairo_new_path (cr2);
161 cairo_arc (cr2, 200, 400, 10., 0, 0);
162 phase = "Degenerate arc (Θ=0)";
163 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
164 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
165 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 210, 400, 0, 0);
167 cairo_new_path (cr2);
168 cairo_arc_negative (cr2, 200, 400, 10., 0, 0);
169 phase = "Degenerate negative arc (Θ=0)";
170 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
171 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 0, 0);
172 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 210, 400, 0, 0);
174 cairo_new_path (cr2);
177 /* Test that with CAIRO_LINE_CAP_ROUND, we get "dots" from
178 * cairo_move_to; cairo_rel_line_to(0,0) */
181 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
182 cairo_set_line_width (cr2, 20);
184 cairo_move_to (cr2, 200, 400);
185 cairo_rel_line_to (cr2, 0, 0);
186 phase = "Single 'dot'";
187 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
188 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 190, 390, 20, 20);
189 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
191 /* Add another dot without starting a new path */
192 cairo_move_to (cr2, 100, 500);
193 cairo_rel_line_to (cr2, 0, 0);
194 phase = "Multiple 'dots'";
195 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
196 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 90, 390, 120, 120);
197 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 100, 400, 100, 100);
199 cairo_new_path (cr2);
203 /* http://bugs.freedesktop.org/show_bug.cgi?id=7965 */
204 phase = "A horizontal, open path";
206 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
207 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_ROUND);
208 cairo_move_to (cr2, 0, 180);
209 cairo_line_to (cr2, 750, 180);
210 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
211 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, -5, 175, 760, 10);
212 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 0, 180, 750, 0);
213 cairo_new_path (cr2);
216 phase = "A vertical, open path";
218 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
219 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_ROUND);
220 cairo_new_path (cr2);
221 cairo_move_to (cr2, 180, 0);
222 cairo_line_to (cr2, 180, 750);
223 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
224 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 175, -5, 10, 760);
225 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 180, 0, 0, 750);
226 cairo_new_path (cr2);
229 phase = "A degenerate open path";
231 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
232 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_ROUND);
233 cairo_new_path (cr2);
234 cairo_move_to (cr2, 180, 0);
235 cairo_line_to (cr2, 180, 0);
236 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 0, 0, 0, 0);
237 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 175, -5, 10, 10);
238 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 180, 0, 0, 0);
239 cairo_new_path (cr2);
242 phase = "Simple rect";
244 cairo_rectangle (cr2, 10, 10, 80, 80);
245 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 80, 80);
246 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 5, 5, 90, 90);
247 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 80, 80);
248 cairo_new_path (cr2);
253 cairo_rectangle (cr2, 10, 10, 10, 10);
254 cairo_rectangle (cr2, 20, 20, 10, 10);
255 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 20, 20);
256 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 5, 5, 30, 30);
257 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 20, 20);
258 cairo_new_path (cr2);
263 cairo_move_to (cr2, 10, 10);
264 cairo_line_to (cr2, 90, 90);
265 cairo_line_to (cr2, 90, 10);
266 cairo_close_path (cr2);
267 /* miter joins protrude 5*(1+sqrt(2)) above the top-left corner and to
268 the right of the bottom-right corner */
269 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 80, 80);
270 errors += !check_extents (ctx, phase, cr2, STROKE, CONTAINS, 0, 5, 95, 95);
271 errors += !check_extents (ctx, phase, cr2, PATH, CONTAINS, 10, 10, 80, 80);
272 cairo_new_path (cr2);
277 cairo_set_line_width (cr2, 4);
279 cairo_rectangle (cr2, 10, 10, 30, 30);
280 cairo_rectangle (cr2, 25, 10, 15, 30);
282 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_EVEN_ODD);
283 phase = "EVEN_ODD overlapping rectangles";
284 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 15, 30);
285 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 8, 8, 34, 34);
286 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 30, 30);
288 /* Test other fill rule with the same path. */
290 cairo_set_fill_rule (cr2, CAIRO_FILL_RULE_WINDING);
291 phase = "WINDING overlapping rectangles";
292 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 30, 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 /* Now, change the direction of the second rectangle and test both
297 * fill rules again. */
298 cairo_new_path (cr2);
299 cairo_rectangle (cr2, 10, 10, 30, 30);
300 cairo_rectangle (cr2, 25, 40, 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, 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 cairo_new_path (cr2);
320 /* http://bugs.freedesktop.org/show_bug.cgi?id=7245 */
323 cairo_arc (cr2, 250.0, 250.0, 157.0, 5.147, 3.432);
324 cairo_set_line_width (cr2, 154.0);
325 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS, 16, 38, 468, 446);
326 cairo_new_path (cr2);
331 cairo_select_font_face (cr2, CAIRO_TEST_FONT_FAMILY " Sans",
332 CAIRO_FONT_SLANT_NORMAL,
333 CAIRO_FONT_WEIGHT_NORMAL);
334 cairo_set_font_size (cr2, 12);
335 cairo_text_extents (cr2, string, &extents);
336 /* double check that the two methods of measuring the text agree... */
337 cairo_scaled_font_text_extents (cairo_get_scaled_font (cr2),
339 &scaled_font_extents);
340 if (memcmp (&extents, &scaled_font_extents, sizeof (extents))) {
341 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",
342 scaled_font_extents.x_bearing,
343 scaled_font_extents.y_bearing,
344 scaled_font_extents.width,
345 scaled_font_extents.height,
353 cairo_move_to (cr2, -extents.x_bearing, -extents.y_bearing);
354 cairo_text_path (cr2, string);
355 cairo_set_line_width (cr2, 2.0);
356 /* XXX: We'd like to be able to use EQUALS here, but currently
357 * when hinting is enabled freetype returns integer extents. See
358 * http://cairographics.org/todo */
359 errors += !check_extents (ctx, phase, cr2, FILL, APPROX_EQUALS,
360 0, 0, extents.width, extents.height);
361 errors += !check_extents (ctx, phase, cr2, STROKE, APPROX_EQUALS,
362 -1, -1, extents.width+2, extents.height+2);
363 errors += !check_extents (ctx, phase, cr2, PATH, APPROX_EQUALS,
364 0, 0, extents.width, extents.height);
365 cairo_new_path (cr2);
368 phase = "User space, simple scale, getting extents with same transform";
370 cairo_scale (cr2, 2, 2);
371 cairo_rectangle (cr2, 5, 5, 40, 40);
372 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 5, 5, 40, 40);
373 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 0, 0, 50, 50);
374 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 5, 5, 40, 40);
375 cairo_new_path (cr2);
378 phase = "User space, simple scale, getting extents with no transform";
381 cairo_scale (cr2, 2, 2);
382 cairo_rectangle (cr2, 5, 5, 40, 40);
384 errors += !check_extents (ctx, phase, cr2, FILL, EQUALS, 10, 10, 80, 80);
385 errors += !check_extents (ctx, phase, cr2, STROKE, EQUALS, 5, 5, 90, 90);
386 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 10, 10, 80, 80);
387 cairo_new_path (cr2);
390 phase = "User space, rotation, getting extents with transform";
392 cairo_rectangle (cr2, -50, -50, 50, 50);
393 cairo_rotate (cr2, -M_PI/4);
394 /* the path in user space is now (nearly) the square rotated by
395 45 degrees about the origin. Thus its x1 and x2 are both nearly 0.
396 This should show any bugs where we just transform device-space
397 x1,y1 and x2,y2 to get the extents. */
398 /* The largest axis-aligned square inside the rotated path has
399 side lengths 50*sqrt(2), so a bit over 35 on either side of
400 the axes. With the stroke width added to the rotated path,
401 the largest axis-aligned square is a bit over 38 on either side of
403 errors += !check_extents (ctx, phase, cr2, FILL, CONTAINS, -35, -35, 35, 35);
404 errors += !check_extents (ctx, phase, cr2, STROKE, CONTAINS, -38, -38, 38, 38);
405 errors += !check_extents (ctx, phase, cr2, PATH, CONTAINS, -35, -35, 35, 35);
406 cairo_new_path (cr2);
409 status = cairo_status (cr2);
413 return cairo_test_status_from_status (ctx, status);
415 return errors == 0 ? CAIRO_TEST_SUCCESS : CAIRO_TEST_FAILURE;
418 CAIRO_TEST (get_path_extents,
419 "Test cairo_fill_extents and cairo_stroke_extents",
420 "extents, path", /* keywords */
421 NULL, /* requirements */