Tizen 2.0 Release
[framework/graphics/cairo.git] / test / get-path-extents.c
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 static cairo_bool_t
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)
39 {
40     double ext_x1, ext_y1, ext_x2, ext_y2;
41     const char *type_string;
42     const char *relation_string;
43
44     switch (type) {
45     default:
46     case FILL:
47         type_string = "fill";
48         cairo_fill_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
49         break;
50     case STROKE:
51         type_string = "stroke";
52         cairo_stroke_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
53         break;
54     case PATH:
55         type_string = "path";
56         cairo_path_extents (cr, &ext_x1, &ext_y1, &ext_x2, &ext_y2);
57         break;
58     }
59
60     /* ignore results after an error occurs */
61     if (cairo_status (cr))
62         return 1;
63
64     switch (relation) {
65     default:
66     case EQUALS:
67         relation_string = "equal";
68         if (ext_x1 == x && ext_y1 == y && ext_x2 == x + width && ext_y2 == y + height)
69             return 1;
70         break;
71     case APPROX_EQUALS:
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.)
77         {
78             return 1;
79         }
80         break;
81     case CONTAINS:
82         relation_string = "contain";
83         if (width == 0 || height == 0) {
84             /* odd test that doesn't really test anything... */
85             return 1;
86         }
87         if (ext_x1 <= x && ext_y1 <= y && ext_x2 >= x + width && ext_y2 >= y + height)
88             return 1;
89         break;
90     }
91
92     cairo_test_log (ctx, "Error: %s; %s extents (%g, %g) x (%g, %g) should %s (%g, %g) x (%g, %g)\n",
93                     message, type_string,
94                     ext_x1, ext_y1, ext_x2 - ext_x1, ext_y2 - ext_y1,
95                     relation_string,
96                     x, y, width, height);
97     return 0;
98 }
99
100 static cairo_test_status_t
101 draw (cairo_t *cr, int width, int height)
102 {
103     const cairo_test_context_t *ctx = cairo_test_get_context (cr);
104     cairo_surface_t *surface;
105     cairo_t         *cr2;
106     const char      *phase;
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;
110     int              errors = 0;
111
112     surface = cairo_surface_create_similar (cairo_get_group_target (cr),
113                                             CAIRO_CONTENT_COLOR, 1000, 1000);
114     /* don't use cr accidentally */
115     cr = NULL;
116     cr2 = cairo_create (surface);
117     cairo_surface_destroy (surface);
118
119     cairo_set_line_width (cr2, 10);
120     cairo_set_line_join (cr2, CAIRO_LINE_JOIN_MITER);
121     cairo_set_miter_limit (cr2, 100);
122
123     phase = "No path";
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);
127
128     cairo_save (cr2);
129
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);
137
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);
145
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);
153
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);
160
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);
167
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);
174
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);
181
182     cairo_new_path (cr2);
183     cairo_restore (cr2);
184
185     /* Test that with CAIRO_LINE_CAP_ROUND, we get "dots" from
186      * cairo_move_to; cairo_rel_line_to(0,0) */
187     cairo_save (cr2);
188
189     cairo_set_line_cap (cr2, CAIRO_LINE_CAP_ROUND);
190     cairo_set_line_width (cr2, 20);
191
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);
198
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);
206
207     cairo_new_path (cr2);
208
209     cairo_restore (cr2);
210
211     /* http://bugs.freedesktop.org/show_bug.cgi?id=7965 */
212     phase = "A horizontal, open path";
213     cairo_save (cr2);
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);
222     cairo_restore (cr2);
223
224     phase = "A vertical, 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_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);
235     cairo_restore (cr2);
236
237     phase = "A degenerate open path";
238     cairo_save (cr2);
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);
248     cairo_restore (cr2);
249
250     phase = "Simple rect";
251     cairo_save (cr2);
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);
257     cairo_restore (cr2);
258
259     phase = "Two rects";
260     cairo_save (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);
267     cairo_restore (cr2);
268
269     phase = "Triangle";
270     cairo_save (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);
281     cairo_restore (cr2);
282
283     cairo_save (cr2);
284
285     cairo_set_line_width (cr2, 4);
286
287     cairo_rectangle (cr2, 10, 10, 30, 30);
288     cairo_rectangle (cr2, 25, 10, 15, 30);
289
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);
295
296     /* Test other fill rule with the same path. */
297
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);
303
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);
309
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);
315
316     /* Test other fill rule with the same path. */
317
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);
323
324     cairo_new_path (cr2);
325
326     cairo_restore (cr2);
327
328     /* http://bugs.freedesktop.org/show_bug.cgi?id=7245 */
329     phase = "Arc";
330     cairo_save (cr2);
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);
335     cairo_restore (cr2);
336
337     phase = "Text";
338     cairo_save (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),
346                                     string,
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,
354                         extents.x_bearing,
355                         extents.y_bearing,
356                         extents.width,
357                         extents.height);
358         errors++;
359     }
360
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);
374     cairo_restore (cr2);
375
376     phase = "User space, simple scale, getting extents with same transform";
377     cairo_save (cr2);
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);
384     cairo_restore (cr2);
385
386     phase = "User space, simple scale, getting extents with no transform";
387     cairo_save (cr2);
388     cairo_save (cr2);
389     cairo_scale (cr2, 2, 2);
390     cairo_rectangle (cr2, 5, 5, 40, 40);
391     cairo_restore (cr2);
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);
396     cairo_restore (cr2);
397
398     phase = "User space, rotation, getting extents with transform";
399     cairo_save (cr2);
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
410        the axes. */
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);
415     cairo_restore (cr2);
416
417     status = cairo_status (cr2);
418     cairo_destroy (cr2);
419
420     if (status)
421         return cairo_test_status_from_status (ctx, status);
422
423     return errors == 0 ? CAIRO_TEST_SUCCESS : CAIRO_TEST_FAILURE;
424 }
425
426 CAIRO_TEST (get_path_extents,
427             "Test cairo_fill_extents and cairo_stroke_extents",
428             "extents, path", /* keywords */
429             NULL, /* requirements */
430             0, 0,
431             NULL, draw)