Opensource Compliance Issue.
[platform/core/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
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
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
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, APPROX_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, APPROX_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     /* http://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     /* http://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      * 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);
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)