1 /*
2 * Copyright © 2006 Novell, Inc.
3 *
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.
14 *
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.
22 *
23 * Author: Robert O'Callahan <rocallahan@novell.com>
24 */
25
26 #include "cairo-test.h"
27 #include <stddef.h>
28 #include <math.h>
29
30 enum ExtentsType { FILL, STROKE, PATH };
31
32 enum Relation { EQUALS, APPROX_EQUALS, CONTAINS };
33
34
within_tolerance(double x1,double y1,double x2,double y2,double expected_x1,double expected_y1,double expected_x2,double expected_y2,double tolerance)35 static cairo_bool_t within_tolerance(double x1, double y1,
36 double x2, double y2,
37 double expected_x1, double expected_y1,
38 double expected_x2, double expected_y2,
39 double tolerance)
40 {
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);
45 }
46
47 static cairo_bool_t
check_extents(const cairo_test_context_t * ctx,const char * message,cairo_t * cr,enum ExtentsType type,enum Relation relation,double x,double y,double width,double height)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)
52 {
53 double ext_x1, ext_y1, ext_x2, ext_y2;
54 const char *type_string;
55 const char *relation_string;
56
57 switch (type) {
58 default:
59 case FILL:
60 type_string = "fill";
61 cairo_fill_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
62 break;
63 case STROKE:
64 type_string = "stroke";
65 cairo_stroke_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
66 break;
67 case PATH:
68 type_string = "path";
69 cairo_path_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
70 break;
71 }
72
73 /* ignore results after an error occurs */
74 if (cairo_status (cr))
75 return 1;
76
77 switch (relation) {
78 default:
79 case EQUALS:
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)))
84 return 1;
85 break;
86 case APPROX_EQUALS:
87 relation_string = "approx. equal";
88 if (within_tolerance(x, y, x + width, y + height,
89 ext_x1, ext_y1, ext_x2, ext_y2,
90 1.))
91 return 1;
92 break;
93 case CONTAINS:
94 relation_string = "contain";
95 if (width == 0 || height == 0) {
96 /* odd test that doesn't really test anything... */
97 return 1;
98 }
99 if (ext_x1 <= x && ext_y1 <= y && ext_x2 >= x + width && ext_y2 >= y + height)
100 return 1;
101 break;
102 }
103
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,
107 relation_string,
108 x, y, width, height);
109 return 0;
110 }
111
112 static cairo_test_status_t
draw(cairo_t * cr,int width,int height)113 draw (cairo_t *cr, int width, int height)
114 {
115 const cairo_test_context_t *ctx = cairo_test_get_context (cr);
116 cairo_surface_t *surface;
117 cairo_t *cr2;
118 const char *phase;
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;
122 int errors = 0;
123
124 surface = cairo_surface_create_similar (cairo_get_group_target (cr),
125 CAIRO_CONTENT_COLOR, 1000, 1000);
126 /* don't use cr accidentally */
127 cr = NULL;
128 cr2 = cairo_create (surface);
129 cairo_surface_destroy (surface);
130
131 cairo_set_line_width (cr2, 10);
132 cairo_set_line_join (cr2, CAIRO_LINE_JOIN_MITER);
133 cairo_set_miter_limit (cr2, 100);
134
135 phase = "No path";
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);
139
140 cairo_save (cr2);
141
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);
149
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);
157
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);
165
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);
172
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);
179
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);
186
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);
193
194 cairo_new_path (cr2);
195 cairo_restore (cr2);
196
197 /* Test that with CAIRO_LINE_CAP_ROUND, we get "dots" from
198 * cairo_move_to; cairo_rel_line_to(0,0) */
199 cairo_save (cr2);
200
201 cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
202 cairo_set_line_width (cr2, 20);
203
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, EQUALS, 190, 390, 20, 20);
209 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 200, 400, 0, 0);
210
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, EQUALS, 90, 390, 120, 120);
217 errors += !check_extents (ctx, phase, cr2, PATH, EQUALS, 100, 400, 100, 100);
218
219 cairo_new_path (cr2);
220
221 cairo_restore (cr2);
222
223 /* https://bugs.freedesktop.org/show_bug.cgi?id=7965 */
224 phase = "A horizontal, open path";
225 cairo_save (cr2);
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);
234 cairo_restore (cr2);
235
236 phase = "A vertical, open path";
237 cairo_save (cr2);
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);
247 cairo_restore (cr2);
248
249 phase = "A degenerate open path";
250 cairo_save (cr2);
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);
260 cairo_restore (cr2);
261
262 phase = "Simple rect";
263 cairo_save (cr2);
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);
269 cairo_restore (cr2);
270
271 phase = "Two rects";
272 cairo_save (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);
279 cairo_restore (cr2);
280
281 phase = "Triangle";
282 cairo_save (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);
293 cairo_restore (cr2);
294
295 cairo_save (cr2);
296
297 cairo_set_line_width (cr2, 4);
298
299 cairo_rectangle (cr2, 10, 10, 30, 30);
300 cairo_rectangle (cr2, 25, 10, 15, 30);
301
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);
307
308 /* Test other fill rule with the same path. */
309
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);
315
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);
321
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);
327
328 /* Test other fill rule with the same path. */
329
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);
335
336 cairo_new_path (cr2);
337
338 cairo_restore (cr2);
339
340 /* https://bugs.freedesktop.org/show_bug.cgi?id=7245 */
341 phase = "Arc";
342 cairo_save (cr2);
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);
347 cairo_restore (cr2);
348
349 phase = "Text";
350 cairo_save (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),
358 string,
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,
366 extents.x_bearing,
367 extents.y_bearing,
368 extents.width,
369 extents.height);
370 errors++;
371 }
372
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 * https://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);
386 cairo_restore (cr2);
387
388 phase = "User space, simple scale, getting extents with same transform";
389 cairo_save (cr2);
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);
396 cairo_restore (cr2);
397
398 phase = "User space, simple scale, getting extents with no transform";
399 cairo_save (cr2);
400 cairo_save (cr2);
401 cairo_scale (cr2, 2, 2);
402 cairo_rectangle (cr2, 5, 5, 40, 40);
403 cairo_restore (cr2);
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);
408 cairo_restore (cr2);
409
410 phase = "User space, rotation, getting extents with transform";
411 cairo_save (cr2);
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
422 the axes. */
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);
427 cairo_restore (cr2);
428
429 status = cairo_status (cr2);
430 cairo_destroy (cr2);
431
432 if (status)
433 return cairo_test_status_from_status (ctx, status);
434
435 return errors == 0 ? CAIRO_TEST_SUCCESS : CAIRO_TEST_FAILURE;
436 }
437
438 CAIRO_TEST (get_path_extents,
439 "Test cairo_fill_extents and cairo_stroke_extents",
440 "extents, path", /* keywords */
441 NULL, /* requirements */
442 0, 0,
443 NULL, draw)
444